Skip to content

Commit 0be7c43

Browse files
authored
fix: Update cursor location in response to SELECT events (#194)
* refactor!: Add a .workspace property to LineCursor Add a .workspace property to LineCursor instances, and a corresponding workspace parameter to the class constructor, so that LineCursor instances know explicitly which WorkspaceSvg they are associated with. * fix: Update cursor location in response to SELECT events This solves the issue where, when the cursor was not on a block, clicking on a block to select it would not immediately cause the displayed cursor to disappear, but the cursor location would only be updated when a cursor key was pressed. * refactor: Make it possible to uninstall the LineCursor Turn installCursor into an install method on LineCursor, and add a coresponding uninstall method. * chore: Update comments for PR #194.
1 parent 3ab05d3 commit 0be7c43

File tree

2 files changed

+70
-37
lines changed

2 files changed

+70
-37
lines changed

src/index.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import * as Blockly from 'blockly/core';
88
import {NavigationController} from './navigation_controller';
9-
import {installCursor} from './line_cursor';
9+
import {LineCursor} from './line_cursor';
1010

1111
/** Plugin for keyboard navigation. */
1212
export class KeyboardNavigation {
@@ -22,14 +22,16 @@ export class KeyboardNavigation {
2222
/** Keyboard navigation controller instance for the workspace. */
2323
private navigationController: NavigationController;
2424

25+
/** Cursor for the main workspace. */
26+
private cursor: LineCursor;
27+
2528
/**
2629
* These fields are used to preserve the workspace's initial state to restore
2730
* it when/if keyboard navigation is disabled.
2831
*/
2932
private injectionDivTabIndex: string | null;
3033
private workspaceParentTabIndex: string | null;
3134
private originalTheme: Blockly.Theme;
32-
private originalCursor: Blockly.Cursor | null;
3335

3436
/**
3537
* Constructs the keyboard navigation.
@@ -48,8 +50,9 @@ export class KeyboardNavigation {
4850

4951
this.originalTheme = workspace.getTheme();
5052
this.setGlowTheme();
51-
this.originalCursor = workspace.getMarkerManager().getCursor();
52-
installCursor(workspace.getMarkerManager());
53+
54+
this.cursor = new LineCursor(workspace);
55+
this.cursor.install();
5356

5457
// Ensure that only the root SVG G (group) has a tab index.
5558
this.injectionDivTabIndex = workspace
@@ -106,10 +109,7 @@ export class KeyboardNavigation {
106109
.setAttribute('tabindex', this.injectionDivTabIndex);
107110
}
108111

109-
if (this.originalCursor) {
110-
const markerManager = this.workspace.getMarkerManager();
111-
markerManager.setCursor(this.originalCursor);
112-
}
112+
this.cursor.uninstall();
113113

114114
this.workspace.setTheme(this.originalTheme);
115115

src/line_cursor.ts

Lines changed: 62 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,49 @@ import {ASTNode, Marker} from 'blockly/core';
2222
export class LineCursor extends Marker {
2323
override type = 'cursor';
2424

25+
/** Has the cursor been installed in a workspace's marker manager? */
26+
private installed = false;
27+
28+
/** Old Cursor instance, saved during installation. */
29+
private oldCursor: Blockly.Cursor | null = null;
30+
2531
/**
26-
* Constructor for a line cursor.
32+
* @param workspace The workspace this cursor belongs to.
2733
*/
28-
constructor() {
34+
constructor(public readonly workspace: Blockly.WorkspaceSvg) {
2935
super();
36+
// Bind selectListener to facilitate future install/uninstall.
37+
this.selectListener = this.selectListener.bind(this);
38+
}
39+
40+
/**
41+
* Install this LineCursor in its workspace's marker manager and set
42+
* up the select listener. The original cursor (if any) is saved
43+
* for future use by .uninstall(), and its location is used to set
44+
* this one's.
45+
*/
46+
install() {
47+
if (this.installed) throw new Error('LineCursor already installed');
48+
const markerManager = this.workspace.getMarkerManager();
49+
this.oldCursor = markerManager.getCursor();
50+
markerManager.setCursor(this);
51+
if (this.oldCursor) this.setCurNode(this.oldCursor.getCurNode());
52+
this.workspace.addChangeListener(this.selectListener);
53+
this.installed = true;
54+
}
55+
56+
/**
57+
* Remove the select listener and uninstall this LineCursor from its
58+
* workspace's marker manager, restoring any previously-existing
59+
* cursor. Does not attempt to adjust original cursor's location.
60+
*/
61+
uninstall() {
62+
if (!this.installed) throw new Error('LineCursor not yet installed');
63+
this.workspace.removeChangeListener(this.selectListener.bind(this));
64+
if (this.oldCursor) {
65+
this.workspace.getMarkerManager().setCursor(this.oldCursor);
66+
}
67+
this.installed = false;
3068
}
3169

3270
/**
@@ -410,25 +448,24 @@ export class LineCursor extends Marker {
410448
* if so, update the cursor location (and any highlighting) to
411449
* match.
412450
*
413-
* This works reasonably well but has some glitches, most notably
414-
* that if the cursor is not on a block (e.g. it is on a connection
415-
* or the workspace) then it will remain visible in its previous
416-
* location until a cursor key is pressed.
451+
* Doing this only when getCurNode would naturally be called works
452+
* reasonably well but has some glitches, most notably that if the
453+
* cursor was not on a block (e.g. it was on a connection or the
454+
* workspace) when the user selected a block then it will remain
455+
* visible in its previous location until some keyboard navigation occurs.
456+
*
457+
* To ameliorate this, the LineCursor constructor adds an event
458+
* listener that calls getCurNode in response to SELECTED events.
417459
*
418-
* TODO(#97): Remove this hack once Blockly is modified to update
419-
* the cursor/focus itself.
460+
* Remove this hack once Blockly is modified to update the
461+
* cursor/focus itself.
420462
*
421463
* @returns The current field, connection, or block the cursor is on.
422464
*/
423465
override getCurNode(): ASTNode {
424466
const curNode = super.getCurNode();
425467
const selected = Blockly.common.getSelected();
426-
if (
427-
(selected?.workspace as Blockly.WorkspaceSvg)
428-
?.getMarkerManager()
429-
.getCursor() !== this
430-
)
431-
return curNode;
468+
if (selected?.workspace !== this.workspace) return curNode;
432469

433470
// Selected item is on workspace that this cursor belongs to.
434471
const curLocation = curNode?.getLocation();
@@ -511,6 +548,17 @@ export class LineCursor extends Marker {
511548

512549
drawer.draw(oldNode, newNode);
513550
}
551+
552+
/**
553+
* Event listener that syncs the cursor location to the selected
554+
* block on SELECTED events.
555+
*/
556+
private selectListener(event: Blockly.Events.Abstract) {
557+
if (event.type !== Blockly.Events.SELECTED) return;
558+
const selectedEvent = event as Blockly.Events.Selected;
559+
if (selectedEvent.workspaceId !== this.workspace.id) return;
560+
this.getCurNode();
561+
}
514562
}
515563

516564
export const registrationName = 'LineCursor';
@@ -521,18 +569,3 @@ Blockly.registry.register(registrationType, registrationName, LineCursor);
521569
export const pluginInfo = {
522570
[registrationType.toString()]: registrationName,
523571
};
524-
525-
/**
526-
* Install this cursor on the marker manager in the same position as
527-
* the previous cursor.
528-
*
529-
* @param markerManager The currently active marker manager.
530-
*/
531-
export function installCursor(markerManager: Blockly.MarkerManager) {
532-
const oldCurNode = markerManager.getCursor()?.getCurNode();
533-
const lineCursor = new LineCursor();
534-
markerManager.setCursor(lineCursor);
535-
if (oldCurNode) {
536-
markerManager.getCursor()?.setCurNode(oldCurNode);
537-
}
538-
}

0 commit comments

Comments
 (0)