diff --git a/src/vs/base/browser/ui/centered/centeredViewLayout.css b/src/vs/base/browser/ui/centered/centeredViewLayout.css new file mode 100644 index 0000000000000000000000000000000000000000..854530732a0bbc8b65101ecd7882c1ae6476e086 --- /dev/null +++ b/src/vs/base/browser/ui/centered/centeredViewLayout.css @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench > .part.editor > .content .centered-view-layout { + height: 100%; +} diff --git a/src/vs/base/browser/ui/centered/centeredViewLayout.ts b/src/vs/base/browser/ui/centered/centeredViewLayout.ts new file mode 100644 index 0000000000000000000000000000000000000000..c3c97743d8b3817f960794655605782d764ffa8a --- /dev/null +++ b/src/vs/base/browser/ui/centered/centeredViewLayout.ts @@ -0,0 +1,132 @@ +/*--------------------------------------------------------------------------------------------- + * 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!./centeredViewLayout'; + +import { SplitView, Orientation, ISplitViewStyles, IView as ISplitViewView } from 'vs/base/browser/ui/splitview/splitview'; +import { $ } from 'vs/base/browser/dom'; +import { Event, mapEvent, anyEvent } from 'vs/base/common/event'; +import { IView } from 'vs/base/browser/ui/grid/gridview'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; + +function toSplitViewView(view: IView, getHeight: () => number): ISplitViewView { + return { + element: view.element, + maximumSize: view.maximumWidth, + minimumSize: view.minimumWidth, + onDidChange: mapEvent(view.onDidChange, widthAndHeight => widthAndHeight && widthAndHeight.width), + layout: size => view.layout(size, getHeight()) + }; +} + +export interface CenteredViewState { + leftMarginRatio: number; + rightMarginRatio: number; +} + +const GOLDEN_RATIO = { + leftMarginRatio: 0.1909, + rightMarginRatio: 0.1909 +}; + +export class CenteredViewLayout { + + private splitView: SplitView; + private element: HTMLElement; + private width: number = 0; + private height: number = 0; + private style: ISplitViewStyles; + private didLayout = false; + private splitViewDisposable: IDisposable[] = []; + + constructor(private container: HTMLElement, private view: IView, public readonly state: CenteredViewState = GOLDEN_RATIO) { + this.container.appendChild(this.view.element); + } + + layout(width: number, height: number): void { + this.width = width; + this.height = height; + if (this.splitView) { + this.splitView.layout(width); + if (!this.didLayout) { + this.splitView.resizeView(0, this.state.leftMarginRatio * this.width); + this.splitView.resizeView(2, this.state.rightMarginRatio * this.width); + } + } else { + this.view.layout(width, height); + } + this.didLayout = true; + } + + isActive(): boolean { + return !!this.splitView; + } + + styles(style: ISplitViewStyles): void { + this.style = style; + if (this.splitView) { + this.splitView.style(this.style); + } + } + + resetView(view: IView): void { + this.view = view; + if (this.splitView) { + const size = this.splitView.getViewSize(1); + this.splitView.removeView(1); + this.splitView.addView(toSplitViewView(this.view, () => this.height), size, 1); + this.splitView.distributeViewSizes(); + } + } + + activate(active: boolean): void { + if (active === !!this.splitView) { + return; + } + + if (active) { + this.element = $('.centered-view-layout'); + this.container.removeChild(this.view.element); + this.container.appendChild(this.element); + this.splitView = new SplitView(this.element, { + inverseAltBehavior: true, + orientation: Orientation.HORIZONTAL, + styles: this.style + }); + + const onDidSizesChange = anyEvent(this.splitView.onDidSashChange, this.splitView.onDidSashReset); + this.splitViewDisposable.push(onDidSizesChange(() => { + this.state.leftMarginRatio = this.splitView.getViewSize(0) / this.width; + this.state.rightMarginRatio = this.splitView.getViewSize(2) / this.width; + })); + + this.splitView.layout(this.width); + + const getEmptyView = () => ({ + element: $('.centered-layout-margin'), + layout: () => undefined, + minimumSize: 40, + maximumSize: Number.POSITIVE_INFINITY, + onDidChange: Event.None + }); + + this.splitView.addView(toSplitViewView(this.view, () => this.height), 0); + this.splitView.addView(getEmptyView(), this.state.leftMarginRatio * this.width, 0); + this.splitView.addView(getEmptyView(), this.state.rightMarginRatio * this.width, 2); + } else { + this.splitViewDisposable = dispose(this.splitViewDisposable); + this.splitView.dispose(); + this.splitView = undefined; + this.container.removeChild(this.element); + this.container.appendChild(this.view.element); + } + } + + dispose(): void { + if (this.splitView) { + this.splitView.dispose(); + } + } +} diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 91b37e7c19e659d7e329c76062e13be41ee02fb8..53be6129449316c5e8aa035379bbaa01b63906a4 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -34,6 +34,8 @@ import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/ import { EditorDropTarget } from 'vs/workbench/browser/parts/editor/editorDropTarget'; import { localize } from 'vs/nls'; import { Color } from 'vs/base/common/color'; +import { CenteredViewLayout } from 'vs/base/browser/ui/centered/centeredViewLayout'; +import { IView } from 'vs/base/browser/ui/grid/gridview'; interface IEditorPartUIState { serializedGrid: ISerializedGrid; @@ -46,6 +48,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor _serviceBrand: any; private static readonly EDITOR_PART_UI_STATE_STORAGE_KEY = 'editorpart.state'; + private static readonly EDITOR_PART_CENTERED_VIEW_STORAGE_KEY = 'editorpart.centeredview'; //#region Events @@ -77,6 +80,8 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor private _preferredSize: Dimension; private memento: object; + private globalMemento: object; + private _partOptions: IEditorPartOptions; private _activeGroup: IEditorGroupView; @@ -90,6 +95,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor private whenRestoredComplete: TValueCallback; private previousUIState: IEditorPartUIState; + private centeredViewLayout: CenteredViewLayout; constructor( id: string, @@ -106,6 +112,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor this._partOptions = getEditorPartOptions(this.configurationService.getValue()); this.memento = this.getMemento(this.storageService, Scope.WORKSPACE); + this.globalMemento = this.getMemento(this.storageService, Scope.GLOBAL); this._whenRestored = new TPromise(resolve => { this.whenRestoredComplete = resolve; @@ -699,7 +706,9 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor protected updateStyles(): void { this.container.style.backgroundColor = this.getColor(editorBackground); - this.gridWidget.style({ separatorBorder: this.gridSeparatorBorder }); + const separatorBorderStyle = { separatorBorder: this.gridSeparatorBorder }; + this.gridWidget.style(separatorBorderStyle); + this.centeredViewLayout.styles(separatorBorderStyle); } createContentArea(parent: HTMLElement): HTMLElement { @@ -709,8 +718,9 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor addClass(this.container, 'content'); parent.appendChild(this.container); - // Grid control + // Grid control with center layout this.doCreateGridControl(); + this.centeredViewLayout = new CenteredViewLayout(this.container, this.getGridAsView(), this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY]); // Drop support this._register(this.instantiationService.createInstance(EditorDropTarget, this, this.container)); @@ -718,6 +728,26 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor return this.container; } + private getGridAsView(): IView { + return { + element: this.gridWidget.element, + layout: (width, height) => this.gridWidget.layout(width, height), + minimumWidth: this.gridWidget.minimumWidth, + maximumWidth: this.gridWidget.maximumWidth, + minimumHeight: this.gridWidget.minimumHeight, + maximumHeight: this.gridWidget.minimumHeight, + onDidChange: this.gridWidget.onDidChange + }; + } + + centerLayout(active: boolean): void { + this.centeredViewLayout.activate(active); + } + + isLayoutCentered(): boolean { + return this.centeredViewLayout.isActive(); + } + private doCreateGridControl(): void { // Grid Widget (with previous UI state) @@ -809,7 +839,9 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor this.gridWidget = gridWidget; if (gridWidget) { - this.container.appendChild(gridWidget.element); + if (this.centeredViewLayout) { + this.centeredViewLayout.resetView(this.getGridAsView()); + } this._onDidSizeConstraintsChange.input = gridWidget.onDidChange; } @@ -967,7 +999,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor // Layout Grid try { - this.gridWidget.layout(this.dimension.width, this.dimension.height); + this.centeredViewLayout.layout(this.dimension.width, this.dimension.height); } catch (error) { this.gridError(error); } @@ -992,6 +1024,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor this.memento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] = uiState; } } + this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY] = this.centeredViewLayout.state; // Forward to all groups this.groupViews.forEach(group => group.shutdown()); @@ -1009,6 +1042,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor if (this.gridWidget) { this.gridWidget.dispose(); } + this.centeredViewLayout.dispose(); super.dispose(); } diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index 7068b59ac50cd9352f0f106ae2282b3f7630b647..1dd8ea0391a246f346030c54c6b41e69717a46b6 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -110,7 +110,7 @@ import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { PreferencesService } from 'vs/workbench/services/preferences/browser/preferencesService'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService, GroupDirection, preferredSideBySideGroupDirection, GroupOrientation } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService, GroupDirection, preferredSideBySideGroupDirection } from 'vs/workbench/services/group/common/editorGroupsService'; import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; import { IExtensionUrlHandler, ExtensionUrlHandler } from 'vs/platform/url/electron-browser/inactiveExtensionUrlHandler'; import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; @@ -227,7 +227,6 @@ export class Workbench extends Disposable implements IPartService { private panelHidden: boolean; private menubarHidden: boolean; private zenMode: IZenMode; - private centeredEditorLayoutActive: boolean; private fontAliasing: FontAliasingOption; private hasInitialFilesToOpen: boolean; @@ -714,7 +713,7 @@ export class Workbench extends Disposable implements IPartService { // Restore Forced Editor Center Mode if (this.storageService.getBoolean(Workbench.centeredEditorLayoutActiveStorageKey, StorageScope.WORKSPACE, false)) { - this.centeredEditorLayoutActive = true; + this.centerEditorLayout(true); } const onRestored = (error?: Error): IWorkbenchStartedInfo => { @@ -872,9 +871,6 @@ export class Workbench extends Disposable implements IPartService { wasPanelVisible: false, transitionDisposeables: [] }; - - // Centered Editor Layout - this.centeredEditorLayoutActive = false; } private setPanelPositionFromStorageOrConfig() { @@ -1293,39 +1289,14 @@ export class Workbench extends Disposable implements IPartService { } isEditorLayoutCentered(): boolean { - return this.centeredEditorLayoutActive; + return this.editorPart.isLayoutCentered(); } - // TODO@ben support centered editor layout using empty groups or not? functionality missing: - // - resize sashes left and right in sync - // - IEditorInput.supportsCenteredEditorLayout() no longer supported - // - should we just allow to enter layout even if groups > 1? what does it then mean to be - // actively in centered editor layout though? centerEditorLayout(active: boolean, skipLayout?: boolean): void { - this.centeredEditorLayoutActive = active; - this.storageService.store(Workbench.centeredEditorLayoutActiveStorageKey, this.centeredEditorLayoutActive, StorageScope.WORKSPACE); + this.storageService.store(Workbench.centeredEditorLayoutActiveStorageKey, active, StorageScope.WORKSPACE); // Enter Centered Editor Layout - if (active) { - if (this.editorGroupService.count === 1) { - const activeGroup = this.editorGroupService.activeGroup; - this.editorGroupService.addGroup(activeGroup, GroupDirection.LEFT); - this.editorGroupService.addGroup(activeGroup, GroupDirection.RIGHT); - - this.editorGroupService.applyLayout({ groups: [{ size: 0.2 }, { size: 0.6 }, { size: 0.2 }], orientation: GroupOrientation.HORIZONTAL }); - } - } - - // Leave Centered Editor Layout - else { - if (this.editorGroupService.count === 3) { - this.editorGroupService.groups.forEach(group => { - if (group.count === 0) { - this.editorGroupService.removeGroup(group); - } - }); - } - } + this.editorPart.centerLayout(active); if (!skipLayout) { this.layout(); @@ -1514,4 +1485,4 @@ export class Workbench extends Disposable implements IPartService { } //#endregion -} \ No newline at end of file +}