图标注册(IconRegistry)
文档:
vjcad/doc/ui/icons.md示例:vjcad/example/ui/01ribbon.js、命令 / 插件示例中频繁使用
1. 模型
vjcad 把所有图标做成"按 类别 + 名字 索引"的单例字典:
class IconRegistry { // 全部静态方法,无 getInstance
static getIcon(key: string, category?: IconCategory): string | undefined;
static registerIcon(key: string, svg: string, options?: IconRegistryOptions): boolean;
static registerIcons(icons: Record<string, string>, category: IconCategory): void;
static registerCommandIcon(commandName: string, svg: string, override?: boolean): boolean;
static getCommandIcon(commandName: string): string | undefined;
static hasIcon(key: string, category?: IconCategory): boolean;
static hasCommandIcon(commandName: string): boolean;
static getIconsByCategory(category: IconCategory): Record<string, string>;
static getIconInfo(key: string): IconInfo | undefined;
static removeIcon(key: string, category: IconCategory): boolean;
static createIconElement(key: string, category?: IconCategory, className?: string): HTMLElement | null;
}
interface IconRegistryOptions {
category?: IconCategory; // 默认 Custom
override?: boolean; // 是否覆盖已存在图标
}
enum IconCategory {
Commands = 'commands', Actbar = 'actbar', FuncButtons = 'func-buttons',
LayerDialog = 'layer-dialog', Menubar = 'menubar', Osnap = 'osnap',
Palette = 'palette', PaletteLayer = 'palette-layer', PalettePlot = 'palette-plot',
Misc = 'misc', Custom = 'custom', // 无 Toolbar/Sidebar/General;默认类别是 Custom
}
注册后并不是所有 UI 都能找到——各 UI 查找的分类不同:
| UI | 查找方式 |
|---|---|
Ribbon 按钮 icon、右键菜单 icon、菜单默认的命令图标、命令面板 |
只查 Commands 分类 |
菜单项显式 icon 字段 |
全分类搜索 |
侧栏 ActivityBar 图标、<tool-bar-button src> |
解析 images/<分类>/<名>.svg 路径,到对应分类查 |
浮动工具栏 icon |
不查注册表,只认内联 SVG 串或图片 URL |
所以给命令 / Ribbon / 右键用的图标要注册到 IconCategory.Commands(registerCommandIcon 或插件 context.registerIcon)。所有 key 统一小写化存储与查询——getIcon('MYCMD') 和 getIcon('mycmd') 等价。移除用 removeIcon(key, category);重复注册默认 override: true(即静默覆盖)。
⚠️ 不对称:
getIcon/hasIcon的category省略时全分类搜索,但removeIcon的category是必填——removeIcon('my-icon')不带分类返回false删不掉,必须removeIcon('my-icon', IconCategory.Custom)(注册时默认入 Custom 类)。
2. 注册示例
const ICON_LINE = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<line x1="4" y1="20" x2="20" y2="4"/>
</svg>`;
IconRegistry.registerIcon('my-line', ICON_LINE, { category: IconCategory.Commands });
// 之后菜单 / Ribbon 用名字引用
{ command: 'MYLINE', label: '我的线', icon: 'my-line' }
不显式指定时 options.category 默认 Custom,不是 General——而 Ribbon / 右键菜单只查 Commands,注册到 Custom 的图标在那里显示不出来。
3. 命令图标的命名约定
图标 key 在注册和查询时都会被 .toLowerCase() 归一化——只有一个 key 空间,大小写不敏感:
context.registerCommand('DRAWCROSS', '绘制十字', DrawCrossCommand, opt);
context.registerIcon('DRAWCROSS', SVG);
// PluginContext.registerIcon(name, svg) → IconRegistry.registerCommandIcon(name.toUpperCase(), svg)
// 最终存进 commands 类别的 key 是 'drawcross'(自动小写化)
一次注册即可——菜单、Ribbon、命令面板查 commands 类别时同样走小写化查询,'DRAWCROSS' 和 'drawcross' 命中同一个图标。
4. 常用获取 / 检查
const svg = IconRegistry.getIcon('line', IconCategory.Commands);
if (!svg) console.warn('未注册');
if (!IconRegistry.hasIcon('my-line', IconCategory.Commands)) {
IconRegistry.registerIcon('my-line', SVG, { category: IconCategory.Commands });
}
// 覆盖已注册图标(override 默认就是 true,可省):
IconRegistry.registerIcon('my-line', SVG2, { category: IconCategory.Commands });
// 真要移除:
IconRegistry.removeIcon('my-line', IconCategory.Commands);
5. SVG 设计规范
<svg viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round">
<!-- 路径 -->
</svg>
要点:
viewBox="0 0 24 24"与系统其他图标一致;不一致会显示比例错乱stroke="currentColor"让按钮主题色(深 / 浅)自动套用;写死#fff在浅色主题下会"消失"- 单色图标用线性描边,
stroke-width="2"视觉密度统一 - 主体填色推荐
fill="none"或fill="currentColor",避免硬编码 - 多色 / 渐变可以使用,但要在两种主题下都好看
6. 在 MenuBar / Ribbon / Toolbar 中使用
// 菜单项
{ type: 'command', command: 'MYCMD' } // 自动用 IconRegistry.getIcon('MYCMD', Commands)
// Ribbon 按钮(icon 是 Commands 分类里的图标名;提示字段叫 prompt,没有 tooltip)
{ cmd: 'MYCMD', icon: 'mycmd', label: '我的命令', prompt: '...' }
// 浮动工具栏
{ id: 'mycmd', icon: IconRegistry.getIcon('mycmd', IconCategory.Commands),
tooltip: '我的命令', command: 'MYCMD' }
7. 内置图标速查(节选)
| name(小写) | 含义 |
|---|---|
line pline circle arc ellipse polygon rectang |
绘制 |
move copy rotate scale mirror offset trim extend fillet explode erase |
编辑(没有 chamfer) |
undo redo regen zoom |
系统 |
hatch text mtext dimlinear dimdiameter dimradius dimaligned angledim |
标注 / 填充(角度标注是 angledim,没有 dimangular) |
完整名以注册表为准:Object.keys(IconRegistry.getAllCommandIcons())(或 IconRegistry.getIconsByCategory(IconCategory.Commands))列出全部内置命令图标名。
8. 跨插件共享 / 优先级
- 第二次注册同名图标 会覆盖 之前的(无警告)
- 这意味着插件可以"皮肤化"内置图标;也意味着插件互相有可能踩
- 建议:插件自己的图标加前缀
myplugin-foo,避免冲突
红旗
- 没有正确写
currentColor时,主题切换图标会"消失" viewBox不为 24×24 → 在 Ribbon 里被裁切或留白- 注册重复名导致 谁后注册谁生效(
override默认 true,无警告);多插件场景务必用前缀 - 图标 key 大小写不敏感(统一小写化)——注册
'DRAWCROSS'还是'drawcross'结果一样,一次即可 - SVG 字符串末尾 / 开头不要带
<?xml ... ?>声明 —— vjcad 内部直接innerHTML拼接
参考(官方文档 MCP)
- 文档:
vjcad/doc/ui/icons.md - 示例:
vjcad/example/ui/01ribbon.js、命令 / 插件示例中频繁使用