Merge branch 'main' into eli/workbench-motion

This commit is contained in:
Elijah King
2026-02-10 15:27:08 -08:00
committed by GitHub
134 changed files with 4398 additions and 4040 deletions

View File

@@ -7,11 +7,13 @@ import { BrowserFeatures } from '../../canIUse.js';
import * as DOM from '../../dom.js';
import { StandardMouseEvent } from '../../mouseEvent.js';
import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../common/lifecycle.js';
import { AnchorAlignment, AnchorAxisAlignment, AnchorPosition, IRect, layout2d } from '../../../common/layout.js';
import * as platform from '../../../common/platform.js';
import { Range } from '../../../common/range.js';
import { OmitOptional } from '../../../common/types.js';
import './contextview.css';
export { AnchorAlignment, AnchorAxisAlignment, AnchorPosition } from '../../../common/layout.js';
export const enum ContextViewDOMPosition {
ABSOLUTE = 1,
FIXED,
@@ -31,18 +33,6 @@ export function isAnchor(obj: unknown): obj is IAnchor | OmitOptional<IAnchor> {
return !!anchor && typeof anchor.x === 'number' && typeof anchor.y === 'number';
}
export const enum AnchorAlignment {
LEFT, RIGHT
}
export const enum AnchorPosition {
BELOW, ABOVE
}
export const enum AnchorAxisAlignment {
VERTICAL, HORIZONTAL
}
export interface IDelegate {
/**
* The anchor where to position the context view.
@@ -73,66 +63,40 @@ export interface IContextViewProvider {
layout(): void;
}
export interface IPosition {
top: number;
left: number;
}
export function getAnchorRect(anchor: HTMLElement | StandardMouseEvent | IAnchor): IRect {
// Get the element's position and size (to anchor the view)
if (DOM.isHTMLElement(anchor)) {
const elementPosition = DOM.getDomNodePagePosition(anchor);
export interface ISize {
width: number;
height: number;
}
// In areas where zoom is applied to the element or its ancestors, we need to adjust the size of the element
// e.g. The title bar has counter zoom behavior meaning it applies the inverse of zoom level.
// Window Zoom Level: 1.5, Title Bar Zoom: 1/1.5, Size Multiplier: 1.5
const zoom = DOM.getDomNodeZoomLevel(anchor);
export interface IView extends IPosition, ISize { }
export const enum LayoutAnchorPosition {
Before,
After
}
export enum LayoutAnchorMode {
AVOID,
ALIGN
}
export interface ILayoutAnchor {
offset: number;
size: number;
mode?: LayoutAnchorMode; // default: AVOID
position: LayoutAnchorPosition;
}
/**
* Lays out a one dimensional view next to an anchor in a viewport.
*
* @returns The view offset within the viewport.
*/
export function layout(viewportSize: number, viewSize: number, anchor: ILayoutAnchor): number {
const layoutAfterAnchorBoundary = anchor.mode === LayoutAnchorMode.ALIGN ? anchor.offset : anchor.offset + anchor.size;
const layoutBeforeAnchorBoundary = anchor.mode === LayoutAnchorMode.ALIGN ? anchor.offset + anchor.size : anchor.offset;
if (anchor.position === LayoutAnchorPosition.Before) {
if (viewSize <= viewportSize - layoutAfterAnchorBoundary) {
return layoutAfterAnchorBoundary; // happy case, lay it out after the anchor
}
if (viewSize <= layoutBeforeAnchorBoundary) {
return layoutBeforeAnchorBoundary - viewSize; // ok case, lay it out before the anchor
}
return Math.max(viewportSize - viewSize, 0); // sad case, lay it over the anchor
return {
top: elementPosition.top * zoom,
left: elementPosition.left * zoom,
width: elementPosition.width * zoom,
height: elementPosition.height * zoom
};
} else if (isAnchor(anchor)) {
return {
top: anchor.y,
left: anchor.x,
width: anchor.width || 1,
height: anchor.height || 2
};
} else {
if (viewSize <= layoutBeforeAnchorBoundary) {
return layoutBeforeAnchorBoundary - viewSize; // happy case, lay it out before the anchor
}
if (viewSize <= viewportSize - layoutAfterAnchorBoundary && layoutBeforeAnchorBoundary < viewSize / 2) {
return layoutAfterAnchorBoundary; // ok case, lay it out after the anchor
}
return 0; // sad case, lay it over the anchor
return {
top: anchor.posy,
left: anchor.posx,
// We are about to position the context view where the mouse
// cursor is. To prevent the view being exactly under the mouse
// when showing and thus potentially triggering an action within,
// we treat the mouse location like a small sized block element.
width: 2,
height: 2
};
}
}
@@ -270,82 +234,14 @@ export class ContextView extends Disposable {
}
// Get anchor
const anchor = this.delegate!.getAnchor();
// Compute around
let around: IView;
// Get the element's position and size (to anchor the view)
if (DOM.isHTMLElement(anchor)) {
const elementPosition = DOM.getDomNodePagePosition(anchor);
// In areas where zoom is applied to the element or its ancestors, we need to adjust the size of the element
// e.g. The title bar has counter zoom behavior meaning it applies the inverse of zoom level.
// Window Zoom Level: 1.5, Title Bar Zoom: 1/1.5, Size Multiplier: 1.5
const zoom = DOM.getDomNodeZoomLevel(anchor);
around = {
top: elementPosition.top * zoom,
left: elementPosition.left * zoom,
width: elementPosition.width * zoom,
height: elementPosition.height * zoom
};
} else if (isAnchor(anchor)) {
around = {
top: anchor.y,
left: anchor.x,
width: anchor.width || 1,
height: anchor.height || 2
};
} else {
around = {
top: anchor.posy,
left: anchor.posx,
// We are about to position the context view where the mouse
// cursor is. To prevent the view being exactly under the mouse
// when showing and thus potentially triggering an action within,
// we treat the mouse location like a small sized block element.
width: 2,
height: 2
};
}
const viewSizeWidth = DOM.getTotalWidth(this.view);
const viewSizeHeight = DOM.getTotalHeight(this.view);
const anchorPosition = this.delegate!.anchorPosition ?? AnchorPosition.BELOW;
const anchorAlignment = this.delegate!.anchorAlignment ?? AnchorAlignment.LEFT;
const anchorAxisAlignment = this.delegate!.anchorAxisAlignment ?? AnchorAxisAlignment.VERTICAL;
let top: number;
let left: number;
const anchor = getAnchorRect(this.delegate!.getAnchor());
const activeWindow = DOM.getActiveWindow();
if (anchorAxisAlignment === AnchorAxisAlignment.VERTICAL) {
const verticalAnchor: ILayoutAnchor = { offset: around.top - activeWindow.pageYOffset, size: around.height, position: anchorPosition === AnchorPosition.BELOW ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After };
const horizontalAnchor: ILayoutAnchor = { offset: around.left, size: around.width, position: anchorAlignment === AnchorAlignment.LEFT ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, mode: LayoutAnchorMode.ALIGN };
top = layout(activeWindow.innerHeight, viewSizeHeight, verticalAnchor) + activeWindow.pageYOffset;
// if view intersects vertically with anchor, we must avoid the anchor
if (Range.intersects({ start: top, end: top + viewSizeHeight }, { start: verticalAnchor.offset, end: verticalAnchor.offset + verticalAnchor.size })) {
horizontalAnchor.mode = LayoutAnchorMode.AVOID;
}
left = layout(activeWindow.innerWidth, viewSizeWidth, horizontalAnchor);
} else {
const horizontalAnchor: ILayoutAnchor = { offset: around.left, size: around.width, position: anchorAlignment === AnchorAlignment.LEFT ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After };
const verticalAnchor: ILayoutAnchor = { offset: around.top, size: around.height, position: anchorPosition === AnchorPosition.BELOW ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, mode: LayoutAnchorMode.ALIGN };
left = layout(activeWindow.innerWidth, viewSizeWidth, horizontalAnchor);
// if view intersects horizontally with anchor, we must avoid the anchor
if (Range.intersects({ start: left, end: left + viewSizeWidth }, { start: horizontalAnchor.offset, end: horizontalAnchor.offset + horizontalAnchor.size })) {
verticalAnchor.mode = LayoutAnchorMode.AVOID;
}
top = layout(activeWindow.innerHeight, viewSizeHeight, verticalAnchor) + activeWindow.pageYOffset;
}
const viewport = { top: activeWindow.pageYOffset, left: activeWindow.pageXOffset, width: activeWindow.innerWidth, height: activeWindow.innerHeight };
const view = { width: DOM.getTotalWidth(this.view), height: DOM.getTotalHeight(this.view) };
const anchorPosition = this.delegate!.anchorPosition;
const anchorAlignment = this.delegate!.anchorAlignment;
const anchorAxisAlignment = this.delegate!.anchorAxisAlignment;
const { top, left } = layout2d(viewport, view, anchor, { anchorAlignment, anchorPosition, anchorAxisAlignment });
this.view.classList.remove('top', 'bottom', 'left', 'right');
this.view.classList.add(anchorPosition === AnchorPosition.BELOW ? 'bottom' : 'top');

View File

@@ -11,7 +11,6 @@ import { StandardKeyboardEvent } from '../../keyboardEvent.js';
import { StandardMouseEvent } from '../../mouseEvent.js';
import { ActionBar, ActionsOrientation, IActionViewItemProvider } from '../actionbar/actionbar.js';
import { ActionViewItem, BaseActionViewItem, IActionViewItemOptions } from '../actionbar/actionViewItems.js';
import { AnchorAlignment, layout, LayoutAnchorPosition } from '../contextview/contextview.js';
import { DomScrollableElement } from '../scrollbar/scrollableElement.js';
import { EmptySubmenuAction, IAction, IActionRunner, Separator, SubmenuAction } from '../../../common/actions.js';
import { RunOnceScheduler } from '../../../common/async.js';
@@ -26,6 +25,7 @@ import { DisposableStore } from '../../../common/lifecycle.js';
import { isLinux, isMacintosh } from '../../../common/platform.js';
import { ScrollbarVisibility, ScrollEvent } from '../../../common/scrollable.js';
import * as strings from '../../../common/strings.js';
import { AnchorAlignment, layout, LayoutAnchorPosition } from '../../../common/layout.js';
export const MENU_MNEMONIC_REGEX = /\(&([^\s&])\)|(^|[^&])&([^\s&])/;
export const MENU_ESCAPED_MNEMONIC_REGEX = /(&amp;)?(&amp;)([^\s&])/g;
@@ -859,7 +859,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
const ret = { top: 0, left: 0 };
// Start with horizontal
ret.left = layout(windowDimensions.width, submenu.width, { position: expandDirection.horizontal === HorizontalDirection.Right ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, offset: entry.left, size: entry.width });
ret.left = layout(windowDimensions.width, submenu.width, { position: expandDirection.horizontal === HorizontalDirection.Right ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, offset: entry.left, size: entry.width }).position;
// We don't have enough room to layout the menu fully, so we are overlapping the menu
if (ret.left >= entry.left && ret.left < entry.left + entry.width) {
@@ -872,7 +872,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
}
// Now that we have a horizontal position, try layout vertically
ret.top = layout(windowDimensions.height, submenu.height, { position: LayoutAnchorPosition.Before, offset: entry.top, size: 0 });
ret.top = layout(windowDimensions.height, submenu.height, { position: LayoutAnchorPosition.Before, offset: entry.top, size: 0 }).position;
// We didn't have enough room below, but we did above, so we shift down to align the menu
if (ret.top + submenu.height === entry.top && ret.top + entry.height + submenu.height <= windowDimensions.height) {