快乐的梦鱼 il y a 2 semaines
Parent
commit
2485e925b7

+ 107 - 4
src/components/canvas/MiniRender.ts

@@ -42,8 +42,8 @@ export namespace MiniRender {
     anchorY: number; // 0..1 (relative to height)
   }
 
-  export type RenderObjectEventName = "added" | "removed";
-  export type RenderObjectEventHandler = (obj: RenderObject) => void;
+  export type RenderObjectEventName = "added" | "removed" | "click" | "touchstart" | "touchmove" | "touchend" | "touchcancel";
+  export type RenderObjectEventHandler = (obj: RenderObject, data?: any) => void;
 
   export class RenderObject implements TransformLike, IRenderable, IUpdatable {
     public readonly id: RenderObjectId;
@@ -62,6 +62,7 @@ export namespace MiniRender {
 
     public visible = true;
     public zIndex = 0;
+    public interactive = false;
 
     public parent: Container | null = null;
 
@@ -86,10 +87,15 @@ export namespace MiniRender {
       this.listeners.get(event)?.delete(handler);
     }
 
-    public emit(event: RenderObjectEventName): void {
+    public emit(event: RenderObjectEventName, data?: any): void {
       const set = this.listeners.get(event);
       if (!set) return;
-      for (const h of set) h(this);
+      for (const h of set) h(this, data);
+    }
+
+    public hasListener(event: RenderObjectEventName): boolean {
+      const set = this.listeners.get(event);
+      return !!set && set.size > 0;
     }
 
     protected withTransform(rc: RenderContext, draw: () => void): void {
@@ -119,6 +125,45 @@ export namespace MiniRender {
     }
 
     public update(_dtMs: number): void {}
+
+    public parentToLocal(px: number, py: number): { x: number; y: number } | null {
+      const ax = this.anchorX * this.width;
+      const ay = this.anchorY * this.height;
+
+      let lx = px - (this.x + ax);
+      let ly = py - (this.y + ay);
+
+      if (this.rotation) {
+        const cos = Math.cos(-this.rotation);
+        const sin = Math.sin(-this.rotation);
+        const rx = lx * cos - ly * sin;
+        const ry = lx * sin + ly * cos;
+        lx = rx;
+        ly = ry;
+      }
+
+      if (this.scaleX === 0 || this.scaleY === 0) return null;
+      lx /= this.scaleX;
+      ly /= this.scaleY;
+
+      lx += ax;
+      ly += ay;
+
+      return { x: lx, y: ly };
+    }
+
+    public hitTest(px: number, py: number): RenderObject | null {
+      if (!this.visible || this.alpha <= 0) return null;
+      if (!this.interactive) return null;
+
+      const local = this.parentToLocal(px, py);
+      if (!local) return null;
+
+      if (local.x >= 0 && local.x <= this.width && local.y >= 0 && local.y <= this.height) {
+        return this;
+      }
+      return null;
+    }
   }
 
   export class Container extends RenderObject {
@@ -168,6 +213,26 @@ export namespace MiniRender {
     protected override draw(rc: RenderContext): void {
       for (const c of this._children) c.render(rc);
     }
+
+    public override hitTest(px: number, py: number): RenderObject | null {
+      if (!this.visible || this.alpha <= 0) return null;
+
+      const local = this.parentToLocal(px, py);
+      if (!local) return null;
+
+      for (let i = this._children.length - 1; i >= 0; i--) {
+        const hit = this._children[i].hitTest(local.x, local.y);
+        if (hit) return hit;
+      }
+
+      if (this.interactive && this.hasListener("click")) {
+        if (local.x >= 0 && local.x <= this.width && local.y >= 0 && local.y <= this.height) {
+          return this;
+        }
+      }
+
+      return null;
+    }
   }
 
   export class Rect extends RenderObject {
@@ -601,11 +666,42 @@ export namespace MiniRender {
 
     private rafId: number | null = null;
     private lastTs: number | null = null;
+    private currentDragObject: RenderObject | null = null;
+
     public readonly canvas: RendeCanvasInterface;
     public readonly width: number;
     public readonly height: number;
     public readonly backgroundColor: string;
 
+
+    public readonly events = {
+      handleTouchStart: (e: any) => {
+        const pos = this.getEventPosition(e);
+        const hit = this.root.hitTest(pos.x, pos.y);
+        if (hit) {
+          hit.emit("touchstart", e);
+          this.currentDragObject = hit;
+        }
+      },
+      handleTouchMove: (e: any) => {
+        if (this.currentDragObject) {
+          this.currentDragObject.emit("touchmove", e);
+        }
+      },
+      handleTouchEnd: (e: any) => {
+        if (this.currentDragObject) {
+          this.currentDragObject.emit("touchend", e);
+          this.currentDragObject.emit("click", e);
+          this.currentDragObject = null;
+        }
+      },
+      handleTouchCancel: (e: any) => {
+        if (this.currentDragObject) 
+          this.currentDragObject.emit("touchcancel", e);
+        this.currentDragObject = null;
+      },
+    };
+
     constructor(
       public options: {
         canvas: RendeCanvasInterface,
@@ -675,5 +771,12 @@ export namespace MiniRender {
       this.assets.clear();
       this.root.removeAll();
     }
+
+    private getEventPosition(e: any): { x: number; y: number } {
+      if (e?.detail?.x !== undefined) return { x: e.detail.x, y: e.detail.y };
+      if (e?.offsetX !== undefined) return { x: e.offsetX, y: e.offsetY };
+      if (e?.touches?.[0]) return { x: e.touches[0].x, y: e.touches[0].y };
+      return { x: 0, y: 0 };
+    }
   }
 }

+ 11 - 0
src/pages/home/village/components/VillageTree.vue

@@ -8,6 +8,10 @@
       width: systemInfo.windowWidth + 'px',
       height: HEIGHT + 'px',
     }"
+    @touchstart="render.events.handleTouchStart"
+    @touchmove="render.events.handleTouchMove"
+    @touchend="render.events.handleTouchEnd"
+    @touchcancel="render.events.handleTouchCancel"
   />
 </template>
 
@@ -86,6 +90,13 @@ const render = new MiniRender.Scene(
     flag.y = 144;
     flag.width = 80;
     flag.height = 100;
+    flag.interactive = true;
+    flag.on('click', () => {
+      uni.showToast({
+        title: 'flag clicked',
+        icon: 'success',
+      });
+    });
 
     render.root
       .add(bg)