Skip to content

Commit 6a1ca40

Browse files
authored
Merge pull request microsoft#52064 from Microsoft/isidorn/centeredLayout
Centered editor layout
2 parents 04cc579 + 8527801 commit 6a1ca40

4 files changed

Lines changed: 184 additions & 39 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
.monaco-workbench > .part.editor > .content .centered-view-layout {
7+
height: 100%;
8+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import 'vs/css!./centeredViewLayout';
7+
8+
import { SplitView, Orientation, ISplitViewStyles, IView as ISplitViewView } from 'vs/base/browser/ui/splitview/splitview';
9+
import { $ } from 'vs/base/browser/dom';
10+
import { Event, mapEvent, anyEvent } from 'vs/base/common/event';
11+
import { IView } from 'vs/base/browser/ui/grid/gridview';
12+
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
13+
14+
function toSplitViewView(view: IView, getHeight: () => number): ISplitViewView {
15+
return {
16+
element: view.element,
17+
maximumSize: view.maximumWidth,
18+
minimumSize: view.minimumWidth,
19+
onDidChange: mapEvent(view.onDidChange, widthAndHeight => widthAndHeight && widthAndHeight.width),
20+
layout: size => view.layout(size, getHeight())
21+
};
22+
}
23+
24+
export interface CenteredViewState {
25+
leftMarginRatio: number;
26+
rightMarginRatio: number;
27+
}
28+
29+
const GOLDEN_RATIO = {
30+
leftMarginRatio: 0.1909,
31+
rightMarginRatio: 0.1909
32+
};
33+
34+
export class CenteredViewLayout {
35+
36+
private splitView: SplitView;
37+
private element: HTMLElement;
38+
private width: number = 0;
39+
private height: number = 0;
40+
private style: ISplitViewStyles;
41+
private didLayout = false;
42+
private splitViewDisposable: IDisposable[] = [];
43+
44+
constructor(private container: HTMLElement, private view: IView, public readonly state: CenteredViewState = GOLDEN_RATIO) {
45+
this.container.appendChild(this.view.element);
46+
}
47+
48+
layout(width: number, height: number): void {
49+
this.width = width;
50+
this.height = height;
51+
if (this.splitView) {
52+
this.splitView.layout(width);
53+
if (!this.didLayout) {
54+
this.splitView.resizeView(0, this.state.leftMarginRatio * this.width);
55+
this.splitView.resizeView(2, this.state.rightMarginRatio * this.width);
56+
}
57+
} else {
58+
this.view.layout(width, height);
59+
}
60+
this.didLayout = true;
61+
}
62+
63+
isActive(): boolean {
64+
return !!this.splitView;
65+
}
66+
67+
styles(style: ISplitViewStyles): void {
68+
this.style = style;
69+
if (this.splitView) {
70+
this.splitView.style(this.style);
71+
}
72+
}
73+
74+
resetView(view: IView): void {
75+
this.view = view;
76+
if (this.splitView) {
77+
const size = this.splitView.getViewSize(1);
78+
this.splitView.removeView(1);
79+
this.splitView.addView(toSplitViewView(this.view, () => this.height), size, 1);
80+
this.splitView.distributeViewSizes();
81+
}
82+
}
83+
84+
activate(active: boolean): void {
85+
if (active === !!this.splitView) {
86+
return;
87+
}
88+
89+
if (active) {
90+
this.element = $('.centered-view-layout');
91+
this.container.removeChild(this.view.element);
92+
this.container.appendChild(this.element);
93+
this.splitView = new SplitView(this.element, {
94+
inverseAltBehavior: true,
95+
orientation: Orientation.HORIZONTAL,
96+
styles: this.style
97+
});
98+
99+
const onDidSizesChange = anyEvent(this.splitView.onDidSashChange, this.splitView.onDidSashReset);
100+
this.splitViewDisposable.push(onDidSizesChange(() => {
101+
this.state.leftMarginRatio = this.splitView.getViewSize(0) / this.width;
102+
this.state.rightMarginRatio = this.splitView.getViewSize(2) / this.width;
103+
}));
104+
105+
this.splitView.layout(this.width);
106+
107+
const getEmptyView = () => ({
108+
element: $('.centered-layout-margin'),
109+
layout: () => undefined,
110+
minimumSize: 40,
111+
maximumSize: Number.POSITIVE_INFINITY,
112+
onDidChange: Event.None
113+
});
114+
115+
this.splitView.addView(toSplitViewView(this.view, () => this.height), 0);
116+
this.splitView.addView(getEmptyView(), this.state.leftMarginRatio * this.width, 0);
117+
this.splitView.addView(getEmptyView(), this.state.rightMarginRatio * this.width, 2);
118+
} else {
119+
this.splitViewDisposable = dispose(this.splitViewDisposable);
120+
this.splitView.dispose();
121+
this.splitView = undefined;
122+
this.container.removeChild(this.element);
123+
this.container.appendChild(this.view.element);
124+
}
125+
}
126+
127+
dispose(): void {
128+
if (this.splitView) {
129+
this.splitView.dispose();
130+
}
131+
}
132+
}

src/vs/workbench/browser/parts/editor/editorPart.ts

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/
3434
import { EditorDropTarget } from 'vs/workbench/browser/parts/editor/editorDropTarget';
3535
import { localize } from 'vs/nls';
3636
import { Color } from 'vs/base/common/color';
37+
import { CenteredViewLayout } from 'vs/base/browser/ui/centered/centeredViewLayout';
38+
import { IView } from 'vs/base/browser/ui/grid/gridview';
3739

3840
interface IEditorPartUIState {
3941
serializedGrid: ISerializedGrid;
@@ -46,6 +48,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
4648
_serviceBrand: any;
4749

4850
private static readonly EDITOR_PART_UI_STATE_STORAGE_KEY = 'editorpart.state';
51+
private static readonly EDITOR_PART_CENTERED_VIEW_STORAGE_KEY = 'editorpart.centeredview';
4952

5053
//#region Events
5154

@@ -77,6 +80,8 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
7780
private _preferredSize: Dimension;
7881

7982
private memento: object;
83+
private globalMemento: object;
84+
8085
private _partOptions: IEditorPartOptions;
8186

8287
private _activeGroup: IEditorGroupView;
@@ -90,6 +95,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
9095
private whenRestoredComplete: TValueCallback<void>;
9196

9297
private previousUIState: IEditorPartUIState;
98+
private centeredViewLayout: CenteredViewLayout;
9399

94100
constructor(
95101
id: string,
@@ -106,6 +112,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
106112

107113
this._partOptions = getEditorPartOptions(this.configurationService.getValue<IWorkbenchEditorConfiguration>());
108114
this.memento = this.getMemento(this.storageService, Scope.WORKSPACE);
115+
this.globalMemento = this.getMemento(this.storageService, Scope.GLOBAL);
109116

110117
this._whenRestored = new TPromise(resolve => {
111118
this.whenRestoredComplete = resolve;
@@ -699,7 +706,9 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
699706
protected updateStyles(): void {
700707
this.container.style.backgroundColor = this.getColor(editorBackground);
701708

702-
this.gridWidget.style({ separatorBorder: this.gridSeparatorBorder });
709+
const separatorBorderStyle = { separatorBorder: this.gridSeparatorBorder };
710+
this.gridWidget.style(separatorBorderStyle);
711+
this.centeredViewLayout.styles(separatorBorderStyle);
703712
}
704713

705714
createContentArea(parent: HTMLElement): HTMLElement {
@@ -709,15 +718,36 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
709718
addClass(this.container, 'content');
710719
parent.appendChild(this.container);
711720

712-
// Grid control
721+
// Grid control with center layout
713722
this.doCreateGridControl();
723+
this.centeredViewLayout = new CenteredViewLayout(this.container, this.getGridAsView(), this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY]);
714724

715725
// Drop support
716726
this._register(this.instantiationService.createInstance(EditorDropTarget, this, this.container));
717727

718728
return this.container;
719729
}
720730

731+
private getGridAsView(): IView {
732+
return {
733+
element: this.gridWidget.element,
734+
layout: (width, height) => this.gridWidget.layout(width, height),
735+
minimumWidth: this.gridWidget.minimumWidth,
736+
maximumWidth: this.gridWidget.maximumWidth,
737+
minimumHeight: this.gridWidget.minimumHeight,
738+
maximumHeight: this.gridWidget.minimumHeight,
739+
onDidChange: this.gridWidget.onDidChange
740+
};
741+
}
742+
743+
centerLayout(active: boolean): void {
744+
this.centeredViewLayout.activate(active);
745+
}
746+
747+
isLayoutCentered(): boolean {
748+
return this.centeredViewLayout.isActive();
749+
}
750+
721751
private doCreateGridControl(): void {
722752

723753
// Grid Widget (with previous UI state)
@@ -809,7 +839,9 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
809839
this.gridWidget = gridWidget;
810840

811841
if (gridWidget) {
812-
this.container.appendChild(gridWidget.element);
842+
if (this.centeredViewLayout) {
843+
this.centeredViewLayout.resetView(this.getGridAsView());
844+
}
813845
this._onDidSizeConstraintsChange.input = gridWidget.onDidChange;
814846
}
815847

@@ -967,7 +999,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
967999

9681000
// Layout Grid
9691001
try {
970-
this.gridWidget.layout(this.dimension.width, this.dimension.height);
1002+
this.centeredViewLayout.layout(this.dimension.width, this.dimension.height);
9711003
} catch (error) {
9721004
this.gridError(error);
9731005
}
@@ -992,6 +1024,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
9921024
this.memento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] = uiState;
9931025
}
9941026
}
1027+
this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY] = this.centeredViewLayout.state;
9951028

9961029
// Forward to all groups
9971030
this.groupViews.forEach(group => group.shutdown());
@@ -1009,6 +1042,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
10091042
if (this.gridWidget) {
10101043
this.gridWidget.dispose();
10111044
}
1045+
this.centeredViewLayout.dispose();
10121046

10131047
super.dispose();
10141048
}

src/vs/workbench/electron-browser/workbench.ts

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver
110110
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
111111
import { PreferencesService } from 'vs/workbench/services/preferences/browser/preferencesService';
112112
import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService';
113-
import { IEditorGroupsService, GroupDirection, preferredSideBySideGroupDirection, GroupOrientation } from 'vs/workbench/services/group/common/editorGroupsService';
113+
import { IEditorGroupsService, GroupDirection, preferredSideBySideGroupDirection } from 'vs/workbench/services/group/common/editorGroupsService';
114114
import { EditorService } from 'vs/workbench/services/editor/browser/editorService';
115115
import { IExtensionUrlHandler, ExtensionUrlHandler } from 'vs/platform/url/electron-browser/inactiveExtensionUrlHandler';
116116
import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService';
@@ -227,7 +227,6 @@ export class Workbench extends Disposable implements IPartService {
227227
private panelHidden: boolean;
228228
private menubarHidden: boolean;
229229
private zenMode: IZenMode;
230-
private centeredEditorLayoutActive: boolean;
231230
private fontAliasing: FontAliasingOption;
232231
private hasInitialFilesToOpen: boolean;
233232

@@ -714,7 +713,7 @@ export class Workbench extends Disposable implements IPartService {
714713

715714
// Restore Forced Editor Center Mode
716715
if (this.storageService.getBoolean(Workbench.centeredEditorLayoutActiveStorageKey, StorageScope.WORKSPACE, false)) {
717-
this.centeredEditorLayoutActive = true;
716+
this.centerEditorLayout(true);
718717
}
719718

720719
const onRestored = (error?: Error): IWorkbenchStartedInfo => {
@@ -872,9 +871,6 @@ export class Workbench extends Disposable implements IPartService {
872871
wasPanelVisible: false,
873872
transitionDisposeables: []
874873
};
875-
876-
// Centered Editor Layout
877-
this.centeredEditorLayoutActive = false;
878874
}
879875

880876
private setPanelPositionFromStorageOrConfig() {
@@ -1293,39 +1289,14 @@ export class Workbench extends Disposable implements IPartService {
12931289
}
12941290

12951291
isEditorLayoutCentered(): boolean {
1296-
return this.centeredEditorLayoutActive;
1292+
return this.editorPart.isLayoutCentered();
12971293
}
12981294

1299-
// TODO@ben support centered editor layout using empty groups or not? functionality missing:
1300-
// - resize sashes left and right in sync
1301-
// - IEditorInput.supportsCenteredEditorLayout() no longer supported
1302-
// - should we just allow to enter layout even if groups > 1? what does it then mean to be
1303-
// actively in centered editor layout though?
13041295
centerEditorLayout(active: boolean, skipLayout?: boolean): void {
1305-
this.centeredEditorLayoutActive = active;
1306-
this.storageService.store(Workbench.centeredEditorLayoutActiveStorageKey, this.centeredEditorLayoutActive, StorageScope.WORKSPACE);
1296+
this.storageService.store(Workbench.centeredEditorLayoutActiveStorageKey, active, StorageScope.WORKSPACE);
13071297

13081298
// Enter Centered Editor Layout
1309-
if (active) {
1310-
if (this.editorGroupService.count === 1) {
1311-
const activeGroup = this.editorGroupService.activeGroup;
1312-
this.editorGroupService.addGroup(activeGroup, GroupDirection.LEFT);
1313-
this.editorGroupService.addGroup(activeGroup, GroupDirection.RIGHT);
1314-
1315-
this.editorGroupService.applyLayout({ groups: [{ size: 0.2 }, { size: 0.6 }, { size: 0.2 }], orientation: GroupOrientation.HORIZONTAL });
1316-
}
1317-
}
1318-
1319-
// Leave Centered Editor Layout
1320-
else {
1321-
if (this.editorGroupService.count === 3) {
1322-
this.editorGroupService.groups.forEach(group => {
1323-
if (group.count === 0) {
1324-
this.editorGroupService.removeGroup(group);
1325-
}
1326-
});
1327-
}
1328-
}
1299+
this.editorPart.centerLayout(active);
13291300

13301301
if (!skipLayout) {
13311302
this.layout();
@@ -1514,4 +1485,4 @@ export class Workbench extends Disposable implements IPartService {
15141485
}
15151486

15161487
//#endregion
1517-
}
1488+
}

0 commit comments

Comments
 (0)