Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/browser/ui/centered/centeredViewLayout.ts
3296 views
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 { $, IDomNodePagePosition } from '../../dom.js';
7
import { IView, IViewSize } from '../grid/grid.js';
8
import { IBoundarySashes } from '../sash/sash.js';
9
import { DistributeSizing, ISplitViewStyles, IView as ISplitViewView, Orientation, SplitView } from '../splitview/splitview.js';
10
import { Color } from '../../../common/color.js';
11
import { Event } from '../../../common/event.js';
12
import { DisposableStore, IDisposable } from '../../../common/lifecycle.js';
13
14
export interface CenteredViewState {
15
// width of the fixed centered layout
16
targetWidth: number;
17
// proportional size of left margin
18
leftMarginRatio: number;
19
// proportional size of right margin
20
rightMarginRatio: number;
21
}
22
23
const defaultState: CenteredViewState = {
24
targetWidth: 900,
25
leftMarginRatio: 0.1909,
26
rightMarginRatio: 0.1909,
27
};
28
29
const distributeSizing: DistributeSizing = { type: 'distribute' };
30
31
function createEmptyView(background: Color | undefined): ISplitViewView<{ top: number; left: number }> {
32
const element = $('.centered-layout-margin');
33
element.style.height = '100%';
34
if (background) {
35
element.style.backgroundColor = background.toString();
36
}
37
38
return {
39
element,
40
layout: () => undefined,
41
minimumSize: 60,
42
maximumSize: Number.POSITIVE_INFINITY,
43
onDidChange: Event.None
44
};
45
}
46
47
function toSplitViewView(view: IView, getHeight: () => number): ISplitViewView<{ top: number; left: number }> {
48
return {
49
element: view.element,
50
get maximumSize() { return view.maximumWidth; },
51
get minimumSize() { return view.minimumWidth; },
52
onDidChange: Event.map(view.onDidChange, e => e && e.width),
53
layout: (size, offset, ctx) => view.layout(size, getHeight(), ctx?.top ?? 0, (ctx?.left ?? 0) + offset)
54
};
55
}
56
57
export interface ICenteredViewStyles extends ISplitViewStyles {
58
background: Color;
59
}
60
61
export class CenteredViewLayout implements IDisposable {
62
63
private splitView?: SplitView<{ top: number; left: number }>;
64
private lastLayoutPosition: IDomNodePagePosition = { width: 0, height: 0, left: 0, top: 0 };
65
private style!: ICenteredViewStyles;
66
private didLayout = false;
67
private emptyViews: ISplitViewView<{ top: number; left: number }>[] | undefined;
68
private readonly splitViewDisposables = new DisposableStore();
69
70
constructor(
71
private container: HTMLElement,
72
private view: IView,
73
public state: CenteredViewState = { ...defaultState },
74
private centeredLayoutFixedWidth: boolean = false
75
) {
76
this.container.appendChild(this.view.element);
77
// Make sure to hide the split view overflow like sashes #52892
78
this.container.style.overflow = 'hidden';
79
}
80
81
get minimumWidth(): number { return this.splitView ? this.splitView.minimumSize : this.view.minimumWidth; }
82
get maximumWidth(): number { return this.splitView ? this.splitView.maximumSize : this.view.maximumWidth; }
83
get minimumHeight(): number { return this.view.minimumHeight; }
84
get maximumHeight(): number { return this.view.maximumHeight; }
85
get onDidChange(): Event<IViewSize | undefined> { return this.view.onDidChange; }
86
87
private _boundarySashes: IBoundarySashes = {};
88
get boundarySashes(): IBoundarySashes { return this._boundarySashes; }
89
set boundarySashes(boundarySashes: IBoundarySashes) {
90
this._boundarySashes = boundarySashes;
91
92
if (!this.splitView) {
93
return;
94
}
95
96
this.splitView.orthogonalStartSash = boundarySashes.top;
97
this.splitView.orthogonalEndSash = boundarySashes.bottom;
98
}
99
100
layout(width: number, height: number, top: number, left: number): void {
101
this.lastLayoutPosition = { width, height, top, left };
102
if (this.splitView) {
103
this.splitView.layout(width, this.lastLayoutPosition);
104
if (!this.didLayout || this.centeredLayoutFixedWidth) {
105
this.resizeSplitViews();
106
}
107
} else {
108
this.view.layout(width, height, top, left);
109
}
110
111
this.didLayout = true;
112
}
113
114
private resizeSplitViews(): void {
115
if (!this.splitView) {
116
return;
117
}
118
if (this.centeredLayoutFixedWidth) {
119
const centerViewWidth = Math.min(this.lastLayoutPosition.width, this.state.targetWidth);
120
const marginWidthFloat = (this.lastLayoutPosition.width - centerViewWidth) / 2;
121
this.splitView.resizeView(0, Math.floor(marginWidthFloat));
122
this.splitView.resizeView(1, centerViewWidth);
123
this.splitView.resizeView(2, Math.ceil(marginWidthFloat));
124
} else {
125
const leftMargin = this.state.leftMarginRatio * this.lastLayoutPosition.width;
126
const rightMargin = this.state.rightMarginRatio * this.lastLayoutPosition.width;
127
const center = this.lastLayoutPosition.width - leftMargin - rightMargin;
128
this.splitView.resizeView(0, leftMargin);
129
this.splitView.resizeView(1, center);
130
this.splitView.resizeView(2, rightMargin);
131
}
132
}
133
134
setFixedWidth(option: boolean) {
135
this.centeredLayoutFixedWidth = option;
136
if (!!this.splitView) {
137
this.updateState();
138
this.resizeSplitViews();
139
}
140
}
141
142
private updateState() {
143
if (!!this.splitView) {
144
this.state.targetWidth = this.splitView.getViewSize(1);
145
this.state.leftMarginRatio = this.splitView.getViewSize(0) / this.lastLayoutPosition.width;
146
this.state.rightMarginRatio = this.splitView.getViewSize(2) / this.lastLayoutPosition.width;
147
}
148
}
149
150
isActive(): boolean {
151
return !!this.splitView;
152
}
153
154
styles(style: ICenteredViewStyles): void {
155
this.style = style;
156
if (this.splitView && this.emptyViews) {
157
this.splitView.style(this.style);
158
this.emptyViews[0].element.style.backgroundColor = this.style.background.toString();
159
this.emptyViews[1].element.style.backgroundColor = this.style.background.toString();
160
}
161
}
162
163
activate(active: boolean): void {
164
if (active === this.isActive()) {
165
return;
166
}
167
168
if (active) {
169
this.view.element.remove();
170
this.splitView = new SplitView(this.container, {
171
inverseAltBehavior: true,
172
orientation: Orientation.HORIZONTAL,
173
styles: this.style
174
});
175
this.splitView.orthogonalStartSash = this.boundarySashes.top;
176
this.splitView.orthogonalEndSash = this.boundarySashes.bottom;
177
178
this.splitViewDisposables.add(this.splitView.onDidSashChange(() => {
179
if (!!this.splitView) {
180
this.updateState();
181
}
182
}));
183
this.splitViewDisposables.add(this.splitView.onDidSashReset(() => {
184
this.state = { ...defaultState };
185
this.resizeSplitViews();
186
}));
187
188
this.splitView.layout(this.lastLayoutPosition.width, this.lastLayoutPosition);
189
const backgroundColor = this.style ? this.style.background : undefined;
190
this.emptyViews = [createEmptyView(backgroundColor), createEmptyView(backgroundColor)];
191
192
this.splitView.addView(this.emptyViews[0], distributeSizing, 0);
193
this.splitView.addView(toSplitViewView(this.view, () => this.lastLayoutPosition.height), distributeSizing, 1);
194
this.splitView.addView(this.emptyViews[1], distributeSizing, 2);
195
196
this.resizeSplitViews();
197
} else {
198
this.splitView?.el.remove();
199
this.splitViewDisposables.clear();
200
this.splitView?.dispose();
201
this.splitView = undefined;
202
this.emptyViews = undefined;
203
this.container.appendChild(this.view.element);
204
this.view.layout(this.lastLayoutPosition.width, this.lastLayoutPosition.height, this.lastLayoutPosition.top, this.lastLayoutPosition.left);
205
}
206
}
207
208
isDefault(state: CenteredViewState): boolean {
209
if (this.centeredLayoutFixedWidth) {
210
return state.targetWidth === defaultState.targetWidth;
211
} else {
212
return state.leftMarginRatio === defaultState.leftMarginRatio
213
&& state.rightMarginRatio === defaultState.rightMarginRatio;
214
}
215
}
216
217
dispose(): void {
218
this.splitViewDisposables.dispose();
219
220
if (this.splitView) {
221
this.splitView.dispose();
222
this.splitView = undefined;
223
}
224
}
225
}
226
227