# 创建自定义图标

要创建自定义图标,需要实现 IIcon 接口。这个接口定义了图标如何渲染、点击后做什么等行为。

推荐继承 Blockly.icons.Icon 抽象类,因为它已经为 IIcon 的多个方法提供了默认实现。

class MyIcon extends Blockly.icons.Icon {
  // 构造函数应始终接收 sourceBlock,确保 SVG 元素可正确创建。
  constructor(sourceBlock) {
    super(sourceBlock);
  }
}

# 指定图标类型

getType 方法返回图标类型值。它用于序列化时注册图标,也用于从 getIcon 读取图标实例。

# 创建图标视图

图标视图是指显示在块上的 SVG 元素。

# 初始化视图

initView 方法用于创建图标在块上的 SVG 元素。新元素应作为 this.svgRoot 的子元素,这样图标销毁时可自动清理。

Blockly.utils.dom 模块提供了更简洁的 SVG 创建接口。

initView(pointerdownListener) {
  if (this.svgRoot) return;  // Already initialized.

  // This adds the pointerdownListener to the svgRoot element.
  // If you do not call `super` you must do this yourself.
  super.initView(pointerdownListener);

  Blockly.utils.dom.createSvgElement(
    Blockly.utils.Svg.CIRCLE,
    {
      'class': 'my-css-class',
      'r': '8',
      'cx': '8',
      'cy': '8',
    },
    this.svgRoot  // Append to the svgRoot.
  );
}

# 返回尺寸

getSize 方法返回图标尺寸,渲染器会据此为图标预留足够宽度。

尺寸单位是工作区单位,不会随工作区缩放变化。

getSize() {
  return new Blockly.utils.Size(16, 16);
}

# 设置顺序

图标在块上有固定顺序。例如,内置变形器图标总在注释图标前,注释图标又在警告图标前。

排序由 getWeight 返回值控制。权重更大的图标会在权重更小的图标后渲染。

getWeight() {
  return 10;
}

提示

权重只在图标挂到块上时计算一次,不支持动态权重。

# 实现点击行为

很多图标是可交互的,点击后会触发动作。比如内置图标点击后会显示气泡 (opens new window)。你可以在 onClick 方法中实现此类逻辑。

onClick() {
  // Do something when clicked.
}

# 响应块状态变化

有些图标还需要响应块状态变化,尤其是“可编辑状态”和“折叠状态”。

# 可编辑状态

如果图标在块不可编辑时要有不同行为(例如不可点击),可实现 updateEditable 方法。块可编辑状态变化时会自动调用该方法。

updateEditable() {
  if (this.sourceBlock.isEditable()) {
    // Do editable things.
  } else {
    // Do non-editable things.
  }
}

# 折叠状态

有些图标在块折叠时也需要显示,但默认不会显示。此时可重写 isShownWhenCollapsed 并返回 true

isShownWhenCollapsed() {
  return true;
}

然后重写 updateCollapsed

updateCollapsed() {
  // By default icons are hidden when the block is collapsed. We want it to
  // be shown, so do nothing.
}

# 释放图标资源

图标销毁时应清理自身创建的 DOM 元素和外部引用。追加到 this.svgRoot 的元素默认会自动销毁,但其他引用需要手动清理。应在 dispose 方法中完成。

dispose() {
  // Always call super!
  super.dispose();

  this.myBubble?.dispose();
  this.myOtherReference?.dispose();
}