layout.ts 36.6 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6 7 8 9 10 11 12
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, position, size } from 'vs/base/browser/dom';
import { onDidChangeFullscreen, isFullscreen, getZoomFactor } from 'vs/base/browser/browser';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { Registry } from 'vs/platform/registry/common/platform';
import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform';
B
Benjamin Pasero 已提交
13
import { pathsToEditors } from 'vs/workbench/common/editor';
14 15
import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart';
import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart';
16 17 18 19 20 21 22 23 24 25
import { PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel';
import { Position, Parts, IWorkbenchLayoutService, ILayoutOptions } from 'vs/workbench/services/layout/browser/layoutService';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason } from 'vs/platform/storage/common/storage';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { ITitleService } from 'vs/workbench/services/title/common/titleService';
import { IInstantiationService, ServicesAccessor, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { LifecyclePhase, StartupKind, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
26
import { IWindowService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows';
27
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
28 29 30 31 32 33 34 35
import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { Sizing, Direction, Grid, View } from 'vs/base/browser/ui/grid/grid';
import { WorkbenchLegacyLayout } from 'vs/workbench/browser/legacyLayout';
import { IDimension } from 'vs/platform/layout/browser/layoutService';
import { Part } from 'vs/workbench/browser/part';
import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService';
36
import { IFileService } from 'vs/platform/files/common/files';
37 38 39 40 41 42 43 44 45 46 47

enum Settings {
	MENUBAR_VISIBLE = 'window.menuBarVisibility',
	ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible',
	STATUSBAR_VISIBLE = 'workbench.statusBar.visible',

	SIDEBAR_POSITION = 'workbench.sideBar.location',
	PANEL_POSITION = 'workbench.panel.defaultLocation',

	ZEN_MODE_RESTORE = 'zenMode.restore'
}
E
Erich Gamma 已提交
48

49 50 51 52 53 54 55 56 57 58
enum Storage {
	SIDEBAR_HIDDEN = 'workbench.sidebar.hidden',

	PANEL_HIDDEN = 'workbench.panel.hidden',
	PANEL_POSITION = 'workbench.panel.location',

	ZEN_MODE_ENABLED = 'workbench.zenmode.active',
	CENTERED_LAYOUT_ENABLED = 'workbench.centerededitorlayout.active',
}

B
Benjamin Pasero 已提交
59
export abstract class Layout extends Disposable implements IWorkbenchLayoutService {
60 61 62 63 64 65 66 67 68

	_serviceBrand: ServiceIdentifier<any>;

	private readonly _onTitleBarVisibilityChange: Emitter<void> = this._register(new Emitter<void>());
	get onTitleBarVisibilityChange(): Event<void> { return this._onTitleBarVisibilityChange.event; }

	private readonly _onZenMode: Emitter<boolean> = this._register(new Emitter<boolean>());
	get onZenModeChange(): Event<boolean> { return this._onZenMode.event; }

S
SteVen Batten 已提交
69 70 71 72 73 74
	private readonly _onFullscreen: Emitter<boolean> = this._register(new Emitter<boolean>());
	get onFullscreenChange(): Event<boolean> { return this._onFullscreen.event; }

	private readonly _onCenteredLayout: Emitter<boolean> = this._register(new Emitter<boolean>());
	get onCenteredLayoutChange(): Event<boolean> { return this._onCenteredLayout.event; }

75 76 77
	private readonly _onPanelPositionChange: Emitter<string> = this._register(new Emitter<string>());
	get onPanelPositionChange(): Event<string> { return this._onPanelPositionChange.event; }

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
	private readonly _onLayout = this._register(new Emitter<IDimension>());
	get onLayout(): Event<IDimension> { return this._onLayout.event; }

	private _dimension: IDimension;
	get dimension(): IDimension { return this._dimension; }

	private _container: HTMLElement = document.createElement('div');
	get container(): HTMLElement { return this._container; }

	private parts: Map<string, Part> = new Map<string, Part>();

	private workbenchGrid: Grid<View> | WorkbenchLegacyLayout;

	private disposed: boolean;

	private titleBarPartView: View;
	private activityBarPartView: View;
	private sideBarPartView: View;
	private panelPartView: View;
	private editorPartView: View;
	private statusBarPartView: View;

100
	private environmentService: IWorkbenchEnvironmentService;
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
	private configurationService: IConfigurationService;
	private lifecycleService: ILifecycleService;
	private storageService: IStorageService;
	private windowService: IWindowService;
	private editorService: IEditorService;
	private editorGroupService: IEditorGroupsService;
	private panelService: IPanelService;
	private titleService: ITitleService;
	private viewletService: IViewletService;
	private contextService: IWorkspaceContextService;
	private backupFileService: IBackupFileService;

	protected readonly state = {
		fullscreen: false,

		menuBar: {
			visibility: 'default' as MenuBarVisibility,
			toggled: false
		},

		activityBar: {
			hidden: false
I
isidor 已提交
123
		},
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164

		sideBar: {
			hidden: false,
			position: Position.LEFT,
			width: 300,
			viewletToRestore: undefined as string | undefined
		},

		editor: {
			hidden: false,
			centered: false,
			restoreCentered: false,
			restoreEditors: false,
			editorsToOpen: [] as Promise<IResourceEditor[]> | IResourceEditor[]
		},

		panel: {
			hidden: false,
			position: Position.BOTTOM,
			height: 350,
			width: 350,
			panelToRestore: undefined as string | undefined
		},

		statusBar: {
			hidden: false
		},

		zenMode: {
			active: false,
			restore: false,
			transitionedToFullScreen: false,
			transitionedToCenteredEditorLayout: false,
			wasSideBarVisible: false,
			wasPanelVisible: false,
			transitionDisposeables: [] as IDisposable[]
		}
	};

	constructor(
		protected readonly parent: HTMLElement
E
Erich Gamma 已提交
165
	) {
166
		super();
167
	}
168

169 170 171
	protected initLayout(accessor: ServicesAccessor): void {

		// Services
172
		this.environmentService = accessor.get(IWorkbenchEnvironmentService);
173 174 175 176 177
		this.configurationService = accessor.get(IConfigurationService);
		this.lifecycleService = accessor.get(ILifecycleService);
		this.windowService = accessor.get(IWindowService);
		this.contextService = accessor.get(IWorkspaceContextService);
		this.storageService = accessor.get(IStorageService);
B
Benjamin Pasero 已提交
178
		this.backupFileService = accessor.get(IBackupFileService);
179 180 181 182 183 184 185 186 187 188 189 190 191 192

		// Parts
		this.editorService = accessor.get(IEditorService);
		this.editorGroupService = accessor.get(IEditorGroupsService);
		this.panelService = accessor.get(IPanelService);
		this.viewletService = accessor.get(IViewletService);
		this.titleService = accessor.get(ITitleService);
		accessor.get(IStatusbarService); // not used, but called to ensure instantiated
		accessor.get(IActivityBarService); // not used, but called to ensure instantiated

		// Listeners
		this.registerLayoutListeners();

		// State
193
		this.initLayoutState(accessor.get(ILifecycleService), accessor.get(IFileService));
194
	}
195

196
	private registerLayoutListeners(): void {
197

198 199
		// Storage
		this._register(this.storageService.onWillSaveState(e => this.saveLayoutState(e)));
E
Erich Gamma 已提交
200

201 202 203
		// Restore editor if hidden and it changes
		this._register(this.editorService.onDidVisibleEditorsChange(() => this.setEditorHidden(false)));
		this._register(this.editorGroupService.onDidActivateGroup(() => this.setEditorHidden(false)));
204

205 206
		// Configuration changes
		this._register(this.configurationService.onDidChangeConfiguration(() => this.doUpdateLayoutConfiguration()));
E
Erich Gamma 已提交
207

208 209 210 211 212 213
		// Fullscreen changes
		this._register(onDidChangeFullscreen(() => this.onFullscreenChanged()));

		// Group changes
		this._register(this.editorGroupService.onDidAddGroup(() => this.centerEditorLayout(this.state.editor.centered)));
		this._register(this.editorGroupService.onDidRemoveGroup(() => this.centerEditorLayout(this.state.editor.centered)));
214

215 216
		// Prevent workbench from scrolling #55456
		this._register(addDisposableListener(this.container, EventType.SCROLL, () => this.container.scrollTop = 0));
M
Martin Aeschlimann 已提交
217

218 219 220 221
		// Menubar visibility changes
		if ((isWindows || isLinux) && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
			this._register(this.titleService.onMenubarVisibilityChange(visible => this.onMenubarToggled(visible)));
		}
E
Erich Gamma 已提交
222 223
	}

224 225 226
	private onMenubarToggled(visible: boolean) {
		if (visible !== this.state.menuBar.toggled) {
			this.state.menuBar.toggled = visible;
227

228 229 230 231 232 233
			if (this.state.fullscreen && (this.state.menuBar.visibility === 'toggle' || this.state.menuBar.visibility === 'default')) {
				this._onTitleBarVisibilityChange.fire();
				this.layout();
			}
		}
	}
234

235 236
	private onFullscreenChanged(): void {
		this.state.fullscreen = isFullscreen();
237

238 239 240 241 242 243 244 245
		// Apply as CSS class
		if (this.state.fullscreen) {
			addClass(this.container, 'fullscreen');
		} else {
			removeClass(this.container, 'fullscreen');

			if (this.state.zenMode.transitionedToFullScreen && this.state.zenMode.active) {
				this.toggleZenMode();
246 247
			}
		}
248

S
SteVen Batten 已提交
249 250
		this._onFullscreen.fire(this.state.fullscreen);

251 252 253 254 255
		// Changing fullscreen state of the window has an impact on custom title bar visibility, so we need to update
		if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
			this._onTitleBarVisibilityChange.fire();
			this.layout(); // handle title bar when fullscreen changes
		}
256 257
	}

258 259 260 261 262 263 264
	private doUpdateLayoutConfiguration(skipLayout?: boolean): void {

		// Sidebar position
		const newSidebarPositionValue = this.configurationService.getValue<string>(Settings.SIDEBAR_POSITION);
		const newSidebarPosition = (newSidebarPositionValue === 'right') ? Position.RIGHT : Position.LEFT;
		if (newSidebarPosition !== this.getSideBarPosition()) {
			this.setSideBarPosition(newSidebarPosition);
I
isidor 已提交
265 266
		}

267 268 269 270 271 272 273 274 275 276
		// Panel position
		this.updatePanelPosition();

		if (!this.state.zenMode.active) {

			// Statusbar visibility
			const newStatusbarHiddenValue = !this.configurationService.getValue<boolean>(Settings.STATUSBAR_VISIBLE);
			if (newStatusbarHiddenValue !== this.state.statusBar.hidden) {
				this.setStatusBarHidden(newStatusbarHiddenValue, skipLayout);
			}
I
isidor 已提交
277

278 279 280 281 282
			// Activitybar visibility
			const newActivityBarHiddenValue = !this.configurationService.getValue<boolean>(Settings.ACTIVITYBAR_VISIBLE);
			if (newActivityBarHiddenValue !== this.state.activityBar.hidden) {
				this.setActivityBarHidden(newActivityBarHiddenValue, skipLayout);
			}
283 284
		}

285 286 287
		// Menubar visibility
		const newMenubarVisibility = this.configurationService.getValue<MenuBarVisibility>(Settings.MENUBAR_VISIBLE);
		this.setMenubarVisibility(newMenubarVisibility, !!skipLayout);
288 289
	}

290 291 292 293
	private setSideBarPosition(position: Position): void {
		const activityBar = this.getPart(Parts.ACTIVITYBAR_PART);
		const sideBar = this.getPart(Parts.SIDEBAR_PART);
		const wasHidden = this.state.sideBar.hidden;
294

295 296
		if (this.state.sideBar.hidden) {
			this.setSideBarHidden(false, true /* Skip Layout */);
297 298
		}

299 300 301 302 303 304 305 306 307
		const newPositionValue = (position === Position.LEFT) ? 'left' : 'right';
		const oldPositionValue = (this.state.sideBar.position === Position.LEFT) ? 'left' : 'right';
		this.state.sideBar.position = position;

		// Adjust CSS
		removeClass(activityBar.getContainer(), oldPositionValue);
		removeClass(sideBar.getContainer(), oldPositionValue);
		addClass(activityBar.getContainer(), newPositionValue);
		addClass(sideBar.getContainer(), newPositionValue);
308

309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
		// Update Styles
		activityBar.updateStyles();
		sideBar.updateStyles();

		// Layout
		if (this.workbenchGrid instanceof Grid) {
			if (!wasHidden) {
				this.state.sideBar.width = this.workbenchGrid.getViewSize(this.sideBarPartView);
			}

			this.workbenchGrid.removeView(this.sideBarPartView);
			this.workbenchGrid.removeView(this.activityBarPartView);

			if (!this.state.panel.hidden && this.state.panel.position === Position.BOTTOM) {
				this.workbenchGrid.removeView(this.panelPartView);
			}

			this.layout();
		} else {
			this.workbenchGrid.layout();
		}
330 331
	}

332
	private initLayoutState(lifecycleService: ILifecycleService, fileService: IFileService): void {
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355

		// Fullscreen
		this.state.fullscreen = isFullscreen();

		// Menubar visibility
		this.state.menuBar.visibility = this.configurationService.getValue<MenuBarVisibility>(Settings.MENUBAR_VISIBLE);

		// Activity bar visibility
		this.state.activityBar.hidden = !this.configurationService.getValue<string>(Settings.ACTIVITYBAR_VISIBLE);

		// Sidebar visibility
		this.state.sideBar.hidden = this.storageService.getBoolean(Storage.SIDEBAR_HIDDEN, StorageScope.WORKSPACE, this.contextService.getWorkbenchState() === WorkbenchState.EMPTY);

		// Sidebar position
		this.state.sideBar.position = (this.configurationService.getValue<string>(Settings.SIDEBAR_POSITION) === 'right') ? Position.RIGHT : Position.LEFT;

		// Sidebar viewlet
		if (!this.state.sideBar.hidden) {

			// Only restore last viewlet if window was reloaded or we are in development mode
			let viewletToRestore: string;
			if (!this.environmentService.isBuilt || lifecycleService.startupKind === StartupKind.ReloadedWindow) {
				viewletToRestore = this.storageService.get(SidebarPart.activeViewletSettingsKey, StorageScope.WORKSPACE, this.viewletService.getDefaultViewletId());
B
Benjamin Pasero 已提交
356
			} else {
357 358 359 360 361 362 363
				viewletToRestore = this.viewletService.getDefaultViewletId();
			}

			if (viewletToRestore) {
				this.state.sideBar.viewletToRestore = viewletToRestore;
			} else {
				this.state.sideBar.hidden = true; // we hide sidebar if there is no viewlet to restore
B
Benjamin Pasero 已提交
364 365 366
			}
		}

367 368 369 370
		// Editor centered layout
		this.state.editor.restoreCentered = this.storageService.getBoolean(Storage.CENTERED_LAYOUT_ENABLED, StorageScope.WORKSPACE, false);

		// Editors to open
371
		this.state.editor.editorsToOpen = this.resolveEditorsToOpen(fileService);
372 373 374 375 376 377

		// Panel visibility
		this.state.panel.hidden = this.storageService.getBoolean(Storage.PANEL_HIDDEN, StorageScope.WORKSPACE, true);

		// Panel position
		this.updatePanelPosition();
378

379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
		// Panel to restore
		if (!this.state.panel.hidden) {
			const panelRegistry = Registry.as<PanelRegistry>(PanelExtensions.Panels);

			let panelToRestore = this.storageService.get(PanelPart.activePanelSettingsKey, StorageScope.WORKSPACE, panelRegistry.getDefaultPanelId());
			if (!panelRegistry.hasPanel(panelToRestore)) {
				panelToRestore = panelRegistry.getDefaultPanelId(); // fallback to default if panel is unknown
			}

			if (panelToRestore) {
				this.state.panel.panelToRestore = panelToRestore;
			} else {
				this.state.panel.hidden = true; // we hide panel if there is no panel to restore
			}
		}

		// Statusbar visibility
		this.state.statusBar.hidden = !this.configurationService.getValue<string>(Settings.STATUSBAR_VISIBLE);

		// Zen mode enablement
		this.state.zenMode.restore = this.storageService.getBoolean(Storage.ZEN_MODE_ENABLED, StorageScope.WORKSPACE, false) && this.configurationService.getValue(Settings.ZEN_MODE_RESTORE);
400 401
	}

402
	private resolveEditorsToOpen(fileService: IFileService): Promise<IResourceEditor[]> | IResourceEditor[] {
403
		const configuration = this.environmentService.configuration;
404 405 406 407 408 409 410 411 412
		const hasInitialFilesToOpen = this.hasInitialFilesToOpen();

		// Only restore editors if we are not instructed to open files initially
		this.state.editor.restoreEditors = !hasInitialFilesToOpen;

		// Files to open, diff or create
		if (hasInitialFilesToOpen) {

			// Files to diff is exclusive
413 414 415 416 417 418 419 420 421
			return pathsToEditors(configuration.filesToDiff, fileService).then(filesToDiff => {
				if (filesToDiff && filesToDiff.length === 2) {
					return [{
						leftResource: filesToDiff[0].resource,
						rightResource: filesToDiff[1].resource,
						options: { pinned: true },
						forceFile: true
					}];
				}
422

423 424 425
				// Otherwise: Open/Create files
				return pathsToEditors(configuration.filesToOpenOrCreate, fileService);
			});
426 427 428 429
		}

		// Empty workbench
		else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && this.configurationService.inspect('workbench.startupEditor').value === 'newUntitledFile') {
B
Benjamin Pasero 已提交
430
			if (this.editorGroupService.willRestoreEditors) {
431 432 433 434 435 436 437 438
				return []; // do not open any empty untitled file if we restored editors from previous session
			}

			return this.backupFileService.hasBackups().then(hasBackups => {
				if (hasBackups) {
					return []; // do not open any empty untitled file if we have backups to restore
				}

B
Benjamin Pasero 已提交
439
				return [Object.create(null)]; // open empty untitled file
440
			});
441 442
		}

443
		return [];
444 445
	}

446
	private hasInitialFilesToOpen(): boolean {
447
		const configuration = this.environmentService.configuration;
448

449
		return !!(
450 451 452
			(configuration.filesToOpenOrCreate && configuration.filesToOpenOrCreate.length > 0) ||
			(configuration.filesToDiff && configuration.filesToDiff.length > 0)
		);
453 454
	}

455 456 457 458 459
	private updatePanelPosition() {
		const defaultPanelPosition = this.configurationService.getValue<string>(Settings.PANEL_POSITION);
		const panelPosition = this.storageService.get(Storage.PANEL_POSITION, StorageScope.WORKSPACE, defaultPanelPosition);

		this.state.panel.position = (panelPosition === 'right') ? Position.RIGHT : Position.BOTTOM;
460 461
	}

462 463 464
	registerPart(part: Part): void {
		this.parts.set(part.getId(), part);
	}
E
Erich Gamma 已提交
465

B
Benjamin Pasero 已提交
466 467 468 469 470
	protected getPart(key: Parts): Part {
		const part = this.parts.get(key);
		if (!part) {
			throw new Error(`Unknown part ${key}`);
		}
E
Erich Gamma 已提交
471

B
Benjamin Pasero 已提交
472 473
		return part;
	}
474

475 476 477
	isRestored(): boolean {
		return this.lifecycleService.phase >= LifecyclePhase.Restored;
	}
I
isidor 已提交
478

479 480 481 482 483
	hasFocus(part: Parts): boolean {
		const activeElement = document.activeElement;
		if (!activeElement) {
			return false;
		}
E
Erich Gamma 已提交
484

485
		const container = this.getContainer(part);
E
Erich Gamma 已提交
486

487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
		return isAncestor(activeElement, container);
	}

	getContainer(part: Parts): HTMLElement {
		switch (part) {
			case Parts.TITLEBAR_PART:
				return this.getPart(Parts.TITLEBAR_PART).getContainer();
			case Parts.ACTIVITYBAR_PART:
				return this.getPart(Parts.ACTIVITYBAR_PART).getContainer();
			case Parts.SIDEBAR_PART:
				return this.getPart(Parts.SIDEBAR_PART).getContainer();
			case Parts.PANEL_PART:
				return this.getPart(Parts.PANEL_PART).getContainer();
			case Parts.EDITOR_PART:
				return this.getPart(Parts.EDITOR_PART).getContainer();
			case Parts.STATUSBAR_PART:
				return this.getPart(Parts.STATUSBAR_PART).getContainer();
		}
	}
E
Erich Gamma 已提交
506

507 508 509 510 511 512 513 514 515 516 517 518 519
	isVisible(part: Parts): boolean {
		switch (part) {
			case Parts.TITLEBAR_PART:
				if (getTitleBarStyle(this.configurationService, this.environmentService) === 'native') {
					return false;
				} else if (!this.state.fullscreen) {
					return true;
				} else if (isMacintosh) {
					return false;
				} else if (this.state.menuBar.visibility === 'visible') {
					return true;
				} else if (this.state.menuBar.visibility === 'toggle' || this.state.menuBar.visibility === 'default') {
					return this.state.menuBar.toggled;
E
Erich Gamma 已提交
520 521
				}

522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
				return false;
			case Parts.SIDEBAR_PART:
				return !this.state.sideBar.hidden;
			case Parts.PANEL_PART:
				return !this.state.panel.hidden;
			case Parts.STATUSBAR_PART:
				return !this.state.statusBar.hidden;
			case Parts.ACTIVITYBAR_PART:
				return !this.state.activityBar.hidden;
			case Parts.EDITOR_PART:
				return this.workbenchGrid instanceof Grid ? !this.state.editor.hidden : true;
		}

		return true; // any other part cannot be hidden
	}

	getTitleBarOffset(): number {
		let offset = 0;
		if (this.isVisible(Parts.TITLEBAR_PART)) {
			if (this.workbenchGrid instanceof Grid) {
				offset = this.getPart(Parts.TITLEBAR_PART).maximumHeight;
			} else {
				offset = this.workbenchGrid.partLayoutInfo.titlebar.height;

				if (isMacintosh || this.state.menuBar.visibility === 'hidden') {
					offset /= getZoomFactor();
E
Erich Gamma 已提交
548 549
				}
			}
550
		}
E
Erich Gamma 已提交
551

552 553 554 555 556 557
		return offset;
	}

	getWorkbenchElement(): HTMLElement {
		return this.container;
	}
E
Erich Gamma 已提交
558

559 560 561
	toggleZenMode(skipLayout?: boolean, restoring = false): void {
		this.state.zenMode.active = !this.state.zenMode.active;
		this.state.zenMode.transitionDisposeables = dispose(this.state.zenMode.transitionDisposeables);
I
isidor 已提交
562

563
		const setLineNumbers = (lineNumbers: any) => this.editorService.visibleTextEditorWidgets.forEach(editor => editor.updateOptions({ lineNumbers }));
564

565 566 567
		// Check if zen mode transitioned to full screen and if now we are out of zen mode
		// -> we need to go out of full screen (same goes for the centered editor layout)
		let toggleFullScreen = false;
568

569 570 571 572 573 574 575 576 577 578
		// Zen Mode Active
		if (this.state.zenMode.active) {
			const config: {
				fullScreen: boolean;
				centerLayout: boolean;
				hideTabs: boolean;
				hideActivityBar: boolean;
				hideStatusBar: boolean;
				hideLineNumbers: boolean;
			} = this.configurationService.getValue('zenMode');
I
isidor 已提交
579

580 581 582 583 584 585 586 587 588 589 590 591
			toggleFullScreen = !this.state.fullscreen && config.fullScreen;

			this.state.zenMode.transitionedToFullScreen = restoring ? config.fullScreen : toggleFullScreen;
			this.state.zenMode.transitionedToCenteredEditorLayout = !this.isEditorLayoutCentered() && config.centerLayout;
			this.state.zenMode.wasSideBarVisible = this.isVisible(Parts.SIDEBAR_PART);
			this.state.zenMode.wasPanelVisible = this.isVisible(Parts.PANEL_PART);

			this.setPanelHidden(true, true);
			this.setSideBarHidden(true, true);

			if (config.hideActivityBar) {
				this.setActivityBarHidden(true, true);
I
isidor 已提交
592
			}
593

594 595
			if (config.hideStatusBar) {
				this.setStatusBarHidden(true, true);
I
isidor 已提交
596 597
			}

598 599 600 601
			if (config.hideLineNumbers) {
				setLineNumbers('off');
				this.state.zenMode.transitionDisposeables.push(this.editorService.onDidVisibleEditorsChange(() => setLineNumbers('off')));
			}
602

603 604 605
			if (config.hideTabs && this.editorGroupService.partOptions.showTabs) {
				this.state.zenMode.transitionDisposeables.push(this.editorGroupService.enforcePartOptions({ showTabs: false }));
			}
606

607 608 609 610
			if (config.centerLayout) {
				this.centerEditorLayout(true, true);
			}
		}
611

612 613 614 615
		// Zen Mode Inactive
		else {
			if (this.state.zenMode.wasPanelVisible) {
				this.setPanelHidden(false, true);
616 617
			}

618 619
			if (this.state.zenMode.wasSideBarVisible) {
				this.setSideBarHidden(false, true);
620 621
			}

622 623
			if (this.state.zenMode.transitionedToCenteredEditorLayout) {
				this.centerEditorLayout(false, true);
624
			}
M
Maxime Quandalle 已提交
625

626
			setLineNumbers(this.configurationService.getValue('editor.lineNumbers'));
627

628 629
			// Status bar and activity bar visibility come from settings -> update their visibility.
			this.doUpdateLayoutConfiguration(true);
630

631
			this.editorGroupService.activeGroup.focus();
M
Maxime Quandalle 已提交
632

633 634
			toggleFullScreen = this.state.zenMode.transitionedToFullScreen && this.state.fullscreen;
		}
635

636
		if (!skipLayout) {
637
			this.layout();
638
		}
M
Maxime Quandalle 已提交
639

640 641 642
		if (toggleFullScreen) {
			this.windowService.toggleFullScreen();
		}
643

644 645 646
		// Event
		this._onZenMode.fire(this.state.zenMode.active);
	}
647

648 649
	private setStatusBarHidden(hidden: boolean, skipLayout?: boolean): void {
		this.state.statusBar.hidden = hidden;
650

651 652 653 654 655 656
		// Adjust CSS
		if (hidden) {
			addClass(this.container, 'nostatusbar');
		} else {
			removeClass(this.container, 'nostatusbar');
		}
E
Erich Gamma 已提交
657

658 659 660 661
		// Layout
		if (!skipLayout) {
			if (this.workbenchGrid instanceof Grid) {
				this.layout();
I
isidor 已提交
662
			} else {
663
				this.workbenchGrid.layout();
I
isidor 已提交
664
			}
665 666
		}
	}
667

668 669 670 671 672 673 674
	protected createWorkbenchLayout(instantiationService: IInstantiationService): void {
		const titleBar = this.getPart(Parts.TITLEBAR_PART);
		const editorPart = this.getPart(Parts.EDITOR_PART);
		const activityBar = this.getPart(Parts.ACTIVITYBAR_PART);
		const panelPart = this.getPart(Parts.PANEL_PART);
		const sideBar = this.getPart(Parts.SIDEBAR_PART);
		const statusBar = this.getPart(Parts.STATUSBAR_PART);
675

676 677 678 679 680 681 682 683 684 685 686 687 688
		if (this.configurationService.getValue('workbench.useExperimentalGridLayout')) {

			// Create view wrappers for all parts
			this.titleBarPartView = new View(titleBar);
			this.sideBarPartView = new View(sideBar);
			this.activityBarPartView = new View(activityBar);
			this.editorPartView = new View(editorPart);
			this.panelPartView = new View(panelPart);
			this.statusBarPartView = new View(statusBar);

			this.workbenchGrid = new Grid(this.editorPartView, { proportionalLayout: false });

			this.container.prepend(this.workbenchGrid.element);
I
isidor 已提交
689
		} else {
690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718
			this.workbenchGrid = instantiationService.createInstance(
				WorkbenchLegacyLayout,
				this.parent,
				this.container,
				{
					titlebar: titleBar,
					activitybar: activityBar,
					editor: editorPart,
					sidebar: sideBar,
					panel: panelPart,
					statusbar: statusBar,
				}
			);
		}
	}

	layout(options?: ILayoutOptions): void {
		if (!this.disposed) {
			this._dimension = getClientArea(this.parent);

			if (this.workbenchGrid instanceof Grid) {
				position(this.container, 0, 0, 0, 0, 'relative');
				size(this.container, this._dimension.width, this._dimension.height);

				// Layout the grid widget
				this.workbenchGrid.layout(this._dimension.width, this._dimension.height);

				// Layout grid views
				this.layoutGrid();
I
isidor 已提交
719
			} else {
720
				this.workbenchGrid.layout(options);
I
isidor 已提交
721
			}
722

723 724 725 726
			// Emit as event
			this._onLayout.fire(this._dimension);
		}
	}
727

728 729 730
	private layoutGrid(): void {
		if (!(this.workbenchGrid instanceof Grid)) {
			return;
I
isidor 已提交
731
		}
732

733 734 735 736 737
		let panelInGrid = this.workbenchGrid.hasView(this.panelPartView);
		let sidebarInGrid = this.workbenchGrid.hasView(this.sideBarPartView);
		let activityBarInGrid = this.workbenchGrid.hasView(this.activityBarPartView);
		let statusBarInGrid = this.workbenchGrid.hasView(this.statusBarPartView);
		let titlebarInGrid = this.workbenchGrid.hasView(this.titleBarPartView);
738

739 740 741 742 743
		// Add parts to grid
		if (!statusBarInGrid) {
			this.workbenchGrid.addView(this.statusBarPartView, Sizing.Split, this.editorPartView, Direction.Down);
			statusBarInGrid = true;
		}
I
isidor 已提交
744

745 746 747 748
		if (!titlebarInGrid && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
			this.workbenchGrid.addView(this.titleBarPartView, Sizing.Split, this.editorPartView, Direction.Up);
			titlebarInGrid = true;
		}
E
Erich Gamma 已提交
749

750 751 752 753
		if (!activityBarInGrid) {
			this.workbenchGrid.addView(this.activityBarPartView, Sizing.Split, panelInGrid && this.state.sideBar.position === this.state.panel.position ? this.panelPartView : this.editorPartView, this.state.sideBar.position === Position.RIGHT ? Direction.Right : Direction.Left);
			activityBarInGrid = true;
		}
E
Erich Gamma 已提交
754

755 756 757 758
		if (!sidebarInGrid) {
			this.workbenchGrid.addView(this.sideBarPartView, this.state.sideBar.width !== undefined ? this.state.sideBar.width : Sizing.Split, this.activityBarPartView, this.state.sideBar.position === Position.LEFT ? Direction.Right : Direction.Left);
			sidebarInGrid = true;
		}
B
Benjamin Pasero 已提交
759

760 761 762 763
		if (!panelInGrid) {
			this.workbenchGrid.addView(this.panelPartView, this.getPanelDimension(this.state.panel.position) !== undefined ? this.getPanelDimension(this.state.panel.position) : Sizing.Split, this.editorPartView, this.state.panel.position === Position.BOTTOM ? Direction.Down : Direction.Right);
			panelInGrid = true;
		}
764

765 766 767 768
		// Hide parts
		if (this.state.panel.hidden) {
			this.panelPartView.hide();
		}
B
Benjamin Pasero 已提交
769

770 771
		if (this.state.statusBar.hidden) {
			this.statusBarPartView.hide();
772 773
		}

774 775 776
		if (!this.isVisible(Parts.TITLEBAR_PART)) {
			this.titleBarPartView.hide();
		}
B
Benjamin Pasero 已提交
777

778 779 780
		if (this.state.activityBar.hidden) {
			this.activityBarPartView.hide();
		}
781

782 783
		if (this.state.sideBar.hidden) {
			this.sideBarPartView.hide();
784
		}
E
Erich Gamma 已提交
785

786 787
		if (this.state.editor.hidden) {
			this.editorPartView.hide();
E
Erich Gamma 已提交
788 789
		}

790 791 792
		// Show visible parts
		if (!this.state.editor.hidden) {
			this.editorPartView.show();
I
isidor 已提交
793 794
		}

795 796 797
		if (!this.state.statusBar.hidden) {
			this.statusBarPartView.show();
		}
E
Erich Gamma 已提交
798

799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839
		if (this.isVisible(Parts.TITLEBAR_PART)) {
			this.titleBarPartView.show();
		}

		if (!this.state.activityBar.hidden) {
			this.activityBarPartView.show();
		}

		if (!this.state.sideBar.hidden) {
			this.sideBarPartView.show();
		}

		if (!this.state.panel.hidden) {
			this.panelPartView.show();
		}
	}

	private getPanelDimension(position: Position): number {
		return position === Position.BOTTOM ? this.state.panel.height : this.state.panel.width;
	}

	isEditorLayoutCentered(): boolean {
		return this.state.editor.centered;
	}

	centerEditorLayout(active: boolean, skipLayout?: boolean): void {
		this.state.editor.centered = active;

		this.storageService.store(Storage.CENTERED_LAYOUT_ENABLED, active, StorageScope.WORKSPACE);

		let smartActive = active;
		if (this.editorGroupService.groups.length > 1 && this.configurationService.getValue('workbench.editor.centeredLayoutAutoResize')) {
			smartActive = false; // Respect the auto resize setting - do not go into centered layout if there is more than 1 group.
		}

		// Enter Centered Editor Layout
		if (this.editorGroupService.isLayoutCentered() !== smartActive) {
			this.editorGroupService.centerLayout(smartActive);

			if (!skipLayout) {
				this.layout();
J
Johannes Rieken 已提交
840
			}
841
		}
842

S
SteVen Batten 已提交
843
		this._onCenteredLayout.fire(this.state.editor.centered);
844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862
	}

	resizePart(part: Parts, sizeChange: number): void {
		let view: View;
		switch (part) {
			case Parts.SIDEBAR_PART:
				view = this.sideBarPartView;
			case Parts.PANEL_PART:
				view = this.panelPartView;
			case Parts.EDITOR_PART:
				view = this.editorPartView;
				if (this.workbenchGrid instanceof Grid) {
					this.workbenchGrid.resizeView(view, this.workbenchGrid.getViewSize(view) + sizeChange);
				} else {
					this.workbenchGrid.resizePart(part, sizeChange);
				}
				break;
			default:
				return; // Cannot resize other parts
863
		}
864
	}
865

866 867
	setActivityBarHidden(hidden: boolean, skipLayout?: boolean): void {
		this.state.activityBar.hidden = hidden;
E
Erich Gamma 已提交
868

869 870 871 872
		// Layout
		if (!skipLayout) {
			if (this.workbenchGrid instanceof Grid) {
				this.layout();
I
isidor 已提交
873
			} else {
874
				this.workbenchGrid.layout();
I
isidor 已提交
875
			}
876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901
		}
	}

	setEditorHidden(hidden: boolean, skipLayout?: boolean): void {
		if (!(this.workbenchGrid instanceof Grid) || hidden === this.state.editor.hidden) {
			return;
		}

		this.state.editor.hidden = hidden;

		// The editor and the panel cannot be hidden at the same time
		if (this.state.editor.hidden && this.state.panel.hidden) {
			this.setPanelHidden(false, true);
		}

		if (!skipLayout) {
			this.layout();
		}
	}

	setSideBarHidden(hidden: boolean, skipLayout?: boolean): void {
		this.state.sideBar.hidden = hidden;

		// Adjust CSS
		if (hidden) {
			addClass(this.container, 'nosidebar');
E
Erich Gamma 已提交
902
		} else {
903 904 905 906 907 908 909 910 911 912 913
			removeClass(this.container, 'nosidebar');
		}

		// If sidebar becomes hidden, also hide the current active Viewlet if any
		if (hidden && this.viewletService.getActiveViewlet()) {
			this.viewletService.hideActiveViewlet();

			// Pass Focus to Editor or Panel if Sidebar is now hidden
			const activePanel = this.panelService.getActivePanel();
			if (this.hasFocus(Parts.PANEL_PART) && activePanel) {
				activePanel.focus();
I
isidor 已提交
914
			} else {
915
				this.editorGroupService.activeGroup.focus();
I
isidor 已提交
916
			}
E
Erich Gamma 已提交
917 918
		}

919 920 921 922 923 924 925 926 927
		// If sidebar becomes visible, show last active Viewlet or default viewlet
		else if (!hidden && !this.viewletService.getActiveViewlet()) {
			const viewletToOpen = this.viewletService.getLastActiveViewletId();
			if (viewletToOpen) {
				const viewlet = this.viewletService.openViewlet(viewletToOpen, true);
				if (!viewlet) {
					this.viewletService.openViewlet(this.viewletService.getDefaultViewletId(), true);
				}
			}
E
Erich Gamma 已提交
928
		}
929 930 931 932 933

		// Remember in settings
		const defaultHidden = this.contextService.getWorkbenchState() === WorkbenchState.EMPTY;
		if (hidden !== defaultHidden) {
			this.storageService.store(Storage.SIDEBAR_HIDDEN, hidden ? 'true' : 'false', StorageScope.WORKSPACE);
B
Benjamin Pasero 已提交
934
		} else {
935
			this.storageService.remove(Storage.SIDEBAR_HIDDEN, StorageScope.WORKSPACE);
B
Benjamin Pasero 已提交
936
		}
E
Erich Gamma 已提交
937

938 939 940 941 942 943 944
		// Layout
		if (!skipLayout) {
			if (this.workbenchGrid instanceof Grid) {
				this.layout();
			} else {
				this.workbenchGrid.layout();
			}
E
Erich Gamma 已提交
945
		}
946
	}
E
Erich Gamma 已提交
947

948 949 950 951 952 953
	setPanelHidden(hidden: boolean, skipLayout?: boolean): void {
		this.state.panel.hidden = hidden;

		// Adjust CSS
		if (hidden) {
			addClass(this.container, 'nopanel');
954
		} else {
955
			removeClass(this.container, 'nopanel');
956
		}
E
Erich Gamma 已提交
957

958 959 960 961 962
		// If panel part becomes hidden, also hide the current active panel if any
		if (hidden && this.panelService.getActivePanel()) {
			this.panelService.hideActivePanel();
			this.editorGroupService.activeGroup.focus(); // Pass focus to editor group if panel part is now hidden
		}
C
Christof Marti 已提交
963

964 965 966 967 968 969 970 971
		// If panel part becomes visible, show last active panel or default panel
		else if (!hidden && !this.panelService.getActivePanel()) {
			const panelToOpen = this.panelService.getLastActivePanelId();
			if (panelToOpen) {
				const focus = !skipLayout;
				this.panelService.openPanel(panelToOpen, focus);
			}
		}
972

973 974 975
		// Remember in settings
		if (!hidden) {
			this.storageService.store(Storage.PANEL_HIDDEN, 'false', StorageScope.WORKSPACE);
976
		} else {
977
			this.storageService.remove(Storage.PANEL_HIDDEN, StorageScope.WORKSPACE);
978
		}
E
Erich Gamma 已提交
979

980 981 982 983
		// The editor and panel cannot be hidden at the same time
		if (hidden && this.state.editor.hidden) {
			this.setEditorHidden(false, true);
		}
E
Erich Gamma 已提交
984

985 986 987 988 989 990 991 992
		// Layout
		if (!skipLayout) {
			if (this.workbenchGrid instanceof Grid) {
				this.layout();
			} else {
				this.workbenchGrid.layout();
			}
		}
E
Erich Gamma 已提交
993 994
	}

995 996 997 998 999 1000
	toggleMaximizedPanel(): void {
		if (this.workbenchGrid instanceof Grid) {
			this.workbenchGrid.maximizeViewSize(this.panelPartView);
		} else {
			this.workbenchGrid.layout({ toggleMaximizedPanel: true, source: Parts.PANEL_PART });
		}
E
Erich Gamma 已提交
1001 1002
	}

1003 1004 1005 1006 1007 1008
	isPanelMaximized(): boolean {
		if (this.workbenchGrid instanceof Grid) {
			try {
				return this.workbenchGrid.getViewSize2(this.panelPartView).height === this.getPart(Parts.PANEL_PART).maximumHeight;
			} catch (e) {
				return false;
1009
			}
1010 1011
		} else {
			return this.workbenchGrid.isPanelMaximized();
E
Erich Gamma 已提交
1012
		}
1013
	}
E
Erich Gamma 已提交
1014

1015 1016
	getSideBarPosition(): Position {
		return this.state.sideBar.position;
E
Erich Gamma 已提交
1017 1018
	}

1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031
	setMenubarVisibility(visibility: MenuBarVisibility, skipLayout: boolean): void {
		if (this.state.menuBar.visibility !== visibility) {
			this.state.menuBar.visibility = visibility;

			// Layout
			if (!skipLayout) {
				if (this.workbenchGrid instanceof Grid) {
					const dimensions = getClientArea(this.parent);
					this.workbenchGrid.layout(dimensions.width, dimensions.height);
				} else {
					this.workbenchGrid.layout();
				}
			}
1032
		}
1033
	}
1034

1035 1036
	getMenubarVisibility(): MenuBarVisibility {
		return this.state.menuBar.visibility;
E
Erich Gamma 已提交
1037 1038
	}

1039 1040
	getPanelPosition(): Position {
		return this.state.panel.position;
I
isidor 已提交
1041 1042
	}

1043 1044 1045 1046 1047 1048 1049 1050
	setPanelPosition(position: Position): void {
		const panelPart = this.getPart(Parts.PANEL_PART);
		const wasHidden = this.state.panel.hidden;

		if (this.state.panel.hidden) {
			this.setPanelHidden(false, true /* Skip Layout */);
		} else {
			this.savePanelDimension();
1051 1052
		}

1053 1054 1055
		const newPositionValue = (position === Position.BOTTOM) ? 'bottom' : 'right';
		const oldPositionValue = (this.state.panel.position === Position.BOTTOM) ? 'bottom' : 'right';
		this.state.panel.position = position;
I
isidor 已提交
1056

1057 1058 1059 1060 1061 1062 1063
		function positionToString(position: Position): string {
			switch (position) {
				case Position.LEFT: return 'left';
				case Position.RIGHT: return 'right';
				case Position.BOTTOM: return 'bottom';
			}
		}
I
isidor 已提交
1064

1065
		this.storageService.store(Storage.PANEL_POSITION, positionToString(this.state.panel.position), StorageScope.WORKSPACE);
1066

1067 1068
		this._onPanelPositionChange.fire(positionToString(this.state.panel.position));

1069 1070 1071
		// Adjust CSS
		removeClass(panelPart.getContainer(), oldPositionValue);
		addClass(panelPart.getContainer(), newPositionValue);
1072

1073 1074
		// Update Styles
		panelPart.updateStyles();
1075

1076 1077 1078 1079 1080
		// Layout
		if (this.workbenchGrid instanceof Grid) {
			if (!wasHidden) {
				this.savePanelDimension();
			}
1081

1082 1083 1084 1085 1086 1087
			this.workbenchGrid.removeView(this.panelPartView);
			this.layout();
		} else {
			this.workbenchGrid.layout();
		}
	}
1088

1089 1090 1091 1092
	private savePanelDimension(): void {
		if (!(this.workbenchGrid instanceof Grid)) {
			return;
		}
1093

1094 1095 1096 1097
		if (this.state.panel.position === Position.BOTTOM) {
			this.state.panel.height = this.workbenchGrid.getViewSize(this.panelPartView);
		} else {
			this.state.panel.width = this.workbenchGrid.getViewSize(this.panelPartView);
1098
		}
1099
	}
1100

1101 1102 1103 1104 1105 1106 1107
	private saveLayoutState(e: IWillSaveStateEvent): void {

		// Zen Mode
		if (this.state.zenMode.active) {
			this.storageService.store(Storage.ZEN_MODE_ENABLED, true, StorageScope.WORKSPACE);
		} else {
			this.storageService.remove(Storage.ZEN_MODE_ENABLED, StorageScope.WORKSPACE);
1108
		}
1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120

		if (e.reason === WillSaveStateReason.SHUTDOWN && this.state.zenMode.active) {
			if (!this.configurationService.getValue(Settings.ZEN_MODE_RESTORE)) {
				this.toggleZenMode(true); // We will not restore zen mode, need to clear all zen mode state changes
			}
		}
	}

	dispose(): void {
		super.dispose();

		this.disposed = true;
1121
	}
1122
}