titlebarPart.ts 24.1 KB
Newer Older
B
Benjamin Pasero 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import 'vs/css!./media/titlebarpart';
7
import * as resources from 'vs/base/common/resources';
B
Benjamin Pasero 已提交
8
import { Part } from 'vs/workbench/browser/part';
9
import { ITitleService, ITitleProperties } from 'vs/workbench/services/title/common/titleService';
B
Benjamin Pasero 已提交
10
import { getZoomFactor } from 'vs/base/browser/browser';
11
import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility } from 'vs/platform/windows/common/windows';
B
Benjamin Pasero 已提交
12 13
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
14
import { IAction } from 'vs/base/common/actions';
15
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
B
Benjamin Pasero 已提交
16
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
17
import { DisposableStore, dispose } from 'vs/base/common/lifecycle';
18
import * as nls from 'vs/nls';
B
Benjamin Pasero 已提交
19
import { toResource, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor';
20
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
21
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
S
SteVen Batten 已提交
22
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
23
import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme';
24
import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform';
25
import { URI } from 'vs/base/common/uri';
R
Ryan Adolf 已提交
26
import { Color } from 'vs/base/common/color';
B
Benjamin Pasero 已提交
27
import { trim } from 'vs/base/common/strings';
S
SteVen Batten 已提交
28
import { EventType, EventHelper, Dimension, isAncestor, hide, show, removeClass, addClass, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame, removeNode } from 'vs/base/browser/dom';
29
import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl';
30
import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation';
31
import { template } from 'vs/base/common/labels';
I
isidor 已提交
32
import { ILabelService } from 'vs/platform/label/common/label';
B
Benjamin Pasero 已提交
33
import { Emitter } from 'vs/base/common/event';
B
Benjamin Pasero 已提交
34
import { IStorageService } from 'vs/platform/storage/common/storage';
35
import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
36
import { RunOnceScheduler } from 'vs/base/common/async';
37
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
38 39 40
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
41
import { IHostService } from 'vs/workbench/services/host/browser/host';
42
import { IProductService } from 'vs/platform/product/common/productService';
43
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
B
Benjamin Pasero 已提交
44

45
// TODO@sbatten https://github.com/microsoft/vscode/issues/81360
46
// eslint-disable-next-line code-layering, code-import-patterns
47 48
import { IElectronService } from 'vs/platform/electron/node/electron';

49
export class TitlebarPart extends Part implements ITitleService {
B
Benjamin Pasero 已提交
50

51
	private static readonly NLS_UNSUPPORTED = nls.localize('patchedWindowTitle', "[Unsupported]");
52
	private static readonly NLS_USER_IS_ADMIN = isWindows ? nls.localize('userIsAdmin', "[Administrator]") : nls.localize('userIsSudo', "[Superuser]");
53 54 55
	private static readonly NLS_EXTENSION_HOST = nls.localize('devExtensionWindowTitlePrefix', "[Extension Development Host]");
	private static readonly TITLE_DIRTY = '\u25cf ';
	private static readonly TITLE_SEPARATOR = isMacintosh ? '' : ' - '; // macOS uses special - separator
56

57
	//#region IView
58

B
Benjamin Pasero 已提交
59 60
	readonly minimumWidth: number = 0;
	readonly maximumWidth: number = Number.POSITIVE_INFINITY;
S
SteVen Batten 已提交
61 62
	get minimumHeight(): number { return isMacintosh && !isWeb ? 22 / getZoomFactor() : (30 / (this.currentMenubarVisibility === 'hidden' ? getZoomFactor() : 1)); }
	get maximumHeight(): number { return isMacintosh && !isWeb ? 22 / getZoomFactor() : (30 / (this.currentMenubarVisibility === 'hidden' ? getZoomFactor() : 1)); }
B
Benjamin Pasero 已提交
63

64
	//#endregion
65

66
	private _onMenubarVisibilityChange = this._register(new Emitter<boolean>());
B
Benjamin Pasero 已提交
67
	readonly onMenubarVisibilityChange = this._onMenubarVisibilityChange.event;
68

69
	_serviceBrand: undefined;
B
Benjamin Pasero 已提交
70

S
SteVen Batten 已提交
71 72 73 74 75
	private title!: HTMLElement;
	private dragRegion: HTMLElement | undefined;
	private windowControls: HTMLElement | undefined;
	private maxRestoreControl: HTMLElement | undefined;
	private appIcon: HTMLElement | undefined;
76
	private customMenubar: CustomMenubarControl | undefined;
S
SteVen Batten 已提交
77
	private menubar?: HTMLElement;
S
SteVen Batten 已提交
78 79
	private resizer: HTMLElement | undefined;
	private lastLayoutDimensions: Dimension | undefined;
80
	private titleBarStyle: 'native' | 'custom';
B
Benjamin Pasero 已提交
81

S
SteVen Batten 已提交
82
	private pendingTitle: string | undefined;
83

S
SteVen Batten 已提交
84
	private isInactive: boolean = false;
B
Benjamin Pasero 已提交
85

86 87
	private readonly properties: ITitleProperties = { isPure: true, isAdmin: false };
	private readonly activeEditorListeners = this._register(new DisposableStore());
88

89 90
	private titleUpdater: RunOnceScheduler = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0));

91 92
	private contextMenu: IMenu;

93
	constructor(
94 95 96
		@IContextMenuService private readonly contextMenuService: IContextMenuService,
		@IConfigurationService private readonly configurationService: IConfigurationService,
		@IEditorService private readonly editorService: IEditorService,
97
		@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
98 99
		@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
		@IInstantiationService private readonly instantiationService: IInstantiationService,
I
isidor 已提交
100
		@IThemeService themeService: IThemeService,
101
		@ILabelService private readonly labelService: ILabelService,
102
		@IStorageService storageService: IStorageService,
103 104
		@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
		@IMenuService menuService: IMenuService,
105
		@IContextKeyService contextKeyService: IContextKeyService,
106
		@IHostService private readonly hostService: IHostService,
107
		@IProductService private readonly productService: IProductService,
108
		@optional(IElectronService) private electronService: IElectronService
109
	) {
110
		super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
111

112 113
		this.contextMenu = this._register(menuService.createMenu(MenuId.TitleBarContext, contextKeyService));

114 115
		this.titleBarStyle = getTitleBarStyle(this.configurationService, this.environmentService);

116 117 118 119
		this.registerListeners();
	}

	private registerListeners(): void {
120
		this._register(this.hostService.onDidChangeFocus(focused => focused ? this.onFocus() : this.onBlur()));
B
Benjamin Pasero 已提交
121 122
		this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChanged(e)));
		this._register(this.editorService.onDidActiveEditorChange(() => this.onActiveEditorChange()));
123 124 125 126
		this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.titleUpdater.schedule()));
		this._register(this.contextService.onDidChangeWorkbenchState(() => this.titleUpdater.schedule()));
		this._register(this.contextService.onDidChangeWorkspaceName(() => this.titleUpdater.schedule()));
		this._register(this.labelService.onDidChangeFormatters(() => this.titleUpdater.schedule()));
127 128
	}

B
Benjamin Pasero 已提交
129 130 131 132 133 134 135 136 137 138
	private onBlur(): void {
		this.isInactive = true;
		this.updateStyles();
	}

	private onFocus(): void {
		this.isInactive = false;
		this.updateStyles();
	}

139 140
	private onConfigurationChanged(event: IConfigurationChangeEvent): void {
		if (event.affectsConfiguration('window.title')) {
141
			this.titleUpdater.schedule();
142
		}
S
SteVen Batten 已提交
143

144 145 146 147 148 149 150
		if (this.titleBarStyle !== 'native') {
			if (event.affectsConfiguration('window.menuBarVisibility')) {
				if (this.currentMenubarVisibility === 'compact') {
					this.uninstallMenubar();
				} else {
					this.installMenubar();
				}
S
SteVen Batten 已提交
151 152 153
			}
		}

S
SteVen Batten 已提交
154 155 156 157 158
		if (event.affectsConfiguration('window.doubleClickIconToClose')) {
			if (this.appIcon) {
				this.onUpdateAppIconDragBehavior();
			}
		}
159 160
	}

S
SteVen Batten 已提交
161
	private onMenubarVisibilityChanged(visible: boolean) {
162
		if (isWeb || isWindows || isLinux) {
S
SteVen Batten 已提交
163
			// Hide title when toggling menu bar
S
SteVen Batten 已提交
164
			if (!isWeb && this.currentMenubarVisibility === 'toggle' && visible) {
S
SteVen Batten 已提交
165
				// Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor
S
SteVen Batten 已提交
166 167 168 169
				if (this.dragRegion) {
					hide(this.dragRegion);
					setTimeout(() => show(this.dragRegion!), 50);
				}
S
SteVen Batten 已提交
170
			}
S
SteVen Batten 已提交
171 172

			this.adjustTitleMarginToCenter();
173 174

			this._onMenubarVisibilityChange.fire(visible);
S
SteVen Batten 已提交
175
		}
176 177
	}

S
SteVen Batten 已提交
178
	private onMenubarFocusChanged(focused: boolean) {
179
		if (!isWeb && (isWindows || isLinux) && this.currentMenubarVisibility !== 'compact' && this.dragRegion) {
S
SteVen Batten 已提交
180 181 182 183 184 185 186 187
			if (focused) {
				hide(this.dragRegion);
			} else {
				show(this.dragRegion);
			}
		}
	}

B
Benjamin Pasero 已提交
188
	private onActiveEditorChange(): void {
189 190

		// Dispose old listeners
191
		this.activeEditorListeners.clear();
192 193

		// Calculate New Window Title
194
		this.titleUpdater.schedule();
195 196

		// Apply listener for dirty and label changes
B
Benjamin Pasero 已提交
197
		const activeEditor = this.editorService.activeEditor;
B
Benjamin Pasero 已提交
198
		if (activeEditor) {
199 200
			this.activeEditorListeners.add(activeEditor.onDidChangeDirty(() => this.titleUpdater.schedule()));
			this.activeEditorListeners.add(activeEditor.onDidChangeLabel(() => this.titleUpdater.schedule()));
201 202 203
		}
	}

B
Benjamin Pasero 已提交
204 205 206 207 208 209
	private doUpdateTitle(): void {
		const title = this.getWindowTitle();

		// Always set the native window title to identify us properly to the OS
		let nativeTitle = title;
		if (!trim(nativeTitle)) {
210
			nativeTitle = this.productService.nameLong;
B
Benjamin Pasero 已提交
211 212 213 214 215 216 217 218 219
		}
		window.document.title = nativeTitle;

		// Apply custom title if we can
		if (this.title) {
			this.title.innerText = title;
		} else {
			this.pendingTitle = title;
		}
S
SteVen Batten 已提交
220

221
		if ((isWeb || isWindows || isLinux) && this.title) {
S
SteVen Batten 已提交
222 223 224
			if (this.lastLayoutDimensions) {
				this.updateLayout(this.lastLayoutDimensions);
			}
225
		}
B
Benjamin Pasero 已提交
226 227
	}

228 229 230
	private getWindowTitle(): string {
		let title = this.doGetWindowTitle();

231
		if (this.properties.isAdmin) {
232
			title = `${title || this.productService.nameLong} ${TitlebarPart.NLS_USER_IS_ADMIN}`;
233 234 235
		}

		if (!this.properties.isPure) {
236
			title = `${title || this.productService.nameLong} ${TitlebarPart.NLS_UNSUPPORTED}`;
237 238 239
		}

		if (this.environmentService.isExtensionDevelopment) {
240
			title = `${TitlebarPart.NLS_EXTENSION_HOST} - ${title || this.productService.nameLong}`;
241 242 243 244 245
		}

		return title;
	}

B
Benjamin Pasero 已提交
246
	updateProperties(properties: ITitleProperties): void {
247 248 249 250 251 252 253
		const isAdmin = typeof properties.isAdmin === 'boolean' ? properties.isAdmin : this.properties.isAdmin;
		const isPure = typeof properties.isPure === 'boolean' ? properties.isPure : this.properties.isPure;

		if (isAdmin !== this.properties.isAdmin || isPure !== this.properties.isPure) {
			this.properties.isAdmin = isAdmin;
			this.properties.isPure = isPure;

254
			this.titleUpdater.schedule();
255 256 257
		}
	}

258 259 260
	/**
	 * Possible template values:
	 *
261 262
	 * {activeEditorLong}: e.g. /Users/Development/myFolder/myFileFolder/myFile.txt
	 * {activeEditorMedium}: e.g. myFolder/myFileFolder/myFile.txt
B
Benjamin Pasero 已提交
263
	 * {activeEditorShort}: e.g. myFile.txt
264 265 266
	 * {activeFolderLong}: e.g. /Users/Development/myFolder/myFileFolder
	 * {activeFolderMedium}: e.g. myFolder/myFileFolder
	 * {activeFolderShort}: e.g. myFileFolder
267
	 * {rootName}: e.g. myFolder1, myFolder2, myFolder3
268
	 * {rootPath}: e.g. /Users/Development
269 270
	 * {folderName}: e.g. myFolder
	 * {folderPath}: e.g. /Users/Development/myFolder
271
	 * {appName}: e.g. VS Code
272
	 * {remoteName}: e.g. SSH
273
	 * {dirty}: indicator
274 275 276
	 * {separator}: conditional separator
	 */
	private doGetWindowTitle(): string {
B
Benjamin Pasero 已提交
277
		const editor = this.editorService.activeEditor;
B
Benjamin Pasero 已提交
278
		const workspace = this.contextService.getWorkspace();
279

B
Benjamin Pasero 已提交
280
		// Compute root
281
		let root: URI | undefined;
282
		if (workspace.configuration) {
283
			root = workspace.configuration;
284 285
		} else if (workspace.folders.length) {
			root = workspace.folders[0].uri;
286 287
		}

B
Benjamin Pasero 已提交
288 289 290
		// Compute active editor folder
		const editorResource = editor ? toResource(editor) : undefined;
		let editorFolderResource = editorResource ? resources.dirname(editorResource) : undefined;
B
Benjamin Pasero 已提交
291
		if (editorFolderResource?.path === '.') {
B
Benjamin Pasero 已提交
292 293 294
			editorFolderResource = undefined;
		}

295 296
		// Compute folder resource
		// Single Root Workspace: always the root single workspace in this case
297
		// Otherwise: root folder of the currently active file if any
298 299 300 301 302 303 304 305 306
		let folder: IWorkspaceFolder | null = null;
		if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
			folder = workspace.folders[0];
		} else {
			const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
			if (resource) {
				folder = this.contextService.getWorkspaceFolder(resource);
			}
		}
307

308
		// Variables
B
Benjamin Pasero 已提交
309 310 311
		const activeEditorShort = editor ? editor.getTitle(Verbosity.SHORT) : '';
		const activeEditorMedium = editor ? editor.getTitle(Verbosity.MEDIUM) : activeEditorShort;
		const activeEditorLong = editor ? editor.getTitle(Verbosity.LONG) : activeEditorMedium;
B
Benjamin Pasero 已提交
312 313 314
		const activeFolderShort = editorFolderResource ? resources.basename(editorFolderResource) : '';
		const activeFolderMedium = editorFolderResource ? this.labelService.getUriLabel(editorFolderResource, { relative: true }) : '';
		const activeFolderLong = editorFolderResource ? this.labelService.getUriLabel(editorFolderResource) : '';
I
isidor 已提交
315
		const rootName = this.labelService.getWorkspaceLabel(workspace);
I
isidor 已提交
316
		const rootPath = root ? this.labelService.getUriLabel(root) : '';
B
Benjamin Pasero 已提交
317
		const folderName = folder ? folder.name : '';
I
isidor 已提交
318
		const folderPath = folder ? this.labelService.getUriLabel(folder.uri) : '';
319
		const dirty = editor?.isDirty() && !editor.isSaving() ? TitlebarPart.TITLE_DIRTY : '';
320
		const appName = this.productService.nameLong;
321
		const remoteName = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.environmentService.configuration.remoteAuthority);
322
		const separator = TitlebarPart.TITLE_SEPARATOR;
323
		const titleTemplate = this.configurationService.getValue<string>('window.title');
324

I
isidor 已提交
325
		return template(titleTemplate, {
B
Benjamin Pasero 已提交
326 327 328
			activeEditorShort,
			activeEditorLong,
			activeEditorMedium,
329 330 331
			activeFolderShort,
			activeFolderMedium,
			activeFolderLong,
332 333
			rootName,
			rootPath,
334 335
			folderName,
			folderPath,
336 337
			dirty,
			appName,
338
			remoteName,
339 340 341 342
			separator: { label: separator }
		});
	}

S
SteVen Batten 已提交
343 344 345 346 347 348 349 350 351 352 353 354 355
	private uninstallMenubar(): void {
		if (this.customMenubar) {
			this.customMenubar.dispose();
			this.customMenubar = undefined;
		}

		if (this.menubar) {
			removeNode(this.menubar);
			this.menubar = undefined;
		}
	}

	private installMenubar(): void {
S
SteVen Batten 已提交
356 357 358 359 360
		// If the menubar is already installed, skip
		if (this.menubar) {
			return;
		}

S
SteVen Batten 已提交
361 362 363 364 365 366 367 368 369 370 371 372
		this.customMenubar = this._register(this.instantiationService.createInstance(CustomMenubarControl));

		this.menubar = this.element.insertBefore($('div.menubar'), this.title);

		this.menubar.setAttribute('role', 'menubar');

		this.customMenubar.create(this.menubar);

		this._register(this.customMenubar.onVisibilityChange(e => this.onMenubarVisibilityChanged(e)));
		this._register(this.customMenubar.onFocusStateChange(e => this.onMenubarFocusChanged(e)));
	}

B
Benjamin Pasero 已提交
373
	createContentArea(parent: HTMLElement): HTMLElement {
374
		this.element = parent;
B
Benjamin Pasero 已提交
375

376
		// Draggable region that we can manipulate for #52522
377 378 379
		if (!isWeb) {
			this.dragRegion = append(this.element, $('div.titlebar-drag-region'));
		}
380

381 382
		// App Icon (Native Windows/Linux)
		if (!isMacintosh && !isWeb) {
383
			this.appIcon = append(this.element, $('div.window-appicon'));
S
SteVen Batten 已提交
384 385 386
			this.onUpdateAppIconDragBehavior();

			this._register(addDisposableListener(this.appIcon, EventType.DBLCLICK, (e => {
387
				this.electronService.closeWindow();
S
SteVen Batten 已提交
388
			})));
389
		}
S
SteVen Batten 已提交
390

391
		// Menubar: install a custom menu bar depending on configuration
S
SteVen Batten 已提交
392
		// and when not in activity bar
393
		if (this.titleBarStyle !== 'native'
S
SteVen Batten 已提交
394 395 396
			&& (!isMacintosh || isWeb)
			&& this.currentMenubarVisibility !== 'compact') {
			this.installMenubar();
S
SteVen Batten 已提交
397 398
		}

B
Benjamin Pasero 已提交
399
		// Title
400
		this.title = append(this.element, $('div.window-title'));
B
Benjamin Pasero 已提交
401
		if (this.pendingTitle) {
402
			this.title.innerText = this.pendingTitle;
B
Benjamin Pasero 已提交
403
		} else {
404
			this.titleUpdater.schedule();
B
Benjamin Pasero 已提交
405 406
		}

B
Benjamin Pasero 已提交
407
		// Context menu on title
408 409 410 411
		[EventType.CONTEXT_MENU, EventType.MOUSE_DOWN].forEach(event => {
			this._register(addDisposableListener(this.title, event, e => {
				if (e.type === EventType.CONTEXT_MENU || e.metaKey) {
					EventHelper.stop(e);
B
Benjamin Pasero 已提交
412

413 414 415
					this.onContextMenu(e);
				}
			}));
B
Benjamin Pasero 已提交
416 417
		});

418 419
		// Window Controls (Native Windows/Linux)
		if (!isMacintosh && !isWeb) {
420
			this.windowControls = append(this.element, $('div.window-controls-container'));
421

B
Benjamin Pasero 已提交
422
			// Minimize
423
			const minimizeIcon = append(this.windowControls, $('div.window-icon.window-minimize.codicon.codicon-chrome-minimize'));
424
			this._register(addDisposableListener(minimizeIcon, EventType.CLICK, e => {
425
				this.electronService.minimizeWindow();
426
			}));
R
Ryan Adolf 已提交
427

B
Benjamin Pasero 已提交
428
			// Restore
429
			this.maxRestoreControl = append(this.windowControls, $('div.window-icon.window-max-restore.codicon'));
430
			this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, async e => {
431
				const maximized = await this.electronService.isMaximized();
432
				if (maximized) {
433
					return this.electronService.unmaximizeWindow();
434 435
				}

436
				return this.electronService.maximizeWindow();
437
			}));
R
Ryan Adolf 已提交
438

B
Benjamin Pasero 已提交
439
			// Close
440
			const closeIcon = append(this.windowControls, $('div.window-icon.window-close.codicon.codicon-chrome-close'));
441
			this._register(addDisposableListener(closeIcon, EventType.CLICK, e => {
442
				this.electronService.closeWindow();
443
			}));
444

S
SteVen Batten 已提交
445
			// Resizer
446
			this.resizer = append(this.element, $('div.resizer'));
S
SteVen Batten 已提交
447

448 449
			this._register(this.layoutService.onMaximizeChange(maximized => this.onDidChangeMaximized(maximized)));
			this.onDidChangeMaximized(this.layoutService.isWindowMaximized());
450
		}
R
Ryan Adolf 已提交
451

452 453
		// Since the title area is used to drag the window, we do not want to steal focus from the
		// currently active element. So we restore focus after a timeout back to where it was.
454
		this._register(addDisposableListener(this.element, EventType.MOUSE_DOWN, e => {
S
SteVen Batten 已提交
455
			if (e.target && this.menubar && isAncestor(e.target as HTMLElement, this.menubar)) {
S
SteVen Batten 已提交
456 457 458
				return;
			}

459 460 461 462 463 464
			const active = document.activeElement;
			setTimeout(() => {
				if (active instanceof HTMLElement) {
					active.focus();
				}
			}, 0 /* need a timeout because we are in capture phase */);
465
		}, true /* use capture to know the currently active element properly */));
466

S
SteVen Batten 已提交
467 468
		this.updateStyles();

469
		return this.element;
B
Benjamin Pasero 已提交
470 471
	}

472
	private onDidChangeMaximized(maximized: boolean) {
S
SteVen Batten 已提交
473 474
		if (this.maxRestoreControl) {
			if (maximized) {
475 476
				removeClass(this.maxRestoreControl, 'codicon-chrome-maximize');
				addClass(this.maxRestoreControl, 'codicon-chrome-restore');
S
SteVen Batten 已提交
477
			} else {
478 479
				removeClass(this.maxRestoreControl, 'codicon-chrome-restore');
				addClass(this.maxRestoreControl, 'codicon-chrome-maximize');
S
SteVen Batten 已提交
480
			}
S
SteVen Batten 已提交
481
		}
S
SteVen Batten 已提交
482

S
SteVen Batten 已提交
483 484
		if (this.resizer) {
			if (maximized) {
485
				hide(this.resizer);
S
SteVen Batten 已提交
486
			} else {
487
				show(this.resizer);
S
SteVen Batten 已提交
488
			}
S
SteVen Batten 已提交
489
		}
S
SteVen Batten 已提交
490 491

		this.adjustTitleMarginToCenter();
492 493
	}

494
	updateStyles(): void {
B
Benjamin Pasero 已提交
495 496 497
		super.updateStyles();

		// Part container
498
		if (this.element) {
S
SteVen Batten 已提交
499
			if (this.isInactive) {
500
				addClass(this.element, 'inactive');
S
SteVen Batten 已提交
501
			} else {
502
				removeClass(this.element, 'inactive');
S
SteVen Batten 已提交
503 504
			}

505 506 507 508 509 510 511
			const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND, (color, theme) => {
				// LCD Rendering Support: the title bar part is a defining its own GPU layer.
				// To benefit from LCD font rendering, we must ensure that we always set an
				// opaque background color. As such, we compute an opaque color given we know
				// the background color is the workbench background.
				return color.isOpaque() ? color : color.makeOpaque(WORKBENCH_BACKGROUND(theme));
			}) || '';
512
			this.element.style.backgroundColor = titleBackground;
M
Matt Bierner 已提交
513
			if (titleBackground && Color.fromHex(titleBackground).isLighter()) {
514
				addClass(this.element, 'light');
S
SteVen Batten 已提交
515
			} else {
516
				removeClass(this.element, 'light');
S
SteVen Batten 已提交
517
			}
518

B
Benjamin Pasero 已提交
519
			const titleForeground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_FOREGROUND : TITLE_BAR_ACTIVE_FOREGROUND);
M
Matt Bierner 已提交
520
			this.element.style.color = titleForeground || '';
B
Benjamin Pasero 已提交
521

522
			const titleBorder = this.getColor(TITLE_BAR_BORDER);
M
Matt Bierner 已提交
523
			this.element.style.borderBottom = titleBorder ? `1px solid ${titleBorder}` : '';
524
		}
B
Benjamin Pasero 已提交
525 526
	}

S
SteVen Batten 已提交
527 528
	private onUpdateAppIconDragBehavior() {
		const setting = this.configurationService.getValue('window.doubleClickIconToClose');
S
SteVen Batten 已提交
529
		if (setting && this.appIcon) {
S
SteVen Batten 已提交
530
			(this.appIcon.style as any)['-webkit-app-region'] = 'no-drag';
S
SteVen Batten 已提交
531
		} else if (this.appIcon) {
S
SteVen Batten 已提交
532
			(this.appIcon.style as any)['-webkit-app-region'] = 'drag';
S
SteVen Batten 已提交
533 534 535
		}
	}

B
Benjamin Pasero 已提交
536 537 538 539 540 541
	private onContextMenu(e: MouseEvent): void {

		// Find target anchor
		const event = new StandardMouseEvent(e);
		const anchor = { x: event.posx, y: event.posy };

542
		// Fill in contributed actions
B
Benjamin Pasero 已提交
543
		const actions: IAction[] = [];
544
		const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, undefined, actions, this.contextMenuService);
B
Benjamin Pasero 已提交
545

546 547 548 549 550 551
		// Show it
		this.contextMenuService.showContextMenu({
			getAnchor: () => anchor,
			getActions: () => actions,
			onHide: () => dispose(actionsDisposable)
		});
B
Benjamin Pasero 已提交
552 553
	}

S
SteVen Batten 已提交
554
	private adjustTitleMarginToCenter(): void {
S
SteVen Batten 已提交
555
		if (this.customMenubar && this.menubar) {
556 557 558 559 560 561 562
			const leftMarker = (this.appIcon ? this.appIcon.clientWidth : 0) + this.menubar.clientWidth + 10;
			const rightMarker = this.element.clientWidth - (this.windowControls ? this.windowControls.clientWidth : 0) - 10;

			// Not enough space to center the titlebar within window,
			// Center between menu and window controls
			if (leftMarker > (this.element.clientWidth - this.title.clientWidth) / 2 ||
				rightMarker < (this.element.clientWidth + this.title.clientWidth) / 2) {
M
Matt Bierner 已提交
563 564
				this.title.style.position = '';
				this.title.style.left = '';
M
Matt Bierner 已提交
565
				this.title.style.transform = '';
566 567
				return;
			}
S
SteVen Batten 已提交
568
		}
569 570 571 572

		this.title.style.position = 'absolute';
		this.title.style.left = '50%';
		this.title.style.transform = 'translate(-50%, 0)';
S
SteVen Batten 已提交
573 574
	}

S
SteVen Batten 已提交
575
	private get currentMenubarVisibility(): MenuBarVisibility {
576
		return getMenuBarVisibility(this.configurationService, this.environmentService);
S
SteVen Batten 已提交
577 578
	}

579
	updateLayout(dimension: Dimension): void {
S
SteVen Batten 已提交
580 581
		this.lastLayoutDimensions = dimension;

B
Benjamin Pasero 已提交
582
		if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
583
			// Only prevent zooming behavior on macOS or when the menubar is not visible
S
SteVen Batten 已提交
584
			if ((!isWeb && isMacintosh) || this.currentMenubarVisibility === 'hidden') {
585
				this.title.style.zoom = `${1 / getZoomFactor()}`;
586
				if (!isWeb && (isWindows || isLinux)) {
S
SteVen Batten 已提交
587 588 589 590 591 592 593
					if (this.appIcon) {
						this.appIcon.style.zoom = `${1 / getZoomFactor()}`;
					}

					if (this.windowControls) {
						this.windowControls.style.zoom = `${1 / getZoomFactor()}`;
					}
S
SteVen Batten 已提交
594
				}
595 596
			} else {
				this.title.style.zoom = null;
597
				if (!isWeb && (isWindows || isLinux)) {
S
SteVen Batten 已提交
598 599 600 601 602 603 604
					if (this.appIcon) {
						this.appIcon.style.zoom = null;
					}

					if (this.windowControls) {
						this.windowControls.style.zoom = null;
					}
S
SteVen Batten 已提交
605
				}
606 607
			}

S
SteVen Batten 已提交
608
			runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter());
609

610
			if (this.customMenubar) {
M
Matt Bierner 已提交
611
				const menubarDimension = new Dimension(0, dimension.height);
612
				this.customMenubar.layout(menubarDimension);
613
			}
614
		}
615 616
	}

B
Benjamin Pasero 已提交
617 618
	layout(width: number, height: number): void {
		this.updateLayout(new Dimension(width, height));
619

B
Benjamin Pasero 已提交
620
		super.layoutContents(width, height);
621
	}
B
Benjamin Pasero 已提交
622

623 624 625 626
	toJSON(): object {
		return {
			type: Parts.TITLEBAR_PART
		};
B
Benjamin Pasero 已提交
627
	}
B
Benjamin Pasero 已提交
628 629
}

S
SteVen Batten 已提交
630 631 632 633
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
	const titlebarActiveFg = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND);
	if (titlebarActiveFg) {
		collector.addRule(`
634
		.monaco-workbench .part.titlebar > .window-controls-container .window-icon {
635
			color: ${titlebarActiveFg};
S
SteVen Batten 已提交
636 637 638 639 640 641 642
		}
		`);
	}

	const titlebarInactiveFg = theme.getColor(TITLE_BAR_INACTIVE_FOREGROUND);
	if (titlebarInactiveFg) {
		collector.addRule(`
643
		.monaco-workbench .part.titlebar.inactive > .window-controls-container .window-icon {
644
				color: ${titlebarInactiveFg};
S
SteVen Batten 已提交
645 646 647 648
			}
		`);
	}
});
649

650
registerSingleton(ITitleService, TitlebarPart);