-
Notifications
You must be signed in to change notification settings - Fork 13
feat: Add a visual indicator to blocks in move mode. #472
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 4 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
439bd77
feat: Add a visual indicator to blocks in move mode.
gonfunko 5b36f24
fix: Fix positioning of move indicator in RTL mode.
gonfunko d3f7026
chore: Make the linter ignore svg attribute names.
gonfunko 310f347
Merge branch 'main' into move-indicator
gonfunko c6432d5
chore: Remove errant RTL flag.
gonfunko f13124f
refactor: Move drag indicator addition/removal into KeyboardDragStrat…
gonfunko File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| /** | ||
| * @license | ||
| * Copyright 2025 Google LLC | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| import * as Blockly from 'blockly/core'; | ||
| import {MoveIndicatorBubble} from './move_indicator'; | ||
|
|
||
| /** | ||
| * Invisible icon that acts as an anchor for a move indicator bubble. | ||
| */ | ||
| export class MoveIcon implements Blockly.IIcon, Blockly.IHasBubble { | ||
| private moveIndicator: MoveIndicatorBubble; | ||
| static readonly type = new Blockly.icons.IconType('moveIndicator'); | ||
|
|
||
| /** | ||
| * Creates a new MoveIcon instance. | ||
| * | ||
| * @param sourceBlock The block this icon is attached to. | ||
| */ | ||
| constructor(private sourceBlock: Blockly.BlockSvg) { | ||
| this.moveIndicator = new MoveIndicatorBubble(this.sourceBlock); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the type of this icon. | ||
| */ | ||
| getType(): Blockly.icons.IconType<MoveIcon> { | ||
| return MoveIcon.type; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the weight of this icon, which controls its position relative to | ||
| * other icons. | ||
| * | ||
| * @returns The weight of this icon. | ||
| */ | ||
| getWeight(): number { | ||
| return -1; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the size of this icon. | ||
| * | ||
| * @returns A rect with negative width and no height to offset the default | ||
| * padding applied to icons. | ||
| */ | ||
| getSize(): Blockly.utils.Size { | ||
| // Awful hack to cancel out the default padding added to icons. | ||
| return new Blockly.utils.Size(-8, 0); | ||
| } | ||
|
|
||
| /** | ||
| * Returns whether this icon is visible when its parent block is collapsed. | ||
| * | ||
| * @returns False since this icon is never visible. | ||
| */ | ||
| isShownWhenCollapsed(): boolean { | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Returns whether this icon can be clicked in the flyout. | ||
| * | ||
| * @returns False since this icon is invisible and not clickable. | ||
| */ | ||
| isClickableInFlyout(): boolean { | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Returns whether this icon's attached bubble is visible. | ||
| * | ||
| * @returns True because this icon only exists to host its bubble. | ||
| */ | ||
| bubbleIsVisible(): boolean { | ||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Called when the location of this icon's block changes. | ||
| * | ||
| * @param blockOrigin The new location of this icon's block. | ||
| */ | ||
| onLocationChange(blockOrigin: Blockly.utils.Coordinate) { | ||
| this.moveIndicator?.updateLocation(); | ||
| } | ||
|
|
||
| /** | ||
| * Disposes of this icon. | ||
| */ | ||
| dispose() { | ||
| this.moveIndicator?.dispose(); | ||
| } | ||
|
|
||
| // These methods are required by the interfaces, but intentionally have no | ||
| // implementation, largely because this icon has no visual representation. | ||
| applyColour() {} | ||
|
|
||
| hideForInsertionMarker() {} | ||
|
|
||
| updateEditable() {} | ||
|
|
||
| updateCollapsed() {} | ||
|
|
||
| setOffsetInBlock() {} | ||
|
|
||
| onClick() {} | ||
|
|
||
| async setBubbleVisible(visible: boolean) {} | ||
|
|
||
| initView(pointerDownListener: (e: PointerEvent) => void) {} | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| /** | ||
| * @license | ||
| * Copyright 2025 Google LLC | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| import * as Blockly from 'blockly/core'; | ||
|
|
||
| /** | ||
| * Bubble that displays a four-way arrow attached to a block to indicate that | ||
| * it is in move mode. | ||
| */ | ||
| export class MoveIndicatorBubble | ||
| implements Blockly.IBubble, Blockly.IRenderedElement | ||
| { | ||
| /** | ||
| * Root SVG element for this bubble. | ||
| */ | ||
| svgRoot: SVGGElement; | ||
|
|
||
| /** | ||
| * The location of this bubble in workspace coordinates. | ||
| */ | ||
| location = new Blockly.utils.Coordinate(0, 0); | ||
|
|
||
| /** | ||
| * Creates a new move indicator bubble. | ||
| * | ||
| * @param sourceBlock The block this bubble should be associated with. | ||
| */ | ||
| /* eslint-disable @typescript-eslint/naming-convention */ | ||
| constructor(private sourceBlock: Blockly.BlockSvg) { | ||
| this.svgRoot = Blockly.utils.dom.createSvgElement( | ||
| Blockly.utils.Svg.G, | ||
| {}, | ||
| this.sourceBlock.workspace.getBubbleCanvas(), | ||
| ); | ||
| const rtl = this.sourceBlock.workspace.RTL; | ||
| Blockly.utils.dom.createSvgElement( | ||
| Blockly.utils.Svg.CIRCLE, | ||
| { | ||
| 'fill': 'white', | ||
| 'fill-opacity': '0.8', | ||
| 'stroke': 'grey', | ||
| 'stroke-width': '1', | ||
| 'r': 20, | ||
| 'cx': 20 * (rtl ? -1 : 1), | ||
| 'cy': 20, | ||
| }, | ||
| this.svgRoot, | ||
| ); | ||
| Blockly.utils.dom.createSvgElement( | ||
| Blockly.utils.Svg.PATH, | ||
| { | ||
| 'fill': 'none', | ||
| 'stroke': 'currentColor', | ||
| 'stroke-linecap': 'round', | ||
| 'stroke-linejoin': 'round', | ||
| 'stroke-width': '2', | ||
| 'd': 'm18 9l3 3l-3 3m-3-3h6M6 9l-3 3l3 3m-3-3h6m0 6l3 3l3-3m-3-3v6m3-15l-3-3l-3 3m3-3v6', | ||
| 'transform': `translate(${(rtl ? -4 : 1) * 8} 8)`, | ||
| }, | ||
| this.svgRoot, | ||
| ); | ||
|
|
||
| this.updateLocation(); | ||
| } | ||
|
|
||
| /** | ||
| * Returns whether this bubble is movable by the user. | ||
| * | ||
| * @returns Always returns false. | ||
| */ | ||
| isMovable(): boolean { | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the root SVG element for this bubble. | ||
| * | ||
| * @returns The root SVG element. | ||
| */ | ||
| getSvgRoot(): SVGGElement { | ||
| return this.svgRoot; | ||
| } | ||
|
|
||
| /** | ||
| * Recalculates this bubble's location, keeping it adjacent to its block. | ||
| */ | ||
| updateLocation() { | ||
| const bounds = this.sourceBlock.getBoundingRectangle(); | ||
| const x = this.sourceBlock.workspace.RTL | ||
| ? bounds.left + 20 | ||
| : bounds.right - 20; | ||
| const y = bounds.top - 20; | ||
| this.moveTo(x, y); | ||
| this.sourceBlock.workspace.getLayerManager()?.moveToDragLayer(this); | ||
| } | ||
|
|
||
| /** | ||
| * Moves this bubble to the specified location. | ||
| * | ||
| * @param x The location on the X axis to move to. | ||
| * @param y The location on the Y axis to move to. | ||
| */ | ||
| moveTo(x: number, y: number) { | ||
| this.location.x = x; | ||
| this.location.y = y; | ||
| this.svgRoot.setAttribute('transform', `translate(${x}, ${y})`); | ||
| } | ||
|
|
||
| /** | ||
| * Returns this bubble's location in workspace coordinates. | ||
| * | ||
| * @returns The bubble's location. | ||
| */ | ||
| getRelativeToSurfaceXY(): Blockly.utils.Coordinate { | ||
| return this.location; | ||
| } | ||
|
|
||
| /** | ||
| * Disposes of this move indicator bubble. | ||
| */ | ||
| dispose() { | ||
| Blockly.utils.dom.removeNode(this.svgRoot); | ||
| } | ||
|
|
||
| // These methods are required by the interfaces, but intentionally have no | ||
| // implementation, largely because this bubble's location is fixed relative | ||
| // to its block and is not draggable by the user. | ||
| showContextMenu() {} | ||
|
|
||
| setDragging(dragging: boolean) {} | ||
|
|
||
| startDrag(event: PointerEvent) {} | ||
|
|
||
| drag(newLocation: Blockly.utils.Coordinate, event: PointerEvent) {} | ||
|
|
||
| moveDuringDrag(newLocation: Blockly.utils.Coordinate) {} | ||
|
|
||
| endDrag() {} | ||
|
|
||
| revertDrag() {} | ||
|
|
||
| setDeleteStyle(enable: boolean) {} | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.