mirror of
https://github.com/motajs/template.git
synced 2026-11-16 00:44:43 +08:00
refactor: 渲染系统从 eventemitter3 换为钩子系统
This commit is contained in:
1 parent
e094ac28a4
commit
7acb1d52e7
12 files changed
+334
-316
No files matched your search
@@ -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."
|
||||
}
|
||||
}
|
||||
@@ -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 EventEmitter.ValidEventTypes> =
|
||||
T extends string | symbol
|
||||
? T
|
||||
: {
|
||||
[P in keyof T]: T[P] extends any[]
|
||||
? (...args: T[P]) => void
|
||||
: (...args: any[]) => void;
|
||||
};
|
||||
|
||||
type MappingEvent<E extends ERenderItemEvent> = {
|
||||
[P in keyof WrapEventEmitterEvents<E> as P extends string
|
||||
? `on${Capitalize<P>}`
|
||||
: never]?: WrapEventEmitterEvents<E>[P];
|
||||
};
|
||||
|
||||
export type TagDefine<T extends object, E extends ERenderItemEvent> = T &
|
||||
MappingEvent<E> &
|
||||
ReservedProps;
|
||||
export type TagDefine<T extends object, H extends IRenderItemBaseHooks> = T & {
|
||||
[K in keyof H]?: H[K];
|
||||
} & ReservedProps;
|
||||
|
||||
declare module 'vue/jsx-runtime' {
|
||||
namespace JSX {
|
||||
export interface IntrinsicElements {
|
||||
container: TagDefine<ContainerProps, ERenderItemEvent>;
|
||||
container: TagDefine<ContainerProps, IRenderItemBaseHooks>;
|
||||
'container-custom': TagDefine<
|
||||
ConatinerCustomProps,
|
||||
ERenderItemEvent
|
||||
IRenderItemBaseHooks
|
||||
>;
|
||||
shader: TagDefine<ShaderProps, ERenderItemEvent>;
|
||||
text: TagDefine<TextProps, ERenderItemEvent>;
|
||||
image: TagDefine<ImageProps, ERenderItemEvent>;
|
||||
animated: TagDefine<AnimatedProps, ERenderItemEvent>;
|
||||
comment: TagDefine<CommentProps, ERenderItemEvent>;
|
||||
custom: TagDefine<CustomProps, ERenderItemEvent>;
|
||||
'g-rect': TagDefine<RectProps, ERenderItemEvent>;
|
||||
'g-circle': TagDefine<CirclesProps, ERenderItemEvent>;
|
||||
'g-ellipse': TagDefine<EllipseProps, ERenderItemEvent>;
|
||||
'g-line': TagDefine<LineProps, ERenderItemEvent>;
|
||||
'g-bezier': TagDefine<BezierProps, ERenderItemEvent>;
|
||||
'g-quad': TagDefine<QuadraticProps, ERenderItemEvent>;
|
||||
'g-path': TagDefine<PathProps, ERenderItemEvent>;
|
||||
'g-rectr': TagDefine<RectRProps, ERenderItemEvent>;
|
||||
shader: TagDefine<ShaderProps, IRenderItemBaseHooks>;
|
||||
text: TagDefine<TextProps, IRenderItemBaseHooks>;
|
||||
image: TagDefine<ImageProps, IRenderItemBaseHooks>;
|
||||
animated: TagDefine<AnimatedProps, IRenderItemBaseHooks>;
|
||||
comment: TagDefine<CommentProps, IRenderItemBaseHooks>;
|
||||
custom: TagDefine<CustomProps, IRenderItemBaseHooks>;
|
||||
'g-rect': TagDefine<RectProps, IRenderItemBaseHooks>;
|
||||
'g-circle': TagDefine<CirclesProps, IRenderItemBaseHooks>;
|
||||
'g-ellipse': TagDefine<EllipseProps, IRenderItemBaseHooks>;
|
||||
'g-line': TagDefine<LineProps, IRenderItemBaseHooks>;
|
||||
'g-bezier': TagDefine<BezierProps, IRenderItemBaseHooks>;
|
||||
'g-quad': TagDefine<QuadraticProps, IRenderItemBaseHooks>;
|
||||
'g-path': TagDefine<PathProps, IRenderItemBaseHooks>;
|
||||
'g-rectr': TagDefine<RectRProps, IRenderItemBaseHooks>;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<P, E>;
|
||||
H extends IRenderItemBaseHooks = IRenderItemBaseHooks
|
||||
> = TagDefine<P, H>;
|
||||
|
||||
//#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<T>
|
||||
>(
|
||||
item: IRenderItem,
|
||||
key: K,
|
||||
listener: EventEmitter.EventListener<T, K>
|
||||
): void;
|
||||
|
||||
/**
|
||||
* 创建一个动画执行器。
|
||||
*
|
||||
|
||||
@@ -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<T>
|
||||
>(
|
||||
item: IRenderItem,
|
||||
key: K,
|
||||
listener: EventEmitter.EventListener<T, K>
|
||||
): void {
|
||||
item.on(key, listener);
|
||||
onUnmounted(() => {
|
||||
item.off(key, listener);
|
||||
});
|
||||
}
|
||||
|
||||
useAnimater(excitation: IExcitation<number>): IAnimater {
|
||||
const anim = new Animater();
|
||||
onMounted(() => {
|
||||
|
||||
@@ -87,9 +87,7 @@ export interface IScanAnimaterData {
|
||||
/**
|
||||
* 扫描动画控制器,会按照先从左到右,再从上到下的顺序依次输出,可以用于动画精灵图等
|
||||
*/
|
||||
export class TextureScanAnimater
|
||||
implements ITextureAnimater<IScanAnimaterData>
|
||||
{
|
||||
export class TextureScanAnimater implements ITextureAnimater<IScanAnimaterData> {
|
||||
*once(
|
||||
texture: ITexture,
|
||||
data: IScanAnimaterData
|
||||
|
||||
@@ -101,9 +101,7 @@ interface MaxRectsRectangle extends IRectangle {
|
||||
readonly data: ITexture;
|
||||
}
|
||||
|
||||
export class TextureMaxRectsStreamComposer
|
||||
implements ITextureStreamComposer<void>
|
||||
{
|
||||
export class TextureMaxRectsStreamComposer implements ITextureStreamComposer<void> {
|
||||
/** Max Rects 打包器 */
|
||||
readonly packer: MaxRectsPacker<MaxRectsRectangle>;
|
||||
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
type AdapterFunction<T> = (item: T, ...params: any[]) => Promise<any>;
|
||||
type SyncAdapterFunction<T> = (item: T, ...params: any[]) => any;
|
||||
type GlobalAdapterFunction = (...params: any[]) => Promise<any>;
|
||||
|
||||
/**
|
||||
* 渲染适配器,用作渲染层与数据层沟通的桥梁,用于在数据层等待渲染层执行,常用与动画等。
|
||||
* 例如移动图块,移动图块操作先由数据层执行,删除原有图块,然后通知渲染层进行移动动画,
|
||||
* 而移动动画是耗时的,因此需要通过本类进行适配,实现等待移动动画执行完毕,然后进行后续操作
|
||||
*/
|
||||
export class RenderAdapter<T> {
|
||||
static adapters: Map<string, RenderAdapter<any>> = new Map();
|
||||
|
||||
/** 所有元素的集合 */
|
||||
items: Set<T> = new Set();
|
||||
/** 适配器的id */
|
||||
id: string;
|
||||
|
||||
private execute: Map<string, AdapterFunction<T>> = new Map();
|
||||
private syncExecutes: Map<string, SyncAdapterFunction<T>> = new Map();
|
||||
private globalExecutes: Map<string, GlobalAdapterFunction> = 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<T>): void {
|
||||
this.execute.set(id, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置同步执行函数
|
||||
* @param fn 对于每个元素执行的函数
|
||||
*/
|
||||
receiveSync(id: string, fn: SyncAdapterFunction<T>): void {
|
||||
this.syncExecutes.set(id, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对所有元素执行函数,当所有元素都运行完毕后兑现,类似于Promise.all
|
||||
* @returns 包含每个元素运行结果的数组
|
||||
*/
|
||||
all<R = any>(fn: string, ...params: any[]): Promise<R[]> {
|
||||
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<R = any>(fn: string, ...params: any[]): Promise<R> {
|
||||
const execute = this.execute.get(fn);
|
||||
if (!execute) {
|
||||
return Promise.reject();
|
||||
} else {
|
||||
return Promise.any(
|
||||
[...this.items].map(v => execute!(v, ...params))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对所有元素执行同步函数
|
||||
* @returns 包含每个元素运行结果的数组
|
||||
*/
|
||||
sync<R = any>(fn: string, ...params: any[]): R[] {
|
||||
const execute = this.syncExecutes.get(fn);
|
||||
if (!execute) {
|
||||
return [];
|
||||
} else {
|
||||
return [...this.items].map(v => execute!(v, ...params));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用一个全局函数
|
||||
*/
|
||||
global<R = any>(id: string, ...params: any[]): Promise<R> {
|
||||
const execute = this.globalExecutes.get(id);
|
||||
if (!execute) {
|
||||
return Promise.reject();
|
||||
} else {
|
||||
return execute(...params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁这个adapter
|
||||
*/
|
||||
destroy() {
|
||||
RenderAdapter.adapters.delete(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取适配器
|
||||
*/
|
||||
static get<T>(id: string): RenderAdapter<T> | undefined {
|
||||
return this.adapters.get(id);
|
||||
}
|
||||
}
|
||||
@@ -142,13 +142,3 @@ export interface ActionEventMap {
|
||||
[ActionType.Up]: IActionEvent;
|
||||
[ActionType.Wheel]: IWheelEvent;
|
||||
}
|
||||
|
||||
export const eventNameMap: Record<ActionType, keyof ERenderItemActionEvent> = {
|
||||
[ActionType.Click]: 'click',
|
||||
[ActionType.Down]: 'down',
|
||||
[ActionType.Move]: 'move',
|
||||
[ActionType.Up]: 'up',
|
||||
[ActionType.Enter]: 'enter',
|
||||
[ActionType.Leave]: 'leave',
|
||||
[ActionType.Wheel]: 'wheel'
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './adapter';
|
||||
export * from './canvas2d';
|
||||
export * from './container';
|
||||
export * from './custom';
|
||||
|
||||
@@ -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<ERenderItemEvent>
|
||||
extends Hookable<IRenderItemBaseHooks>
|
||||
implements IRenderItem, ITransformUpdatable<Transform>
|
||||
{
|
||||
//#region 元素属性
|
||||
@@ -123,6 +127,11 @@ export abstract class RenderItem
|
||||
|
||||
//#region 交互事件
|
||||
|
||||
/** 当前元素包含的监听事件名称列表 */
|
||||
private readonly eventNames: Set<string> = new Set();
|
||||
/** 当前元素包含的监听事件,这是为了能够使用 `prop` 传入时不需要为每个内容都添加一个 `hook` 对象 */
|
||||
private readonly events: Map<string, (...params: any[]) => any> = new Map();
|
||||
|
||||
/** 是否调用了 `ev.stopPropagation` */
|
||||
protected propagationStoped: Map<ActionType, boolean> = 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<IRenderItemBaseHooks>
|
||||
): IHookController<IRenderItemBaseHooks> {
|
||||
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<T extends ActionType>(
|
||||
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<string, (...params: any[]) => any>) {}
|
||||
|
||||
onClick(ev: Readonly<IActionEvent>): void {
|
||||
this.list.get('onClick')?.(ev);
|
||||
}
|
||||
|
||||
onClickCapture(ev: Readonly<IActionEvent>): void {
|
||||
this.list.get('onClickCapture')?.(ev);
|
||||
}
|
||||
|
||||
onDown(ev: Readonly<IActionEvent>): void {
|
||||
this.list.get('onDown')?.(ev);
|
||||
}
|
||||
|
||||
onDownCapture(ev: Readonly<IActionEvent>): void {
|
||||
this.list.get('onDownCapture')?.(ev);
|
||||
}
|
||||
|
||||
onMove(ev: Readonly<IActionEvent>): void {
|
||||
this.list.get('onMove')?.(ev);
|
||||
}
|
||||
|
||||
onMoveCapture(ev: Readonly<IActionEvent>): void {
|
||||
this.list.get('onMoveCapture')?.(ev);
|
||||
}
|
||||
|
||||
onUp(ev: Readonly<IActionEvent>): void {
|
||||
this.list.get('onUp')?.(ev);
|
||||
}
|
||||
|
||||
onUpCapture(ev: Readonly<IActionEvent>): void {
|
||||
this.list.get('onUpCapture')?.(ev);
|
||||
}
|
||||
|
||||
onEnter(ev: Readonly<IActionEventBase>): void {
|
||||
this.list.get('onEnter')?.(ev);
|
||||
}
|
||||
|
||||
onLeave(ev: Readonly<IActionEventBase>): void {
|
||||
this.list.get('onLeave')?.(ev);
|
||||
}
|
||||
|
||||
onWheel(ev: Readonly<IWheelEvent>): void {
|
||||
this.list.get('onWheel')?.(ev);
|
||||
}
|
||||
|
||||
onWheelCapture(ev: Readonly<IWheelEvent>): 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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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<IActionEvent>): void;
|
||||
|
||||
/**
|
||||
* 当这个元素被点击时触发,触发阶段为捕获阶段,点击要求按下与抬起都在同一个元素上完成
|
||||
* @param ev 交互事件对象
|
||||
*/
|
||||
onClickCapture?(ev: Readonly<IActionEvent>): void;
|
||||
|
||||
/**
|
||||
* 当鼠标或触屏在这个元素上按下时触发,触发阶段为冒泡阶段
|
||||
* @param ev 交互事件对象
|
||||
*/
|
||||
onDown?(ev: Readonly<IActionEvent>): void;
|
||||
|
||||
/**
|
||||
* 当鼠标或触屏在这个元素上按下时触发,触发阶段为捕获阶段
|
||||
* @param ev 交互事件对象
|
||||
*/
|
||||
onDownCapture?(ev: Readonly<IActionEvent>): void;
|
||||
|
||||
/**
|
||||
* 当鼠标或触屏在这个元素上移动时触发,触发阶段为冒泡阶段
|
||||
* @param ev 交互事件对象
|
||||
*/
|
||||
onMove?(ev: Readonly<IActionEvent>): void;
|
||||
|
||||
/**
|
||||
* 当鼠标或触屏在这个元素上移动时触发,触发阶段为捕获阶段
|
||||
* @param ev 交互事件对象
|
||||
*/
|
||||
onMoveCapture?(ev: Readonly<IActionEvent>): void;
|
||||
|
||||
/**
|
||||
* 当鼠标或触屏在这个元素上抬起时触发,触发阶段为冒泡阶段
|
||||
* @param ev 交互事件对象
|
||||
*/
|
||||
onUp?(ev: Readonly<IActionEvent>): void;
|
||||
|
||||
/**
|
||||
* 当鼠标或触屏在这个元素上抬起时触发,触发阶段为捕获阶段
|
||||
* @param ev 交互事件对象
|
||||
*/
|
||||
onUpCapture?(ev: Readonly<IActionEvent>): void;
|
||||
|
||||
/**
|
||||
* 当鼠标或触屏进入这个元素时触发,不区分捕获和冒泡阶段,在绝大多数情况下能保证触发顺序
|
||||
* @param ev 交互事件对象
|
||||
*/
|
||||
onEnter?(ev: Readonly<IActionEventBase>): void;
|
||||
|
||||
/**
|
||||
* 当鼠标或触屏离开这个元素时触发,不区分捕获和冒泡阶段,在绝大多数情况下能保证触发顺序
|
||||
* @param ev 交互事件对象
|
||||
*/
|
||||
onLeave?(ev: Readonly<IActionEventBase>): void;
|
||||
|
||||
/**
|
||||
* 当鼠标在当前元素上滚动时触发(某些触屏手势也可以触发),触发阶段为冒泡阶段
|
||||
* @param ev 交互事件对象
|
||||
*/
|
||||
onWheel?(ev: Readonly<IWheelEvent>): void;
|
||||
|
||||
/**
|
||||
* 当鼠标在当前元素上滚动时触发(某些触屏手势也可以触发),触发阶段为冒泡阶段
|
||||
* @param ev 交互事件对象
|
||||
*/
|
||||
onWheelCapture?(ev: Readonly<IWheelEvent>): void;
|
||||
|
||||
/**
|
||||
* 当元素的尺寸被修改时触发
|
||||
* @param width 元素宽度
|
||||
* @param height 元素高度
|
||||
*/
|
||||
onResize?(width: number, height: number): void;
|
||||
|
||||
/**
|
||||
* 当元素的变换矩阵发生变化时触发
|
||||
* @param transform 变换矩阵对象
|
||||
*/
|
||||
onTransform?(transform: Transform): void;
|
||||
}
|
||||
|
||||
export interface IRenderItem
|
||||
extends ITransformUpdatable<Transform>, EventEmitter<ERenderItemEvent> {
|
||||
extends ITransformUpdatable<Transform>, IHookable<IRenderItemBaseHooks> {
|
||||
/** 是否为根元素 */
|
||||
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 其他接口
|
||||
|
||||
Reference in new issue
Block a user