titlebarPart.ts 24.0 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';
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

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

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

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

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

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

91 92
	private contextMenu: IMenu;

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

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

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

116 117 118 119
		this.registerListeners();
	}

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

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

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

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

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

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

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

			this.adjustTitleMarginToCenter();
173 174

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

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

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

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

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

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

B
Benjamin Pasero 已提交
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
	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 已提交
220

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

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

231
		if (this.properties.isAdmin) {
B
Benjamin Pasero 已提交
232
			title = `${title || this.environmentService.appNameLong} ${TitlebarPart.NLS_USER_IS_ADMIN}`;
233 234 235
		}

		if (!this.properties.isPure) {
B
Benjamin Pasero 已提交
236
			title = `${title || this.environmentService.appNameLong} ${TitlebarPart.NLS_UNSUPPORTED}`;
237 238 239
		}

		if (this.environmentService.isExtensionDevelopment) {
B
Benjamin Pasero 已提交
240
			title = `${TitlebarPart.NLS_EXTENSION_HOST} - ${title || this.environmentService.appNameLong}`;
241 242 243 244 245
		}

		return title;
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		this.customMenubar.create(this.menubar);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

448
			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, 'codicon-chrome-maximize');
				addClass(this.maxRestoreControl, 'codicon-chrome-restore');
S
SteVen Batten 已提交
482
			} else {
483 484
				removeClass(this.maxRestoreControl, 'codicon-chrome-restore');
				addClass(this.maxRestoreControl, 'codicon-chrome-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
	private onUpdateAppIconDragBehavior() {
		const setting = this.configurationService.getValue('window.doubleClickIconToClose');
S
SteVen Batten 已提交
528
		if (setting && this.appIcon) {
S
SteVen Batten 已提交
529
			(this.appIcon.style as any)['-webkit-app-region'] = 'no-drag';
S
SteVen Batten 已提交
530
		} else if (this.appIcon) {
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
	private get currentMenubarVisibility(): MenuBarVisibility {
575
		return getMenuBarVisibility(this.configurationService, this.environmentService);
S
SteVen Batten 已提交
576 577
	}

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)) {
S
SteVen Batten 已提交
586 587 588 589 590 591 592
					if (this.appIcon) {
						this.appIcon.style.zoom = `${1 / getZoomFactor()}`;
					}

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

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

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

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

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

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

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

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

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

649
registerSingleton(ITitleService, TitlebarPart);