titlebarPart.ts 23.7 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 } 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 { EditorInput, toResource, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor';
20
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
21
import { IWorkspaceContextService, WorkbenchState } 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 } 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';
33
import { Event, 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';
B
Benjamin Pasero 已提交
42

43 44 45
// TODO@sbatten https://github.com/microsoft/vscode/issues/81360
// tslint:disable-next-line: import-patterns layering
import { IElectronService } from 'vs/platform/electron/node/electron';
46
// tslint:disable-next-line: import-patterns layering
47
import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService';
48

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>());
67
	readonly onMenubarVisibilityChange: Event<boolean> = this._onMenubarVisibilityChange.event;
68

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

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

B
Benjamin Pasero 已提交
81
	private pendingTitle: string;
82

B
Benjamin Pasero 已提交
83 84
	private isInactive: boolean;

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

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

90 91
	private contextMenu: IMenu;

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

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

113 114 115 116
		this.registerListeners();
	}

	private registerListeners(): void {
117
		this._register(this.hostService.onDidChangeFocus(focused => focused ? this.onFocus() : this.onBlur()));
B
Benjamin Pasero 已提交
118 119
		this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChanged(e)));
		this._register(this.editorService.onDidActiveEditorChange(() => this.onActiveEditorChange()));
120 121 122 123
		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()));
124 125
	}

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

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

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

S
SteVen Batten 已提交
141 142 143 144 145 146 147 148
		if (event.affectsConfiguration('window.menuBarVisibility')) {
			if (this.currentMenubarVisibility === 'compact') {
				this.uninstallMenubar();
			} else {
				this.installMenubar();
			}
		}

S
SteVen Batten 已提交
149 150 151 152 153
		if (event.affectsConfiguration('window.doubleClickIconToClose')) {
			if (this.appIcon) {
				this.onUpdateAppIconDragBehavior();
			}
		}
154 155
	}

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

			this.adjustTitleMarginToCenter();
166 167

			this._onMenubarVisibilityChange.fire(visible);
S
SteVen Batten 已提交
168
		}
169 170
	}

S
SteVen Batten 已提交
171
	private onMenubarFocusChanged(focused: boolean) {
S
SteVen Batten 已提交
172
		if (!isWeb && (isWindows || isLinux) && this.currentMenubarVisibility === 'compact') {
S
SteVen Batten 已提交
173 174 175 176 177 178 179 180
			if (focused) {
				hide(this.dragRegion);
			} else {
				show(this.dragRegion);
			}
		}
	}

B
Benjamin Pasero 已提交
181
	private onActiveEditorChange(): void {
182 183

		// Dispose old listeners
184
		this.activeEditorListeners.clear();
185 186

		// Calculate New Window Title
187
		this.titleUpdater.schedule();
188 189

		// Apply listener for dirty and label changes
B
Benjamin Pasero 已提交
190 191
		const activeEditor = this.editorService.activeEditor;
		if (activeEditor instanceof EditorInput) {
192 193
			this.activeEditorListeners.add(activeEditor.onDidChangeDirty(() => this.titleUpdater.schedule()));
			this.activeEditorListeners.add(activeEditor.onDidChangeLabel(() => this.titleUpdater.schedule()));
194 195 196
		}
	}

B
Benjamin Pasero 已提交
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
	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)) {
			nativeTitle = this.environmentService.appNameLong;
		}
		window.document.title = nativeTitle;

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

214
		if ((isWeb || isWindows || isLinux) && this.title) {
S
SteVen Batten 已提交
215 216 217
			if (this.lastLayoutDimensions) {
				this.updateLayout(this.lastLayoutDimensions);
			}
218
		}
B
Benjamin Pasero 已提交
219 220
	}

221 222 223
	private getWindowTitle(): string {
		let title = this.doGetWindowTitle();

224
		if (this.properties.isAdmin) {
B
Benjamin Pasero 已提交
225
			title = `${title || this.environmentService.appNameLong} ${TitlebarPart.NLS_USER_IS_ADMIN}`;
226 227 228
		}

		if (!this.properties.isPure) {
B
Benjamin Pasero 已提交
229
			title = `${title || this.environmentService.appNameLong} ${TitlebarPart.NLS_UNSUPPORTED}`;
230 231 232
		}

		if (this.environmentService.isExtensionDevelopment) {
B
Benjamin Pasero 已提交
233
			title = `${TitlebarPart.NLS_EXTENSION_HOST} - ${title || this.environmentService.appNameLong}`;
234 235 236 237 238
		}

		return title;
	}

B
Benjamin Pasero 已提交
239
	updateProperties(properties: ITitleProperties): void {
240 241 242 243 244 245 246
		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;

247
			this.titleUpdater.schedule();
248 249 250
		}
	}

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

B
Benjamin Pasero 已提交
273
		// Compute root
274
		let root: URI | undefined;
275
		if (workspace.configuration) {
276
			root = workspace.configuration;
277 278
		} else if (workspace.folders.length) {
			root = workspace.folders[0].uri;
279 280
		}

B
Benjamin Pasero 已提交
281 282 283 284 285 286 287
		// Compute active editor folder
		const editorResource = editor ? toResource(editor) : undefined;
		let editorFolderResource = editorResource ? resources.dirname(editorResource) : undefined;
		if (editorFolderResource && editorFolderResource.path === '.') {
			editorFolderResource = undefined;
		}

288 289
		// Compute folder resource
		// Single Root Workspace: always the root single workspace in this case
290
		// Otherwise: root folder of the currently active file if any
B
Benjamin Pasero 已提交
291
		const folder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER ? workspace.folders[0] : this.contextService.getWorkspaceFolder(toResource(editor, { supportSideBySide: SideBySideEditor.MASTER })!);
292

293
		// Variables
B
Benjamin Pasero 已提交
294 295 296
		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 已提交
297 298 299
		const activeFolderShort = editorFolderResource ? resources.basename(editorFolderResource) : '';
		const activeFolderMedium = editorFolderResource ? this.labelService.getUriLabel(editorFolderResource, { relative: true }) : '';
		const activeFolderLong = editorFolderResource ? this.labelService.getUriLabel(editorFolderResource) : '';
I
isidor 已提交
300
		const rootName = this.labelService.getWorkspaceLabel(workspace);
I
isidor 已提交
301
		const rootPath = root ? this.labelService.getUriLabel(root) : '';
B
Benjamin Pasero 已提交
302
		const folderName = folder ? folder.name : '';
I
isidor 已提交
303
		const folderPath = folder ? this.labelService.getUriLabel(folder.uri) : '';
B
Benjamin Pasero 已提交
304
		const dirty = editor && editor.isDirty() ? TitlebarPart.TITLE_DIRTY : '';
305
		const appName = this.environmentService.appNameLong;
306
		const remoteName = this.environmentService.configuration.remoteAuthority;
307
		const separator = TitlebarPart.TITLE_SEPARATOR;
308
		const titleTemplate = this.configurationService.getValue<string>('window.title');
309

I
isidor 已提交
310
		return template(titleTemplate, {
B
Benjamin Pasero 已提交
311 312 313
			activeEditorShort,
			activeEditorLong,
			activeEditorMedium,
314 315 316
			activeFolderShort,
			activeFolderMedium,
			activeFolderLong,
317 318
			rootName,
			rootPath,
319 320
			folderName,
			folderPath,
321 322
			dirty,
			appName,
323
			remoteName,
324 325 326 327
			separator: { label: separator }
		});
	}

S
SteVen Batten 已提交
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
	private uninstallMenubar(): void {
		if (this.customMenubar) {
			this.customMenubar.dispose();
			this.customMenubar = undefined;
		}

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

	private installMenubar(): void {
		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 已提交
353
	createContentArea(parent: HTMLElement): HTMLElement {
354
		this.element = parent;
B
Benjamin Pasero 已提交
355

356
		// Draggable region that we can manipulate for #52522
357 358 359
		if (!isWeb) {
			this.dragRegion = append(this.element, $('div.titlebar-drag-region'));
		}
360

361 362
		// App Icon (Native Windows/Linux)
		if (!isMacintosh && !isWeb) {
363
			this.appIcon = append(this.element, $('div.window-appicon'));
S
SteVen Batten 已提交
364 365 366
			this.onUpdateAppIconDragBehavior();

			this._register(addDisposableListener(this.appIcon, EventType.DBLCLICK, (e => {
367
				this.electronService.closeWindow();
S
SteVen Batten 已提交
368
			})));
369
		}
S
SteVen Batten 已提交
370

371
		// Menubar: install a custom menu bar depending on configuration
S
SteVen Batten 已提交
372 373 374 375 376
		// and when not in activity bar
		if (getTitleBarStyle(this.configurationService, this.environmentService) !== 'native'
			&& (!isMacintosh || isWeb)
			&& this.currentMenubarVisibility !== 'compact') {
			this.installMenubar();
S
SteVen Batten 已提交
377 378
		}

B
Benjamin Pasero 已提交
379
		// Title
380
		this.title = append(this.element, $('div.window-title'));
B
Benjamin Pasero 已提交
381
		if (this.pendingTitle) {
382
			this.title.innerText = this.pendingTitle;
B
Benjamin Pasero 已提交
383
		} else {
384
			this.titleUpdater.schedule();
B
Benjamin Pasero 已提交
385 386
		}

B
Benjamin Pasero 已提交
387
		// Context menu on title
388 389 390 391
		[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 已提交
392

393 394 395
					this.onContextMenu(e);
				}
			}));
B
Benjamin Pasero 已提交
396 397
		});

398 399
		// Window Controls (Native Windows/Linux)
		if (!isMacintosh && !isWeb) {
400
			this.windowControls = append(this.element, $('div.window-controls-container'));
401

B
Benjamin Pasero 已提交
402
			// Minimize
403 404 405 406
			const minimizeIconContainer = append(this.windowControls, $('div.window-icon-bg'));
			const minimizeIcon = append(minimizeIconContainer, $('div.window-icon'));
			addClass(minimizeIcon, 'window-minimize');
			this._register(addDisposableListener(minimizeIcon, EventType.CLICK, e => {
407
				this.electronService.minimizeWindow();
408
			}));
R
Ryan Adolf 已提交
409

B
Benjamin Pasero 已提交
410
			// Restore
411 412 413
			const restoreIconContainer = append(this.windowControls, $('div.window-icon-bg'));
			this.maxRestoreControl = append(restoreIconContainer, $('div.window-icon'));
			addClass(this.maxRestoreControl, 'window-max-restore');
414
			this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, async e => {
415
				const maximized = await this.electronService.isMaximized();
416
				if (maximized) {
417
					return this.electronService.unmaximizeWindow();
418 419
				}

420
				return this.electronService.maximizeWindow();
421
			}));
R
Ryan Adolf 已提交
422

B
Benjamin Pasero 已提交
423
			// Close
424 425 426 427 428
			const closeIconContainer = append(this.windowControls, $('div.window-icon-bg'));
			addClass(closeIconContainer, 'window-close-bg');
			const closeIcon = append(closeIconContainer, $('div.window-icon'));
			addClass(closeIcon, 'window-close');
			this._register(addDisposableListener(closeIcon, EventType.CLICK, e => {
429
				this.electronService.closeWindow();
430
			}));
431

S
SteVen Batten 已提交
432
			// Resizer
433
			this.resizer = append(this.element, $('div.resizer'));
S
SteVen Batten 已提交
434

435
			const isMaximized = this.environmentService.configuration.maximized ? true : false;
436
			this.onDidChangeMaximized(isMaximized);
437 438

			this._register(Event.any(
439 440
				Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.electronEnvironmentService.windowId), _ => true),
				Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.electronEnvironmentService.windowId), _ => false)
441
			)(e => this.onDidChangeMaximized(e)));
442
		}
R
Ryan Adolf 已提交
443

444 445
		// 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.
446
		this._register(addDisposableListener(this.element, EventType.MOUSE_DOWN, e => {
S
SteVen Batten 已提交
447
			if (e.target && this.menubar && isAncestor(e.target as HTMLElement, this.menubar)) {
S
SteVen Batten 已提交
448 449 450
				return;
			}

451 452 453 454 455 456
			const active = document.activeElement;
			setTimeout(() => {
				if (active instanceof HTMLElement) {
					active.focus();
				}
			}, 0 /* need a timeout because we are in capture phase */);
457
		}, true /* use capture to know the currently active element properly */));
458

S
SteVen Batten 已提交
459 460
		this.updateStyles();

461
		return this.element;
B
Benjamin Pasero 已提交
462 463
	}

464
	private onDidChangeMaximized(maximized: boolean) {
S
SteVen Batten 已提交
465 466
		if (this.maxRestoreControl) {
			if (maximized) {
467 468
				removeClass(this.maxRestoreControl, 'window-maximize');
				addClass(this.maxRestoreControl, 'window-unmaximize');
S
SteVen Batten 已提交
469
			} else {
470 471
				removeClass(this.maxRestoreControl, 'window-unmaximize');
				addClass(this.maxRestoreControl, 'window-maximize');
S
SteVen Batten 已提交
472
			}
S
SteVen Batten 已提交
473
		}
S
SteVen Batten 已提交
474

S
SteVen Batten 已提交
475 476
		if (this.resizer) {
			if (maximized) {
477
				hide(this.resizer);
S
SteVen Batten 已提交
478
			} else {
479
				show(this.resizer);
S
SteVen Batten 已提交
480
			}
S
SteVen Batten 已提交
481
		}
S
SteVen Batten 已提交
482 483

		this.adjustTitleMarginToCenter();
484 485
	}

486
	updateStyles(): void {
B
Benjamin Pasero 已提交
487 488 489
		super.updateStyles();

		// Part container
490
		if (this.element) {
S
SteVen Batten 已提交
491
			if (this.isInactive) {
492
				addClass(this.element, 'inactive');
S
SteVen Batten 已提交
493
			} else {
494
				removeClass(this.element, 'inactive');
S
SteVen Batten 已提交
495 496
			}

B
Benjamin Pasero 已提交
497
			const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND);
498
			this.element.style.backgroundColor = titleBackground;
M
Matt Bierner 已提交
499
			if (titleBackground && Color.fromHex(titleBackground).isLighter()) {
500
				addClass(this.element, 'light');
S
SteVen Batten 已提交
501
			} else {
502
				removeClass(this.element, 'light');
S
SteVen Batten 已提交
503
			}
504

B
Benjamin Pasero 已提交
505
			const titleForeground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_FOREGROUND : TITLE_BAR_ACTIVE_FOREGROUND);
506
			this.element.style.color = titleForeground;
B
Benjamin Pasero 已提交
507

508
			const titleBorder = this.getColor(TITLE_BAR_BORDER);
509
			this.element.style.borderBottom = titleBorder ? `1px solid ${titleBorder}` : null;
510
		}
B
Benjamin Pasero 已提交
511 512
	}

S
SteVen Batten 已提交
513 514 515
	private onUpdateAppIconDragBehavior() {
		const setting = this.configurationService.getValue('window.doubleClickIconToClose');
		if (setting) {
S
SteVen Batten 已提交
516
			(this.appIcon.style as any)['-webkit-app-region'] = 'no-drag';
517
		} else {
S
SteVen Batten 已提交
518
			(this.appIcon.style as any)['-webkit-app-region'] = 'drag';
S
SteVen Batten 已提交
519 520 521
		}
	}

B
Benjamin Pasero 已提交
522 523 524 525 526 527
	private onContextMenu(e: MouseEvent): void {

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

528
		// Fill in contributed actions
B
Benjamin Pasero 已提交
529
		const actions: IAction[] = [];
530
		const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, undefined, actions, this.contextMenuService);
B
Benjamin Pasero 已提交
531

532 533 534 535 536 537
		// Show it
		this.contextMenuService.showContextMenu({
			getAnchor: () => anchor,
			getActions: () => actions,
			onHide: () => dispose(actionsDisposable)
		});
B
Benjamin Pasero 已提交
538 539
	}

S
SteVen Batten 已提交
540
	private adjustTitleMarginToCenter(): void {
S
SteVen Batten 已提交
541
		if (this.customMenubar && this.menubar) {
542 543 544 545 546 547 548 549 550
			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) {
				this.title.style.position = null;
				this.title.style.left = null;
M
Matt Bierner 已提交
551
				this.title.style.transform = '';
552 553
				return;
			}
S
SteVen Batten 已提交
554
		}
555 556 557 558

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

S
SteVen Batten 已提交
561 562 563 564
	private get currentMenubarVisibility(): MenuBarVisibility {
		return this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility');
	}

565
	updateLayout(dimension: Dimension): void {
S
SteVen Batten 已提交
566 567
		this.lastLayoutDimensions = dimension;

B
Benjamin Pasero 已提交
568
		if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
569
			// Only prevent zooming behavior on macOS or when the menubar is not visible
S
SteVen Batten 已提交
570
			if ((!isWeb && isMacintosh) || this.currentMenubarVisibility === 'hidden') {
571
				this.title.style.zoom = `${1 / getZoomFactor()}`;
572
				if (!isWeb && (isWindows || isLinux)) {
573 574
					this.appIcon.style.zoom = `${1 / getZoomFactor()}`;
					this.windowControls.style.zoom = `${1 / getZoomFactor()}`;
S
SteVen Batten 已提交
575
				}
576 577
			} else {
				this.title.style.zoom = null;
578
				if (!isWeb && (isWindows || isLinux)) {
579 580
					this.appIcon.style.zoom = null;
					this.windowControls.style.zoom = null;
S
SteVen Batten 已提交
581
				}
582 583
			}

S
SteVen Batten 已提交
584
			runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter());
585

586
			if (this.customMenubar) {
M
Matt Bierner 已提交
587
				const menubarDimension = new Dimension(0, dimension.height);
588
				this.customMenubar.layout(menubarDimension);
589
			}
590
		}
591 592
	}

B
Benjamin Pasero 已提交
593 594
	layout(width: number, height: number): void {
		this.updateLayout(new Dimension(width, height));
595

B
Benjamin Pasero 已提交
596
		super.layoutContents(width, height);
597
	}
B
Benjamin Pasero 已提交
598

599 600 601 602
	toJSON(): object {
		return {
			type: Parts.TITLEBAR_PART
		};
B
Benjamin Pasero 已提交
603
	}
B
Benjamin Pasero 已提交
604 605
}

S
SteVen Batten 已提交
606 607 608 609
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
	const titlebarActiveFg = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND);
	if (titlebarActiveFg) {
		collector.addRule(`
610
		.monaco-workbench .part.titlebar > .window-controls-container .window-icon {
S
SteVen Batten 已提交
611 612 613 614 615 616 617 618
			background-color: ${titlebarActiveFg};
		}
		`);
	}

	const titlebarInactiveFg = theme.getColor(TITLE_BAR_INACTIVE_FOREGROUND);
	if (titlebarInactiveFg) {
		collector.addRule(`
619
		.monaco-workbench .part.titlebar.inactive > .window-controls-container .window-icon {
S
SteVen Batten 已提交
620 621 622 623 624
				background-color: ${titlebarInactiveFg};
			}
		`);
	}
});
625

626
registerSingleton(ITitleService, TitlebarPart);