Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.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 './overlayWidgets.css';
7
import { FastDomNode, createFastDomNode } from '../../../../base/browser/fastDomNode.js';
8
import { IOverlayWidget, IOverlayWidgetPosition, IOverlayWidgetPositionCoordinates, OverlayWidgetPositionPreference } from '../../editorBrowser.js';
9
import { PartFingerprint, PartFingerprints, ViewPart } from '../../view/viewPart.js';
10
import { RenderingContext, RestrictedRenderingContext } from '../../view/renderingContext.js';
11
import { ViewContext } from '../../../common/viewModel/viewContext.js';
12
import * as viewEvents from '../../../common/viewEvents.js';
13
import { EditorOption } from '../../../common/config/editorOptions.js';
14
import * as dom from '../../../../base/browser/dom.js';
15
16
17
interface IWidgetData {
18
widget: IOverlayWidget;
19
preference: OverlayWidgetPositionPreference | IOverlayWidgetPositionCoordinates | null;
20
stack?: number;
21
domNode: FastDomNode<HTMLElement>;
22
}
23
24
interface IWidgetMap {
25
[key: string]: IWidgetData;
26
}
27
28
/*
29
* This view part for rendering the overlay widgets, which are
30
* floating widgets positioned based on the editor's viewport,
31
* such as the find widget.
32
*/
33
export class ViewOverlayWidgets extends ViewPart {
34
35
private readonly _viewDomNode: FastDomNode<HTMLElement>;
36
private _widgets: IWidgetMap;
37
private _viewDomNodeRect: dom.IDomNodePagePosition;
38
private readonly _domNode: FastDomNode<HTMLElement>;
39
public readonly overflowingOverlayWidgetsDomNode: FastDomNode<HTMLElement>;
40
private _verticalScrollbarWidth: number;
41
private _minimapWidth: number;
42
private _horizontalScrollbarHeight: number;
43
private _editorHeight: number;
44
private _editorWidth: number;
45
46
constructor(context: ViewContext, viewDomNode: FastDomNode<HTMLElement>) {
47
super(context);
48
this._viewDomNode = viewDomNode;
49
50
const options = this._context.configuration.options;
51
const layoutInfo = options.get(EditorOption.layoutInfo);
52
53
this._widgets = {};
54
this._verticalScrollbarWidth = layoutInfo.verticalScrollbarWidth;
55
this._minimapWidth = layoutInfo.minimap.minimapWidth;
56
this._horizontalScrollbarHeight = layoutInfo.horizontalScrollbarHeight;
57
this._editorHeight = layoutInfo.height;
58
this._editorWidth = layoutInfo.width;
59
this._viewDomNodeRect = { top: 0, left: 0, width: 0, height: 0 };
60
61
this._domNode = createFastDomNode(document.createElement('div'));
62
PartFingerprints.write(this._domNode, PartFingerprint.OverlayWidgets);
63
this._domNode.setClassName('overlayWidgets');
64
65
this.overflowingOverlayWidgetsDomNode = createFastDomNode(document.createElement('div'));
66
PartFingerprints.write(this.overflowingOverlayWidgetsDomNode, PartFingerprint.OverflowingOverlayWidgets);
67
this.overflowingOverlayWidgetsDomNode.setClassName('overflowingOverlayWidgets');
68
}
69
70
public override dispose(): void {
71
super.dispose();
72
this._widgets = {};
73
}
74
75
public getDomNode(): FastDomNode<HTMLElement> {
76
return this._domNode;
77
}
78
79
// ---- begin view event handlers
80
81
public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
82
const options = this._context.configuration.options;
83
const layoutInfo = options.get(EditorOption.layoutInfo);
84
85
this._verticalScrollbarWidth = layoutInfo.verticalScrollbarWidth;
86
this._minimapWidth = layoutInfo.minimap.minimapWidth;
87
this._horizontalScrollbarHeight = layoutInfo.horizontalScrollbarHeight;
88
this._editorHeight = layoutInfo.height;
89
this._editorWidth = layoutInfo.width;
90
return true;
91
}
92
93
// ---- end view event handlers
94
95
private _widgetCanOverflow(widget: IOverlayWidget): boolean {
96
const options = this._context.configuration.options;
97
const allowOverflow = options.get(EditorOption.allowOverflow);
98
return (widget.allowEditorOverflow || false) && allowOverflow;
99
}
100
101
public addWidget(widget: IOverlayWidget): void {
102
const domNode = createFastDomNode(widget.getDomNode());
103
104
this._widgets[widget.getId()] = {
105
widget: widget,
106
preference: null,
107
domNode: domNode
108
};
109
110
// This is sync because a widget wants to be in the dom
111
domNode.setPosition('absolute');
112
domNode.setAttribute('widgetId', widget.getId());
113
114
if (this._widgetCanOverflow(widget)) {
115
this.overflowingOverlayWidgetsDomNode.appendChild(domNode);
116
} else {
117
this._domNode.appendChild(domNode);
118
}
119
120
this.setShouldRender();
121
this._updateMaxMinWidth();
122
}
123
124
public setWidgetPosition(widget: IOverlayWidget, position: IOverlayWidgetPosition | null): boolean {
125
const widgetData = this._widgets[widget.getId()];
126
const preference = position ? position.preference : null;
127
const stack = position?.stackOridinal;
128
if (widgetData.preference === preference && widgetData.stack === stack) {
129
this._updateMaxMinWidth();
130
return false;
131
}
132
133
widgetData.preference = preference;
134
widgetData.stack = stack;
135
this.setShouldRender();
136
this._updateMaxMinWidth();
137
138
return true;
139
}
140
141
public removeWidget(widget: IOverlayWidget): void {
142
const widgetId = widget.getId();
143
if (this._widgets.hasOwnProperty(widgetId)) {
144
const widgetData = this._widgets[widgetId];
145
const domNode = widgetData.domNode.domNode;
146
delete this._widgets[widgetId];
147
148
domNode.remove();
149
this.setShouldRender();
150
this._updateMaxMinWidth();
151
}
152
}
153
154
private _updateMaxMinWidth(): void {
155
let maxMinWidth = 0;
156
const keys = Object.keys(this._widgets);
157
for (let i = 0, len = keys.length; i < len; i++) {
158
const widgetId = keys[i];
159
const widget = this._widgets[widgetId];
160
const widgetMinWidthInPx = widget.widget.getMinContentWidthInPx?.();
161
if (typeof widgetMinWidthInPx !== 'undefined') {
162
maxMinWidth = Math.max(maxMinWidth, widgetMinWidthInPx);
163
}
164
}
165
this._context.viewLayout.setOverlayWidgetsMinWidth(maxMinWidth);
166
}
167
168
private _renderWidget(widgetData: IWidgetData, stackCoordinates: number[]): void {
169
const domNode = widgetData.domNode;
170
171
if (widgetData.preference === null) {
172
domNode.setTop('');
173
return;
174
}
175
176
const maxRight = (2 * this._verticalScrollbarWidth) + this._minimapWidth;
177
if (widgetData.preference === OverlayWidgetPositionPreference.TOP_RIGHT_CORNER || widgetData.preference === OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER) {
178
if (widgetData.preference === OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER) {
179
const widgetHeight = domNode.domNode.clientHeight;
180
domNode.setTop((this._editorHeight - widgetHeight - 2 * this._horizontalScrollbarHeight));
181
} else {
182
domNode.setTop(0);
183
}
184
185
if (widgetData.stack !== undefined) {
186
domNode.setTop(stackCoordinates[widgetData.preference]);
187
stackCoordinates[widgetData.preference] += domNode.domNode.clientWidth;
188
} else {
189
domNode.setRight(maxRight);
190
}
191
} else if (widgetData.preference === OverlayWidgetPositionPreference.TOP_CENTER) {
192
domNode.domNode.style.right = '50%';
193
if (widgetData.stack !== undefined) {
194
domNode.setTop(stackCoordinates[OverlayWidgetPositionPreference.TOP_CENTER]);
195
stackCoordinates[OverlayWidgetPositionPreference.TOP_CENTER] += domNode.domNode.clientHeight;
196
} else {
197
domNode.setTop(0);
198
}
199
} else {
200
const { top, left } = widgetData.preference;
201
const fixedOverflowWidgets = this._context.configuration.options.get(EditorOption.fixedOverflowWidgets);
202
if (fixedOverflowWidgets && this._widgetCanOverflow(widgetData.widget)) {
203
// top, left are computed relative to the editor and we need them relative to the page
204
const editorBoundingBox = this._viewDomNodeRect;
205
domNode.setTop(top + editorBoundingBox.top);
206
domNode.setLeft(left + editorBoundingBox.left);
207
domNode.setPosition('fixed');
208
209
} else {
210
domNode.setTop(top);
211
domNode.setLeft(left);
212
domNode.setPosition('absolute');
213
}
214
}
215
}
216
217
public prepareRender(ctx: RenderingContext): void {
218
this._viewDomNodeRect = dom.getDomNodePagePosition(this._viewDomNode.domNode);
219
}
220
221
public render(ctx: RestrictedRenderingContext): void {
222
this._domNode.setWidth(this._editorWidth);
223
224
const keys = Object.keys(this._widgets);
225
const stackCoordinates = Array.from({ length: OverlayWidgetPositionPreference.TOP_CENTER + 1 }, () => 0);
226
keys.sort((a, b) => (this._widgets[a].stack || 0) - (this._widgets[b].stack || 0));
227
228
for (let i = 0, len = keys.length; i < len; i++) {
229
const widgetId = keys[i];
230
this._renderWidget(this._widgets[widgetId], stackCoordinates);
231
}
232
}
233
}
234
235