Path: blob/main/src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Event } from '../../../../base/common/event.js';6import { getZoomFactor } from '../../../../base/browser/browser.js';7import { $, addDisposableListener, append, EventType, getWindow, getWindowId, hide, show } from '../../../../base/browser/dom.js';8import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';9import { IConfigurationService, IConfigurationChangeEvent } from '../../../../platform/configuration/common/configuration.js';10import { IStorageService } from '../../../../platform/storage/common/storage.js';11import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js';12import { IHostService } from '../../../services/host/browser/host.js';13import { isMacintosh, isWindows, isLinux, isBigSurOrNewer } from '../../../../base/common/platform.js';14import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js';15import { BrowserTitlebarPart as BrowserTitlebarPart, BrowserTitleService, IAuxiliaryTitlebarPart } from '../../../browser/parts/titlebar/titlebarPart.js';16import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';17import { IThemeService } from '../../../../platform/theme/common/themeService.js';18import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser/layoutService.js';19import { INativeHostService } from '../../../../platform/native/common/native.js';20import { hasNativeTitlebar, useWindowControlsOverlay, DEFAULT_CUSTOM_TITLEBAR_HEIGHT, hasNativeMenu } from '../../../../platform/window/common/window.js';21import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';22import { Codicon } from '../../../../base/common/codicons.js';23import { ThemeIcon } from '../../../../base/common/themables.js';24import { NativeMenubarControl } from './menubarControl.js';25import { IEditorGroupsContainer, IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js';26import { IEditorService } from '../../../services/editor/common/editorService.js';27import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';28import { CodeWindow, mainWindow } from '../../../../base/browser/window.js';29import { IsWindowAlwaysOnTopContext } from '../../../common/contextkeys.js';3031export class NativeTitlebarPart extends BrowserTitlebarPart {3233//#region IView3435override get minimumHeight(): number {36if (!isMacintosh) {37return super.minimumHeight;38}3940return (this.isCommandCenterVisible ? DEFAULT_CUSTOM_TITLEBAR_HEIGHT : this.macTitlebarSize) / (this.preventZoom ? getZoomFactor(getWindow(this.element)) : 1);41}42override get maximumHeight(): number { return this.minimumHeight; }4344private bigSurOrNewer: boolean;45private get macTitlebarSize() {46if (this.bigSurOrNewer) {47return 28; // macOS Big Sur increases title bar height48}4950return 22;51}5253//#endregion5455private maxRestoreControl: HTMLElement | undefined;56private resizer: HTMLElement | undefined;5758private cachedWindowControlStyles: { bgColor: string; fgColor: string } | undefined;59private cachedWindowControlHeight: number | undefined;6061constructor(62id: string,63targetWindow: CodeWindow,64editorGroupsContainer: IEditorGroupsContainer,65@IContextMenuService contextMenuService: IContextMenuService,66@IConfigurationService configurationService: IConfigurationService,67@INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService,68@IInstantiationService instantiationService: IInstantiationService,69@IThemeService themeService: IThemeService,70@IStorageService storageService: IStorageService,71@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,72@IContextKeyService contextKeyService: IContextKeyService,73@IHostService hostService: IHostService,74@INativeHostService private readonly nativeHostService: INativeHostService,75@IEditorGroupsService editorGroupService: IEditorGroupsService,76@IEditorService editorService: IEditorService,77@IMenuService menuService: IMenuService,78@IKeybindingService keybindingService: IKeybindingService79) {80super(id, targetWindow, editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, editorService, menuService, keybindingService);8182this.bigSurOrNewer = isBigSurOrNewer(environmentService.os.release);8384this.handleWindowsAlwaysOnTop(targetWindow.vscodeWindowId);85}8687private async handleWindowsAlwaysOnTop(targetWindowId: number): Promise<void> {88const isWindowAlwaysOnTopContext = IsWindowAlwaysOnTopContext.bindTo(this.contextKeyService);8990this._register(this.nativeHostService.onDidChangeWindowAlwaysOnTop(({ windowId, alwaysOnTop }) => {91if (windowId === targetWindowId) {92isWindowAlwaysOnTopContext.set(alwaysOnTop);93}94}));9596isWindowAlwaysOnTopContext.set(await this.nativeHostService.isWindowAlwaysOnTop({ targetWindowId }));97}9899protected override onMenubarVisibilityChanged(visible: boolean): void {100101// Hide title when toggling menu bar102if ((isWindows || isLinux) && this.currentMenubarVisibility === 'toggle' && visible) {103104// Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor105if (this.dragRegion) {106hide(this.dragRegion);107setTimeout(() => show(this.dragRegion!), 50);108}109}110111super.onMenubarVisibilityChanged(visible);112}113114protected override onConfigurationChanged(event: IConfigurationChangeEvent): void {115super.onConfigurationChanged(event);116117if (event.affectsConfiguration('window.doubleClickIconToClose')) {118if (this.appIcon) {119this.onUpdateAppIconDragBehavior();120}121}122}123124private onUpdateAppIconDragBehavior(): void {125const setting = this.configurationService.getValue('window.doubleClickIconToClose');126if (setting && this.appIcon) {127(this.appIcon.style as any)['-webkit-app-region'] = 'no-drag';128} else if (this.appIcon) {129(this.appIcon.style as any)['-webkit-app-region'] = 'drag';130}131}132133protected override installMenubar(): void {134super.installMenubar();135136if (this.menubar) {137return;138}139140if (this.customMenubar.value) {141this._register(this.customMenubar.value.onFocusStateChange(e => this.onMenubarFocusChanged(e)));142}143}144145private onMenubarFocusChanged(focused: boolean): void {146if ((isWindows || isLinux) && this.currentMenubarVisibility !== 'compact' && this.dragRegion) {147if (focused) {148hide(this.dragRegion);149} else {150show(this.dragRegion);151}152}153}154155protected override createContentArea(parent: HTMLElement): HTMLElement {156const result = super.createContentArea(parent);157const targetWindow = getWindow(parent);158const targetWindowId = getWindowId(targetWindow);159160// Native menu controller161if (isMacintosh || hasNativeMenu(this.configurationService)) {162this._register(this.instantiationService.createInstance(NativeMenubarControl));163}164165// App Icon (Native Windows/Linux)166if (this.appIcon) {167this.onUpdateAppIconDragBehavior();168169this._register(addDisposableListener(this.appIcon, EventType.DBLCLICK, (() => {170this.nativeHostService.closeWindow({ targetWindowId });171})));172}173174// Custom Window Controls (Native Windows/Linux)175if (176!hasNativeTitlebar(this.configurationService) && // not for native title bars177!useWindowControlsOverlay(this.configurationService) && // not when controls are natively drawn178this.windowControlsContainer179) {180181// Minimize182const minimizeIcon = append(this.windowControlsContainer, $('div.window-icon.window-minimize' + ThemeIcon.asCSSSelector(Codicon.chromeMinimize)));183this._register(addDisposableListener(minimizeIcon, EventType.CLICK, () => {184this.nativeHostService.minimizeWindow({ targetWindowId });185}));186187// Restore188this.maxRestoreControl = append(this.windowControlsContainer, $('div.window-icon.window-max-restore'));189this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, async () => {190const maximized = await this.nativeHostService.isMaximized({ targetWindowId });191if (maximized) {192return this.nativeHostService.unmaximizeWindow({ targetWindowId });193}194195return this.nativeHostService.maximizeWindow({ targetWindowId });196}));197198// Close199const closeIcon = append(this.windowControlsContainer, $('div.window-icon.window-close' + ThemeIcon.asCSSSelector(Codicon.chromeClose)));200this._register(addDisposableListener(closeIcon, EventType.CLICK, () => {201this.nativeHostService.closeWindow({ targetWindowId });202}));203204// Resizer205this.resizer = append(this.rootContainer, $('div.resizer'));206this._register(Event.runAndSubscribe(this.layoutService.onDidChangeWindowMaximized, ({ windowId, maximized }) => {207if (windowId === targetWindowId) {208this.onDidChangeWindowMaximized(maximized);209}210}, { windowId: targetWindowId, maximized: this.layoutService.isWindowMaximized(targetWindow) }));211}212213// Window System Context Menu214// See https://github.com/electron/electron/issues/24893215if (isWindows && !hasNativeTitlebar(this.configurationService)) {216this._register(this.nativeHostService.onDidTriggerWindowSystemContextMenu(({ windowId, x, y }) => {217if (targetWindowId !== windowId) {218return;219}220221const zoomFactor = getZoomFactor(getWindow(this.element));222this.onContextMenu(new MouseEvent(EventType.MOUSE_UP, { clientX: x / zoomFactor, clientY: y / zoomFactor }), MenuId.TitleBarContext);223}));224}225226return result;227}228229private onDidChangeWindowMaximized(maximized: boolean): void {230if (this.maxRestoreControl) {231if (maximized) {232this.maxRestoreControl.classList.remove(...ThemeIcon.asClassNameArray(Codicon.chromeMaximize));233this.maxRestoreControl.classList.add(...ThemeIcon.asClassNameArray(Codicon.chromeRestore));234} else {235this.maxRestoreControl.classList.remove(...ThemeIcon.asClassNameArray(Codicon.chromeRestore));236this.maxRestoreControl.classList.add(...ThemeIcon.asClassNameArray(Codicon.chromeMaximize));237}238}239240if (this.resizer) {241if (maximized) {242hide(this.resizer);243} else {244show(this.resizer);245}246}247}248249override updateStyles(): void {250super.updateStyles();251252// Part container253if (this.element) {254if (useWindowControlsOverlay(this.configurationService)) {255if (256!this.cachedWindowControlStyles ||257this.cachedWindowControlStyles.bgColor !== this.element.style.backgroundColor ||258this.cachedWindowControlStyles.fgColor !== this.element.style.color259) {260this.nativeHostService.updateWindowControls({261targetWindowId: getWindowId(getWindow(this.element)),262backgroundColor: this.element.style.backgroundColor,263foregroundColor: this.element.style.color264});265}266}267}268}269270override layout(width: number, height: number): void {271super.layout(width, height);272273if (useWindowControlsOverlay(this.configurationService)) {274275// When the user goes into full screen mode, the height of the title bar becomes 0.276// Instead, set it back to the default titlebar height for Catalina users277// so that they can have the traffic lights rendered at the proper offset.278// Ref https://github.com/microsoft/vscode/issues/159862279280const newHeight = (height > 0 || this.bigSurOrNewer) ? Math.round(height * getZoomFactor(getWindow(this.element))) : this.macTitlebarSize;281if (newHeight !== this.cachedWindowControlHeight) {282this.cachedWindowControlHeight = newHeight;283this.nativeHostService.updateWindowControls({284targetWindowId: getWindowId(getWindow(this.element)),285height: newHeight286});287}288}289}290}291292export class MainNativeTitlebarPart extends NativeTitlebarPart {293294constructor(295@IContextMenuService contextMenuService: IContextMenuService,296@IConfigurationService configurationService: IConfigurationService,297@INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService,298@IInstantiationService instantiationService: IInstantiationService,299@IThemeService themeService: IThemeService,300@IStorageService storageService: IStorageService,301@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,302@IContextKeyService contextKeyService: IContextKeyService,303@IHostService hostService: IHostService,304@INativeHostService nativeHostService: INativeHostService,305@IEditorGroupsService editorGroupService: IEditorGroupsService,306@IEditorService editorService: IEditorService,307@IMenuService menuService: IMenuService,308@IKeybindingService keybindingService: IKeybindingService309) {310super(Parts.TITLEBAR_PART, mainWindow, editorGroupService.mainPart, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, nativeHostService, editorGroupService, editorService, menuService, keybindingService);311}312}313314export class AuxiliaryNativeTitlebarPart extends NativeTitlebarPart implements IAuxiliaryTitlebarPart {315316private static COUNTER = 1;317318get height() { return this.minimumHeight; }319320constructor(321readonly container: HTMLElement,322editorGroupsContainer: IEditorGroupsContainer,323private readonly mainTitlebar: BrowserTitlebarPart,324@IContextMenuService contextMenuService: IContextMenuService,325@IConfigurationService configurationService: IConfigurationService,326@INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService,327@IInstantiationService instantiationService: IInstantiationService,328@IThemeService themeService: IThemeService,329@IStorageService storageService: IStorageService,330@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,331@IContextKeyService contextKeyService: IContextKeyService,332@IHostService hostService: IHostService,333@INativeHostService nativeHostService: INativeHostService,334@IEditorGroupsService editorGroupService: IEditorGroupsService,335@IEditorService editorService: IEditorService,336@IMenuService menuService: IMenuService,337@IKeybindingService keybindingService: IKeybindingService338) {339const id = AuxiliaryNativeTitlebarPart.COUNTER++;340super(`workbench.parts.auxiliaryTitle.${id}`, getWindow(container), editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, nativeHostService, editorGroupService, editorService, menuService, keybindingService);341}342343override get preventZoom(): boolean {344345// Prevent zooming behavior if any of the following conditions are met:346// 1. Shrinking below the window control size (zoom < 1)347// 2. No custom items are present in the main title bar348// The auxiliary title bar never contains any zoomable items itself,349// but we want to match the behavior of the main title bar.350351return getZoomFactor(getWindow(this.element)) < 1 || !this.mainTitlebar.hasZoomableElements;352}353}354355export class NativeTitleService extends BrowserTitleService {356357protected override createMainTitlebarPart(): MainNativeTitlebarPart {358return this.instantiationService.createInstance(MainNativeTitlebarPart);359}360361protected override doCreateAuxiliaryTitlebarPart(container: HTMLElement, editorGroupsContainer: IEditorGroupsContainer, instantiationService: IInstantiationService): AuxiliaryNativeTitlebarPart {362return instantiationService.createInstance(AuxiliaryNativeTitlebarPart, container, editorGroupsContainer, this.mainPart);363}364}365366367