creating blocks from the main workspace using the new dragging strategy

134 views
Skip to first unread message

francisc...@ubbu.io

unread,
Sep 19, 2024, 9:20:11 AM9/19/24
to Blockly
Hello,
I'm trying to create a block A when dragging starts on block B. I managed to create the Block A with the new dragging strategy but I'm struggling to switch the dragging to the newly created block.
I'm trying to do something like:
drag start block B > create block A > stop dragging block B > start dragging block A

Does anyone have an idea how to do this?
This strategy is being applied to block B and the code is as follows:

import * as Blockly from 'blockly/core';

export default class ArgumentDragStrategy extends Blockly.dragging.BlockDragStrategy {
  startDrag(e?: PointerEvent): void {
    const workspace = this.workspace as Blockly.WorkspaceSvg;
    const block = this.block as Blockly.BlockSvg;

    const newBlock = workspace.newBlock('Number') as Blockly.BlockSvg;
    newBlock.initSvg();
    newBlock.render();

    const position = block.getRelativeToSurfaceXY();
    newBlock.moveTo(position);

    // help...
  }

  endDrag(e?: PointerEvent): void {
  }

  drag(newLoc: Blockly.utils.Coordinate): void {
  }

  isMovable(): boolean {
    return super.isMovable();
  }
}

francisc...@ubbu.io

unread,
Sep 19, 2024, 3:40:54 PM9/19/24
to Blockly
I've managed to do what I intended but I'm not sure if it's the best implementation since I'm accessing/mutating private properties. This is what I came up with:


import * as Blockly from 'blockly/core';

export default class ArgumentDragStrategy extends Blockly.dragging
  .BlockDragStrategy {
  startDrag(e?: PointerEvent): void {
    super.startDrag(e);


    const block = this.block as Blockly.BlockSvg;
    const workspace = block.workspace;


    const newBlock = workspace.newBlock('Number') as Blockly.BlockSvg;
    newBlock.initSvg();
    newBlock.render();

    const position = block.getRelativeToSurfaceXY();
    newBlock.moveTo(position);

    this.endDrag(e);

    const gesture = workspace.getGesture(e as PointerEvent) as Blockly.Gesture;
    Blockly.common.setSelected(newBlock);

    gesture.dragger = gesture.createDragger(newBlock, workspace);
    gesture.dragger.onDragStart(e);
    gesture.dragger.onDrag(e, position);
  }

  // Override: prevents block from moving
  drag(newLoc: Blockly.utils.Coordinate): void {}

Aaron Dodson

unread,
Sep 19, 2024, 3:42:19 PM9/19/24
to Blockly
I recently did something similar here: https://github.com/gonfunko/scratch-blocks/commit/0ca0620aad343cdbb1d632ddf3ae73a70085aa03 In this particular case I was duplicating the block on drag, but it should work if you create a completely separate block too.

Christopher Allen

unread,
Sep 20, 2024, 10:48:31 AM9/20/24
to blo...@googlegroups.com
Hi Francisco,

In addition to Aaron's final implementation, I thought you might be interested in this excerpt from the original design doc for the new custom dragging support in Blockly v11, in particular from the section on how the new machinery will support duplicate-on-drag, in which you can see that the anticipated implementation is that the drag strategy of the block being duplicated (or, in your case, spawning a new block) will proxy the startDrag / drag / endDrag  call to the new block (with some minor corrections by myself):

[Developers of blocks that should duplicate on drag] an implement this by creating a custom IDraggable block drag strategy they can assign to the protected dragStrategy property of the block. The custom IDraggable will duplicate the block and delegate drag operations to that duplicated block instead of dragging the original block.


class DuplicateOnDragStrategy implements IDraggable {
  private dupe;
  startDrag(e: PointerEvent) {
    this.dupe = Blockly.serialization.blocks.append(
        Blockly.serialization.blocks.save(this), this.workspace);
  },

  drag(newLoc: Coordinate, target: IDragTarget, e?: PointerEvent) {
    this.dupe.drag(newLoc, target, e);
  },

  endDrag(e: PointerEvent) {
    this.dupe.endDrag(e);
  }
}

Blockly.Blocks['my_block'] = {
  init: function() {
    this.dragStrategy = new DuplicateOnDragStrategy();
  }
}

…which is perhaps a roundabout way of saying that the design does not anticipate updating which block the dragger thinks is being dragged.


Christopher

francisc...@ubbu.io

unread,
Sep 23, 2024, 7:50:23 AM9/23/24
to Blockly
Thank you both for sharing your input! I tried Christophers suggestion but I'm having a problem with the undo/redo. After I create block A and end it's the drag I was expecting that the undo would move block A to the position of block B and the 2nd undo would destroy it, but block A moves first to the top of the screen before the deletion. Something like:
drag start block B > create block A > stop dragging block A > undo (move block A to block B) > undo (move block A to top-left corner of the workspace) > undo (delete block A)

The code is as follows:


import * as Blockly from 'blockly/core';

export default class ArgumentDragStrategy extends Blockly.dragging
  .BlockDragStrategy {
  public targetWorkspace;
  public targetBlock: Blockly.BlockSvg | null;

  constructor(block: Blockly.BlockSvg) {
    super(block);

    this.targetBlock = null;
    this.targetWorkspace = block.workspace;
  }

  startDrag(e?: PointerEvent): void {
    this.targetBlock = this.targetWorkspace.newBlock('Number');
    this.targetBlock.initSvg();
    this.targetBlock.render();

    this.targetBlock.startDrag(e);
  }

  drag(newLoc: Blockly.utils.Coordinate): void {
    this.targetBlock?.drag(newLoc);
  }

  endDrag(e: PointerEvent) {
    this.targetBlock?.endDrag(e);
  }
}

I've also tried pasting block A directly at the position of block B, disabling events (which causes other problems) and I still have the same problem. Does anyone have any suggestions?

francisc...@ubbu.io

unread,
Sep 23, 2024, 7:50:27 AM9/23/24
to Blockly
I've also improved a bit my previous implementation to, and is working fine but this line (gesture.dragger = dragger;) seems a bit hacky:


import * as Blockly from 'blockly/core';

export default class ArgumentDragStrategy extends Blockly.dragging
  .BlockDragStrategy {
  public targetBlock;

  constructor(block: Blockly.BlockSvg) {
    super(block);

    this.targetBlock = block;
  }

  startDrag(e?: PointerEvent): void {
    super.startDrag(e);

    const block = this.targetBlock;
    const workspace = block.workspace;

    const newBlock = workspace.newBlock('Number');

    newBlock.initSvg();
    newBlock.render();

    const position = block.getRelativeToSurfaceXY();
    newBlock.moveTo(position);

    this.endDrag(e);

    const DraggerClass = Blockly.registry.getClassFromOptions(
      Blockly.registry.Type.BLOCK_DRAGGER,
      workspace.options,
      true
    )!;
    const dragger = new DraggerClass(newBlock, workspace);

    Blockly.common.setSelected(newBlock);


    const gesture = workspace.getGesture(e as PointerEvent) as Blockly.Gesture;
    gesture.dragger = dragger;

    dragger.onDragStart(e!);

  }

  // Override: prevents block from moving
  drag(newLoc: Blockly.utils.Coordinate): void {}
}

On Friday, September 20, 2024 at 3:48:31 PM UTC+1 cpca...@google.com wrote:

Christopher Allen

unread,
Sep 23, 2024, 8:49:45 AM9/23/24
to blo...@googlegroups.com
Hi Francisco,

Thank you both for sharing your input! I tried Christophers suggestion but I'm having a problem with the undo/redo. After I create block A and end it's the drag I was expecting that the undo would move block A to the position of block B and the 2nd undo would destroy it, but block A moves first to the top of the screen before the deletion. Something like:
drag start block B > create block A > stop dragging block A > undo (move block A to block B) > undo (move block A to top-left corner of the workspace) > undo (delete block A)

This has to do with event groups: each event has a .group property, and consecutive events with the same (non-empty) group value will be undone in a single action.

You will need to ensure that the BlockCreate event and the first BlockMove event have the same .group value, and this can be achieved by using the Blockly.Events.setGroup and Blockly.Events.getGroup methods—but unfortunately they are not properly documented in our API reference at the moment (thanks for drawing our attention to this problem), so you will probably want to consult the source code for these methods as well as for calls to them.

Best wishes,

Christopher

francisc...@ubbu.io

unread,
Sep 25, 2024, 5:20:54 AM9/25/24
to Blockly
Hello!
Thank you for your reply! I managed to do what was intended using the Blockly.Events.setGroup.

The result:
class MyDragStrategy extends Blockly.dragging.BlockDragStrategy {

  public targetBlock: Blockly.BlockSvg | null;

  constructor(block: Blockly.BlockSvg) {
    super(block);

    this.targetBlock = null;
  }

  startDrag(e?: PointerEvent): void {

    const block = this.block as Blockly.BlockSvg;

    const prevGroupId = Blockly.Events.getGroup();
    Blockly.Events.setGroup(true);

    this.targetBlock = block.workspace.newBlock('Number');
    this.targetBlock.initSvg();
    this.targetBlock.render();

    const position = block.getRelativeToSurfaceXY();
    this.targetBlock.moveTo(position);

    this.targetBlock.startDrag(e);
    Blockly.Events.setGroup(prevGroupId);

  }

  drag(newLoc: Blockly.utils.Coordinate): void {
    this.targetBlock?.drag(newLoc);
  }

  endDrag(e: PointerEvent) {
    this.targetBlock?.endDrag(e);
  }
}

francisc...@ubbu.io

unread,
Sep 27, 2024, 4:13:52 AM9/27/24
to Blockly
I've ran into an interesting edge case where if I drag block A directly to the trash, block B is removed instead. I don't have the same problem with my implementation which leads me to believe that we really need to update which block the dragger thinks is being dragged.

Christopher Allen

unread,
Oct 7, 2024, 7:36:12 AM10/7/24
to blo...@googlegroups.com
Hi Francisco,

I've ran into an interesting edge case where if I drag block A directly to the trash, block B is removed instead. I don't have the same problem with my implementation which leads me to believe that we really need to update which block the dragger thinks is being dragged.

Ooh, thanks for drawing this to our attention.  There are some ongoing projects using the new dragging mechanics that could be affected by this problem, which we might otherwise have overlooked.


Christopher
 
Reply all
Reply to author
Forward
0 new messages