(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 = '
' + '层级' + '
' + '' + '' + '' + '
'; 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 = '
等待场景…
'; return; } var scrollTop = this.treeEl.scrollTop; var html = []; this._renderNode(this._tree.root, 0, html, true, true); this.treeEl.innerHTML = html.join('') || '
空
'; 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 ? '' : ''; var ts = info.hasScript ? 'TS' : ''; out.push( '
' + twist + '' + '' + escapeHtml(name) + '' + ts + '
' ); 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, '&').replace(//g, '>'); } function escapeAttr(s) { return escapeHtml(s).replace(/"/g, '"'); } global.PWHierarchyPanel = HierarchyPanel; })(window);