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
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, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
}
|
|
function escapeAttr(s) {
|
|
return escapeHtml(s).replace(/"/g, '"');
|
|
}
|
|
|
|
global.PWHierarchyPanel = HierarchyPanel;
|
|
})(window);
|
|
|