diff --git a/arduino-ide-extension/src/browser/style/browser-menu.css b/arduino-ide-extension/src/browser/style/browser-menu.css index 85fb053a830..0a8de37c0b9 100644 --- a/arduino-ide-extension/src/browser/style/browser-menu.css +++ b/arduino-ide-extension/src/browser/style/browser-menu.css @@ -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; +} diff --git a/arduino-ide-extension/src/electron-browser/theia/core/electron-context-menu-renderer.ts b/arduino-ide-extension/src/electron-browser/theia/core/electron-context-menu-renderer.ts index d594d01da29..3ad5ecb61a6 100644 --- a/arduino-ide-extension/src/electron-browser/theia/core/electron-context-menu-renderer.ts +++ b/arduino-ide-extension/src/electron-browser/theia/core/electron-context-menu-renderer.ts @@ -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(); @@ -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.