Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/browser/part.ts
3294 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 extends Component 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
private titleArea: HTMLElement | undefined;
47
private 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
* Returns the title area container.
103
*/
104
protected getTitleArea(): HTMLElement | undefined {
105
return this.titleArea;
106
}
107
108
/**
109
* Subclasses override to provide a content area implementation.
110
*/
111
protected createContentArea(parent: HTMLElement, options?: object): HTMLElement | undefined {
112
return undefined;
113
}
114
115
/**
116
* Returns the content area container.
117
*/
118
protected getContentArea(): HTMLElement | undefined {
119
return this.contentArea;
120
}
121
122
/**
123
* Sets the header area
124
*/
125
protected setHeaderArea(headerContainer: HTMLElement): void {
126
if (this.headerArea) {
127
throw new Error('Header already exists');
128
}
129
130
if (!this.parent || !this.titleArea) {
131
return;
132
}
133
134
prepend(this.parent, headerContainer);
135
headerContainer.classList.add('header-or-footer');
136
headerContainer.classList.add('header');
137
138
this.headerArea = headerContainer;
139
this.partLayout?.setHeaderVisibility(true);
140
this.relayout();
141
}
142
143
/**
144
* Sets the footer area
145
*/
146
protected setFooterArea(footerContainer: HTMLElement): void {
147
if (this.footerArea) {
148
throw new Error('Footer already exists');
149
}
150
151
if (!this.parent || !this.titleArea) {
152
return;
153
}
154
155
this.parent.appendChild(footerContainer);
156
footerContainer.classList.add('header-or-footer');
157
footerContainer.classList.add('footer');
158
159
this.footerArea = footerContainer;
160
this.partLayout?.setFooterVisibility(true);
161
this.relayout();
162
}
163
164
/**
165
* removes the header area
166
*/
167
protected removeHeaderArea(): void {
168
if (this.headerArea) {
169
this.headerArea.remove();
170
this.headerArea = undefined;
171
this.partLayout?.setHeaderVisibility(false);
172
this.relayout();
173
}
174
}
175
176
/**
177
* removes the footer area
178
*/
179
protected removeFooterArea(): void {
180
if (this.footerArea) {
181
this.footerArea.remove();
182
this.footerArea = undefined;
183
this.partLayout?.setFooterVisibility(false);
184
this.relayout();
185
}
186
}
187
188
private relayout() {
189
if (this.dimension && this.contentPosition) {
190
this.layout(this.dimension.width, this.dimension.height, this.contentPosition.top, this.contentPosition.left);
191
}
192
}
193
/**
194
* Layout title and content area in the given dimension.
195
*/
196
protected layoutContents(width: number, height: number): ILayoutContentResult {
197
const partLayout = assertReturnsDefined(this.partLayout);
198
199
return partLayout.layout(width, height);
200
}
201
202
//#region ISerializableView
203
204
protected _onDidChange = this._register(new Emitter<IViewSize | undefined>());
205
get onDidChange(): Event<IViewSize | undefined> { return this._onDidChange.event; }
206
207
element!: HTMLElement;
208
209
abstract minimumWidth: number;
210
abstract maximumWidth: number;
211
abstract minimumHeight: number;
212
abstract maximumHeight: number;
213
214
layout(width: number, height: number, top: number, left: number): void {
215
this._dimension = new Dimension(width, height);
216
this._contentPosition = { top, left };
217
}
218
219
setVisible(visible: boolean) {
220
this._onDidVisibilityChange.fire(visible);
221
}
222
223
abstract toJSON(): object;
224
225
//#endregion
226
}
227
228
class PartLayout {
229
230
private static readonly HEADER_HEIGHT = 35;
231
private static readonly TITLE_HEIGHT = 35;
232
private static readonly Footer_HEIGHT = 35;
233
234
private headerVisible: boolean = false;
235
private footerVisible: boolean = false;
236
237
constructor(private options: IPartOptions, private contentArea: HTMLElement | undefined) { }
238
239
layout(width: number, height: number): ILayoutContentResult {
240
241
// Title Size: Width (Fill), Height (Variable)
242
let titleSize: Dimension;
243
if (this.options.hasTitle) {
244
titleSize = new Dimension(width, Math.min(height, PartLayout.TITLE_HEIGHT));
245
} else {
246
titleSize = Dimension.None;
247
}
248
249
// Header Size: Width (Fill), Height (Variable)
250
let headerSize: Dimension;
251
if (this.headerVisible) {
252
headerSize = new Dimension(width, Math.min(height, PartLayout.HEADER_HEIGHT));
253
} else {
254
headerSize = Dimension.None;
255
}
256
257
// Footer Size: Width (Fill), Height (Variable)
258
let footerSize: Dimension;
259
if (this.footerVisible) {
260
footerSize = new Dimension(width, Math.min(height, PartLayout.Footer_HEIGHT));
261
} else {
262
footerSize = Dimension.None;
263
}
264
265
let contentWidth = width;
266
if (this.options && typeof this.options.borderWidth === 'function') {
267
contentWidth -= this.options.borderWidth(); // adjust for border size
268
}
269
270
// Content Size: Width (Fill), Height (Variable)
271
const contentSize = new Dimension(contentWidth, height - titleSize.height - headerSize.height - footerSize.height);
272
273
// Content
274
if (this.contentArea) {
275
size(this.contentArea, contentSize.width, contentSize.height);
276
}
277
278
return { headerSize, titleSize, contentSize, footerSize };
279
}
280
281
setFooterVisibility(visible: boolean): void {
282
this.footerVisible = visible;
283
}
284
285
setHeaderVisibility(visible: boolean): void {
286
this.headerVisible = visible;
287
}
288
}
289
290
export interface IMultiWindowPart {
291
readonly element: HTMLElement;
292
}
293
294
export abstract class MultiWindowParts<T extends IMultiWindowPart> extends Component {
295
296
protected readonly _parts = new Set<T>();
297
get parts() { return Array.from(this._parts); }
298
299
abstract readonly mainPart: T;
300
301
registerPart(part: T): IDisposable {
302
this._parts.add(part);
303
304
return toDisposable(() => this.unregisterPart(part));
305
}
306
307
protected unregisterPart(part: T): void {
308
this._parts.delete(part);
309
}
310
311
getPart(container: HTMLElement): T {
312
return this.getPartByDocument(container.ownerDocument);
313
}
314
315
protected getPartByDocument(document: Document): T {
316
if (this._parts.size > 1) {
317
for (const part of this._parts) {
318
if (part.element?.ownerDocument === document) {
319
return part;
320
}
321
}
322
}
323
324
return this.mainPart;
325
}
326
327
get activePart(): T {
328
return this.getPartByDocument(getActiveDocument());
329
}
330
}
331
332