From 7acb1d52e71d2a641dd50a51d3e5a92aa0091d0d Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Wed, 11 Nov 2026 15:50:58 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=B8=B2=E6=9F=93=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E4=BB=8E=20eventemitter3=20=E6=8D=A2=E4=B8=BA?= =?UTF-8?q?=E9=92=A9=E5=AD=90=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/common/src/logger.json | 1 + packages/render-vue/src/elements.ts | 56 ++-- packages/render-vue/src/types.ts | 33 +-- packages/render-vue/src/use.ts | 17 +- packages/render/src/assets/animater.ts | 4 +- packages/render/src/assets/streamComposer.ts | 4 +- packages/render/src/core/adapter.ts | 135 ---------- packages/render/src/core/event.ts | 10 - packages/render/src/core/index.ts | 1 - packages/render/src/core/item.ts | 256 +++++++++++++++---- packages/render/src/core/render.ts | 27 +- packages/render/src/core/types.ts | 106 +++++++- 12 files changed, 334 insertions(+), 316 deletions(-) delete mode 100644 packages/render/src/core/adapter.ts diff --git a/packages/common/src/logger.json b/packages/common/src/logger.json index de4228c..ce0c9eb 100644 --- a/packages/common/src/logger.json +++ b/packages/common/src/logger.json @@ -320,6 +320,7 @@ "212": "Duplicate implements for hotkey command '$1', old one will be overwritten. This probably caused by unclearing implementation config, please check.", "213": "Setting item '$1' has existed in group '$2'. The item will be moved to new group '$3'.", "214": "Range setting item get a number range that cannot be divided into integer by step: range=[$1, $2], step=$3. This may cause issues, please ensure number range can be divided by step evenly.", + "215": "Cannot recognize '$1' as a proper render item prop or emit.", "1001": "Event(setBlock): Unknown tile '$1' when setting block." } } diff --git a/packages/render-vue/src/elements.ts b/packages/render-vue/src/elements.ts index d486ceb..2d291d6 100644 --- a/packages/render-vue/src/elements.ts +++ b/packages/render-vue/src/elements.ts @@ -1,5 +1,4 @@ import { ReservedProps } from 'vue'; -import EventEmitter from 'eventemitter3'; import { AnimatedProps, BezierProps, @@ -18,49 +17,34 @@ import { ShaderProps, TextProps } from './props'; -import { ERenderItemEvent } from '@motajs/render'; +import { IRenderItemBaseHooks } from '@motajs/render'; -export type WrapEventEmitterEvents = - T extends string | symbol - ? T - : { - [P in keyof T]: T[P] extends any[] - ? (...args: T[P]) => void - : (...args: any[]) => void; - }; - -type MappingEvent = { - [P in keyof WrapEventEmitterEvents as P extends string - ? `on${Capitalize

}` - : never]?: WrapEventEmitterEvents[P]; -}; - -export type TagDefine = T & - MappingEvent & - ReservedProps; +export type TagDefine = T & { + [K in keyof H]?: H[K]; +} & ReservedProps; declare module 'vue/jsx-runtime' { namespace JSX { export interface IntrinsicElements { - container: TagDefine; + container: TagDefine; 'container-custom': TagDefine< ConatinerCustomProps, - ERenderItemEvent + IRenderItemBaseHooks >; - shader: TagDefine; - text: TagDefine; - image: TagDefine; - animated: TagDefine; - comment: TagDefine; - custom: TagDefine; - 'g-rect': TagDefine; - 'g-circle': TagDefine; - 'g-ellipse': TagDefine; - 'g-line': TagDefine; - 'g-bezier': TagDefine; - 'g-quad': TagDefine; - 'g-path': TagDefine; - 'g-rectr': TagDefine; + shader: TagDefine; + text: TagDefine; + image: TagDefine; + animated: TagDefine; + comment: TagDefine; + custom: TagDefine; + 'g-rect': TagDefine; + 'g-circle': TagDefine; + 'g-ellipse': TagDefine; + 'g-line': TagDefine; + 'g-bezier': TagDefine; + 'g-quad': TagDefine; + 'g-path': TagDefine; + 'g-rectr': TagDefine; } } } diff --git a/packages/render-vue/src/types.ts b/packages/render-vue/src/types.ts index a90549e..1865970 100644 --- a/packages/render-vue/src/types.ts +++ b/packages/render-vue/src/types.ts @@ -1,4 +1,8 @@ -import { ERenderItemEvent, IRenderItem, IRenderTreeRoot } from '@motajs/render'; +import { + IRenderItem, + IRenderItemBaseHooks, + IRenderTreeRoot +} from '@motajs/render'; import { VNodeProps } from 'vue'; import { BaseProps } from './props'; import { TagDefine } from './elements'; @@ -8,7 +12,6 @@ import { IExcitation, ITransition } from '@motajs/animate'; -import EventEmitter from 'eventemitter3'; //#region 标签管理 @@ -57,8 +60,8 @@ export interface IRenderTagManager { export type DefaultProps< P extends BaseProps = BaseProps, - E extends ERenderItemEvent = ERenderItemEvent -> = TagDefine; + H extends IRenderItemBaseHooks = IRenderItemBaseHooks +> = TagDefine; //#endregion @@ -88,28 +91,6 @@ export interface IRendererUsing { */ onExcitedFunc(fn: (payload: number) => void): void; - /** - * 监听渲染元素的指定事件。 - * - * 在组件内如果你需要监听事件,应该先考虑使用 `JSX` 的 `onXxx` 来监听事件,而不是使用此接口。 - * - * 如果 `JSX` 的 `onXxx` 不能满足你的需求,再考虑使用此接口,在组件内**不要**直接使用 `item.on` 来监听事件, - * 否则可能导致事件监听没有正确删除,出现内存泄漏问题。 - * - * 在**组件外不要**使用此接口,否则可能没有效果! - * @param item 要监听的渲染元素 - * @param key 事件类型 - * @param listener 当事件触发时执行的函数 - */ - listenEvent< - T extends ERenderItemEvent, - K extends EventEmitter.EventNames - >( - item: IRenderItem, - key: K, - listener: EventEmitter.EventListener - ): void; - /** * 创建一个动画执行器。 * diff --git a/packages/render-vue/src/use.ts b/packages/render-vue/src/use.ts index b23674c..d56d304 100644 --- a/packages/render-vue/src/use.ts +++ b/packages/render-vue/src/use.ts @@ -1,6 +1,5 @@ -import { ERenderItemEvent, IRenderItem, IRenderTreeRoot } from '@motajs/render'; +import { IRenderTreeRoot } from '@motajs/render'; import { onMounted, onUnmounted } from 'vue'; -import EventEmitter from 'eventemitter3'; import { IRendererUsing } from './types'; import { IExcitable, @@ -28,20 +27,6 @@ export class RendererUsing implements IRendererUsing { this.onExcited(excited(fn)); } - listenEvent< - T extends ERenderItemEvent, - K extends EventEmitter.EventNames - >( - item: IRenderItem, - key: K, - listener: EventEmitter.EventListener - ): void { - item.on(key, listener); - onUnmounted(() => { - item.off(key, listener); - }); - } - useAnimater(excitation: IExcitation): IAnimater { const anim = new Animater(); onMounted(() => { diff --git a/packages/render/src/assets/animater.ts b/packages/render/src/assets/animater.ts index 4b2ffe4..c117cb4 100644 --- a/packages/render/src/assets/animater.ts +++ b/packages/render/src/assets/animater.ts @@ -87,9 +87,7 @@ export interface IScanAnimaterData { /** * 扫描动画控制器,会按照先从左到右,再从上到下的顺序依次输出,可以用于动画精灵图等 */ -export class TextureScanAnimater - implements ITextureAnimater -{ +export class TextureScanAnimater implements ITextureAnimater { *once( texture: ITexture, data: IScanAnimaterData diff --git a/packages/render/src/assets/streamComposer.ts b/packages/render/src/assets/streamComposer.ts index d8f6e7a..d169a4e 100644 --- a/packages/render/src/assets/streamComposer.ts +++ b/packages/render/src/assets/streamComposer.ts @@ -101,9 +101,7 @@ interface MaxRectsRectangle extends IRectangle { readonly data: ITexture; } -export class TextureMaxRectsStreamComposer - implements ITextureStreamComposer -{ +export class TextureMaxRectsStreamComposer implements ITextureStreamComposer { /** Max Rects 打包器 */ readonly packer: MaxRectsPacker; diff --git a/packages/render/src/core/adapter.ts b/packages/render/src/core/adapter.ts deleted file mode 100644 index 7ff4abd..0000000 --- a/packages/render/src/core/adapter.ts +++ /dev/null @@ -1,135 +0,0 @@ -type AdapterFunction = (item: T, ...params: any[]) => Promise; -type SyncAdapterFunction = (item: T, ...params: any[]) => any; -type GlobalAdapterFunction = (...params: any[]) => Promise; - -/** - * 渲染适配器,用作渲染层与数据层沟通的桥梁,用于在数据层等待渲染层执行,常用与动画等。 - * 例如移动图块,移动图块操作先由数据层执行,删除原有图块,然后通知渲染层进行移动动画, - * 而移动动画是耗时的,因此需要通过本类进行适配,实现等待移动动画执行完毕,然后进行后续操作 - */ -export class RenderAdapter { - static adapters: Map> = new Map(); - - /** 所有元素的集合 */ - items: Set = new Set(); - /** 适配器的id */ - id: string; - - private execute: Map> = new Map(); - private syncExecutes: Map> = new Map(); - private globalExecutes: Map = new Map(); - - constructor(id: string) { - this.id = id; - RenderAdapter.adapters.set(id, this); - } - - /** - * 添加一个元素 - */ - add(item: T) { - this.items.add(item); - } - - /** - * 移除一个元素 - */ - remove(item: T) { - this.items.delete(item); - } - - /** - * 定义一个类似于静态函数的函数,只会调用一次,不会对每个元素执行 - * @param id 函数名称 - * @param fn 执行的函数 - */ - receiveGlobal(id: string, fn: GlobalAdapterFunction) { - this.globalExecutes.set(id, fn); - } - - /** - * 设置执行函数 - * @param fn 对于每个元素执行的函数 - */ - receive(id: string, fn: AdapterFunction): void { - this.execute.set(id, fn); - } - - /** - * 设置同步执行函数 - * @param fn 对于每个元素执行的函数 - */ - receiveSync(id: string, fn: SyncAdapterFunction): void { - this.syncExecutes.set(id, fn); - } - - /** - * 对所有元素执行函数,当所有元素都运行完毕后兑现,类似于Promise.all - * @returns 包含每个元素运行结果的数组 - */ - all(fn: string, ...params: any[]): Promise { - const execute = this.execute.get(fn); - - if (!execute) { - return Promise.reject(); - } else { - return Promise.all( - [...this.items].map(v => execute!(v, ...params)) - ); - } - } - - /** - * 对所有元素执行函数,当任意一个元素运行完毕后兑现,类似于Promise.any - * @returns 最先运行完毕的元素的结果 - */ - any(fn: string, ...params: any[]): Promise { - const execute = this.execute.get(fn); - if (!execute) { - return Promise.reject(); - } else { - return Promise.any( - [...this.items].map(v => execute!(v, ...params)) - ); - } - } - - /** - * 对所有元素执行同步函数 - * @returns 包含每个元素运行结果的数组 - */ - sync(fn: string, ...params: any[]): R[] { - const execute = this.syncExecutes.get(fn); - if (!execute) { - return []; - } else { - return [...this.items].map(v => execute!(v, ...params)); - } - } - - /** - * 调用一个全局函数 - */ - global(id: string, ...params: any[]): Promise { - const execute = this.globalExecutes.get(id); - if (!execute) { - return Promise.reject(); - } else { - return execute(...params); - } - } - - /** - * 销毁这个adapter - */ - destroy() { - RenderAdapter.adapters.delete(this.id); - } - - /** - * 获取适配器 - */ - static get(id: string): RenderAdapter | undefined { - return this.adapters.get(id); - } -} diff --git a/packages/render/src/core/event.ts b/packages/render/src/core/event.ts index 6a0999e..a915e78 100644 --- a/packages/render/src/core/event.ts +++ b/packages/render/src/core/event.ts @@ -142,13 +142,3 @@ export interface ActionEventMap { [ActionType.Up]: IActionEvent; [ActionType.Wheel]: IWheelEvent; } - -export const eventNameMap: Record = { - [ActionType.Click]: 'click', - [ActionType.Down]: 'down', - [ActionType.Move]: 'move', - [ActionType.Up]: 'up', - [ActionType.Enter]: 'enter', - [ActionType.Leave]: 'leave', - [ActionType.Wheel]: 'wheel' -}; diff --git a/packages/render/src/core/index.ts b/packages/render/src/core/index.ts index 2ba497c..2da9538 100644 --- a/packages/render/src/core/index.ts +++ b/packages/render/src/core/index.ts @@ -1,4 +1,3 @@ -export * from './adapter'; export * from './canvas2d'; export * from './container'; export * from './custom'; diff --git a/packages/render/src/core/item.ts b/packages/render/src/core/item.ts index 1a91a43..3f405b8 100644 --- a/packages/render/src/core/item.ts +++ b/packages/render/src/core/item.ts @@ -1,23 +1,27 @@ import { isEqual, isNil } from 'lodash-es'; -import { EventEmitter } from 'eventemitter3'; import { MotaOffscreenCanvas2D } from './canvas2d'; import { ITransformUpdatable, Transform } from './transform'; -import { logger } from '@motajs/common'; +import { + Hookable, + HookController, + IHookController, + logger +} from '@motajs/common'; import { ElementNamespace, ComponentInternalInstance } from 'vue'; import { ActionEventMap, ActionType, - ERenderItemActionEvent, - eventNameMap, EventProgress, IActionEvent, + IActionEventBase, + IWheelEvent, MouseType } from './event'; import { vec3 } from 'gl-matrix'; import { - ERenderItemEvent, ExcitableDelegation, IRenderItem, + IRenderItemBaseHooks, IRenderTreeRoot, RenderPosition } from './types'; @@ -34,7 +38,7 @@ interface ToDelegateExcitables { } export abstract class RenderItem - extends EventEmitter + extends Hookable implements IRenderItem, ITransformUpdatable { //#region 元素属性 @@ -123,6 +127,11 @@ export abstract class RenderItem //#region 交互事件 + /** 当前元素包含的监听事件名称列表 */ + private readonly eventNames: Set = new Set(); + /** 当前元素包含的监听事件,这是为了能够使用 `prop` 传入时不需要为每个内容都添加一个 `hook` 对象 */ + private readonly events: Map any> = new Map(); + /** 是否调用了 `ev.stopPropagation` */ protected propagationStoped: Map = new Map(); /** 捕获阶段缓存的事件对象 */ @@ -170,6 +179,36 @@ export abstract class RenderItem } else { this.cache = null; } + this.eventNames + .add('onClick') + .add('onClickCapture') + .add('onDown') + .add('onDownCapture') + .add('onMove') + .add('onMoveCapture') + .add('onUp') + .add('onUpCapture') + .add('onEnter') + .add('onLeave') + .add('onWheel') + .add('onWheelCapture') + .add('onResize') + .add('onTransform'); + + this.addHook(this.getDefaultHook()); + } + + /** + * 获取默认钩子对象,子类可以拓展以实现自己默认钩子的 `prop` 支持 + */ + protected getDefaultHook(): IRenderItemBaseHooks { + return new RenderItemDefaultHooks(this.events); + } + + protected createController( + hook: Partial + ): IHookController { + return new HookController(this, hook); } setID(id: string): void { @@ -322,7 +361,7 @@ export abstract class RenderItem this.cache!.size(width, height); } this.update(); - this.emit('resize', width, height); + this.forEachHook(hook => hook.onResize?.(width, height)); } /** @@ -489,11 +528,31 @@ export abstract class RenderItem } updateTransform(transform: Transform) { - // 更新变换矩阵时,不需要更新自身的缓存,直接调用父元素的更新即可 - this.parent?.update(); + // 更新变换矩阵时,只需要更新有 `selfTransform` 的子元素,其他的不需要更新 + let childUpdated = false; + if (this.children.size > 0) { + const stack: RenderItem[] = [this]; + while (stack.length > 0) { + const item = stack.pop(); + if (!item || !item.selfTransform) continue; + childUpdated = true; + item.cacheDirty = true; + item.children.forEach(v => stack.push(v)); + } + } + // 根据子元素更新情况以及自身是否 `selfTransform` 来决定是更新自己还是更新父元素 + if (!childUpdated) { + if (this.selfTransform) { + this.update(); + } else { + this.parent?.update(); + } + } else { + this.parent?.update(); + } this.x = transform.x; this.y = transform.y; - this.emit('transform', this, transform); + this.forEachHook(hook => hook.onTransform?.(transform)); } requestBeforeFrame(fn: () => void): void { @@ -616,7 +675,7 @@ export abstract class RenderItem this.checkRoot(); this.transform.bind(this); this.onResize(parent.scale); - this.emit('resize', this.width, this.height); + this.forEachHook(hook => hook.onResize?.(this.width, this.height)); } /** @@ -690,22 +749,60 @@ export abstract class RenderItem this.noEvent = ignore; } - /** - * 根据事件类型和事件阶段获取事件名称 - * @param type 事件类型 - * @param progress 事件阶段 - */ - getEventName( - type: ActionType, - progress: EventProgress - ): keyof ERenderItemActionEvent { - if (type === ActionType.Enter || type === ActionType.Leave) { - return eventNameMap[type]; - } else if (progress === EventProgress.Capture) { - return `${eventNameMap[type]}Capture` as keyof ERenderItemActionEvent; - } else { - return eventNameMap[type]; + private emitEvent( + type: T, + progress: EventProgress, + ev: ActionEventMap[T] + ): ActionEventMap[T] { + if (type === ActionType.Enter) { + this.forEachHook(hook => hook.onEnter?.(ev)); + return ev; + } else if (type === ActionType.Leave) { + this.forEachHook(hook => hook.onLeave?.(ev)); + return ev; } + + if (progress === EventProgress.Capture) { + switch (type) { + case ActionType.Click: + this.forEachHook(hook => hook.onClickCapture?.(ev)); + break; + case ActionType.Down: + this.forEachHook(hook => hook.onDownCapture?.(ev)); + break; + case ActionType.Move: + this.forEachHook(hook => hook.onMoveCapture?.(ev)); + break; + case ActionType.Up: + this.forEachHook(hook => hook.onUpCapture?.(ev)); + break; + case ActionType.Wheel: + this.forEachHook(hook => + hook.onWheelCapture?.(ev as IWheelEvent) + ); + break; + } + } else { + switch (type) { + case ActionType.Click: + this.forEachHook(hook => hook.onClick?.(ev)); + break; + case ActionType.Down: + this.forEachHook(hook => hook.onDown?.(ev)); + break; + case ActionType.Move: + this.forEachHook(hook => hook.onMove?.(ev)); + break; + case ActionType.Up: + this.forEachHook(hook => hook.onUp?.(ev)); + break; + case ActionType.Wheel: + this.forEachHook(hook => hook.onWheel?.(ev as IWheelEvent)); + break; + } + } + + return ev; } /** @@ -734,13 +831,17 @@ export abstract class RenderItem event: ActionEventMap[T] ) { const ev = this.processEvent(type, progress, event); - if (ev) { - const name = this.getEventName(type, progress); - this.emit(name, ev); - if (!this.propagationStoped.get(type)) { - this.propagateEvent(type, progress, ev); - } + if (!ev) { + this.propagationStoped.set(type, false); + return ev; } + + this.emitEvent(type, progress, ev); + + if (!this.propagationStoped.get(type)) { + this.propagateEvent(type, progress, ev); + } + this.propagationStoped.set(type, false); return ev; } @@ -947,21 +1048,6 @@ export abstract class RenderItem } } - /** - * 解析事件key - * @param key 键名 - * @returns 返回字符串表示解析后的键名,返回空字符串说明没有对应的事件 - */ - protected parseEvent(key: string): string { - if (key.startsWith('on')) { - const code = key.charCodeAt(2); - if (code >= 65 && code <= 90) { - return key[2].toLowerCase() + key.slice(3); - } - } - return ''; - } - /** * `override` 以自定义 `vue` 参数处理,返回 false 以避免后续判断提高运行速度。 * @param _key 属性键名 @@ -1057,8 +1143,7 @@ export abstract class RenderItem } case 'transform': { if (!this.assertType(nextValue, Transform, key)) return; - this.transform = nextValue; - this.update(); + this.setTransform(nextValue); return; } case 'selfTransform': { @@ -1136,12 +1221,12 @@ export abstract class RenderItem return; } } - const ev = this.parseEvent(key); - if (ev.length > 0) { - if (prevValue) { - this.off(ev as keyof ERenderItemActionEvent, prevValue); - } - this.on(ev as keyof ERenderItemActionEvent, nextValue); + if (this.eventNames.has(key)) { + // 事件参数 + if (!this.assertType(nextValue, Function, key)) return; + this.events.set(key, nextValue); + } else { + logger.warn(215, key); } } @@ -1152,7 +1237,6 @@ export abstract class RenderItem */ destroy(): void { this.remove(); - this.removeAllListeners(); this.canvases.clear(); this.cache?.clear(); this.propagationStoped.clear(); @@ -1163,3 +1247,63 @@ export abstract class RenderItem this.setRoot(null); } } + +export class RenderItemDefaultHooks implements IRenderItemBaseHooks { + constructor(readonly list: Map any>) {} + + onClick(ev: Readonly): void { + this.list.get('onClick')?.(ev); + } + + onClickCapture(ev: Readonly): void { + this.list.get('onClickCapture')?.(ev); + } + + onDown(ev: Readonly): void { + this.list.get('onDown')?.(ev); + } + + onDownCapture(ev: Readonly): void { + this.list.get('onDownCapture')?.(ev); + } + + onMove(ev: Readonly): void { + this.list.get('onMove')?.(ev); + } + + onMoveCapture(ev: Readonly): void { + this.list.get('onMoveCapture')?.(ev); + } + + onUp(ev: Readonly): void { + this.list.get('onUp')?.(ev); + } + + onUpCapture(ev: Readonly): void { + this.list.get('onUpCapture')?.(ev); + } + + onEnter(ev: Readonly): void { + this.list.get('onEnter')?.(ev); + } + + onLeave(ev: Readonly): void { + this.list.get('onLeave')?.(ev); + } + + onWheel(ev: Readonly): void { + this.list.get('onWheel')?.(ev); + } + + onWheelCapture(ev: Readonly): void { + this.list.get('onWheelCapture')?.(ev); + } + + onResize(width: number, height: number): void { + this.list.get('onResize')?.(width, height); + } + + onTransform(transform: Transform): void { + this.list.get('onTransform')?.(transform); + } +} diff --git a/packages/render/src/core/render.ts b/packages/render/src/core/render.ts index f471440..9335d60 100644 --- a/packages/render/src/core/render.ts +++ b/packages/render/src/core/render.ts @@ -333,7 +333,8 @@ export class MotaRenderer this.getMouseType(ev) ); this.hoveredElement.forEach(v => { - v.emit('leave', this.createMouseActionBase(ev, id, v)); + const base = this.createMouseActionBase(ev, id, v); + v.forEachHook(hook => hook.onLeave?.(base)); }); this.hoveredElement.clear(); this.beforeHovered.clear(); @@ -677,18 +678,14 @@ export class MotaRenderer // 先 leave,再 enter before.forEach(v => { if (!now.has(v)) { - v.emit( - 'leave', - this.createMouseActionBase(event, ev.identifier, v) - ); + const e = this.createMouseActionBase(event, ev.identifier, v); + v.forEachHook(hook => hook.onLeave?.(e)); } }); now.forEach(v => { if (!before.has(v)) { - v.emit( - 'enter', - this.createMouseActionBase(event, ev.identifier, v) - ); + const e = this.createMouseActionBase(event, ev.identifier, v); + v.forEachHook(hook => hook.onEnter?.(e)); } }); } @@ -702,18 +699,14 @@ export class MotaRenderer // 先 leave,再 enter before.forEach(v => { if (!now.has(v)) { - v.emit( - 'leave', - this.createTouchActionBase(event, ev.identifier, v) - ); + const e = this.createTouchActionBase(event, ev.identifier, v); + v.forEachHook(hook => hook.onLeave?.(e)); } }); now.forEach(v => { if (!before.has(v)) { - v.emit( - 'enter', - this.createTouchActionBase(event, ev.identifier, v) - ); + const e = this.createTouchActionBase(event, ev.identifier, v); + v.forEachHook(hook => hook.onEnter?.(e)); } }); } diff --git a/packages/render/src/core/types.ts b/packages/render/src/core/types.ts index 4e5865c..fb90457 100644 --- a/packages/render/src/core/types.ts +++ b/packages/render/src/core/types.ts @@ -4,15 +4,18 @@ import { ActionEventMap, ActionType, ERenderItemActionEvent, - EventProgress + EventProgress, + IActionEvent, + IActionEventBase, + IWheelEvent } from './event'; import { MotaOffscreenCanvas2D } from './canvas2d'; import { Font } from '../style'; import { DefineComponent, DefineSetupFnComponent } from 'vue'; import { JSX } from 'vue/jsx-runtime'; -import EventEmitter from 'eventemitter3'; import { SizedCanvasImageSource } from '../types'; import { ITexture, ITextureAnimater } from '../assets'; +import { IHookable, IHookBase } from '@motajs/common'; //#region 功能类型 @@ -59,8 +62,95 @@ export interface ERenderItemEvent extends ERenderItemActionEvent { transform: [item: IRenderItem, transform: Transform]; } +export interface IRenderItemBaseHooks extends IHookBase { + /** + * 当这个元素被点击时触发,触发阶段为冒泡阶段,点击要求按下与抬起都在同一个元素上完成 + * @param ev 交互事件对象 + */ + onClick?(ev: Readonly): void; + + /** + * 当这个元素被点击时触发,触发阶段为捕获阶段,点击要求按下与抬起都在同一个元素上完成 + * @param ev 交互事件对象 + */ + onClickCapture?(ev: Readonly): void; + + /** + * 当鼠标或触屏在这个元素上按下时触发,触发阶段为冒泡阶段 + * @param ev 交互事件对象 + */ + onDown?(ev: Readonly): void; + + /** + * 当鼠标或触屏在这个元素上按下时触发,触发阶段为捕获阶段 + * @param ev 交互事件对象 + */ + onDownCapture?(ev: Readonly): void; + + /** + * 当鼠标或触屏在这个元素上移动时触发,触发阶段为冒泡阶段 + * @param ev 交互事件对象 + */ + onMove?(ev: Readonly): void; + + /** + * 当鼠标或触屏在这个元素上移动时触发,触发阶段为捕获阶段 + * @param ev 交互事件对象 + */ + onMoveCapture?(ev: Readonly): void; + + /** + * 当鼠标或触屏在这个元素上抬起时触发,触发阶段为冒泡阶段 + * @param ev 交互事件对象 + */ + onUp?(ev: Readonly): void; + + /** + * 当鼠标或触屏在这个元素上抬起时触发,触发阶段为捕获阶段 + * @param ev 交互事件对象 + */ + onUpCapture?(ev: Readonly): void; + + /** + * 当鼠标或触屏进入这个元素时触发,不区分捕获和冒泡阶段,在绝大多数情况下能保证触发顺序 + * @param ev 交互事件对象 + */ + onEnter?(ev: Readonly): void; + + /** + * 当鼠标或触屏离开这个元素时触发,不区分捕获和冒泡阶段,在绝大多数情况下能保证触发顺序 + * @param ev 交互事件对象 + */ + onLeave?(ev: Readonly): void; + + /** + * 当鼠标在当前元素上滚动时触发(某些触屏手势也可以触发),触发阶段为冒泡阶段 + * @param ev 交互事件对象 + */ + onWheel?(ev: Readonly): void; + + /** + * 当鼠标在当前元素上滚动时触发(某些触屏手势也可以触发),触发阶段为冒泡阶段 + * @param ev 交互事件对象 + */ + onWheelCapture?(ev: Readonly): void; + + /** + * 当元素的尺寸被修改时触发 + * @param width 元素宽度 + * @param height 元素高度 + */ + onResize?(width: number, height: number): void; + + /** + * 当元素的变换矩阵发生变化时触发 + * @param transform 变换矩阵对象 + */ + onTransform?(transform: Transform): void; +} + export interface IRenderItem - extends ITransformUpdatable, EventEmitter { + extends ITransformUpdatable, IHookable { /** 是否为根元素 */ readonly isRoot: boolean; /** 当前元素的父元素 */ @@ -363,16 +453,6 @@ export interface IRenderItem */ ignoreEvent(ignore: boolean): void; - /** - * 根据事件类型和事件阶段获取事件名称 - * @param type 事件类型 - * @param progress 事件阶段 - */ - getEventName( - type: ActionType, - progress: EventProgress - ): keyof ERenderItemActionEvent; - //#endregion //#region 其他接口