Skip to content

Commit

Permalink
Fix #9779: add support for window.menuBarVisibility
Browse files Browse the repository at this point in the history
Signed-off-by: Esther Perelman <esther.perelman@sap.com>
  • Loading branch information
EstherPerelman authored and ChayaLau committed Aug 5, 2021
1 parent dece352 commit 3ff1351
Showing 11 changed files with 170 additions and 64 deletions.
Original file line number Diff line number Diff line change
@@ -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 {
36 changes: 29 additions & 7 deletions packages/core/src/browser/common-frontend-contribution.ts
Original file line number Diff line number Diff line change
@@ -354,16 +354,38 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
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);
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;
}
}
});
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 +400,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);
}
});
}
15 changes: 15 additions & 0 deletions packages/core/src/browser/core-preferences.ts
Original file line number Diff line number Diff line change
@@ -97,6 +97,20 @@ 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. This value is ignored when `#window.titleBarStyle#` is `native`.'
],
default: 'classic',
scope: 'application',
markdownDescription: `Control the visibility of the menu bar. A setting of 'toggle' means that the menu bar is hidden and a single press of the Alt key will show it.
A setting of 'compact' will move the menu into the sidebar.`,
},
}
};

@@ -112,6 +126,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');
6 changes: 3 additions & 3 deletions packages/core/src/browser/frontend-application-module.ts
Original file line number Diff line number Diff line change
@@ -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);
33 changes: 28 additions & 5 deletions packages/core/src/browser/menu/browser-menu-plugin.ts
Original file line number Diff line number Diff line change
@@ -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<MenuWidget>;
@@ -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);
@@ -348,6 +368,9 @@ export class BrowserMenuBarContribution implements FrontendApplicationContributi
@inject(ApplicationShell)
protected readonly shell: ApplicationShell;

@inject(CorePreferences)
protected readonly corePreferences: CorePreferences;

constructor(
@inject(BrowserMainMenuFactory) protected readonly factory: BrowserMainMenuFactory
) { }
2 changes: 1 addition & 1 deletion packages/core/src/browser/shell/index.ts
Original file line number Diff line number Diff line change
@@ -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';
44 changes: 35 additions & 9 deletions packages/core/src/browser/shell/side-panel-handler.ts
Original file line number Diff line number Diff line change
@@ -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);
}

Original file line number Diff line number Diff line change
@@ -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<HTMLElement, MouseEvent>, menuPath: MenuPath): void {
12 changes: 8 additions & 4 deletions packages/core/src/browser/style/sidepanel.css
Original file line number Diff line number Diff line change
@@ -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
|----------------------------------------------------------------------------*/
Loading

0 comments on commit 3ff1351

Please sign in to comment.