From b204cecc3a7bc0b9eaa757a9f6defa05247aa9c8 Mon Sep 17 00:00:00 2001 From: Hongtao Lye Date: Fri, 6 Dec 2024 16:25:30 +0800 Subject: [PATCH] feat: add event support --- .../gfx-tool/default-tool-ext/event-ext.ts | 68 +++++++++++++++++++ .../edgeless/gfx-tool/default-tool-ext/ext.ts | 12 ++++ .../mind-map-ext/mind-map-ext.ts | 4 ++ .../edgeless/gfx-tool/default-tool.ts | 27 +++++++- .../framework/block-std/src/gfx/model/base.ts | 15 ++++ .../src/gfx/model/surface/element-model.ts | 15 ++++ .../gfx/model/surface/local-element-model.ts | 15 ++++ 7 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 packages/blocks/src/root-block/edgeless/gfx-tool/default-tool-ext/event-ext.ts diff --git a/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool-ext/event-ext.ts b/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool-ext/event-ext.ts new file mode 100644 index 0000000000000..8494ece96c24d --- /dev/null +++ b/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool-ext/event-ext.ts @@ -0,0 +1,68 @@ +import type { PointerEventState } from '@blocksuite/block-std'; +import type { + GfxLocalElementModel, + GfxPrimitiveElementModel, +} from '@blocksuite/block-std/gfx'; + +import { Bound } from '@blocksuite/global/utils'; + +import { DefaultModeDragType, DefaultToolExt } from './ext.js'; + +export class CanvasElementEventExt extends DefaultToolExt { + private _currentStackedElm: ( + | GfxPrimitiveElementModel + | GfxLocalElementModel + )[] = []; + + override supportedDragTypes: DefaultModeDragType[] = [ + DefaultModeDragType.None, + ]; + + private _executeInReverseOrder( + callback: (elm: GfxPrimitiveElementModel | GfxLocalElementModel) => void, + arr = this._currentStackedElm + ): void { + for (let i = arr.length - 1; i >= 0; i--) { + callback(arr[i]); + } + } + + override click(_evt: PointerEventState): void { + this._executeInReverseOrder(elm => elm.onClick?.(_evt)); + } + + override dblClick(_evt: PointerEventState): void { + this._executeInReverseOrder(elm => elm.onDblClick?.(_evt)); + } + + override pointerDown(_evt: PointerEventState): void { + this._executeInReverseOrder(elm => elm.onPointerDown?.(_evt)); + } + + override pointerMove(_evt: PointerEventState): void { + const [x, y] = this.gfx.viewport.toModelCoord(_evt.x, _evt.y); + const hoveredElm = this.gfx.grid.search(new Bound(x, y, 1, 1), { + filter: ['canvas', 'local'], + }) as (GfxPrimitiveElementModel | GfxLocalElementModel)[]; + const stackedElmMap = new Map( + this._currentStackedElm.map((val, index) => [val, index]) + ); + + this._executeInReverseOrder(elm => { + if (stackedElmMap.has(elm)) { + const idx = stackedElmMap.get(elm)!; + this._currentStackedElm.splice(idx, 1); + elm.onPointerMove?.(_evt); + } else { + elm.onPointerEnter?.(_evt); + } + }, hoveredElm); + this._executeInReverseOrder(elm => elm.onPointerLeave?.(_evt)); + + this._currentStackedElm = hoveredElm; + } + + override pointerUp(_evt: PointerEventState): void { + this._executeInReverseOrder(elm => elm.onPointerUp?.(_evt)); + } +} diff --git a/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool-ext/ext.ts b/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool-ext/ext.ts index 67147f3d43a00..707ce18e43940 100644 --- a/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool-ext/ext.ts +++ b/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool-ext/ext.ts @@ -27,6 +27,8 @@ export type DragState = { }; export class DefaultToolExt { + readonly supportedDragTypes: DefaultModeDragType[] = []; + get gfx() { return this.defaultTool.gfx; } @@ -37,6 +39,10 @@ export class DefaultToolExt { constructor(protected defaultTool: DefaultTool) {} + click(_evt: PointerEventState) {} + + dblClick(_evt: PointerEventState) {} + initDrag(_: DragState): { dragStart?: (evt: PointerEventState) => void; dragMove?: (evt: PointerEventState) => void; @@ -47,5 +53,11 @@ export class DefaultToolExt { mounted() {} + pointerDown(_evt: PointerEventState) {} + + pointerMove(_evt: PointerEventState) {} + + pointerUp(_evt: PointerEventState) {} + unmounted() {} } diff --git a/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool-ext/mind-map-ext/mind-map-ext.ts b/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool-ext/mind-map-ext/mind-map-ext.ts index d8368323bc81e..bf3082491d4e4 100644 --- a/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool-ext/mind-map-ext/mind-map-ext.ts +++ b/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool-ext/mind-map-ext/mind-map-ext.ts @@ -39,6 +39,10 @@ type DragMindMapCtx = { export class MindMapExt extends DefaultToolExt { private _responseAreaUpdated = new Set(); + override supportedDragTypes: DefaultModeDragType[] = [ + DefaultModeDragType.ContentMoving, + ]; + private get _indicatorOverlay() { return this.std.getOptional( OverlayIdentifier('mindmap-indicator') diff --git a/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool.ts b/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool.ts index cef4cc67050f2..6fb690e777dfc 100644 --- a/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool.ts +++ b/packages/blocks/src/root-block/edgeless/gfx-tool/default-tool.ts @@ -66,6 +66,7 @@ import { mountTextElementEditor, } from '../utils/text.js'; import { fitToScreen } from '../utils/viewport.js'; +import { CanvasElementEventExt } from './default-tool-ext/event-ext.js'; import { DefaultModeDragType } from './default-tool-ext/ext.js'; import { MindMapExt } from './default-tool-ext/mind-map-ext/mind-map-ext.js'; @@ -241,6 +242,12 @@ export class DefaultTool extends BaseTool { ) as EdgelessFrameManager; } + private get _supportedExts() { + return this._exts.filter(ext => + ext.supportedDragTypes.includes(this.dragType) + ); + } + /** * Get the end position of the dragging area in the model coordinate */ @@ -578,7 +585,7 @@ export class DefaultTool extends BaseTool { event, }; - this._extHandlers = this._exts.map(ext => ext.initDrag(ctx)); + this._extHandlers = this._supportedExts.map(ext => ext.initDrag(ctx)); this._selectedBounds = this._toBeMoved.map(element => Bound.deserialize(element.xywh) ); @@ -732,6 +739,7 @@ export class DefaultTool extends BaseTool { } this._isDoubleClickedOnMask = false; + this._supportedExts.forEach(ext => ext.click?.(e)); } override deactivate() { @@ -809,6 +817,8 @@ export class DefaultTool extends BaseTool { } } + this._supportedExts.forEach(ext => ext.click?.(e)); + if ( e.raw.target && e.raw.target instanceof HTMLElement && @@ -990,14 +1000,21 @@ export class DefaultTool extends BaseTool { }) ); - this._exts = [MindMapExt].map(constructor => new constructor(this)); + this._exts = [MindMapExt, CanvasElementEventExt].map( + constructor => new constructor(this) + ); this._exts.forEach(ext => ext.mounted()); } + override pointerDown(e: PointerEventState): void { + this._supportedExts.forEach(ext => ext.pointerDown(e)); + } + override pointerMove(e: PointerEventState) { const hovered = this._pick(e.x, e.y, { hitThreshold: 10, }); + if ( isFrameBlock(hovered) && hovered.externalBound?.isPointInBound( @@ -1008,6 +1025,12 @@ export class DefaultTool extends BaseTool { } else { this.frameOverlay.clear(); } + + this._supportedExts.forEach(ext => ext.pointerMove(e)); + } + + override pointerUp(e: PointerEventState) { + this._supportedExts.forEach(ext => ext.pointerUp(e)); } override tripleClick() { diff --git a/packages/framework/block-std/src/gfx/model/base.ts b/packages/framework/block-std/src/gfx/model/base.ts index e4635d01c1455..d8d54c3c3ba14 100644 --- a/packages/framework/block-std/src/gfx/model/base.ts +++ b/packages/framework/block-std/src/gfx/model/base.ts @@ -7,6 +7,7 @@ import type { XYWH, } from '@blocksuite/global/utils'; +import type { PointerEventState } from '../../event/index.js'; import type { EditorHost } from '../../view/element/lit-host.js'; import type { GfxGroupModel, GfxModel } from './model.js'; @@ -42,6 +43,20 @@ export interface GfxCompatibleInterface extends IBound, GfxElementGeometry { readonly deserializedXYWH: XYWH; readonly elementBound: Bound; + + onPointerEnter?: (e: PointerEventState) => void; + + onPointerLeave?: (e: PointerEventState) => void; + + onPointerMove?: (e: PointerEventState) => void; + + onPointerDown?: (e: PointerEventState) => void; + + onPointerUp?: (e: PointerEventState) => void; + + onClick?: (e: PointerEventState) => void; + + onDblClick?: (e: PointerEventState) => void; } /** diff --git a/packages/framework/block-std/src/gfx/model/surface/element-model.ts b/packages/framework/block-std/src/gfx/model/surface/element-model.ts index a778c6cd03da4..e12dcc8251c6c 100644 --- a/packages/framework/block-std/src/gfx/model/surface/element-model.ts +++ b/packages/framework/block-std/src/gfx/model/surface/element-model.ts @@ -17,6 +17,7 @@ import { import { DocCollection, type Y } from '@blocksuite/store'; import { createMutex } from 'lib0/mutex'; +import type { PointerEventState } from '../../../event/index.js'; import type { EditorHost } from '../../../view/index.js'; import type { GfxCompatibleInterface, @@ -79,6 +80,20 @@ export abstract class GfxPrimitiveElementModel< protected _stashed: Map; + onClick?: ((e: PointerEventState) => void) | undefined; + + onDblClick?: ((e: PointerEventState) => void) | undefined; + + onPointerDown?: ((e: PointerEventState) => void) | undefined; + + onPointerEnter?: ((e: PointerEventState) => void) | undefined; + + onPointerLeave?: ((e: PointerEventState) => void) | undefined; + + onPointerMove?: ((e: PointerEventState) => void) | undefined; + + onPointerUp?: ((e: PointerEventState) => void) | undefined; + abstract rotate: number; surface!: SurfaceBlockModel; diff --git a/packages/framework/block-std/src/gfx/model/surface/local-element-model.ts b/packages/framework/block-std/src/gfx/model/surface/local-element-model.ts index 24fcd43986f6a..57ee514de33ed 100644 --- a/packages/framework/block-std/src/gfx/model/surface/local-element-model.ts +++ b/packages/framework/block-std/src/gfx/model/surface/local-element-model.ts @@ -11,6 +11,7 @@ import { rotatePoints, } from '@blocksuite/global/utils'; +import type { PointerEventState } from '../../../event/index.js'; import type { EditorHost } from '../../../view/index.js'; import type { GfxCompatibleInterface, PointTestOptions } from '../base.js'; import type { GfxGroupModel } from '../model.js'; @@ -31,6 +32,20 @@ export abstract class GfxLocalElementModel implements GfxCompatibleInterface { index: string = 'a0'; + onClick?: (e: PointerEventState) => void; + + onDblClick?: (e: PointerEventState) => void; + + onPointerDown?: (e: PointerEventState) => void; + + onPointerEnter?: (e: PointerEventState) => void; + + onPointerLeave?: (e: PointerEventState) => void; + + onPointerMove?: (e: PointerEventState) => void; + + onPointerUp?: (e: PointerEventState) => void; + opacity: number = 1; rotate: number = 0;