titlebarPart.ts 22.5 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 { IWindowService, 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 } 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';
B
Benjamin Pasero 已提交
41

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
export class TitlebarPart extends Part implements ITitleService {
B
Benjamin Pasero 已提交
47

48
	private static readonly NLS_UNSUPPORTED = nls.localize('patchedWindowTitle', "[Unsupported]");
49
	private static readonly NLS_USER_IS_ADMIN = isWindows ? nls.localize('userIsAdmin', "[Administrator]") : nls.localize('userIsSudo', "[Superuser]");
50 51 52
	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
53

54
	//#region IView
55

B
Benjamin Pasero 已提交
56 57
	readonly minimumWidth: number = 0;
	readonly maximumWidth: number = Number.POSITIVE_INFINITY;
58 59
	get minimumHeight(): number { return isMacintosh && !isWeb ? 22 / getZoomFactor() : (30 / (this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'hidden' ? getZoomFactor() : 1)); }
	get maximumHeight(): number { return isMacintosh && !isWeb ? 22 / getZoomFactor() : (30 / (this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'hidden' ? getZoomFactor() : 1)); }
B
Benjamin Pasero 已提交
60

61
	//#endregion
62

63
	private _onMenubarVisibilityChange = this._register(new Emitter<boolean>());
64
	readonly onMenubarVisibilityChange: Event<boolean> = this._onMenubarVisibilityChange.event;
65

66
	_serviceBrand: undefined;
B
Benjamin Pasero 已提交
67

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

B
Benjamin Pasero 已提交
78
	private pendingTitle: string;
79

B
Benjamin Pasero 已提交
80 81
	private isInactive: boolean;

82 83
	private readonly properties: ITitleProperties = { isPure: true, isAdmin: false };
	private readonly activeEditorListeners = this._register(new DisposableStore());
84

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

87 88
	private contextMenu: IMenu;

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

107 108
		this.contextMenu = this._register(menuService.createMenu(MenuId.TitleBarContext, contextKeyService));

109 110 111 112
		this.registerListeners();
	}

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

B
Benjamin Pasero 已提交
122 123 124 125 126 127 128 129 130 131
	private onBlur(): void {
		this.isInactive = true;
		this.updateStyles();
	}

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

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

		if (event.affectsConfiguration('window.doubleClickIconToClose')) {
			if (this.appIcon) {
				this.onUpdateAppIconDragBehavior();
			}
		}
142 143
	}

S
SteVen Batten 已提交
144
	private onMenubarVisibilityChanged(visible: boolean) {
145
		if (isWeb || isWindows || isLinux) {
S
SteVen Batten 已提交
146
			// Hide title when toggling menu bar
147
			if (!isWeb && this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'toggle' && visible) {
S
SteVen Batten 已提交
148
				// Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor
149 150
				hide(this.dragRegion);
				setTimeout(() => show(this.dragRegion), 50);
S
SteVen Batten 已提交
151
			}
S
SteVen Batten 已提交
152 153

			this.adjustTitleMarginToCenter();
154 155

			this._onMenubarVisibilityChange.fire(visible);
S
SteVen Batten 已提交
156
		}
157 158
	}

S
SteVen Batten 已提交
159
	private onMenubarFocusChanged(focused: boolean) {
160
		if (!isWeb && (isWindows || isLinux)) {
S
SteVen Batten 已提交
161 162 163 164 165 166 167 168
			if (focused) {
				hide(this.dragRegion);
			} else {
				show(this.dragRegion);
			}
		}
	}

B
Benjamin Pasero 已提交
169
	private onActiveEditorChange(): void {
170 171

		// Dispose old listeners
172
		this.activeEditorListeners.clear();
173 174

		// Calculate New Window Title
175
		this.titleUpdater.schedule();
176 177

		// Apply listener for dirty and label changes
B
Benjamin Pasero 已提交
178 179
		const activeEditor = this.editorService.activeEditor;
		if (activeEditor instanceof EditorInput) {
180 181
			this.activeEditorListeners.add(activeEditor.onDidChangeDirty(() => this.titleUpdater.schedule()));
			this.activeEditorListeners.add(activeEditor.onDidChangeLabel(() => this.titleUpdater.schedule()));
182 183 184
		}
	}

B
Benjamin Pasero 已提交
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
	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 已提交
201

202
		if ((isWeb || isWindows || isLinux) && this.title) {
S
SteVen Batten 已提交
203 204 205
			if (this.lastLayoutDimensions) {
				this.updateLayout(this.lastLayoutDimensions);
			}
206
		}
B
Benjamin Pasero 已提交
207 208
	}

209 210 211
	private getWindowTitle(): string {
		let title = this.doGetWindowTitle();

212
		if (this.properties.isAdmin) {
B
Benjamin Pasero 已提交
213
			title = `${title || this.environmentService.appNameLong} ${TitlebarPart.NLS_USER_IS_ADMIN}`;
214 215 216
		}

		if (!this.properties.isPure) {
B
Benjamin Pasero 已提交
217
			title = `${title || this.environmentService.appNameLong} ${TitlebarPart.NLS_UNSUPPORTED}`;
218 219 220
		}

		if (this.environmentService.isExtensionDevelopment) {
B
Benjamin Pasero 已提交
221
			title = `${TitlebarPart.NLS_EXTENSION_HOST} - ${title || this.environmentService.appNameLong}`;
222 223 224 225 226
		}

		return title;
	}

B
Benjamin Pasero 已提交
227
	updateProperties(properties: ITitleProperties): void {
228 229 230 231 232 233 234
		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;

235
			this.titleUpdater.schedule();
236 237 238
		}
	}

239 240 241
	/**
	 * Possible template values:
	 *
242 243
	 * {activeEditorLong}: e.g. /Users/Development/myFolder/myFileFolder/myFile.txt
	 * {activeEditorMedium}: e.g. myFolder/myFileFolder/myFile.txt
B
Benjamin Pasero 已提交
244
	 * {activeEditorShort}: e.g. myFile.txt
245 246 247
	 * {activeFolderLong}: e.g. /Users/Development/myFolder/myFileFolder
	 * {activeFolderMedium}: e.g. myFolder/myFileFolder
	 * {activeFolderShort}: e.g. myFileFolder
248
	 * {rootName}: e.g. myFolder1, myFolder2, myFolder3
249
	 * {rootPath}: e.g. /Users/Development
250 251
	 * {folderName}: e.g. myFolder
	 * {folderPath}: e.g. /Users/Development/myFolder
252
	 * {appName}: e.g. VS Code
253
	 * {remoteName}: e.g. SSH
254
	 * {dirty}: indicator
255 256 257
	 * {separator}: conditional separator
	 */
	private doGetWindowTitle(): string {
B
Benjamin Pasero 已提交
258
		const editor = this.editorService.activeEditor;
B
Benjamin Pasero 已提交
259
		const workspace = this.contextService.getWorkspace();
260

B
Benjamin Pasero 已提交
261
		// Compute root
262
		let root: URI | undefined;
263
		if (workspace.configuration) {
264
			root = workspace.configuration;
265 266
		} else if (workspace.folders.length) {
			root = workspace.folders[0].uri;
267 268
		}

B
Benjamin Pasero 已提交
269 270 271 272 273 274 275
		// Compute active editor folder
		const editorResource = editor ? toResource(editor) : undefined;
		let editorFolderResource = editorResource ? resources.dirname(editorResource) : undefined;
		if (editorFolderResource && editorFolderResource.path === '.') {
			editorFolderResource = undefined;
		}

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

281
		// Variables
B
Benjamin Pasero 已提交
282 283 284
		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 已提交
285 286 287
		const activeFolderShort = editorFolderResource ? resources.basename(editorFolderResource) : '';
		const activeFolderMedium = editorFolderResource ? this.labelService.getUriLabel(editorFolderResource, { relative: true }) : '';
		const activeFolderLong = editorFolderResource ? this.labelService.getUriLabel(editorFolderResource) : '';
I
isidor 已提交
288
		const rootName = this.labelService.getWorkspaceLabel(workspace);
I
isidor 已提交
289
		const rootPath = root ? this.labelService.getUriLabel(root) : '';
B
Benjamin Pasero 已提交
290
		const folderName = folder ? folder.name : '';
I
isidor 已提交
291
		const folderPath = folder ? this.labelService.getUriLabel(folder.uri) : '';
B
Benjamin Pasero 已提交
292
		const dirty = editor && editor.isDirty() ? TitlebarPart.TITLE_DIRTY : '';
293
		const appName = this.environmentService.appNameLong;
294
		const remoteName = this.environmentService.configuration.remoteAuthority;
295
		const separator = TitlebarPart.TITLE_SEPARATOR;
296
		const titleTemplate = this.configurationService.getValue<string>('window.title');
297

I
isidor 已提交
298
		return template(titleTemplate, {
B
Benjamin Pasero 已提交
299 300 301
			activeEditorShort,
			activeEditorLong,
			activeEditorMedium,
302 303 304
			activeFolderShort,
			activeFolderMedium,
			activeFolderLong,
305 306
			rootName,
			rootPath,
307 308
			folderName,
			folderPath,
309 310
			dirty,
			appName,
311
			remoteName,
312 313 314 315
			separator: { label: separator }
		});
	}

B
Benjamin Pasero 已提交
316
	createContentArea(parent: HTMLElement): HTMLElement {
317
		this.element = parent;
B
Benjamin Pasero 已提交
318

319
		// Draggable region that we can manipulate for #52522
320 321 322
		if (!isWeb) {
			this.dragRegion = append(this.element, $('div.titlebar-drag-region'));
		}
323

324 325
		// App Icon (Native Windows/Linux)
		if (!isMacintosh && !isWeb) {
326
			this.appIcon = append(this.element, $('div.window-appicon'));
S
SteVen Batten 已提交
327 328 329
			this.onUpdateAppIconDragBehavior();

			this._register(addDisposableListener(this.appIcon, EventType.DBLCLICK, (e => {
330
				this.electronService.closeWindow();
S
SteVen Batten 已提交
331
			})));
332
		}
S
SteVen Batten 已提交
333

334 335 336
		// Menubar: install a custom menu bar depending on configuration
		if (getTitleBarStyle(this.configurationService, this.environmentService) !== 'native' && (!isMacintosh || isWeb)) {
			this.customMenubar = this._register(this.instantiationService.createInstance(CustomMenubarControl));
S
SteVen Batten 已提交
337 338
			this.menubar = append(this.element, $('div.menubar'));
			this.menubar.setAttribute('role', 'menubar');
S
SteVen Batten 已提交
339

340
			this.customMenubar.create(this.menubar);
S
SteVen Batten 已提交
341

342 343
			this._register(this.customMenubar.onVisibilityChange(e => this.onMenubarVisibilityChanged(e)));
			this._register(this.customMenubar.onFocusStateChange(e => this.onMenubarFocusChanged(e)));
S
SteVen Batten 已提交
344 345
		}

B
Benjamin Pasero 已提交
346
		// Title
347
		this.title = append(this.element, $('div.window-title'));
B
Benjamin Pasero 已提交
348
		if (this.pendingTitle) {
349
			this.title.innerText = this.pendingTitle;
B
Benjamin Pasero 已提交
350
		} else {
351
			this.titleUpdater.schedule();
B
Benjamin Pasero 已提交
352 353
		}

B
Benjamin Pasero 已提交
354
		// Context menu on title
355 356 357 358
		[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 已提交
359

360 361 362
					this.onContextMenu(e);
				}
			}));
B
Benjamin Pasero 已提交
363 364
		});

365 366
		// Window Controls (Native Windows/Linux)
		if (!isMacintosh && !isWeb) {
367
			this.windowControls = append(this.element, $('div.window-controls-container'));
368

B
Benjamin Pasero 已提交
369
			// Minimize
370 371 372 373
			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 => {
374
				this.electronService.minimizeWindow();
375
			}));
R
Ryan Adolf 已提交
376

B
Benjamin Pasero 已提交
377
			// Restore
378 379 380
			const restoreIconContainer = append(this.windowControls, $('div.window-icon-bg'));
			this.maxRestoreControl = append(restoreIconContainer, $('div.window-icon'));
			addClass(this.maxRestoreControl, 'window-max-restore');
381
			this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, async e => {
382
				const maximized = await this.electronService.isMaximized();
383
				if (maximized) {
384
					return this.electronService.unmaximizeWindow();
385 386
				}

387
				return this.electronService.maximizeWindow();
388
			}));
R
Ryan Adolf 已提交
389

B
Benjamin Pasero 已提交
390
			// Close
391 392 393 394 395
			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 => {
396
				this.electronService.closeWindow();
397
			}));
398

S
SteVen Batten 已提交
399
			// Resizer
400
			this.resizer = append(this.element, $('div.resizer'));
S
SteVen Batten 已提交
401

402
			const isMaximized = this.environmentService.configuration.maximized ? true : false;
403
			this.onDidChangeMaximized(isMaximized);
404
			this._register(this.windowService.onDidChangeMaximize(this.onDidChangeMaximized, this));
405
		}
R
Ryan Adolf 已提交
406

407 408
		// 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.
409
		this._register(addDisposableListener(this.element, EventType.MOUSE_DOWN, e => {
410
			if (e.target && isAncestor(e.target as HTMLElement, this.menubar)) {
S
SteVen Batten 已提交
411 412 413
				return;
			}

414 415 416 417 418 419
			const active = document.activeElement;
			setTimeout(() => {
				if (active instanceof HTMLElement) {
					active.focus();
				}
			}, 0 /* need a timeout because we are in capture phase */);
420
		}, true /* use capture to know the currently active element properly */));
421

S
SteVen Batten 已提交
422 423
		this.updateStyles();

424
		return this.element;
B
Benjamin Pasero 已提交
425 426
	}

427
	private onDidChangeMaximized(maximized: boolean) {
S
SteVen Batten 已提交
428 429
		if (this.maxRestoreControl) {
			if (maximized) {
430 431
				removeClass(this.maxRestoreControl, 'window-maximize');
				addClass(this.maxRestoreControl, 'window-unmaximize');
S
SteVen Batten 已提交
432
			} else {
433 434
				removeClass(this.maxRestoreControl, 'window-unmaximize');
				addClass(this.maxRestoreControl, 'window-maximize');
S
SteVen Batten 已提交
435
			}
S
SteVen Batten 已提交
436
		}
S
SteVen Batten 已提交
437

S
SteVen Batten 已提交
438 439
		if (this.resizer) {
			if (maximized) {
440
				hide(this.resizer);
S
SteVen Batten 已提交
441
			} else {
442
				show(this.resizer);
S
SteVen Batten 已提交
443
			}
S
SteVen Batten 已提交
444
		}
S
SteVen Batten 已提交
445 446

		this.adjustTitleMarginToCenter();
447 448
	}

449
	updateStyles(): void {
B
Benjamin Pasero 已提交
450 451 452
		super.updateStyles();

		// Part container
453
		if (this.element) {
S
SteVen Batten 已提交
454
			if (this.isInactive) {
455
				addClass(this.element, 'inactive');
S
SteVen Batten 已提交
456
			} else {
457
				removeClass(this.element, 'inactive');
S
SteVen Batten 已提交
458 459
			}

B
Benjamin Pasero 已提交
460
			const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND);
461
			this.element.style.backgroundColor = titleBackground;
M
Matt Bierner 已提交
462
			if (titleBackground && Color.fromHex(titleBackground).isLighter()) {
463
				addClass(this.element, 'light');
S
SteVen Batten 已提交
464
			} else {
465
				removeClass(this.element, 'light');
S
SteVen Batten 已提交
466
			}
467

B
Benjamin Pasero 已提交
468
			const titleForeground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_FOREGROUND : TITLE_BAR_ACTIVE_FOREGROUND);
469
			this.element.style.color = titleForeground;
B
Benjamin Pasero 已提交
470

471
			const titleBorder = this.getColor(TITLE_BAR_BORDER);
472
			this.element.style.borderBottom = titleBorder ? `1px solid ${titleBorder}` : null;
473
		}
B
Benjamin Pasero 已提交
474 475
	}

S
SteVen Batten 已提交
476 477 478
	private onUpdateAppIconDragBehavior() {
		const setting = this.configurationService.getValue('window.doubleClickIconToClose');
		if (setting) {
S
SteVen Batten 已提交
479
			(this.appIcon.style as any)['-webkit-app-region'] = 'no-drag';
480
		} else {
S
SteVen Batten 已提交
481
			(this.appIcon.style as any)['-webkit-app-region'] = 'drag';
S
SteVen Batten 已提交
482 483 484
		}
	}

B
Benjamin Pasero 已提交
485 486 487 488 489 490
	private onContextMenu(e: MouseEvent): void {

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

491
		// Fill in contributed actions
B
Benjamin Pasero 已提交
492
		const actions: IAction[] = [];
493
		const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, undefined, actions, this.contextMenuService);
B
Benjamin Pasero 已提交
494

495 496 497 498 499 500
		// Show it
		this.contextMenuService.showContextMenu({
			getAnchor: () => anchor,
			getActions: () => actions,
			onHide: () => dispose(actionsDisposable)
		});
B
Benjamin Pasero 已提交
501 502
	}

S
SteVen Batten 已提交
503
	private adjustTitleMarginToCenter(): void {
504
		if (this.customMenubar) {
505 506 507 508 509 510 511 512 513
			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 已提交
514
				this.title.style.transform = '';
515 516
				return;
			}
S
SteVen Batten 已提交
517
		}
518 519 520 521

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

524
	updateLayout(dimension: Dimension): void {
S
SteVen Batten 已提交
525 526
		this.lastLayoutDimensions = dimension;

B
Benjamin Pasero 已提交
527
		if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
528
			// Only prevent zooming behavior on macOS or when the menubar is not visible
529
			if ((!isWeb && isMacintosh) || this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'hidden') {
530
				this.title.style.zoom = `${1 / getZoomFactor()}`;
531
				if (!isWeb && (isWindows || isLinux)) {
532 533
					this.appIcon.style.zoom = `${1 / getZoomFactor()}`;
					this.windowControls.style.zoom = `${1 / getZoomFactor()}`;
S
SteVen Batten 已提交
534
				}
535 536
			} else {
				this.title.style.zoom = null;
537
				if (!isWeb && (isWindows || isLinux)) {
538 539
					this.appIcon.style.zoom = null;
					this.windowControls.style.zoom = null;
S
SteVen Batten 已提交
540
				}
541 542
			}

S
SteVen Batten 已提交
543
			runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter());
544

545
			if (this.customMenubar) {
M
Matt Bierner 已提交
546
				const menubarDimension = new Dimension(0, dimension.height);
547
				this.customMenubar.layout(menubarDimension);
548
			}
549
		}
550 551
	}

B
Benjamin Pasero 已提交
552 553
	layout(width: number, height: number): void {
		this.updateLayout(new Dimension(width, height));
554

B
Benjamin Pasero 已提交
555
		super.layoutContents(width, height);
556
	}
B
Benjamin Pasero 已提交
557

558 559 560 561
	toJSON(): object {
		return {
			type: Parts.TITLEBAR_PART
		};
B
Benjamin Pasero 已提交
562
	}
B
Benjamin Pasero 已提交
563 564
}

S
SteVen Batten 已提交
565 566 567 568
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
	const titlebarActiveFg = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND);
	if (titlebarActiveFg) {
		collector.addRule(`
569
		.monaco-workbench .part.titlebar > .window-controls-container .window-icon {
S
SteVen Batten 已提交
570 571 572 573 574 575 576 577
			background-color: ${titlebarActiveFg};
		}
		`);
	}

	const titlebarInactiveFg = theme.getColor(TITLE_BAR_INACTIVE_FOREGROUND);
	if (titlebarInactiveFg) {
		collector.addRule(`
578
		.monaco-workbench .part.titlebar.inactive > .window-controls-container .window-icon {
S
SteVen Batten 已提交
579 580 581 582 583
				background-color: ${titlebarInactiveFg};
			}
		`);
	}
});
584

585
registerSingleton(ITitleService, TitlebarPart);