mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-25 00:23:25 +08:00
Compare commits
5 Commits
69da048438
...
fa7d2b2c16
Author | SHA1 | Date | |
---|---|---|---|
fa7d2b2c16 | |||
4d61de8735 | |||
48cc34aa2d | |||
4b2d94e422 | |||
b448f18616 |
@ -2549,8 +2549,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
|
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001651:
|
caniuse-lite@1.0.30001700:
|
||||||
resolution: {integrity: sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==}
|
resolution: {integrity: sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==}
|
||||||
|
|
||||||
ccount@2.0.1:
|
ccount@2.0.1:
|
||||||
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
||||||
@ -7241,7 +7241,7 @@ snapshots:
|
|||||||
autoprefixer@10.4.20(postcss@8.4.49):
|
autoprefixer@10.4.20(postcss@8.4.49):
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.23.3
|
browserslist: 4.23.3
|
||||||
caniuse-lite: 1.0.30001651
|
caniuse-lite: 1.0.30001700
|
||||||
fraction.js: 4.3.7
|
fraction.js: 4.3.7
|
||||||
normalize-range: 0.1.2
|
normalize-range: 0.1.2
|
||||||
picocolors: 1.0.1
|
picocolors: 1.0.1
|
||||||
@ -7320,7 +7320,7 @@ snapshots:
|
|||||||
|
|
||||||
browserslist@4.23.3:
|
browserslist@4.23.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
caniuse-lite: 1.0.30001651
|
caniuse-lite: 1.0.30001700
|
||||||
electron-to-chromium: 1.5.11
|
electron-to-chromium: 1.5.11
|
||||||
node-releases: 2.0.18
|
node-releases: 2.0.18
|
||||||
update-browserslist-db: 1.1.0(browserslist@4.23.3)
|
update-browserslist-db: 1.1.0(browserslist@4.23.3)
|
||||||
@ -7395,7 +7395,7 @@ snapshots:
|
|||||||
|
|
||||||
camelcase@6.3.0: {}
|
camelcase@6.3.0: {}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001651: {}
|
caniuse-lite@1.0.30001700: {}
|
||||||
|
|
||||||
ccount@2.0.1: {}
|
ccount@2.0.1: {}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||||
|
import { ActionType, EventProgress, ActionEventMap } from './event';
|
||||||
import {
|
import {
|
||||||
ERenderItemEvent,
|
ERenderItemEvent,
|
||||||
IRenderChildable,
|
IRenderChildable,
|
||||||
@ -77,12 +78,58 @@ export class Container<E extends EContainerEvent = EContainerEvent>
|
|||||||
this.update(this);
|
this.update(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
append(parent: RenderItem): void {
|
||||||
|
super.append(parent);
|
||||||
|
if (this.root) {
|
||||||
|
this.forEachChild(ele => {
|
||||||
|
ele.checkRoot();
|
||||||
|
this.root?.connect(ele);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 遍历这个元素中的每个子元素,并执行传入的函数
|
||||||
|
* @param fn 对每个元素执行的函数
|
||||||
|
*/
|
||||||
|
forEachChild(fn: (ele: RenderItem) => void) {
|
||||||
|
const stack: RenderItem[] = [this];
|
||||||
|
while (stack.length > 0) {
|
||||||
|
const ele = stack.pop()!;
|
||||||
|
stack.push(...ele.children);
|
||||||
|
fn(ele);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private sortChildren() {
|
private sortChildren() {
|
||||||
this.sortedChildren = [...this.children].sort(
|
this.sortedChildren = [...this.children].sort(
|
||||||
(a, b) => a.zIndex - b.zIndex
|
(a, b) => a.zIndex - b.zIndex
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected propagateEvent<T extends ActionType>(
|
||||||
|
type: T,
|
||||||
|
progress: EventProgress,
|
||||||
|
event: ActionEventMap[T]
|
||||||
|
): void {
|
||||||
|
const len = this.sortedChildren.length;
|
||||||
|
if (progress === EventProgress.Capture) {
|
||||||
|
let success = false;
|
||||||
|
for (let i = len - 1; i >= 0; i--) {
|
||||||
|
if (this.sortedChildren[i].captureEvent(type, event)) {
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果没有子元素能够触发,那么自身触发冒泡
|
||||||
|
if (!success) {
|
||||||
|
this.bubbleEvent(type, event);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.parent?.bubbleEvent(type, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
super.destroy();
|
super.destroy();
|
||||||
this.children.forEach(v => {
|
this.children.forEach(v => {
|
||||||
|
155
src/core/render/event.ts
Normal file
155
src/core/render/event.ts
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import type { RenderItem } from './item';
|
||||||
|
|
||||||
|
export const enum MouseType {
|
||||||
|
/** 没有按键按下 */
|
||||||
|
None = 0,
|
||||||
|
/** 左键 */
|
||||||
|
Left = 1 << 0,
|
||||||
|
/** 中键,即按下滚轮 */
|
||||||
|
Middle = 1 << 1,
|
||||||
|
/** 右键 */
|
||||||
|
Right = 1 << 2,
|
||||||
|
/** 侧键后退 */
|
||||||
|
Back = 1 << 3,
|
||||||
|
/** 侧键前进 */
|
||||||
|
Forward = 1 << 4
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum WheelType {
|
||||||
|
None,
|
||||||
|
/** 以像素为单位 */
|
||||||
|
Pixel,
|
||||||
|
/** 以行为单位,每行长度视浏览器设置而定,约为 1rem */
|
||||||
|
Line,
|
||||||
|
/** 以页为单位,一般为一个屏幕高度 */
|
||||||
|
Page
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum ActionType {
|
||||||
|
/** 点击事件,即按下与抬起都在该元素上时触发 */
|
||||||
|
Click,
|
||||||
|
/** 鼠标或手指按下事件 */
|
||||||
|
Down,
|
||||||
|
/** 鼠标或手指移动事件 */
|
||||||
|
Move,
|
||||||
|
/** 鼠标或手指抬起事件 */
|
||||||
|
Up,
|
||||||
|
/** 鼠标或手指移动入该元素时触发的事件 */
|
||||||
|
Enter,
|
||||||
|
/** 鼠标或手指移出该元素时触发的事件 */
|
||||||
|
Leave,
|
||||||
|
/** 鼠标在该元素上滚轮时触发的事件 */
|
||||||
|
Wheel
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum EventProgress {
|
||||||
|
/** 捕获阶段 */
|
||||||
|
Capture,
|
||||||
|
/** 冒泡阶段 */
|
||||||
|
Bubble
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IActionEvent {
|
||||||
|
/** 当前事件是监听的哪个元素 */
|
||||||
|
target: RenderItem;
|
||||||
|
/** 这次操作的标识符,在按下、移动、抬起阶段中保持不变 */
|
||||||
|
identifier: number;
|
||||||
|
/** 是触摸操作还是鼠标操作 */
|
||||||
|
touch: boolean;
|
||||||
|
/** 相对于触发元素左上角的横坐标 */
|
||||||
|
offsetX: number;
|
||||||
|
/** 相对于触发元素左上角的纵坐标 */
|
||||||
|
offsetY: number;
|
||||||
|
/** 相对于整个画布左上角的横坐标 */
|
||||||
|
absoluteX: number;
|
||||||
|
/** 相对于整个画布左上角的纵坐标 */
|
||||||
|
absoluteY: number;
|
||||||
|
/**
|
||||||
|
* 触发的按键种类,会出现在点击、按下、抬起三个事件中,而其他的如移动等该值只会是 {@link MouseType.None},
|
||||||
|
* 电脑端可以有左键、中键、右键等,手机只会触发左键,每一项的值参考 {@link MouseType}
|
||||||
|
*/
|
||||||
|
type: MouseType;
|
||||||
|
/**
|
||||||
|
* 当前按下了哪些按键。该值是一个数字,可以通过位运算判断是否按下了某个按键。
|
||||||
|
* 例如通过 `buttons & MouseType.Left` 来判断是否按下了左键。
|
||||||
|
* 注意在鼠标抬起或鼠标点击事件中,并不会包含触发的那个按键
|
||||||
|
*/
|
||||||
|
buttons: number;
|
||||||
|
/** 触发时是否按下了 alt 键 */
|
||||||
|
altKey: boolean;
|
||||||
|
/** 触发时是否按下了 shift 键 */
|
||||||
|
shiftKey: boolean;
|
||||||
|
/** 触发时是否按下了 ctrl 键 */
|
||||||
|
ctrlKey: boolean;
|
||||||
|
/** 触发时是否按下了 Windows(Windows) / Command(Mac) 键 */
|
||||||
|
metaKey: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用后将停止事件的继续传播。
|
||||||
|
* 在捕获阶段,将会阻止捕获的进一步进行,在冒泡阶段,将会阻止冒泡的进一步进行。
|
||||||
|
* 如果当前元素有很多监听器,该方法并不会阻止其他监听器的执行。
|
||||||
|
*/
|
||||||
|
stopPropagation(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWheelEvent extends IActionEvent {
|
||||||
|
/** 滚轮事件的鼠标横向滚动量 */
|
||||||
|
wheelX: number;
|
||||||
|
/** 滚轮事件的鼠标纵向滚动量 */
|
||||||
|
wheelY: number;
|
||||||
|
/** 滚轮事件的鼠标垂直屏幕的滚动量 */
|
||||||
|
wheelZ: number;
|
||||||
|
/** 滚轮事件的滚轮类型,表示了对应值的单位 */
|
||||||
|
wheelType: WheelType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ERenderItemActionEvent {
|
||||||
|
/** 当这个元素被点击时的捕获阶段触发 */
|
||||||
|
clickCapture: [ev: Readonly<IActionEvent>];
|
||||||
|
/** 当这个元素被点击时的冒泡阶段触发 */
|
||||||
|
click: [ev: Readonly<IActionEvent>];
|
||||||
|
/** 当鼠标或手指在该元素上按下的捕获阶段触发 */
|
||||||
|
downCapture: [ev: Readonly<IActionEvent>];
|
||||||
|
/** 当鼠标或手指在该元素上按下的冒泡阶段触发 */
|
||||||
|
down: [ev: Readonly<IActionEvent>];
|
||||||
|
/** 当鼠标或手指在该元素上移动的捕获阶段触发 */
|
||||||
|
moveCapture: [ev: Readonly<IActionEvent>];
|
||||||
|
/** 当鼠标或手指在该元素上移动的冒泡阶段触发 */
|
||||||
|
move: [ev: Readonly<IActionEvent>];
|
||||||
|
/** 当鼠标或手指在该元素上抬起的捕获阶段触发 */
|
||||||
|
upCapture: [ev: Readonly<IActionEvent>];
|
||||||
|
/** 当鼠标或手指在该元素上抬起的冒泡阶段触发 */
|
||||||
|
up: [ev: Readonly<IActionEvent>];
|
||||||
|
/** 当鼠标或手指进入该元素的捕获阶段触发 */
|
||||||
|
enterCapture: [ev: Readonly<IActionEvent>];
|
||||||
|
/** 当鼠标或手指进入该元素的冒泡阶段触发 */
|
||||||
|
enter: [ev: Readonly<IActionEvent>];
|
||||||
|
/** 当鼠标或手指离开该元素的捕获阶段触发 */
|
||||||
|
leaveCapture: [ev: Readonly<IActionEvent>];
|
||||||
|
/** 当鼠标或手指离开该元素的冒泡阶段触发 */
|
||||||
|
leave: [ev: Readonly<IActionEvent>];
|
||||||
|
/** 当鼠标滚轮时的捕获阶段触发 */
|
||||||
|
wheelCapture: [ev: Readonly<IWheelEvent>];
|
||||||
|
/** 当鼠标滚轮时的冒泡阶段触发 */
|
||||||
|
wheel: [ev: Readonly<IWheelEvent>];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActionEventMap {
|
||||||
|
[ActionType.Click]: IActionEvent;
|
||||||
|
[ActionType.Down]: IActionEvent;
|
||||||
|
[ActionType.Enter]: IActionEvent;
|
||||||
|
[ActionType.Leave]: IActionEvent;
|
||||||
|
[ActionType.Move]: IActionEvent;
|
||||||
|
[ActionType.Up]: IActionEvent;
|
||||||
|
[ActionType.Wheel]: IWheelEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const eventNameMap: Record<ActionType, string> = {
|
||||||
|
[ActionType.Click]: 'click',
|
||||||
|
[ActionType.Down]: 'down',
|
||||||
|
[ActionType.Move]: 'move',
|
||||||
|
[ActionType.Up]: 'up',
|
||||||
|
[ActionType.Enter]: 'enter',
|
||||||
|
[ActionType.Leave]: 'leave',
|
||||||
|
[ActionType.Wheel]: 'wheel'
|
||||||
|
};
|
@ -6,6 +6,16 @@ import { Transform } from './transform';
|
|||||||
import { logger } from '../common/logger';
|
import { logger } from '../common/logger';
|
||||||
import { ElementNamespace, ComponentInternalInstance } from 'vue';
|
import { ElementNamespace, ComponentInternalInstance } from 'vue';
|
||||||
import { transformCanvas } from './utils';
|
import { transformCanvas } from './utils';
|
||||||
|
import {
|
||||||
|
ActionEventMap,
|
||||||
|
ActionType,
|
||||||
|
ERenderItemActionEvent,
|
||||||
|
eventNameMap,
|
||||||
|
EventProgress,
|
||||||
|
IActionEvent,
|
||||||
|
MouseType
|
||||||
|
} from './event';
|
||||||
|
import { vec3 } from 'gl-matrix';
|
||||||
|
|
||||||
export type RenderFunction = (
|
export type RenderFunction = (
|
||||||
canvas: MotaOffscreenCanvas2D,
|
canvas: MotaOffscreenCanvas2D,
|
||||||
@ -140,114 +150,39 @@ export interface IRenderVueSupport {
|
|||||||
): void;
|
): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum MouseType {
|
export interface IRenderTreeRoot {
|
||||||
/** 没有按键按下 */
|
readonly isRoot: true;
|
||||||
None = 0,
|
|
||||||
/** 左键 */
|
|
||||||
Left = 1 << 0,
|
|
||||||
/** 中键,即按下滚轮 */
|
|
||||||
Middle = 1 << 1,
|
|
||||||
/** 右键 */
|
|
||||||
Right = 1 << 2,
|
|
||||||
/** 侧键后退 */
|
|
||||||
Back = 1 << 3,
|
|
||||||
/** 侧键前进 */
|
|
||||||
Forward = 1 << 4
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum WheelType {
|
|
||||||
None,
|
|
||||||
/** 以像素为单位 */
|
|
||||||
Pixel,
|
|
||||||
/** 以行为单位,每行长度视浏览器设置而定,约为 1rem */
|
|
||||||
Line,
|
|
||||||
/** 以页为单位,一般为一个屏幕高度 */
|
|
||||||
Page
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IActionEvent {
|
|
||||||
/** 当前事件是监听的哪个元素 */
|
|
||||||
readonly target: RenderItem;
|
|
||||||
/** 这次操作的标识符,在按下、移动、抬起阶段中保持不变 */
|
|
||||||
readonly identifier: number;
|
|
||||||
/** 相对于触发元素左上角的横坐标 */
|
|
||||||
readonly offsetX: number;
|
|
||||||
/** 相对于触发元素左上角的纵坐标 */
|
|
||||||
readonly offsetY: number;
|
|
||||||
/** 相对于整个画布左上角的横坐标 */
|
|
||||||
readonly absoluteX: number;
|
|
||||||
/** 相对于整个画布左上角的纵坐标 */
|
|
||||||
readonly absoluteY: number;
|
|
||||||
/**
|
|
||||||
* 触发的按键种类,会出现在点击、按下、抬起三个事件中,而其他的如移动等该值只会是 {@link MouseType.None},
|
|
||||||
* 电脑端可以有左键、中键、右键等,手机只会触发左键,每一项的值参考 {@link MouseType}
|
|
||||||
*/
|
|
||||||
readonly type: MouseType;
|
|
||||||
/**
|
|
||||||
* 当前按下了哪些按键。该值是一个数字,可以通过位运算判断是否按下了某个按键。
|
|
||||||
* 例如通过 `buttons & MouseType.Left` 来判断是否按下了左键。
|
|
||||||
*/
|
|
||||||
readonly buttons: number;
|
|
||||||
/** 触发时是否按下了 alt 键 */
|
|
||||||
readonly altKey: boolean;
|
|
||||||
/** 触发时是否按下了 shift 键 */
|
|
||||||
readonly shiftKey: boolean;
|
|
||||||
/** 触发时是否按下了 ctrl 键 */
|
|
||||||
readonly ctrlKey: boolean;
|
|
||||||
/** 触发时是否按下了 Windows(Windows) / Command(Mac) 键 */
|
|
||||||
readonly metaKey: boolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调用后将停止事件的继续传播。
|
* 将一个渲染元素连接到此根元素
|
||||||
* 在捕获阶段,将会阻止捕获的进一步进行,在冒泡阶段,将会阻止冒泡的进一步进行。
|
* @param item 要连接到此根元素的渲染元素
|
||||||
* 如果当前元素有很多监听器,该方法并不会阻止其他监听器的执行。
|
|
||||||
*/
|
*/
|
||||||
stopPropagation(): void;
|
connect(item: RenderItem): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将已连接的渲染元素从此根元素中去掉
|
||||||
|
* @param item 要取消连接的渲染元素
|
||||||
|
*/
|
||||||
|
disconnect(item: RenderItem): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改已连接的元素的 id
|
||||||
|
* @param item 修改了 id 的元素
|
||||||
|
* @param previous 先前的元素 id
|
||||||
|
* @param current 现在的元素 id
|
||||||
|
*/
|
||||||
|
modifyId(item: RenderItem, previous: string, current: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取渲染至的目标画布,即显示在画面上的画布
|
||||||
|
*/
|
||||||
|
getCanvas(): HTMLCanvasElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWheelEvent extends IActionEvent {
|
export interface ERenderItemEvent extends ERenderItemActionEvent {
|
||||||
/** 滚轮事件的鼠标横向滚动量 */
|
|
||||||
readonly wheelX: number;
|
|
||||||
/** 滚轮事件的鼠标纵向滚动量 */
|
|
||||||
readonly wheelY: number;
|
|
||||||
/** 滚轮事件的鼠标垂直屏幕的滚动量 */
|
|
||||||
readonly wheelZ: number;
|
|
||||||
/** 滚轮事件的滚轮类型,表示了对应值的单位 */
|
|
||||||
readonly wheelType: WheelType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ERenderItemEvent {
|
|
||||||
beforeRender: [transform: Transform];
|
beforeRender: [transform: Transform];
|
||||||
afterRender: [transform: Transform];
|
afterRender: [transform: Transform];
|
||||||
destroy: [];
|
destroy: [];
|
||||||
/** 当这个元素被点击时的捕获阶段触发 */
|
|
||||||
clickCapture: [ev: IActionEvent];
|
|
||||||
/** 当这个元素被点击时的冒泡阶段触发 */
|
|
||||||
click: [ev: IActionEvent];
|
|
||||||
/** 当鼠标或手指在该元素上按下的捕获阶段触发 */
|
|
||||||
downCapture: [ev: IActionEvent];
|
|
||||||
/** 当鼠标或手指在该元素上按下的冒泡阶段触发 */
|
|
||||||
down: [ev: IActionEvent];
|
|
||||||
/** 当鼠标或手指在该元素上移动的捕获阶段触发 */
|
|
||||||
moveCapture: [ev: IActionEvent];
|
|
||||||
/** 当鼠标或手指在该元素上移动的冒泡阶段触发 */
|
|
||||||
move: [ev: IActionEvent];
|
|
||||||
/** 当鼠标或手指在该元素上抬起的捕获阶段触发 */
|
|
||||||
upCapture: [ev: IActionEvent];
|
|
||||||
/** 当鼠标或手指在该元素上抬起的冒泡阶段触发 */
|
|
||||||
up: [ev: IActionEvent];
|
|
||||||
/** 当鼠标或手指进入该元素的捕获阶段触发 */
|
|
||||||
enterCapture: [ev: IActionEvent];
|
|
||||||
/** 当鼠标或手指进入该元素的冒泡阶段触发 */
|
|
||||||
enter: [ev: IActionEvent];
|
|
||||||
/** 当鼠标或手指离开该元素的捕获阶段触发 */
|
|
||||||
leaveCapture: [ev: IActionEvent];
|
|
||||||
/** 当鼠标或手指离开该元素的冒泡阶段触发 */
|
|
||||||
leave: [ev: IActionEvent];
|
|
||||||
/** 当鼠标滚轮时的捕获阶段触发 */
|
|
||||||
wheelCapture: [ev: IWheelEvent];
|
|
||||||
/** 当鼠标滚轮时的冒泡阶段触发 */
|
|
||||||
wheel: [ev: IWheelEvent];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TickerDelegation {
|
interface TickerDelegation {
|
||||||
@ -281,25 +216,22 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
/** ticker委托id */
|
/** ticker委托id */
|
||||||
static tickerId: number = 0;
|
static tickerId: number = 0;
|
||||||
|
|
||||||
/** id到渲染元素的映射 */
|
|
||||||
static itemMap: Map<string, RenderItem> = new Map();
|
|
||||||
|
|
||||||
readonly uid: number = count++;
|
readonly uid: number = count++;
|
||||||
|
|
||||||
private _id: string = '';
|
//#region 元素属性
|
||||||
|
|
||||||
|
private _id: string = '';
|
||||||
|
/**
|
||||||
|
* 元素的 id,原则上不可重复
|
||||||
|
*/
|
||||||
get id(): string {
|
get id(): string {
|
||||||
return this._id;
|
return this._id;
|
||||||
}
|
}
|
||||||
set id(v: string) {
|
set id(v: string) {
|
||||||
if (this.isRoot || this.findRoot()) {
|
this.checkRoot();
|
||||||
if (RenderItem.itemMap.has(this._id)) {
|
const prev = this._id;
|
||||||
logger.warn(23, this._id);
|
|
||||||
RenderItem.itemMap.delete(this._id);
|
|
||||||
}
|
|
||||||
RenderItem.itemMap.set(v, this);
|
|
||||||
}
|
|
||||||
this._id = v;
|
this._id = v;
|
||||||
|
this._root?.modifyId(this, prev, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 元素纵深,表示了遮挡关系 */
|
/** 元素纵深,表示了遮挡关系 */
|
||||||
@ -328,27 +260,52 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
/** 不透明度 */
|
/** 不透明度 */
|
||||||
alpha: number = 1;
|
alpha: number = 1;
|
||||||
|
|
||||||
|
get x() {
|
||||||
|
return this._transform.x;
|
||||||
|
}
|
||||||
|
get y() {
|
||||||
|
return this._transform.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 该元素的变换矩阵 */
|
||||||
|
private _transform: Transform = new Transform();
|
||||||
|
set transform(value: Transform) {
|
||||||
|
this._transform.bind();
|
||||||
|
this._transform = value;
|
||||||
|
value.bind(this);
|
||||||
|
}
|
||||||
|
get transform() {
|
||||||
|
return this._transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region 父子关系
|
||||||
|
|
||||||
private _parent?: RenderItem;
|
private _parent?: RenderItem;
|
||||||
/** 当前元素的父元素 */
|
/** 当前元素的父元素 */
|
||||||
get parent() {
|
get parent() {
|
||||||
return this._parent;
|
return this._parent;
|
||||||
}
|
}
|
||||||
/** 当前元素是否为根元素 */
|
/** 当前元素是否为根元素,如果是根元素,那么必须实现 `IRenderTreeRoot` 接口 */
|
||||||
readonly isRoot: boolean = false;
|
readonly isRoot: boolean = false;
|
||||||
|
|
||||||
/** 该元素的变换矩阵 */
|
private _root?: RenderItem & IRenderTreeRoot;
|
||||||
transform: Transform = new Transform();
|
get root() {
|
||||||
|
return this._root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 当前元素是否已经连接至任意根元素 */
|
||||||
|
get connected() {
|
||||||
|
return !!this._root;
|
||||||
|
}
|
||||||
|
|
||||||
/** 该渲染元素的子元素 */
|
/** 该渲染元素的子元素 */
|
||||||
children: Set<RenderItem<ERenderItemEvent>> = new Set();
|
children: Set<RenderItem<ERenderItemEvent>> = new Set();
|
||||||
|
|
||||||
get x() {
|
//#endregion
|
||||||
return this.transform.x;
|
|
||||||
}
|
|
||||||
get y() {
|
|
||||||
return this.transform.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
//#region 渲染配置与缓存
|
||||||
/** 渲染缓存信息 */
|
/** 渲染缓存信息 */
|
||||||
protected cache: MotaOffscreenCanvas2D = new MotaOffscreenCanvas2D();
|
protected cache: MotaOffscreenCanvas2D = new MotaOffscreenCanvas2D();
|
||||||
/** 是否需要更新缓存 */
|
/** 是否需要更新缓存 */
|
||||||
@ -357,6 +314,26 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
readonly enableCache: boolean = true;
|
readonly enableCache: boolean = true;
|
||||||
/** 是否启用transform下穿机制,即画布的变换是否会继续作用到下一层画布 */
|
/** 是否启用transform下穿机制,即画布的变换是否会继续作用到下一层画布 */
|
||||||
readonly transformFallThrough: boolean = false;
|
readonly transformFallThrough: boolean = false;
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region 交互事件
|
||||||
|
|
||||||
|
/** 是否调用了 `ev.stopPropagation` */
|
||||||
|
protected propagationStoped: Map<ActionType, boolean> = new Map();
|
||||||
|
/** 捕获阶段缓存的事件对象 */
|
||||||
|
private cachedEvent: Map<ActionType, IActionEvent> = new Map();
|
||||||
|
/** 下穿模式下当前下穿过来的变换矩阵 */
|
||||||
|
private fallTransform?: Transform;
|
||||||
|
/** 鼠标当前是否覆盖在当前元素上 */
|
||||||
|
private hovered: boolean = false;
|
||||||
|
/** 是否在元素内 */
|
||||||
|
private inElement: boolean = false;
|
||||||
|
/** 鼠标标识符映射,键为按下的鼠标按键类型,值表示本次操作的唯一标识符,在按下、移动、抬起过程中保持一致 */
|
||||||
|
protected mouseId: Map<MouseType, number> = new Map();
|
||||||
|
/** 当前所有的触摸标识符 */
|
||||||
|
protected touchId: Set<number> = new Set();
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
type: RenderItemPosition,
|
type: RenderItemPosition,
|
||||||
@ -369,22 +346,10 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
this.transformFallThrough = transformFallThrough;
|
this.transformFallThrough = transformFallThrough;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
|
||||||
this.transform.bind(this);
|
this._transform.bind(this);
|
||||||
this.cache.withGameScale(true);
|
this.cache.withGameScale(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private findRoot() {
|
|
||||||
let ele: RenderItem = this;
|
|
||||||
while (!ele.isRoot) {
|
|
||||||
if (!ele.parent) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
ele = ele.parent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ele;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 渲染函数
|
* 渲染函数
|
||||||
* @param canvas 渲染至的画布
|
* @param canvas 渲染至的画布
|
||||||
@ -398,25 +363,18 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
transform: Transform
|
transform: Transform
|
||||||
): void;
|
): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改这个对象的大小
|
|
||||||
*/
|
|
||||||
size(width: number, height: number): void {
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
this.cache.size(width, height);
|
|
||||||
this.update(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 渲染当前对象
|
* 渲染当前对象
|
||||||
* @param canvas 渲染至的画布
|
* @param canvas 渲染至的画布
|
||||||
* @param transform 父元素的变换矩阵
|
* @param transform 由父元素传递过来的变换矩阵
|
||||||
*/
|
*/
|
||||||
renderContent(canvas: MotaOffscreenCanvas2D, transform: Transform) {
|
renderContent(canvas: MotaOffscreenCanvas2D, transform: Transform) {
|
||||||
if (this.hidden) return;
|
if (this.hidden) return;
|
||||||
this.emit('beforeRender', transform);
|
this.emit('beforeRender', transform);
|
||||||
const tran = this.transformFallThrough ? transform : this.transform;
|
if (this.transformFallThrough) {
|
||||||
|
this.fallTransform = transform;
|
||||||
|
}
|
||||||
|
const tran = this.transformFallThrough ? transform : this._transform;
|
||||||
|
|
||||||
const ax = -this.anchorX * this.width;
|
const ax = -this.anchorX * this.width;
|
||||||
const ay = -this.anchorY * this.height;
|
const ay = -this.anchorY * this.height;
|
||||||
@ -424,8 +382,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
const ctx = canvas.ctx;
|
const ctx = canvas.ctx;
|
||||||
ctx.save();
|
ctx.save();
|
||||||
canvas.setAntiAliasing(this.antiAliasing);
|
canvas.setAntiAliasing(this.antiAliasing);
|
||||||
if (this.enableCache) canvas.ctx.filter = this.filter;
|
|
||||||
if (this.type === 'static') transformCanvas(canvas, tran);
|
if (this.type === 'static') transformCanvas(canvas, tran);
|
||||||
|
ctx.filter = this.filter;
|
||||||
ctx.globalAlpha = this.alpha;
|
ctx.globalAlpha = this.alpha;
|
||||||
ctx.globalCompositeOperation = this.composite;
|
ctx.globalCompositeOperation = this.composite;
|
||||||
if (this.enableCache) {
|
if (this.enableCache) {
|
||||||
@ -447,13 +405,25 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
this.emit('afterRender', transform);
|
this.emit('afterRender', transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#region 修改元素属性
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改这个对象的大小
|
||||||
|
*/
|
||||||
|
size(width: number, height: number): void {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.cache.size(width, height);
|
||||||
|
this.update(this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置这个元素的位置,等效于`transform.setTranslate(x, y)`
|
* 设置这个元素的位置,等效于`transform.setTranslate(x, y)`
|
||||||
* @param x 横坐标
|
* @param x 横坐标
|
||||||
* @param y 纵坐标
|
* @param y 纵坐标
|
||||||
*/
|
*/
|
||||||
pos(x: number, y: number) {
|
pos(x: number, y: number) {
|
||||||
this.transform.setTranslate(x, y);
|
this._transform.setTranslate(x, y);
|
||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -484,32 +454,6 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前元素的绝对位置(不建议使用,因为应当很少会有获取绝对位置的需求)
|
|
||||||
*/
|
|
||||||
getAbsolutePosition(): LocArr {
|
|
||||||
if (this.type === 'absolute') return [0, 0];
|
|
||||||
const { x, y } = this.transform;
|
|
||||||
if (!this.parent) return [x, y];
|
|
||||||
else {
|
|
||||||
const [px, py] = this.parent.getAbsolutePosition();
|
|
||||||
return [x + px, y + py];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setAnchor(x: number, y: number): void {
|
|
||||||
this.anchorX = x;
|
|
||||||
this.anchorY = y;
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
update(item: RenderItem<any> = this): void {
|
|
||||||
if (this.cacheDirty) return;
|
|
||||||
this.cacheDirty = true;
|
|
||||||
if (this.hidden) return;
|
|
||||||
this.parent?.update(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
setHD(hd: boolean): void {
|
setHD(hd: boolean): void {
|
||||||
this.highResolution = hd;
|
this.highResolution = hd;
|
||||||
this.cache.setHD(hd);
|
this.cache.setHD(hd);
|
||||||
@ -527,6 +471,57 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
this.parent?.requestSort();
|
this.parent?.requestSort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAnchor(x: number, y: number): void {
|
||||||
|
this.anchorX = x;
|
||||||
|
this.anchorY = y;
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隐藏这个元素
|
||||||
|
*/
|
||||||
|
hide() {
|
||||||
|
if (this.hidden) return;
|
||||||
|
this.hidden = true;
|
||||||
|
this.update(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示这个元素
|
||||||
|
*/
|
||||||
|
show() {
|
||||||
|
if (!this.hidden) return;
|
||||||
|
this.hidden = false;
|
||||||
|
this.refreshAllChildren();
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前元素的绝对位置(不建议使用,因为应当很少会有获取绝对位置的需求)
|
||||||
|
*/
|
||||||
|
getAbsolutePosition(x: number = 0, y: number = 0): LocArr {
|
||||||
|
if (this.type === 'absolute') {
|
||||||
|
if (this.parent) return this.parent.getAbsolutePosition(0, 0);
|
||||||
|
else return [0, 0];
|
||||||
|
}
|
||||||
|
const [px, py] = this._transform.transformed(x, y);
|
||||||
|
if (!this.parent) return [px, py];
|
||||||
|
else {
|
||||||
|
const [px, py] = this.parent.getAbsolutePosition();
|
||||||
|
return [x + px, y + py];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update(item: RenderItem<any> = this): void {
|
||||||
|
if (this.cacheDirty) return;
|
||||||
|
this.cacheDirty = true;
|
||||||
|
if (this.hidden) return;
|
||||||
|
this.parent?.update(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region 动画帧与 ticker
|
||||||
|
|
||||||
requestBeforeFrame(fn: () => void): void {
|
requestBeforeFrame(fn: () => void): void {
|
||||||
beforeFrame.push(fn);
|
beforeFrame.push(fn);
|
||||||
}
|
}
|
||||||
@ -570,22 +565,27 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
return RenderItem.tickerMap.has(id);
|
return RenderItem.tickerMap.has(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
//#endregion
|
||||||
* 隐藏这个元素
|
|
||||||
*/
|
|
||||||
hide() {
|
|
||||||
if (this.hidden) return;
|
|
||||||
this.hidden = true;
|
|
||||||
this.update(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
//#region 父子关系
|
||||||
* 显示这个元素
|
|
||||||
*/
|
checkRoot() {
|
||||||
show() {
|
if (this._root) return this._root;
|
||||||
if (!this.hidden) return;
|
if (this.isRoot) return this;
|
||||||
this.hidden = false;
|
let ele: RenderItem = this;
|
||||||
this.refreshAllChildren();
|
while (!ele.isRoot) {
|
||||||
|
if (ele._root) {
|
||||||
|
this._root = ele._root;
|
||||||
|
return this._root;
|
||||||
|
}
|
||||||
|
if (!ele._parent) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
ele = ele._parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._root = ele as RenderItem & IRenderTreeRoot;
|
||||||
|
return ele;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -614,11 +614,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
this._parent = parent;
|
this._parent = parent;
|
||||||
parent.requestSort();
|
parent.requestSort();
|
||||||
this.update();
|
this.update();
|
||||||
if (this._id !== '') {
|
this.checkRoot();
|
||||||
const root = this.findRoot();
|
this._root?.connect(this);
|
||||||
if (!root) return;
|
|
||||||
RenderItem.itemMap.set(this._id, this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -633,7 +630,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
parent.requestSort();
|
parent.requestSort();
|
||||||
parent.update();
|
parent.update();
|
||||||
if (!success) return false;
|
if (!success) return false;
|
||||||
RenderItem.itemMap.delete(this._id);
|
this._root?.disconnect(this);
|
||||||
|
this._root = void 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -660,6 +658,237 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
logger.warn(37);
|
logger.warn(37);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region 交互事件
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据事件类型和事件阶段获取事件名称
|
||||||
|
* @param type 事件类型
|
||||||
|
* @param progress 事件阶段
|
||||||
|
*/
|
||||||
|
getEventName(
|
||||||
|
type: ActionType,
|
||||||
|
progress: EventProgress
|
||||||
|
): keyof ERenderItemActionEvent {
|
||||||
|
if (progress === EventProgress.Capture) {
|
||||||
|
return `${eventNameMap[type]}Capture` as keyof ERenderItemActionEvent;
|
||||||
|
} else {
|
||||||
|
return eventNameMap[type] as keyof ERenderItemActionEvent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传递事件,即将事件传递给父元素或子元素等,可以通过 override 来实现自己的事件传递,
|
||||||
|
* 例如 Container 元素就需要在捕获阶段将事件传递给所有子元素,
|
||||||
|
* 默认行为是,捕获阶段触发自身冒泡,冒泡阶段触发父元素冒泡,适用于大部分不包含子元素的元素
|
||||||
|
* @param type 事件类型
|
||||||
|
* @param progress 事件阶段,捕获阶段或冒泡阶段
|
||||||
|
* @param event 正在处理的事件对象
|
||||||
|
*/
|
||||||
|
protected propagateEvent<T extends ActionType>(
|
||||||
|
type: T,
|
||||||
|
progress: EventProgress,
|
||||||
|
event: ActionEventMap[T]
|
||||||
|
): void {
|
||||||
|
if (progress === EventProgress.Capture) {
|
||||||
|
this.bubbleEvent(type, event);
|
||||||
|
} else {
|
||||||
|
this.parent?.bubbleEvent(type, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleEvent<T extends ActionType>(
|
||||||
|
type: T,
|
||||||
|
progress: EventProgress,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.propagationStoped.set(type, false);
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 捕获事件
|
||||||
|
* @param type 事件类型
|
||||||
|
* @param event 由父元素传递来的事件
|
||||||
|
*/
|
||||||
|
captureEvent<T extends ActionType>(type: T, event: ActionEventMap[T]) {
|
||||||
|
return this.handleEvent(type, EventProgress.Capture, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 冒泡事件
|
||||||
|
* @param type 事件类型
|
||||||
|
* @param event 由子元素传递来的事件
|
||||||
|
*/
|
||||||
|
bubbleEvent<T extends ActionType>(type: T, event: ActionEventMap[T]) {
|
||||||
|
return this.handleEvent(type, EventProgress.Bubble, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理事件,用于根据上一级传递的事件内容生成新的事件内容,并执行一些事件的默认行为
|
||||||
|
* @param type 事件类型
|
||||||
|
* @param progress 事件阶段,捕获阶段还是冒泡阶段
|
||||||
|
* @param event 由上一级(捕获阶段的父元素,冒泡阶段的子元素)传递来的事件内容
|
||||||
|
*/
|
||||||
|
protected processEvent<T extends ActionType>(
|
||||||
|
type: T,
|
||||||
|
progress: EventProgress,
|
||||||
|
event: ActionEventMap[T]
|
||||||
|
): ActionEventMap[T] | null {
|
||||||
|
if (progress === EventProgress.Capture) {
|
||||||
|
// 捕获阶段需要计算鼠标位置
|
||||||
|
const tran = this.transformFallThrough
|
||||||
|
? this.fallTransform
|
||||||
|
: this._transform;
|
||||||
|
if (!tran) return null;
|
||||||
|
const [nx, ny] = this.calActionPosition(event, tran);
|
||||||
|
const inElement = this.isActionInElement(nx, ny);
|
||||||
|
// 在元素范围内,执行事件
|
||||||
|
const newEvent: ActionEventMap[T] = {
|
||||||
|
...event,
|
||||||
|
offsetX: nx,
|
||||||
|
offsetY: ny,
|
||||||
|
target: this,
|
||||||
|
stopPropagation: () => {
|
||||||
|
this.propagationStoped.set(type, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.inElement = inElement;
|
||||||
|
if (!this.processCapture(type, newEvent, inElement)) return null;
|
||||||
|
this.cachedEvent.set(type, newEvent);
|
||||||
|
return newEvent;
|
||||||
|
} else {
|
||||||
|
const newEvent = this.cachedEvent.get(type) as ActionEventMap[T];
|
||||||
|
this.processBubble(type, newEvent, this.inElement);
|
||||||
|
this.cachedEvent.delete(type);
|
||||||
|
return newEvent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理捕获阶段的事件,可以通过 override 来添加新内容,注意调用 `super.processCapture` 来执行默认行为
|
||||||
|
* @param type 事件类型
|
||||||
|
* @param event 正在处理的事件对象
|
||||||
|
* @param inElement 当前鼠标是否在元素内
|
||||||
|
* @returns 是否继续传递事件
|
||||||
|
*/
|
||||||
|
protected processCapture<T extends ActionType>(
|
||||||
|
type: T,
|
||||||
|
event: ActionEventMap[T],
|
||||||
|
inElement: boolean
|
||||||
|
): boolean {
|
||||||
|
switch (type) {
|
||||||
|
case ActionType.Move: {
|
||||||
|
if (this.hovered && !inElement) {
|
||||||
|
this.hovered = false;
|
||||||
|
this.emit('leaveCapture', event);
|
||||||
|
this.emit('leave', event);
|
||||||
|
return false;
|
||||||
|
} else if (!this.hovered && inElement) {
|
||||||
|
this.hovered = true;
|
||||||
|
this.emit('enterCapture', event);
|
||||||
|
this.emit('enter', event);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ActionType.Down: {
|
||||||
|
// 记录标识符,用于判定 click
|
||||||
|
if (!inElement) return false;
|
||||||
|
if (event.touch) {
|
||||||
|
this.touchId.add(event.identifier);
|
||||||
|
} else {
|
||||||
|
this.mouseId.set(event.type, event.identifier);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ActionType.Click: {
|
||||||
|
if (!inElement) return false;
|
||||||
|
if (event.touch) {
|
||||||
|
if (!this.touchId.has(event.identifier)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.touchId.delete(event.identifier);
|
||||||
|
} else {
|
||||||
|
if (this.mouseId.get(event.type) !== event.identifier) {
|
||||||
|
this.mouseId.delete(event.type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.mouseId.delete(event.type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理冒泡阶段的事件,可以通过 override 来添加新内容,注意调用 `super.processBubble` 来执行默认行为
|
||||||
|
* @param type 事件类型
|
||||||
|
* @param event 正在处理的事件对象
|
||||||
|
* @param inElement 当前鼠标是否在元素内
|
||||||
|
* @returns 是否继续传递事件
|
||||||
|
*/
|
||||||
|
protected processBubble<T extends ActionType>(
|
||||||
|
_type: T,
|
||||||
|
_event: ActionEventMap[T],
|
||||||
|
inElement: boolean
|
||||||
|
): boolean {
|
||||||
|
return inElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算一个点击事件在该元素上的位置
|
||||||
|
* @param event 触发的事件
|
||||||
|
* @param transform 当前的变换矩阵
|
||||||
|
*/
|
||||||
|
protected calActionPosition(
|
||||||
|
event: IActionEvent,
|
||||||
|
transform: Transform
|
||||||
|
): vec3 {
|
||||||
|
const x = event.offsetX + this.anchorX * this.width;
|
||||||
|
const y = event.offsetY + this.anchorY * this.height;
|
||||||
|
if (this.type === 'absolute') return [x, y, 0];
|
||||||
|
else return transform.untransformed(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断一个点击事件是否在元素内,可以通过 override 来修改其行为
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
*/
|
||||||
|
protected isActionInElement(x: number, y: number) {
|
||||||
|
return x >= 0 && x < this.width && y >= 0 && y < this.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
actionClick() {}
|
||||||
|
|
||||||
|
actionDown() {}
|
||||||
|
|
||||||
|
actionUp() {}
|
||||||
|
|
||||||
|
actionMove() {}
|
||||||
|
|
||||||
|
actionEnter() {}
|
||||||
|
|
||||||
|
actionLeave() {}
|
||||||
|
|
||||||
|
actionWheel() {}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region vue支持 props处理
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断一个prop是否是期望类型
|
* 判断一个prop是否是期望类型
|
||||||
* @param value 实际值
|
* @param value 实际值
|
||||||
@ -720,12 +949,12 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
switch (key) {
|
switch (key) {
|
||||||
case 'x': {
|
case 'x': {
|
||||||
if (!this.assertType(nextValue, 'number', key)) return;
|
if (!this.assertType(nextValue, 'number', key)) return;
|
||||||
this.pos(nextValue, this.transform.y);
|
this.pos(nextValue, this._transform.y);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'y': {
|
case 'y': {
|
||||||
if (!this.assertType(nextValue, 'number', key)) return;
|
if (!this.assertType(nextValue, 'number', key)) return;
|
||||||
this.pos(this.transform.x, nextValue);
|
this.pos(this._transform.x, nextValue);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'anchorX': {
|
case 'anchorX': {
|
||||||
@ -811,6 +1040,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 摧毁这个渲染元素,摧毁后不应继续使用
|
* 摧毁这个渲染元素,摧毁后不应继续使用
|
||||||
*/
|
*/
|
||||||
@ -819,7 +1050,6 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
this.emit('destroy');
|
this.emit('destroy');
|
||||||
this.removeAllListeners();
|
this.removeAllListeners();
|
||||||
this.cache.delete();
|
this.cache.delete();
|
||||||
RenderItem.itemMap.delete(this._id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,6 +203,7 @@ export class Damage extends RenderItem<EDamageEvent> {
|
|||||||
this.cellSize = size;
|
this.cellSize = size;
|
||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新怪物列表。更新后,{@link Damage.enemy} 会丢失原来的怪物列表引用,换为传入的列表引用
|
* 更新怪物列表。更新后,{@link Damage.enemy} 会丢失原来的怪物列表引用,换为传入的列表引用
|
||||||
* @param enemy 怪物列表
|
* @param enemy 怪物列表
|
||||||
|
@ -79,6 +79,54 @@ export abstract class GraphicItemBase
|
|||||||
private strokeAndFill: boolean = false;
|
private strokeAndFill: boolean = false;
|
||||||
private propFillSet: boolean = false;
|
private propFillSet: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取这个元素的绘制路径
|
||||||
|
*/
|
||||||
|
abstract getPath(): Path2D;
|
||||||
|
|
||||||
|
protected render(
|
||||||
|
canvas: MotaOffscreenCanvas2D,
|
||||||
|
_transform: Transform
|
||||||
|
): void {
|
||||||
|
const ctx = canvas.ctx;
|
||||||
|
this.setCanvasState(canvas);
|
||||||
|
const path = this.getPath();
|
||||||
|
switch (this.mode) {
|
||||||
|
case GraphicMode.Fill:
|
||||||
|
ctx.fill(path, this.fillRule);
|
||||||
|
break;
|
||||||
|
case GraphicMode.Stroke:
|
||||||
|
ctx.stroke(path);
|
||||||
|
break;
|
||||||
|
case GraphicMode.FillAndStroke:
|
||||||
|
ctx.fill(path, this.fillRule);
|
||||||
|
ctx.stroke(path);
|
||||||
|
break;
|
||||||
|
case GraphicMode.StrokeAndFill:
|
||||||
|
ctx.stroke(path);
|
||||||
|
ctx.fill(path, this.fillRule);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isActionInElement(x: number, y: number): boolean {
|
||||||
|
const ctx = this.cache.ctx;
|
||||||
|
const path = this.getPath();
|
||||||
|
switch (this.mode) {
|
||||||
|
case GraphicMode.Fill:
|
||||||
|
return ctx.isPointInPath(path, x, y, this.fillRule);
|
||||||
|
case GraphicMode.Stroke:
|
||||||
|
return ctx.isPointInStroke(path, x, y);
|
||||||
|
case GraphicMode.FillAndStroke:
|
||||||
|
case GraphicMode.StrokeAndFill:
|
||||||
|
return (
|
||||||
|
ctx.isPointInPath(path, x, y, this.fillRule) ||
|
||||||
|
ctx.isPointInStroke(path, x, y)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置描边绘制的信息
|
* 设置描边绘制的信息
|
||||||
* @param options 线的信息
|
* @param options 线的信息
|
||||||
@ -253,31 +301,10 @@ export abstract class GraphicItemBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Rect extends GraphicItemBase {
|
export class Rect extends GraphicItemBase {
|
||||||
protected render(
|
getPath(): Path2D {
|
||||||
canvas: MotaOffscreenCanvas2D,
|
const path = new Path2D();
|
||||||
_transform: Transform
|
path.rect(this.x, this.y, this.width, this.height);
|
||||||
): void {
|
return path;
|
||||||
const ctx = canvas.ctx;
|
|
||||||
this.setCanvasState(canvas);
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.rect(this.x, this.y, this.width, this.height);
|
|
||||||
|
|
||||||
switch (this.mode) {
|
|
||||||
case GraphicMode.Fill:
|
|
||||||
ctx.fill(this.fillRule);
|
|
||||||
break;
|
|
||||||
case GraphicMode.Stroke:
|
|
||||||
ctx.stroke();
|
|
||||||
break;
|
|
||||||
case GraphicMode.FillAndStroke:
|
|
||||||
ctx.fill(this.fillRule);
|
|
||||||
ctx.stroke();
|
|
||||||
break;
|
|
||||||
case GraphicMode.StrokeAndFill:
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.fill(this.fillRule);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,8 +236,8 @@ export class Icon extends RenderItem<EIconEvent> implements IAnimateFrame {
|
|||||||
const renderable = this.renderable;
|
const renderable = this.renderable;
|
||||||
if (!renderable) return;
|
if (!renderable) return;
|
||||||
const [x, y, w, h] = renderable.render[0];
|
const [x, y, w, h] = renderable.render[0];
|
||||||
const cw = canvas.width;
|
const cw = this.width;
|
||||||
const ch = canvas.height;
|
const ch = this.height;
|
||||||
const frame = this.animate
|
const frame = this.animate
|
||||||
? RenderItem.animatedFrame % renderable.frame
|
? RenderItem.animatedFrame % renderable.frame
|
||||||
: 0;
|
: 0;
|
||||||
|
@ -1,15 +1,55 @@
|
|||||||
import { logger } from '../common/logger';
|
import { logger } from '../common/logger';
|
||||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||||
import { Container } from './container';
|
import { Container } from './container';
|
||||||
import { RenderItem } from './item';
|
import {
|
||||||
|
ActionType,
|
||||||
|
IActionEvent,
|
||||||
|
IWheelEvent,
|
||||||
|
MouseType,
|
||||||
|
WheelType
|
||||||
|
} from './event';
|
||||||
|
import { IRenderTreeRoot, RenderItem } from './item';
|
||||||
import { Transform } from './transform';
|
import { Transform } from './transform';
|
||||||
|
|
||||||
export class MotaRenderer extends Container {
|
interface TouchInfo {
|
||||||
|
/** 这次触摸在渲染系统的标识符 */
|
||||||
|
identifier: number;
|
||||||
|
/** 浏览器的 clientX,用于判断这个触点有没有移动 */
|
||||||
|
clientX: number;
|
||||||
|
/** 浏览器的 clientY,用于判断这个触点有没有移动 */
|
||||||
|
clientY: number;
|
||||||
|
/** 是否覆盖在了当前元素上 */
|
||||||
|
hovered: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MouseInfo {
|
||||||
|
/** 这个鼠标按键的标识符 */
|
||||||
|
identifier: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MotaRenderer extends Container implements IRenderTreeRoot {
|
||||||
static list: Map<string, MotaRenderer> = new Map();
|
static list: Map<string, MotaRenderer> = new Map();
|
||||||
|
|
||||||
|
/** 所有连接到此根元素的渲染元素的 id 到元素自身的映射 */
|
||||||
|
protected idMap: Map<string, RenderItem> = new Map();
|
||||||
|
|
||||||
|
/** 最后一次按下的鼠标按键,用于处理鼠标移动 */
|
||||||
|
private lastMouse: MouseType = MouseType.None;
|
||||||
|
/** 每个触点的信息 */
|
||||||
|
private touchInfo: Map<number, TouchInfo> = new Map();
|
||||||
|
/** 触点列表 */
|
||||||
|
private touchList: Map<number, Touch> = new Map();
|
||||||
|
/** 每个鼠标按键的信息 */
|
||||||
|
private mouseInfo: Map<MouseType, MouseInfo> = new Map();
|
||||||
|
/** 操作的标识符 */
|
||||||
|
private actionIdentifier: number = 0;
|
||||||
|
|
||||||
|
/** 用于终止 document 上的监听 */
|
||||||
|
private abort?: AbortController;
|
||||||
|
|
||||||
target!: MotaOffscreenCanvas2D;
|
target!: MotaOffscreenCanvas2D;
|
||||||
|
|
||||||
readonly isRoot: boolean = true;
|
readonly isRoot = true;
|
||||||
|
|
||||||
constructor(id: string = 'render-main') {
|
constructor(id: string = 'render-main') {
|
||||||
super('static', false);
|
super('static', false);
|
||||||
@ -38,6 +78,301 @@ export class MotaRenderer extends Container {
|
|||||||
};
|
};
|
||||||
|
|
||||||
update();
|
update();
|
||||||
|
this.listen();
|
||||||
|
}
|
||||||
|
|
||||||
|
private listen() {
|
||||||
|
// 画布监听
|
||||||
|
const canvas = this.target.canvas;
|
||||||
|
canvas.addEventListener('mousedown', ev => {
|
||||||
|
const mouse = this.getMouseType(ev);
|
||||||
|
this.lastMouse = mouse;
|
||||||
|
this.captureEvent(
|
||||||
|
ActionType.Down,
|
||||||
|
this.createMouseAction(ev, ActionType.Down, mouse)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
canvas.addEventListener('mouseup', ev => {
|
||||||
|
const event = this.createMouseAction(ev, ActionType.Up);
|
||||||
|
this.captureEvent(ActionType.Up, event);
|
||||||
|
this.captureEvent(ActionType.Click, event);
|
||||||
|
});
|
||||||
|
canvas.addEventListener('mousemove', ev => {
|
||||||
|
const event = this.createMouseAction(
|
||||||
|
ev,
|
||||||
|
ActionType.Move,
|
||||||
|
this.lastMouse
|
||||||
|
);
|
||||||
|
this.captureEvent(ActionType.Move, event);
|
||||||
|
});
|
||||||
|
canvas.addEventListener('mouseenter', ev => {
|
||||||
|
const event = this.createMouseAction(ev, ActionType.Enter);
|
||||||
|
this.emit('enterCapture', event);
|
||||||
|
this.emit('enter', event);
|
||||||
|
});
|
||||||
|
canvas.addEventListener('mouseleave', ev => {
|
||||||
|
const event = this.createMouseAction(ev, ActionType.Leave);
|
||||||
|
this.emit('leaveCapture', event);
|
||||||
|
this.emit('leave', event);
|
||||||
|
});
|
||||||
|
document.addEventListener('touchstart', ev => {
|
||||||
|
this.createTouchAction(ev, ActionType.Down).forEach(v => {
|
||||||
|
this.captureEvent(ActionType.Down, v);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
document.addEventListener('touchend', ev => {
|
||||||
|
this.createTouchAction(ev, ActionType.Up).forEach(v => {
|
||||||
|
this.captureEvent(ActionType.Up, v);
|
||||||
|
this.captureEvent(ActionType.Click, v);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
document.addEventListener('touchcancel', ev => {
|
||||||
|
this.createTouchAction(ev, ActionType.Up).forEach(v => {
|
||||||
|
this.captureEvent(ActionType.Up, v);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
document.addEventListener('touchmove', ev => {
|
||||||
|
this.createTouchAction(ev, ActionType.Move).forEach(v => {
|
||||||
|
const touch = this.touchInfo.get(v.identifier);
|
||||||
|
if (!touch) return;
|
||||||
|
const inElement = this.isTouchInCanvas(v.offsetX, v.offsetY);
|
||||||
|
if (touch.hovered && !inElement) {
|
||||||
|
this.emit('leaveCapture', v);
|
||||||
|
this.emit('leave', v);
|
||||||
|
}
|
||||||
|
if (!touch.hovered && inElement) {
|
||||||
|
this.emit('enterCapture', v);
|
||||||
|
this.emit('enter', v);
|
||||||
|
}
|
||||||
|
this.captureEvent(ActionType.Move, v);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
canvas.addEventListener('wheel', ev => {
|
||||||
|
this.captureEvent(
|
||||||
|
ActionType.Wheel,
|
||||||
|
this.createWheelAction(ev, ActionType.Wheel)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// 文档监听
|
||||||
|
const abort = new AbortController();
|
||||||
|
const signal = abort.signal;
|
||||||
|
this.abort = abort;
|
||||||
|
const clear = (ev: MouseEvent) => {
|
||||||
|
const mouse = this.getMouseButtons(ev);
|
||||||
|
for (const button of this.mouseInfo.keys()) {
|
||||||
|
if (!(mouse & button)) {
|
||||||
|
this.mouseInfo.delete(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('click', clear, { signal });
|
||||||
|
document.addEventListener('mouseenter', clear, { signal });
|
||||||
|
document.addEventListener('mouseleave', clear, { signal });
|
||||||
|
}
|
||||||
|
|
||||||
|
private isTouchInCanvas(clientX: number, clientY: number) {
|
||||||
|
const rect = this.target.canvas.getBoundingClientRect();
|
||||||
|
const { left, right, top, bottom } = rect;
|
||||||
|
const x = clientX;
|
||||||
|
const y = clientY;
|
||||||
|
return x >= left && x <= right && y >= top && y <= bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMouseType(ev: MouseEvent): MouseType {
|
||||||
|
switch (ev.button) {
|
||||||
|
case 0:
|
||||||
|
return MouseType.Left;
|
||||||
|
case 1:
|
||||||
|
return MouseType.Middle;
|
||||||
|
case 2:
|
||||||
|
return MouseType.Right;
|
||||||
|
case 3:
|
||||||
|
return MouseType.Back;
|
||||||
|
case 4:
|
||||||
|
return MouseType.Forward;
|
||||||
|
}
|
||||||
|
return MouseType.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getActiveMouseIdentifier(mouse: MouseType) {
|
||||||
|
if (this.lastMouse === MouseType.None) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
const info = this.mouseInfo.get(mouse);
|
||||||
|
if (!info) return -1;
|
||||||
|
else return info.identifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMouseIdentifier(type: ActionType, mouse: MouseType): number {
|
||||||
|
switch (type) {
|
||||||
|
case ActionType.Down: {
|
||||||
|
const id = this.actionIdentifier++;
|
||||||
|
this.mouseInfo.set(mouse, { identifier: id });
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
case ActionType.Move:
|
||||||
|
case ActionType.Enter:
|
||||||
|
case ActionType.Leave:
|
||||||
|
case ActionType.Wheel: {
|
||||||
|
return this.getActiveMouseIdentifier(mouse);
|
||||||
|
}
|
||||||
|
case ActionType.Up:
|
||||||
|
case ActionType.Click: {
|
||||||
|
const id = this.getActiveMouseIdentifier(mouse);
|
||||||
|
this.mouseInfo.delete(mouse);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMouseButtons(event: MouseEvent): number {
|
||||||
|
if (event.buttons === 0) return MouseType.None;
|
||||||
|
let buttons = 0;
|
||||||
|
if (event.buttons & 0b1) buttons |= MouseType.Left;
|
||||||
|
if (event.buttons & 0b10) buttons |= MouseType.Right;
|
||||||
|
if (event.buttons & 0b100) buttons |= MouseType.Middle;
|
||||||
|
if (event.buttons & 0b1000) buttons |= MouseType.Back;
|
||||||
|
if (event.buttons & 0b10000) buttons |= MouseType.Forward;
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createMouseAction(
|
||||||
|
event: MouseEvent,
|
||||||
|
type: ActionType,
|
||||||
|
mouse: MouseType = this.getMouseType(event)
|
||||||
|
): IActionEvent {
|
||||||
|
const id = this.getMouseIdentifier(type, mouse);
|
||||||
|
const x = event.offsetX / core.domStyle.scale;
|
||||||
|
const y = event.offsetY / core.domStyle.scale;
|
||||||
|
return {
|
||||||
|
target: this,
|
||||||
|
identifier: id,
|
||||||
|
touch: false,
|
||||||
|
offsetX: x,
|
||||||
|
offsetY: y,
|
||||||
|
absoluteX: x,
|
||||||
|
absoluteY: y,
|
||||||
|
type: mouse,
|
||||||
|
buttons: this.getMouseButtons(event),
|
||||||
|
altKey: event.altKey,
|
||||||
|
ctrlKey: event.ctrlKey,
|
||||||
|
shiftKey: event.shiftKey,
|
||||||
|
metaKey: event.metaKey,
|
||||||
|
stopPropagation: () => {
|
||||||
|
this.propagationStoped.set(type, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private createWheelAction(
|
||||||
|
event: WheelEvent,
|
||||||
|
type: ActionType,
|
||||||
|
mouse: MouseType = this.getMouseType(event)
|
||||||
|
): IWheelEvent {
|
||||||
|
const ev = this.createMouseAction(event, type, mouse) as IWheelEvent;
|
||||||
|
ev.wheelX = event.deltaX;
|
||||||
|
ev.wheelY = event.deltaY;
|
||||||
|
ev.wheelZ = event.deltaZ;
|
||||||
|
switch (event.deltaMode) {
|
||||||
|
case 0x00:
|
||||||
|
ev.wheelType = WheelType.Pixel;
|
||||||
|
break;
|
||||||
|
case 0x01:
|
||||||
|
ev.wheelType = WheelType.Line;
|
||||||
|
break;
|
||||||
|
case 0x02:
|
||||||
|
ev.wheelType = WheelType.Page;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ev.wheelType = WheelType.None;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTouchIdentifier(touch: Touch, type: ActionType) {
|
||||||
|
if (type === ActionType.Down) {
|
||||||
|
const id = this.actionIdentifier++;
|
||||||
|
this.touchInfo.set(touch.identifier, {
|
||||||
|
identifier: id,
|
||||||
|
clientX: touch.clientX,
|
||||||
|
clientY: touch.clientY,
|
||||||
|
hovered: this.isTouchInCanvas(touch.clientX, touch.clientY)
|
||||||
|
});
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
const info = this.touchInfo.get(touch.identifier);
|
||||||
|
if (!info) return -1;
|
||||||
|
return info.identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createTouch(
|
||||||
|
touch: Touch,
|
||||||
|
type: ActionType,
|
||||||
|
event: TouchEvent,
|
||||||
|
rect: DOMRect
|
||||||
|
): IActionEvent {
|
||||||
|
const x = (touch.clientX - rect.left) / core.domStyle.scale;
|
||||||
|
const y = (touch.clientY - rect.top) / core.domStyle.scale;
|
||||||
|
return {
|
||||||
|
target: this,
|
||||||
|
identifier: this.getTouchIdentifier(touch, type),
|
||||||
|
touch: true,
|
||||||
|
offsetX: x,
|
||||||
|
offsetY: y,
|
||||||
|
absoluteX: x,
|
||||||
|
absoluteY: y,
|
||||||
|
type: MouseType.Left,
|
||||||
|
buttons: MouseType.Left,
|
||||||
|
altKey: event.altKey,
|
||||||
|
ctrlKey: event.ctrlKey,
|
||||||
|
shiftKey: event.shiftKey,
|
||||||
|
metaKey: event.metaKey,
|
||||||
|
stopPropagation: () => {
|
||||||
|
this.propagationStoped.set(type, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private createTouchAction(
|
||||||
|
event: TouchEvent,
|
||||||
|
type: ActionType
|
||||||
|
): IActionEvent[] {
|
||||||
|
const list: IActionEvent[] = [];
|
||||||
|
const rect = this.target.canvas.getBoundingClientRect();
|
||||||
|
if (type === ActionType.Up) {
|
||||||
|
// 抬起是一个需要特殊处理的东西,因为 touches 不会包含这个内容,所以需要特殊处理
|
||||||
|
const touches = Array.from(event.touches).map(v => v.identifier);
|
||||||
|
for (const [id, touch] of this.touchList) {
|
||||||
|
if (!touches.includes(id)) {
|
||||||
|
// 如果不包含,才需要触发
|
||||||
|
if (this.isTouchInCanvas(touch.clientX, touch.clientY)) {
|
||||||
|
const ev = this.createTouch(touch, type, event, rect);
|
||||||
|
list.push(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Array.from(event.touches).forEach(v => {
|
||||||
|
const ev = this.createTouch(v, type, event, rect);
|
||||||
|
if (type === ActionType.Move) {
|
||||||
|
const touch = this.touchInfo.get(v.identifier);
|
||||||
|
if (!touch) return;
|
||||||
|
const moveX = touch.clientX - v.clientX;
|
||||||
|
const moveY = touch.clientY - v.clientY;
|
||||||
|
if (moveX !== 0 || moveY !== 0) {
|
||||||
|
list.push(ev);
|
||||||
|
}
|
||||||
|
} else if (type === ActionType.Down) {
|
||||||
|
this.touchList.set(v.identifier, v);
|
||||||
|
if (this.isTouchInCanvas(v.clientX, v.clientY)) {
|
||||||
|
list.push(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(_item: RenderItem = this) {
|
update(_item: RenderItem = this) {
|
||||||
@ -56,13 +391,13 @@ export class MotaRenderer extends Container {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
getElementById(id: string): RenderItem | null {
|
getElementById(id: string): RenderItem | null {
|
||||||
const map = RenderItem.itemMap;
|
if (id.length === 0) return null;
|
||||||
const item = map.get(id);
|
const item = this.idMap.get(id);
|
||||||
if (item) return item;
|
if (item) return item;
|
||||||
else {
|
else {
|
||||||
const item = this.searchElement(this, id);
|
const item = this.searchElement(this, id);
|
||||||
if (item) {
|
if (item) {
|
||||||
map.set(id, item);
|
this.idMap.set(id, item);
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@ -79,10 +414,63 @@ export class MotaRenderer extends Container {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connect(item: RenderItem): void {
|
||||||
|
if (item.id.length === 0) return;
|
||||||
|
const existed = this.idMap.get(item.id);
|
||||||
|
if (existed) {
|
||||||
|
if (existed === item) return;
|
||||||
|
else logger.warn(23, item.id);
|
||||||
|
} else {
|
||||||
|
this.idMap.set(item.id, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect(item: RenderItem): void {
|
||||||
|
this.idMap.delete(item.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyId(item: RenderItem, previous: string, current: string): void {
|
||||||
|
this.idMap.delete(previous);
|
||||||
|
if (current.length !== 0) {
|
||||||
|
if (this.idMap.has(item.id)) {
|
||||||
|
logger.warn(23, item.id);
|
||||||
|
} else {
|
||||||
|
this.idMap.set(item.id, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getCanvas(): HTMLCanvasElement {
|
||||||
|
return this.target.canvas;
|
||||||
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
super.destroy();
|
super.destroy();
|
||||||
MotaRenderer.list.delete(this.id);
|
MotaRenderer.list.delete(this.id);
|
||||||
this.target.delete();
|
this.target.delete();
|
||||||
|
this.abort?.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
private toTagString(item: RenderItem, space: number, deep: number): string {
|
||||||
|
const name = item.constructor.name;
|
||||||
|
if (item.children.size === 0) {
|
||||||
|
return `${' '.repeat(deep * space)}<${name} id="${item.id}" type="${item.type}"></${name}>\n`;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
`${' '.repeat(deep * space)}<${name} id="${item.id}" type="${item.type}">\n` +
|
||||||
|
`${[...item.children].map(v => this.toTagString(v, space, deep + 1)).join('')}` +
|
||||||
|
`${' '.repeat(deep * space)}</${name}>\n`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调试功能,将渲染树输出为 XML 标签形式,只包含渲染元素类名,以及元素 id 等基础属性,不包含属性值等
|
||||||
|
* @param space 缩进空格数
|
||||||
|
*/
|
||||||
|
toTagTree(space: number = 4) {
|
||||||
|
if (!import.meta.env.DEV) return '';
|
||||||
|
return this.toTagString(this, space, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get(id: string) {
|
static get(id: string) {
|
||||||
@ -95,3 +483,8 @@ window.addEventListener('resize', () => {
|
|||||||
v.requestAfterFrame(() => v.refreshAllChildren())
|
v.requestAfterFrame(() => v.refreshAllChildren())
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// @ts-expect-error debug
|
||||||
|
window.logTagTree = () => {
|
||||||
|
console.log(MotaRenderer.get('render-main')?.toTagTree());
|
||||||
|
};
|
||||||
|
@ -31,6 +31,7 @@ export interface BaseProps {
|
|||||||
id?: string;
|
id?: string;
|
||||||
alpha?: number;
|
alpha?: number;
|
||||||
composite?: GlobalCompositeOperation;
|
composite?: GlobalCompositeOperation;
|
||||||
|
cursor?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpriteProps extends BaseProps {
|
export interface SpriteProps extends BaseProps {
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||||
import { Transform } from './transform';
|
import { Transform } from './transform';
|
||||||
import { ElementNamespace, ComponentInternalInstance } from 'vue';
|
import { ElementNamespace, ComponentInternalInstance } from 'vue';
|
||||||
|
import { ActionType, EventProgress, ActionEventMap } from './event';
|
||||||
|
|
||||||
export interface ESpriteEvent extends ERenderItemEvent {}
|
export interface ESpriteEvent extends ERenderItemEvent {}
|
||||||
|
|
||||||
@ -42,6 +43,14 @@ export class Sprite<
|
|||||||
this.update(this);
|
this.update(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected propagateEvent<T extends ActionType>(
|
||||||
|
type: T,
|
||||||
|
_progress: EventProgress,
|
||||||
|
event: ActionEventMap[T]
|
||||||
|
): void {
|
||||||
|
this.parent?.bubbleEvent(type, event);
|
||||||
|
}
|
||||||
|
|
||||||
patchProp(
|
patchProp(
|
||||||
key: string,
|
key: string,
|
||||||
prevValue: any,
|
prevValue: any,
|
||||||
|
@ -205,7 +205,8 @@ export class Transform {
|
|||||||
* @param x 横坐标
|
* @param x 横坐标
|
||||||
* @param y 纵坐标
|
* @param y 纵坐标
|
||||||
*/
|
*/
|
||||||
transformed(x: number, y: number) {
|
transformed(x: number, y: number): vec3 {
|
||||||
|
if (!this.modified) return [x, y, 1];
|
||||||
return multiplyVec3(this.mat, [x, y, 1]);
|
return multiplyVec3(this.mat, [x, y, 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,7 +215,8 @@ export class Transform {
|
|||||||
* @param x 横坐标
|
* @param x 横坐标
|
||||||
* @param y 纵坐标
|
* @param y 纵坐标
|
||||||
*/
|
*/
|
||||||
untransformed(x: number, y: number) {
|
untransformed(x: number, y: number): vec3 {
|
||||||
|
if (!this.modified) return [x, y, 1];
|
||||||
const invert = mat3.create();
|
const invert = mat3.create();
|
||||||
mat3.invert(invert, this.mat);
|
mat3.invert(invert, this.mat);
|
||||||
return multiplyVec3(invert, [x, y, 1]);
|
return multiplyVec3(invert, [x, y, 1]);
|
||||||
|
@ -87,6 +87,7 @@
|
|||||||
"53": "Cannot $1 audio route '$2', since there is not added route named it.",
|
"53": "Cannot $1 audio route '$2', since there is not added route named it.",
|
||||||
"54": "Missing start tag for '$1', index: $2.",
|
"54": "Missing start tag for '$1', index: $2.",
|
||||||
"55": "Unchildable tag '$1' should follow with param.",
|
"55": "Unchildable tag '$1' should follow with param.",
|
||||||
|
"56": "Method '$1' is deprecated. Consider using '$2' instead.",
|
||||||
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
|
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
|
||||||
"1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance."
|
"1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance."
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ import { mainSetting } from '@/core/main/setting';
|
|||||||
import { sleep } from 'mutate-animate';
|
import { sleep } from 'mutate-animate';
|
||||||
import { isNil } from 'lodash-es';
|
import { isNil } from 'lodash-es';
|
||||||
|
|
||||||
|
// todo: 添加弃用警告 logger.warn(56)
|
||||||
|
|
||||||
export function patchAudio() {
|
export function patchAudio() {
|
||||||
const patch = new Patch(PatchClass.Control);
|
const patch = new Patch(PatchClass.Control);
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@ import { Patch, PatchClass } from '@/common/patch';
|
|||||||
import { WeatherController } from '../weather';
|
import { WeatherController } from '../weather';
|
||||||
import { isNil } from 'lodash-es';
|
import { isNil } from 'lodash-es';
|
||||||
|
|
||||||
|
// todo: 添加弃用警告 logger.warn(56)
|
||||||
|
|
||||||
export function patchWeather() {
|
export function patchWeather() {
|
||||||
const patch = new Patch(PatchClass.Control);
|
const patch = new Patch(PatchClass.Control);
|
||||||
let nowWeather: string = '';
|
let nowWeather: string = '';
|
||||||
|
@ -360,6 +360,7 @@ export const Textbox = defineComponent<
|
|||||||
)}
|
)}
|
||||||
<TextContent
|
<TextContent
|
||||||
{...data}
|
{...data}
|
||||||
|
id=""
|
||||||
hidden={false}
|
hidden={false}
|
||||||
x={data.padding!}
|
x={data.padding!}
|
||||||
y={contentY.value + data.padding!}
|
y={contentY.value + data.padding!}
|
||||||
|
@ -442,6 +442,7 @@ export function getVitualKeyOnce(
|
|||||||
assist: number = 0,
|
assist: number = 0,
|
||||||
emittable: KeyCode[] = []
|
emittable: KeyCode[] = []
|
||||||
): Promise<KeyboardEmits> {
|
): Promise<KeyboardEmits> {
|
||||||
|
// todo: 正确触发后删除监听器
|
||||||
return new Promise(res => {
|
return new Promise(res => {
|
||||||
const key = Keyboard.get('full')!;
|
const key = Keyboard.get('full')!;
|
||||||
key.withAssist(assist);
|
key.withAssist(assist);
|
||||||
|
Loading…
Reference in New Issue
Block a user