titlebarPart.ts 22.8 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 } 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
import { Schemas } from 'vs/base/common/network';
39 40 41
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 已提交
42

43
export class TitlebarPart extends Part implements ITitleService {
B
Benjamin Pasero 已提交
44

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

51
	//#region IView
52

B
Benjamin Pasero 已提交
53 54
	readonly minimumWidth: number = 0;
	readonly maximumWidth: number = Number.POSITIVE_INFINITY;
55 56
	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 已提交
57

58
	//#endregion
59

60
	private _onMenubarVisibilityChange = this._register(new Emitter<boolean>());
61
	readonly onMenubarVisibilityChange: Event<boolean> = this._onMenubarVisibilityChange.event;
62

63
	_serviceBrand: undefined;
B
Benjamin Pasero 已提交
64

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

B
Benjamin Pasero 已提交
75
	private pendingTitle: string;
76

B
Benjamin Pasero 已提交
77 78
	private isInactive: boolean;

79 80
	private readonly properties: ITitleProperties = { isPure: true, isAdmin: false };
	private readonly activeEditorListeners = this._register(new DisposableStore());
81

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

84 85
	private contextMenu: IMenu;

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

103 104
		this.contextMenu = this._register(menuService.createMenu(MenuId.TitleBarContext, contextKeyService));

105 106 107 108
		this.registerListeners();
	}

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

B
Benjamin Pasero 已提交
118 119 120 121 122 123 124 125 126 127
	private onBlur(): void {
		this.isInactive = true;
		this.updateStyles();
	}

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

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

		if (event.affectsConfiguration('window.doubleClickIconToClose')) {
			if (this.appIcon) {
				this.onUpdateAppIconDragBehavior();
			}
		}
138 139
	}

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

			this.adjustTitleMarginToCenter();
150 151

			this._onMenubarVisibilityChange.fire(visible);
S
SteVen Batten 已提交
152
		}
153 154
	}

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

B
Benjamin Pasero 已提交
165
	private onActiveEditorChange(): void {
166 167

		// Dispose old listeners
168
		this.activeEditorListeners.clear();
169 170

		// Calculate New Window Title
171
		this.titleUpdater.schedule();
172 173

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

		// Represented File Name
		this.updateRepresentedFilename();
	}

	private updateRepresentedFilename(): void {
185
		const file = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file });
B
Benjamin Pasero 已提交
186 187 188 189
		const path = file ? file.fsPath : '';

		// Apply to window
		this.windowService.setRepresentedFilename(path);
190 191
	}

B
Benjamin Pasero 已提交
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
	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 已提交
208

209
		if ((isWeb || isWindows || isLinux) && this.title) {
S
SteVen Batten 已提交
210 211 212
			if (this.lastLayoutDimensions) {
				this.updateLayout(this.lastLayoutDimensions);
			}
213
		}
B
Benjamin Pasero 已提交
214 215
	}

216 217 218
	private getWindowTitle(): string {
		let title = this.doGetWindowTitle();

219
		if (this.properties.isAdmin) {
B
Benjamin Pasero 已提交
220
			title = `${title || this.environmentService.appNameLong} ${TitlebarPart.NLS_USER_IS_ADMIN}`;
221 222 223
		}

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

		if (this.environmentService.isExtensionDevelopment) {
B
Benjamin Pasero 已提交
228
			title = `${TitlebarPart.NLS_EXTENSION_HOST} - ${title || this.environmentService.appNameLong}`;
229 230 231 232 233
		}

		return title;
	}

B
Benjamin Pasero 已提交
234
	updateProperties(properties: ITitleProperties): void {
235 236 237 238 239 240 241
		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;

242
			this.titleUpdater.schedule();
243 244 245
		}
	}

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

B
Benjamin Pasero 已提交
267
		// Compute root
268
		let root: URI | undefined;
269
		if (workspace.configuration) {
270
			root = workspace.configuration;
271 272
		} else if (workspace.folders.length) {
			root = workspace.folders[0].uri;
273 274
		}

B
Benjamin Pasero 已提交
275 276 277 278 279 280 281
		// Compute active editor folder
		const editorResource = editor ? toResource(editor) : undefined;
		let editorFolderResource = editorResource ? resources.dirname(editorResource) : undefined;
		if (editorFolderResource && editorFolderResource.path === '.') {
			editorFolderResource = undefined;
		}

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

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

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

B
Benjamin Pasero 已提交
320
	createContentArea(parent: HTMLElement): HTMLElement {
321
		this.element = parent;
B
Benjamin Pasero 已提交
322

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

328 329
		// App Icon (Native Windows/Linux)
		if (!isMacintosh && !isWeb) {
330
			this.appIcon = append(this.element, $('div.window-appicon'));
S
SteVen Batten 已提交
331 332 333 334 335
			this.onUpdateAppIconDragBehavior();

			this._register(addDisposableListener(this.appIcon, EventType.DBLCLICK, (e => {
				this.windowService.closeWindow();
			})));
336
		}
S
SteVen Batten 已提交
337

338 339 340
		// 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 已提交
341 342
			this.menubar = append(this.element, $('div.menubar'));
			this.menubar.setAttribute('role', 'menubar');
S
SteVen Batten 已提交
343

344
			this.customMenubar.create(this.menubar);
S
SteVen Batten 已提交
345

346 347
			this._register(this.customMenubar.onVisibilityChange(e => this.onMenubarVisibilityChanged(e)));
			this._register(this.customMenubar.onFocusStateChange(e => this.onMenubarFocusChanged(e)));
S
SteVen Batten 已提交
348 349
		}

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

358
		// Maximize/Restore on doubleclick
359
		if (isMacintosh && !isWeb) {
360
			this._register(addDisposableListener(this.element, EventType.DBLCLICK, e => {
S
SteVen Batten 已提交
361
				EventHelper.stop(e);
362

S
SteVen Batten 已提交
363
				this.onTitleDoubleclick();
364
			}));
S
SteVen Batten 已提交
365
		}
366

B
Benjamin Pasero 已提交
367
		// Context menu on title
368 369 370 371
		[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 已提交
372

373 374 375
					this.onContextMenu(e);
				}
			}));
B
Benjamin Pasero 已提交
376 377
		});

378 379
		// Window Controls (Native Windows/Linux)
		if (!isMacintosh && !isWeb) {
380
			this.windowControls = append(this.element, $('div.window-controls-container'));
381

B
Benjamin Pasero 已提交
382
			// Minimize
383 384 385 386 387 388
			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 => {
				this.windowService.minimizeWindow();
			}));
R
Ryan Adolf 已提交
389

B
Benjamin Pasero 已提交
390
			// Restore
391 392 393
			const restoreIconContainer = append(this.windowControls, $('div.window-icon-bg'));
			this.maxRestoreControl = append(restoreIconContainer, $('div.window-icon'));
			addClass(this.maxRestoreControl, 'window-max-restore');
394 395 396 397 398 399 400
			this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, async e => {
				const maximized = await this.windowService.isMaximized();
				if (maximized) {
					return this.windowService.unmaximizeWindow();
				}

				return this.windowService.maximizeWindow();
401
			}));
R
Ryan Adolf 已提交
402

B
Benjamin Pasero 已提交
403
			// Close
404 405 406 407 408 409 410
			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 => {
				this.windowService.closeWindow();
			}));
411

S
SteVen Batten 已提交
412
			// Resizer
413
			this.resizer = append(this.element, $('div.resizer'));
S
SteVen Batten 已提交
414

415
			const isMaximized = this.environmentService.configuration.maximized ? true : false;
416
			this.onDidChangeMaximized(isMaximized);
417
			this.windowService.onDidChangeMaximize(this.onDidChangeMaximized, this);
418
		}
R
Ryan Adolf 已提交
419

420 421
		// 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.
422
		this._register(addDisposableListener(this.element, EventType.MOUSE_DOWN, e => {
423
			if (e.target && isAncestor(e.target as HTMLElement, this.menubar)) {
S
SteVen Batten 已提交
424 425 426
				return;
			}

427 428 429 430 431 432
			const active = document.activeElement;
			setTimeout(() => {
				if (active instanceof HTMLElement) {
					active.focus();
				}
			}, 0 /* need a timeout because we are in capture phase */);
433
		}, true /* use capture to know the currently active element properly */));
434

S
SteVen Batten 已提交
435 436
		this.updateStyles();

437
		return this.element;
B
Benjamin Pasero 已提交
438 439
	}

440
	private onDidChangeMaximized(maximized: boolean) {
S
SteVen Batten 已提交
441 442
		if (this.maxRestoreControl) {
			if (maximized) {
443 444
				removeClass(this.maxRestoreControl, 'window-maximize');
				addClass(this.maxRestoreControl, 'window-unmaximize');
S
SteVen Batten 已提交
445
			} else {
446 447
				removeClass(this.maxRestoreControl, 'window-unmaximize');
				addClass(this.maxRestoreControl, 'window-maximize');
S
SteVen Batten 已提交
448
			}
S
SteVen Batten 已提交
449
		}
S
SteVen Batten 已提交
450

S
SteVen Batten 已提交
451 452
		if (this.resizer) {
			if (maximized) {
453
				hide(this.resizer);
S
SteVen Batten 已提交
454
			} else {
455
				show(this.resizer);
S
SteVen Batten 已提交
456
			}
S
SteVen Batten 已提交
457
		}
S
SteVen Batten 已提交
458 459

		this.adjustTitleMarginToCenter();
460 461
	}

462
	updateStyles(): void {
B
Benjamin Pasero 已提交
463 464 465
		super.updateStyles();

		// Part container
466
		if (this.element) {
S
SteVen Batten 已提交
467
			if (this.isInactive) {
468
				addClass(this.element, 'inactive');
S
SteVen Batten 已提交
469
			} else {
470
				removeClass(this.element, 'inactive');
S
SteVen Batten 已提交
471 472
			}

B
Benjamin Pasero 已提交
473
			const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND);
474
			this.element.style.backgroundColor = titleBackground;
M
Matt Bierner 已提交
475
			if (titleBackground && Color.fromHex(titleBackground).isLighter()) {
476
				addClass(this.element, 'light');
S
SteVen Batten 已提交
477
			} else {
478
				removeClass(this.element, 'light');
S
SteVen Batten 已提交
479
			}
480

B
Benjamin Pasero 已提交
481
			const titleForeground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_FOREGROUND : TITLE_BAR_ACTIVE_FOREGROUND);
482
			this.element.style.color = titleForeground;
B
Benjamin Pasero 已提交
483

484
			const titleBorder = this.getColor(TITLE_BAR_BORDER);
485
			this.element.style.borderBottom = titleBorder ? `1px solid ${titleBorder}` : null;
486
		}
B
Benjamin Pasero 已提交
487 488
	}

489
	private onTitleDoubleclick(): void {
490
		this.windowService.onWindowTitleDoubleClick();
491 492
	}

S
SteVen Batten 已提交
493 494 495
	private onUpdateAppIconDragBehavior() {
		const setting = this.configurationService.getValue('window.doubleClickIconToClose');
		if (setting) {
S
SteVen Batten 已提交
496
			(this.appIcon.style as any)['-webkit-app-region'] = 'no-drag';
497
		} else {
S
SteVen Batten 已提交
498
			(this.appIcon.style as any)['-webkit-app-region'] = 'drag';
S
SteVen Batten 已提交
499 500 501
		}
	}

B
Benjamin Pasero 已提交
502 503 504 505 506 507
	private onContextMenu(e: MouseEvent): void {

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

508
		// Fill in contributed actions
B
Benjamin Pasero 已提交
509
		const actions: IAction[] = [];
510
		const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, undefined, actions, this.contextMenuService);
B
Benjamin Pasero 已提交
511

512 513 514 515 516 517
		// Show it
		this.contextMenuService.showContextMenu({
			getAnchor: () => anchor,
			getActions: () => actions,
			onHide: () => dispose(actionsDisposable)
		});
B
Benjamin Pasero 已提交
518 519
	}

S
SteVen Batten 已提交
520
	private adjustTitleMarginToCenter(): void {
521
		if (this.customMenubar) {
522 523 524 525 526 527 528 529 530
			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 已提交
531
				this.title.style.transform = '';
532 533
				return;
			}
S
SteVen Batten 已提交
534
		}
535 536 537 538

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

541
	updateLayout(dimension: Dimension): void {
S
SteVen Batten 已提交
542 543
		this.lastLayoutDimensions = dimension;

B
Benjamin Pasero 已提交
544
		if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
545
			// Only prevent zooming behavior on macOS or when the menubar is not visible
546
			if ((!isWeb && isMacintosh) || this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'hidden') {
547
				this.title.style.zoom = `${1 / getZoomFactor()}`;
548
				if (!isWeb && (isWindows || isLinux)) {
549 550
					this.appIcon.style.zoom = `${1 / getZoomFactor()}`;
					this.windowControls.style.zoom = `${1 / getZoomFactor()}`;
S
SteVen Batten 已提交
551
				}
552 553
			} else {
				this.title.style.zoom = null;
554
				if (!isWeb && (isWindows || isLinux)) {
555 556
					this.appIcon.style.zoom = null;
					this.windowControls.style.zoom = null;
S
SteVen Batten 已提交
557
				}
558 559
			}

S
SteVen Batten 已提交
560
			runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter());
561

562
			if (this.customMenubar) {
M
Matt Bierner 已提交
563
				const menubarDimension = new Dimension(0, dimension.height);
564
				this.customMenubar.layout(menubarDimension);
565
			}
566
		}
567 568
	}

B
Benjamin Pasero 已提交
569 570
	layout(width: number, height: number): void {
		this.updateLayout(new Dimension(width, height));
571

B
Benjamin Pasero 已提交
572
		super.layoutContents(width, height);
573
	}
B
Benjamin Pasero 已提交
574

575 576 577 578
	toJSON(): object {
		return {
			type: Parts.TITLEBAR_PART
		};
B
Benjamin Pasero 已提交
579
	}
B
Benjamin Pasero 已提交
580 581
}

S
SteVen Batten 已提交
582 583 584 585
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
	const titlebarActiveFg = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND);
	if (titlebarActiveFg) {
		collector.addRule(`
586
		.monaco-workbench .part.titlebar > .window-controls-container .window-icon {
S
SteVen Batten 已提交
587 588 589 590 591 592 593 594
			background-color: ${titlebarActiveFg};
		}
		`);
	}

	const titlebarInactiveFg = theme.getColor(TITLE_BAR_INACTIVE_FOREGROUND);
	if (titlebarInactiveFg) {
		collector.addRule(`
595
		.monaco-workbench .part.titlebar.inactive > .window-controls-container .window-icon {
S
SteVen Batten 已提交
596 597 598 599 600
				background-color: ${titlebarInactiveFg};
			}
		`);
	}
});
601

602
registerSingleton(ITitleService, TitlebarPart);