Skip to content

Commit 5d6ef71

Browse files
authored
fix: Put cursor on newly-created block, not its top connection (#252)
* fix: Put cursor on newly-created block, not its top connection Set the cursor's curNode to a BLOCK node rather than a PREVIOUS or OUTPUT node. Fixes #221. Note that due to issues with how Blockly renders markers, this change results in the block being both selected and having a block marker, though the latter disappears as soon as the cursor is moved. * fix: Prevent renderer from making block marker SVG reappear Override Marker.prototype.setCurNode and .draw to prevent those methods from calling this.drawer.draw; instead, route those calls via drawMarker (a revised version of the previous updateFocusIndication method). This ensures that this.drawer.draw() is only called if curNode is not a BLOCK node, even when the block to which the marker is attached is being rendered. Since MARKER_MOVED events are normally fired by MarkerSvg.prototype.draw calling this.fireMarkerEvent, drawMarker will arrange to call .fireMarkerEvent directly when it does not call this.drawer.draw.
1 parent aefac78 commit 5d6ef71

File tree

2 files changed

+70
-44
lines changed

2 files changed

+70
-44
lines changed

src/line_cursor.ts

Lines changed: 66 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -523,39 +523,49 @@ export class LineCursor extends Marker {
523523
return curNode;
524524
}
525525
const newNode = new ASTNode(ASTNode.types.BLOCK, selected);
526-
super.setCurNode(newNode);
527-
this.updateFocusIndication(curNode, newNode);
526+
this.setCurNode(newNode);
528527
return newNode;
529528
}
530529

531530
/**
532531
* Set the location of the cursor and draw it.
533532
*
534-
* Overrides drawing logic to call `setSelected` if the location is
535-
* a block, or `addSelect` if it's a shadow block (since shadow
536-
* blocks can't be selected).
537-
*
538-
* TODO(#142): The selection and fake-selection code was originally
539-
* a hack added for testing on October 28 2024, because the default
540-
* drawer behaviour was to draw a box around the block and all
541-
* attached child blocks, which was confusing when navigating
542-
* stacks.
543-
*
544-
* Since then we have decided that we probably _do_ in most cases
545-
* want navigating to a block to select the block, but more
546-
* particularly that we want navigation to move _focus_. Replace
547-
* this selection hack with non-hacky changing of focus once that's
548-
* possible.
533+
* Overrides normal Marker setCurNode logic to call
534+
* this.drawMarker() instead of this.drawer.draw() directly.
549535
*
550536
* @param newNode The new location of the cursor.
551537
*/
552538
override setCurNode(newNode: ASTNode) {
553-
const oldNode = this.getCurNode();
539+
const oldNode = super.getCurNode();
540+
// Kludge: we can't set this.curNode directly, so we have to call
541+
// super.setCurNode(...) to do it for us - but that would call
542+
// this.drawer.draw(...), so prevent that by temporarily setting
543+
// this.drawer to null (which we also can't do directly!)
544+
const drawer = this.getDrawer();
545+
this.setDrawer(null as any); // Cast required since param is not nullable.
554546
super.setCurNode(newNode);
547+
this.setDrawer(drawer);
548+
// Draw this marker the way we want to.
549+
this.drawMarker(oldNode, newNode);
550+
// Try to scroll cursor into view.
555551
if (newNode?.getType() === ASTNode.types.BLOCK) {
556552
this.scrollBlockIntoView(newNode.getLocation() as Blockly.BlockSvg);
557553
}
558-
this.updateFocusIndication(oldNode, newNode);
554+
}
555+
556+
/**
557+
* Redraw the current marker.
558+
*
559+
* Overrides normal Marker drawing logic to use this.drawMarker()
560+
* instead of this.drawer.draw() directly.
561+
*
562+
* This hooks the method used by the renderer to draw the marker,
563+
* preventing the marker drawer from showing a marker if we don't
564+
* want it to.
565+
*/
566+
override draw() {
567+
const curNode = super.getCurNode();
568+
this.drawMarker(curNode, curNode);
559569
}
560570

561571
/**
@@ -610,20 +620,30 @@ export class LineCursor extends Marker {
610620
}
611621

612622
/**
613-
* Implements fake selection of shadow blocks as described in
614-
* documentation for setCurNode.
623+
* Draw this cursor's marker.
624+
*
625+
* This is a wrapper around this.drawer.draw (usually implemented by
626+
* MarkerSvg.prototype.draw) that will, if newNode is a BLOCK node,
627+
* instead call `setSelected` to select it (if it's a regular block)
628+
* or `addSelect` (if it's a shadow block, since shadow blocks can't
629+
* be selected) instead of using the normal drawer logic.
630+
*
631+
* TODO(#142): The selection and fake-selection code was originally
632+
* a hack added for testing on October 28 2024, because the default
633+
* drawer (MarkerSvg) behaviour in Zelos was to draw a box around
634+
* the block and all attached child blocks, which was confusing when
635+
* navigating stacks.
636+
*
637+
* Since then we have decided that we probably _do_ in most cases
638+
* want navigating to a block to select the block, but more
639+
* particularly that we want navigation to move _focus_. Replace
640+
* this selection hack with non-hacky changing of focus once that's
641+
* possible.
615642
*
616643
* @param oldNode The previous node.
617-
* @param newNode The newly-selected node.
644+
* @param curNode The current node.
618645
*/
619-
private updateFocusIndication(oldNode: ASTNode, newNode: ASTNode) {
620-
const drawer = this.getDrawer();
621-
622-
if (!drawer) {
623-
console.error('could not find a drawer');
624-
return;
625-
}
626-
646+
private drawMarker(oldNode: ASTNode, curNode: ASTNode) {
627647
// If old node was a block, unselect it or remove fake selection.
628648
if (oldNode?.getType() === ASTNode.types.BLOCK) {
629649
const block = oldNode.getLocation() as Blockly.BlockSvg;
@@ -634,19 +654,25 @@ export class LineCursor extends Marker {
634654
}
635655
}
636656

637-
// If new node is a block, select it or make it look selected.
638-
if (newNode?.getType() === ASTNode.types.BLOCK) {
639-
drawer.hide();
640-
const block = newNode.getLocation() as Blockly.BlockSvg;
641-
if (!block.isShadow()) {
642-
Blockly.common.setSelected(block);
643-
} else {
644-
block.addSelect();
645-
}
657+
// If curNode node is not block, just use the drawer.
658+
if (curNode?.getType() !== ASTNode.types.BLOCK) {
659+
this.getDrawer()?.draw(oldNode, curNode);
646660
return;
647661
}
648662

649-
drawer.draw(oldNode, newNode);
663+
// curNode is a block. Hide any visible marker SVG and instead
664+
// select the block or make it look selected.
665+
super.hide(); // Calls this.drawer?.hide().
666+
const block = curNode.getLocation() as Blockly.BlockSvg;
667+
if (!block.isShadow()) {
668+
Blockly.common.setSelected(block);
669+
} else {
670+
block.addSelect();
671+
}
672+
673+
// Call MarkerSvg.prototype.fireMarkerEvent like
674+
// MarkerSvg.prototype.draw would (even though it's private).
675+
(this.getDrawer() as any)?.fireMarkerEvent?.(oldNode, curNode);
650676
}
651677

652678
/**

src/navigation.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -607,9 +607,7 @@ export class Navigation {
607607
*/
608608
insertFromFlyout(workspace: Blockly.WorkspaceSvg) {
609609
const newBlock = this.createNewBlock(workspace);
610-
if (!newBlock) {
611-
return;
612-
}
610+
if (!newBlock) return;
613611
if (this.markedNode) {
614612
if (
615613
!this.tryToConnectNodes(
@@ -625,7 +623,9 @@ export class Navigation {
625623
}
626624

627625
this.focusWorkspace(workspace);
628-
workspace.getCursor()!.setCurNode(Blockly.ASTNode.createTopNode(newBlock)!);
626+
workspace
627+
.getCursor()!
628+
.setCurNode(Blockly.ASTNode.createBlockNode(newBlock)!);
629629
this.removeMark(workspace);
630630
}
631631

0 commit comments

Comments
 (0)