(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, }; /** * Runtime hierarchy — editor-like expand/collapse, icons, TS badge. * @constructor */ function HierarchyPanel() { this.el = null; this.treeEl = null; this._cc = null; this._pick = null; this._filter = ''; this._timer = 0; this._expanded = {}; this._sceneKey = ''; this._defaultInited = false; } /** @param {*} pick */ HierarchyPanel.prototype.setPick = function (pick) { this._pick = pick; }; /** @param {*} cc */ HierarchyPanel.prototype.bindEngine = function (cc) { this._cc = cc; var self = this; if (this._timer) clearInterval(this._timer); this._timer = setInterval(function () { self.refresh(); }, 1500); this.refresh(); }; 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.refresh(); }); root.querySelector('#pw-hier-refresh').addEventListener('click', function () { self.refresh(); }); root.querySelector('#pw-hier-collapse').addEventListener('click', function () { self._expanded = {}; self._defaultInited = false; self.refresh(); }); return root; }; HierarchyPanel.prototype.refresh = function () { if (!this.treeEl) return; var cc = this._cc; if (!cc || !cc.director) { this.treeEl.innerHTML = '
引擎未就绪
'; return; } var scene = cc.director.getScene(); if (!scene) { this.treeEl.innerHTML = '
无场景
'; return; } var sceneKey = scene.uuid || scene._id || scene.name || 'scene'; if (sceneKey !== this._sceneKey) { this._sceneKey = sceneKey; this._expanded = {}; this._defaultInited = false; } if (!this._defaultInited) { // 默认仅展开场景根,子节点全部折叠 this._expanded[this._nodeKey(scene)] = true; this._defaultInited = true; } var scrollTop = this.treeEl.scrollTop; var selected = this._pick ? this._pick.getSelected() : null; var html = []; this._renderNode(scene, 0, selected, html, true); this.treeEl.innerHTML = html.join('') || '
空
'; this.treeEl.scrollTop = scrollTop; this._bindTreeEvents(scene); }; HierarchyPanel.prototype._bindTreeEvents = function (scene) { 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.refresh(); }); } 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 path = ev.currentTarget.getAttribute('data-path'); var node = self._findByPath(scene, path); if (self._pick) self._pick.select(node); self.refresh(); }); 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.refresh(); }); } }; HierarchyPanel.prototype._renderNode = function (node, depth, selected, out, forceShow) { if (!node) return; var name = node.name || '(未命名)'; var key = this._nodeKey(node); 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); var cls = 'pw-tree-item'; if (selected && selected === node) 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, selected, out, false); } }; HierarchyPanel.prototype._classify = function (node) { var comps = []; var names = []; var hasScript = false; if (node.components) { for (var i = 0; i < node.components.length; i++) { var comp = node.components[i]; if (!comp || !comp.constructor) continue; var n = comp.constructor.name || ''; comps.push(n); names.push(n); if (n && !BUILTIN[n] && n.indexOf('cc.') !== 0) hasScript = true; } } var isScene = !!(node.constructor && (node.constructor.name === 'Scene' || node.isScene)); if (!node.parent) isScene = true; var icon = 'node'; var kind = 'node'; if (isScene) { 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: comps }; }; HierarchyPanel.prototype._nodeKey = function (node) { return String(node.uuid || node._id || this._pathOf(node)); }; 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; }; HierarchyPanel.prototype._pathOf = function (node) { var parts = []; var cur = node; var guard = 0; while (cur && guard++ < 256) { parts.unshift((cur.name || '') + '#' + (cur.uuid || cur._id || parts.length)); cur = cur.parent; } return parts.join('/'); }; HierarchyPanel.prototype._findByPath = function (root, path) { if (!root || !path) return null; if (this._pathOf(root) === path) return root; var children = root.children; if (!children) return null; for (var i = 0; i < children.length; i++) { var found = this._findByPath(children[i], path); if (found) return found; } return null; }; 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);