Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/browser/part.ts
5237 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 './media/part.css';
7
import { Component } from '../common/component.js';
8
import { IThemeService, IColorTheme } from '../../platform/theme/common/themeService.js';
9
import { Dimension, size, IDimension, getActiveDocument, prepend, IDomPosition } from '../../base/browser/dom.js';
10
import { IStorageService } from '../../platform/storage/common/storage.js';
11
import { ISerializableView, IViewSize } from '../../base/browser/ui/grid/grid.js';
12
import { Event, Emitter } from '../../base/common/event.js';
13
import { IWorkbenchLayoutService } from '../services/layout/browser/layoutService.js';
14
import { assertReturnsDefined } from '../../base/common/types.js';
15
import { IDisposable, toDisposable } from '../../base/common/lifecycle.js';
16
17
export interface IPartOptions {
18
readonly hasTitle?: boolean;
19
readonly borderWidth?: () => number;
20
}
21
22
export interface ILayoutContentResult {
23
readonly headerSize: IDimension;
24
readonly titleSize: IDimension;
25
readonly contentSize: IDimension;
26
readonly footerSize: IDimension;
27
}
28
29
/**
30
* Parts are layed out in the workbench and have their own layout that
31
* arranges an optional title and mandatory content area to show content.
32
*/
33
export abstract class Part<MementoType extends object = object> extends Component<MementoType> implements ISerializableView {
34
35
private _dimension: Dimension | undefined;
36
get dimension(): Dimension | undefined { return this._dimension; }
37
38
private _contentPosition: IDomPosition | undefined;
39
get contentPosition(): IDomPosition | undefined { return this._contentPosition; }
40
41
protected _onDidVisibilityChange = this._register(new Emitter<boolean>());
42
readonly onDidVisibilityChange = this._onDidVisibilityChange.event;
43
44
private parent: HTMLElement | undefined;
45
private headerArea: HTMLElement | undefined;
46
protected titleArea: HTMLElement | undefined;
47
protected contentArea: HTMLElement | undefined;
48
private footerArea: HTMLElement | undefined;
49
private partLayout: PartLayout | undefined;
50
51
constructor(
52
id: string,
53
private options: IPartOptions,
54
themeService: IThemeService,
55
storageService: IStorageService,
56
protected readonly layoutService: IWorkbenchLayoutService
57
) {
58
super(id, themeService, storageService);
59
60
this._register(layoutService.registerPart(this));
61
}
62
63
protected override onThemeChange(theme: IColorTheme): void {
64
65
// only call if our create() method has been called
66
if (this.parent) {
67
super.onThemeChange(theme);
68
}
69
}
70
71
/**
72
* Note: Clients should not call this method, the workbench calls this
73
* method. Calling it otherwise may result in unexpected behavior.
74
*
75
* Called to create title and content area of the part.
76
*/
77
create(parent: HTMLElement, options?: object): void {
78
this.parent = parent;
79
this.titleArea = this.createTitleArea(parent, options);
80
this.contentArea = this.createContentArea(parent, options);
81
82
this.partLayout = new PartLayout(this.options, this.contentArea);
83
84
this.updateStyles();
85
}
86
87
/**
88
* Returns the overall part container.
89
*/
90
getContainer(): HTMLElement | undefined {
91
return this.parent;
92
}
93
94
/**
95
* Subclasses override to provide a title area implementation.
96
*/
97
protected createTitleArea(parent: HTMLElement, options?: object): HTMLElement | undefined {
98
return undefined;
99
}
100
101
/**
102
* Subclasses override to provide a content area implementation.
103
*/
104
protected createContentArea(parent: HTMLElement, options?: object): HTMLElement | undefined {
105
return undefined;
106
}
107
108
protected setHeaderArea(headerContainer: HTMLElement): void {
109
if (this.headerArea) {
110
throw new Error('Header already exists');
111
}
112
113
if (!this.parent || !this.titleArea) {
114
return;
115
}
116
117
prepend(this.parent, headerContainer);
118
headerContainer.classList.add('header-or-footer');
119
headerContainer.classList.add('header');
120
121
this.headerArea = headerContainer;
122
this.partLayout?.setHeaderVisibility(true);
123
this.relayout();
124
}
125
126
protected setFooterArea(footerContainer: HTMLElement): void {
127
if (this.footerArea) {
128
throw new Error('Footer already exists');
129
}
130
131
if (!this.parent || !this.titleArea) {
132
return;
133
}
134
135
this.parent.appendChild(footerContainer);
136
footerContainer.classList.add('header-or-footer');
137
footerContainer.classList.add('footer');
138
139
this.footerArea = footerContainer;
140
this.partLayout?.setFooterVisibility(true);
141
this.relayout();
142
}
143
144
protected removeHeaderArea(): void {
145
if (this.headerArea) {
146
this.headerArea.remove();
147
this.headerArea = undefined;
148
this.partLayout?.setHeaderVisibility(false);
149
this.relayout();
150
}
151
}
152
153
protected removeFooterArea(): void {
154
if (this.footerArea) {
155
this.footerArea.remove();
156
this.footerArea = undefined;
157
this.partLayout?.setFooterVisibility(false);
158
this.relayout();
159
}
160
}
161
162
private relayout() {
163
if (this.dimension && this.contentPosition) {
164
this.layout(this.dimension.width, this.dimension.height, this.contentPosition.top, this.contentPosition.left);
165
}
166
}
167
/**
168
* Layout title and content area in the given dimension.
169
*/
170
protected layoutContents(width: number, height: number): ILayoutContentResult {
171
const partLayout = assertReturnsDefined(this.partLayout);
172
173
return partLayout.layout(width, height);
174
}
175
176
//#region ISerializableView
177
178
protected _onDidChange = this._register(new Emitter<IViewSize | undefined>());
179
get onDidChange(): Event<IViewSize | undefined> { return this._onDidChange.event; }
180
181
element!: HTMLElement;
182
183
abstract minimumWidth: number;
184
abstract maximumWidth: number;
185
abstract minimumHeight: number;
186
abstract maximumHeight: number;
187
188
layout(width: number, height: number, top: number, left: number): void {
189
this._dimension = new Dimension(width, height);
190
this._contentPosition = { top, left };
191
}
192
193
setVisible(visible: boolean) {
194
this._onDidVisibilityChange.fire(visible);
195
}
196
197
abstract toJSON(): object;
198
199
//#endregion
200
}
201
202
class PartLayout {
203
204
private static readonly HEADER_HEIGHT = 35;
205
private static readonly TITLE_HEIGHT = 35;
206
private static readonly Footer_HEIGHT = 35;
207
208
private headerVisible: boolean = false;
209
private footerVisible: boolean = false;
210
211
constructor(private options: IPartOptions, private contentArea: HTMLElement | undefined) { }
212
213
layout(width: number, height: number): ILayoutContentResult {
214
215
// Title Size: Width (Fill), Height (Variable)
216
let titleSize: Dimension;
217
if (this.options.hasTitle) {
218
titleSize = new Dimension(width, Math.min(height, PartLayout.TITLE_HEIGHT));
219
} else {
220
titleSize = Dimension.None;
221
}
222
223
// Header Size: Width (Fill), Height (Variable)
224
let headerSize: Dimension;
225
if (this.headerVisible) {
226
headerSize = new Dimension(width, Math.min(height, PartLayout.HEADER_HEIGHT));
227
} else {
228
headerSize = Dimension.None;
229
}
230
231
// Footer Size: Width (Fill), Height (Variable)
232
let footerSize: Dimension;
233
if (this.footerVisible) {
234
footerSize = new Dimension(width, Math.min(height, PartLayout.Footer_HEIGHT));
235
} else {
236
footerSize = Dimension.None;
237
}
238
239
let contentWidth = width;
240
if (this.options && typeof this.options.borderWidth === 'function') {
241
contentWidth -= this.options.borderWidth(); // adjust for border size
242
}
243
244
// Content Size: Width (Fill), Height (Variable)
245
const contentSize = new Dimension(contentWidth, height - titleSize.height - headerSize.height - footerSize.height);
246
247
// Content
248
if (this.contentArea) {
249
size(this.contentArea, contentSize.width, contentSize.height);
250
}
251
252
return { headerSize, titleSize, contentSize, footerSize };
253
}
254
255
setFooterVisibility(visible: boolean): void {
256
this.footerVisible = visible;
257
}
258
259
setHeaderVisibility(visible: boolean): void {
260
this.headerVisible = visible;
261
}
262
}
263
264
export interface IMultiWindowPart {
265
readonly element: HTMLElement;
266
}
267
268
export abstract class MultiWindowParts<T extends IMultiWindowPart, MementoType extends object = object> extends Component<MementoType> {
269
270
protected readonly _parts = new Set<T>();
271
get parts() { return Array.from(this._parts); }
272
273
abstract readonly mainPart: T;
274
275
registerPart(part: T): IDisposable {
276
this._parts.add(part);
277
278
return toDisposable(() => this.unregisterPart(part));
279
}
280
281
protected unregisterPart(part: T): void {
282
this._parts.delete(part);
283
}
284
285
getPart(container: HTMLElement): T {
286
return this.getPartByDocument(container.ownerDocument);
287
}
288
289
protected getPartByDocument(document: Document): T {
290
if (this._parts.size > 1) {
291
for (const part of this._parts) {
292
if (part.element?.ownerDocument === document) {
293
return part;
294
}
295
}
296
}
297
298
return this.mainPart;
299
}
300
301
get activePart(): T {
302
return this.getPartByDocument(getActiveDocument());
303
}
304
}
305
306