diff --git a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts index 9ed8fd3c13529ba51305736a30882c8e062242ed..db045b26c13f31f396a4bf92b5173031298b6da1 100644 --- a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -13,6 +13,7 @@ import { Emitter } from 'vs/base/common/event'; import { BaseActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IActionProvider, DropdownMenu, IDropdownMenuOptions, ILabelRenderer } from 'vs/base/browser/ui/dropdown/dropdown'; import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; +import { asArray } from 'vs/base/common/arrays'; export interface IKeybindingProvider { (action: IAction): ResolvedKeybinding | undefined; @@ -26,7 +27,7 @@ export interface IDropdownMenuActionViewItemOptions extends IBaseActionViewItemO readonly actionViewItemProvider?: IActionViewItemProvider; readonly keybindingProvider?: IKeybindingProvider; readonly actionRunner?: IActionRunner; - readonly clazz?: string; + readonly classNames?: string[] | string; readonly anchorAlignmentProvider?: IAnchorAlignmentProvider; readonly menuAsChild?: boolean; } @@ -57,11 +58,17 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { render(container: HTMLElement): void { const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable | null => { - this.element = append(el, $('a.action-label.codicon')); // todo@aeschli: remove codicon, should come through `this.options.clazz` - if (this.options.clazz) { - addClasses(this.element, this.options.clazz); + this.element = append(el, $('a.action-label')); + + const classNames = this.options.classNames ? asArray(this.options.classNames) : []; + + // todo@aeschli: remove codicon, should come through `this.options.classNames` + if (!classNames.find(c => c === 'icon')) { + classNames.push('codicon'); } + addClasses(this.element, ...classNames); + this.element.tabIndex = 0; this.element.setAttribute('role', 'button'); this.element.setAttribute('aria-haspopup', 'true'); diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 6346bbdc4b4473bacc35b04ac316c91e774bf7d0..4e41977fb17f8ea51e32144124e240e771c9120f 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -61,49 +61,57 @@ export class ToolBar extends Disposable { ariaLabel: options.ariaLabel, actionRunner: options.actionRunner, actionViewItemProvider: (action: IAction) => { - if (action instanceof SubmenuAction) { - const actions = Array.isArray(action.actions) ? action.actions : action.actions(); - const result = new DropdownMenuActionViewItem( + if (action.id === ToggleMenuAction.ID) { + this.toggleMenuActionViewItem = new DropdownMenuActionViewItem( action, - actions, + (action).menuActions, contextMenuProvider, { actionViewItemProvider: this.options.actionViewItemProvider, actionRunner: this.actionRunner, keybindingProvider: this.options.getKeyBinding, - clazz: action.class, + classNames: toolBarMoreIcon.classNames, anchorAlignmentProvider: this.options.anchorAlignmentProvider, menuAsChild: true } ); - result.setActionContext(this.actionBar.context); - this.submenuActionViewItems.push(result); - this.disposables.add(this._onDidChangeDropdownVisibility.add(result.onDidChangeVisibility)); + this.toggleMenuActionViewItem.setActionContext(this.actionBar.context); + this.disposables.add(this._onDidChangeDropdownVisibility.add(this.toggleMenuActionViewItem.onDidChangeVisibility)); - return result; + return this.toggleMenuActionViewItem; } - if (action.id === ToggleMenuAction.ID) { - this.toggleMenuActionViewItem = new DropdownMenuActionViewItem( + if (options.actionViewItemProvider) { + const result = options.actionViewItemProvider(action); + + if (result) { + return result; + } + } + + if (action instanceof SubmenuAction) { + const actions = Array.isArray(action.actions) ? action.actions : action.actions(); + const result = new DropdownMenuActionViewItem( action, - (action).menuActions, + actions, contextMenuProvider, { actionViewItemProvider: this.options.actionViewItemProvider, actionRunner: this.actionRunner, keybindingProvider: this.options.getKeyBinding, - clazz: toolBarMoreIcon.classNames, + classNames: action.class, anchorAlignmentProvider: this.options.anchorAlignmentProvider, menuAsChild: true } ); - this.toggleMenuActionViewItem.setActionContext(this.actionBar.context); - this.disposables.add(this._onDidChangeDropdownVisibility.add(this.toggleMenuActionViewItem.onDidChangeVisibility)); + result.setActionContext(this.actionBar.context); + this.submenuActionViewItems.push(result); + this.disposables.add(this._onDidChangeDropdownVisibility.add(result.onDidChangeVisibility)); - return this.toggleMenuActionViewItem; + return result; } - return options.actionViewItemProvider ? options.actionViewItemProvider(action) : undefined; + return undefined; } })); } diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index ba590f2fa8d2a5c26aa2be4899d950621b8dd6f2..d0867eb9d8d66b099c894d9b06889dd02a9ad051 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -17,6 +17,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; // The alternative key on all platforms is alt. On windows we also support shift as an alternative key #44136 class AlternativeKeyEmitter extends Emitter { @@ -126,9 +127,9 @@ function fillInActions(groups: ReadonlyArray<[string, ReadonlyArray(); - static readonly ICON_PATH_TO_CSS_RULES: Map = new Map(); +export class MenuEntryActionViewItem extends ActionViewItem { private _wantsAltCommand: boolean = false; private readonly _itemClassDispose = this._register(new MutableDisposable()); @@ -227,7 +228,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { } } - _updateItemClass(item: ICommandAction): void { + private _updateItemClass(item: ICommandAction): void { this._itemClassDispose.value = undefined; const icon = this._commandAction.checked && (item.toggled as { icon?: Icon })?.icon ? (item.toggled as { icon: Icon }).icon : item.icon; @@ -252,13 +253,13 @@ export class MenuEntryActionViewItem extends ActionViewItem { const iconPathMapKey = icon.dark.toString(); - if (MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { - iconClass = MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!; + if (ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { + iconClass = ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!; } else { iconClass = ids.nextId(); createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(icon.light || icon.dark)}`); createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(icon.dark)}`); - MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); + ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); } if (this.label) { @@ -274,3 +275,34 @@ export class MenuEntryActionViewItem extends ActionViewItem { } } } + +export class SubmenuEntryActionViewItem extends DropdownMenuActionViewItem { + + constructor( + action: SubmenuItemAction, + @INotificationService _notificationService: INotificationService, + @IContextMenuService _contextMenuService: IContextMenuService + ) { + const classNames: string[] = []; + + if (action.item.icon) { + if (ThemeIcon.isThemeIcon(action.item.icon)) { + classNames.push(ThemeIcon.asClassName(action.item.icon)!); + } else if (action.item.icon.dark?.scheme) { + const iconPathMapKey = action.item.icon.dark.toString(); + + if (ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { + classNames.push('icon', ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!); + } else { + const className = ids.nextId(); + classNames.push('icon', className); + createCSSRule(`.icon.${className}`, `background-image: ${asCSSUrl(action.item.icon.light || action.item.icon.dark)}`); + createCSSRule(`.vs-dark .icon.${className}, .hc-black .icon.${className}`, `background-image: ${asCSSUrl(action.item.icon.dark)}`); + ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, className); + } + } + } + + super(action, Array.isArray(action.actions) ? action.actions : action.actions(), _contextMenuService, { classNames }); + } +} diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index b1f77d11db7891517d83b7672fef7605e8f4e090..567ec2a4a1bbb2c5441c4f3fdfb83a43b535b65d 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -211,7 +211,7 @@ export class NotificationRenderer implements IListRenderer { if (action && action instanceof ConfigureNotificationAction) { - const item = new DropdownMenuActionViewItem(action, action.configurationActions, this.contextMenuService, { actionRunner: this.actionRunner, clazz: action.class }); + const item = new DropdownMenuActionViewItem(action, action.configurationActions, this.contextMenuService, { actionRunner: this.actionRunner, classNames: action.class }); data.toDispose.add(item); return item; diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index b98bcd63b7b080f0dd12b59f98e798e6ad4425b9..a95e5b57d2ebcd07cea3e9a2a42358abbbb68f7a 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -158,7 +158,7 @@ export class CommentNode extends Disposable { { actionViewItemProvider: action => this.actionViewItemProvider(action as Action), actionRunner: this.actionRunner, - clazz: 'toolbar-toggle-pickReactions codicon codicon-reactions', + classNames: ['toolbar-toggle-pickReactions', 'codicon', 'codicon-reactions'], anchorAlignmentProvider: () => AnchorAlignment.RIGHT } ); @@ -267,7 +267,7 @@ export class CommentNode extends Disposable { return this.actionViewItemProvider(action as Action); }, actionRunner: this.actionRunner, - clazz: 'toolbar-toggle-pickReactions', + classNames: 'toolbar-toggle-pickReactions', anchorAlignmentProvider: () => AnchorAlignment.RIGHT } ); @@ -287,7 +287,7 @@ export class CommentNode extends Disposable { { actionViewItemProvider: action => this.actionViewItemProvider(action as Action), actionRunner: this.actionRunner, - clazz: 'toolbar-toggle-pickReactions', + classNames: 'toolbar-toggle-pickReactions', anchorAlignmentProvider: () => AnchorAlignment.RIGHT } ); diff --git a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts index 2f63744cbee52d78607fdf902cca7f2b863d089f..48d031cea16d47b817dbb31d8ed6e964da90954a 100644 --- a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts @@ -182,7 +182,7 @@ class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem { contextMenuService, { actionRunner, - clazz: action.class, + classNames: action.class, anchorAlignmentProvider: () => AnchorAlignment.RIGHT } );