Path: blob/main/src/vs/platform/actions/browser/floatingMenu.ts
3294 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 { $, append, clearNode } from '../../../base/browser/dom.js';6import { Widget } from '../../../base/browser/ui/widget.js';7import { IAction } from '../../../base/common/actions.js';8import { Emitter } from '../../../base/common/event.js';9import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';10import { getFlatActionBarActions } from './menuEntryActionViewItem.js';11import { IMenu, IMenuService, MenuId } from '../common/actions.js';12import { IContextKeyService } from '../../contextkey/common/contextkey.js';13import { IInstantiationService } from '../../instantiation/common/instantiation.js';14import { asCssVariable, asCssVariableWithDefault, buttonBackground, buttonForeground, contrastBorder, editorBackground, editorForeground } from '../../theme/common/colorRegistry.js';1516export class FloatingClickWidget extends Widget {1718private readonly _onClick = this._register(new Emitter<void>());19readonly onClick = this._onClick.event;2021private _domNode: HTMLElement;2223constructor(private label: string) {24super();2526this._domNode = $('.floating-click-widget');27this._domNode.style.padding = '6px 11px';28this._domNode.style.borderRadius = '2px';29this._domNode.style.cursor = 'pointer';30this._domNode.style.zIndex = '1';31}3233getDomNode(): HTMLElement {34return this._domNode;35}3637render() {38clearNode(this._domNode);39this._domNode.style.backgroundColor = asCssVariableWithDefault(buttonBackground, asCssVariable(editorBackground));40this._domNode.style.color = asCssVariableWithDefault(buttonForeground, asCssVariable(editorForeground));41this._domNode.style.border = `1px solid ${asCssVariable(contrastBorder)}`;4243append(this._domNode, $('')).textContent = this.label;4445this.onclick(this._domNode, () => this._onClick.fire());46}47}4849export abstract class AbstractFloatingClickMenu extends Disposable {50private readonly renderEmitter = new Emitter<FloatingClickWidget>();51protected get onDidRender() { return this.renderEmitter.event; }52private readonly menu: IMenu;5354constructor(55menuId: MenuId,56@IMenuService menuService: IMenuService,57@IContextKeyService contextKeyService: IContextKeyService58) {59super();60this.menu = this._register(menuService.createMenu(menuId, contextKeyService));61}6263/** Should be called in implementation constructors after they initialized */64protected render() {65const menuDisposables = this._register(new DisposableStore());66const renderMenuAsFloatingClickBtn = () => {67menuDisposables.clear();68if (!this.isVisible()) {69return;70}71const actions = getFlatActionBarActions(this.menu.getActions({ renderShortTitle: true, shouldForwardArgs: true }));72if (actions.length === 0) {73return;74}75// todo@jrieken find a way to handle N actions, like showing a context menu76const [first] = actions;77const widget = this.createWidget(first, menuDisposables);78menuDisposables.add(widget);79menuDisposables.add(widget.onClick(() => first.run(this.getActionArg())));80widget.render();81};82this._register(this.menu.onDidChange(renderMenuAsFloatingClickBtn));83renderMenuAsFloatingClickBtn();84}8586protected abstract createWidget(action: IAction, disposables: DisposableStore): FloatingClickWidget;8788protected getActionArg(): unknown {89return undefined;90}9192protected isVisible() {93return true;94}95}9697export class FloatingClickMenu extends AbstractFloatingClickMenu {9899constructor(100private readonly options: {101/** Element the menu should be rendered into. */102container: HTMLElement;103/** Menu to show. If no actions are present, the button is hidden. */104menuId: MenuId;105/** Argument provided to the menu action */106getActionArg: () => void;107},108@IInstantiationService private readonly instantiationService: IInstantiationService,109@IMenuService menuService: IMenuService,110@IContextKeyService contextKeyService: IContextKeyService111) {112super(options.menuId, menuService, contextKeyService);113this.render();114}115116protected override createWidget(action: IAction, disposable: DisposableStore): FloatingClickWidget {117const w = this.instantiationService.createInstance(FloatingClickWidget, action.label);118const node = w.getDomNode();119this.options.container.appendChild(node);120disposable.add(toDisposable(() => node.remove()));121return w;122}123124protected override getActionArg(): unknown {125return this.options.getActionArg();126}127}128129130