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 { IDisposable, dispose } 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 59 60
	private _onMenubarVisibilityChange = this._register(new Emitter<boolean>());
	get onMenubarVisibilityChange(): Event<boolean> { return this._onMenubarVisibilityChange.event; }

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
	private properties: ITitleProperties;
79 80
	private activeEditorListeners: IDisposable[];

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
		this.properties = { isPure: true, isAdmin: false };
100 101
		this.activeEditorListeners = [];

102 103 104 105
		this.registerListeners();
	}

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

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

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

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

		if (event.affectsConfiguration('window.doubleClickIconToClose')) {
			if (this.appIcon) {
				this.onUpdateAppIconDragBehavior();
			}
		}
135 136
	}

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

			this.adjustTitleMarginToCenter();
147 148

			this._onMenubarVisibilityChange.fire(visible);
S
SteVen Batten 已提交
149
		}
150 151
	}

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

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

		// Dispose old listeners
		dispose(this.activeEditorListeners);
		this.activeEditorListeners = [];

		// Calculate New Window Title
169
		this.titleUpdater.schedule();
170 171

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

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

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

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

		// Keep for context menu
		this.representedFileName = path;
191 192
	}

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

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

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

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

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

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

		return title;
	}

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

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

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

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

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

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

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

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

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

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

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

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

S
SteVen Batten 已提交
339
		// Menubar: the menubar part which is responsible for populating both the custom and native menubars
S
SteVen Batten 已提交
340 341 342 343 344 345 346
		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 已提交
347

S
SteVen Batten 已提交
348
			customMenubarControl.create(this.menubar);
S
SteVen Batten 已提交
349

S
SteVen Batten 已提交
350 351
			this._register(customMenubarControl.onVisibilityChange(e => this.onMenubarVisibilityChanged(e)));
			this._register(customMenubarControl.onFocusStateChange(e => this.onMenubarFocusChanged(e)));
S
SteVen Batten 已提交
352 353
		}

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

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

S
SteVen Batten 已提交
367
				this.onTitleDoubleclick();
368
			}));
S
SteVen Batten 已提交
369
		}
370

B
Benjamin Pasero 已提交
371
		// Context menu on title
372 373 374 375
		[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 已提交
376

377 378 379
					this.onContextMenu(e);
				}
			}));
B
Benjamin Pasero 已提交
380 381
		});

382 383
		// Window Controls (Native Windows/Linux)
		if (!isMacintosh && !isWeb) {
384
			this.windowControls = append(this.element, $('div.window-controls-container'));
385

S
SteVen Batten 已提交
386

B
Benjamin Pasero 已提交
387
			// Minimize
388 389 390 391 392 393
			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 已提交
394

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

B
Benjamin Pasero 已提交
408
			// Close
409 410 411 412 413 414 415
			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();
			}));
416

S
SteVen Batten 已提交
417
			// Resizer
418
			this.resizer = append(this.element, $('div.resizer'));
S
SteVen Batten 已提交
419

420
			const isMaximized = this.environmentService.configuration.maximized ? true : false;
421
			this.onDidChangeMaximized(isMaximized);
422
			this.windowService.onDidChangeMaximize(this.onDidChangeMaximized, this);
423
		}
R
Ryan Adolf 已提交
424

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

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

S
SteVen Batten 已提交
440 441
		this.updateStyles();

442
		return this.element;
B
Benjamin Pasero 已提交
443 444
	}

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

S
SteVen Batten 已提交
456 457
		if (this.resizer) {
			if (maximized) {
458
				hide(this.resizer);
S
SteVen Batten 已提交
459
			} else {
460
				show(this.resizer);
S
SteVen Batten 已提交
461
			}
S
SteVen Batten 已提交
462
		}
S
SteVen Batten 已提交
463 464

		this.adjustTitleMarginToCenter();
465 466
	}

467
	updateStyles(): void {
B
Benjamin Pasero 已提交
468 469 470
		super.updateStyles();

		// Part container
471
		if (this.element) {
S
SteVen Batten 已提交
472
			if (this.isInactive) {
473
				addClass(this.element, 'inactive');
S
SteVen Batten 已提交
474
			} else {
475
				removeClass(this.element, 'inactive');
S
SteVen Batten 已提交
476 477
			}

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

B
Benjamin Pasero 已提交
486
			const titleForeground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_FOREGROUND : TITLE_BAR_ACTIVE_FOREGROUND);
487
			this.element.style.color = titleForeground;
B
Benjamin Pasero 已提交
488

489
			const titleBorder = this.getColor(TITLE_BAR_BORDER);
490
			this.element.style.borderBottom = titleBorder ? `1px solid ${titleBorder}` : null;
491
		}
B
Benjamin Pasero 已提交
492 493
	}

494
	private onTitleDoubleclick(): void {
495
		this.windowService.onWindowTitleDoubleClick();
496 497
	}

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

B
Benjamin Pasero 已提交
507 508 509 510 511 512 513 514 515 516 517
	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,
518
				getActions: () => actions,
B
Benjamin Pasero 已提交
519 520 521 522 523 524 525 526 527
				onHide: () => actions.forEach(a => a.dispose())
			});
		}
	}

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

		if (this.representedFileName) {
B
Benjamin Pasero 已提交
528
			const segments = this.representedFileName.split(posix.sep);
B
Benjamin Pasero 已提交
529
			for (let i = segments.length; i > 0; i--) {
530 531 532 533 534 535 536
				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 已提交
537
				const path = segments.slice(0, pathOffset).join(posix.sep);
538

B
Benjamin Pasero 已提交
539
				let label: string;
540
				if (!isFile) {
B
Benjamin Pasero 已提交
541
					label = getBaseLabel(dirname(path));
B
Benjamin Pasero 已提交
542
				} else {
I
isidor 已提交
543
					label = getBaseLabel(path);
544 545
				}

B
Benjamin Pasero 已提交
546
				actions.push(new ShowItemInFolderAction(path, label || posix.sep, this.windowsService));
B
Benjamin Pasero 已提交
547 548 549 550 551 552
			}
		}

		return actions;
	}

S
SteVen Batten 已提交
553
	private adjustTitleMarginToCenter(): void {
554 555 556 557 558 559 560 561 562 563 564 565 566
		if (!isMacintosh || isWeb) {
			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 已提交
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
	}

574
	updateLayout(dimension: Dimension): void {
S
SteVen Batten 已提交
575 576
		this.lastLayoutDimensions = dimension;

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

S
SteVen Batten 已提交
593
			runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter());
594

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

B
Benjamin Pasero 已提交
602 603
	layout(width: number, height: number): void {
		this.updateLayout(new Dimension(width, height));
604

B
Benjamin Pasero 已提交
605
		super.layoutContents(width, height);
606
	}
B
Benjamin Pasero 已提交
607

608 609 610 611
	toJSON(): object {
		return {
			type: Parts.TITLEBAR_PART
		};
B
Benjamin Pasero 已提交
612
	}
B
Benjamin Pasero 已提交
613 614 615 616
}

class ShowItemInFolderAction extends Action {

617 618
	constructor(private path: string, label: string, private windowsService: IWindowsService) {
		super('showItemInFolder.action.id', label);
B
Benjamin Pasero 已提交
619 620
	}

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

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

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

registerSingleton(ITitleService, TitlebarPart);