适配v2,v3的hover,select逻辑

This commit is contained in:
xu_yanfeng 2025-01-24 16:05:05 +08:00
parent e47cf0d3c0
commit eac45d1fd0
6 changed files with 481 additions and 378 deletions

View File

@ -1,375 +0,0 @@
import ccui from "@xuyanfeng/cc-ui";
import { IUiMenuItem } from "@xuyanfeng/cc-ui/types/cc-menu/const";
import { DocumentEvent } from "../const";
import { Inspector } from "./inspector";
import { Msg } from "../../core/types";
declare const cc: any;
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
class RectPoints {
points: Point[] = [];
get len() {
return this.points.length;
}
at(index: number) {
return this.points[index];
}
test(width: number, height: number) {
this.points.push(new Point(0, 0));
this.points.push(new Point(width, 0));
this.points.push(new Point(width, height));
this.points.push(new Point(0, height));
}
}
interface DrawOptions {
fill: boolean;
fillColor: string;
stroke: boolean;
strokeColor: string;
}
export class Hint {
private draw = null;
public engineVersion: string = "";
private inspector: Inspector = null;
constructor(inspector: Inspector) {
this.inspector = inspector;
document.addEventListener(DocumentEvent.GameInspectorBegan, (event: CustomEvent) => {
const el = this.getTargetElement();
if (!el) {
return;
}
const cursor = el.style.cursor;
el.style.cursor = "zoom-in";
const test = (event: MouseEvent) => {
event.stopPropagation();
event.preventDefault();
el.removeEventListener("mousedown", test, true);
el.style.cursor = cursor;
const e = new CustomEvent(DocumentEvent.GameInspectorEnd);
document.dispatchEvent(e);
this.updateHint(event, el);
};
el.addEventListener("mousedown", test, true);
});
}
private nodeVisible(node) {
let parent = node;
while (parent) {
if (parent) {
}
}
}
private updateHint(event: MouseEvent, canvas: HTMLCanvasElement) {
this.cleanHover();
this.cleanSelected();
const rect = canvas.getBoundingClientRect();
let x = event.clientX - rect.x;
let y = rect.y + rect.height - event.clientY;
x *= window.devicePixelRatio;
y *= window.devicePixelRatio;
this.inspector.updateTreeInfo(false);
const nodes = [];
this.inspector.forEachNode((item) => {
const b = item._uiProps?.uiTransformComp?.hitTest({ x, y }, 0);
if (b && item.active && item.activeInHierarchy) {
nodes.push(item);
}
});
if (nodes.length === 1) {
const item = nodes[0];
this.setSelected(item);
this.sendInspectNodeMsg(item);
} else {
nodes.reverse();
const menu = nodes.map((item) => {
const path = this.getPath(item);
return {
name: path,
callback: () => {
this.cleanHover();
this.setSelected(item);
this.sendInspectNodeMsg(item);
},
enter: () => {
this.setHover(item);
},
leave: () => {
this.cleanHover();
},
} as IUiMenuItem;
});
ccui.menu.showMenuByMouseEvent(event, menu, 0.8);
}
}
private sendInspectNodeMsg(node: any) {
if (node.uuid) {
this.inspector.sendMsgToContent(Msg.InspectNode, node.uuid);
}
}
private getPath(node: any) {
let path = [];
let parent = node;
while (parent) {
path.push(parent.name);
parent = parent.parent;
}
path = path.reverse();
return path.join("/");
}
private getTargetElement(): HTMLCanvasElement | null {
// @ts-ignore
if (typeof cc !== "undefined" && cc.game && cc.game.canvas) {
// @ts-ignore
return cc.game.canvas;
} else {
null;
}
}
private get isEngineV2() {
return this.engineVersion.startsWith("2.");
}
private get isEngineV3() {
return this.engineVersion.startsWith("3.");
}
public cleanHover() {
this.hoverNodes = [];
if (this.draw) {
this.draw.clear();
}
}
public cleanSelected() {
this.selectedNodes = [];
if (this.draw) {
this.draw.clear();
}
}
private initDrawNode() {
if (this.draw && !this.draw.isValid) {
this.draw = null;
}
if (this.draw) {
return;
}
const scene = cc.director.getScene();
if (!scene) {
return;
}
let node = new cc.Node("draw-node");
const canvas = cc.find("Canvas");
if (canvas) {
// 3.x 需要放到canvas下边
if (canvas.layer) {
node.layer = canvas.layer;
}
canvas.addChild(node);
} else {
scene.addChild(node);
}
this.draw = node.addComponent(cc.Graphics);
}
private hoverNodes = [];
private selectedNodes = [];
private resetIndex() {
if (this.isEngineV3) {
const len = this.draw.node.parent.children.length;
this.draw.node.setSiblingIndex(len);
} else if (this.isEngineV2) {
}
}
public update() {
this.initDrawNode();
if (!this.draw) {
return;
}
this.draw.clear();
this.resetIndex();
// this.testRect();
this.hoverNodes.forEach((node) => {
if (node.isValid) {
this.hintNode(node, {
fill: true,
fillColor: "#00ff0055",
stroke: true,
strokeColor: "#ffffff44",
});
}
});
this.selectedNodes.forEach((node) => {
if (node.isValid) {
this.hintNode(node, {
fill: false,
fillColor: "#ff0000",
stroke: true,
strokeColor: "#ff0000",
});
}
});
}
private testRect() {
const points = new RectPoints();
points.test(100, 100);
this.drawRect(points, {
fill: true,
fillColor: "#00ff0099",
stroke: true,
strokeColor: "#ff000099",
});
}
public setHover(node: any) {
if (node.isValid) {
this.hoverNodes = [node];
}
}
public setSelected(node: any) {
if (node.isValid) {
this.selectedNodes = [node];
}
}
private getRectPoints(node: any): RectPoints | null {
if (this.isEngineV2) {
const points: RectPoints = new RectPoints();
return points;
} else if (this.isEngineV3) {
// v3版本
if (!node.worldPosition) {
return null;
}
const transform = cc.UITransformComponent || cc.UITransform;
if (!transform) {
return null;
}
const tr = node.getComponent(transform);
if (!tr) {
return null;
}
const { anchorPoint, width, height } = tr;
const points: RectPoints = new RectPoints();
const x = -anchorPoint.x * width;
const y = -anchorPoint.y * height;
const m = node.worldMatrix;
[
new Point(x, y), //
new Point(x + width, y),
new Point(x + width, y + height),
new Point(x, y + height),
].forEach(({ x, y }: Point) => {
let rhw = m.m03 * x + m.m07 * y + m.m15;
rhw = rhw ? 1 / rhw : 1;
let worldX = (m.m00 * x + m.m04 * y + m.m12) * rhw;
let worldY = (m.m01 * x + m.m05 * y + m.m13) * rhw;
let worldZ = (m.m02 * x + m.m06 * y + m.m14) * rhw;
const pos = this.draw.getComponent(transform).convertToNodeSpaceAR(cc.v3(worldX, worldY, worldZ));
points.points.push(new Point(pos.x, pos.y));
});
return points;
}
return null;
}
/**
* wireframe模式
*/
private getTrangles(node: any) {
const comps = node._components;
if (comps && Array.isArray(comps)) {
const m = node.worldMatrix;
comps.forEach((comp) => {
const { renderData } = comp;
if (!renderData) {
return;
}
const { data, _vc: VertexCount, _ic: IndexCount, chunk } = renderData;
if (!data) {
return;
}
// 获取indexBuffer的索引顺序
const ib = chunk.meshBuffer.iData;
let indexOffset = chunk.meshBuffer.indexOffset;
const vidOrigin = chunk.vertexOffset;
const arr = [];
for (let i = 0; i < IndexCount; i++) {
const index = ib[indexOffset + i] - vidOrigin;
arr.push(index);
}
for (let j = 0; j < data.length; j++) {
const item = data[j];
const { x, y, z, u, v, color } = item;
let rhw = m.m03 * x + m.m07 * y + m.m15;
rhw = rhw ? 1 / rhw : 1;
let worldX = (m.m00 * x + m.m04 * y + m.m12) * rhw;
let worldY = (m.m01 * x + m.m05 * y + m.m13) * rhw;
let worldZ = (m.m02 * x + m.m06 * y + m.m14) * rhw;
// const pos = this.draw.getComponent(transform).convertToNodeSpaceAR(cc.v3(worldX, worldY, worldZ));
// points.points.push(new Point(pos.x, pos.y));
}
});
}
}
private getBox(node: any) {
if (node.getBoundingBoxToWorld) {
return node.getBoundingBoxToWorld();
}
if (cc.UITransformComponent) {
const tr = node.getComponent(cc.UITransformComponent);
if (tr && tr.getBoundingBoxToWorld) {
return tr.getBoundingBoxToWorld();
}
}
return null;
}
private hintNode(node: any, opts: DrawOptions) {
const points = this.getRectPoints(node);
if (!points) {
return;
}
this.drawRect(points, opts);
}
private drawRect(points: RectPoints, opts: DrawOptions) {
this.draw.lineWidth = 2;
for (let i = 0; i < points.len; i++) {
const p = points.at(i);
if (i === 0) {
this.draw.moveTo(p.x, p.y);
} else {
this.draw.lineTo(p.x, p.y);
}
}
this.draw.close();
if (opts.stroke) {
this.draw.strokeColor = new cc.Color().fromHEX(opts.strokeColor);
this.draw.stroke();
}
if (opts.fill) {
this.draw.fillColor = new cc.Color().fromHEX(opts.fillColor);
this.draw.fill();
}
}
private fillRect(points: RectPoints, color: string) {
this.draw.fillColor = new cc.Color().fromHEX(color);
for (let i = 0; i < points.len; i++) {
const p = points.at(i);
if (i === 0) {
this.draw.moveTo(p.x, p.y);
} else {
this.draw.lineTo(p.x, p.y);
}
}
this.draw.close();
this.draw.fill();
}
}

View File

@ -0,0 +1,92 @@
declare const cc: any;
export interface DrawOptions {
fill: boolean;
fillColor: string;
stroke: boolean;
strokeColor: string;
}
export class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
export class RectPoints {
points: Point[] = [];
get len() {
return this.points.length;
}
add(point: Point) {
this.points.push(point);
}
at(index: number) {
return this.points[index];
}
test(width: number, height: number) {
this.points.push(new Point(0, 0));
this.points.push(new Point(width, 0));
this.points.push(new Point(width, height));
this.points.push(new Point(0, height));
}
}
export class HintAdapter {
protected draw = null;
constructor() {}
resetIndex() {
throw new Error("not implemented");
}
getRectPoints(node: any): RectPoints | null {
throw new Error("not implemented");
}
clear() {
if (this.draw) {
this.draw.clear();
}
}
initDrawNode() {
if (this.draw && !this.draw.isValid) {
this.draw = null;
}
if (this.draw) {
return;
}
const scene = cc.director.getScene();
if (!scene) {
return;
}
let node = new cc.Node("draw-node");
const canvas = cc.find("Canvas");
this.addDraw(scene, canvas, node);
this.draw = node.addComponent(cc.Graphics);
}
public isDrawValid() {
return this.draw && this.draw.isValid;
}
protected addDraw(scene: any, canvas: any, node: any) {
throw new Error("not implemented");
}
public drawRect(points: RectPoints, opts: DrawOptions) {
this.draw.lineWidth = 2;
for (let i = 0; i < points.len; i++) {
const p = points.at(i);
if (i === 0) {
this.draw.moveTo(p.x, p.y);
} else {
this.draw.lineTo(p.x, p.y);
}
}
if (points.len) {
this.draw.close();
}
if (opts.stroke) {
this.draw.strokeColor = new cc.Color().fromHEX(opts.strokeColor);
this.draw.stroke();
}
if (opts.fill) {
this.draw.fillColor = new cc.Color().fromHEX(opts.fillColor);
this.draw.fill();
}
}
}

View File

@ -0,0 +1,99 @@
import { HintAdapter, Point, RectPoints } from "./adapter";
declare const cc: any;
export class HintV2 extends HintAdapter {
constructor() {
super();
}
protected addDraw(scene: any, canvas: any, node: any): void {
scene.addChild(node);
}
private canvasBoundingRect = {
left: 0,
top: 0,
width: 0,
height: 0,
};
private _updateCanvasBoundingRect() {
// @ts-ignore
const element: any = cc.game.canvas;
var docElem = document.documentElement;
var leftOffset = window.pageXOffset - docElem.clientLeft;
var topOffset = window.pageYOffset - docElem.clientTop;
if (element.getBoundingClientRect) {
var box = element.getBoundingClientRect();
this.canvasBoundingRect.left = box.left + leftOffset;
this.canvasBoundingRect.top = box.top + topOffset;
this.canvasBoundingRect.width = box.width;
this.canvasBoundingRect.height = box.height;
} else if (element instanceof HTMLCanvasElement) {
this.canvasBoundingRect.left = leftOffset;
this.canvasBoundingRect.top = topOffset;
this.canvasBoundingRect.width = element.width;
this.canvasBoundingRect.height = element.height;
} else {
this.canvasBoundingRect.left = leftOffset;
this.canvasBoundingRect.top = topOffset;
this.canvasBoundingRect.width = parseInt(element.style.width);
this.canvasBoundingRect.height = parseInt(element.style.height);
}
}
getPointByEvent(event: MouseEvent) {
this._updateCanvasBoundingRect();
if (event.pageX != null)
//not avalable in <= IE8
return { x: event.pageX, y: event.pageY };
this.canvasBoundingRect.left -= document.body.scrollLeft;
this.canvasBoundingRect.top -= document.body.scrollTop;
return { x: event.clientX, y: event.clientY };
}
private hit(event: MouseEvent) {
let location = this.getPointByEvent(event);
// @ts-ignore
cc.view._convertMouseToLocationInView(location, this.canvasBoundingRect);
}
resetIndex(): void {
const node = this.draw.node;
node.zIndex = node.parent.children.length;
}
getRectPoints(node: any): RectPoints | null {
const points: RectPoints = new RectPoints();
const { anchorX, anchorY, width, height } = node;
const x = -anchorX * width;
const y = -anchorY * height;
const matrixm = node._worldMatrix.m;
const m00 = matrixm[0],
m01 = matrixm[1],
m04 = matrixm[4],
m05 = matrixm[5],
m12_tx = matrixm[12],
m13_ty = matrixm[13];
[
new Point(x, y), //
new Point(x + width, y),
new Point(x + width, y + height),
new Point(x, y + height),
].forEach(({ x, y }: Point) => {
const worldX = x * m00 + y * m04 + m12_tx; // x
const worldY = x * m01 + y * m05 + m13_ty; // y
const pos = this.draw.node.convertToNodeSpaceAR(cc.v2(worldX, worldY));
points.add(new Point(pos.x, pos.y));
});
return points;
}
private getBox(node: any) {
if (node.getBoundingBoxToWorld) {
return node.getBoundingBoxToWorld();
}
if (cc.UITransformComponent) {
const tr = node.getComponent(cc.UITransformComponent);
if (tr && tr.getBoundingBoxToWorld) {
return tr.getBoundingBoxToWorld();
}
}
return null;
}
}

View File

@ -0,0 +1,98 @@
import { HintAdapter, Point, RectPoints } from "./adapter";
declare const cc: any;
export class HintV3 extends HintAdapter {
resetIndex(): void {
const node = this.draw.node;
const len = node.parent.children.length;
node.setSiblingIndex(len);
}
protected addDraw(scene: any, canvas: any, node: any): void {
if (canvas) {
// 3.x 需要放到canvas下边
if (canvas.layer) {
node.layer = canvas.layer;
}
canvas.addChild(node);
} else {
scene.addChild(node);
}
}
getRectPoints(node: any): RectPoints | null {
if (!node.worldPosition) {
return null;
}
const transform = cc.UITransformComponent || cc.UITransform;
if (!transform) {
return null;
}
const tr = node.getComponent(transform);
if (!tr) {
return null;
}
const { anchorPoint, width, height } = tr;
const points: RectPoints = new RectPoints();
const x = -anchorPoint.x * width;
const y = -anchorPoint.y * height;
const m = node.worldMatrix;
[
new Point(x, y), //
new Point(x + width, y),
new Point(x + width, y + height),
new Point(x, y + height),
].forEach(({ x, y }: Point) => {
let rhw = m.m03 * x + m.m07 * y + m.m15;
rhw = rhw ? 1 / rhw : 1;
let worldX = (m.m00 * x + m.m04 * y + m.m12) * rhw;
let worldY = (m.m01 * x + m.m05 * y + m.m13) * rhw;
let worldZ = (m.m02 * x + m.m06 * y + m.m14) * rhw;
const pos = this.draw.getComponent(transform).convertToNodeSpaceAR(cc.v3(worldX, worldY, worldZ));
points.add(new Point(pos.x, pos.y));
});
return points;
}
/**
* wireframe模式
*/
private getTrangles(node: any) {
const comps = node._components;
if (comps && Array.isArray(comps)) {
const m = node.worldMatrix;
comps.forEach((comp) => {
const { renderData } = comp;
if (!renderData) {
return;
}
const { data, _vc: VertexCount, _ic: IndexCount, chunk } = renderData;
if (!data) {
return;
}
// 获取indexBuffer的索引顺序
const ib = chunk.meshBuffer.iData;
let indexOffset = chunk.meshBuffer.indexOffset;
const vidOrigin = chunk.vertexOffset;
const arr = [];
for (let i = 0; i < IndexCount; i++) {
const index = ib[indexOffset + i] - vidOrigin;
arr.push(index);
}
for (let j = 0; j < data.length; j++) {
const item = data[j];
const { x, y, z, u, v, color } = item;
let rhw = m.m03 * x + m.m07 * y + m.m15;
rhw = rhw ? 1 / rhw : 1;
let worldX = (m.m00 * x + m.m04 * y + m.m12) * rhw;
let worldY = (m.m01 * x + m.m05 * y + m.m13) * rhw;
let worldZ = (m.m02 * x + m.m06 * y + m.m14) * rhw;
// const pos = this.draw.getComponent(transform).convertToNodeSpaceAR(cc.v3(worldX, worldY, worldZ));
// points.points.push(new Point(pos.x, pos.y));
}
});
}
}
}

View File

@ -0,0 +1,189 @@
import ccui from "@xuyanfeng/cc-ui";
import { IUiMenuItem } from "@xuyanfeng/cc-ui/types/cc-menu/const";
import { DocumentEvent } from "../../const";
import { Inspector } from "../inspector";
import { Msg } from "../../../core/types";
import { DrawOptions, HintAdapter, RectPoints } from "./adapter";
import { HintV2 } from "./hint-v2";
import { HintV3 } from "./hint-v3";
declare const cc: any;
/**
* hint的流程
*/
export class Hint {
private engineVersion: string = "";
private hintAdapter: HintAdapter = null;
public setEngineVersion(version: string) {
this.engineVersion = version;
if (version.startsWith("2.")) {
this.hintAdapter = new HintV2();
} else if (version.startsWith("3.")) {
this.hintAdapter = new HintV3();
}
}
private inspector: Inspector = null;
constructor(inspector: Inspector) {
this.inspector = inspector;
document.addEventListener(DocumentEvent.GameInspectorBegan, (event: CustomEvent) => {
const el = this.getTargetElement();
if (!el) {
return;
}
const cursor = el.style.cursor;
el.style.cursor = "zoom-in";
const test = (event: MouseEvent) => {
event.stopPropagation();
event.preventDefault();
el.removeEventListener("mousedown", test, true);
el.style.cursor = cursor;
const e = new CustomEvent(DocumentEvent.GameInspectorEnd);
document.dispatchEvent(e);
this.updateHint(event, el);
};
el.addEventListener("mousedown", test, true);
});
}
private updateHint(event: MouseEvent, canvas: HTMLCanvasElement) {
this.cleanHover();
this.cleanSelected();
const rect = canvas.getBoundingClientRect();
let x = event.clientX - rect.x;
let y = rect.y + rect.height - event.clientY;
x *= window.devicePixelRatio;
y *= window.devicePixelRatio;
this.inspector.updateTreeInfo(false);
const nodes = [];
this.inspector.forEachNode((item) => {
let b = false;
if (this.inspector.isCreatorV3) {
b = item._uiProps?.uiTransformComp?.hitTest({ x, y }, 0);
} else {
}
if (b && item.active && item.activeInHierarchy) {
nodes.push(item);
}
});
if (nodes.length === 1) {
const item = nodes[0];
this.setSelected(item);
this.sendInspectNodeMsg(item);
} else {
nodes.reverse();
const menu = nodes.map((item) => {
const path = this.getPath(item);
return {
name: path,
callback: () => {
this.cleanHover();
this.setSelected(item);
this.sendInspectNodeMsg(item);
},
enter: () => {
this.setHover(item);
},
leave: () => {
this.cleanHover();
},
} as IUiMenuItem;
});
ccui.menu.showMenuByMouseEvent(event, menu, 0.8);
}
}
private sendInspectNodeMsg(node: any) {
if (node.uuid) {
this.inspector.sendMsgToContent(Msg.InspectNode, node.uuid);
}
}
private getPath(node: any) {
let path = [];
let parent = node;
while (parent) {
path.push(parent.name);
parent = parent.parent;
}
path = path.reverse();
return path.join("/");
}
private getTargetElement(): HTMLCanvasElement | null {
// @ts-ignore
if (typeof cc !== "undefined" && cc.game && cc.game.canvas) {
// @ts-ignore
return cc.game.canvas;
} else {
null;
}
}
public cleanHover() {
this.hoverNodes = [];
this.hintAdapter.clear();
}
public cleanSelected() {
this.selectedNodes = [];
this.hintAdapter.clear();
}
private hoverNodes = [];
private selectedNodes = [];
public update() {
this.hintAdapter.initDrawNode();
if (!this.hintAdapter.isDrawValid()) {
return;
}
this.hintAdapter.clear();
this.hintAdapter.resetIndex();
// this.testRect();
this.hoverNodes.forEach((node) => {
if (node.isValid) {
this.hintNode(node, {
fill: true,
fillColor: "#00ff0055",
stroke: true,
strokeColor: "#ffffff44",
});
}
});
this.selectedNodes.forEach((node) => {
if (node.isValid) {
this.hintNode(node, {
fill: false,
fillColor: "#ff0000",
stroke: true,
strokeColor: "#ff0000",
});
}
});
}
private testRect() {
const points = new RectPoints();
points.test(100, 100);
this.hintAdapter.drawRect(points, {
fill: true,
fillColor: "#00ff0099",
stroke: true,
strokeColor: "#ff000099",
});
}
public setHover(node: any) {
if (node.isValid) {
this.hoverNodes = [node];
}
}
public setSelected(node: any) {
if (node.isValid) {
this.selectedNodes = [node];
}
}
private hintNode(node: any, opts: DrawOptions) {
const points = this.hintAdapter.getRectPoints(node);
if (!points) {
return;
}
this.hintAdapter.drawRect(points, opts);
}
}

View File

@ -110,7 +110,7 @@ export class Inspector extends InjectEvent {
const uuid: string = pluginEvent.data;
if (uuid) {
const node = this.inspectorGameMemoryStorage[uuid];
if (node.isValid) {
if (node && node.isValid) {
this.hint.setHover(node);
} else {
this.hint.cleanHover();
@ -124,7 +124,7 @@ export class Inspector extends InjectEvent {
const uuid: string = pluginEvent.data;
if (uuid) {
const node = this.inspectorGameMemoryStorage[uuid];
if (node.isValid) {
if (node && node.isValid) {
this.hint.setSelected(node);
} else {
this.hint.cleanSelected();
@ -159,7 +159,7 @@ export class Inspector extends InjectEvent {
this.hint.update();
});
const version = this.getEngineVersion();
this.hint.engineVersion = version;
this.hint.setEngineVersion(version);
if (b && version) {
this.uploadEngineVersion(version);
}