titlebarPart.ts 23.7 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 { dirname, posix } from 'vs/base/common/path';
8
import * as resources from 'vs/base/common/resources';
B
Benjamin Pasero 已提交
9
import { Part } from 'vs/workbench/browser/part';
10
import { ITitleService, ITitleProperties } from 'vs/workbench/services/title/common/titleService';
B
Benjamin Pasero 已提交
11
import { getZoomFactor } from 'vs/base/browser/browser';
B
Benjamin Pasero 已提交
12
import { IWindowService, IWindowsService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows';
B
Benjamin Pasero 已提交
13 14 15
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { IAction, Action } from 'vs/base/common/actions';
16
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
B
Benjamin Pasero 已提交
17
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
18
import { DisposableStore } from 'vs/base/common/lifecycle';
19
import * as nls from 'vs/nls';
B
Benjamin Pasero 已提交
20
import { EditorInput, toResource, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor';
21
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
22
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
S
SteVen Batten 已提交
23
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
24
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';
25
import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform';
26
import { URI } from 'vs/base/common/uri';
R
Ryan Adolf 已提交
27
import { Color } from 'vs/base/common/color';
B
Benjamin Pasero 已提交
28
import { trim } from 'vs/base/common/strings';
S
SteVen Batten 已提交
29
import { EventType, EventHelper, Dimension, isAncestor, hide, show, removeClass, addClass, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
S
SteVen Batten 已提交
30
import { MenubarControl, NativeMenubarControl, CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl';
B
Benjamin Pasero 已提交
31
import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
I
isidor 已提交
32
import { template, getBaseLabel } from 'vs/base/common/labels';
I
isidor 已提交
33
import { ILabelService } from 'vs/platform/label/common/label';
34
import { Event, Emitter } from 'vs/base/common/event';
B
Benjamin Pasero 已提交
35
import { IStorageService } from 'vs/platform/storage/common/storage';
36
import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
37
import { RunOnceScheduler } from 'vs/base/common/async';
38
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
39
import { Schemas } from 'vs/base/common/network';
B
Benjamin Pasero 已提交
40

41
export class TitlebarPart extends Part implements ITitleService {
B
Benjamin Pasero 已提交
42

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

49
	//#region IView
50

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

56
	//#endregion
57

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

B
Benjamin Pasero 已提交
61
	_serviceBrand: ServiceIdentifier<any>;
B
Benjamin Pasero 已提交
62

63 64 65 66 67
	private title: HTMLElement;
	private dragRegion: HTMLElement;
	private windowControls: HTMLElement;
	private maxRestoreControl: HTMLElement;
	private appIcon: HTMLElement;
68
	private menubarPart: MenubarControl;
69 70
	private menubar: HTMLElement;
	private resizer: HTMLElement;
S
SteVen Batten 已提交
71
	private lastLayoutDimensions: Dimension;
B
Benjamin Pasero 已提交
72

B
Benjamin Pasero 已提交
73
	private pendingTitle: string;
B
Benjamin Pasero 已提交
74
	private representedFileName: string;
75

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

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

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

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

		this.registerListeners();
	}

	private registerListeners(): void {
103
		this._register(this.windowService.onDidChangeFocus(focused => focused ? this.onFocus() : this.onBlur()));
B
Benjamin Pasero 已提交
104 105
		this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChanged(e)));
		this._register(this.editorService.onDidActiveEditorChange(() => this.onActiveEditorChange()));
106 107 108 109
		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()));
110 111
	}

B
Benjamin Pasero 已提交
112 113 114 115 116 117 118 119 120 121
	private onBlur(): void {
		this.isInactive = true;
		this.updateStyles();
	}

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

122 123
	private onConfigurationChanged(event: IConfigurationChangeEvent): void {
		if (event.affectsConfiguration('window.title')) {
124
			this.titleUpdater.schedule();
125
		}
S
SteVen Batten 已提交
126 127 128 129 130 131

		if (event.affectsConfiguration('window.doubleClickIconToClose')) {
			if (this.appIcon) {
				this.onUpdateAppIconDragBehavior();
			}
		}
132 133
	}

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

			this.adjustTitleMarginToCenter();
144 145

			this._onMenubarVisibilityChange.fire(visible);
S
SteVen Batten 已提交
146
		}
147 148
	}

S
SteVen Batten 已提交
149
	private onMenubarFocusChanged(focused: boolean) {
150
		if (!isWeb && (isWindows || isLinux)) {
S
SteVen Batten 已提交
151 152 153 154 155 156 157 158
			if (focused) {
				hide(this.dragRegion);
			} else {
				show(this.dragRegion);
			}
		}
	}

B
Benjamin Pasero 已提交
159
	private onActiveEditorChange(): void {
160 161

		// Dispose old listeners
162
		this.activeEditorListeners.clear();
163 164

		// Calculate New Window Title
165
		this.titleUpdater.schedule();
166 167

		// Apply listener for dirty and label changes
B
Benjamin Pasero 已提交
168 169
		const activeEditor = this.editorService.activeEditor;
		if (activeEditor instanceof EditorInput) {
170 171
			this.activeEditorListeners.add(activeEditor.onDidChangeDirty(() => this.titleUpdater.schedule()));
			this.activeEditorListeners.add(activeEditor.onDidChangeLabel(() => this.titleUpdater.schedule()));
172
		}
B
Benjamin Pasero 已提交
173 174 175 176 177 178

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

	private updateRepresentedFilename(): void {
179
		const file = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file });
B
Benjamin Pasero 已提交
180 181 182 183 184 185 186
		const path = file ? file.fsPath : '';

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

		// Keep for context menu
		this.representedFileName = path;
187 188
	}

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

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

213 214 215
	private getWindowTitle(): string {
		let title = this.doGetWindowTitle();

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

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

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

		return title;
	}

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

239
			this.titleUpdater.schedule();
240 241 242
		}
	}

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

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

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

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

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

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

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

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

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

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

S
SteVen Batten 已提交
335
		// Menubar: the menubar part which is responsible for populating both the custom and native menubars
S
SteVen Batten 已提交
336 337 338 339 340 341 342
		if ((isMacintosh && !isWeb) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native') {
			this.menubarPart = this.instantiationService.createInstance(NativeMenubarControl);
		} else {
			const customMenubarControl = this.instantiationService.createInstance(CustomMenubarControl);
			this.menubarPart = customMenubarControl;
			this.menubar = append(this.element, $('div.menubar'));
			this.menubar.setAttribute('role', 'menubar');
S
SteVen Batten 已提交
343

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

S
SteVen Batten 已提交
346 347
			this._register(customMenubarControl.onVisibilityChange(e => this.onMenubarVisibilityChanged(e)));
			this._register(customMenubarControl.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

S
SteVen Batten 已提交
382

B
Benjamin Pasero 已提交
383
			// Minimize
384 385 386 387 388 389
			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 已提交
390

B
Benjamin Pasero 已提交
391
			// Restore
392 393 394
			const restoreIconContainer = append(this.windowControls, $('div.window-icon-bg'));
			this.maxRestoreControl = append(restoreIconContainer, $('div.window-icon'));
			addClass(this.maxRestoreControl, 'window-max-restore');
395 396 397 398 399 400 401
			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();
402
			}));
R
Ryan Adolf 已提交
403

B
Benjamin Pasero 已提交
404
			// Close
405 406 407 408 409 410 411
			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();
			}));
412

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

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

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

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

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

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

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

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

		this.adjustTitleMarginToCenter();
461 462
	}

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

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

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

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

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

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

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

B
Benjamin Pasero 已提交
503 504 505 506 507 508 509 510 511 512 513
	private onContextMenu(e: MouseEvent): void {

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

		// Show menu
		const actions = this.getContextMenuActions();
		if (actions.length) {
			this.contextMenuService.showContextMenu({
				getAnchor: () => anchor,
514
				getActions: () => actions,
B
Benjamin Pasero 已提交
515 516 517 518 519 520 521 522 523
				onHide: () => actions.forEach(a => a.dispose())
			});
		}
	}

	private getContextMenuActions(): IAction[] {
		const actions: IAction[] = [];

		if (this.representedFileName) {
B
Benjamin Pasero 已提交
524
			const segments = this.representedFileName.split(posix.sep);
B
Benjamin Pasero 已提交
525
			for (let i = segments.length; i > 0; i--) {
526 527 528 529 530 531 532
				const isFile = (i === segments.length);

				let pathOffset = i;
				if (!isFile) {
					pathOffset++; // for segments which are not the file name we want to open the folder
				}

B
Benjamin Pasero 已提交
533
				const path = segments.slice(0, pathOffset).join(posix.sep);
534

B
Benjamin Pasero 已提交
535
				let label: string;
536
				if (!isFile) {
B
Benjamin Pasero 已提交
537
					label = getBaseLabel(dirname(path));
B
Benjamin Pasero 已提交
538
				} else {
I
isidor 已提交
539
					label = getBaseLabel(path);
540 541
				}

B
Benjamin Pasero 已提交
542
				actions.push(new ShowItemInFolderAction(path, label || posix.sep, this.windowsService));
B
Benjamin Pasero 已提交
543 544 545 546 547 548
			}
		}

		return actions;
	}

S
SteVen Batten 已提交
549
	private adjustTitleMarginToCenter(): void {
550
		if (this.menubarPart instanceof CustomMenubarControl) {
551 552 553 554 555 556 557 558 559 560 561 562
			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;
				this.title.style.transform = null;
				return;
			}
S
SteVen Batten 已提交
563
		}
564 565 566 567

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

570
	updateLayout(dimension: Dimension): void {
S
SteVen Batten 已提交
571 572
		this.lastLayoutDimensions = dimension;

B
Benjamin Pasero 已提交
573
		if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
574
			// Only prevent zooming behavior on macOS or when the menubar is not visible
575
			if ((!isWeb && isMacintosh) || this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'hidden') {
576
				this.title.style.zoom = `${1 / getZoomFactor()}`;
577
				if (!isWeb && (isWindows || isLinux)) {
578 579
					this.appIcon.style.zoom = `${1 / getZoomFactor()}`;
					this.windowControls.style.zoom = `${1 / getZoomFactor()}`;
S
SteVen Batten 已提交
580
				}
581 582
			} else {
				this.title.style.zoom = null;
583
				if (!isWeb && (isWindows || isLinux)) {
584 585
					this.appIcon.style.zoom = null;
					this.windowControls.style.zoom = null;
S
SteVen Batten 已提交
586
				}
587 588
			}

S
SteVen Batten 已提交
589
			runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter());
590

S
SteVen Batten 已提交
591
			if (this.menubarPart instanceof CustomMenubarControl) {
M
Matt Bierner 已提交
592
				const menubarDimension = new Dimension(0, dimension.height);
593 594
				this.menubarPart.layout(menubarDimension);
			}
595
		}
596 597
	}

B
Benjamin Pasero 已提交
598 599
	layout(width: number, height: number): void {
		this.updateLayout(new Dimension(width, height));
600

B
Benjamin Pasero 已提交
601
		super.layoutContents(width, height);
602
	}
B
Benjamin Pasero 已提交
603

604 605 606 607
	toJSON(): object {
		return {
			type: Parts.TITLEBAR_PART
		};
B
Benjamin Pasero 已提交
608
	}
B
Benjamin Pasero 已提交
609 610 611 612
}

class ShowItemInFolderAction extends Action {

613 614
	constructor(private path: string, label: string, private windowsService: IWindowsService) {
		super('showItemInFolder.action.id', label);
B
Benjamin Pasero 已提交
615 616
	}

J
Johannes Rieken 已提交
617
	run(): Promise<void> {
M
Martin Aeschlimann 已提交
618
		return this.windowsService.showItemInFolder(URI.file(this.path));
B
Benjamin Pasero 已提交
619
	}
R
Ryan Adolf 已提交
620
}
S
SteVen Batten 已提交
621 622 623 624 625

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

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

registerSingleton(ITitleService, TitlebarPart);