Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/actions/browser/floatingMenu.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 { $, append, clearNode } from '../../../base/browser/dom.js';
7
import { Widget } from '../../../base/browser/ui/widget.js';
8
import { IAction } from '../../../base/common/actions.js';
9
import { Emitter } from '../../../base/common/event.js';
10
import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';
11
import { getFlatActionBarActions } from './menuEntryActionViewItem.js';
12
import { IMenu, IMenuService, MenuId } from '../common/actions.js';
13
import { IContextKeyService } from '../../contextkey/common/contextkey.js';
14
import { IInstantiationService } from '../../instantiation/common/instantiation.js';
15
import { asCssVariable, asCssVariableWithDefault, buttonBackground, buttonForeground, contrastBorder, editorBackground, editorForeground } from '../../theme/common/colorRegistry.js';
16
17
export class FloatingClickWidget extends Widget {
18
19
private readonly _onClick = this._register(new Emitter<void>());
20
readonly onClick = this._onClick.event;
21
22
private _domNode: HTMLElement;
23
24
constructor(private label: string) {
25
super();
26
27
this._domNode = $('.floating-click-widget');
28
this._domNode.style.padding = '6px 11px';
29
this._domNode.style.borderRadius = '2px';
30
this._domNode.style.cursor = 'pointer';
31
this._domNode.style.zIndex = '1';
32
}
33
34
getDomNode(): HTMLElement {
35
return this._domNode;
36
}
37
38
render() {
39
clearNode(this._domNode);
40
this._domNode.style.backgroundColor = asCssVariableWithDefault(buttonBackground, asCssVariable(editorBackground));
41
this._domNode.style.color = asCssVariableWithDefault(buttonForeground, asCssVariable(editorForeground));
42
this._domNode.style.border = `1px solid ${asCssVariable(contrastBorder)}`;
43
44
append(this._domNode, $('')).textContent = this.label;
45
46
this.onclick(this._domNode, () => this._onClick.fire());
47
}
48
}
49
50
export abstract class AbstractFloatingClickMenu extends Disposable {
51
private readonly renderEmitter = new Emitter<FloatingClickWidget>();
52
protected get onDidRender() { return this.renderEmitter.event; }
53
private readonly menu: IMenu;
54
55
constructor(
56
menuId: MenuId,
57
@IMenuService menuService: IMenuService,
58
@IContextKeyService contextKeyService: IContextKeyService
59
) {
60
super();
61
this.menu = this._register(menuService.createMenu(menuId, contextKeyService));
62
}
63
64
/** Should be called in implementation constructors after they initialized */
65
protected render() {
66
const menuDisposables = this._register(new DisposableStore());
67
const renderMenuAsFloatingClickBtn = () => {
68
menuDisposables.clear();
69
if (!this.isVisible()) {
70
return;
71
}
72
const actions = getFlatActionBarActions(this.menu.getActions({ renderShortTitle: true, shouldForwardArgs: true }));
73
if (actions.length === 0) {
74
return;
75
}
76
// todo@jrieken find a way to handle N actions, like showing a context menu
77
const [first] = actions;
78
const widget = this.createWidget(first, menuDisposables);
79
menuDisposables.add(widget);
80
menuDisposables.add(widget.onClick(() => first.run(this.getActionArg())));
81
widget.render();
82
};
83
this._register(this.menu.onDidChange(renderMenuAsFloatingClickBtn));
84
renderMenuAsFloatingClickBtn();
85
}
86
87
protected abstract createWidget(action: IAction, disposables: DisposableStore): FloatingClickWidget;
88
89
protected getActionArg(): unknown {
90
return undefined;
91
}
92
93
protected isVisible() {
94
return true;
95
}
96
}
97
98
export class FloatingClickMenu extends AbstractFloatingClickMenu {
99
100
constructor(
101
private readonly options: {
102
/** Element the menu should be rendered into. */
103
container: HTMLElement;
104
/** Menu to show. If no actions are present, the button is hidden. */
105
menuId: MenuId;
106
/** Argument provided to the menu action */
107
getActionArg: () => void;
108
},
109
@IInstantiationService private readonly instantiationService: IInstantiationService,
110
@IMenuService menuService: IMenuService,
111
@IContextKeyService contextKeyService: IContextKeyService
112
) {
113
super(options.menuId, menuService, contextKeyService);
114
this.render();
115
}
116
117
protected override createWidget(action: IAction, disposable: DisposableStore): FloatingClickWidget {
118
const w = this.instantiationService.createInstance(FloatingClickWidget, action.label);
119
const node = w.getDomNode();
120
this.options.container.appendChild(node);
121
disposable.add(toDisposable(() => node.remove()));
122
return w;
123
}
124
125
protected override getActionArg(): unknown {
126
return this.options.getActionArg();
127
}
128
}
129
130