Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/microsoft/vscode.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/vs/workbench/browser/parts/titlebar/titlebarPart.ts')
-rw-r--r--src/vs/workbench/browser/parts/titlebar/titlebarPart.ts180
1 files changed, 137 insertions, 43 deletions
diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts
index 4934df5077c..2dbbcce47c8 100644
--- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts
+++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts
@@ -12,10 +12,10 @@ import { getZoomFactor } from 'vs/base/browser/browser';
import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility } from 'vs/platform/window/common/window';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
-import { IAction } from 'vs/base/common/actions';
+import { IAction, toAction } from 'vs/base/common/actions';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
-import { DisposableStore, dispose } from 'vs/base/common/lifecycle';
+import { DisposableStore, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { EditorResourceAccessor, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor';
import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
@@ -25,7 +25,7 @@ import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform'
import { URI } from 'vs/base/common/uri';
import { Color } from 'vs/base/common/color';
import { trim } from 'vs/base/common/strings';
-import { EventType, EventHelper, Dimension, isAncestor, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame, prepend } from 'vs/base/browser/dom';
+import { EventType, EventHelper, Dimension, isAncestor, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame, prepend, clearNode } from 'vs/base/browser/dom';
import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { template } from 'vs/base/common/labels';
@@ -34,8 +34,8 @@ import { Emitter } from 'vs/base/common/event';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { RunOnceScheduler } from 'vs/base/common/async';
-import { createActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
-import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions';
+import { createActionViewItem, createAndFillInContextMenuActions, DropdownWithDefaultActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
+import { IMenuService, IMenu, MenuId, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IProductService } from 'vs/platform/product/common/productService';
@@ -45,6 +45,7 @@ import { Codicon } from 'vs/base/common/codicons';
import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtualWorkspace';
import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
+import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
export class TitlebarPart extends Part implements ITitleService {
@@ -57,7 +58,7 @@ export class TitlebarPart extends Part implements ITitleService {
readonly minimumWidth: number = 0;
readonly maximumWidth: number = Number.POSITIVE_INFINITY;
- get minimumHeight(): number { return 30 / (this.currentMenubarVisibility === 'hidden' ? getZoomFactor() : 1); }
+ get minimumHeight(): number { return 30 / (this.currentMenubarVisibility === 'hidden' || getZoomFactor() < 1 ? getZoomFactor() : 1); }
get maximumHeight(): number { return this.minimumHeight; }
//#endregion
@@ -68,15 +69,19 @@ export class TitlebarPart extends Part implements ITitleService {
declare readonly _serviceBrand: undefined;
protected rootContainer!: HTMLElement;
+ protected windowControls: HTMLElement | undefined;
+ protected readonly titleMenuElement: HTMLElement = $('div.title-menu');
protected title!: HTMLElement;
protected customMenubar: CustomMenubarControl | undefined;
protected appIcon: HTMLElement | undefined;
private appIconBadge: HTMLElement | undefined;
protected menubar?: HTMLElement;
- protected windowControls: HTMLElement | undefined;
+ protected layoutControls: HTMLElement | undefined;
private layoutToolbar: ToolBar | undefined;
protected lastLayoutDimensions: Dimension | undefined;
+
+ private readonly titleMenuDisposables = this._register(new DisposableStore());
private titleBarStyle: 'native' | 'custom';
private pendingTitle: string | undefined;
@@ -87,6 +92,7 @@ export class TitlebarPart extends Part implements ITitleService {
private readonly activeEditorListeners = this._register(new DisposableStore());
private readonly titleUpdater = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0));
+ private readonly onDidUpdateTitle = new Emitter<void>();
private contextMenu: IMenu;
@@ -105,6 +111,7 @@ export class TitlebarPart extends Part implements ITitleService {
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IHostService private readonly hostService: IHostService,
@IProductService private readonly productService: IProductService,
+ @IKeybindingService private readonly keybindingService: IKeybindingService,
) {
super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
@@ -150,8 +157,12 @@ export class TitlebarPart extends Part implements ITitleService {
}
}
- if (this.titleBarStyle !== 'native' && this.windowControls && event.affectsConfiguration('workbench.layoutControl.enabled')) {
- this.windowControls.classList.toggle('show-layout-control', this.layoutControlEnabled);
+ if (this.titleBarStyle !== 'native' && this.layoutControls && event.affectsConfiguration('workbench.layoutControl.enabled')) {
+ this.layoutControls.classList.toggle('show-layout-control', this.layoutControlEnabled);
+ }
+
+ if (event.affectsConfiguration('window.experimental.titleMenu')) {
+ this.updateTitleMenu();
}
}
@@ -196,11 +207,11 @@ export class TitlebarPart extends Part implements ITitleService {
this.pendingTitle = title;
}
- if ((isWeb || isWindows || isLinux) && this.title) {
- if (this.lastLayoutDimensions) {
- this.updateLayout(this.lastLayoutDimensions);
- }
+ if (this.lastLayoutDimensions) {
+ this.updateLayout(this.lastLayoutDimensions);
}
+
+ this.onDidUpdateTitle.fire();
}
private getWindowTitle(): string {
@@ -356,7 +367,7 @@ export class TitlebarPart extends Part implements ITitleService {
this.customMenubar = this._register(this.instantiationService.createInstance(CustomMenubarControl));
- this.menubar = this.rootContainer.insertBefore($('div.menubar'), this.title);
+ this.menubar = this.rootContainer.insertBefore($('div.menubar'), this.titleMenuElement);
this.menubar.setAttribute('role', 'menubar');
this.customMenubar.create(this.menubar);
@@ -364,9 +375,54 @@ export class TitlebarPart extends Part implements ITitleService {
this._register(this.customMenubar.onVisibilityChange(e => this.onMenubarVisibilityChanged(e)));
}
+ private updateTitleMenu(): void {
+ this.titleMenuDisposables.clear();
+ const enableTitleMenu = this.configurationService.getValue<boolean>('window.experimental.titleMenu');
+ this.rootContainer.classList.toggle('enable-title-menu', enableTitleMenu);
+ if (!enableTitleMenu) {
+ return;
+ }
+
+
+ const that = this;
+ const titleToolbar = new ToolBar(this.titleMenuElement, this.contextMenuService, {
+ actionViewItemProvider: (action) => {
+
+ if (action instanceof SubmenuItemAction && action.item.submenu === MenuId.TitleMenuQuickPick) {
+ class QuickInputDropDown extends DropdownWithDefaultActionViewItem {
+ override render(container: HTMLElement): void {
+ super.render(container);
+ container.classList.add('quickopen');
+ container.title = that.getWindowTitle();
+ this._store.add(that.onDidUpdateTitle.event(() => container.title = that.getWindowTitle()));
+ }
+ }
+ return that.instantiationService.createInstance(QuickInputDropDown, action, {
+ keybindingProvider: action => that.keybindingService.lookupKeybinding(action.id),
+ renderKeybindingWithDefaultActionLabel: true
+ });
+ }
+ return undefined;
+ }
+ });
+ const titleMenu = this.titleMenuDisposables.add(this.menuService.createMenu(MenuId.TitleMenu, this.contextKeyService));
+ const titleMenuDisposables = this.titleMenuDisposables.add(new DisposableStore());
+ const updateTitleMenu = () => {
+ titleMenuDisposables.clear();
+ const actions: IAction[] = [];
+ titleMenuDisposables.add(createAndFillInContextMenuActions(titleMenu, undefined, actions));
+ titleToolbar.setActions(actions);
+ };
+ this.titleMenuDisposables.add(titleMenu.onDidChange(updateTitleMenu));
+ this.titleMenuDisposables.add(this.keybindingService.onDidUpdateKeybindings(updateTitleMenu));
+ this.titleMenuDisposables.add(toDisposable(() => clearNode(this.titleMenuElement)));
+ updateTitleMenu();
+ }
+
override createContentArea(parent: HTMLElement): HTMLElement {
this.element = parent;
this.rootContainer = append(parent, $('.titlebar-container'));
+ append(this.rootContainer, this.titleMenuElement);
// App Icon (Native Windows/Linux and Web)
if (!isMacintosh || isWeb) {
@@ -395,6 +451,9 @@ export class TitlebarPart extends Part implements ITitleService {
this.installMenubar();
}
+ // Title Menu
+ this.updateTitleMenu();
+
// Title
this.title = append(this.rootContainer, $('div.window-title'));
if (this.pendingTitle) {
@@ -404,16 +463,22 @@ export class TitlebarPart extends Part implements ITitleService {
}
if (this.titleBarStyle !== 'native') {
- this.windowControls = append(this.rootContainer, $('div.window-controls-container'));
- this.windowControls.classList.toggle('show-layout-control', this.layoutControlEnabled);
+ this.layoutControls = append(this.rootContainer, $('div.layout-controls-container'));
+ this.layoutControls.classList.toggle('show-layout-control', this.layoutControlEnabled);
- const layoutDropdownContainer = append(this.windowControls, $('div.layout-dropdown-container'));
- this.layoutToolbar = new ToolBar(layoutDropdownContainer, this.contextMenuService, {
+ this.layoutToolbar = new ToolBar(this.layoutControls, this.contextMenuService, {
actionViewItemProvider: action => {
return createActionViewItem(this.instantiationService, action);
- }
+ },
+ allowContextMenu: true
});
+ this._register(addDisposableListener(this.layoutControls, EventType.CONTEXT_MENU, e => {
+ EventHelper.stop(e);
+
+ this.onLayoutControlContextMenu(e, this.layoutControls!);
+ }));
+
const menu = this._register(this.menuService.createMenu(MenuId.LayoutControlMenu, this.contextKeyService));
const updateLayoutMenu = () => {
@@ -433,6 +498,8 @@ export class TitlebarPart extends Part implements ITitleService {
updateLayoutMenu();
}
+ this.windowControls = append(this.element, $('div.window-controls-container'));
+
// Context menu on title
[EventType.CONTEXT_MENU, EventType.MOUSE_DOWN].forEach(event => {
this._register(addDisposableListener(this.title, event, e => {
@@ -455,6 +522,10 @@ export class TitlebarPart extends Part implements ITitleService {
return;
}
+ if (e.target && isAncestor(e.target as HTMLElement, this.titleMenuElement)) {
+ return;
+ }
+
const active = document.activeElement;
setTimeout(() => {
if (active instanceof HTMLElement) {
@@ -507,7 +578,6 @@ export class TitlebarPart extends Part implements ITitleService {
}
private onContextMenu(e: MouseEvent): void {
-
// Find target anchor
const event = new StandardMouseEvent(e);
const anchor = { x: event.posx, y: event.posy };
@@ -524,20 +594,41 @@ export class TitlebarPart extends Part implements ITitleService {
});
}
- protected adjustTitleMarginToCenter(): void {
- if (this.customMenubar && this.menubar) {
- const leftMarker = (this.appIcon ? this.appIcon.clientWidth : 0) + this.menubar.clientWidth + 10;
- const rightMarker = this.element.clientWidth - 10;
-
- // Not enough space to center the titlebar within window,
- // Center between menu and window controls
- if (leftMarker > (this.element.clientWidth - this.title.clientWidth) / 2 ||
- rightMarker < (this.element.clientWidth + this.title.clientWidth) / 2) {
- this.title.style.position = '';
- this.title.style.left = '';
- this.title.style.transform = '';
- return;
+ private onLayoutControlContextMenu(e: MouseEvent, el: HTMLElement): void {
+ // Find target anchor
+ const event = new StandardMouseEvent(e);
+ const anchor = { x: event.posx, y: event.posy };
+
+ const actions: IAction[] = [];
+ actions.push(toAction({
+ id: 'layoutControl.hide',
+ label: localize('layoutControl.hide', "Hide Layout Control"),
+ run: () => {
+ this.configurationService.updateValue('workbench.layoutControl.enabled', false);
}
+ }));
+
+ // Show it
+ this.contextMenuService.showContextMenu({
+ getAnchor: () => anchor,
+ getActions: () => actions,
+ domForShadowRoot: el
+ });
+ }
+
+ protected adjustTitleMarginToCenter(): void {
+ const base = isMacintosh ? (this.windowControls?.clientWidth ?? 0) : 0;
+ const leftMarker = base + (this.appIcon?.clientWidth ?? 0) + (this.menubar?.clientWidth ?? 0) + 10;
+ const rightMarker = base + this.rootContainer.clientWidth - (this.layoutControls?.clientWidth ?? 0) - 10;
+
+ // Not enough space to center the titlebar within window,
+ // Center between left and right controls
+ if (leftMarker > (this.rootContainer.clientWidth + (this.windowControls?.clientWidth ?? 0) - this.title.clientWidth) / 2 ||
+ rightMarker < (this.rootContainer.clientWidth + (this.windowControls?.clientWidth ?? 0) + this.title.clientWidth) / 2) {
+ this.title.style.position = '';
+ this.title.style.left = '';
+ this.title.style.transform = '';
+ return;
}
this.title.style.position = 'absolute';
@@ -557,16 +648,13 @@ export class TitlebarPart extends Part implements ITitleService {
this.lastLayoutDimensions = dimension;
if (getTitleBarStyle(this.configurationService) === 'custom') {
- // Only prevent zooming behavior on macOS or when the menubar is not visible
- if ((!isWeb && isMacintosh) || this.currentMenubarVisibility === 'hidden') {
- this.rootContainer.style.height = `${100.0 * getZoomFactor()}%`;
- this.rootContainer.style.width = `${100.0 * getZoomFactor()}%`;
- this.rootContainer.style.transform = `scale(${1 / getZoomFactor()})`;
- } else {
- this.rootContainer.style.height = '100%';
- this.rootContainer.style.width = '100%';
- this.rootContainer.style.transform = '';
- }
+ // Prevent zooming behavior if any of the following conditions are met:
+ // 1. Native macOS
+ // 2. Menubar is hidden
+ // 3. Shrinking below the window control size (zoom < 1)
+ const zoomFactor = getZoomFactor();
+ this.element.style.setProperty('--zoom-factor', zoomFactor.toString());
+ this.rootContainer.classList.toggle('counter-zoom', zoomFactor < 1 || (!isWeb && isMacintosh) || this.currentMenubarVisibility === 'hidden');
runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter());
@@ -609,3 +697,9 @@ registerThemingParticipant((theme, collector) => {
`);
}
});
+
+MenuRegistry.appendMenuItem(MenuId.TitleMenu, {
+ submenu: MenuId.TitleMenuQuickPick,
+ title: localize('title', "Select Mode"),
+ order: Number.MAX_SAFE_INTEGER
+});