(function (global) { 'use strict'; var STORAGE_KEY = 'preview-workbench-layout-v7'; var PANEL_MEM_KEY = 'preview-workbench-panel-mem-v1'; var COMPACT_W = 960; var COMPACT_SHORT = 720; var PANEL_TITLES = { hierarchy: '层级', inspector: '属性', assets: '资源', perf: '性能', console: '控制台', }; /** * Creator 风格递归停靠布局: * - 拖到标题栏/中心区 = 合并为同一区域页签 * - 拖到区域边沿 = 在该方向新建布局区域 * @constructor */ function WorkbenchLayout(root) { this.root = root; this.workspace = document.getElementById('pw-workspace'); this.gameHost = document.getElementById('pw-game-host'); this.dropOverlay = document.getElementById('pw-drop-overlay'); this.mode = 'desktop'; this.preset = 'analyze'; this.activeDrawer = ''; this._panelBodies = {}; this._panelMeta = {}; this._tree = null; this._leafEls = {}; this._nodeEls = {}; this._dragPanel = null; this._hoverTargetId = null; this._hoverEdge = null; this._hoverGroup = null; this._hoverGroupCols = null; this._splitDrag = null; this._leafSeq = 1; /** 用户是否已改过布局(拖拽/开关面板);未改动时引擎就绪可按横竖屏重套默认 */ this._dirty = false; /** @type {Object.} 面板开关记忆 */ this._panelMemory = {}; /** @type {Object.} */ this._floats = {}; this._floatLayer = null; this._floatDrag = null; /** @type {function(Object.):void|null} */ this.onVisibilityChange = null; /** @type {HTMLElement|null} 游戏占位格(iframe 永不插入此处) */ this._gamePlaceholder = null; /** 关闭工作台时隐藏调试面板,仍保留设备预览选项(默认开,由 Host 按偏好关闭) */ this._workbenchOff = false; this._treeBeforeOff = null; this._onResize = this._onWindowResize.bind(this); this._onDragOver = this._onDocDragOver.bind(this); this._onDrop = this._onDocDrop.bind(this); this._onDragEnd = this._onDocDragEnd.bind(this); this._onFloatMove = this._onFloatMove.bind(this); this._onFloatUp = this._onFloatUp.bind(this); } WorkbenchLayout.prototype.init = function () { this._load(); this._ensureFloatLayer(); if (!this._tree) this._tree = this._presetTree(this.preset); this._bindToolbar(); window.addEventListener('resize', this._onResize); document.addEventListener('dragover', this._onDragOver); document.addEventListener('drop', this._onDrop); document.addEventListener('dragend', this._onDragEnd); this.refreshMode(); this.rebuild(); this._restoreFloats(); }; WorkbenchLayout.prototype._notifyViewResize = function () { if (global.__PreviewWorkbench && global.__PreviewWorkbench.device) { global.__PreviewWorkbench.device.apply(false); } else if (global.__PW_Bridge) { global.__PW_Bridge.call('device.apply', { force: true }).catch(function () { /* */ }); } }; /** @returns {Object.} */ WorkbenchLayout.prototype.getVisiblePanels = function () { var map = {}; var ids = ['hierarchy', 'inspector', 'assets', 'perf', 'console']; for (var i = 0; i < ids.length; i++) { map[ids[i]] = this.isPanelVisible(ids[i]); } return map; }; /** @param {string} panelId */ WorkbenchLayout.prototype.isPanelVisible = function (panelId) { if (this._workbenchOff) return false; if (this.mode === 'compact') return this.activeDrawer === panelId; if (this._floats[panelId]) { var f = this._floats[panelId]; if (f && f.el && f.el.hidden) return false; return true; } return !!this._findTabsWith(this._tree, panelId); }; WorkbenchLayout.prototype._emitVisibility = function () { this._syncPanelButtons(); if (typeof this.onVisibilityChange === 'function') { this.onVisibilityChange(this.getVisiblePanels()); } }; /** 面板开关按钮高亮:打开 active,关闭灭(与 FPS 按钮一致) */ WorkbenchLayout.prototype._syncPanelButtons = function () { var map = this.getVisiblePanels(); var buttons = this.root.querySelectorAll('.pw-panel-btn'); for (var i = 0; i < buttons.length; i++) { var btn = buttons[i]; var pid = btn.getAttribute('data-panel'); if (!pid) continue; var on = !!map[pid]; btn.classList.toggle('active', on); btn.setAttribute('aria-pressed', on ? 'true' : 'false'); } }; WorkbenchLayout.prototype.registerPanel = function (id, el, title) { this._panelBodies[id] = el; this._panelMeta[id] = { title: title || PANEL_TITLES[id] || id }; if (!el.classList.contains('pw-panel-root')) { el.classList.add('pw-panel-root'); } // 去掉面板自带标题(区域页签负责标题) var head = null; for (var i = 0; i < el.children.length; i++) { if (el.children[i].classList.contains('pw-panel-head')) { head = el.children[i]; break; } } if (head) { // 保留工具条到面板顶部工具行 var tools = head.querySelector('.pw-panel-tools'); if (tools && tools.childNodes.length) { var bar = document.createElement('div'); bar.className = 'pw-panel-toolbar'; while (tools.firstChild) bar.appendChild(tools.firstChild); head.parentNode.insertBefore(bar, head); } head.parentNode.removeChild(head); } }; WorkbenchLayout.prototype.refreshMode = function () { var w = window.innerWidth; var h = window.innerHeight; var short = Math.min(w, h); var compact = w < COMPACT_W || short < COMPACT_SHORT; this.mode = compact ? 'compact' : 'desktop'; this.root.classList.toggle('pw-mode-compact', compact); this.root.classList.toggle('pw-mode-desktop', !compact); this.root.classList.toggle('pw-compact-portrait', compact && h >= w); this.root.classList.toggle('pw-compact-landscape', compact && h < w); if (!compact) this.closeDrawer(); }; WorkbenchLayout.prototype.applyPreset = function (name, persist) { this.preset = name || 'analyze'; this._dirty = false; this._tree = this._presetTree(this.preset); var sel = document.getElementById('pw-layout-preset'); if (sel) sel.value = this.preset; this.rebuild(); if (persist !== false) this._save(); }; WorkbenchLayout.prototype.resetLayout = function () { try { localStorage.removeItem(STORAGE_KEY); } catch (e) { /* */ } this._dirty = false; this.applyPreset(this.preset || 'analyze', true); }; /** * 引擎就绪后:未手动改布局时,按设计分辨率横竖屏重套「分析」默认。 */ WorkbenchLayout.prototype.onEngineReady = function () { if (this._dirty) return; if (this.preset !== 'analyze' && this.preset !== 'portrait' && this.preset !== 'landscape') return; if (this.preset === 'analyze') { var next = this._presetTree('analyze'); // 工作台关闭时只更新备份,避免与「仅游戏」树冲突导致布局抖动 if (this._workbenchOff) { this._treeBeforeOff = next; return; } this._tree = next; this.rebuild(); this._save(); } }; /** 设计分辨率是否竖屏(高 ≥ 宽) */ WorkbenchLayout.prototype._ensureId = function (node) { if (!node) return; if (node.type === 'game') { node.id = 'leaf_game'; return; } if (!node.id) node.id = (node.type === 'split' ? 'split_' : 'leaf_') + (this._leafSeq++); }; WorkbenchLayout.prototype.rebuild = function () { if (!this.workspace) return; // 只卸面板 DOM;游戏 iframe 宿主永不搬移(搬移会导致 iframe 整页重载) for (var id in this._panelBodies) { if (!Object.prototype.hasOwnProperty.call(this._panelBodies, id)) continue; if (this._floats[id]) continue; var p = this._panelBodies[id]; if (p && p.parentNode) p.parentNode.removeChild(p); } this._ensureGameHostParked(); this._gamePlaceholder = null; this._leafEls = {}; this._nodeEls = {}; while (this.workspace.firstChild) this.workspace.removeChild(this.workspace.firstChild); if (this.mode === 'compact') { if (this.gameHost) this.gameHost.classList.add('pw-game-compact'); this._syncGameHostGeom(); this._notifyViewResize(); this._emitVisibility(); return; } if (this.gameHost) this.gameHost.classList.remove('pw-game-compact'); var el = this._renderNode(this._tree); if (el) this.workspace.appendChild(el); var floatIds = []; for (var fid in this._floats) { if (Object.prototype.hasOwnProperty.call(this._floats, fid)) floatIds.push(fid); } for (var fi = 0; fi < floatIds.length; fi++) { var pid = floatIds[fi]; if (this._findTabsWith(this._tree, pid)) { this._closeFloat(pid, false); continue; } var f = this._floats[pid]; var body = this._panelBodies[pid]; var fb = f && f.el ? f.el.querySelector('.pw-float-body') : null; if (fb && body && body.parentNode !== fb) fb.appendChild(body); } var self = this; this._syncGameHostGeom(); if (typeof requestAnimationFrame === 'function') { requestAnimationFrame(function () { self._syncGameHostGeom(); }); } this._notifyViewResize(); this._emitVisibility(); }; /** 游戏宿主固定挂在 root 下,禁止插入停靠树(避免 iframe reload) */ WorkbenchLayout.prototype._ensureGameHostParked = function () { if (!this.gameHost || !this.root) return; if (this.gameHost.parentNode !== this.root) { this.root.appendChild(this.gameHost); } this.gameHost.classList.add('pw-game-overlay'); }; /** 将 overlay 对齐到占位格(或紧凑/关闭模式下的工作区) */ WorkbenchLayout.prototype._syncGameHostGeom = function () { if (!this.gameHost || !this.root) return; this._ensureGameHostParked(); var rootRect = this.root.getBoundingClientRect(); var target = null; if (this._workbenchOff) { target = this.workspace || this.root; } else if (this.mode === 'compact') { target = this.workspace; } else { target = this._gamePlaceholder; } if (!target) { this.gameHost.hidden = true; return; } var r = target.getBoundingClientRect(); if (r.width < 2 || r.height < 2) { this.gameHost.hidden = true; return; } this.gameHost.hidden = false; this.gameHost.style.pointerEvents = 'auto'; this.gameHost.style.left = Math.max(0, r.left - rootRect.left) + 'px'; this.gameHost.style.top = Math.max(0, r.top - rootRect.top) + 'px'; this.gameHost.style.width = Math.max(1, r.width) + 'px'; this.gameHost.style.height = Math.max(1, r.height) + 'px'; var iframe = document.getElementById('pw-game-iframe'); if (iframe) iframe.style.pointerEvents = 'auto'; }; WorkbenchLayout.prototype._bindToolbar = function () { var self = this; var preset = document.getElementById('pw-layout-preset'); if (preset) { preset.value = this.preset; preset.addEventListener('change', function () { self.applyPreset(preset.value, true); }); } var reset = document.getElementById('pw-layout-reset'); if (reset) reset.addEventListener('click', function () { self.resetLayout(); }); var buttons = this.root.querySelectorAll('.pw-panel-btn'); for (var i = 0; i < buttons.length; i++) { buttons[i].addEventListener('click', function (ev) { self.togglePanel(ev.currentTarget.getAttribute('data-panel')); }); } var close = document.getElementById('pw-drawer-close'); if (close) close.addEventListener('click', function () { self.closeDrawer(); }); }; WorkbenchLayout.prototype._onWindowResize = function () { this.refreshMode(); if (this.mode === 'desktop' && !this.workspace.firstChild) this.rebuild(); this._syncGameHostGeom(); this._notifyViewResize(); }; /** * 一键开关工作台:关闭时备份布局并只留游戏与设备选项,开启时还原。 * @param {boolean} on */ WorkbenchLayout.prototype.setWorkbenchEnabled = function (on) { on = !!on; if (on === !this._workbenchOff) return; this._workbenchOff = !on; this.root.classList.toggle('pw-workbench-off', this._workbenchOff); if (this._workbenchOff) { try { this._treeBeforeOff = JSON.parse(JSON.stringify(this._tree)); } catch (e) { this._treeBeforeOff = this._tree; } // 隐藏悬浮窗但不销毁,便于恢复 for (var fid in this._floats) { if (!Object.prototype.hasOwnProperty.call(this._floats, fid)) continue; var f = this._floats[fid]; if (f && f.el) f.el.hidden = true; } this.closeDrawer(); this._tree = this._gameNode(); this.rebuild(); } else { for (var id in this._floats) { if (!Object.prototype.hasOwnProperty.call(this._floats, id)) continue; var fl = this._floats[id]; if (fl && fl.el) fl.el.hidden = false; } if (this._treeBeforeOff) { this._tree = this._treeBeforeOff; this._treeBeforeOff = null; this._reseedLeafIds(this._tree); } else { this._tree = this._presetTree(this.preset || 'analyze'); } this.rebuild(); } }; /** @returns {boolean} */ WorkbenchLayout.prototype.isWorkbenchEnabled = function () { return !this._workbenchOff; }; WorkbenchLayout.prototype._save = function () { try { localStorage.setItem(STORAGE_KEY, JSON.stringify({ preset: this.preset, tree: this._tree, dirty: !!this._dirty, })); this._savePanelMemory(); } catch (e) { console.warn('[调试工作台] 布局保存失败', e); } }; WorkbenchLayout.prototype._load = function () { this._loadPanelMemory(); try { var raw = localStorage.getItem(STORAGE_KEY); if (!raw) return; var data = JSON.parse(raw); if (data.preset) this.preset = data.preset; if (data.tree) { this._tree = data.tree; this._reseedLeafIds(this._tree); this._dirty = !!data.dirty; } } catch (e) { console.warn('[调试工作台] 布局读取失败', e); this._tree = null; } }; WorkbenchLayout.prototype._reseedLeafIds = function (node) { if (!node) return; if (node.type === 'tabs' || node.type === 'game') { if (!node.id) node.id = node.type === 'game' ? 'leaf_game' : ('leaf_' + (this._leafSeq++)); if (node.type === 'game') node.id = 'leaf_game'; var n = parseInt(String(node.id).replace(/\D/g, ''), 10); if (!isNaN(n) && n >= this._leafSeq) this._leafSeq = n + 1; } if (node.type === 'split') { for (var i = 0; i < node.children.length; i++) this._reseedLeafIds(node.children[i]); } }; global.PWLayout = WorkbenchLayout; })(window);