titlebarPart.ts 23.9 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, 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 } 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
291 292 293 294 295 296 297 298 299
		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);
			}
		}
300

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

I
isidor 已提交
318
		return template(titleTemplate, {
B
Benjamin Pasero 已提交
319 320 321
			activeEditorShort,
			activeEditorLong,
			activeEditorMedium,
322 323 324
			activeFolderShort,
			activeFolderMedium,
			activeFolderLong,
325 326
			rootName,
			rootPath,
327 328
			folderName,
			folderPath,
329 330
			dirty,
			appName,
331
			remoteName,
332 333 334 335
			separator: { label: separator }
		});
	}

S
SteVen Batten 已提交
336 337 338 339 340 341 342 343 344 345 346 347 348
	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 已提交
349 350 351 352 353
		// If the menubar is already installed, skip
		if (this.menubar) {
			return;
		}

S
SteVen Batten 已提交
354 355 356 357 358 359 360 361 362 363 364 365
		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 已提交
366
	createContentArea(parent: HTMLElement): HTMLElement {
367
		this.element = parent;
B
Benjamin Pasero 已提交
368

369
		// Draggable region that we can manipulate for #52522
370 371 372
		if (!isWeb) {
			this.dragRegion = append(this.element, $('div.titlebar-drag-region'));
		}
373

374 375
		// App Icon (Native Windows/Linux)
		if (!isMacintosh && !isWeb) {
376
			this.appIcon = append(this.element, $('div.window-appicon'));
S
SteVen Batten 已提交
377 378 379
			this.onUpdateAppIconDragBehavior();

			this._register(addDisposableListener(this.appIcon, EventType.DBLCLICK, (e => {
380
				this.electronService.closeWindow();
S
SteVen Batten 已提交
381
			})));
382
		}
S
SteVen Batten 已提交
383

384
		// Menubar: install a custom menu bar depending on configuration
S
SteVen Batten 已提交
385 386 387 388 389
		// and when not in activity bar
		if (getTitleBarStyle(this.configurationService, this.environmentService) !== 'native'
			&& (!isMacintosh || isWeb)
			&& this.currentMenubarVisibility !== 'compact') {
			this.installMenubar();
S
SteVen Batten 已提交
390 391
		}

B
Benjamin Pasero 已提交
392
		// Title
393
		this.title = append(this.element, $('div.window-title'));
B
Benjamin Pasero 已提交
394
		if (this.pendingTitle) {
395
			this.title.innerText = this.pendingTitle;
B
Benjamin Pasero 已提交
396
		} else {
397
			this.titleUpdater.schedule();
B
Benjamin Pasero 已提交
398 399
		}

B
Benjamin Pasero 已提交
400
		// Context menu on title
401 402 403 404
		[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 已提交
405

406 407 408
					this.onContextMenu(e);
				}
			}));
B
Benjamin Pasero 已提交
409 410
		});

411 412
		// Window Controls (Native Windows/Linux)
		if (!isMacintosh && !isWeb) {
413
			this.windowControls = append(this.element, $('div.window-controls-container'));
414

B
Benjamin Pasero 已提交
415
			// Minimize
416 417 418 419
			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 => {
420
				this.electronService.minimizeWindow();
421
			}));
R
Ryan Adolf 已提交
422

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

433
				return this.electronService.maximizeWindow();
434
			}));
R
Ryan Adolf 已提交
435

B
Benjamin Pasero 已提交
436
			// Close
437 438 439 440 441
			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 => {
442
				this.electronService.closeWindow();
443
			}));
444

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

448
			const isMaximized = this.environmentService.configuration.maximized ? true : false;
449
			this.onDidChangeMaximized(isMaximized);
450 451

			this._register(Event.any(
452 453
				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)
454
			)(e => this.onDidChangeMaximized(e)));
455
		}
R
Ryan Adolf 已提交
456

457 458
		// 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.
459
		this._register(addDisposableListener(this.element, EventType.MOUSE_DOWN, e => {
S
SteVen Batten 已提交
460
			if (e.target && this.menubar && isAncestor(e.target as HTMLElement, this.menubar)) {
S
SteVen Batten 已提交
461 462 463
				return;
			}

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

S
SteVen Batten 已提交
472 473
		this.updateStyles();

474
		return this.element;
B
Benjamin Pasero 已提交
475 476
	}

477
	private onDidChangeMaximized(maximized: boolean) {
S
SteVen Batten 已提交
478 479
		if (this.maxRestoreControl) {
			if (maximized) {
480 481
				removeClass(this.maxRestoreControl, 'window-maximize');
				addClass(this.maxRestoreControl, 'window-unmaximize');
S
SteVen Batten 已提交
482
			} else {
483 484
				removeClass(this.maxRestoreControl, 'window-unmaximize');
				addClass(this.maxRestoreControl, 'window-maximize');
S
SteVen Batten 已提交
485
			}
S
SteVen Batten 已提交
486
		}
S
SteVen Batten 已提交
487

S
SteVen Batten 已提交
488 489
		if (this.resizer) {
			if (maximized) {
490
				hide(this.resizer);
S
SteVen Batten 已提交
491
			} else {
492
				show(this.resizer);
S
SteVen Batten 已提交
493
			}
S
SteVen Batten 已提交
494
		}
S
SteVen Batten 已提交
495 496

		this.adjustTitleMarginToCenter();
497 498
	}

499
	updateStyles(): void {
B
Benjamin Pasero 已提交
500 501 502
		super.updateStyles();

		// Part container
503
		if (this.element) {
S
SteVen Batten 已提交
504
			if (this.isInactive) {
505
				addClass(this.element, 'inactive');
S
SteVen Batten 已提交
506
			} else {
507
				removeClass(this.element, 'inactive');
S
SteVen Batten 已提交
508 509
			}

M
Matt Bierner 已提交
510
			const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND) || '';
511
			this.element.style.backgroundColor = titleBackground;
M
Matt Bierner 已提交
512
			if (titleBackground && Color.fromHex(titleBackground).isLighter()) {
513
				addClass(this.element, 'light');
S
SteVen Batten 已提交
514
			} else {
515
				removeClass(this.element, 'light');
S
SteVen Batten 已提交
516
			}
517

B
Benjamin Pasero 已提交
518
			const titleForeground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_FOREGROUND : TITLE_BAR_ACTIVE_FOREGROUND);
519
			this.element.style.color = titleForeground;
B
Benjamin Pasero 已提交
520

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

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

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

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

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

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

S
SteVen Batten 已提交
553
	private adjustTitleMarginToCenter(): void {
S
SteVen Batten 已提交
554
		if (this.customMenubar && this.menubar) {
555 556 557 558 559 560 561
			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 已提交
562 563
				this.title.style.position = '';
				this.title.style.left = '';
M
Matt Bierner 已提交
564
				this.title.style.transform = '';
565 566
				return;
			}
S
SteVen Batten 已提交
567
		}
568 569 570 571

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

S
SteVen Batten 已提交
574 575 576 577
	private get currentMenubarVisibility(): MenuBarVisibility {
		return this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility');
	}

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

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

S
SteVen Batten 已提交
597
			runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter());
598

599
			if (this.customMenubar) {
M
Matt Bierner 已提交
600
				const menubarDimension = new Dimension(0, dimension.height);
601
				this.customMenubar.layout(menubarDimension);
602
			}
603
		}
604 605
	}

B
Benjamin Pasero 已提交
606 607
	layout(width: number, height: number): void {
		this.updateLayout(new Dimension(width, height));
608

B
Benjamin Pasero 已提交
609
		super.layoutContents(width, height);
610
	}
B
Benjamin Pasero 已提交
611

612 613 614 615
	toJSON(): object {
		return {
			type: Parts.TITLEBAR_PART
		};
B
Benjamin Pasero 已提交
616
	}
B
Benjamin Pasero 已提交
617 618
}

S
SteVen Batten 已提交
619 620 621 622
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
	const titlebarActiveFg = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND);
	if (titlebarActiveFg) {
		collector.addRule(`
623
		.monaco-workbench .part.titlebar > .window-controls-container .window-icon {
S
SteVen Batten 已提交
624 625 626 627 628 629 630 631
			background-color: ${titlebarActiveFg};
		}
		`);
	}

	const titlebarInactiveFg = theme.getColor(TITLE_BAR_INACTIVE_FOREGROUND);
	if (titlebarInactiveFg) {
		collector.addRule(`
632
		.monaco-workbench .part.titlebar.inactive > .window-controls-container .window-icon {
S
SteVen Batten 已提交
633 634 635 636 637
				background-color: ${titlebarInactiveFg};
			}
		`);
	}
});
638

639
registerSingleton(ITitleService, TitlebarPart);