From d90d00570f6f1df39f8ffb673f386cc9b02192dc Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Mon, 22 May 2023 13:10:42 -0700 Subject: [PATCH] fix: gestures handling icons (#7101) * fix: add handling icon clicks to the gesture system * fix: error message --- core/block_svg.ts | 18 +++++++++++-- core/gesture.ts | 57 ++++++++++++++++++++++++++++++++++++---- tests/mocha/icon_test.js | 4 +-- 3 files changed, 70 insertions(+), 9 deletions(-) diff --git a/core/block_svg.ts b/core/block_svg.ts index cb24eda72..86b4bcdd7 100644 --- a/core/block_svg.ts +++ b/core/block_svg.ts @@ -199,7 +199,7 @@ export class BlockSvg } for (const icon of this.getIcons()) { if (isIcon(icon)) { - // icon.initView(); + icon.initView(this.createIconPointerDownListener(icon)); icon.updateEditable(); } else { // TODO (#7042): Remove old icon handling. @@ -1051,7 +1051,7 @@ export class BlockSvg override addIcon(icon: T): T { super.addIcon(icon); if (this.rendered) { - // icon.initView(); + icon.initView(this.createIconPointerDownListener(icon)); icon.applyColour(); icon.updateEditable(); // TODO: Change this based on #7024. @@ -1061,6 +1061,20 @@ export class BlockSvg return icon; } + /** + * Creates a pointer down event listener for the icon to append to its + * root svg. + */ + private createIconPointerDownListener(icon: IIcon) { + return (e: PointerEvent) => { + if (this.isDeadOrDying()) return; + const gesture = this.workspace.getGesture(e); + if (gesture) { + gesture.setStartIcon(icon); + } + }; + } + override removeIcon(type: string): boolean { const removed = super.removeIcon(type); if (this.rendered) { diff --git a/core/gesture.ts b/core/gesture.ts index ab807ff2d..71ec9211e 100644 --- a/core/gesture.ts +++ b/core/gesture.ts @@ -36,6 +36,7 @@ import {Coordinate} from './utils/coordinate.js'; import {WorkspaceCommentSvg} from './workspace_comment_svg.js'; import {WorkspaceDragger} from './workspace_dragger.js'; import type {WorkspaceSvg} from './workspace_svg.js'; +import type {IIcon} from './interfaces/i_icon.js'; /** * Note: In this file "start" refers to pointerdown @@ -72,6 +73,12 @@ export class Gesture { */ private startField: Field | null = null; + /** + * The icon that the gesture started on, or null if it did not start on an + * icon. + */ + private startIcon: IIcon | null = null; + /** * The block that the gesture started on, or null if it did not start on a * block. @@ -614,9 +621,9 @@ export class Gesture { } this.isEnding_ = true; // The ordering of these checks is important: drags have higher priority - // than clicks. Fields have higher priority than blocks; blocks have - // higher priority than workspaces. The ordering within drags does not - // matter, because the three types of dragging are exclusive. + // than clicks. Fields and icons have higher priority than blocks; blocks + // have higher priority than workspaces. The ordering within drags does + // not matter, because the three types of dragging are exclusive. if (this.bubbleDragger) { this.bubbleDragger.endBubbleDrag(e, this.currentDragDeltaXY); } else if (this.blockDragger) { @@ -628,6 +635,8 @@ export class Gesture { this.doBubbleClick(); } else if (this.isFieldClick()) { this.doFieldClick(); + } else if (this.isIconClick()) { + this.doIconClick(); } else if (this.isBlockClick()) { this.doBlockClick(); } else if (this.isWorkspaceClick()) { @@ -917,7 +926,7 @@ export class Gesture { private doFieldClick() { if (!this.startField) { throw new Error( - 'Cannot do a field click because the start field is ' + 'undefined' + 'Cannot do a field click because the start field is undefined' ); } @@ -930,6 +939,17 @@ export class Gesture { this.bringBlockToFront(); } + /** Execute an icon click. */ + private doIconClick() { + if (!this.startIcon) { + throw new Error( + 'Cannot do an icon click because the start icon is undefined' + ); + } + + this.startIcon.onClick(); + } + /** Execute a block click. */ private doBlockClick() { // Block click in an autoclosing flyout. @@ -1015,6 +1035,23 @@ export class Gesture { } } + /** + * Record the icon that a gesture started on. + * + * @param icon The icon the gesture started on. + * @internal + */ + setStartIcon(icon: IIcon) { + if (this.gestureHasStarted) { + throw Error( + 'Tried to call gesture.setStartIcon, ' + + 'but the gesture had already been started.' + ); + } + + if (!this.startIcon) this.startIcon = icon; + } + /** * Record the bubble that a gesture started on * @@ -1112,7 +1149,12 @@ export class Gesture { // A block click starts on a block, never escapes the drag radius, and is // not a field click. const hasStartBlock = !!this.startBlock; - return hasStartBlock && !this.hasExceededDragRadius && !this.isFieldClick(); + return ( + hasStartBlock && + !this.hasExceededDragRadius && + !this.isFieldClick() && + !this.isIconClick() + ); } /** @@ -1132,6 +1174,11 @@ export class Gesture { ); } + /** @return Whether this gesture is a click on an icon. */ + private isIconClick(): boolean { + return !!this.startIcon && !this.hasExceededDragRadius; + } + /** * Whether this gesture is a click on a workspace. This should only be called * when ending a gesture (pointerup). diff --git a/tests/mocha/icon_test.js b/tests/mocha/icon_test.js index f873de0ae..f21232c3f 100644 --- a/tests/mocha/icon_test.js +++ b/tests/mocha/icon_test.js @@ -124,7 +124,7 @@ suite('Icon', function () { ); }); - test.skip('initView is called by headful blocks during initSvg', function () { + test('initView is called by headful blocks during initSvg', function () { const workspace = createWorkspaceSvg(); const block = createUninitializedBlock(workspace); const icon = new MockIcon(); @@ -142,7 +142,7 @@ suite('Icon', function () { ); }); - test.skip( + test( 'initView is called by headful blocks that are currently ' + 'rendered when the icon is added', function () {