Path: blob/main/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import './overlayWidgets.css';6import { FastDomNode, createFastDomNode } from '../../../../base/browser/fastDomNode.js';7import { IOverlayWidget, IOverlayWidgetPosition, IOverlayWidgetPositionCoordinates, OverlayWidgetPositionPreference } from '../../editorBrowser.js';8import { PartFingerprint, PartFingerprints, ViewPart } from '../../view/viewPart.js';9import { RenderingContext, RestrictedRenderingContext } from '../../view/renderingContext.js';10import { ViewContext } from '../../../common/viewModel/viewContext.js';11import * as viewEvents from '../../../common/viewEvents.js';12import { EditorOption } from '../../../common/config/editorOptions.js';13import * as dom from '../../../../base/browser/dom.js';141516interface IWidgetData {17widget: IOverlayWidget;18preference: OverlayWidgetPositionPreference | IOverlayWidgetPositionCoordinates | null;19stack?: number;20domNode: FastDomNode<HTMLElement>;21}2223interface IWidgetMap {24[key: string]: IWidgetData;25}2627/*28* This view part for rendering the overlay widgets, which are29* floating widgets positioned based on the editor's viewport,30* such as the find widget.31*/32export class ViewOverlayWidgets extends ViewPart {3334private readonly _viewDomNode: FastDomNode<HTMLElement>;35private _widgets: IWidgetMap;36private _viewDomNodeRect: dom.IDomNodePagePosition;37private readonly _domNode: FastDomNode<HTMLElement>;38public readonly overflowingOverlayWidgetsDomNode: FastDomNode<HTMLElement>;39private _verticalScrollbarWidth: number;40private _minimapWidth: number;41private _horizontalScrollbarHeight: number;42private _editorHeight: number;43private _editorWidth: number;4445constructor(context: ViewContext, viewDomNode: FastDomNode<HTMLElement>) {46super(context);47this._viewDomNode = viewDomNode;4849const options = this._context.configuration.options;50const layoutInfo = options.get(EditorOption.layoutInfo);5152this._widgets = {};53this._verticalScrollbarWidth = layoutInfo.verticalScrollbarWidth;54this._minimapWidth = layoutInfo.minimap.minimapWidth;55this._horizontalScrollbarHeight = layoutInfo.horizontalScrollbarHeight;56this._editorHeight = layoutInfo.height;57this._editorWidth = layoutInfo.width;58this._viewDomNodeRect = { top: 0, left: 0, width: 0, height: 0 };5960this._domNode = createFastDomNode(document.createElement('div'));61PartFingerprints.write(this._domNode, PartFingerprint.OverlayWidgets);62this._domNode.setClassName('overlayWidgets');6364this.overflowingOverlayWidgetsDomNode = createFastDomNode(document.createElement('div'));65PartFingerprints.write(this.overflowingOverlayWidgetsDomNode, PartFingerprint.OverflowingOverlayWidgets);66this.overflowingOverlayWidgetsDomNode.setClassName('overflowingOverlayWidgets');67}6869public override dispose(): void {70super.dispose();71this._widgets = {};72}7374public getDomNode(): FastDomNode<HTMLElement> {75return this._domNode;76}7778// ---- begin view event handlers7980public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {81const options = this._context.configuration.options;82const layoutInfo = options.get(EditorOption.layoutInfo);8384this._verticalScrollbarWidth = layoutInfo.verticalScrollbarWidth;85this._minimapWidth = layoutInfo.minimap.minimapWidth;86this._horizontalScrollbarHeight = layoutInfo.horizontalScrollbarHeight;87this._editorHeight = layoutInfo.height;88this._editorWidth = layoutInfo.width;89return true;90}9192// ---- end view event handlers9394private _widgetCanOverflow(widget: IOverlayWidget): boolean {95const options = this._context.configuration.options;96const allowOverflow = options.get(EditorOption.allowOverflow);97return (widget.allowEditorOverflow || false) && allowOverflow;98}99100public addWidget(widget: IOverlayWidget): void {101const domNode = createFastDomNode(widget.getDomNode());102103this._widgets[widget.getId()] = {104widget: widget,105preference: null,106domNode: domNode107};108109// This is sync because a widget wants to be in the dom110domNode.setPosition('absolute');111domNode.setAttribute('widgetId', widget.getId());112113if (this._widgetCanOverflow(widget)) {114this.overflowingOverlayWidgetsDomNode.appendChild(domNode);115} else {116this._domNode.appendChild(domNode);117}118119this.setShouldRender();120this._updateMaxMinWidth();121}122123public setWidgetPosition(widget: IOverlayWidget, position: IOverlayWidgetPosition | null): boolean {124const widgetData = this._widgets[widget.getId()];125const preference = position ? position.preference : null;126const stack = position?.stackOridinal;127if (widgetData.preference === preference && widgetData.stack === stack) {128this._updateMaxMinWidth();129return false;130}131132widgetData.preference = preference;133widgetData.stack = stack;134this.setShouldRender();135this._updateMaxMinWidth();136137return true;138}139140public removeWidget(widget: IOverlayWidget): void {141const widgetId = widget.getId();142if (this._widgets.hasOwnProperty(widgetId)) {143const widgetData = this._widgets[widgetId];144const domNode = widgetData.domNode.domNode;145delete this._widgets[widgetId];146147domNode.remove();148this.setShouldRender();149this._updateMaxMinWidth();150}151}152153private _updateMaxMinWidth(): void {154let maxMinWidth = 0;155const keys = Object.keys(this._widgets);156for (let i = 0, len = keys.length; i < len; i++) {157const widgetId = keys[i];158const widget = this._widgets[widgetId];159const widgetMinWidthInPx = widget.widget.getMinContentWidthInPx?.();160if (typeof widgetMinWidthInPx !== 'undefined') {161maxMinWidth = Math.max(maxMinWidth, widgetMinWidthInPx);162}163}164this._context.viewLayout.setOverlayWidgetsMinWidth(maxMinWidth);165}166167private _renderWidget(widgetData: IWidgetData, stackCoordinates: number[]): void {168const domNode = widgetData.domNode;169170if (widgetData.preference === null) {171domNode.setTop('');172return;173}174175const maxRight = (2 * this._verticalScrollbarWidth) + this._minimapWidth;176if (widgetData.preference === OverlayWidgetPositionPreference.TOP_RIGHT_CORNER || widgetData.preference === OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER) {177if (widgetData.preference === OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER) {178const widgetHeight = domNode.domNode.clientHeight;179domNode.setTop((this._editorHeight - widgetHeight - 2 * this._horizontalScrollbarHeight));180} else {181domNode.setTop(0);182}183184if (widgetData.stack !== undefined) {185domNode.setTop(stackCoordinates[widgetData.preference]);186stackCoordinates[widgetData.preference] += domNode.domNode.clientWidth;187} else {188domNode.setRight(maxRight);189}190} else if (widgetData.preference === OverlayWidgetPositionPreference.TOP_CENTER) {191domNode.domNode.style.right = '50%';192if (widgetData.stack !== undefined) {193domNode.setTop(stackCoordinates[OverlayWidgetPositionPreference.TOP_CENTER]);194stackCoordinates[OverlayWidgetPositionPreference.TOP_CENTER] += domNode.domNode.clientHeight;195} else {196domNode.setTop(0);197}198} else {199const { top, left } = widgetData.preference;200const fixedOverflowWidgets = this._context.configuration.options.get(EditorOption.fixedOverflowWidgets);201if (fixedOverflowWidgets && this._widgetCanOverflow(widgetData.widget)) {202// top, left are computed relative to the editor and we need them relative to the page203const editorBoundingBox = this._viewDomNodeRect;204domNode.setTop(top + editorBoundingBox.top);205domNode.setLeft(left + editorBoundingBox.left);206domNode.setPosition('fixed');207208} else {209domNode.setTop(top);210domNode.setLeft(left);211domNode.setPosition('absolute');212}213}214}215216public prepareRender(ctx: RenderingContext): void {217this._viewDomNodeRect = dom.getDomNodePagePosition(this._viewDomNode.domNode);218}219220public render(ctx: RestrictedRenderingContext): void {221this._domNode.setWidth(this._editorWidth);222223const keys = Object.keys(this._widgets);224const stackCoordinates = Array.from({ length: OverlayWidgetPositionPreference.TOP_CENTER + 1 }, () => 0);225keys.sort((a, b) => (this._widgets[a].stack || 0) - (this._widgets[b].stack || 0));226227for (let i = 0, len = keys.length; i < len; i++) {228const widgetId = keys[i];229this._renderWidget(this._widgets[widgetId], stackCoordinates);230}231}232}233234235