titlebarPart.ts 22.4 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';
B
Benjamin Pasero 已提交
7
import { dirname, posix } from 'vs/base/common/paths.node';
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';
S
SteVen Batten 已提交
31
import { IInstantiationService } 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 37
import { ISerializableView } from 'vs/base/browser/ui/grid/grid';
import { Parts } from 'vs/workbench/services/part/common/partService';
B
Benjamin Pasero 已提交
38

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

B
Benjamin Pasero 已提交
41
	_serviceBrand: any;
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
	element: HTMLElement;
50 51 52 53 54
	private title: HTMLElement;
	private dragRegion: HTMLElement;
	private windowControls: HTMLElement;
	private maxRestoreControl: HTMLElement;
	private appIcon: HTMLElement;
55
	private menubarPart: MenubarControl;
56 57
	private menubar: HTMLElement;
	private resizer: HTMLElement;
B
Benjamin Pasero 已提交
58

B
Benjamin Pasero 已提交
59
	private pendingTitle: string;
B
Benjamin Pasero 已提交
60
	private representedFileName: string;
61

B
Benjamin Pasero 已提交
62 63
	private isInactive: boolean;

64
	private properties: ITitleProperties;
65 66
	private activeEditorListeners: IDisposable[];

67 68
	minimumWidth: number = 0;
	maximumWidth: number = Number.POSITIVE_INFINITY;
69 70
	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)); }
71 72 73 74

	private _onDidChange = new Emitter<{ width: number; height: number; }>();
	readonly onDidChange = this._onDidChange.event;

75 76
	constructor(
		id: string,
77 78 79 80 81 82 83 84
		@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 已提交
85
		@IThemeService themeService: IThemeService,
86
		@ILabelService private readonly labelService: ILabelService,
87
		@IStorageService storageService: IStorageService
88
	) {
89
		super(id, { hasTitle: false }, themeService, storageService);
90

91
		this.properties = { isPure: true, isAdmin: false };
92 93
		this.activeEditorListeners = [];

94 95 96 97
		this.registerListeners();
	}

	private registerListeners(): void {
98
		this._register(this.windowService.onDidChangeFocus(focused => focused ? this.onFocus() : this.onBlur()));
B
Benjamin Pasero 已提交
99 100
		this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChanged(e)));
		this._register(this.editorService.onDidActiveEditorChange(() => this.onActiveEditorChange()));
B
Benjamin Pasero 已提交
101 102 103
		this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.doUpdateTitle()));
		this._register(this.contextService.onDidChangeWorkbenchState(() => this.doUpdateTitle()));
		this._register(this.contextService.onDidChangeWorkspaceName(() => this.doUpdateTitle()));
104
		this._register(this.labelService.onDidChangeFormatters(() => this.doUpdateTitle()));
105 106
	}

B
Benjamin Pasero 已提交
107 108 109 110 111 112 113 114 115 116
	private onBlur(): void {
		this.isInactive = true;
		this.updateStyles();
	}

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

117 118
	private onConfigurationChanged(event: IConfigurationChangeEvent): void {
		if (event.affectsConfiguration('window.title')) {
B
Benjamin Pasero 已提交
119
			this.doUpdateTitle();
120
		}
S
SteVen Batten 已提交
121 122 123 124 125 126

		if (event.affectsConfiguration('window.doubleClickIconToClose')) {
			if (this.appIcon) {
				this.onUpdateAppIconDragBehavior();
			}
		}
127 128
	}

S
SteVen Batten 已提交
129 130 131 132 133
	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
134 135
				hide(this.dragRegion);
				setTimeout(() => show(this.dragRegion), 50);
S
SteVen Batten 已提交
136
			}
S
SteVen Batten 已提交
137 138

			this.adjustTitleMarginToCenter();
S
SteVen Batten 已提交
139
		}
140 141
	}

S
SteVen Batten 已提交
142 143 144 145 146 147 148 149 150 151
	private onMenubarFocusChanged(focused: boolean) {
		if (isWindows || isLinux) {
			if (focused) {
				hide(this.dragRegion);
			} else {
				show(this.dragRegion);
			}
		}
	}

152 153 154 155
	onMenubarVisibilityChange(): Event<boolean> {
		return this.menubarPart.onVisibilityChange;
	}

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

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

		// Calculate New Window Title
B
Benjamin Pasero 已提交
163
		this.doUpdateTitle();
164 165

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

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

	private updateRepresentedFilename(): void {
		const file = toResource(this.editorService.activeEditor, { supportSideBySide: true, filter: 'file' });
		const path = file ? file.fsPath : '';

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

		// Keep for context menu
		this.representedFileName = path;
185 186
	}

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

204
		if ((isWindows || isLinux) && this.title) {
205 206
			this.adjustTitleMarginToCenter();
		}
B
Benjamin Pasero 已提交
207 208
	}

209 210 211
	private getWindowTitle(): string {
		let title = this.doGetWindowTitle();

212
		if (this.properties.isAdmin) {
B
Benjamin Pasero 已提交
213
			title = `${title || this.environmentService.appNameLong} ${TitlebarPart.NLS_USER_IS_ADMIN}`;
214 215 216
		}

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

		if (this.environmentService.isExtensionDevelopment) {
B
Benjamin Pasero 已提交
221
			title = `${TitlebarPart.NLS_EXTENSION_HOST} - ${title || this.environmentService.appNameLong}`;
222 223 224 225 226
		}

		return title;
	}

B
Benjamin Pasero 已提交
227
	updateProperties(properties: ITitleProperties): void {
228 229 230 231 232 233 234
		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;

B
Benjamin Pasero 已提交
235
			this.doUpdateTitle();
236 237 238
		}
	}

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

B
Benjamin Pasero 已提交
260
		// Compute root
261
		let root: URI;
262
		if (workspace.configuration) {
263
			root = workspace.configuration;
264 265
		} else if (workspace.folders.length) {
			root = workspace.folders[0].uri;
266 267
		}

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

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

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

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

B
Benjamin Pasero 已提交
313
	createContentArea(parent: HTMLElement): HTMLElement {
314
		this.element = parent;
B
Benjamin Pasero 已提交
315

316
		// Draggable region that we can manipulate for #52522
317
		this.dragRegion = append(this.element, $('div.titlebar-drag-region'));
318

B
Benjamin Pasero 已提交
319
		// App Icon (Windows/Linux)
R
Ryan Adolf 已提交
320
		if (!isMacintosh) {
321
			this.appIcon = append(this.element, $('div.window-appicon'));
S
SteVen Batten 已提交
322 323 324 325 326
			this.onUpdateAppIconDragBehavior();

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

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

334
		this.menubarPart.create(this.menubar);
S
SteVen Batten 已提交
335 336 337

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

B
Benjamin Pasero 已提交
341
		// Title
342
		this.title = append(this.element, $('div.window-title'));
B
Benjamin Pasero 已提交
343
		if (this.pendingTitle) {
344
			this.title.innerText = this.pendingTitle;
B
Benjamin Pasero 已提交
345
		} else {
B
Benjamin Pasero 已提交
346
			this.doUpdateTitle();
B
Benjamin Pasero 已提交
347 348
		}

349
		// Maximize/Restore on doubleclick
S
SteVen Batten 已提交
350
		if (isMacintosh) {
351
			this._register(addDisposableListener(this.element, EventType.DBLCLICK, e => {
S
SteVen Batten 已提交
352
				EventHelper.stop(e);
353

S
SteVen Batten 已提交
354
				this.onTitleDoubleclick();
355
			}));
S
SteVen Batten 已提交
356
		}
357

B
Benjamin Pasero 已提交
358
		// Context menu on title
359 360 361 362
		[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 已提交
363

364 365 366
					this.onContextMenu(e);
				}
			}));
B
Benjamin Pasero 已提交
367 368
		});

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

S
SteVen Batten 已提交
373

B
Benjamin Pasero 已提交
374
			// Minimize
375 376 377 378 379 380
			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 已提交
381

B
Benjamin Pasero 已提交
382
			// Restore
383 384 385 386
			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 已提交
387 388 389 390
				this.windowService.isMaximized().then((maximized) => {
					if (maximized) {
						return this.windowService.unmaximizeWindow();
					}
B
Benjamin Pasero 已提交
391 392

					return this.windowService.maximizeWindow();
393 394
				});
			}));
R
Ryan Adolf 已提交
395

B
Benjamin Pasero 已提交
396
			// Close
397 398 399 400 401 402 403
			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();
			}));
404

S
SteVen Batten 已提交
405
			// Resizer
406
			this.resizer = append(this.element, $('div.resizer'));
S
SteVen Batten 已提交
407

B
Benjamin Pasero 已提交
408
			const isMaximized = this.windowService.getConfiguration().maximized ? true : false;
409
			this.onDidChangeMaximized(isMaximized);
410
			this.windowService.onDidChangeMaximize(this.onDidChangeMaximized, this);
411
		}
R
Ryan Adolf 已提交
412

413 414
		// 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.
415
		this._register(addDisposableListener(this.element, EventType.MOUSE_DOWN, e => {
416
			if (e.target && isAncestor(e.target as HTMLElement, this.menubar)) {
S
SteVen Batten 已提交
417 418 419
				return;
			}

420 421 422 423 424 425
			const active = document.activeElement;
			setTimeout(() => {
				if (active instanceof HTMLElement) {
					active.focus();
				}
			}, 0 /* need a timeout because we are in capture phase */);
426
		}, true /* use capture to know the currently active element properly */));
427

S
SteVen Batten 已提交
428 429
		this.updateStyles();

430
		return this.element;
B
Benjamin Pasero 已提交
431 432
	}

433
	private onDidChangeMaximized(maximized: boolean) {
S
SteVen Batten 已提交
434 435
		if (this.maxRestoreControl) {
			if (maximized) {
436 437
				removeClass(this.maxRestoreControl, 'window-maximize');
				addClass(this.maxRestoreControl, 'window-unmaximize');
S
SteVen Batten 已提交
438
			} else {
439 440
				removeClass(this.maxRestoreControl, 'window-unmaximize');
				addClass(this.maxRestoreControl, 'window-maximize');
S
SteVen Batten 已提交
441
			}
S
SteVen Batten 已提交
442
		}
S
SteVen Batten 已提交
443

S
SteVen Batten 已提交
444 445
		if (this.resizer) {
			if (maximized) {
446
				hide(this.resizer);
S
SteVen Batten 已提交
447
			} else {
448
				show(this.resizer);
S
SteVen Batten 已提交
449
			}
S
SteVen Batten 已提交
450
		}
S
SteVen Batten 已提交
451 452

		this.adjustTitleMarginToCenter();
453 454
	}

B
Benjamin Pasero 已提交
455 456 457 458
	protected updateStyles(): void {
		super.updateStyles();

		// Part container
459
		if (this.element) {
S
SteVen Batten 已提交
460
			if (this.isInactive) {
461
				addClass(this.element, 'inactive');
S
SteVen Batten 已提交
462
			} else {
463
				removeClass(this.element, 'inactive');
S
SteVen Batten 已提交
464 465
			}

B
Benjamin Pasero 已提交
466
			const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND);
467
			this.element.style.backgroundColor = titleBackground;
B
Benjamin Pasero 已提交
468
			if (Color.fromHex(titleBackground).isLighter()) {
469
				addClass(this.element, 'light');
S
SteVen Batten 已提交
470
			} else {
471
				removeClass(this.element, 'light');
S
SteVen Batten 已提交
472
			}
473

B
Benjamin Pasero 已提交
474
			const titleForeground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_FOREGROUND : TITLE_BAR_ACTIVE_FOREGROUND);
475
			this.element.style.color = titleForeground;
B
Benjamin Pasero 已提交
476

477
			const titleBorder = this.getColor(TITLE_BAR_BORDER);
478
			this.element.style.borderBottom = titleBorder ? `1px solid ${titleBorder}` : null;
479
		}
B
Benjamin Pasero 已提交
480 481
	}

482
	private onTitleDoubleclick(): void {
483
		this.windowService.onWindowTitleDoubleClick();
484 485
	}

S
SteVen Batten 已提交
486 487 488 489 490 491 492 493 494 495
	private onUpdateAppIconDragBehavior() {
		const setting = this.configurationService.getValue('window.doubleClickIconToClose');
		if (setting) {
			this.appIcon.style['-webkit-app-region'] = 'no-drag';
		}
		else {
			this.appIcon.style['-webkit-app-region'] = 'drag';
		}
	}

B
Benjamin Pasero 已提交
496 497 498 499 500 501 502 503 504 505 506
	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,
507
				getActions: () => actions,
B
Benjamin Pasero 已提交
508 509 510 511 512 513 514 515 516
				onHide: () => actions.forEach(a => a.dispose())
			});
		}
	}

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

		if (this.representedFileName) {
B
Benjamin Pasero 已提交
517
			const segments = this.representedFileName.split(posix.sep);
B
Benjamin Pasero 已提交
518
			for (let i = segments.length; i > 0; i--) {
519 520 521 522 523 524 525
				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 已提交
526
				const path = segments.slice(0, pathOffset).join(posix.sep);
527

B
Benjamin Pasero 已提交
528
				let label: string;
529
				if (!isFile) {
B
Benjamin Pasero 已提交
530
					label = getBaseLabel(dirname(path));
B
Benjamin Pasero 已提交
531
				} else {
I
isidor 已提交
532
					label = getBaseLabel(path);
533 534
				}

B
Benjamin Pasero 已提交
535
				actions.push(new ShowItemInFolderAction(path, label || posix.sep, this.windowsService));
B
Benjamin Pasero 已提交
536 537 538 539 540 541
			}
		}

		return actions;
	}

S
SteVen Batten 已提交
542
	private adjustTitleMarginToCenter(): void {
S
SteVen Batten 已提交
543
		if (!isMacintosh &&
544 545
			(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 已提交
546 547 548 549 550 551 552 553
			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 已提交
554 555
	}

556
	updateLayout(dimension: Dimension): void {
B
Benjamin Pasero 已提交
557
		if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
558 559
			// Only prevent zooming behavior on macOS or when the menubar is not visible
			if (isMacintosh || this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'hidden') {
560
				this.title.style.zoom = `${1 / getZoomFactor()}`;
561
				if (isWindows || isLinux) {
562 563
					this.appIcon.style.zoom = `${1 / getZoomFactor()}`;
					this.windowControls.style.zoom = `${1 / getZoomFactor()}`;
S
SteVen Batten 已提交
564
				}
565 566 567 568 569
			} else {
				this.title.style.zoom = null;
				if (isWindows || isLinux) {
					this.appIcon.style.zoom = null;
					this.windowControls.style.zoom = null;
S
SteVen Batten 已提交
570
				}
571 572
			}

S
SteVen Batten 已提交
573
			runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter());
574

575 576 577 578
			if (this.menubarPart) {
				const menubarDimension = new Dimension(undefined, dimension.height);
				this.menubarPart.layout(menubarDimension);
			}
579
		}
580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
	}

	layout(dimension: Dimension): Dimension[];
	layout(width: number, height: number): void;
	layout(dim1: Dimension | number, dim2?: number): Dimension[] | void {
		if (dim1 instanceof Dimension) {
			this.updateLayout(dim1);

			return super.layout(dim1);
		}

		const dimensions = new Dimension(dim1, dim2);
		this.updateLayout(dimensions);

		super.layout(dimensions);
	}
B
Benjamin Pasero 已提交
596

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

class ShowItemInFolderAction extends Action {

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

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

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

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