提交 37da787e 编写于 作者: S Sandeep Somavarapu

Implement toggle aware icons and labels in command actions

上级 849aadb7
......@@ -22,7 +22,7 @@ import { INativeWindowConfiguration } from 'vs/platform/windows/node/window';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { ISerializableMenuItemAction } from 'vs/platform/actions/common/actions';
import * as perf from 'vs/base/common/performance';
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/common/extensionGalleryService';
import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
......@@ -1094,7 +1094,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
}
updateTouchBar(groups: ISerializableCommandAction[][]): void {
updateTouchBar(groups: ISerializableMenuItemAction[][]): void {
if (!isMacintosh) {
return; // only supported on macOS
}
......@@ -1123,10 +1123,10 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this._win.setTouchBar(new TouchBar({ items: this.touchBarGroups }));
}
private createTouchBarGroup(items: ISerializableCommandAction[] = []): TouchBarSegmentedControl {
private createTouchBarGroup(): TouchBarSegmentedControl {
// Group Segments
const segments = this.createTouchBarGroupSegments(items);
const segments = this.createTouchBarGroupSegments();
// Group Control
const control = new TouchBar.TouchBarSegmentedControl({
......@@ -1141,7 +1141,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
return control;
}
private createTouchBarGroupSegments(items: ISerializableCommandAction[] = []): ITouchBarSegment[] {
private createTouchBarGroupSegments(items: ISerializableMenuItemAction[] = []): ITouchBarSegment[] {
const segments: ITouchBarSegment[] = items.map(item => {
let icon: NativeImage | undefined;
if (item.icon && !ThemeIcon.isThemeIcon(item.icon) && item.icon?.dark?.scheme === 'file') {
......
......@@ -12,7 +12,7 @@ import { IdGenerator } from 'vs/base/common/idGenerator';
import { IDisposable, toDisposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { localize } from 'vs/nls';
import { ICommandAction, IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INotificationService } from 'vs/platform/notification/common/notification';
......@@ -148,7 +148,7 @@ export class MenuEntryActionViewItem extends ActionViewItem {
@INotificationService protected _notificationService: INotificationService,
@IContextMenuService _contextMenuService: IContextMenuService
) {
super(undefined, _action, { icon: !!(_action.class || _action.item.icon), label: !_action.class && !_action.item.icon });
super(undefined, _action, { icon: !!(_action.class || _action.icon), label: !_action.class && !_action.icon });
this._altKey = AlternativeKeyEmitter.getInstance(_contextMenuService);
}
......@@ -171,7 +171,7 @@ export class MenuEntryActionViewItem extends ActionViewItem {
render(container: HTMLElement): void {
super.render(container);
this._updateItemClass(this._action.item);
this._updateItemClass(this._action);
let mouseOver = false;
......@@ -226,15 +226,15 @@ export class MenuEntryActionViewItem extends ActionViewItem {
if (this.options.icon) {
if (this._commandAction !== this._action) {
if (this._action.alt) {
this._updateItemClass(this._action.alt.item);
this._updateItemClass(this._action.alt);
}
} else if ((<MenuItemAction>this._action).alt) {
this._updateItemClass(this._action.item);
this._updateItemClass(this._action);
}
}
}
_updateItemClass(item: ICommandAction): void {
_updateItemClass(item: MenuItemAction): void {
this._itemClassDispose.value = undefined;
if (ThemeIcon.isThemeIcon(item.icon)) {
......
......@@ -20,16 +20,37 @@ export interface ILocalizedString {
original: string;
}
export type Icon = { dark?: URI; light?: URI; } | ThemeIcon;
export type ToggleAwareIcon = { toggled?: Icon, untoggled?: Icon };
export type ToggleAwareTitle = { toggled?: string | ILocalizedString, untoggled?: string | ILocalizedString };
export interface ICommandAction {
id: string;
title: string | ILocalizedString;
title: string | ILocalizedString | ToggleAwareTitle;
category?: string | ILocalizedString;
icon?: { dark?: URI; light?: URI; } | ThemeIcon;
icon?: Icon | ToggleAwareIcon;
precondition?: ContextKeyExpression;
toggled?: ContextKeyExpression;
}
export type ISerializableCommandAction = UriDto<ICommandAction>;
export function isToggleAwareTitle(thing: unknown): thing is ToggleAwareTitle {
return thing && typeof thing === 'object'
&& ((typeof (thing as ToggleAwareTitle).toggled === 'string' || typeof (thing as ToggleAwareTitle).toggled === 'object')
|| (typeof (thing as ToggleAwareTitle).untoggled === 'string' || typeof (thing as ToggleAwareTitle).untoggled === 'object'));
}
export function isIcon(thing: unknown): thing is Icon {
if (ThemeIcon.isThemeIcon(thing)) {
return true;
}
return thing && typeof thing === 'object'
&& ((thing as { dark?: URI, light?: URI }).dark instanceof URI || (thing as { dark?: URI, light?: URI }).light instanceof URI);
}
export function isToggleAwareIcon(thing: unknown): thing is ToggleAwareIcon {
return thing && typeof thing === 'object'
&& (isIcon((thing as ToggleAwareIcon).toggled) || isIcon((thing as ToggleAwareIcon).untoggled));
}
export interface IMenuItem {
command: ICommandAction;
......@@ -260,9 +281,18 @@ export class SubmenuItemAction extends Action {
}
}
export type ISerializableMenuItemAction = UriDto<{
id: string;
title: string | ILocalizedString;
category: string | ILocalizedString | undefined;
icon: Icon | undefined;
}>;
export class MenuItemAction extends ExecuteCommandAction {
readonly item: ICommandAction;
readonly title: string | ILocalizedString;
readonly category: string | ILocalizedString | undefined;
readonly icon: Icon | undefined;
readonly alt: MenuItemAction | undefined;
private _options: IMenuActionOptions;
......@@ -274,14 +304,17 @@ export class MenuItemAction extends ExecuteCommandAction {
@IContextKeyService contextKeyService: IContextKeyService,
@ICommandService commandService: ICommandService
) {
typeof item.title === 'string' ? super(item.id, item.title, commandService) : super(item.id, item.title.value, commandService);
super(item.id, '', commandService);
this.title = (isToggleAwareTitle(item.title) ? this._checked ? item.title.toggled : item.title.untoggled : item.title) || '';
this._label = typeof this.title === 'string' ? this.title : this.title.value;
this._cssClass = undefined;
this._enabled = !item.precondition || contextKeyService.contextMatchesRules(item.precondition);
this._checked = Boolean(item.toggled && contextKeyService.contextMatchesRules(item.toggled));
this.category = item.category;
this.icon = isToggleAwareIcon(item.icon) ? this.checked ? item.icon.toggled : item.icon.untoggled : item.icon;
this._options = options || {};
this.item = item;
this.alt = alt ? new MenuItemAction(alt, undefined, this._options, contextKeyService, commandService) : undefined;
}
......@@ -305,6 +338,15 @@ export class MenuItemAction extends ExecuteCommandAction {
return super.run(...runArgs);
}
serialize(): ISerializableMenuItemAction {
return {
id: this.id,
title: this.title,
category: this.category,
icon: this.icon
};
}
}
export class SyncActionDescriptor {
......
......@@ -12,7 +12,7 @@ import { IOpenedWindow, OpenContext, IWindowOpenable, IOpenEmptyWindowOptions }
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs';
import { isMacintosh } from 'vs/base/common/platform';
import { IElectronService } from 'vs/platform/electron/node/electron';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { ISerializableMenuItemAction } from 'vs/platform/actions/common/actions';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { AddFirstParameterToFunctions } from 'vs/base/common/types';
import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs';
......@@ -279,7 +279,7 @@ export class ElectronMainService implements IElectronMainService {
return true;
}
async updateTouchBar(windowId: number | undefined, items: ISerializableCommandAction[][]): Promise<void> {
async updateTouchBar(windowId: number | undefined, items: ISerializableMenuItemAction[][]): Promise<void> {
const window = this.windowById(windowId);
if (window) {
window.updateTouchBar(items);
......
......@@ -8,7 +8,7 @@ import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDial
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWindowOpenable, IOpenEmptyWindowOptions, IOpenedWindow } from 'vs/platform/windows/common/windows';
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { ISerializableMenuItemAction } from 'vs/platform/actions/common/actions';
import { INativeOpenWindowOptions } from 'vs/platform/windows/node/window';
export const IElectronService = createDecorator<IElectronService>('electronService');
......@@ -60,7 +60,7 @@ export interface IElectronService {
setRepresentedFilename(path: string): Promise<void>;
setDocumentEdited(edited: boolean): Promise<void>;
openExternal(url: string): Promise<boolean>;
updateTouchBar(items: ISerializableCommandAction[][]): Promise<void>;
updateTouchBar(items: ISerializableMenuItemAction[][]): Promise<void>;
// macOS Touchbar
newWindowTab(): Promise<void>;
......
......@@ -10,7 +10,7 @@ import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { ISerializableMenuItemAction } from 'vs/platform/actions/common/actions';
import { URI } from 'vs/base/common/uri';
import { Rectangle, BrowserWindow } from 'electron';
import { IDisposable } from 'vs/base/common/lifecycle';
......@@ -82,7 +82,7 @@ export interface ICodeWindow extends IDisposable {
handleTitleDoubleClick(): void;
updateTouchBar(items: ISerializableCommandAction[][]): void;
updateTouchBar(items: ISerializableMenuItemAction[][]): void;
serializeWindowState(): IWindowState;
}
......
......@@ -569,10 +569,10 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable {
const entries: ActionCommandEntry[] = [];
for (let action of actions) {
const title = typeof action.item.title === 'string' ? action.item.title : action.item.title.value;
const title = typeof action.title === 'string' ? action.title : action.title.value;
let category, label = title;
if (action.item.category) {
category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value;
if (action.category) {
category = typeof action.category === 'string' ? action.category : action.category.value;
label = localize('cat.title', "{0}: {1}", category, title);
}
......@@ -580,8 +580,8 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable {
const labelHighlights = wordFilter(searchValue, label);
// Add an 'alias' in original language when running in different locale
const aliasTitle = (!Language.isDefaultVariant() && typeof action.item.title !== 'string') ? action.item.title.original : undefined;
const aliasCategory = (!Language.isDefaultVariant() && category && action.item.category && typeof action.item.category !== 'string') ? action.item.category.original : undefined;
const aliasTitle = (!Language.isDefaultVariant() && typeof action.title !== 'string') ? action.title.original : undefined;
const aliasCategory = (!Language.isDefaultVariant() && category && action.category && typeof action.category !== 'string') ? action.category.original : undefined;
let alias;
if (aliasTitle && category) {
alias = aliasCategory ? `${aliasCategory}: ${aliasTitle}` : `${category}: ${aliasTitle}`;
......@@ -591,7 +591,7 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable {
const aliasHighlights = alias ? wordFilter(searchValue, alias) : null;
if (labelHighlights || aliasHighlights) {
entries.push(this.instantiationService.createInstance(ActionCommandEntry, action.id, this.keybindingService.lookupKeybinding(action.item.id), label, alias, { label: labelHighlights, alias: aliasHighlights }, action, (id: string) => this.onBeforeRunCommand(id)));
entries.push(this.instantiationService.createInstance(ActionCommandEntry, action.id, this.keybindingService.lookupKeybinding(action.id), label, alias, { label: labelHighlights, alias: aliasHighlights }, action, (id: string) => this.onBeforeRunCommand(id)));
}
}
}
......
......@@ -200,14 +200,14 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc
}
for (let action of actionGroup[1]) {
if (action instanceof MenuItemAction) {
let label = typeof action.item.title === 'string' ? action.item.title : action.item.title.value;
if (action.item.category) {
const category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value;
let label = typeof action.title === 'string' ? action.title : action.title.value;
if (action.category) {
const category = typeof action.category === 'string' ? action.category : action.category.value;
label = nls.localize('cat.title', "{0}: {1}", category, label);
}
items.push({
type: 'item',
id: action.item.id,
id: action.id,
label
});
}
......
......@@ -23,7 +23,7 @@ import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService';
import { ipcRenderer as ipc, webFrame, crashReporter, CrashReporterStartOptions, Event as IpcEvent } from 'electron';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions';
import { IMenuService, MenuId, IMenu, MenuItemAction, SubmenuItemAction, MenuRegistry, ISerializableMenuItemAction } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { RunOnceScheduler } from 'vs/base/common/async';
......@@ -68,7 +68,7 @@ export class NativeWindow extends Disposable {
private touchBarMenu: IMenu | undefined;
private readonly touchBarDisposables = this._register(new DisposableStore());
private lastInstalledTouchedBar: ICommandAction[][] | undefined;
private lastInstalledTouchedBar: ISerializableMenuItemAction[][] | undefined;
private readonly customTitleContextMenuDisposable = this._register(new DisposableStore());
......@@ -504,18 +504,18 @@ export class NativeWindow extends Disposable {
this.touchBarDisposables.add(createAndFillInActionBarActions(this.touchBarMenu, undefined, actions));
// Convert into command action multi array
const items: ICommandAction[][] = [];
let group: ICommandAction[] = [];
const items: ISerializableMenuItemAction[][] = [];
let group: ISerializableMenuItemAction[] = [];
if (!disabled) {
for (const action of actions) {
// Command
if (action instanceof MenuItemAction) {
if (ignoredItems.indexOf(action.item.id) >= 0) {
if (ignoredItems.indexOf(action.id) >= 0) {
continue; // ignored
}
group.push(action.item);
group.push(action.serialize());
}
// Separator
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册