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.
 
 
 
 

263 lines
11 KiB

(function (global) {
'use strict';
var BUILTIN = {
UITransform: 1, Canvas: 1, Widget: 1, Camera: 1, Sprite: 1, Label: 1,
RichText: 1, Button: 1, Layout: 1, ScrollView: 1, Mask: 1, Graphics: 1,
Animation: 1, AudioSource: 1, UIOpacity: 1, BlockInputEvents: 1,
SafeArea: 1, EditBox: 1, Toggle: 1, PageView: 1, Slider: 1,
ProgressBar: 1, Skeleton: 1, ParticleSystem2D: 1, MeshRenderer: 1,
Light: 1, DirectionalLight: 1, SkinnedMeshRenderer: 1, RigidBody2D: 1,
BoxCollider2D: 1, CircleCollider2D: 1, PolygonCollider2D: 1,
TiledMap: 1, TiledLayer: 1, VideoPlayer: 1, WebView: 1,
SubContextView: 1, Mobiledirector: 1, RenderRoot2D: 1,
};
/**
* 层级面板(壳侧):仅渲染 Agent 序列化树,不碰 cc。
* @constructor
*/
function HierarchyPanel() {
this.el = null;
this.treeEl = null;
this._bridge = null;
this._filter = '';
this._expanded = {};
this._defaultInited = false;
this._tree = null;
this._selectedUuid = '';
}
HierarchyPanel.prototype.setBridge = function (bridge) {
this._bridge = bridge;
};
HierarchyPanel.prototype.setSelectedUuid = function (uuid) {
this._selectedUuid = uuid || '';
var ok = this._expandToUuid(this._selectedUuid);
if (!ok && this._bridge && this._selectedUuid) {
// 树里还没有该节点时拉一份最新层级
var self = this;
this._bridge.call('hier.getTree').then(function (t) {
self.applyTree(t);
}).catch(function () {
self._render();
});
return;
}
this._render();
this._scrollToSelected();
};
HierarchyPanel.prototype.applyTree = function (data) {
this._tree = data;
if (data && data.selected) this._selectedUuid = data.selected;
if (data && data.root) {
var key = data.root.uuid || 'scene';
if (!this._defaultInited) {
this._expanded[key] = true;
this._defaultInited = true;
}
if (this._selectedUuid) this._expandToUuid(this._selectedUuid);
}
this._render();
if (this._selectedUuid) this._scrollToSelected();
};
/** 展开选中节点的全部祖先,使其在树中可见 @returns {boolean} 是否找到 */
HierarchyPanel.prototype._expandToUuid = function (uuid) {
if (!uuid || !this._tree || !this._tree.root) return false;
var path = [];
var found = findPath(this._tree.root, uuid, path);
if (!found) return false;
for (var i = 0; i < path.length - 1; i++) {
var key = path[i].uuid || path[i].name || '';
if (key) this._expanded[key] = true;
}
// 根节点也展开
var rootKey = this._tree.root.uuid || this._tree.root.name || 'scene';
this._expanded[rootKey] = true;
return true;
};
/** 把选中项滚入可视区 */
HierarchyPanel.prototype._scrollToSelected = function () {
if (!this.treeEl || !this._selectedUuid) return;
var el = this.treeEl.querySelector('.pw-tree-item.selected');
if (el && typeof el.scrollIntoView === 'function') {
el.scrollIntoView({ block: 'nearest' });
}
};
HierarchyPanel.prototype.mount = function () {
var self = this;
var root = document.createElement('div');
root.className = 'pw-panel-inner';
root.style.height = '100%';
root.style.display = 'flex';
root.style.flexDirection = 'column';
root.innerHTML =
'<div class="pw-panel-head">' +
'<span class="pw-panel-title">层级</span>' +
'<div class="pw-panel-tools">' +
'<input id="pw-hier-filter" class="pw-input" placeholder="搜索节点" style="width:90px" />' +
'<button type="button" id="pw-hier-collapse" class="pw-btn" title="全部折叠">折叠</button>' +
'<button type="button" id="pw-hier-refresh" class="pw-btn">刷新</button>' +
'</div></div><div class="pw-panel-body pw-hier-tree" id="pw-hier-tree"></div>';
this.el = root;
this.treeEl = root.querySelector('#pw-hier-tree');
root.querySelector('#pw-hier-filter').addEventListener('input', function (ev) {
self._filter = (ev.target.value || '').toLowerCase();
self._render();
});
root.querySelector('#pw-hier-refresh').addEventListener('click', function () {
if (!self._bridge) return;
self._bridge.call('hier.getTree').then(function (t) { self.applyTree(t); });
});
root.querySelector('#pw-hier-collapse').addEventListener('click', function () {
self._expanded = {};
self._defaultInited = false;
self._render();
});
return root;
};
HierarchyPanel.prototype._render = function () {
if (!this.treeEl) return;
if (!this._tree || !this._tree.root) {
this.treeEl.innerHTML = '<div class="pw-muted">等待场景…</div>';
return;
}
var scrollTop = this.treeEl.scrollTop;
var html = [];
this._renderNode(this._tree.root, 0, html, true, true);
this.treeEl.innerHTML = html.join('') || '<div class="pw-muted">空</div>';
this.treeEl.scrollTop = scrollTop;
this._bindTreeEvents();
};
HierarchyPanel.prototype._bindTreeEvents = function () {
var self = this;
var twists = this.treeEl.querySelectorAll('.pw-tree-twist:not(.empty)');
for (var i = 0; i < twists.length; i++) {
twists[i].addEventListener('click', function (ev) {
ev.stopPropagation();
var key = ev.currentTarget.getAttribute('data-key');
self._expanded[key] = !self._expanded[key];
self._render();
});
}
var items = this.treeEl.querySelectorAll('.pw-tree-item');
for (var j = 0; j < items.length; j++) {
items[j].addEventListener('click', function (ev) {
if (ev.target.classList.contains('pw-tree-twist')) return;
var uuid = ev.currentTarget.getAttribute('data-uuid');
self._selectedUuid = uuid || '';
if (self._bridge) self._bridge.call('hier.select', { uuid: uuid });
self._render();
});
items[j].addEventListener('dblclick', function (ev) {
ev.preventDefault();
var key = ev.currentTarget.getAttribute('data-key');
if (!key) return;
self._expanded[key] = !self._expanded[key];
self._render();
});
}
};
HierarchyPanel.prototype._renderNode = function (node, depth, out, forceShow, isRoot) {
if (!node) return;
var name = node.name || '(未命名)';
var key = node.uuid || name;
var children = node.children || [];
var hasChildren = children.length > 0;
var filterHit = !this._filter || name.toLowerCase().indexOf(this._filter) >= 0;
var childHit = this._filter ? this._childMatch(node) : false;
if (this._filter && !filterHit && !childHit && !forceShow) return;
var expanded = !!this._expanded[key];
if (this._filter && childHit) expanded = true;
var info = this._classify(node, !!isRoot);
var cls = 'pw-tree-item';
if (this._selectedUuid && this._selectedUuid === node.uuid) cls += ' selected';
if (node.active === false) cls += ' inactive';
var pad = 4 + depth * 14;
var twist = hasChildren
? '<button type="button" class="pw-tree-twist" data-key="' + escapeAttr(key) + '">' + (expanded ? '▼' : '▶') + '</button>'
: '<button type="button" class="pw-tree-twist empty">▶</button>';
var ts = info.hasScript ? '<span class="pw-tree-ts">TS</span>' : '';
out.push(
'<div class="' + cls + '" data-uuid="' + escapeAttr(node.uuid || '') + '" data-key="' + escapeAttr(key) +
'" style="padding-left:' + pad + 'px" title="' + escapeAttr(info.comps.join(', ')) + '">' +
twist +
'<span class="pw-tree-icon ' + info.icon + '"></span>' +
'<span class="pw-tree-name kind-' + info.kind + '">' + escapeHtml(name) + '</span>' +
ts +
'</div>'
);
if (!hasChildren || !expanded) return;
for (var i = 0; i < children.length; i++) {
this._renderNode(children[i], depth + 1, out, false, false);
}
};
HierarchyPanel.prototype._classify = function (node, isRoot) {
var names = node.comps || [];
var hasScript = false;
for (var i = 0; i < names.length; i++) {
var n = names[i];
if (n && !BUILTIN[n] && n.indexOf('cc.') !== 0) hasScript = true;
}
var icon = 'node';
var kind = 'node';
if (isRoot) { icon = 'scene'; kind = 'scene'; }
else if (hasName(names, 'Camera')) { icon = 'camera'; kind = 'camera'; }
else if (hasName(names, 'Canvas')) { icon = 'canvas'; kind = 'canvas'; }
else if (hasName(names, 'Label') || hasName(names, 'RichText')) { icon = 'label'; kind = 'label'; }
else if (hasName(names, 'Sprite')) { icon = 'sprite'; kind = 'sprite'; }
else if (hasScript) { icon = 'script'; kind = 'script'; }
else if (hasName(names, 'UITransform')) { icon = 'ui'; kind = 'ui'; }
return { icon: icon, kind: kind, hasScript: hasScript, comps: names };
};
HierarchyPanel.prototype._childMatch = function (node) {
var children = node.children;
if (!children) return false;
for (var i = 0; i < children.length; i++) {
var n = children[i];
if ((n.name || '').toLowerCase().indexOf(this._filter) >= 0) return true;
if (this._childMatch(n)) return true;
}
return false;
};
/** DFS 找 uuid 路径,path 收集祖先+自身 */
function findPath(node, uuid, path) {
if (!node) return false;
path.push(node);
if (node.uuid === uuid) return true;
var children = node.children || [];
for (var i = 0; i < children.length; i++) {
if (findPath(children[i], uuid, path)) return true;
}
path.pop();
return false;
}
function hasName(list, name) {
for (var i = 0; i < list.length; i++) if (list[i] === name) return true;
return false;
}
function escapeHtml(s) {
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function escapeAttr(s) {
return escapeHtml(s).replace(/"/g, '&quot;');
}
global.PWHierarchyPanel = HierarchyPanel;
})(window);