You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

253 lines
8.8 KiB

(function (global) {
'use strict';
/**
* Canvas pick / inspect mode without project code.
* @constructor
*/
function NodePickController() {
this.mode = 'game';
this._cc = null;
this._selected = null;
this._onSelect = null;
this._onMode = null;
this._boundDown = this._onPointerDown.bind(this);
this._canvas = null;
this._overlay = null;
this._raf = 0;
}
/** @param {*} cc */
NodePickController.prototype.bindEngine = function (cc) {
this._cc = cc;
this._canvas = document.getElementById('GameCanvas');
this._ensureOverlay();
this.setMode('game');
};
/** 挂到 pw-root,用 fixed 定位,避免被预览容器 overflow 裁切 */
NodePickController.prototype._ensureOverlay = function () {
var overlay = document.getElementById('pw-pick-overlay');
var root = document.getElementById('pw-root');
if (!overlay) {
overlay = document.createElement('div');
overlay.id = 'pw-pick-overlay';
overlay.className = 'pw-pick-overlay';
overlay.hidden = true;
}
if (root && overlay.parentNode !== root) root.appendChild(overlay);
this._overlay = overlay;
};
/** @param {function(*):void} cb */
NodePickController.prototype.onSelect = function (cb) {
this._onSelect = cb;
};
/** @param {function(string):void} cb */
NodePickController.prototype.onModeChange = function (cb) {
this._onMode = cb;
};
/** @returns {*} */
NodePickController.prototype.getSelected = function () {
return this._selected;
};
/**
* @param {string} mode game|inspect
*/
NodePickController.prototype.setMode = function (mode) {
this.mode = mode === 'inspect' ? 'inspect' : 'game';
var root = document.getElementById('pw-root');
if (root) root.classList.toggle('pw-inspect', this.mode === 'inspect');
var gameBtn = document.getElementById('pw-mode-game');
var inspectBtn = document.getElementById('pw-mode-inspect');
if (gameBtn) gameBtn.classList.toggle('active', this.mode === 'game');
if (inspectBtn) inspectBtn.classList.toggle('active', this.mode === 'inspect');
if (!this._canvas) return;
if (this.mode === 'inspect') {
this._canvas.addEventListener('pointerdown', this._boundDown, true);
} else {
this._canvas.removeEventListener('pointerdown', this._boundDown, true);
}
if (this._selected) this._startOverlayLoop();
else {
this._stopOverlayLoop();
if (this._overlay) this._overlay.hidden = true;
}
if (this._onMode) this._onMode(this.mode);
};
/** @param {*} node */
NodePickController.prototype.select = function (node) {
this._selected = node || null;
this._ensureOverlay();
if (this._selected) this._startOverlayLoop();
else {
this._stopOverlayLoop();
if (this._overlay) this._overlay.hidden = true;
}
this._updateOverlay();
if (this._onSelect) this._onSelect(this._selected);
};
NodePickController.prototype._onPointerDown = function (ev) {
if (this.mode !== 'inspect') return;
ev.preventDefault();
ev.stopPropagation();
var node = this._hitTest(ev.clientX, ev.clientY);
this.select(node);
};
NodePickController.prototype._hitTest = function (clientX, clientY) {
var cc = this._cc;
var canvas = this._canvas;
if (!cc || !canvas) return null;
var scene = cc.director.getScene();
if (!scene) return null;
var stack = [];
this._walk(scene, function (n) { stack.push(n); });
for (var i = stack.length - 1; i >= 0; i--) {
var node = stack[i];
if (!node || !node.activeInHierarchy) continue;
var uit = node.getComponent && node.getComponent(cc.UITransform);
if (!uit) continue;
var box = this._getNodeClientRect(node);
if (!box) continue;
if (clientX >= box.left && clientX <= box.right && clientY >= box.top && clientY <= box.bottom) {
return node;
}
}
return null;
};
NodePickController.prototype._walk = function (node, fn) {
if (!node) return;
fn(node);
var children = node.children;
if (!children) return;
for (var i = 0; i < children.length; i++) this._walk(children[i], fn);
};
NodePickController.prototype._startOverlayLoop = function () {
var self = this;
this._stopOverlayLoop();
var tick = function () {
self._updateOverlay();
self._raf = requestAnimationFrame(tick);
};
this._raf = requestAnimationFrame(tick);
};
NodePickController.prototype._stopOverlayLoop = function () {
if (this._raf) cancelAnimationFrame(this._raf);
this._raf = 0;
};
/**
* UI 节点包围盒 → 浏览器 client 坐标。
* 使用可见区域中心原点映射(与竖屏 UI Canvas 一致),避免 worldToScreen 在自定义帧尺寸下偏移。
* @param {*} node
*/
NodePickController.prototype._getNodeClientRect = function (node) {
var cc = this._cc;
var canvas = this._canvas;
if (!cc || !canvas || !node || !node.isValid) return null;
var uit = node.getComponent && node.getComponent(cc.UITransform);
if (!uit) return null;
var canvasRect = canvas.getBoundingClientRect();
if (canvasRect.width < 2 || canvasRect.height < 2) return null;
var corners = null;
if (typeof uit.getBoundingBoxToWorld === 'function') {
var box = uit.getBoundingBoxToWorld();
if (box && box.width >= 0 && box.height >= 0) {
corners = [
{ x: box.x, y: box.y },
{ x: box.x + box.width, y: box.y },
{ x: box.x + box.width, y: box.y + box.height },
{ x: box.x, y: box.y + box.height },
];
}
}
if (!corners) corners = this._localCornersToWorld(uit);
if (!corners) return null;
var vs = cc.view && typeof cc.view.getVisibleSize === 'function'
? cc.view.getVisibleSize()
: { width: canvasRect.width, height: canvasRect.height };
var vw = Math.max(1, vs.width);
var vh = Math.max(1, vs.height);
var xs = [];
var ys = [];
for (var i = 0; i < corners.length; i++) {
// UI 世界坐标:原点在可见区域中心,Y 向上
var nx = corners[i].x / vw + 0.5;
var ny = 0.5 - corners[i].y / vh;
xs.push(canvasRect.left + nx * canvasRect.width);
ys.push(canvasRect.top + ny * canvasRect.height);
}
var left = Math.min.apply(null, xs);
var right = Math.max.apply(null, xs);
var top = Math.min.apply(null, ys);
var bottom = Math.max.apply(null, ys);
return {
left: left,
top: top,
right: right,
bottom: bottom,
width: Math.max(2, right - left),
height: Math.max(2, bottom - top),
};
};
NodePickController.prototype._localCornersToWorld = function (uit) {
var cc = this._cc;
var w = uit.width;
var h = uit.height;
var ax = uit.anchorX != null ? uit.anchorX : (uit.anchorPoint ? uit.anchorPoint.x : 0.5);
var ay = uit.anchorY != null ? uit.anchorY : (uit.anchorPoint ? uit.anchorPoint.y : 0.5);
var locals = [
{ x: -ax * w, y: -ay * h },
{ x: (1 - ax) * w, y: -ay * h },
{ x: (1 - ax) * w, y: (1 - ay) * h },
{ x: -ax * w, y: (1 - ay) * h },
];
if (typeof uit.convertToWorldSpaceAR !== 'function' || !cc.Vec3) return null;
var out = [];
for (var i = 0; i < 4; i++) {
out.push(uit.convertToWorldSpaceAR(new cc.Vec3(locals[i].x, locals[i].y, 0)));
}
return out;
};
NodePickController.prototype._updateOverlay = function () {
var overlay = this._overlay;
var node = this._selected;
var cc = this._cc;
if (!overlay) return;
if (!node || !node.isValid || !cc) {
overlay.hidden = true;
return;
}
var box = this._getNodeClientRect(node);
if (!box) {
overlay.hidden = true;
return;
}
overlay.hidden = false;
overlay.style.position = 'fixed';
overlay.style.left = box.left + 'px';
overlay.style.top = box.top + 'px';
overlay.style.width = box.width + 'px';
overlay.style.height = box.height + 'px';
};
global.PWNodePick = NodePickController;
})(window);