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.
515 lines
19 KiB
515 lines
19 KiB
(function (global) {
|
|
'use strict';
|
|
|
|
var WorkbenchAgent = global.PWAgent;
|
|
var U = global.PWAgentUtil;
|
|
var nodeId = U && U.nodeId;
|
|
var walk = U && U.walk;
|
|
if (!WorkbenchAgent) {
|
|
console.error('[调试工作台] agent-pick 需先加载 agent-core');
|
|
return;
|
|
}
|
|
|
|
function log() {
|
|
try {
|
|
var fn = console.log || console.info;
|
|
fn.apply(console, ['[检视]'].concat(Array.prototype.slice.call(arguments)));
|
|
} catch (e) { /* */ }
|
|
}
|
|
|
|
/** 检视模式:点选节点(Creator 场景拾取) */
|
|
WorkbenchAgent.prototype._setPickMode = function (mode) {
|
|
this._pickMode = mode === 'inspect' ? 'inspect' : 'game';
|
|
try {
|
|
document.documentElement.classList.toggle('pw-inspect', this._pickMode === 'inspect');
|
|
var root = document.getElementById('pw-root') || document.body;
|
|
if (root && root.classList) {
|
|
root.classList.toggle('pw-inspect', this._pickMode === 'inspect');
|
|
}
|
|
} catch (e) { /* */ }
|
|
this._bindCanvasPick(this._pickMode === 'inspect');
|
|
log('setMode', this._pickMode);
|
|
return this._pickMode;
|
|
};
|
|
|
|
/**
|
|
* 壳层 / 游戏内共用入口。
|
|
* 同一位置重复点击时,在重叠节点里从上到下循环切换。
|
|
* @param {{clientX:number, clientY:number, space?:string}} params
|
|
*/
|
|
WorkbenchAgent.prototype._pickAt = function (params) {
|
|
var clientX = params && Number(params.clientX);
|
|
var clientY = params && Number(params.clientY);
|
|
if (!isFinite(clientX) || !isFinite(clientY)) {
|
|
return { uuid: this._selectedUuid || '', ok: false, reason: 'bad-coord' };
|
|
}
|
|
var r = this._hitTestDetailed(clientX, clientY);
|
|
var uuid = r.uuid || '';
|
|
var cycleInfo = '';
|
|
|
|
// 同点循环:位置接近时在命中列表里从上到下轮换
|
|
if (r.hits && r.hits.length) {
|
|
uuid = this._cyclePick(clientX, clientY, r.hits);
|
|
if (this._pickCycleList && this._pickCycleList.length > 1) {
|
|
cycleInfo = ' [' + (this._pickCycleIndex + 1) + '/' + this._pickCycleList.length + ']';
|
|
}
|
|
}
|
|
|
|
log('pickAt', clientX, clientY, '→', uuid || '(none)', (r.method || '') + cycleInfo);
|
|
var res = this._selectNode(uuid);
|
|
res.method = r.method || '';
|
|
res.cycle = this._pickCycleList && this._pickCycleList.length
|
|
? { index: this._pickCycleIndex, total: this._pickCycleList.length, names: this._pickCycleList.map(function (h) { return h.name; }) }
|
|
: null;
|
|
res.debug = r.debug || null;
|
|
return res;
|
|
};
|
|
|
|
/**
|
|
* 同一坐标重复点击 → 在命中列表(上→下)中循环。
|
|
* 位置偏移超过阈值则重置,从最上层重新开始。
|
|
*/
|
|
WorkbenchAgent.prototype._cyclePick = function (clientX, clientY, hits) {
|
|
var EPS = 8;
|
|
if (!hits || !hits.length) return '';
|
|
this._pickCycleList = hits;
|
|
this._pickCycleX = clientX;
|
|
this._pickCycleY = clientY;
|
|
|
|
var sameSpot = this._pickCycleArmed &&
|
|
Math.abs(clientX - (this._pickCycleLastX || 0)) <= EPS &&
|
|
Math.abs(clientY - (this._pickCycleLastY || 0)) <= EPS;
|
|
this._pickCycleLastX = clientX;
|
|
this._pickCycleLastY = clientY;
|
|
this._pickCycleArmed = true;
|
|
|
|
if (!sameSpot) {
|
|
this._pickCycleIndex = 0;
|
|
return hits[0].uuid;
|
|
}
|
|
|
|
// 从当前选中节点的下一层开始
|
|
var idx = 0;
|
|
for (var i = 0; i < hits.length; i++) {
|
|
if (hits[i].uuid === this._selectedUuid) {
|
|
idx = (i + 1) % hits.length;
|
|
break;
|
|
}
|
|
}
|
|
// 若上一选中不在列表里,则从 0 起
|
|
this._pickCycleIndex = idx;
|
|
return hits[idx].uuid;
|
|
};
|
|
|
|
WorkbenchAgent.prototype._bindCanvasPick = function (on) {
|
|
var self = this;
|
|
if (!this._onPickDown) {
|
|
this._onPickDown = function (ev) {
|
|
if (self._pickMode !== 'inspect') return;
|
|
if (ev && ev.preventDefault) ev.preventDefault();
|
|
if (ev && ev.stopPropagation) ev.stopPropagation();
|
|
self._pickAt({ clientX: ev.clientX, clientY: ev.clientY, space: 'viewport' });
|
|
};
|
|
}
|
|
if (this._pickBoundEls && this._pickBoundEls.length) {
|
|
for (var i = 0; i < this._pickBoundEls.length; i++) {
|
|
this._pickBoundEls[i].removeEventListener('pointerdown', this._onPickDown, true);
|
|
this._pickBoundEls[i].removeEventListener('mousedown', this._onPickDown, true);
|
|
}
|
|
}
|
|
this._pickBoundEls = [];
|
|
if (!on || this._pickMode !== 'inspect') return;
|
|
|
|
var targets = this._pickEventTargets();
|
|
for (var j = 0; j < targets.length; j++) {
|
|
if (!targets[j]) continue;
|
|
targets[j].addEventListener('pointerdown', this._onPickDown, true);
|
|
targets[j].addEventListener('mousedown', this._onPickDown, true);
|
|
this._pickBoundEls.push(targets[j]);
|
|
}
|
|
window.addEventListener('pointerdown', this._onPickDown, true);
|
|
window.addEventListener('mousedown', this._onPickDown, true);
|
|
this._pickBoundEls.push(window);
|
|
log('bind pick', this._pickBoundEls.length);
|
|
};
|
|
|
|
WorkbenchAgent.prototype._pickEventTargets = function () {
|
|
var list = [];
|
|
var ids = ['GameCanvas', 'Cocos3dGameContainer', 'GameDiv', 'content', 'pw-center', 'pw-game-host'];
|
|
for (var i = 0; i < ids.length; i++) {
|
|
var el = document.getElementById(ids[i]);
|
|
if (el && list.indexOf(el) < 0) list.push(el);
|
|
}
|
|
var cv = global.cc && global.cc.game && global.cc.game.canvas;
|
|
if (cv && list.indexOf(cv) < 0) list.push(cv);
|
|
return list;
|
|
};
|
|
|
|
WorkbenchAgent.prototype._getCanvasEl = function () {
|
|
return document.getElementById('GameCanvas') ||
|
|
(global.cc && global.cc.game && global.cc.game.canvas) ||
|
|
null;
|
|
};
|
|
|
|
WorkbenchAgent.prototype._uiCamera = function () {
|
|
var cc = this.cc;
|
|
if (!cc) return null;
|
|
try {
|
|
var scene = cc.director && cc.director.getScene && cc.director.getScene();
|
|
if (scene && cc.UITransform) {
|
|
var canvasNode = null;
|
|
walk(scene, function (n) {
|
|
if (!canvasNode && n && n.name === 'Canvas') canvasNode = n;
|
|
});
|
|
if (canvasNode && canvasNode.getComponent && cc.Camera) {
|
|
var cam = canvasNode.getComponent(cc.Camera);
|
|
if (cam) return cam;
|
|
}
|
|
}
|
|
if (cc.director && cc.director.root && cc.director.root.batcher2D && cc.director.root.batcher2D.camera) {
|
|
return cc.director.root.batcher2D.camera;
|
|
}
|
|
if (cc.director && cc.director.root && cc.director.root.cameraList && cc.director.root.cameraList.length) {
|
|
var list = cc.director.root.cameraList;
|
|
for (var i = 0; i < list.length; i++) {
|
|
if (list[i] && (list[i].camera || list[i])._uiCamera !== false) {
|
|
return list[i].camera || list[i];
|
|
}
|
|
}
|
|
}
|
|
} catch (e) { /* */ }
|
|
return null;
|
|
};
|
|
|
|
WorkbenchAgent.prototype._canvasWorldBox = function () {
|
|
var cc = this.cc;
|
|
if (!cc || !cc.UITransform) return null;
|
|
var scene = cc.director && cc.director.getScene && cc.director.getScene();
|
|
if (!scene) return null;
|
|
var canvasNode = null;
|
|
walk(scene, function (n) {
|
|
if (!canvasNode && n && n.name === 'Canvas' && n.activeInHierarchy) canvasNode = n;
|
|
});
|
|
if (!canvasNode || !canvasNode.getComponent) return null;
|
|
var uit = canvasNode.getComponent(cc.UITransform);
|
|
if (!uit || !uit.getBoundingBoxToWorld) return null;
|
|
var box = null;
|
|
try { box = uit.getBoundingBoxToWorld(); } catch (e) { return null; }
|
|
if (!box || !(box.width > 0) || !(box.height > 0)) return null;
|
|
return { x: box.x, y: box.y, width: box.width, height: box.height };
|
|
};
|
|
|
|
WorkbenchAgent.prototype._nodeDepth = function (node) {
|
|
var d = 0;
|
|
var p = node && node.parent;
|
|
while (p) {
|
|
d += 1;
|
|
p = p.parent;
|
|
}
|
|
return d;
|
|
};
|
|
|
|
/** 把 client 点换算到 Cocos 屏幕坐标(原点左下、CSS 像素,不乘 DPR) */
|
|
WorkbenchAgent.prototype._toCocosScreen = function (clientX, clientY) {
|
|
var canvas = this._getCanvasEl();
|
|
if (!canvas) return null;
|
|
var rect = canvas.getBoundingClientRect();
|
|
if (!rect.width || !rect.height) return null;
|
|
return {
|
|
sx: clientX - rect.left,
|
|
sy: rect.height - (clientY - rect.top),
|
|
rect: rect,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* 多策略命中,返回最优节点。
|
|
* @returns {{uuid:string, method:string, debug:object}}
|
|
*/
|
|
WorkbenchAgent.prototype._hitTestDetailed = function (clientX, clientY) {
|
|
var cc = this.cc;
|
|
var empty = { uuid: '', method: 'none', debug: null };
|
|
if (!cc || !cc.director) {
|
|
empty.method = 'no-cc';
|
|
return empty;
|
|
}
|
|
var scene = cc.director.getScene();
|
|
if (!scene) {
|
|
empty.method = 'no-scene';
|
|
return empty;
|
|
}
|
|
|
|
var stack = [];
|
|
walk(scene, function (n) {
|
|
if (n && n.activeInHierarchy) stack.push(n);
|
|
});
|
|
|
|
var cam = this._uiCamera();
|
|
var screen = this._toCocosScreen(clientX, clientY);
|
|
var cbox = this._canvasWorldBox();
|
|
|
|
var worlds = [];
|
|
if (screen && cbox) {
|
|
var u = (clientX - screen.rect.left) / screen.rect.width;
|
|
var v = (clientY - screen.rect.top) / screen.rect.height;
|
|
worlds.push({
|
|
x: cbox.x + u * cbox.width,
|
|
y: cbox.y + (1 - v) * cbox.height,
|
|
tag: 'canvasBox',
|
|
});
|
|
}
|
|
if (screen && cc.view && cc.view.getDesignResolutionSize) {
|
|
var d = cc.view.getDesignResolutionSize();
|
|
if (d && d.width) {
|
|
var u2 = (clientX - screen.rect.left) / screen.rect.width;
|
|
var v2 = (clientY - screen.rect.top) / screen.rect.height;
|
|
worlds.push({ x: u2 * d.width, y: (1 - v2) * d.height, tag: 'design' });
|
|
worlds.push({ x: (u2 - 0.5) * d.width, y: (0.5 - v2) * d.height, tag: 'center' });
|
|
}
|
|
}
|
|
|
|
var self = this;
|
|
var uiCount = 0;
|
|
var samples = [];
|
|
var hits = [];
|
|
var hitUuids = {};
|
|
|
|
var getBox = function (uit) {
|
|
var box = null;
|
|
try {
|
|
if (uit && uit.getBoundingBoxToWorld) box = uit.getBoundingBoxToWorld();
|
|
} catch (e) { box = null; }
|
|
if (!box) return null;
|
|
var x = Number(box.x);
|
|
var y = Number(box.y);
|
|
var w = Number(box.width);
|
|
var h = Number(box.height);
|
|
if (!isFinite(x) || !isFinite(y) || !isFinite(w) || !isFinite(h)) return null;
|
|
if (w < 0) { x += w; w = -w; }
|
|
if (h < 0) { y += h; h = -h; }
|
|
return { x: x, y: y, width: w, height: h };
|
|
};
|
|
|
|
var siblingIndex = function (node) {
|
|
var p = node && node.parent;
|
|
if (!p || !p.children) return 0;
|
|
var idx = p.children.indexOf ? p.children.indexOf(node) : 0;
|
|
return idx < 0 ? 0 : idx;
|
|
};
|
|
|
|
var addHit = function (node, method, area) {
|
|
var id = nodeId(node);
|
|
if (hitUuids[id]) return;
|
|
hitUuids[id] = true;
|
|
hits.push({
|
|
uuid: id,
|
|
name: node.name || '',
|
|
isCanvas: (node.name || '') === 'Canvas',
|
|
depth: self._nodeDepth(node),
|
|
sIndex: siblingIndex(node),
|
|
area: area || 0,
|
|
method: method,
|
|
});
|
|
};
|
|
|
|
for (var s = 0; s < stack.length; s++) {
|
|
var node = stack[s];
|
|
if (!node || !node.getComponent) continue;
|
|
var uit = null;
|
|
try {
|
|
uit = (cc.UITransform && node.getComponent(cc.UITransform)) ||
|
|
node.getComponent('cc.UITransform') ||
|
|
node.getComponent('UITransform') ||
|
|
null;
|
|
} catch (e1) { uit = null; }
|
|
if (!uit) {
|
|
// 兜底:任何带 getBoundingBoxToWorld 的组件
|
|
var comps = node.components || [];
|
|
for (var ci = 0; ci < comps.length; ci++) {
|
|
if (comps[ci] && typeof comps[ci].getBoundingBoxToWorld === 'function') {
|
|
uit = comps[ci];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!uit) continue;
|
|
uiCount += 1;
|
|
|
|
var box = getBox(uit);
|
|
if (!box) continue;
|
|
var area = box.width * box.height;
|
|
if (samples.length < 5) {
|
|
samples.push({
|
|
name: node.name || '?',
|
|
box: box,
|
|
});
|
|
}
|
|
|
|
// A: hitTest(camera, screen)
|
|
if (cam && screen && typeof uit.hitTest === 'function') {
|
|
try {
|
|
var sp = cc.Vec2 ? new cc.Vec2(screen.sx, screen.sy) : { x: screen.sx, y: screen.sy };
|
|
if (uit.hitTest(cam, sp)) addHit(node, 'hitTest', area);
|
|
} catch (e2) { /* */ }
|
|
}
|
|
|
|
// B: 世界 AABB(含 Rect.contains)
|
|
for (var w = 0; w < worlds.length; w++) {
|
|
var W = worlds[w];
|
|
var inside = false;
|
|
try {
|
|
var raw = uit.getBoundingBoxToWorld && uit.getBoundingBoxToWorld();
|
|
if (raw && typeof raw.contains === 'function' && cc.Vec2) {
|
|
if (raw.contains(new cc.Vec2(W.x, W.y))) inside = true;
|
|
}
|
|
} catch (e3) { /* */ }
|
|
if (!inside) {
|
|
inside = (W.x >= box.x && W.x <= box.x + box.width && W.y >= box.y && W.y <= box.y + box.height);
|
|
}
|
|
if (inside) {
|
|
addHit(node, 'aabb:' + W.tag, area);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!hits.length) {
|
|
empty.method = uiCount ? 'miss' : 'no-uitransform';
|
|
empty.debug = {
|
|
worlds: worlds,
|
|
screen: screen ? { sx: screen.sx, sy: screen.sy, w: screen.rect.width, h: screen.rect.height, left: screen.rect.left, top: screen.rect.top } : null,
|
|
cbox: cbox,
|
|
nodes: stack.length,
|
|
uiCount: uiCount,
|
|
samples: samples,
|
|
cam: !!cam,
|
|
};
|
|
log('miss', JSON.stringify(empty.debug));
|
|
return empty;
|
|
}
|
|
|
|
// 视觉层级:上→下(更深、更后绘制的兄弟优先)
|
|
hits.sort(function (a, b) {
|
|
if (a.depth !== b.depth) return b.depth - a.depth;
|
|
if (a.sIndex !== b.sIndex) return b.sIndex - a.sIndex;
|
|
return a.area - b.area;
|
|
});
|
|
|
|
return {
|
|
uuid: hits[0].uuid,
|
|
method: hits[0].method,
|
|
hits: hits,
|
|
debug: {
|
|
best: hits[0].name,
|
|
hits: hits.length,
|
|
worlds: worlds,
|
|
cbox: cbox,
|
|
},
|
|
};
|
|
};
|
|
|
|
/** 兼容旧调用 */
|
|
WorkbenchAgent.prototype._hitTest = function (clientX, clientY) {
|
|
return this._hitTestDetailed(clientX, clientY).uuid || '';
|
|
};
|
|
|
|
WorkbenchAgent.prototype._ensureOverlay = function () {
|
|
var el = document.getElementById('pw-pick-overlay');
|
|
if (!el) {
|
|
el = document.createElement('div');
|
|
el.id = 'pw-pick-overlay';
|
|
el.className = 'pw-pick-overlay';
|
|
el.hidden = true;
|
|
(document.body || document.documentElement).appendChild(el);
|
|
}
|
|
this._overlay = el;
|
|
};
|
|
|
|
WorkbenchAgent.prototype._updateOverlay = function () {
|
|
this._ensureOverlay();
|
|
var node = this._findNode(this._selectedUuid);
|
|
var overlay = this._overlay;
|
|
if (!overlay) return;
|
|
if (!node) {
|
|
overlay.hidden = true;
|
|
return;
|
|
}
|
|
var box = this._nodeClientRect(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';
|
|
overlay.style.zIndex = '99999';
|
|
};
|
|
|
|
WorkbenchAgent.prototype._nodeClientRect = function (node) {
|
|
var cc = this.cc;
|
|
var canvas = this._getCanvasEl();
|
|
if (!cc || !canvas || !node || !node.isValid) return null;
|
|
var uit = node.getComponent && node.getComponent(cc.UITransform);
|
|
if (!uit || !uit.getBoundingBoxToWorld) return null;
|
|
var box = null;
|
|
try { box = uit.getBoundingBoxToWorld(); } catch (e) { return null; }
|
|
if (!box) return null;
|
|
|
|
var rect = canvas.getBoundingClientRect();
|
|
var cbox = this._canvasWorldBox();
|
|
var vw, vh, ox, oy;
|
|
if (cbox) {
|
|
vw = Math.max(1, cbox.width);
|
|
vh = Math.max(1, cbox.height);
|
|
ox = cbox.x;
|
|
oy = cbox.y;
|
|
} else {
|
|
var design = cc.view && cc.view.getDesignResolutionSize ? cc.view.getDesignResolutionSize() : null;
|
|
vw = Math.max(1, (design && design.width) || rect.width);
|
|
vh = Math.max(1, (design && design.height) || rect.height);
|
|
ox = 0;
|
|
oy = 0;
|
|
}
|
|
var 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 },
|
|
];
|
|
var xs = [];
|
|
var ys = [];
|
|
for (var i = 0; i < 4; i++) {
|
|
var u = (corners[i].x - ox) / vw;
|
|
var v = 1 - (corners[i].y - oy) / vh;
|
|
xs.push(rect.left + u * rect.width);
|
|
ys.push(rect.top + v * rect.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),
|
|
};
|
|
};
|
|
|
|
WorkbenchAgent.prototype._getOverlayRect = function () {
|
|
var overlay = this._overlay;
|
|
if (!overlay || overlay.hidden) return null;
|
|
return {
|
|
left: parseFloat(overlay.style.left) || 0,
|
|
top: parseFloat(overlay.style.top) || 0,
|
|
width: parseFloat(overlay.style.width) || 0,
|
|
height: parseFloat(overlay.style.height) || 0,
|
|
};
|
|
};
|
|
|
|
})(window);
|
|
|