Path: blob/main/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts
5310 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 } 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;494const textInfo = (localeInfo as { textInfo?: unknown }).textInfo;495if (textInfo && typeof textInfo === 'object' && 'direction' in textInfo && textInfo.direction === 'rtl') {496primaryWindowControlsLocation = 'right';497}498}499500if (isMacintosh && isNative && primaryWindowControlsLocation === 'left') {501// macOS native: controls are on the left and the container is not needed to make room502// for something, except for web where a custom menu being supported). not putting the503// container helps with allowing to move the window when clicking very close to the504// window control buttons.505} else if (getWindowControlsStyle(this.configurationService) === WindowControlsStyle.HIDDEN) {506// Linux/Windows: controls are explicitly disabled507} else {508this.windowControlsContainer = append(primaryWindowControlsLocation === 'left' ? this.leftContent : this.rightContent, $('div.window-controls-container'));509if (isWeb) {510// Web: its possible to have control overlays on both sides, for example on macOS511// with window controls on the left and PWA controls on the right.512append(primaryWindowControlsLocation === 'left' ? this.rightContent : this.leftContent, $('div.window-controls-container'));513}514515if (isWCOEnabled()) {516this.windowControlsContainer.classList.add('wco-enabled');517}518}519}520521// Context menu over title bar: depending on the OS and the location of the click this will either be522// the overall context menu for the entire title bar or a specific title context menu.523// Windows / Linux: we only support the overall context menu on the title bar524// macOS: we support both the overall context menu and the title context menu.525// in addition, we allow Cmd+click to bring up the title context menu.526{527this._register(addDisposableListener(this.rootContainer, EventType.CONTEXT_MENU, e => {528EventHelper.stop(e);529530let targetMenu: MenuId;531if (isMacintosh && isHTMLElement(e.target) && isAncestor(e.target, this.title)) {532targetMenu = MenuId.TitleBarTitleContext;533} else {534targetMenu = MenuId.TitleBarContext;535}536537this.onContextMenu(e, targetMenu);538}));539540if (isMacintosh) {541this._register(addDisposableListener(this.title, EventType.MOUSE_DOWN, e => {542if (e.metaKey) {543EventHelper.stop(e, true /* stop bubbling to prevent command center from opening */);544545this.onContextMenu(e, MenuId.TitleBarTitleContext);546}547}, true /* capture phase to prevent command center from opening */));548}549}550551this.updateStyles();552553return this.element;554}555556private createTitle(): void {557this.titleDisposables.clear();558559const isShowingTitleInNativeTitlebar = hasNativeTitlebar(this.configurationService, this.titleBarStyle);560561// Text Title562if (!this.isCommandCenterVisible) {563if (!isShowingTitleInNativeTitlebar) {564this.title.textContent = this.windowTitle.value;565this.titleDisposables.add(this.windowTitle.onDidChange(() => {566this.title.textContent = this.windowTitle.value;567if (this.lastLayoutDimensions) {568this.updateLayout(this.lastLayoutDimensions); // layout menubar and other renderings in the titlebar569}570}));571} else {572reset(this.title);573}574}575576// Menu Title577else {578const commandCenter = this.instantiationService.createInstance(CommandCenterControl, this.windowTitle, this.hoverDelegate);579reset(this.title, commandCenter.element);580this.titleDisposables.add(commandCenter);581}582}583584private actionViewItemProvider(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined {585586// --- Activity Actions587if (!this.isAuxiliary) {588if (action.id === GLOBAL_ACTIVITY_ID) {589return this.instantiationService.createInstance(SimpleGlobalActivityActionViewItem, { position: () => HoverPosition.BELOW }, options);590}591if (action.id === ACCOUNTS_ACTIVITY_ID) {592return this.instantiationService.createInstance(SimpleAccountActivityActionViewItem, { position: () => HoverPosition.BELOW }, options);593}594}595596// --- Editor Actions597const activeEditorPane = this.editorGroupsContainer.activeGroup?.activeEditorPane;598if (activeEditorPane && activeEditorPane instanceof EditorPane) {599const result = activeEditorPane.getActionViewItem(action, options);600601if (result) {602return result;603}604}605606// Check extensions607return createActionViewItem(this.instantiationService, action, { ...options, menuAsChild: false });608}609610private getKeybinding(action: IAction): ResolvedKeybinding | undefined {611const editorPaneAwareContextKeyService = this.editorGroupsContainer.activeGroup?.activeEditorPane?.scopedContextKeyService ?? this.contextKeyService;612613return this.keybindingService.lookupKeybinding(action.id, editorPaneAwareContextKeyService);614}615616private createActionToolBar(): void {617618// Creates the action tool bar. Depends on the configuration of the title bar menus619// Requires to be recreated whenever editor actions enablement changes620621this.actionToolBarDisposable.clear();622623this.actionToolBar = this.actionToolBarDisposable.add(this.instantiationService.createInstance(WorkbenchToolBar, this.actionToolBarElement, {624contextMenu: MenuId.TitleBarContext,625orientation: ActionsOrientation.HORIZONTAL,626ariaLabel: localize('ariaLabelTitleActions', "Title actions"),627getKeyBinding: action => this.getKeybinding(action),628overflowBehavior: { maxItems: 9, exempted: [ACCOUNTS_ACTIVITY_ID, GLOBAL_ACTIVITY_ID, ...EDITOR_CORE_NAVIGATION_COMMANDS] },629anchorAlignmentProvider: () => AnchorAlignment.RIGHT,630telemetrySource: 'titlePart',631highlightToggledItems: this.editorActionsEnabled || this.isAuxiliary, // Only show toggled state for editor actions or auxiliary title bars632actionViewItemProvider: (action, options) => this.actionViewItemProvider(action, options),633hoverDelegate: this.hoverDelegate634}));635636if (this.editorActionsEnabled) {637this.actionToolBarDisposable.add(this.editorGroupsContainer.onDidChangeActiveGroup(() => this.createActionToolBarMenus({ editorActions: true })));638}639}640641private createActionToolBarMenus(update: true | { editorActions?: boolean; layoutActions?: boolean; globalActions?: boolean; activityActions?: boolean } = true): void {642if (update === true) {643update = { editorActions: true, layoutActions: true, globalActions: true, activityActions: true };644}645646const updateToolBarActions = () => {647const actions: IToolbarActions = { primary: [], secondary: [] };648649// --- Editor Actions650if (this.editorActionsEnabled) {651this.editorActionsChangeDisposable.clear();652653const activeGroup = this.editorGroupsContainer.activeGroup;654if (activeGroup) {655const editorActions = activeGroup.createEditorActions(this.editorActionsChangeDisposable, this.isAuxiliary && this.isCompact ? MenuId.CompactWindowEditorTitle : MenuId.EditorTitle);656657actions.primary.push(...editorActions.actions.primary);658actions.secondary.push(...editorActions.actions.secondary);659660this.editorActionsChangeDisposable.add(editorActions.onDidChange(() => updateToolBarActions()));661}662}663664// --- Global Actions665if (this.globalToolbarMenu) {666fillInActionBarActions(667this.globalToolbarMenu.getActions(),668actions669);670}671672// --- Layout Actions673if (this.layoutToolbarMenu) {674fillInActionBarActions(675this.layoutToolbarMenu.getActions(),676actions,677() => !this.editorActionsEnabled || this.isCompact // layout actions move to "..." if editor actions are enabled unless compact678);679}680681// --- Activity Actions (always at the end)682if (this.activityActionsEnabled) {683if (isAccountsActionVisible(this.storageService)) {684actions.primary.push(ACCOUNTS_ACTIVITY_TILE_ACTION);685}686687actions.primary.push(GLOBAL_ACTIVITY_TITLE_ACTION);688}689690this.actionToolBar.setActions(prepareActions(actions.primary), prepareActions(actions.secondary));691};692693// Create/Update the menus which should be in the title tool bar694695if (update.editorActions) {696this.editorToolbarMenuDisposables.clear();697698// The editor toolbar menu is handled by the editor group so we do not need to manage it here.699// However, depending on the active editor, we need to update the context and action runner of the toolbar menu.700if (this.editorActionsEnabled && this.editorService.activeEditor !== undefined) {701const context: IEditorCommandsContext = { groupId: this.editorGroupsContainer.activeGroup.id };702703this.actionToolBar.actionRunner = this.editorToolbarMenuDisposables.add(new EditorCommandsContextActionRunner(context));704this.actionToolBar.context = context;705} else {706this.actionToolBar.actionRunner = this.editorToolbarMenuDisposables.add(new ActionRunner());707this.actionToolBar.context = undefined;708}709}710711if (update.layoutActions) {712this.layoutToolbarMenuDisposables.clear();713714if (this.layoutControlEnabled) {715this.layoutToolbarMenu = this.menuService.createMenu(MenuId.LayoutControlMenu, this.contextKeyService);716717this.layoutToolbarMenuDisposables.add(this.layoutToolbarMenu);718this.layoutToolbarMenuDisposables.add(this.layoutToolbarMenu.onDidChange(() => updateToolBarActions()));719} else {720this.layoutToolbarMenu = undefined;721}722}723724if (update.globalActions) {725this.globalToolbarMenuDisposables.clear();726727if (this.globalActionsEnabled) {728this.globalToolbarMenu = this.menuService.createMenu(MenuId.TitleBar, this.contextKeyService);729730this.globalToolbarMenuDisposables.add(this.globalToolbarMenu);731this.globalToolbarMenuDisposables.add(this.globalToolbarMenu.onDidChange(() => updateToolBarActions()));732} else {733this.globalToolbarMenu = undefined;734}735}736737if (update.activityActions) {738this.activityToolbarDisposables.clear();739if (this.activityActionsEnabled) {740this.activityToolbarDisposables.add(this.storageService.onDidChangeValue(StorageScope.PROFILE, AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, this._store)(() => updateToolBarActions()));741}742}743744updateToolBarActions();745}746747override updateStyles(): void {748super.updateStyles();749750// Part container751if (this.element) {752if (this.isInactive) {753this.element.classList.add('inactive');754} else {755this.element.classList.remove('inactive');756}757758const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND, (color, theme) => {759// LCD Rendering Support: the title bar part is a defining its own GPU layer.760// To benefit from LCD font rendering, we must ensure that we always set an761// opaque background color. As such, we compute an opaque color given we know762// the background color is the workbench background.763return color.isOpaque() ? color : color.makeOpaque(WORKBENCH_BACKGROUND(theme));764}) || '';765this.element.style.backgroundColor = titleBackground;766767if (this.appIconBadge) {768this.appIconBadge.style.backgroundColor = titleBackground;769}770771if (titleBackground && Color.fromHex(titleBackground).isLighter()) {772this.element.classList.add('light');773} else {774this.element.classList.remove('light');775}776777const titleForeground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_FOREGROUND : TITLE_BAR_ACTIVE_FOREGROUND);778this.element.style.color = titleForeground || '';779780const titleBorder = this.getColor(TITLE_BAR_BORDER);781this.element.style.borderBottom = titleBorder ? `1px solid ${titleBorder}` : '';782}783}784785protected onContextMenu(e: MouseEvent, menuId: MenuId): void {786const event = new StandardMouseEvent(getWindow(this.element), e);787788// Show it789this.contextMenuService.showContextMenu({790getAnchor: () => event,791menuId,792contextKeyService: this.contextKeyService,793domForShadowRoot: isMacintosh && isNative ? event.target : undefined794});795}796797protected get currentMenubarVisibility(): MenuBarVisibility {798if (this.isAuxiliary) {799return 'hidden';800}801802return getMenuBarVisibility(this.configurationService);803}804805private get layoutControlEnabled(): boolean {806return this.configurationService.getValue<boolean>(LayoutSettings.LAYOUT_ACTIONS) !== false;807}808809protected get isCommandCenterVisible() {810return !this.isCompact && this.configurationService.getValue<boolean>(LayoutSettings.COMMAND_CENTER) !== false;811}812813private get editorActionsEnabled(): boolean {814return (this.editorGroupsContainer.partOptions.editorActionsLocation === EditorActionsLocation.TITLEBAR ||815(816this.editorGroupsContainer.partOptions.editorActionsLocation === EditorActionsLocation.DEFAULT &&817this.editorGroupsContainer.partOptions.showTabs === EditorTabsMode.NONE818));819}820821private get activityActionsEnabled(): boolean {822const activityBarPosition = this.configurationService.getValue<ActivityBarPosition>(LayoutSettings.ACTIVITY_BAR_LOCATION);823return !this.isCompact && !this.isAuxiliary && (activityBarPosition === ActivityBarPosition.TOP || activityBarPosition === ActivityBarPosition.BOTTOM);824}825826private get globalActionsEnabled(): boolean {827return !this.isCompact;828}829830get hasZoomableElements(): boolean {831const hasMenubar = !(this.currentMenubarVisibility === 'hidden' || this.currentMenubarVisibility === 'compact' || (!isWeb && isMacintosh));832const hasCommandCenter = this.isCommandCenterVisible;833const hasToolBarActions = this.globalActionsEnabled || this.layoutControlEnabled || this.editorActionsEnabled || this.activityActionsEnabled;834return hasMenubar || hasCommandCenter || hasToolBarActions;835}836837get preventZoom(): boolean {838// Prevent zooming behavior if any of the following conditions are met:839// 1. Shrinking below the window control size (zoom < 1)840// 2. No custom items are present in the title bar841842return getZoomFactor(getWindow(this.element)) < 1 || !this.hasZoomableElements;843}844845override layout(width: number, height: number): void {846this.updateLayout(new Dimension(width, height));847848super.layoutContents(width, height);849}850851private updateLayout(dimension: Dimension): void {852this.lastLayoutDimensions = dimension;853854if (!hasCustomTitlebar(this.configurationService, this.titleBarStyle)) {855return;856}857858const zoomFactor = getZoomFactor(getWindow(this.element));859860this.element.style.setProperty('--zoom-factor', zoomFactor.toString());861this.rootContainer.classList.toggle('counter-zoom', this.preventZoom);862863if (this.customMenubar.value) {864const menubarDimension = new Dimension(0, dimension.height);865this.customMenubar.value.layout(menubarDimension);866}867868const hasCenter = this.isCommandCenterVisible || this.title.textContent !== '';869this.rootContainer.classList.toggle('has-center', hasCenter);870}871872focus(): void {873if (this.customMenubar.value) {874this.customMenubar.value.toggleFocus();875} else {876// eslint-disable-next-line no-restricted-syntax877(this.element.querySelector('[tabindex]:not([tabindex="-1"])') as HTMLElement | null)?.focus();878}879}880881toJSON(): object {882return {883type: Parts.TITLEBAR_PART884};885}886887override dispose(): void {888this._onWillDispose.fire();889890super.dispose();891}892}893894export class MainBrowserTitlebarPart extends BrowserTitlebarPart {895896constructor(897@IContextMenuService contextMenuService: IContextMenuService,898@IConfigurationService configurationService: IConfigurationService,899@IBrowserWorkbenchEnvironmentService environmentService: IBrowserWorkbenchEnvironmentService,900@IInstantiationService instantiationService: IInstantiationService,901@IThemeService themeService: IThemeService,902@IStorageService storageService: IStorageService,903@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,904@IContextKeyService contextKeyService: IContextKeyService,905@IHostService hostService: IHostService,906@IEditorGroupsService editorGroupService: IEditorGroupsService,907@IEditorService editorService: IEditorService,908@IMenuService menuService: IMenuService,909@IKeybindingService keybindingService: IKeybindingService,910) {911super(Parts.TITLEBAR_PART, mainWindow, editorGroupService.mainPart, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, editorService, menuService, keybindingService);912}913}914915export interface IAuxiliaryTitlebarPart extends ITitlebarPart, IView {916readonly container: HTMLElement;917readonly height: number;918919updateOptions(options: { compact: boolean }): void;920}921922export class AuxiliaryBrowserTitlebarPart extends BrowserTitlebarPart implements IAuxiliaryTitlebarPart {923924private static COUNTER = 1;925926get height() { return this.minimumHeight; }927928constructor(929readonly container: HTMLElement,930editorGroupsContainer: IEditorGroupsContainer,931private readonly mainTitlebar: BrowserTitlebarPart,932@IContextMenuService contextMenuService: IContextMenuService,933@IConfigurationService configurationService: IConfigurationService,934@IBrowserWorkbenchEnvironmentService environmentService: IBrowserWorkbenchEnvironmentService,935@IInstantiationService instantiationService: IInstantiationService,936@IThemeService themeService: IThemeService,937@IStorageService storageService: IStorageService,938@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,939@IContextKeyService contextKeyService: IContextKeyService,940@IHostService hostService: IHostService,941@IEditorGroupsService editorGroupService: IEditorGroupsService,942@IEditorService editorService: IEditorService,943@IMenuService menuService: IMenuService,944@IKeybindingService keybindingService: IKeybindingService,945) {946const id = AuxiliaryBrowserTitlebarPart.COUNTER++;947super(`workbench.parts.auxiliaryTitle.${id}`, getWindow(container), editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, editorService, menuService, keybindingService);948}949950override get preventZoom(): boolean {951952// Prevent zooming behavior if any of the following conditions are met:953// 1. Shrinking below the window control size (zoom < 1)954// 2. No custom items are present in the main title bar955// The auxiliary title bar never contains any zoomable items itself,956// but we want to match the behavior of the main title bar.957958return getZoomFactor(getWindow(this.element)) < 1 || !this.mainTitlebar.hasZoomableElements;959}960}961962963