From aa33d1adc5ba41a5bd22a6dc7d45defb36824caa Mon Sep 17 00:00:00 2001 From: Esther Perelman Date: Mon, 2 Aug 2021 09:23:14 +0300 Subject: [PATCH] Fix #9779: add support for window.menuBarVisibility Signed-off-by: Esther Perelman --- CHANGELOG.md | 4 ++ .../sample-updater-frontend-contribution.ts | 2 +- .../browser/common-frontend-contribution.ts | 48 ++++++++++++---- packages/core/src/browser/core-preferences.ts | 17 ++++++ .../browser/frontend-application-module.ts | 6 +- .../src/browser/menu/browser-menu-plugin.ts | 30 ++++++++-- .../src/browser/shell/application-shell.ts | 6 +- packages/core/src/browser/shell/index.ts | 2 +- .../src/browser/shell/side-panel-handler.ts | 44 ++++++++++++--- ...enu-widget.tsx => sidebar-menu-widget.tsx} | 26 ++++----- .../src/browser/shell/theia-dock-panel.ts | 40 +++++++++++++- packages/core/src/browser/style/index.css | 4 ++ packages/core/src/browser/style/sidepanel.css | 12 ++-- .../common/preferences/preference-schema.ts | 1 + .../menu/electron-main-menu-factory.ts | 55 ++++++++++++------- .../menu/electron-menu-contribution.ts | 2 +- 16 files changed, 227 insertions(+), 72 deletions(-) rename packages/core/src/browser/shell/{sidebar-bottom-menu-widget.tsx => sidebar-menu-widget.tsx} (78%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53b42736e9425..a48ca26674307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,10 @@ - [debug] `DebugSession` and `PluginDebugSession` constructors accept a `parentSession` of type `DebugSession | undefined` as their 3rd parameter, offsetting every subsequent parameter by one [#9613](https://github.com/eclipse-theia/theia/pull/9613) - [monaco] upgraded to monaco 0.23.0 including replacement of `quickOpen` API (0.20.x) with `quickInput` API (0.23.x) [#9154](https://github.com/eclipse-theia/theia/pull/9154) - [workspace] `WorkspaceCommandContribution.addFolderToWorkspace` no longer accepts `undefined`. `WorkspaceService.addRoot` now accepts a `URI` or a `URI[]` [#9684](https://github.com/eclipse-theia/theia/pull/9684) +-[core] `SidePanelHandler.addMenu` and `SidePanelHandler.removeMenu` no longer exists, instead added `addBottomMenu` and `addTopMenu` for adding menu, `removeTopMenu` and `removeBottomMenu` for removing menu. +`SidebarBottomMenu` interface is renamed `SidebarMenu` and handles not only bottom menu's. +`packages/core/src/browser/shell/sidebar-bottom-menu-widget.tsx` file is renamed `packages/core/src/browser/shell/sidebar-menu-widget.tsx`. `SidebarBottomMenuWidget` class is renamed `SidebarMenuWidget`. changed style class name from `theia-sidebar-bottom-menu` to `theia-sidebar-menu` + [#9830](https://github.com/eclipse-theia/theia/pull/9830) ## v1.15.0 - 6/30/2021 diff --git a/examples/api-samples/src/electron-browser/updater/sample-updater-frontend-contribution.ts b/examples/api-samples/src/electron-browser/updater/sample-updater-frontend-contribution.ts index 9c9bff21aa780..ed4baca3c1231 100644 --- a/examples/api-samples/src/electron-browser/updater/sample-updater-frontend-contribution.ts +++ b/examples/api-samples/src/electron-browser/updater/sample-updater-frontend-contribution.ts @@ -90,7 +90,7 @@ export class ElectronMenuUpdater { this.setMenu(); } - private setMenu(menu: Menu = this.factory.createMenuBar(), electronWindow: BrowserWindow = remote.getCurrentWindow()): void { + private setMenu(menu: Menu | null = this.factory.createMenuBar(), electronWindow: BrowserWindow = remote.getCurrentWindow()): void { if (isOSX) { remote.Menu.setApplicationMenu(menu); } else { diff --git a/packages/core/src/browser/common-frontend-contribution.ts b/packages/core/src/browser/common-frontend-contribution.ts index 3abcb7012c0ab..4b9a7c58c5d8e 100644 --- a/packages/core/src/browser/common-frontend-contribution.ts +++ b/packages/core/src/browser/common-frontend-contribution.ts @@ -43,9 +43,9 @@ import { environment } from '@theia/application-package/lib/environment'; import { IconThemeService } from './icon-theme-service'; import { ColorContribution } from './color-application-contribution'; import { ColorRegistry, Color } from './color-registry'; -import { CorePreferences } from './core-preferences'; +import { CoreConfiguration, CorePreferences } from './core-preferences'; import { ThemeService } from './theming'; -import { PreferenceService, PreferenceScope } from './preferences'; +import { PreferenceService, PreferenceScope, PreferenceChangeEvent } from './preferences'; import { ClipboardService } from './clipboard-service'; import { EncodingRegistry } from './encoding-registry'; import { UTF8 } from '../common/encodings'; @@ -353,17 +353,11 @@ export class CommonFrontendContribution implements FrontendApplicationContributi this.updateStyles(); this.updateThemeFromPreference('workbench.colorTheme'); this.updateThemeFromPreference('workbench.iconTheme'); - this.preferences.onPreferenceChanged(e => { - if (e.preferenceName === 'workbench.editor.highlightModifiedTabs') { - this.updateStyles(); - } else if (e.preferenceName === 'workbench.colorTheme' || e.preferenceName === 'workbench.iconTheme') { - this.updateThemeFromPreference(e.preferenceName); - } - }); + this.preferences.onPreferenceChanged(e => this.handlePreferenceChange(e, app)); this.themeService.onDidColorThemeChange(() => this.updateThemePreference('workbench.colorTheme')); this.iconThemes.onDidChangeCurrent(() => this.updateThemePreference('workbench.iconTheme')); - app.shell.leftPanelHandler.addMenu({ + app.shell.leftPanelHandler.addBottomMenu({ id: 'settings-menu', iconClass: 'codicon codicon-settings-gear', title: 'Settings', @@ -378,11 +372,11 @@ export class CommonFrontendContribution implements FrontendApplicationContributi order: 1, }; this.authenticationService.onDidRegisterAuthenticationProvider(() => { - app.shell.leftPanelHandler.addMenu(accountsMenu); + app.shell.leftPanelHandler.addBottomMenu(accountsMenu); }); this.authenticationService.onDidUnregisterAuthenticationProvider(() => { if (this.authenticationService.getProviderIds().length === 0) { - app.shell.leftPanelHandler.removeMenu(accountsMenu.id); + app.shell.leftPanelHandler.removeBottomMenu(accountsMenu.id); } }); } @@ -420,6 +414,36 @@ export class CommonFrontendContribution implements FrontendApplicationContributi } } + protected handlePreferenceChange(e: PreferenceChangeEvent, app: FrontendApplication): void { + switch (e.preferenceName) { + case 'workbench.editor.highlightModifiedTabs': { + this.updateStyles(); + break; + } + case 'workbench.colorTheme': + case 'workbench.iconTheme': { + this.updateThemeFromPreference(e.preferenceName); + break; + } + case 'window.menuBarVisibility': { + const { newValue } = e; + const mainMenuId = 'main-menu'; + if (newValue === 'compact') { + app.shell.leftPanelHandler.addTopMenu({ + id: mainMenuId, + iconClass: 'codicon codicon-menu', + title: 'Application Menu', + menuPath: ['menubar'], + order: 0, + }); + } else { + app.shell.leftPanelHandler.removeTopMenu(mainMenuId); + } + break; + } + } + } + onStart(): void { this.storageService.getData<{ recent: Command[] }>(RECENT_COMMANDS_STORAGE_KEY, { recent: [] }) .then(tasks => this.commandRegistry.recent = tasks.recent); diff --git a/packages/core/src/browser/core-preferences.ts b/packages/core/src/browser/core-preferences.ts index 1888d91d0438d..ebc148caa8df7 100644 --- a/packages/core/src/browser/core-preferences.ts +++ b/packages/core/src/browser/core-preferences.ts @@ -18,6 +18,7 @@ import { interfaces } from 'inversify'; import { createPreferenceProxy, PreferenceProxy, PreferenceService, PreferenceContribution, PreferenceSchema } from './preferences'; import { SUPPORTED_ENCODINGS } from './supported-encodings'; import { FrontendApplicationConfigProvider } from './frontend-application-config-provider'; +import { isOSX } from '../common/os'; export const corePreferenceSchema: PreferenceSchema = { 'type': 'object', @@ -97,6 +98,21 @@ export const corePreferenceSchema: PreferenceSchema = { default: 'code', description: 'Whether to interpret keypresses by the `code` of the physical key, or by the `keyCode` provided by the OS.' }, + 'window.menuBarVisibility': { + type: 'string', + enum: ['classic', 'visible', 'hidden', 'compact'], + markdownEnumDescriptions: [ + 'Menu is displayed at the top of the window and only hidden in full screen mode.', + 'Menu is always visible at the top of the window even in full screen mode.', + 'Menu is always hidden.', + 'Menu is displayed as a compact button in the sidebar.' + ], + default: 'classic', + scope: 'application', + markdownDescription: `Control the visibility of the menu bar. + A setting of 'compact' will move the menu into the sidebar.`, + included: !isOSX + }, } }; @@ -112,6 +128,7 @@ export interface CoreConfiguration { 'workbench.silentNotifications': boolean; 'files.encoding': string 'workbench.tree.renderIndentGuides': 'onHover' | 'none' | 'always'; + 'window.menuBarVisibility': 'classic' | 'visible' | 'hidden' | 'compact'; } export const CorePreferences = Symbol('CorePreferences'); diff --git a/packages/core/src/browser/frontend-application-module.ts b/packages/core/src/browser/frontend-application-module.ts index 8ab0b4dd709fc..9283ce978f2fa 100644 --- a/packages/core/src/browser/frontend-application-module.ts +++ b/packages/core/src/browser/frontend-application-module.ts @@ -42,7 +42,7 @@ import { ApplicationShell, ApplicationShellOptions, DockPanelRenderer, TabBarRenderer, TabBarRendererFactory, ShellLayoutRestorer, SidePanelHandler, SidePanelHandlerFactory, - SidebarBottomMenuWidget, SidebarBottomMenuWidgetFactory, + SidebarMenuWidget, SidebarMenuWidgetFactory, SplitPositionHandler, DockPanelRendererFactory, ApplicationShellLayoutMigration, ApplicationShellLayoutMigrationError } from './shell'; import { StatusBar, StatusBarImpl } from './status-bar/status-bar'; @@ -131,8 +131,8 @@ export const frontendApplicationModule = new ContainerModule((bind, unbind, isBo bind(ApplicationShell).toSelf().inSingletonScope(); bind(SidePanelHandlerFactory).toAutoFactory(SidePanelHandler); bind(SidePanelHandler).toSelf(); - bind(SidebarBottomMenuWidgetFactory).toAutoFactory(SidebarBottomMenuWidget); - bind(SidebarBottomMenuWidget).toSelf(); + bind(SidebarMenuWidgetFactory).toAutoFactory(SidebarMenuWidget); + bind(SidebarMenuWidget).toSelf(); bind(SplitPositionHandler).toSelf().inSingletonScope(); bindContributionProvider(bind, TabBarToolbarContribution); diff --git a/packages/core/src/browser/menu/browser-menu-plugin.ts b/packages/core/src/browser/menu/browser-menu-plugin.ts index 9187a01355430..3549ba9b66a5f 100644 --- a/packages/core/src/browser/menu/browser-menu-plugin.ts +++ b/packages/core/src/browser/menu/browser-menu-plugin.ts @@ -27,6 +27,7 @@ import { ContextKeyService } from '../context-key-service'; import { ContextMenuContext } from './context-menu-context'; import { waitForRevealed } from '../widgets'; import { ApplicationShell } from '../shell'; +import { CorePreferences } from '../core-preferences'; export abstract class MenuBarWidget extends MenuBar { abstract activateMenu(label: string, ...labels: string[]): Promise; @@ -45,6 +46,9 @@ export class BrowserMainMenuFactory implements MenuWidgetFactory { @inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry; + @inject(CorePreferences) + protected readonly corePreferences: CorePreferences; + @inject(KeybindingRegistry) protected readonly keybindingRegistry: KeybindingRegistry; @@ -54,15 +58,31 @@ export class BrowserMainMenuFactory implements MenuWidgetFactory { createMenuBar(): MenuBarWidget { const menuBar = new DynamicMenuBarWidget(); menuBar.id = 'theia:menubar'; - this.fillMenuBar(menuBar); - const listener = this.keybindingRegistry.onKeybindingsChanged(() => { - menuBar.clearMenus(); - this.fillMenuBar(menuBar); + const preferenceListener = this.corePreferences.onPreferenceChanged(preference => { + if (preference.preferenceName === 'window.menuBarVisibility') { + this.showMenuBar(menuBar, preference.newValue); + } + }); + const keybindingListener = this.keybindingRegistry.onKeybindingsChanged(() => { + const preference = this.corePreferences['window.menuBarVisibility']; + this.showMenuBar(menuBar, preference); + }); + menuBar.disposed.connect(() => { + preferenceListener.dispose(); + keybindingListener.dispose(); }); - menuBar.disposed.connect(() => listener.dispose()); return menuBar; } + protected showMenuBar(menuBar: DynamicMenuBarWidget, preference: string | undefined): void { + if (preference && ['classic', 'visible'].includes(preference)) { + menuBar.clearMenus(); + this.fillMenuBar(menuBar); + } else { + menuBar.clearMenus(); + } + } + protected fillMenuBar(menuBar: MenuBarWidget): void { const menuModel = this.menuProvider.getMenu(MAIN_MENU_BAR); const menuCommandRegistry = this.createMenuCommandRegistry(menuModel); diff --git a/packages/core/src/browser/shell/application-shell.ts b/packages/core/src/browser/shell/application-shell.ts index 39f4455bd9e83..b8ab89d925b58 100644 --- a/packages/core/src/browser/shell/application-shell.ts +++ b/packages/core/src/browser/shell/application-shell.ts @@ -36,6 +36,7 @@ import { TabBarToolbarRegistry, TabBarToolbarFactory, TabBarToolbar } from './ta import { ContextKeyService } from '../context-key-service'; import { Emitter } from '../../common/event'; import { waitForRevealed, waitForClosed } from '../widgets'; +import { CorePreferences } from '../core-preferences'; /** The class name added to ApplicationShell instances. */ const APPLICATION_SHELL_CLASS = 'theia-ApplicationShell'; @@ -204,7 +205,8 @@ export class ApplicationShell extends Widget { @inject(SidePanelHandlerFactory) sidePanelHandlerFactory: () => SidePanelHandler, @inject(SplitPositionHandler) protected splitPositionHandler: SplitPositionHandler, @inject(FrontendApplicationStateService) protected readonly applicationStateService: FrontendApplicationStateService, - @inject(ApplicationShellOptions) @optional() options: RecursivePartial = {} + @inject(ApplicationShellOptions) @optional() options: RecursivePartial = {}, + @inject(CorePreferences) protected readonly corePreferences: CorePreferences ) { super(options as Widget.IOptions); this.addClass(APPLICATION_SHELL_CLASS); @@ -448,7 +450,7 @@ export class ApplicationShell extends Widget { mode: 'multiple-document', renderer, spacing: 0 - }); + }, this.corePreferences); dockPanel.id = MAIN_AREA_ID; dockPanel.widgetAdded.connect((_, widget) => this.fireDidAddWidget(widget)); dockPanel.widgetRemoved.connect((_, widget) => this.fireDidRemoveWidget(widget)); diff --git a/packages/core/src/browser/shell/index.ts b/packages/core/src/browser/shell/index.ts index b376c3c672c70..c24c9f758e3b1 100644 --- a/packages/core/src/browser/shell/index.ts +++ b/packages/core/src/browser/shell/index.ts @@ -17,7 +17,7 @@ export * from './application-shell'; export * from './shell-layout-restorer'; export * from './side-panel-handler'; -export * from './sidebar-bottom-menu-widget'; +export * from './sidebar-menu-widget'; export * from './split-panels'; export * from './tab-bars'; export * from './view-contribution'; diff --git a/packages/core/src/browser/shell/side-panel-handler.ts b/packages/core/src/browser/shell/side-panel-handler.ts index 66b8cf3f03d46..7d577bbb54a03 100644 --- a/packages/core/src/browser/shell/side-panel-handler.ts +++ b/packages/core/src/browser/shell/side-panel-handler.ts @@ -21,7 +21,7 @@ import { MimeData } from '@phosphor/coreutils'; import { Drag } from '@phosphor/dragdrop'; import { AttachedProperty } from '@phosphor/properties'; import { TabBarRendererFactory, TabBarRenderer, SHELL_TABBAR_CONTEXT_MENU, SideTabBar } from './tab-bars'; -import { SidebarBottomMenuWidget, SidebarBottomMenuWidgetFactory, SidebarBottomMenu } from './sidebar-bottom-menu-widget'; +import { SidebarMenuWidget, SidebarMenuWidgetFactory, SidebarMenu } from './sidebar-menu-widget'; import { SplitPositionHandler, SplitPositionOptions } from './split-panels'; import { animationFrame } from '../browser'; import { FrontendApplicationStateService } from '../frontend-application-state'; @@ -65,12 +65,18 @@ export class SidePanelHandler { * tab bar itself remains visible as long as there is at least one widget. */ tabBar: SideTabBar; + /** + * The menu placed on the sidebar top. + * Displayed as icons. + * Open menus when on clicks. + */ + topMenu: SidebarMenuWidget; /** * The menu placed on the sidebar bottom. * Displayed as icons. * Open menus when on clicks. */ - bottomMenu: SidebarBottomMenuWidget; + bottomMenu: SidebarMenuWidget; /** * A tool bar, which displays a title and widget specific command buttons. */ @@ -107,7 +113,7 @@ export class SidePanelHandler { @inject(TabBarToolbarRegistry) protected tabBarToolBarRegistry: TabBarToolbarRegistry; @inject(TabBarToolbarFactory) protected tabBarToolBarFactory: () => TabBarToolbar; @inject(TabBarRendererFactory) protected tabBarRendererFactory: () => TabBarRenderer; - @inject(SidebarBottomMenuWidgetFactory) protected sidebarBottomWidgetFactory: () => SidebarBottomMenuWidget; + @inject(SidebarMenuWidgetFactory) protected sidebarWidgetFactory: () => SidebarMenuWidget; @inject(SplitPositionHandler) protected splitPositionHandler: SplitPositionHandler; @inject(FrontendApplicationStateService) protected readonly applicationStateService: FrontendApplicationStateService; @@ -120,8 +126,9 @@ export class SidePanelHandler { create(side: 'left' | 'right', options: SidePanel.Options): void { this.side = side; this.options = options; + this.topMenu = this.createSidebarMenu(); this.tabBar = this.createSideBar(); - this.bottomMenu = this.createSidebarBottomMenu(); + this.bottomMenu = this.createSidebarMenu(); this.toolBar = this.createToolbar(); this.dockPanel = this.createSidePanel(); this.container = this.createContainer(); @@ -187,9 +194,9 @@ export class SidePanelHandler { return toolbar; } - protected createSidebarBottomMenu(): SidebarBottomMenuWidget { - const bottomMenu = this.sidebarBottomWidgetFactory(); - bottomMenu.addClass('theia-sidebar-bottom-menu'); + protected createSidebarMenu(): SidebarMenuWidget { + const bottomMenu = this.sidebarWidgetFactory(); + bottomMenu.addClass('theia-sidebar-menu'); return bottomMenu; } @@ -232,6 +239,7 @@ export class SidePanelHandler { const sidebarContainerLayout = new PanelLayout(); const sidebarContainer = new Panel({ layout: sidebarContainerLayout }); sidebarContainer.addClass('theia-app-sidebar-container'); + sidebarContainerLayout.addWidget(this.topMenu); sidebarContainerLayout.addWidget(this.tabBar); sidebarContainerLayout.addWidget(this.bottomMenu); @@ -385,12 +393,30 @@ export class SidePanelHandler { this.dockPanel.addWidget(widget); } + /** + * Add a menu to the sidebar top. + * + * If the menu is already added, it will be ignored. + */ + addTopMenu(menu: SidebarMenu): void { + this.topMenu.addMenu(menu); + } + + /** + * Remove a menu from the sidebar top. + * + * @param menuId id of the menu to remove + */ + removeTopMenu(menuId: string): void { + this.topMenu.removeMenu(menuId); + } + /** * Add a menu to the sidebar bottom. * * If the menu is already added, it will be ignored. */ - addMenu(menu: SidebarBottomMenu): void { + addBottomMenu(menu: SidebarMenu): void { this.bottomMenu.addMenu(menu); } @@ -399,7 +425,7 @@ export class SidePanelHandler { * * @param menuId id of the menu to remove */ - removeMenu(menuId: string): void { + removeBottomMenu(menuId: string): void { this.bottomMenu.removeMenu(menuId); } diff --git a/packages/core/src/browser/shell/sidebar-bottom-menu-widget.tsx b/packages/core/src/browser/shell/sidebar-menu-widget.tsx similarity index 78% rename from packages/core/src/browser/shell/sidebar-bottom-menu-widget.tsx rename to packages/core/src/browser/shell/sidebar-menu-widget.tsx index f85573635bb87..710cdbf424990 100644 --- a/packages/core/src/browser/shell/sidebar-bottom-menu-widget.tsx +++ b/packages/core/src/browser/shell/sidebar-menu-widget.tsx @@ -20,9 +20,9 @@ import { ReactWidget } from '../widgets'; import { ContextMenuRenderer } from '../context-menu-renderer'; import { MenuPath } from '../../common/menu'; -export const SidebarBottomMenuWidgetFactory = Symbol('SidebarBottomMenuWidgetFactory'); +export const SidebarMenuWidgetFactory = Symbol('SidebarMenuWidgetFactory'); -export interface SidebarBottomMenu { +export interface SidebarMenu { id: string; iconClass: string; title: string; @@ -31,11 +31,11 @@ export interface SidebarBottomMenu { } /** - * The menu widget placed on the bottom of the sidebar. + * The menu widget placed on the bottom or top of the sidebar. */ @injectable() -export class SidebarBottomMenuWidget extends ReactWidget { - protected readonly menus: SidebarBottomMenu[]; +export class SidebarMenuWidget extends ReactWidget { + protected readonly menus: SidebarMenu[]; @inject(ContextMenuRenderer) protected readonly contextMenuRenderer: ContextMenuRenderer; @@ -45,7 +45,7 @@ export class SidebarBottomMenuWidget extends ReactWidget { this.menus = []; } - addMenu(menu: SidebarBottomMenu): void { + addMenu(menu: SidebarMenu): void { const exists = this.menus.find(m => m.id === menu.id); if (exists) { return; @@ -55,14 +55,14 @@ export class SidebarBottomMenuWidget extends ReactWidget { } removeMenu(menuId: string): void { - const menu = this.menus.find(m => m.id === menuId); - if (menu) { - const index = this.menus.indexOf(menu); - if (index !== -1) { - this.menus.splice(index, 1); - this.update(); - } + const menu = this.menus.find(m => m.id === menuId); + if (menu) { + const index = this.menus.indexOf(menu); + if (index !== -1) { + this.menus.splice(index, 1); + this.update(); } + } } protected onClick(e: React.MouseEvent, menuPath: MenuPath): void { diff --git a/packages/core/src/browser/shell/theia-dock-panel.ts b/packages/core/src/browser/shell/theia-dock-panel.ts index efbef03ba8258..c48a898b03825 100644 --- a/packages/core/src/browser/shell/theia-dock-panel.ts +++ b/packages/core/src/browser/shell/theia-dock-panel.ts @@ -19,8 +19,12 @@ import { TabBar, Widget, DockPanel, Title, DockLayout } from '@phosphor/widgets' import { Signal } from '@phosphor/signaling'; import { Disposable, DisposableCollection } from '../../common/disposable'; import { MessageLoop } from '../widgets'; +import { CorePreferences } from '../core-preferences'; +import { inject } from 'inversify'; +import { environment } from '../../common'; const MAXIMIZED_CLASS = 'theia-maximized'; +const VISIBLE_MENU_MAXIMIZED_CLASS = 'theia-visible-menu-maximized'; export const MAIN_AREA_ID = 'theia-main-content-panel'; export const BOTTOM_AREA_ID = 'theia-bottom-content-panel'; @@ -44,7 +48,9 @@ export class TheiaDockPanel extends DockPanel { */ readonly widgetRemoved = new Signal(this); - constructor(options?: DockPanel.IOptions) { + constructor(options?: DockPanel.IOptions, + @inject(CorePreferences) protected readonly preferences?: CorePreferences + ) { super(options); this['_onCurrentChanged'] = (sender: TabBar, args: TabBar.ICurrentChangedArgs) => { this.markAsCurrent(args.currentTitle || undefined); @@ -54,6 +60,30 @@ export class TheiaDockPanel extends DockPanel { this.markAsCurrent(args.title); super['_onTabActivateRequested'](sender, args); }; + if (preferences) { + preferences.onPreferenceChanged(preference => { + if (!this.isElectron() && preference.preferenceName === 'window.menuBarVisibility' && (preference.newValue === 'visible' || preference.oldValue === 'visible')) { + this.handleMenuBarVisibility(preference.newValue); + } + }); + } + } + + isElectron(): boolean { + return environment.electron.is(); + } + + protected handleMenuBarVisibility(newValue: string): void { + const areaContainer = this.node.parentElement; + const maximizedElement = this.getMaximizedElement(); + + if (areaContainer === maximizedElement) { + if (newValue === 'visible') { + this.addClass(VISIBLE_MENU_MAXIMIZED_CLASS); + } else { + this.removeClass(VISIBLE_MENU_MAXIMIZED_CLASS); + } + } } protected _currentTitle: Title | undefined; @@ -148,6 +178,11 @@ export class TheiaDockPanel extends DockPanel { } maximizedElement.style.display = 'block'; this.addClass(MAXIMIZED_CLASS); + console.log(this.preferences); + const preference = this.preferences?.get('window.menuBarVisibility') || 'classic'; + if (!this.isElectron() && this.id === MAIN_AREA_ID && preference === 'visible') { + this.addClass(VISIBLE_MENU_MAXIMIZED_CLASS); + } MessageLoop.sendMessage(this, Widget.Msg.BeforeAttach); maximizedElement.appendChild(this.node); MessageLoop.sendMessage(this, Widget.Msg.AfterAttach); @@ -155,6 +190,9 @@ export class TheiaDockPanel extends DockPanel { this.toDisposeOnToggleMaximized.push(Disposable.create(() => { maximizedElement.style.display = 'none'; this.removeClass(MAXIMIZED_CLASS); + if (!this.isElectron()) { + this.removeClass(VISIBLE_MENU_MAXIMIZED_CLASS); + } if (this.isAttached) { MessageLoop.sendMessage(this, Widget.Msg.BeforeDetach); this.node.remove(); diff --git a/packages/core/src/browser/style/index.css b/packages/core/src/browser/style/index.css index 11eac0003e6d1..88171291ad443 100644 --- a/packages/core/src/browser/style/index.css +++ b/packages/core/src/browser/style/index.css @@ -78,6 +78,10 @@ blockquote { background: var(--theia-editor-background); } +.theia-visible-menu-maximized{ + top: var(--theia-private-sidebar-tab-height) !important; +} + .theia-ApplicationShell { position: absolute; top: 0; diff --git a/packages/core/src/browser/style/sidepanel.css b/packages/core/src/browser/style/sidepanel.css index 77e34a6901c0a..9bf6061c0d406 100644 --- a/packages/core/src/browser/style/sidepanel.css +++ b/packages/core/src/browser/style/sidepanel.css @@ -186,17 +186,17 @@ flex-grow: 1; } -.theia-app-sidebar-container .theia-sidebar-bottom-menu { +.theia-app-sidebar-container .theia-sidebar-menu { flex-shrink: 0; } -.p-Widget.theia-sidebar-bottom-menu { +.p-Widget.theia-sidebar-menu { background-color: var(--theia-activityBar-background); display: flex; flex-direction: column-reverse; } -.p-Widget.theia-sidebar-bottom-menu i { +.p-Widget.theia-sidebar-menu i { padding: var(--theia-private-sidebar-tab-padding-top-and-bottom) var(--theia-private-sidebar-tab-padding-left-and-right); display: flex; justify-content: center; @@ -207,10 +207,14 @@ font-size: var(--theia-private-sidebar-icon-size); } -.theia-sidebar-bottom-menu i:hover { +.theia-sidebar-menu i:hover { color: var(--theia-activityBar-foreground); } +.theia-sidebar-menu > i.codicon-menu { + font-size: calc(var(--theia-private-sidebar-icon-size)*0.7); +} + /*----------------------------------------------------------------------------- | Perfect scrollbar |----------------------------------------------------------------------------*/ diff --git a/packages/core/src/common/preferences/preference-schema.ts b/packages/core/src/common/preferences/preference-schema.ts index 0737a5a6e3230..84587f075eb14 100644 --- a/packages/core/src/common/preferences/preference-schema.ts +++ b/packages/core/src/common/preferences/preference-schema.ts @@ -88,6 +88,7 @@ export interface PreferenceDataProperty extends PreferenceItem { description?: string; markdownDescription?: string; scope?: PreferenceScope; + included?: boolean; } export namespace PreferenceDataProperty { export function fromPreferenceSchemaProperty(schemaProps: PreferenceSchemaProperty, defaultScope: PreferenceScope = PreferenceScope.Workspace): PreferenceDataProperty { diff --git a/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts b/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts index e0d22f1973605..ef37c832d3ee0 100644 --- a/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts +++ b/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts @@ -71,33 +71,48 @@ export class ElectronMainMenuFactory { @inject(MenuModelRegistry) protected readonly menuProvider: MenuModelRegistry, @inject(KeybindingRegistry) protected readonly keybindingRegistry: KeybindingRegistry ) { - preferencesService.onPreferenceChanged(debounce(() => { - if (this._menu) { - for (const item of this._toggledCommands) { - this._menu.getMenuItemById(item).checked = this.commandRegistry.isToggled(item); + preferencesService.onPreferenceChanged( + debounce(e => { + if (e.preferenceName === 'window.menuBarVisibility') { + this.setMenuBar(); } - electron.remote.getCurrentWindow().setMenu(this._menu); - } - }, 10)); + if (this._menu) { + for (const item of this._toggledCommands) { + this._menu.getMenuItemById(item).checked = this.commandRegistry.isToggled(item); + } + electron.remote.getCurrentWindow().setMenu(this._menu); + } + }, 10) + ); keybindingRegistry.onKeybindingsChanged(() => { - const createdMenuBar = this.createMenuBar(); - if (isOSX) { - electron.remote.Menu.setApplicationMenu(createdMenuBar); - } else { - electron.remote.getCurrentWindow().setMenu(createdMenuBar); - } + this.setMenuBar(); }); } - createMenuBar(): Electron.Menu { - const menuModel = this.menuProvider.getMenu(MAIN_MENU_BAR); - const template = this.fillMenuTemplate([], menuModel); + setMenuBar(): void { + const createdMenuBar = this.createMenuBar(); if (isOSX) { - template.unshift(this.createOSXMenu()); + electron.remote.Menu.setApplicationMenu(createdMenuBar); + } else { + electron.remote.getCurrentWindow().setMenu(createdMenuBar); + } + } + + createMenuBar(): Electron.Menu | null { + const preference = this.preferencesService.get('window.menuBarVisibility') || 'classic'; + if (['classic', 'visible'].includes(preference)) { + const menuModel = this.menuProvider.getMenu(MAIN_MENU_BAR); + const template = this.fillMenuTemplate([], menuModel); + if (isOSX) { + template.unshift(this.createOSXMenu()); + } + const menu = electron.remote.Menu.buildFromTemplate(template); + this._menu = menu; + return this._menu; } - const menu = electron.remote.Menu.buildFromTemplate(template); - this._menu = menu; - return menu; + this._menu = undefined; + // eslint-disable-next-line no-null/no-null + return null; } createContextMenu(menuPath: MenuPath, args?: any[]): Electron.Menu { diff --git a/packages/core/src/electron-browser/menu/electron-menu-contribution.ts b/packages/core/src/electron-browser/menu/electron-menu-contribution.ts index da71787d08ad4..8e9231e7428de 100644 --- a/packages/core/src/electron-browser/menu/electron-menu-contribution.ts +++ b/packages/core/src/electron-browser/menu/electron-menu-contribution.ts @@ -129,7 +129,7 @@ export class ElectronMenuContribution implements FrontendApplicationContribution } } - private setMenu(menu: electron.Menu = this.factory.createMenuBar(), electronWindow: electron.BrowserWindow = electron.remote.getCurrentWindow()): void { + private setMenu(menu: electron.Menu | null = this.factory.createMenuBar(), electronWindow: electron.BrowserWindow = electron.remote.getCurrentWindow()): void { if (isOSX) { electron.remote.Menu.setApplicationMenu(menu); } else {