Path: blob/main/src/vs/workbench/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 './media/titlebarpart.css';6import { localize, localize2 } from '../../../../nls.js';7import { MultiWindowParts, Part } from '../../part.js';8import { ITitleService } from '../../../services/title/browser/titleService.js';9import { getWCOTitlebarAreaRect, getZoomFactor, isWCOEnabled } from '../../../../base/browser/browser.js';10import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, hasCustomTitlebar, hasNativeTitlebar, DEFAULT_CUSTOM_TITLEBAR_HEIGHT, getWindowControlsStyle, WindowControlsStyle, TitlebarStyle, MenuSettings, hasNativeMenu } from '../../../../platform/window/common/window.js';11import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';12import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js';13import { IConfigurationService, IConfigurationChangeEvent } from '../../../../platform/configuration/common/configuration.js';14import { DisposableStore, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js';15import { IBrowserWorkbenchEnvironmentService } from '../../../services/environment/browser/environmentService.js';16import { IThemeService } from '../../../../platform/theme/common/themeService.js';17import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER, WORKBENCH_BACKGROUND } from '../../../common/theme.js';18import { isMacintosh, isWindows, isLinux, isWeb, isNative, platformLocale } from '../../../../base/common/platform.js';19import { Color } from '../../../../base/common/color.js';20import { EventType, EventHelper, Dimension, append, $, addDisposableListener, prepend, reset, getWindow, getWindowId, isAncestor, getActiveDocument, isHTMLElement } from '../../../../base/browser/dom.js';21import { CustomMenubarControl } from './menubarControl.js';22import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';23import { Emitter, Event } from '../../../../base/common/event.js';24import { IStorageService, StorageScope } from '../../../../platform/storage/common/storage.js';25import { Parts, IWorkbenchLayoutService, ActivityBarPosition, LayoutSettings, EditorActionsLocation, EditorTabsMode } from '../../../services/layout/browser/layoutService.js';26import { createActionViewItem, fillInActionBarActions as fillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';27import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';28import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';29import { IHostService } from '../../../services/host/browser/host.js';30import { WindowTitle } from './windowTitle.js';31import { CommandCenterControl } from './commandCenterControl.js';32import { Categories } from '../../../../platform/action/common/actionCommonCategories.js';33import { WorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js';34import { ACCOUNTS_ACTIVITY_ID, GLOBAL_ACTIVITY_ID } from '../../../common/activity.js';35import { AccountsActivityActionViewItem, isAccountsActionVisible, SimpleAccountActivityActionViewItem, SimpleGlobalActivityActionViewItem } from '../globalCompositeBar.js';36import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js';37import { IEditorGroupsContainer, IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js';38import { ActionRunner, IAction } from '../../../../base/common/actions.js';39import { IEditorService } from '../../../services/editor/common/editorService.js';40import { ActionsOrientation, IActionViewItem, prepareActions } from '../../../../base/browser/ui/actionbar/actionbar.js';41import { EDITOR_CORE_NAVIGATION_COMMANDS } from '../editor/editorCommands.js';42import { AnchorAlignment } from '../../../../base/browser/ui/contextview/contextview.js';43import { EditorPane } from '../editor/editorPane.js';44import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';45import { ResolvedKeybinding } from '../../../../base/common/keybindings.js';46import { EditorCommandsContextActionRunner } from '../editor/editorTabsControl.js';47import { IEditorCommandsContext, IEditorPartOptionsChangeEvent, IToolbarActions } from '../../../common/editor.js';48import { CodeWindow, mainWindow } from '../../../../base/browser/window.js';49import { ACCOUNTS_ACTIVITY_TILE_ACTION, GLOBAL_ACTIVITY_TITLE_ACTION } from './titlebarActions.js';50import { IView } from '../../../../base/browser/ui/grid/grid.js';51import { createInstantHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';52import { IBaseActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';53import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.js';54import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';55import { safeIntl } from '../../../../base/common/date.js';56import { IsCompactTitleBarContext, TitleBarVisibleContext } from '../../../common/contextkeys.js';5758export interface ITitleVariable {59readonly name: string;60readonly contextKey: string;61}6263export interface ITitleProperties {64isPure?: boolean;65isAdmin?: boolean;66prefix?: string;67}6869export interface ITitlebarPart extends IDisposable {7071/**72* An event when the menubar visibility changes.73*/74readonly onMenubarVisibilityChange: Event<boolean>;7576/**77* Update some environmental title properties.78*/79updateProperties(properties: ITitleProperties): void;8081/**82* Adds variables to be supported in the window title.83*/84registerVariables(variables: ITitleVariable[]): void;85}8687export class BrowserTitleService extends MultiWindowParts<BrowserTitlebarPart> implements ITitleService {8889declare _serviceBrand: undefined;9091readonly mainPart: BrowserTitlebarPart;9293constructor(94@IInstantiationService protected readonly instantiationService: IInstantiationService,95@IStorageService storageService: IStorageService,96@IThemeService themeService: IThemeService97) {98super('workbench.titleService', themeService, storageService);99100this.mainPart = this._register(this.createMainTitlebarPart());101this.onMenubarVisibilityChange = this.mainPart.onMenubarVisibilityChange;102this._register(this.registerPart(this.mainPart));103104this.registerActions();105this.registerAPICommands();106}107108protected createMainTitlebarPart(): BrowserTitlebarPart {109return this.instantiationService.createInstance(MainBrowserTitlebarPart);110}111112private registerActions(): void {113114// Focus action115const that = this;116this._register(registerAction2(class FocusTitleBar extends Action2 {117118constructor() {119super({120id: `workbench.action.focusTitleBar`,121title: localize2('focusTitleBar', 'Focus Title Bar'),122category: Categories.View,123f1: true,124precondition: TitleBarVisibleContext125});126}127128run(): void {129that.getPartByDocument(getActiveDocument())?.focus();130}131}));132}133134private registerAPICommands(): void {135this._register(CommandsRegistry.registerCommand({136id: 'registerWindowTitleVariable',137handler: (accessor: ServicesAccessor, name: string, contextKey: string) => {138this.registerVariables([{ name, contextKey }]);139},140metadata: {141description: 'Registers a new title variable',142args: [143{ name: 'name', schema: { type: 'string' }, description: 'The name of the variable to register' },144{ name: 'contextKey', schema: { type: 'string' }, description: 'The context key to use for the value of the variable' }145]146}147}));148}149150//#region Auxiliary Titlebar Parts151152createAuxiliaryTitlebarPart(container: HTMLElement, editorGroupsContainer: IEditorGroupsContainer, instantiationService: IInstantiationService): IAuxiliaryTitlebarPart {153const titlebarPartContainer = $('.part.titlebar', { role: 'none' });154titlebarPartContainer.style.position = 'relative';155container.insertBefore(titlebarPartContainer, container.firstChild); // ensure we are first element156157const disposables = new DisposableStore();158159const titlebarPart = this.doCreateAuxiliaryTitlebarPart(titlebarPartContainer, editorGroupsContainer, instantiationService);160disposables.add(this.registerPart(titlebarPart));161162disposables.add(Event.runAndSubscribe(titlebarPart.onDidChange, () => titlebarPartContainer.style.height = `${titlebarPart.height}px`));163titlebarPart.create(titlebarPartContainer);164165if (this.properties) {166titlebarPart.updateProperties(this.properties);167}168169if (this.variables.size) {170titlebarPart.registerVariables(Array.from(this.variables.values()));171}172173Event.once(titlebarPart.onWillDispose)(() => disposables.dispose());174175return titlebarPart;176}177178protected doCreateAuxiliaryTitlebarPart(container: HTMLElement, editorGroupsContainer: IEditorGroupsContainer, instantiationService: IInstantiationService): BrowserTitlebarPart & IAuxiliaryTitlebarPart {179return instantiationService.createInstance(AuxiliaryBrowserTitlebarPart, container, editorGroupsContainer, this.mainPart);180}181182//#endregion183184185//#region Service Implementation186187readonly onMenubarVisibilityChange: Event<boolean>;188189private properties: ITitleProperties | undefined = undefined;190191updateProperties(properties: ITitleProperties): void {192this.properties = properties;193194for (const part of this.parts) {195part.updateProperties(properties);196}197}198199private readonly variables = new Map<string, ITitleVariable>();200201registerVariables(variables: ITitleVariable[]): void {202const newVariables: ITitleVariable[] = [];203204for (const variable of variables) {205if (!this.variables.has(variable.name)) {206this.variables.set(variable.name, variable);207newVariables.push(variable);208}209}210211for (const part of this.parts) {212part.registerVariables(newVariables);213}214}215216//#endregion217}218219export class BrowserTitlebarPart extends Part implements ITitlebarPart {220221//#region IView222223readonly minimumWidth: number = 0;224readonly maximumWidth: number = Number.POSITIVE_INFINITY;225226get minimumHeight(): number {227const wcoEnabled = isWeb && isWCOEnabled();228let value = this.isCommandCenterVisible || wcoEnabled ? DEFAULT_CUSTOM_TITLEBAR_HEIGHT : 30;229if (wcoEnabled) {230value = Math.max(value, getWCOTitlebarAreaRect(getWindow(this.element))?.height ?? 0);231}232233return value / (this.preventZoom ? getZoomFactor(getWindow(this.element)) : 1);234}235236get maximumHeight(): number { return this.minimumHeight; }237238//#endregion239240//#region Events241242private _onMenubarVisibilityChange = this._register(new Emitter<boolean>());243readonly onMenubarVisibilityChange = this._onMenubarVisibilityChange.event;244245private readonly _onWillDispose = this._register(new Emitter<void>());246readonly onWillDispose = this._onWillDispose.event;247248//#endregion249250protected rootContainer!: HTMLElement;251protected windowControlsContainer: HTMLElement | undefined;252253protected dragRegion: HTMLElement | undefined;254private title!: HTMLElement;255256private leftContent!: HTMLElement;257private centerContent!: HTMLElement;258private rightContent!: HTMLElement;259260protected readonly customMenubar = this._register(new MutableDisposable<CustomMenubarControl>());261protected appIcon: HTMLElement | undefined;262private appIconBadge: HTMLElement | undefined;263protected menubar?: HTMLElement;264private lastLayoutDimensions: Dimension | undefined;265266private actionToolBar!: WorkbenchToolBar;267private readonly actionToolBarDisposable = this._register(new DisposableStore());268private readonly editorActionsChangeDisposable = this._register(new DisposableStore());269private actionToolBarElement!: HTMLElement;270271private globalToolbarMenu: IMenu | undefined;272private layoutToolbarMenu: IMenu | undefined;273274private readonly globalToolbarMenuDisposables = this._register(new DisposableStore());275private readonly editorToolbarMenuDisposables = this._register(new DisposableStore());276private readonly layoutToolbarMenuDisposables = this._register(new DisposableStore());277private readonly activityToolbarDisposables = this._register(new DisposableStore());278279private readonly hoverDelegate: IHoverDelegate;280281private readonly titleDisposables = this._register(new DisposableStore());282private titleBarStyle: TitlebarStyle;283284private isInactive: boolean = false;285286private readonly isAuxiliary: boolean;287private isCompact = false;288289private readonly isCompactContextKey: IContextKey<boolean>;290291private readonly windowTitle: WindowTitle;292293constructor(294id: string,295targetWindow: CodeWindow,296private readonly editorGroupsContainer: IEditorGroupsContainer,297@IContextMenuService private readonly contextMenuService: IContextMenuService,298@IConfigurationService protected readonly configurationService: IConfigurationService,299@IBrowserWorkbenchEnvironmentService protected readonly environmentService: IBrowserWorkbenchEnvironmentService,300@IInstantiationService protected readonly instantiationService: IInstantiationService,301@IThemeService themeService: IThemeService,302@IStorageService private readonly storageService: IStorageService,303@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,304@IContextKeyService protected readonly contextKeyService: IContextKeyService,305@IHostService private readonly hostService: IHostService,306@IEditorService private readonly editorService: IEditorService,307@IMenuService private readonly menuService: IMenuService,308@IKeybindingService private readonly keybindingService: IKeybindingService309) {310super(id, { hasTitle: false }, themeService, storageService, layoutService);311312this.isAuxiliary = targetWindow.vscodeWindowId !== mainWindow.vscodeWindowId;313314this.isCompactContextKey = IsCompactTitleBarContext.bindTo(this.contextKeyService);315316this.titleBarStyle = getTitleBarStyle(this.configurationService);317318this.windowTitle = this._register(instantiationService.createInstance(WindowTitle, targetWindow));319320this.hoverDelegate = this._register(createInstantHoverDelegate());321322this.registerListeners(getWindowId(targetWindow));323}324325private registerListeners(targetWindowId: number): void {326this._register(this.hostService.onDidChangeFocus(focused => focused ? this.onFocus() : this.onBlur()));327this._register(this.hostService.onDidChangeActiveWindow(windowId => windowId === targetWindowId ? this.onFocus() : this.onBlur()));328this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChanged(e)));329this._register(this.editorGroupsContainer.onDidChangeEditorPartOptions(e => this.onEditorPartConfigurationChange(e)));330}331332private onBlur(): void {333this.isInactive = true;334335this.updateStyles();336}337338private onFocus(): void {339this.isInactive = false;340341this.updateStyles();342}343344private onEditorPartConfigurationChange({ oldPartOptions, newPartOptions }: IEditorPartOptionsChangeEvent): void {345if (346oldPartOptions.editorActionsLocation !== newPartOptions.editorActionsLocation ||347oldPartOptions.showTabs !== newPartOptions.showTabs348) {349if (hasCustomTitlebar(this.configurationService, this.titleBarStyle) && this.actionToolBar) {350this.createActionToolBar();351this.createActionToolBarMenus({ editorActions: true });352this._onDidChange.fire(undefined);353}354}355}356357protected onConfigurationChanged(event: IConfigurationChangeEvent): void {358359// Custom menu bar (disabled if auxiliary)360if (!this.isAuxiliary && !hasNativeMenu(this.configurationService, this.titleBarStyle) && (!isMacintosh || isWeb)) {361if (event.affectsConfiguration(MenuSettings.MenuBarVisibility)) {362if (this.currentMenubarVisibility === 'compact') {363this.uninstallMenubar();364} else {365this.installMenubar();366}367}368}369370// Actions371if (hasCustomTitlebar(this.configurationService, this.titleBarStyle) && this.actionToolBar) {372const affectsLayoutControl = event.affectsConfiguration(LayoutSettings.LAYOUT_ACTIONS);373const affectsActivityControl = event.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION);374375if (affectsLayoutControl || affectsActivityControl) {376this.createActionToolBarMenus({ layoutActions: affectsLayoutControl, activityActions: affectsActivityControl });377378this._onDidChange.fire(undefined);379}380}381382// Command Center383if (event.affectsConfiguration(LayoutSettings.COMMAND_CENTER)) {384this.recreateTitle();385}386}387388private recreateTitle(): void {389this.createTitle();390391this._onDidChange.fire(undefined);392}393394updateOptions(options: { compact: boolean }): void {395const oldIsCompact = this.isCompact;396this.isCompact = options.compact;397398this.isCompactContextKey.set(this.isCompact);399400if (oldIsCompact !== this.isCompact) {401this.recreateTitle();402this.createActionToolBarMenus(true);403}404}405406protected installMenubar(): void {407if (this.menubar) {408return; // If the menubar is already installed, skip409}410411this.customMenubar.value = this.instantiationService.createInstance(CustomMenubarControl);412413this.menubar = append(this.leftContent, $('div.menubar'));414this.menubar.setAttribute('role', 'menubar');415416this._register(this.customMenubar.value.onVisibilityChange(e => this.onMenubarVisibilityChanged(e)));417418this.customMenubar.value.create(this.menubar);419}420421private uninstallMenubar(): void {422this.customMenubar.value = undefined;423424this.menubar?.remove();425this.menubar = undefined;426427this.onMenubarVisibilityChanged(false);428}429430protected onMenubarVisibilityChanged(visible: boolean): void {431if (isWeb || isWindows || isLinux) {432if (this.lastLayoutDimensions) {433this.layout(this.lastLayoutDimensions.width, this.lastLayoutDimensions.height);434}435436this._onMenubarVisibilityChange.fire(visible);437}438}439440updateProperties(properties: ITitleProperties): void {441this.windowTitle.updateProperties(properties);442}443444registerVariables(variables: ITitleVariable[]): void {445this.windowTitle.registerVariables(variables);446}447448protected override createContentArea(parent: HTMLElement): HTMLElement {449this.element = parent;450this.rootContainer = append(parent, $('.titlebar-container'));451452this.leftContent = append(this.rootContainer, $('.titlebar-left'));453this.centerContent = append(this.rootContainer, $('.titlebar-center'));454this.rightContent = append(this.rootContainer, $('.titlebar-right'));455456// App Icon (Windows, Linux)457if ((isWindows || isLinux) && !hasNativeTitlebar(this.configurationService, this.titleBarStyle)) {458this.appIcon = prepend(this.leftContent, $('a.window-appicon'));459}460461// Draggable region that we can manipulate for #52522462this.dragRegion = prepend(this.rootContainer, $('div.titlebar-drag-region'));463464// Menubar: install a custom menu bar depending on configuration465if (466!this.isAuxiliary &&467!hasNativeMenu(this.configurationService, this.titleBarStyle) &&468(!isMacintosh || isWeb) &&469this.currentMenubarVisibility !== 'compact'470) {471this.installMenubar();472}473474// Title475this.title = append(this.centerContent, $('div.window-title'));476this.createTitle();477478// Create Toolbar Actions479if (hasCustomTitlebar(this.configurationService, this.titleBarStyle)) {480this.actionToolBarElement = append(this.rightContent, $('div.action-toolbar-container'));481this.createActionToolBar();482this.createActionToolBarMenus();483}484485// Window Controls Container486if (!hasNativeTitlebar(this.configurationService, this.titleBarStyle)) {487let primaryWindowControlsLocation = isMacintosh ? 'left' : 'right';488if (isMacintosh && isNative) {489490// Check if the locale is RTL, macOS will move traffic lights in RTL locales491// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/textInfo492493const localeInfo = safeIntl.Locale(platformLocale).value as any;494if (localeInfo?.textInfo?.direction === 'rtl') {495primaryWindowControlsLocation = 'right';496}497}498499if (isMacintosh && isNative && primaryWindowControlsLocation === 'left') {500// macOS native: controls are on the left and the container is not needed to make room501// for something, except for web where a custom menu being supported). not putting the502// container helps with allowing to move the window when clicking very close to the503// window control buttons.504} else if (getWindowControlsStyle(this.configurationService) === WindowControlsStyle.HIDDEN) {505// Linux/Windows: controls are explicitly disabled506} else {507this.windowControlsContainer = append(primaryWindowControlsLocation === 'left' ? this.leftContent : this.rightContent, $('div.window-controls-container'));508if (isWeb) {509// Web: its possible to have control overlays on both sides, for example on macOS510// with window controls on the left and PWA controls on the right.511append(primaryWindowControlsLocation === 'left' ? this.rightContent : this.leftContent, $('div.window-controls-container'));512}513514if (isWCOEnabled()) {515this.windowControlsContainer.classList.add('wco-enabled');516}517}518}519520// Context menu over title bar: depending on the OS and the location of the click this will either be521// the overall context menu for the entire title bar or a specific title context menu.522// Windows / Linux: we only support the overall context menu on the title bar523// macOS: we support both the overall context menu and the title context menu.524// in addition, we allow Cmd+click to bring up the title context menu.525{526this._register(addDisposableListener(this.rootContainer, EventType.CONTEXT_MENU, e => {527EventHelper.stop(e);528529let targetMenu: MenuId;530if (isMacintosh && isHTMLElement(e.target) && isAncestor(e.target, this.title)) {531targetMenu = MenuId.TitleBarTitleContext;532} else {533targetMenu = MenuId.TitleBarContext;534}535536this.onContextMenu(e, targetMenu);537}));538539if (isMacintosh) {540this._register(addDisposableListener(this.title, EventType.MOUSE_DOWN, e => {541if (e.metaKey) {542EventHelper.stop(e, true /* stop bubbling to prevent command center from opening */);543544this.onContextMenu(e, MenuId.TitleBarTitleContext);545}546}, true /* capture phase to prevent command center from opening */));547}548}549550this.updateStyles();551552return this.element;553}554555private createTitle(): void {556this.titleDisposables.clear();557558const isShowingTitleInNativeTitlebar = hasNativeTitlebar(this.configurationService, this.titleBarStyle);559560// Text Title561if (!this.isCommandCenterVisible) {562if (!isShowingTitleInNativeTitlebar) {563this.title.textContent = this.windowTitle.value;564this.titleDisposables.add(this.windowTitle.onDidChange(() => {565this.title.textContent = this.windowTitle.value;566if (this.lastLayoutDimensions) {567this.updateLayout(this.lastLayoutDimensions); // layout menubar and other renderings in the titlebar568}569}));570} else {571reset(this.title);572}573}574575// Menu Title576else {577const commandCenter = this.instantiationService.createInstance(CommandCenterControl, this.windowTitle, this.hoverDelegate);578reset(this.title, commandCenter.element);579this.titleDisposables.add(commandCenter);580}581}582583private actionViewItemProvider(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined {584585// --- Activity Actions586if (!this.isAuxiliary) {587if (action.id === GLOBAL_ACTIVITY_ID) {588return this.instantiationService.createInstance(SimpleGlobalActivityActionViewItem, { position: () => HoverPosition.BELOW }, options);589}590if (action.id === ACCOUNTS_ACTIVITY_ID) {591return this.instantiationService.createInstance(SimpleAccountActivityActionViewItem, { position: () => HoverPosition.BELOW }, options);592}593}594595// --- Editor Actions596const activeEditorPane = this.editorGroupsContainer.activeGroup?.activeEditorPane;597if (activeEditorPane && activeEditorPane instanceof EditorPane) {598const result = activeEditorPane.getActionViewItem(action, options);599600if (result) {601return result;602}603}604605// Check extensions606return createActionViewItem(this.instantiationService, action, { ...options, menuAsChild: false });607}608609private getKeybinding(action: IAction): ResolvedKeybinding | undefined {610const editorPaneAwareContextKeyService = this.editorGroupsContainer.activeGroup?.activeEditorPane?.scopedContextKeyService ?? this.contextKeyService;611612return this.keybindingService.lookupKeybinding(action.id, editorPaneAwareContextKeyService);613}614615private createActionToolBar(): void {616617// Creates the action tool bar. Depends on the configuration of the title bar menus618// Requires to be recreated whenever editor actions enablement changes619620this.actionToolBarDisposable.clear();621622this.actionToolBar = this.actionToolBarDisposable.add(this.instantiationService.createInstance(WorkbenchToolBar, this.actionToolBarElement, {623contextMenu: MenuId.TitleBarContext,624orientation: ActionsOrientation.HORIZONTAL,625ariaLabel: localize('ariaLabelTitleActions', "Title actions"),626getKeyBinding: action => this.getKeybinding(action),627overflowBehavior: { maxItems: 9, exempted: [ACCOUNTS_ACTIVITY_ID, GLOBAL_ACTIVITY_ID, ...EDITOR_CORE_NAVIGATION_COMMANDS] },628anchorAlignmentProvider: () => AnchorAlignment.RIGHT,629telemetrySource: 'titlePart',630highlightToggledItems: this.editorActionsEnabled || this.isAuxiliary, // Only show toggled state for editor actions or auxiliary title bars631actionViewItemProvider: (action, options) => this.actionViewItemProvider(action, options),632hoverDelegate: this.hoverDelegate633}));634635if (this.editorActionsEnabled) {636this.actionToolBarDisposable.add(this.editorGroupsContainer.onDidChangeActiveGroup(() => this.createActionToolBarMenus({ editorActions: true })));637}638}639640private createActionToolBarMenus(update: true | { editorActions?: boolean; layoutActions?: boolean; globalActions?: boolean; activityActions?: boolean } = true): void {641if (update === true) {642update = { editorActions: true, layoutActions: true, globalActions: true, activityActions: true };643}644645const updateToolBarActions = () => {646const actions: IToolbarActions = { primary: [], secondary: [] };647648// --- Editor Actions649if (this.editorActionsEnabled) {650this.editorActionsChangeDisposable.clear();651652const activeGroup = this.editorGroupsContainer.activeGroup;653if (activeGroup) {654const editorActions = activeGroup.createEditorActions(this.editorActionsChangeDisposable, this.isAuxiliary && this.isCompact ? MenuId.CompactWindowEditorTitle : MenuId.EditorTitle);655656actions.primary.push(...editorActions.actions.primary);657actions.secondary.push(...editorActions.actions.secondary);658659this.editorActionsChangeDisposable.add(editorActions.onDidChange(() => updateToolBarActions()));660}661}662663// --- Global Actions664if (this.globalToolbarMenu) {665fillInActionBarActions(666this.globalToolbarMenu.getActions(),667actions668);669}670671// --- Layout Actions672if (this.layoutToolbarMenu) {673fillInActionBarActions(674this.layoutToolbarMenu.getActions(),675actions,676() => !this.editorActionsEnabled || this.isCompact // layout actions move to "..." if editor actions are enabled unless compact677);678}679680// --- Activity Actions (always at the end)681if (this.activityActionsEnabled) {682if (isAccountsActionVisible(this.storageService)) {683actions.primary.push(ACCOUNTS_ACTIVITY_TILE_ACTION);684}685686actions.primary.push(GLOBAL_ACTIVITY_TITLE_ACTION);687}688689this.actionToolBar.setActions(prepareActions(actions.primary), prepareActions(actions.secondary));690};691692// Create/Update the menus which should be in the title tool bar693694if (update.editorActions) {695this.editorToolbarMenuDisposables.clear();696697// The editor toolbar menu is handled by the editor group so we do not need to manage it here.698// However, depending on the active editor, we need to update the context and action runner of the toolbar menu.699if (this.editorActionsEnabled && this.editorService.activeEditor !== undefined) {700const context: IEditorCommandsContext = { groupId: this.editorGroupsContainer.activeGroup.id };701702this.actionToolBar.actionRunner = this.editorToolbarMenuDisposables.add(new EditorCommandsContextActionRunner(context));703this.actionToolBar.context = context;704} else {705this.actionToolBar.actionRunner = this.editorToolbarMenuDisposables.add(new ActionRunner());706this.actionToolBar.context = undefined;707}708}709710if (update.layoutActions) {711this.layoutToolbarMenuDisposables.clear();712713if (this.layoutControlEnabled) {714this.layoutToolbarMenu = this.menuService.createMenu(MenuId.LayoutControlMenu, this.contextKeyService);715716this.layoutToolbarMenuDisposables.add(this.layoutToolbarMenu);717this.layoutToolbarMenuDisposables.add(this.layoutToolbarMenu.onDidChange(() => updateToolBarActions()));718} else {719this.layoutToolbarMenu = undefined;720}721}722723if (update.globalActions) {724this.globalToolbarMenuDisposables.clear();725726if (this.globalActionsEnabled) {727this.globalToolbarMenu = this.menuService.createMenu(MenuId.TitleBar, this.contextKeyService);728729this.globalToolbarMenuDisposables.add(this.globalToolbarMenu);730this.globalToolbarMenuDisposables.add(this.globalToolbarMenu.onDidChange(() => updateToolBarActions()));731} else {732this.globalToolbarMenu = undefined;733}734}735736if (update.activityActions) {737this.activityToolbarDisposables.clear();738if (this.activityActionsEnabled) {739this.activityToolbarDisposables.add(this.storageService.onDidChangeValue(StorageScope.PROFILE, AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, this._store)(() => updateToolBarActions()));740}741}742743updateToolBarActions();744}745746override updateStyles(): void {747super.updateStyles();748749// Part container750if (this.element) {751if (this.isInactive) {752this.element.classList.add('inactive');753} else {754this.element.classList.remove('inactive');755}756757const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND, (color, theme) => {758// LCD Rendering Support: the title bar part is a defining its own GPU layer.759// To benefit from LCD font rendering, we must ensure that we always set an760// opaque background color. As such, we compute an opaque color given we know761// the background color is the workbench background.762return color.isOpaque() ? color : color.makeOpaque(WORKBENCH_BACKGROUND(theme));763}) || '';764this.element.style.backgroundColor = titleBackground;765766if (this.appIconBadge) {767this.appIconBadge.style.backgroundColor = titleBackground;768}769770if (titleBackground && Color.fromHex(titleBackground).isLighter()) {771this.element.classList.add('light');772} else {773this.element.classList.remove('light');774}775776const titleForeground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_FOREGROUND : TITLE_BAR_ACTIVE_FOREGROUND);777this.element.style.color = titleForeground || '';778779const titleBorder = this.getColor(TITLE_BAR_BORDER);780this.element.style.borderBottom = titleBorder ? `1px solid ${titleBorder}` : '';781}782}783784protected onContextMenu(e: MouseEvent, menuId: MenuId): void {785const event = new StandardMouseEvent(getWindow(this.element), e);786787// Show it788this.contextMenuService.showContextMenu({789getAnchor: () => event,790menuId,791contextKeyService: this.contextKeyService,792domForShadowRoot: isMacintosh && isNative ? event.target : undefined793});794}795796protected get currentMenubarVisibility(): MenuBarVisibility {797if (this.isAuxiliary) {798return 'hidden';799}800801return getMenuBarVisibility(this.configurationService);802}803804private get layoutControlEnabled(): boolean {805return this.configurationService.getValue<boolean>(LayoutSettings.LAYOUT_ACTIONS) !== false;806}807808protected get isCommandCenterVisible() {809return !this.isCompact && this.configurationService.getValue<boolean>(LayoutSettings.COMMAND_CENTER) !== false;810}811812private get editorActionsEnabled(): boolean {813return (this.editorGroupsContainer.partOptions.editorActionsLocation === EditorActionsLocation.TITLEBAR ||814(815this.editorGroupsContainer.partOptions.editorActionsLocation === EditorActionsLocation.DEFAULT &&816this.editorGroupsContainer.partOptions.showTabs === EditorTabsMode.NONE817));818}819820private get activityActionsEnabled(): boolean {821const activityBarPosition = this.configurationService.getValue<ActivityBarPosition>(LayoutSettings.ACTIVITY_BAR_LOCATION);822return !this.isCompact && !this.isAuxiliary && (activityBarPosition === ActivityBarPosition.TOP || activityBarPosition === ActivityBarPosition.BOTTOM);823}824825private get globalActionsEnabled(): boolean {826return !this.isCompact;827}828829get hasZoomableElements(): boolean {830const hasMenubar = !(this.currentMenubarVisibility === 'hidden' || this.currentMenubarVisibility === 'compact' || (!isWeb && isMacintosh));831const hasCommandCenter = this.isCommandCenterVisible;832const hasToolBarActions = this.globalActionsEnabled || this.layoutControlEnabled || this.editorActionsEnabled || this.activityActionsEnabled;833return hasMenubar || hasCommandCenter || hasToolBarActions;834}835836get preventZoom(): boolean {837// Prevent zooming behavior if any of the following conditions are met:838// 1. Shrinking below the window control size (zoom < 1)839// 2. No custom items are present in the title bar840841return getZoomFactor(getWindow(this.element)) < 1 || !this.hasZoomableElements;842}843844override layout(width: number, height: number): void {845this.updateLayout(new Dimension(width, height));846847super.layoutContents(width, height);848}849850private updateLayout(dimension: Dimension): void {851this.lastLayoutDimensions = dimension;852853if (!hasCustomTitlebar(this.configurationService, this.titleBarStyle)) {854return;855}856857const zoomFactor = getZoomFactor(getWindow(this.element));858859this.element.style.setProperty('--zoom-factor', zoomFactor.toString());860this.rootContainer.classList.toggle('counter-zoom', this.preventZoom);861862if (this.customMenubar.value) {863const menubarDimension = new Dimension(0, dimension.height);864this.customMenubar.value.layout(menubarDimension);865}866867const hasCenter = this.isCommandCenterVisible || this.title.textContent !== '';868this.rootContainer.classList.toggle('has-center', hasCenter);869}870871focus(): void {872if (this.customMenubar.value) {873this.customMenubar.value.toggleFocus();874} else {875(this.element.querySelector('[tabindex]:not([tabindex="-1"])') as HTMLElement | null)?.focus();876}877}878879toJSON(): object {880return {881type: Parts.TITLEBAR_PART882};883}884885override dispose(): void {886this._onWillDispose.fire();887888super.dispose();889}890}891892export class MainBrowserTitlebarPart extends BrowserTitlebarPart {893894constructor(895@IContextMenuService contextMenuService: IContextMenuService,896@IConfigurationService configurationService: IConfigurationService,897@IBrowserWorkbenchEnvironmentService environmentService: IBrowserWorkbenchEnvironmentService,898@IInstantiationService instantiationService: IInstantiationService,899@IThemeService themeService: IThemeService,900@IStorageService storageService: IStorageService,901@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,902@IContextKeyService contextKeyService: IContextKeyService,903@IHostService hostService: IHostService,904@IEditorGroupsService editorGroupService: IEditorGroupsService,905@IEditorService editorService: IEditorService,906@IMenuService menuService: IMenuService,907@IKeybindingService keybindingService: IKeybindingService,908) {909super(Parts.TITLEBAR_PART, mainWindow, editorGroupService.mainPart, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, editorService, menuService, keybindingService);910}911}912913export interface IAuxiliaryTitlebarPart extends ITitlebarPart, IView {914readonly container: HTMLElement;915readonly height: number;916917updateOptions(options: { compact: boolean }): void;918}919920export class AuxiliaryBrowserTitlebarPart extends BrowserTitlebarPart implements IAuxiliaryTitlebarPart {921922private static COUNTER = 1;923924get height() { return this.minimumHeight; }925926constructor(927readonly container: HTMLElement,928editorGroupsContainer: IEditorGroupsContainer,929private readonly mainTitlebar: BrowserTitlebarPart,930@IContextMenuService contextMenuService: IContextMenuService,931@IConfigurationService configurationService: IConfigurationService,932@IBrowserWorkbenchEnvironmentService environmentService: IBrowserWorkbenchEnvironmentService,933@IInstantiationService instantiationService: IInstantiationService,934@IThemeService themeService: IThemeService,935@IStorageService storageService: IStorageService,936@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,937@IContextKeyService contextKeyService: IContextKeyService,938@IHostService hostService: IHostService,939@IEditorGroupsService editorGroupService: IEditorGroupsService,940@IEditorService editorService: IEditorService,941@IMenuService menuService: IMenuService,942@IKeybindingService keybindingService: IKeybindingService,943) {944const id = AuxiliaryBrowserTitlebarPart.COUNTER++;945super(`workbench.parts.auxiliaryTitle.${id}`, getWindow(container), editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, editorService, menuService, keybindingService);946}947948override get preventZoom(): boolean {949950// Prevent zooming behavior if any of the following conditions are met:951// 1. Shrinking below the window control size (zoom < 1)952// 2. No custom items are present in the main title bar953// The auxiliary title bar never contains any zoomable items itself,954// but we want to match the behavior of the main title bar.955956return getZoomFactor(getWindow(this.element)) < 1 || !this.mainTitlebar.hasZoomableElements;957}958}959960961