Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions arduino-ide-extension/src/browser/style/browser-menu.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,12 @@
.p-MenuBar-item.p-mod-active {
color: var(--theia-menubar-selectionForeground);
}

/* Fix: Tools menu items unreachable when menu overflows screen height
* Affects boards with many Tools entries e.g. RP2040 Pico core
* https://github.com/arduino/arduino-ide/issues/[ISSUE_NUMBER]
*/
.p-Menu {
max-height: calc(100vh - 50px);
overflow-y: auto;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ export class ElectronContextMenuRenderer extends TheiaElectronContextMenuRendere
contextKeyService,
this.showDisabled(options)
);
const { x, y } = coordinateFromAnchor(anchor);
let { x, y } = coordinateFromAnchor(anchor);

// Fix: Tools menu items unreachable when menu overflows screen height
// Affects boards with many Tools entries e.g. RP2040 Pico core
// https://github.com/arduino/arduino-ide/issues/[ISSUE_NUMBER]
y = this.clampMenuY(y, menu);

const menuHandle = window.electronTheiaCore.popup(menu, x, y, () => {
if (onHide) {
onHide();
Expand All @@ -38,6 +44,58 @@ export class ElectronContextMenuRenderer extends TheiaElectronContextMenuRendere
}
}

/**
* Clamp the menu Y position to ensure it doesn't overflow below the screen.
* Prevents the native OS scroll arrows from creating feedback loops that hide menu items.
*/
private clampMenuY(y: number, menu: unknown): number {
try {
// Use browser's built-in window.screen API
// Safe in renderer context — no Node.js dependencies
const availHeight = window.screen.availHeight;

// Estimate menu height: ~24px per item + 10px padding
const MENU_ITEM_HEIGHT = 24;
const MENU_PADDING = 10;
const estimatedMenuHeight =
this.countMenuItems(menu) * MENU_ITEM_HEIGHT + MENU_PADDING;

// Clamp Y so menu doesn't extend below available screen area
const clampedY = Math.min(y, availHeight - estimatedMenuHeight);
return Math.max(clampedY, 0);
} catch (error) {
console.warn('Failed to clamp menu Y position:', error);
return y;
}
}

/**
* Recursively count menu items to estimate menu height.
*/
private countMenuItems(menu: unknown): number {
try {
// Safety check: ensure menu is an array
if (!Array.isArray(menu)) {
return 0;
}

return menu.reduce((count, item) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const menuItem = item as any;
if (menuItem && menuItem.type === 'separator') {
return count + 1;
}
if (menuItem && Array.isArray(menuItem.submenu)) {
return count + 1 + this.countMenuItems(menuItem.submenu);
}
return count + 1;
}, 0);
} catch (error) {
console.warn('Failed to count menu items:', error);
return 0;
}
}

/**
* Theia does not allow selectively control whether disabled menu items are visible or not. This is a workaround.
* Attach the `showDisabled: true` to the `RenderContextMenuOptions` object, and you can control it.
Expand Down
Loading