custom plus minus mutator

281 views
Skip to first unread message

Jonty Small

unread,
May 11, 2021, 7:40:28 AM5/11/21
to Blockly
Hi all, 

I've been doing some work trying to build my own +- mutator block to allow longer boolean checks (i.e. if one of a list evals to true, or if all of the list evals to true)

i've taken Beka's implementation and kind of butchered her list create function (sorry) as it looked close to what i wanted.

Because i'm evaling booleans i put a check on each input to check if they were booleans and the functionality is working but as the gif below shows the last one i add (that goes over the initialisation of 2 in the list) is rounded rather than diamond shaped which might confuse the end user. 

I've added the full code below as i'm unable to figure out where the error is that might be causing this. Has anyone had similar issues? or know how i might solve it? 

Many thanks!
Jonty 

p.s. if there is a way to include code that formats it better let me know and i'll reupload it with a better formatting so its easier to read

p.p.s the changes i made are in the naming, so type, mutator name, adding in the check at the end of the value inputs and changing the remove minus to remove when it gets to one option so you can't enter something with nothing. 

Blockly.defineBlocksWithJsonArray([
  {
    "type": "oneOf",
    "message0": "one of these are true %1",
    "args0": [
      {
        "type": "input_dummy",
        "name": "EMPTY",
      },
    ],
    "output": "Boolean",
    "style": "logic_blocks",
    "mutator": "oneOf_mutator",
  },
]);
/* eslint-enable quotes */

const listCreateMutator = {
  /**
   * Number of item inputs the block has.
   * @type {number}
   */
  itemCount_: 0,

  /**
   * Creates XML to represent number of text inputs.
   * @return {!Element} XML storage element.
   * @this {Blockly.Block}
   */
  mutationToDom: function() {
    const container = Blockly.utils.xml.createElement('mutation');
    container.setAttribute('items', this.itemCount_);
    return container;
  },
  /**
   * Parses XML to restore the text inputs.
   * @param {!Element} xmlElement XML storage element.
   * @this {Blockly.Block}
   */
  domToMutation: function(xmlElement) {
    const targetCount = parseInt(xmlElement.getAttribute('items'), 10);
    this.updateShape_(targetCount);
  },

  /**
   * Adds inputs to the block until it reaches the target number of inputs.
   * @param {number} targetCount The target number of inputs for the block.
   * @this {Blockly.Block}
   * @private
   */
  updateShape_: function(targetCount) {
    while (this.itemCount_ < targetCount) {
      this.addPart_();
    }
    while (this.itemCount_ > targetCount) {
      this.removePart_();
    }
    this.updateMinus_();
  },

  /**
   * Callback for the plus image. Adds an input to the end of the block and
   * updates the state of the minus.
   */
  plus: function() {
    this.addPart_();
    this.updateMinus_();
  },

  /**
   * Callback for the minus image. Removes an input from the end of the block
   * and updates the state of the minus.
   */
  minus: function() {
    if (this.itemCount_ == 0) {
      return;
    }
    this.removePart_();
    this.updateMinus_();
  },

  // To properly keep track of indices we have to increment before/after adding
  // the inputs, and decrement the opposite.
  // Because we want our first input to be ADD0 (not ADD1) we increment after.

  /**
   * Adds an input to the end of the block. If the block currently has no
   * inputs it updates the top 'EMPTY' input to receive a block.
   * @this {Blockly.Block}
   * @private
   */
  addPart_: function() {
    console.log(this.itemCount_);
    if (this.itemCount_ == 0) {
      this.removeInput('EMPTY');
      this.topInput_ = this.appendValueInput('ADD' + this.itemCount_).setCheck('Boolean')
          .appendField(createPlusField(), 'PLUS')
          .appendField("one of these are true ");
    } else {
      this.appendValueInput('ADD' + this.itemCount_).setCheck('Boolean');
    }
    this.itemCount_++;
  },

  /**
   * Removes an input from the end of the block. If we are removing the last
   * input this updates the block to have an 'EMPTY' top input.
   * @this {Blockly.Block}
   * @private
   */
  removePart_: function() {
    console.log(this.itemCount_);
    this.itemCount_--;
    this.removeInput('ADD' + this.itemCount_);
    if (this.itemCount_ == 0) {
      this.topInput_ = this.appendDummyInput('EMPTY').setCheck('Boolean')
          .appendField(createPlusField(), 'PLUS')
          .appendField("Please add an option to check");
    }
  },

  /**
   * Makes it so the minus is visible iff there is an input available to remove.
   * @private
   */
  updateMinus_: function() {
    const minusField = this.getField('MINUS');
    if (!minusField && this.itemCount_ > 0) {
      this.topInput_.insertFieldAt(1, createMinusField(), 'MINUS');
    } else if (minusField && this.itemCount_ < 2) {
      this.topInput_.removeField('MINUS');
    }
  },
};

/**
 * Updates the shape of the block to have 3 inputs if no mutation is provided.
 * @this {Blockly.Block}
 */
const listCreateHelper = function() {
  this.getInput('EMPTY').insertFieldAt(0, createPlusField(), 'PLUS');
  this.updateShape_(2);
};

Blockly.Extensions.registerMutator('oneOf_mutator',
    listCreateMutator, listCreateHelper);

Animation.gif

Beka Westberg

unread,
May 11, 2021, 5:57:00 PM5/11/21
to blo...@googlegroups.com
Huh interesting issue! I bet this is because while most things you do to modify a block (eg adding an input, adding a field, etc) force the block to rerender (example), setting a connection check does not.

You should be able to fix this by adding a check like this to your addPart_ function:
```
// Always check rendered first! Beware rendered.
if (this.rendered) {
  this.render();
  // This may not be necessary, but no harm in adding it.
  this.bumpNeighbours();
}
```

I hope that fixes the issue! Best of luck with your project :D
--Beka

--
You received this message because you are subscribed to the Google Groups "Blockly" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blockly+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/blockly/fb890474-f45c-444b-98c0-fc1bbcf0a713n%40googlegroups.com.

Jonty Small

unread,
May 13, 2021, 8:18:43 AM5/13/21
to Blockly
Hi Beka,

Thanks! that fixed it! I'll make sure to keep this fix in mind for future potential issues!

Best,
Jonty

feni...@google.com

unread,
May 14, 2021, 10:13:05 PM5/14/21
to Blockly
As a quick follow-up, rerendering a block that is being dragged is not guaranteed to be safe and sometimes ends up with your blocks scattered across the workspace

Jonty Small

unread,
May 19, 2021, 5:28:11 AM5/19/21
to Blockly
Ok, so just make sure that any re rendering occurs on events like clicks then it should be alright? 

Beka Westberg

unread,
May 19, 2021, 5:45:02 PM5/19/21
to blo...@googlegroups.com
Hello,

I think you should be able to prevent the problem Rachel mentioned if you rerender on clicks, but you can also check workspace.isDragging() to make sure the block/workspace isn't being moved.

I hope that helps!
--Beka

Reply all
Reply to author
Forward
0 new messages