Scratch Blocks custom component "drop-down icon"

1. Background 

Since the built-in drop-down icon is used for horizontal layout blocks, it looks awkward when placed in a vertical layout, and the picture of the image field is modified after the drop-down selection. This makes me very unhappy, so I made slight modifications based on the original one, and the effect is as follows:

 2. Instructions for use

(1) Introduce field_icon_dropdown.js into the core folder, the code is below

(2) Register field_icon_dropdown in Blockly so that it can be used anywhere. If you don’t want to inject it, you can directly introduce it with the script tag. The code is as follows

goog.require('Blockly.FieldIconDropDown');

(3) The block definition code is as follows. The following code is directly integrated into a complete block. Take setting up the lantern block as an example:

// ZE3P LED
Blockly.Blocks['ZE3P_led'] = {
  init: function () {
    this.jsonInit({
      "message0": "%1",
      "args0": [
        {
          "type": "field_icon_dropdown",
          "name": "COLOR",
          "options": [
            {
              src: Blockly.mainWorkspace.options.pathToMedia + 'icons/set-led_coral.svg',
              width: 48,
              height: 48,
              value: 'Red'
            },
            {
              src: Blockly.mainWorkspace.options.pathToMedia + 'icons/set-led_green.svg',
              width: 48,
              height: 48,
              value: 'Green'
            },
            {
              src: Blockly.mainWorkspace.options.pathToMedia + 'icons/set-led_blue.svg',
              width: 48,
              height: 48,
              value: 'Blue'
            },
            {
              src: Blockly.mainWorkspace.options.pathToMedia + 'icons/set-led_orange.svg',
              width: 48,
              height: 48,
              value: 'Orange'
            },
            {
              src: Blockly.mainWorkspace.options.pathToMedia + 'icons/set-led_yellow.svg',
              width: 48,
              height: 48,
              value: 'Yellow'
            },
            {
              src: Blockly.mainWorkspace.options.pathToMedia + 'icons/set-led_white.svg',
              width: 48,
              height: 48,
              value: 'White'
            },
          ],
        }
      ],
      "outputShape": Blockly.OUTPUT_SHAPE_ROUND,
      "output": "String",
      "extensions": ["colours_looks"]
    });
  }
}
// ZE3P LED显示颜色
Blockly.Blocks['ZE3P_led_set_color'] = {
  init: function () {
    this.jsonInit({
      "message0": "%1%2",
      "args0": [
        {
          "type": "field_image",
          "src": Blockly.mainWorkspace.options.pathToMedia + "/extensions/ZE3P.png",
          "width": 24,
          "height": 24
        },
        {
          "type": "field_vertical_separator"
        }
      ],
      "message1": "设置彩灯 %1 显示 %2 ",
      "args1": [
        {
          "type": "field_pin_dropdown",
          "name": "INTERFACE",
          "options": Blockly.Blocks.ZE3PInterfaceOptions,
        },
        {
          "type": "input_value",
          "name": "COLOR",
        }
      ],
      "category": Blockly.Categories.looks,
      "extensions": ["colours_looks", "shape_statement"]
    });
  }
};

 (4) Add toolbox configuration, the code is as follows:

<block type="ZE3P_led_set_color" id="ZE3P_led_set_color">
    <value name="COLOR">
        <shadow type="ZE3P_led">
            <field name="COLOR">Red</field>
        </shadow>
    </value>
</block>

(5) Transcoding implementation takes python as an example, the code is like Xia 

// LED颜色
Blockly.Python['ZE3P_led'] = function (block) {
  let color = block.getFieldValue('COLOR') || 0;
  const code = "LedColor." + color;
  return [code, Blockly.Python.ORDER_ATOMIC];
};
// LED显示颜色
Blockly.Python['ZE3P_led_set_color'] = function (block) {
  const pin = block.getFieldValue('INTERFACE') || "";
  const color = Blockly.Python.valueToCode(block, 'COLOR', Blockly.Python.ORDER_ATOMIC) || "";
  return `led.set_color(Interface.${pin}, ${color})\n`;
};

Tip: If you use the registration method, it is best to compile it locally. It is not necessary to use javascript to introduce it. 

3. Effect display

 4. Complete code

The complete field_icon_dropdown.js code is as follows:

'use strict';

goog.provide('Blockly.FieldIconDropDown');
goog.require('Blockly.DropDownDiv');


/**
 * 构造器
 * @param icons
 * @constructor
 */
Blockly.FieldIconDropDown = function (icons) {
  this.icons_ = icons;
  // Example:
  // [{src: '...', width: 20, height: 20, value: 'machine_value'}, ...]
  // 选择第一个为默认值
  const defaultValue = icons[0].value;
  Blockly.FieldIconDropDown.superClass_.constructor.call(this, defaultValue);
  this.addArgType('icon_dropdown');
};
goog.inherits(Blockly.FieldIconDropDown, Blockly.Field);

/**
 * Json配置
 */
Blockly.FieldIconDropDown.fromJson = function (element) {
  return new Blockly.FieldIconDropDown(element['options']);
};

/**
 * 下拉面板宽度(不需要修改,3个图标宽度)
 * @type {number}
 * @const
 */
Blockly.FieldIconDropDown.DROPDOWN_WIDTH = 168;

/**
 * 颜色记录
 */
Blockly.FieldIconDropDown.savedPrimary_ = null;

/**
 * 初始化
 */
Blockly.FieldIconDropDown.prototype.init = function (block) {
  if (this.fieldGroup_) {
    return;
  }
  // 下拉箭头大小
  const arrowSize = 12;

  // 重建dom
  this.fieldGroup_ = Blockly.utils.createSvgElement('g', {}, null);
  this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);

  // 字段宽度
  this.size_.width = 44;

  // 图标
  this.imageElement_ = Blockly.utils.createSvgElement('image', {
    'height': 24 + 'px',
    'width': 24 + 'px',
    'x': 4 + "px",
    'y': 4 + "px",
  }, this.fieldGroup_);
  this.setParentFieldImage(this.getSrcForValue(this.value_));

  // 下拉箭头位置
  this.arrowX_ = 32;
  this.arrowY_ = 10;
  if (block.RTL) {
    this.arrowX_ = -this.arrowX_ - arrowSize;
  }

  // 下拉图标
  this.arrowIcon_ = Blockly.utils.createSvgElement('image', {
    'height': arrowSize + 'px',
    'width': arrowSize + 'px',
    'transform': 'translate(' + this.arrowX_ + ',' + this.arrowY_ + ')'
  }, this.fieldGroup_);
  this.arrowIcon_.setAttributeNS('http://www.w3.org/1999/xlink',
    'xlink:href', Blockly.mainWorkspace.options.pathToMedia + 'dropdown-arrow.svg');

  this.mouseDownWrapper_ = Blockly.bindEventWithChecks_(
    this.getClickTarget_(), 'mousedown', this, this.onMouseDown_);
};

/**
 * 鼠标放置样式
 */
Blockly.FieldIconDropDown.prototype.CURSOR = 'default';

/**
 * 设置值
 */
Blockly.FieldIconDropDown.prototype.setValue = function (newValue) {
  if (newValue === null || newValue === this.value_) {
    return;  // No change
  }
  if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
    Blockly.Events.fire(new Blockly.Events.Change(
      this.sourceBlock_, 'field', this.name, this.value_, newValue));
  }
  this.value_ = newValue;
  this.setParentFieldImage(this.getSrcForValue(this.value_));
};

/**
 * 设置当前选择图片
 */
Blockly.FieldIconDropDown.prototype.setParentFieldImage = function (src) {
  if (this.imageElement_ && src) {
    this.imageElement_.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', src || '');
  }
};

/**
 * 获取值
 */
Blockly.FieldIconDropDown.prototype.getValue = function () {
  return this.value_;
};

/**
 * 根据src获取值
 * @param value
 * @returns {*}
 */
Blockly.FieldIconDropDown.prototype.getSrcForValue = function (value) {
  for (var i = 0, icon; icon = this.icons_[i]; i++) {
    if (icon.value === value) {
      return icon.src;
    }
  }
};

/**
 * 下拉选择
 */
Blockly.FieldIconDropDown.prototype.showEditor_ = function () {
  if (Blockly.DropDownDiv.hideIfOwner(this)) {
    return;
  }
  Blockly.DropDownDiv.hideWithoutAnimation();
  Blockly.DropDownDiv.clearContent();
  // 构建下拉内容
  const contentDiv = Blockly.DropDownDiv.getContentDiv();
  // Accessibility properties
  contentDiv.setAttribute('role', 'menu');
  contentDiv.setAttribute('aria-haspopup', 'true');
  for (let i = 0, icon; icon = this.icons_[i]; i++) {

    // 按钮
    const button = document.createElement('button');
    button.setAttribute('id', ':' + i);
    button.setAttribute('role', 'menuitem');
    button.setAttribute('class', 'blocklyDropDownButton');
    button.title = icon.alt;
    button.style.width = icon.width + 'px';
    button.style.height = icon.height + 'px';
    let backgroundColor = this.sourceBlock_.getColour();
    if (icon.value === this.getValue()) {
      backgroundColor = this.sourceBlock_.getColourTertiary();
      button.setAttribute('aria-selected', 'true');
    }
    button.style.backgroundColor = backgroundColor;
    button.style.borderColor = this.sourceBlock_.getColourTertiary();

    // 事件
    Blockly.bindEvent_(button, 'click', this, this.buttonClick_);
    Blockly.bindEvent_(button, 'mouseup', this, this.buttonClick_);
    Blockly.bindEvent_(button, 'mousedown', button, function (e) {
      this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
      e.preventDefault();
    });
    Blockly.bindEvent_(button, 'mouseover', button, function () {
      this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
      contentDiv.setAttribute('aria-activedescendant', this.id);
    });
    Blockly.bindEvent_(button, 'mouseout', button, function () {
      this.setAttribute('class', 'blocklyDropDownButton');
      contentDiv.removeAttribute('aria-activedescendant');
    });

    // 图标
    const buttonImg = document.createElement('img');
    buttonImg.src = icon.src;
    button.setAttribute('data-value', icon.value);
    buttonImg.setAttribute('data-value', icon.value);
    button.appendChild(buttonImg);
    contentDiv.appendChild(button);
  }
  contentDiv.style.width = Blockly.FieldIconDropDown.DROPDOWN_WIDTH + 'px';

  // 设置颜色
  Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), this.sourceBlock_.getColourTertiary());
  Blockly.DropDownDiv.setCategory(this.sourceBlock_.parentBlock_.getCategory());
  this.savedPrimary_ = this.sourceBlock_.getColour();
  this.sourceBlock_.setColour(this.sourceBlock_.getColourSecondary(),
    this.sourceBlock_.getColourSecondary(),
    this.sourceBlock_.getColourTertiary());

  const scale = this.sourceBlock_.workspace.scale;
  const secondaryYOffset = (
    -(Blockly.BlockSvg.MIN_BLOCK_Y * scale) - (Blockly.BlockSvg.FIELD_Y_OFFSET * scale)
  );
  const renderedPrimary = Blockly.DropDownDiv.showPositionedByBlock(this, this.sourceBlock_, this.onHide_.bind(this), secondaryYOffset);
  if (!renderedPrimary) {
    const arrowX = this.arrowX_ + Blockly.DropDownDiv.ARROW_SIZE / 1.5 + 1;
    const arrowY = this.arrowY_ + Blockly.DropDownDiv.ARROW_SIZE / 1.5;
    this.arrowIcon_.setAttribute('transform', 'translate(' + arrowX + ',' + arrowY + ') rotate(180)');
  }
};

/**
 * 点击按钮
 */
Blockly.FieldIconDropDown.prototype.buttonClick_ = function (e) {
  const value = e.target.getAttribute('data-value');
  this.setValue(value);
  Blockly.DropDownDiv.hide();
};

/**
 * 关闭下拉面板时回掉
 */
Blockly.FieldIconDropDown.prototype.onHide_ = function () {
  if (this.sourceBlock_) {
    this.sourceBlock_.setColour(this.savedPrimary_,
      this.sourceBlock_.getColourSecondary(),
      this.sourceBlock_.getColourTertiary());
  }
  Blockly.DropDownDiv.content_.removeAttribute('role');
  Blockly.DropDownDiv.content_.removeAttribute('aria-haspopup');
  Blockly.DropDownDiv.content_.removeAttribute('aria-activedescendant');
  this.arrowIcon_.setAttribute('transform', 'translate(' + this.arrowX_ + ',' + this.arrowY_ + ')');
};

Blockly.Field.register('field_icon_dropdown', Blockly.FieldIconDropDown);

5. About me

Author: Lu Zhimin

Contact: [email protected]

Guess you like

Origin blog.csdn.net/weixin_43532890/article/details/132057887