titlebarPart.ts 24.2 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 { 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';
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 46 47
// TODO@sbatten https://github.com/microsoft/vscode/issues/81360
// tslint:disable-next-line: import-patterns layering
import { IElectronService } from 'vs/platform/electron/node/electron';
48
// tslint:disable-next-line: import-patterns layering
49
import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService';
50

51
export class TitlebarPart extends Part implements ITitleService {
B
Benjamin Pasero 已提交
52

53
	private static readonly NLS_UNSUPPORTED = nls.localize('patchedWindowTitle', "[Unsupported]");
54
	private static readonly NLS_USER_IS_ADMIN = isWindows ? nls.localize('userIsAdmin', "[Administrator]") : nls.localize('userIsSudo', "[Superuser]");
55 56 57
	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
58

59
	//#region IView
60

B
Benjamin Pasero 已提交
61 62
	readonly minimumWidth: number = 0;
	readonly maximumWidth: number = Number.POSITIVE_INFINITY;
S
SteVen Batten 已提交
63 64
	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 已提交
65

66
	//#endregion
67

68
	private _onMenubarVisibilityChange = this._register(new Emitter<boolean>());
69
	readonly onMenubarVisibilityChange: Event<boolean> = this._onMenubarVisibilityChange.event;
70

71
	_serviceBrand: undefined;
B
Benjamin Pasero 已提交
72

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

S
SteVen Batten 已提交
84
	private pendingTitle: string | undefined;
85

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

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

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

93 94
	private contextMenu: IMenu;

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

115 116
		this.contextMenu = this._register(menuService.createMenu(MenuId.TitleBarContext, contextKeyService));

117 118
		this.titleBarStyle = getTitleBarStyle(this.configurationService, this.environmentService);

119 120 121 122
		this.registerListeners();
	}

	private registerListeners(): void {
123
		this._register(this.hostService.onDidChangeFocus(focused => focused ? this.onFocus() : this.onBlur()));
B
Benjamin Pasero 已提交
124 125
		this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChanged(e)));
		this._register(this.editorService.onDidActiveEditorChange(() => this.onActiveEditorChange()));
126 127 128 129
		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()));
130 131
	}

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

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

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

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

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

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

			this.adjustTitleMarginToCenter();
176 177

			this._onMenubarVisibilityChange.fire(visible);
S
SteVen Batten 已提交
178
		}
179 180
	}

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

B
Benjamin Pasero 已提交
191
	private onActiveEditorChange(): void {
192 193

		// Dispose old listeners
194
		this.activeEditorListeners.clear();
195 196

		// Calculate New Window Title
197
		this.titleUpdater.schedule();
198 199

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

B
Benjamin Pasero 已提交
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)) {
213
			nativeTitle = this.productService.nameLong;
B
Benjamin Pasero 已提交
214 215 216 217 218 219 220 221 222
		}
		window.document.title = nativeTitle;

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

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

231 232 233
	private getWindowTitle(): string {
		let title = this.doGetWindowTitle();

234
		if (this.properties.isAdmin) {
235
			title = `${title || this.productService.nameLong} ${TitlebarPart.NLS_USER_IS_ADMIN}`;
236 237 238
		}

		if (!this.properties.isPure) {
239
			title = `${title || this.productService.nameLong} ${TitlebarPart.NLS_UNSUPPORTED}`;
240 241 242
		}

		if (this.environmentService.isExtensionDevelopment) {
243
			title = `${TitlebarPart.NLS_EXTENSION_HOST} - ${title || this.productService.nameLong}`;
244 245 246 247 248
		}

		return title;
	}

B
Benjamin Pasero 已提交
249
	updateProperties(properties: ITitleProperties): void {
250 251 252 253 254 255 256
		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;

257
			this.titleUpdater.schedule();
258 259 260
		}
	}

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

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

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

298 299
		// Compute folder resource
		// Single Root Workspace: always the root single workspace in this case
300
		// Otherwise: root folder of the currently active file if any
301 302 303 304 305 306 307 308 309
		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);
			}
		}
310

311
		// Variables
B
Benjamin Pasero 已提交
312 313 314
		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 已提交
315 316 317
		const activeFolderShort = editorFolderResource ? resources.basename(editorFolderResource) : '';
		const activeFolderMedium = editorFolderResource ? this.labelService.getUriLabel(editorFolderResource, { relative: true }) : '';
		const activeFolderLong = editorFolderResource ? this.labelService.getUriLabel(editorFolderResource) : '';
I
isidor 已提交
318
		const rootName = this.labelService.getWorkspaceLabel(workspace);
I
isidor 已提交
319
		const rootPath = root ? this.labelService.getUriLabel(root) : '';
B
Benjamin Pasero 已提交
320
		const folderName = folder ? folder.name : '';
I
isidor 已提交
321
		const folderPath = folder ? this.labelService.getUriLabel(folder.uri) : '';
B
Benjamin Pasero 已提交
322
		const dirty = editor?.isDirty() ? TitlebarPart.TITLE_DIRTY : '';
323
		const appName = this.productService.nameLong;
324
		const remoteName = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.environmentService.configuration.remoteAuthority);
325
		const separator = TitlebarPart.TITLE_SEPARATOR;
326
		const titleTemplate = this.configurationService.getValue<string>('window.title');
327

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

S
SteVen Batten 已提交
346 347 348 349 350 351 352 353 354 355 356 357 358
	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 已提交
359 360 361 362 363
		// If the menubar is already installed, skip
		if (this.menubar) {
			return;
		}

S
SteVen Batten 已提交
364 365 366 367 368 369 370 371 372 373 374 375
		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 已提交
376
	createContentArea(parent: HTMLElement): HTMLElement {
377
		this.element = parent;
B
Benjamin Pasero 已提交
378

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

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

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

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

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

B
Benjamin Pasero 已提交
410
		// Context menu on title
411 412 413 414
		[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 已提交
415

416 417 418
					this.onContextMenu(e);
				}
			}));
B
Benjamin Pasero 已提交
419 420
		});

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

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

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

439
				return this.electronService.maximizeWindow();
440
			}));
R
Ryan Adolf 已提交
441

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

S
SteVen Batten 已提交
448
			// Resizer
449
			this.resizer = append(this.element, $('div.resizer'));
S
SteVen Batten 已提交
450

451
			const isMaximized = this.environmentService.configuration.maximized ? true : false;
452
			this.onDidChangeMaximized(isMaximized);
453 454

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

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

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

S
SteVen Batten 已提交
475 476
		this.updateStyles();

477
		return this.element;
B
Benjamin Pasero 已提交
478 479
	}

480
	private onDidChangeMaximized(maximized: boolean) {
S
SteVen Batten 已提交
481 482
		if (this.maxRestoreControl) {
			if (maximized) {
483 484
				removeClass(this.maxRestoreControl, 'codicon-chrome-maximize');
				addClass(this.maxRestoreControl, 'codicon-chrome-restore');
S
SteVen Batten 已提交
485
			} else {
486 487
				removeClass(this.maxRestoreControl, 'codicon-chrome-restore');
				addClass(this.maxRestoreControl, 'codicon-chrome-maximize');
S
SteVen Batten 已提交
488
			}
S
SteVen Batten 已提交
489
		}
S
SteVen Batten 已提交
490

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

		this.adjustTitleMarginToCenter();
500 501
	}

502
	updateStyles(): void {
B
Benjamin Pasero 已提交
503 504 505
		super.updateStyles();

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

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

B
Benjamin Pasero 已提交
521
			const titleForeground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_FOREGROUND : TITLE_BAR_ACTIVE_FOREGROUND);
522
			this.element.style.color = titleForeground;
B
Benjamin Pasero 已提交
523

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

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

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

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

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

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

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

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

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

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

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

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

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

S
SteVen Batten 已提交
610
			runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter());
611

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

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

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

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

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

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

652
registerSingleton(ITitleService, TitlebarPart);