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 { 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 } from 'vs/workbench/common/editor';
21
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
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';
B
Benjamin Pasero 已提交
25
import { isMacintosh, isWindows, isLinux } 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';
30
import { MenubarControl } 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 } from 'vs/workbench/services/layout/browser/layoutService';
37
import { RunOnceScheduler } from 'vs/base/common/async';
B
Benjamin Pasero 已提交
38

39
export class TitlebarPart extends Part implements ITitleService {
B
Benjamin Pasero 已提交
40

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

47
	element: HTMLElement;
B
Benjamin Pasero 已提交
48 49 50 51 52 53

	readonly minimumWidth: number = 0;
	readonly maximumWidth: number = Number.POSITIVE_INFINITY;
	get minimumHeight(): number { return isMacintosh ? 22 / getZoomFactor() : (30 / (this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'hidden' ? getZoomFactor() : 1)); }
	get maximumHeight(): number { return isMacintosh ? 22 / getZoomFactor() : (30 / (this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'hidden' ? getZoomFactor() : 1)); }

54
	private _onDidChange = this._register(new Emitter<{ width: number, height: number }>());
B
Benjamin Pasero 已提交
55 56
	get onDidChange(): Event<{ width: number, height: number }> { return this._onDidChange.event; }

57 58 59
	private _onMenubarVisibilityChange = this._register(new Emitter<boolean>());
	get onMenubarVisibilityChange(): Event<boolean> { return this._onMenubarVisibilityChange.event; }

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

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

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

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

77
	private properties: ITitleProperties;
78 79
	private activeEditorListeners: IDisposable[];

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

82 83
	constructor(
		id: string,
84 85 86 87 88 89 90 91
		@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,
		@IEnvironmentService private readonly environmentService: IEnvironmentService,
		@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
		@IInstantiationService private readonly instantiationService: IInstantiationService,
I
isidor 已提交
92
		@IThemeService themeService: IThemeService,
93
		@ILabelService private readonly labelService: ILabelService,
94
		@IStorageService storageService: IStorageService
95
	) {
96
		super(id, { hasTitle: false }, themeService, storageService);
97

98
		this.properties = { isPure: true, isAdmin: false };
99 100
		this.activeEditorListeners = [];

101 102 103 104
		this.registerListeners();
	}

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

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

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

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

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

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

			this.adjustTitleMarginToCenter();
146 147

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

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

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

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

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

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

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

	private updateRepresentedFilename(): void {
M
Matt Bierner 已提交
182
		const file = toResource(this.editorService.activeEditor || null, { supportSideBySide: true, filter: 'file' });
B
Benjamin Pasero 已提交
183 184 185 186 187 188 189
		const path = file ? file.fsPath : '';

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

		// Keep for context menu
		this.representedFileName = 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 ((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
M
Matt Bierner 已提交
285
		const folder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER ? workspace.folders[0] : this.contextService.getWorkspaceFolder(toResource(editor || null, { supportSideBySide: true })!);
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
		this.dragRegion = append(this.element, $('div.titlebar-drag-region'));
325

B
Benjamin Pasero 已提交
326
		// App Icon (Windows/Linux)
R
Ryan Adolf 已提交
327
		if (!isMacintosh) {
328
			this.appIcon = append(this.element, $('div.window-appicon'));
S
SteVen Batten 已提交
329 330 331 332 333
			this.onUpdateAppIconDragBehavior();

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

S
SteVen Batten 已提交
336
		// Menubar: the menubar part which is responsible for populating both the custom and native menubars
337
		this.menubarPart = this.instantiationService.createInstance(MenubarControl);
338
		this.menubar = append(this.element, $('div.menubar'));
339
		this.menubar.setAttribute('role', 'menubar');
S
SteVen Batten 已提交
340

341
		this.menubarPart.create(this.menubar);
S
SteVen Batten 已提交
342 343 344

		if (!isMacintosh) {
			this._register(this.menubarPart.onVisibilityChange(e => this.onMenubarVisibilityChanged(e)));
S
SteVen Batten 已提交
345
			this._register(this.menubarPart.onFocusStateChange(e => this.onMenubarFocusChanged(e)));
S
SteVen Batten 已提交
346 347
		}

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

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

S
SteVen Batten 已提交
361
				this.onTitleDoubleclick();
362
			}));
S
SteVen Batten 已提交
363
		}
364

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

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

B
Benjamin Pasero 已提交
376
		// Window Controls (Windows/Linux)
R
Ryan Adolf 已提交
377
		if (!isMacintosh) {
378
			this.windowControls = append(this.element, $('div.window-controls-container'));
379

S
SteVen Batten 已提交
380

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

B
Benjamin Pasero 已提交
389
			// Restore
390 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');
			this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, e => {
S
SteVen Batten 已提交
394 395 396 397
				this.windowService.isMaximized().then((maximized) => {
					if (maximized) {
						return this.windowService.unmaximizeWindow();
					}
B
Benjamin Pasero 已提交
398 399

					return this.windowService.maximizeWindow();
400 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

B
Benjamin Pasero 已提交
415
			const isMaximized = this.windowService.getConfiguration().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
	}

B
Benjamin Pasero 已提交
462 463 464 465
	protected updateStyles(): void {
		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 496
	private onUpdateAppIconDragBehavior() {
		const setting = this.configurationService.getValue('window.doubleClickIconToClose');
		if (setting) {
			this.appIcon.style['-webkit-app-region'] = 'no-drag';
497
		} else {
S
SteVen Batten 已提交
498 499 500 501
			this.appIcon.style['-webkit-app-region'] = 'drag';
		}
	}

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

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

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

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

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

		return actions;
	}

S
SteVen Batten 已提交
548
	private adjustTitleMarginToCenter(): void {
S
SteVen Batten 已提交
549
		if (!isMacintosh &&
550 551
			(this.appIcon.clientWidth + this.menubar.clientWidth + 10 > (this.element.clientWidth - this.title.clientWidth) / 2 ||
				this.element.clientWidth - this.windowControls.clientWidth - 10 < (this.element.clientWidth + this.title.clientWidth) / 2)) {
S
SteVen Batten 已提交
552 553 554 555 556 557 558 559
			this.title.style.position = null;
			this.title.style.left = null;
			this.title.style.transform = null;
		} else {
			this.title.style.position = 'absolute';
			this.title.style.left = '50%';
			this.title.style.transform = 'translate(-50%, 0)';
		}
S
SteVen Batten 已提交
560 561
	}

562
	updateLayout(dimension: Dimension): void {
S
SteVen Batten 已提交
563 564
		this.lastLayoutDimensions = dimension;

B
Benjamin Pasero 已提交
565
		if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
566 567
			// Only prevent zooming behavior on macOS or when the menubar is not visible
			if (isMacintosh || this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'hidden') {
568
				this.title.style.zoom = `${1 / getZoomFactor()}`;
569
				if (isWindows || isLinux) {
570 571
					this.appIcon.style.zoom = `${1 / getZoomFactor()}`;
					this.windowControls.style.zoom = `${1 / getZoomFactor()}`;
S
SteVen Batten 已提交
572
				}
573 574 575 576 577
			} else {
				this.title.style.zoom = null;
				if (isWindows || isLinux) {
					this.appIcon.style.zoom = null;
					this.windowControls.style.zoom = null;
S
SteVen Batten 已提交
578
				}
579 580
			}

S
SteVen Batten 已提交
581
			runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter());
582

583
			if (this.menubarPart) {
M
Matt Bierner 已提交
584
				const menubarDimension = new Dimension(0, dimension.height);
585 586
				this.menubarPart.layout(menubarDimension);
			}
587
		}
588 589
	}

B
Benjamin Pasero 已提交
590 591
	layout(width: number, height: number): void {
		this.updateLayout(new Dimension(width, height));
592

B
Benjamin Pasero 已提交
593
		super.layoutContents(width, height);
594
	}
B
Benjamin Pasero 已提交
595

596 597 598 599
	toJSON(): object {
		return {
			type: Parts.TITLEBAR_PART
		};
B
Benjamin Pasero 已提交
600
	}
B
Benjamin Pasero 已提交
601 602 603 604
}

class ShowItemInFolderAction extends Action {

605 606
	constructor(private path: string, label: string, private windowsService: IWindowsService) {
		super('showItemInFolder.action.id', label);
B
Benjamin Pasero 已提交
607 608
	}

J
Johannes Rieken 已提交
609
	run(): Promise<void> {
M
Martin Aeschlimann 已提交
610
		return this.windowsService.showItemInFolder(URI.file(this.path));
B
Benjamin Pasero 已提交
611
	}
R
Ryan Adolf 已提交
612
}
S
SteVen Batten 已提交
613 614 615 616 617

registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
	const titlebarActiveFg = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND);
	if (titlebarActiveFg) {
		collector.addRule(`
618
		.monaco-workbench .part.titlebar > .window-controls-container .window-icon {
S
SteVen Batten 已提交
619 620 621 622 623 624 625 626
			background-color: ${titlebarActiveFg};
		}
		`);
	}

	const titlebarInactiveFg = theme.getColor(TITLE_BAR_INACTIVE_FOREGROUND);
	if (titlebarInactiveFg) {
		collector.addRule(`
627
		.monaco-workbench .part.titlebar.inactive > .window-controls-container .window-icon {
S
SteVen Batten 已提交
628 629 630 631 632
				background-color: ${titlebarInactiveFg};
			}
		`);
	}
});