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.
 
 
 
 

271 lines
10 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,
};
/**
* 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 =
'<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.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 = '<div class="pw-muted">引擎未就绪</div>';
return;
}
var scene = cc.director.getScene();
if (!scene) {
this.treeEl.innerHTML = '<div class="pw-muted">无场景</div>';
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('') || '<div class="pw-muted">空</div>';
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
? '<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-path="' + escapeAttr(this._pathOf(node)) + '" 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, 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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function escapeAttr(s) {
return escapeHtml(s).replace(/"/g, '&quot;');
}
global.PWHierarchyPanel = HierarchyPanel;
})(window);