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.

782 lines
32 KiB

(function (global) {
'use strict';
/**
* 工作台壳:布局 + 面板 + Bridge;游戏在 iframe(?pw=game)内由 Agent 驱动。
*/
function PreviewWorkbenchHost() {
this.layout = null;
this.bridge = null;
this.panels = {};
this._ready = false;
this._paused = false;
this._fpsOn = false;
this._pure = false;
this._workbenchOn = true;
this._subs = {};
this._iframe = null;
this._crossOrigin = false;
3 days ago
this._statsTimer = 0;
}
PreviewWorkbenchHost.prototype.boot = function () {
var root = document.getElementById('pw-root');
if (!root) {
console.warn('[调试工作台] 缺少 #pw-root');
return;
}
// 壳页不再加载引擎;仅挂游戏 iframe
this._mountGameIframe();
this.bridge = new global.PWBridge();
this.bridge.attach(this._iframe, { crossOrigin: this._crossOrigin });
global.__PW_Bridge = this.bridge;
this.layout = new global.PWLayout(root);
this.panels.console = new global.PWConsolePanel();
this.panels.hierarchy = new global.PWHierarchyPanel();
this.panels.inspector = new global.PWInspectorPanel();
this.panels.assets = new global.PWAssetsPanel();
this.panels.perf = new global.PWPerfPanel();
this.panels.console.setBridge(this.bridge);
this.panels.hierarchy.setBridge(this.bridge);
this.panels.inspector.setBridge(this.bridge);
this.panels.assets.setBridge(this.bridge);
this.panels.perf.setBridge(this.bridge);
this.layout.registerPanel('console', this.panels.console.mount(), '控制台');
this.layout.registerPanel('hierarchy', this.panels.hierarchy.mount(), '层级');
this.layout.registerPanel('inspector', this.panels.inspector.mount(), '属性');
this.layout.registerPanel('assets', this.panels.assets.mount(), '资源');
this.layout.registerPanel('perf', this.panels.perf.mount(), '性能');
this.layout.init();
var self = this;
this.layout.onVisibilityChange = function (visibleMap) {
self._syncSubscriptions(visibleMap);
};
this._bindToolbar();
this._bindBridgeEvents();
this._setStatus('引擎等待中…', false);
this.bridge.on('agent.ready', function (data) {
self._ready = !!(data && data.ok);
self._setStatus(self._ready ? '引擎已就绪' : '引擎未就绪', self._ready);
if (self._ready) {
self._fillDeviceSelect();
self._syncSubscriptions(self.layout.getVisiblePanels());
if (self.layout) self.layout.onEngineReady();
3 days ago
self._startLiveStats();
3 days ago
// 刷新后恢复检视模式(新 Agent 默认 game)
if (self._inspectOn && self.bridge) {
self._setInspectCapture(true);
self.bridge.call('pick.setMode', { mode: 'inspect' }).catch(function () { /* */ });
} else {
self._setInspectCapture(false);
}
} else if (self._crossOrigin && !self._fallbackTried) {
self._fallbackTried = true;
global.PWBridge.markCrossOriginFailed();
self._remountSameOrigin();
}
});
// 跨域超时降级
setTimeout(function () {
if (!self._ready && self._crossOrigin && !self._fallbackTried) {
self._fallbackTried = true;
global.PWBridge.markCrossOriginFailed();
self._setStatus('跨域失败,改用同域…', false);
self._remountSameOrigin();
}
}, 5000);
global.__PreviewWorkbench = {
mark: function (label) {
if (self.bridge) self.bridge.call('perf.mark', { text: label });
},
bridge: self.bridge,
isReady: function () { return self._ready; },
isPure: function () { return self._pure; },
isWorkbenchOn: function () { return self._workbenchOn; },
};
};
PreviewWorkbenchHost.prototype._mountGameIframe = function () {
// 防御:壳脚本若被嵌进 iframe,禁止再套一层
if (global.parent && global.parent !== global) {
console.warn('[调试工作台] 壳页处于 iframe 内,跳过再次嵌套');
return;
}
var host = document.getElementById('pw-game-host');
if (!host) return;
while (host.firstChild) host.removeChild(host.firstChild);
host.hidden = false;
host.classList.add('pw-game-iframe-host');
host.style.pointerEvents = 'auto';
// 默认同域,避免跨域预览资源/输入异常
var info = global.PWBridge.buildGameUrl(true);
this._crossOrigin = false;
var iframe = document.createElement('iframe');
iframe.id = 'pw-game-iframe';
iframe.className = 'pw-game-iframe';
iframe.setAttribute('allow', 'autoplay; fullscreen');
iframe.style.pointerEvents = 'auto';
iframe.src = info.url;
host.appendChild(iframe);
this._iframe = iframe;
console.info('[调试工作台] 已挂载游戏 iframe', info.url);
3 days ago
// 检视捕获层必须在 iframe 之上(挂载时清空了宿主,这里补回)
var cap = document.createElement('div');
cap.id = 'pw-pick-capture';
cap.className = 'pw-pick-capture pw-shell-only';
cap.hidden = true;
cap.title = '检视:点击选取节点';
host.appendChild(cap);
// 选中 Gizmo(拖动改坐标 + X/Y 轴箭头)
var gizmo = document.createElement('div');
gizmo.id = 'pw-pick-gizmo';
gizmo.className = 'pw-pick-gizmo pw-shell-only';
gizmo.hidden = true;
gizmo.innerHTML =
'<div class="pw-gizmo-box" data-drag="xy" title="拖动移动节点"></div>' +
'<div class="pw-gizmo-axis pw-gizmo-x" data-drag="x" title="沿 X 轴拖动"><i></i><b></b></div>' +
'<div class="pw-gizmo-axis pw-gizmo-y" data-drag="y" title="沿 Y 轴拖动"><i></i><b></b></div>';
host.appendChild(gizmo);
this._bindGizmoDrag(gizmo);
if (this.layout) this.layout.gameHost = host;
};
4 days ago
/** 刷新并重启游戏(重载 iframe,保留壳布局) */
PreviewWorkbenchHost.prototype._reloadGame = function () {
var self = this;
this._ready = false;
this._paused = false;
this._setStatus('正在刷新游戏…', false);
var pauseBtn = document.getElementById('pw-game-pause');
if (pauseBtn) {
pauseBtn.textContent = '暂停';
pauseBtn.classList.remove('active');
}
for (var k in this._subs) {
if (Object.prototype.hasOwnProperty.call(this._subs, k)) this._subs[k] = false;
}
if (this.bridge) {
try { this.bridge.detach(); } catch (e) { /* */ }
}
this._mountGameIframe();
if (!this._iframe) {
this._setStatus('刷新失败:无游戏宿主', false);
return;
}
this.bridge = new global.PWBridge();
this.bridge.attach(this._iframe, { crossOrigin: this._crossOrigin });
global.__PW_Bridge = this.bridge;
this._wirePanelsBridge();
this._bindBridgeEvents();
this.bridge.on('agent.ready', function (data) {
self._ready = !!(data && data.ok);
self._setStatus(self._ready ? '引擎已就绪' : '引擎未就绪', self._ready);
if (self._ready) {
self._fillDeviceSelect();
if (self._workbenchOn) self._syncSubscriptions(self.layout.getVisiblePanels());
else self.bridge.hibernate().catch(function () { /* */ });
if (self.layout) self.layout.onEngineReady();
}
});
console.info('[调试工作台] 已刷新游戏 iframe');
};
PreviewWorkbenchHost.prototype._remountSameOrigin = function () {
if (this.bridge) this.bridge.detach();
var info = global.PWBridge.buildGameUrl(true);
this._crossOrigin = false;
var host = document.getElementById('pw-game-host');
if (!host) return;
while (host.firstChild) host.removeChild(host.firstChild);
var iframe = document.createElement('iframe');
iframe.id = 'pw-game-iframe';
iframe.className = 'pw-game-iframe';
iframe.style.pointerEvents = 'auto';
iframe.src = info.url;
host.appendChild(iframe);
this._iframe = iframe;
this.bridge = new global.PWBridge();
this.bridge.attach(iframe, { crossOrigin: false });
global.__PW_Bridge = this.bridge;
this._wirePanelsBridge();
this._bindBridgeEvents();
var self = this;
this.bridge.on('agent.ready', function (data) {
self._ready = !!(data && data.ok);
self._setStatus(self._ready ? '引擎已就绪(同域)' : '引擎未就绪', self._ready);
if (self._ready) {
self._fillDeviceSelect();
self._syncSubscriptions(self.layout.getVisiblePanels());
}
});
};
PreviewWorkbenchHost.prototype._wirePanelsBridge = function () {
var b = this.bridge;
if (this.panels.console) this.panels.console.setBridge(b);
if (this.panels.hierarchy) this.panels.hierarchy.setBridge(b);
if (this.panels.inspector) this.panels.inspector.setBridge(b);
if (this.panels.assets) this.panels.assets.setBridge(b);
if (this.panels.perf) this.panels.perf.setBridge(b);
};
PreviewWorkbenchHost.prototype._bindBridgeEvents = function () {
var self = this;
var b = this.bridge;
if (!b) return;
b.on('hier.diff', function (data) {
if (self.panels.hierarchy) self.panels.hierarchy.applyTree(data);
});
b.on('pick.selected', function (data) {
if (self.panels.hierarchy) self.panels.hierarchy.setSelectedUuid(data && data.uuid);
if (self.panels.inspector) self.panels.inspector.loadUuid(data && data.uuid);
3 days ago
if (!data || !data.uuid) self._setPickHighlight(null);
if (self.bridge) {
self.bridge.call('pick.getRect').then(function (rect) {
self._setPickHighlight(rect);
}).catch(function () { /* */ });
}
});
b.on('pick.rect', function (rect) {
self._setPickHighlight(rect);
});
b.on('perf.sample', function (data) {
if (self.panels.perf) self.panels.perf.applySample(data);
});
b.on('assets.update', function (data) {
if (self.panels.assets) self.panels.assets.applyData(data);
});
b.on('console.batch', function (data) {
if (self.panels.console) self.panels.console.applyBatch(data && data.items);
});
};
/**
* 按可见面板订阅 Agent 通道。
* @param {Object.<string,boolean>} visibleMap
*/
PreviewWorkbenchHost.prototype._syncSubscriptions = function (visibleMap) {
if (!this.bridge || this._pure) return;
var map = visibleMap || {};
var channelOf = {
perf: 'perf',
hierarchy: 'hierarchy',
inspector: 'inspector',
assets: 'assets',
console: 'console',
};
var want = {};
for (var pid in channelOf) {
if (!Object.prototype.hasOwnProperty.call(channelOf, pid)) continue;
if (map[pid]) want[channelOf[pid]] = true;
}
if (map.perf) want.markers = true;
// 检视模式由工具栏单独订阅 pick
if (this._inspectOn) want.pick = true;
var all = ['perf', 'hierarchy', 'inspector', 'assets', 'console', 'markers', 'pick'];
for (var i = 0; i < all.length; i++) {
var ch = all[i];
var on = !!want[ch];
var was = !!this._subs[ch];
if (on && !was) {
this._subs[ch] = true;
this.bridge.subscribe(ch);
} else if (!on && was) {
this._subs[ch] = false;
this.bridge.unsubscribe(ch);
}
}
var self = this;
if (map.hierarchy) {
this.bridge.call('hier.getTree').then(function (tree) {
if (self.panels.hierarchy) self.panels.hierarchy.applyTree(tree);
}).catch(function () { /* */ });
}
if (map.assets) {
this.bridge.call('assets.list').then(function (data) {
if (self.panels.assets) self.panels.assets.applyData(data);
}).catch(function () { /* */ });
}
};
PreviewWorkbenchHost.prototype._fillDeviceSelect = function () {
var self = this;
var sel = document.getElementById('pw-device-select');
if (!sel || !this.bridge) return;
this.bridge.call('device.list').then(function (list) {
if (!list) return;
var html = '';
for (var i = 0; i < list.length; i++) {
var d = list[i];
html += '<option value="' + d.id + '">' + d.name + (d.w ? ' (' + d.w + '×' + d.h + ')' : '') + '</option>';
}
sel.innerHTML = html;
return self.bridge.call('device.getState');
}).then(function (st) {
if (st && sel) sel.value = st.id;
var rotateBtn = document.getElementById('pw-device-rotate');
if (rotateBtn && st) {
rotateBtn.classList.toggle('active', !!st.rotated);
3 days ago
rotateBtn.title = st.rotated ? '竖屏(当前已旋转)' : '旋转';
rotateBtn.setAttribute('aria-label', st.rotated ? '竖屏' : '旋转');
}
}).catch(function () { /* */ });
};
PreviewWorkbenchHost.prototype._bindToolbar = function () {
var self = this;
document.getElementById('pw-mode-game').addEventListener('click', function () {
self._inspectOn = false;
3 days ago
self._setInspectCapture(false);
3 days ago
self._setPickHighlight(null);
3 days ago
self.bridge.call('pick.setMode', { mode: 'game' }).catch(function () { /* */ });
document.getElementById('pw-mode-game').classList.add('active');
document.getElementById('pw-mode-inspect').classList.remove('active');
self._syncSubscriptions(self.layout.getVisiblePanels());
});
document.getElementById('pw-mode-inspect').addEventListener('click', function () {
self._inspectOn = true;
3 days ago
self._setInspectCapture(true);
self.bridge.call('pick.setMode', { mode: 'inspect' }).catch(function () { /* */ });
document.getElementById('pw-mode-inspect').classList.add('active');
document.getElementById('pw-mode-game').classList.remove('active');
self._syncSubscriptions(self.layout.getVisiblePanels());
});
var deviceSel = document.getElementById('pw-device-select');
if (deviceSel) {
deviceSel.addEventListener('change', function () {
self.bridge.call('device.set', { id: deviceSel.value });
});
}
var rotateBtn = document.getElementById('pw-device-rotate');
if (rotateBtn) {
rotateBtn.addEventListener('click', function () {
self.bridge.call('device.rotate').then(function (rotated) {
rotateBtn.classList.toggle('active', !!rotated);
3 days ago
rotateBtn.title = rotated ? '竖屏(当前已旋转)' : '旋转';
rotateBtn.setAttribute('aria-label', rotated ? '竖屏' : '旋转');
});
});
}
4 days ago
var refreshBtn = document.getElementById('pw-game-refresh');
if (refreshBtn) {
refreshBtn.addEventListener('click', function () {
self._reloadGame();
});
}
var pauseBtn = document.getElementById('pw-game-pause');
if (pauseBtn) {
pauseBtn.addEventListener('click', function () {
self._paused = !self._paused;
self.bridge.call(self._paused ? 'game.pause' : 'game.resume');
pauseBtn.textContent = self._paused ? '继续' : '暂停';
pauseBtn.classList.toggle('active', self._paused);
});
}
var bindStep = function (btn) {
if (!btn) return;
btn.addEventListener('click', function () {
if (!self._paused) {
self._paused = true;
self.bridge.call('game.pause');
if (pauseBtn) {
pauseBtn.textContent = '继续';
pauseBtn.classList.add('active');
}
}
self.bridge.call('game.step');
});
};
bindStep(document.getElementById('pw-game-step'));
bindStep(document.getElementById('pw-game-step-compact'));
this._bindFpsButton();
this._bindPureButton();
this._bindWorkbenchToggle();
};
/** 右上角:一键开启/关闭调试工作台 */
PreviewWorkbenchHost.prototype._bindWorkbenchToggle = function () {
var self = this;
var btn = document.getElementById('pw-workbench-toggle');
if (!btn) return;
var apply = function (on, persist) {
self._workbenchOn = !!on;
btn.classList.toggle('active', self._workbenchOn);
btn.setAttribute('aria-pressed', self._workbenchOn ? 'true' : 'false');
btn.textContent = self._workbenchOn ? '工作台' : '工作台(关)';
if (self.layout) self.layout.setWorkbenchEnabled(self._workbenchOn);
if (!self.bridge) return;
if (!self._workbenchOn) {
self._inspectOn = false;
self.bridge.call('pick.setMode', { mode: 'game' }).catch(function () { /* */ });
self.bridge.hibernate().catch(function () { /* */ });
for (var k in self._subs) {
if (Object.prototype.hasOwnProperty.call(self._subs, k)) self._subs[k] = false;
}
self._setStatus('工作台已关闭', true);
} else {
self.bridge.wake().then(function () {
self._syncSubscriptions(self.layout.getVisiblePanels());
self._setStatus(self._ready ? '引擎已就绪' : '引擎等待中…', self._ready);
}).catch(function () { /* */ });
}
if (persist !== false) {
try {
localStorage.setItem('preview-workbench-enabled', self._workbenchOn ? '1' : '0');
} catch (e) { /* */ }
}
};
btn.addEventListener('click', function () {
apply(!self._workbenchOn, true);
});
var saved = null;
try { saved = localStorage.getItem('preview-workbench-enabled'); } catch (e2) { /* */ }
// 新用户默认关闭;须在 layout.init / 悬浮窗还原完成后再关,禁止启动期就置 off
if (saved === '1') {
apply(true, false);
} else {
setTimeout(function () { apply(false, saved == null); }, 0);
}
};
PreviewWorkbenchHost.prototype._bindFpsButton = function () {
var self = this;
var main = document.getElementById('pw-show-fps');
var compact = document.getElementById('pw-show-fps-compact');
var syncUi = function (on) {
if (main) {
main.classList.toggle('active', on);
main.setAttribute('aria-pressed', on ? 'true' : 'false');
}
if (compact) {
compact.classList.toggle('active', on);
compact.setAttribute('aria-pressed', on ? 'true' : 'false');
}
};
var apply = function (on) {
self._fpsOn = !!on;
syncUi(self._fpsOn);
if (self.bridge) self.bridge.call('game.fps', { on: self._fpsOn });
};
var toggle = function () { apply(!self._fpsOn); };
if (main) main.addEventListener('click', toggle);
if (compact) compact.addEventListener('click', toggle);
syncUi(false);
setTimeout(function () { apply(false); }, 600);
};
PreviewWorkbenchHost.prototype._bindPureButton = function () {
var self = this;
var btn = document.getElementById('pw-pure-measure');
if (!btn) return;
btn.addEventListener('click', function () {
self._pure = !self._pure;
btn.classList.toggle('active', self._pure);
btn.setAttribute('aria-pressed', self._pure ? 'true' : 'false');
if (self._pure) {
self.bridge.hibernate();
for (var k in self._subs) {
if (Object.prototype.hasOwnProperty.call(self._subs, k)) self._subs[k] = false;
}
3 days ago
self._stopLiveStats(true);
self._setStatus('纯净测帧中', true);
} else {
self.bridge.wake().then(function () {
self._syncSubscriptions(self.layout.getVisiblePanels());
self._setStatus('引擎已就绪', true);
3 days ago
self._startLiveStats();
});
}
});
};
3 days ago
/** 右上角实时 FPS / 总内存(按需拉取,纯净测帧时停) */
PreviewWorkbenchHost.prototype._startLiveStats = function () {
var self = this;
this._stopLiveStats(false);
if (this._pure || !this.bridge) return;
this._statsTimer = setInterval(function () {
if (self._pure || !self.bridge) return;
self.bridge.call('stats.get').then(function (s) {
self._applyLiveStats(s);
}).catch(function () { /* */ });
}, 500);
};
PreviewWorkbenchHost.prototype._stopLiveStats = function (blank) {
if (this._statsTimer) {
clearInterval(this._statsTimer);
this._statsTimer = 0;
}
if (blank) this._applyLiveStats(null);
};
PreviewWorkbenchHost.prototype._applyLiveStats = function (s) {
var fpsEl = document.getElementById('pw-stat-fps');
var memEl = document.getElementById('pw-stat-mem');
if (!fpsEl || !memEl) return;
var levels = ['ok', 'good', 'warn', 'hot', 'bad', 'muted'];
var clear = function (el) {
for (var i = 0; i < levels.length; i++) el.classList.remove(levels[i]);
};
if (!s) {
fpsEl.textContent = 'FPS --';
memEl.textContent = '内存 --';
clear(fpsEl);
clear(memEl);
fpsEl.classList.add('muted');
memEl.classList.add('muted');
return;
}
var fps = Math.round(s.fps || 0);
var mem = Math.round((s.totalMemory || 0) * 10) / 10;
fpsEl.textContent = 'FPS ' + fps;
3 days ago
memEl.textContent = '内存 ' + mem + ' MB';
3 days ago
clear(fpsEl);
if (fps <= 0) fpsEl.classList.add('muted');
else if (fps >= 55) fpsEl.classList.add('ok');
else if (fps >= 40) fpsEl.classList.add('good');
else if (fps >= 25) fpsEl.classList.add('warn');
else if (fps >= 15) fpsEl.classList.add('hot');
else fpsEl.classList.add('bad');
clear(memEl);
if (mem <= 0) memEl.classList.add('muted');
else if (mem < 150) memEl.classList.add('ok');
else if (mem < 300) memEl.classList.add('good');
else if (mem < 500) memEl.classList.add('warn');
else memEl.classList.add('hot');
};
3 days ago
/**
* 检视捕获层:盖在游戏 iframe 上,把点击转成 pick.at(不依赖游戏内 DOM 事件)。
* @param {boolean} on
*/
PreviewWorkbenchHost.prototype._setInspectCapture = function (on) {
var self = this;
var layer = document.getElementById('pw-pick-capture');
var root = document.getElementById('pw-root');
if (root) root.classList.toggle('pw-inspect', !!on);
document.documentElement.classList.toggle('pw-inspect', !!on);
if (!layer) return;
layer.hidden = !on;
if (this._pickCaptureBound) {
layer.removeEventListener('pointerdown', this._onPickCapture, true);
layer.removeEventListener('mousedown', this._onPickCapture, true);
this._pickCaptureBound = false;
}
if (!on) return;
if (!this._onPickCapture) {
this._onPickCapture = function (ev) {
if (!self._inspectOn) return;
if (ev.preventDefault) ev.preventDefault();
if (ev.stopPropagation) ev.stopPropagation();
if (ev.stopImmediatePropagation) ev.stopImmediatePropagation();
var gameX = ev.clientX;
var gameY = ev.clientY;
var iframe = self._iframe;
if (iframe) {
try {
var r = iframe.getBoundingClientRect();
gameX = ev.clientX - r.left;
gameY = ev.clientY - r.top;
// 同域时用游戏内 canvas 矩形再换算一层(兼容设备壳偏移)
var cwin = iframe.contentWindow;
var cdoc = cwin && cwin.document;
var cel = cdoc && (cdoc.getElementById('GameCanvas') || (cwin.cc && cwin.cc.game && cwin.cc.game.canvas));
if (cel && cel.getBoundingClientRect) {
// gameX/Y 已是 iframe 视口坐标;Agent 会减 canvas.rect
// 这里保持 viewport 坐标即可
}
} catch (e) { /* */ }
}
if (!self.bridge) return;
self.bridge.call('pick.at', {
clientX: gameX,
clientY: gameY,
space: 'viewport',
}).then(function (res) {
var name = res && res.debug && res.debug.best ? res.debug.best : '';
if (res && res.uuid) {
var label = name || res.uuid.slice(0, 8);
if (res.cycle && res.cycle.total > 1) {
label += ' (' + (res.cycle.index + 1) + '/' + res.cycle.total + ')';
}
self._setStatus('已选中 ' + label, true);
if (self.panels.inspector) self.panels.inspector.loadUuid(res.uuid);
3 days ago
if (self.panels.hierarchy) self.panels.hierarchy.setSelectedUuid(res.uuid);
self._setPickHighlight(res.rect || null);
if (!res.rect && self.bridge) {
self.bridge.call('pick.getRect').then(function (rect) {
self._setPickHighlight(rect);
}).catch(function () { /* */ });
}
3 days ago
} else {
self._setStatus('未命中 (' + Math.round(gameX) + ',' + Math.round(gameY) + ')', false);
3 days ago
self._setPickHighlight(null);
3 days ago
}
}).catch(function (err) {
self._setStatus('选取失败', false);
console.warn('[检视] pick.at', err);
});
};
}
layer.addEventListener('pointerdown', this._onPickCapture, true);
layer.addEventListener('mousedown', this._onPickCapture, true);
this._pickCaptureBound = true;
};
3 days ago
/**
* 选中 Gizmo:半透明框可拖;红/绿箭头沿 X/Y 轴拖。
* 坐标来自 pick.getRect(含 pos / scale)。
3 days ago
*/
PreviewWorkbenchHost.prototype._setPickHighlight = function (rect) {
var gizmo = document.getElementById('pw-pick-gizmo');
if (!gizmo) return;
if (!rect || !(rect.width > 0) || !(rect.height > 0)) {
gizmo.hidden = true;
this._gizmoState = null;
return;
}
var host = document.getElementById('pw-game-host');
var ox = 0;
var oy = 0;
if (host && this._iframe) {
var hr = host.getBoundingClientRect();
var ir = this._iframe.getBoundingClientRect();
ox = ir.left - hr.left;
oy = ir.top - hr.top;
}
gizmo.hidden = false;
gizmo.style.left = (ox + (rect.left || 0)) + 'px';
gizmo.style.top = (oy + (rect.top || 0)) + 'px';
gizmo.style.width = Math.max(4, rect.width || 0) + 'px';
gizmo.style.height = Math.max(4, rect.height || 0) + 'px';
this._gizmoState = {
uuid: rect.uuid || '',
pos: rect.pos || { x: 0, y: 0, z: 0 },
scale: rect.scale || { x: 1, y: 1 },
ox: ox,
oy: oy,
};
};
/** Gizmo 拖拽:xy / 仅 x / 仅 y */
PreviewWorkbenchHost.prototype._bindGizmoDrag = function (gizmo) {
var self = this;
var drag = null;
var onDown = function (ev) {
if (ev.button != null && ev.button !== 0) return;
var axis = ev.currentTarget.getAttribute('data-drag') || 'xy';
var st = self._gizmoState;
if (!st || !st.uuid) return;
ev.preventDefault();
ev.stopPropagation();
drag = {
axis: axis,
x0: ev.clientX,
y0: ev.clientY,
px: st.pos.x || 0,
py: st.pos.y || 0,
sx: st.scale.x || 1,
sy: st.scale.y || 1,
uuid: st.uuid,
left0: parseFloat(gizmo.style.left) || 0,
top0: parseFloat(gizmo.style.top) || 0,
};
document.addEventListener('pointermove', onMove);
document.addEventListener('pointerup', onUp);
gizmo.classList.add('dragging');
};
var onMove = function (ev) {
if (!drag) return;
ev.preventDefault();
var dx = ev.clientX - drag.x0;
var dy = ev.clientY - drag.y0;
var nx = drag.px;
var ny = drag.py;
if (drag.axis !== 'y') nx = drag.px + dx * drag.sx;
if (drag.axis !== 'x') ny = drag.py - dy * drag.sy;
nx = Math.round(nx * 100) / 100;
ny = Math.round(ny * 100) / 100;
if (self.bridge) {
if (drag.axis !== 'y') {
self.bridge.call('node.set', { uuid: drag.uuid, fid: 'px', value: nx });
}
if (drag.axis !== 'x') {
self.bridge.call('node.set', { uuid: drag.uuid, fid: 'py', value: ny });
}
}
// Gizmo 跟手
if (drag.axis !== 'y') gizmo.style.left = (drag.left0 + dx) + 'px';
if (drag.axis !== 'x') gizmo.style.top = (drag.top0 + dy) + 'px';
if (self._gizmoState) {
self._gizmoState.pos = { x: nx, y: ny, z: (self._gizmoState.pos && self._gizmoState.pos.z) || 0 };
}
};
var onUp = function () {
if (!drag) return;
var uuid = drag.uuid;
drag = null;
gizmo.classList.remove('dragging');
document.removeEventListener('pointermove', onMove);
document.removeEventListener('pointerup', onUp);
if (self.bridge) {
self.bridge.call('pick.getRect').then(function (r) {
self._setPickHighlight(r);
}).catch(function () { /* */ });
if (self.panels.inspector) self.panels.inspector.loadUuid(uuid);
}
};
var handles = gizmo.querySelectorAll('[data-drag]');
for (var i = 0; i < handles.length; i++) {
handles[i].addEventListener('pointerdown', onDown);
handles[i].addEventListener('mousedown', onDown);
}
3 days ago
};
PreviewWorkbenchHost.prototype._setStatus = function (text, ok) {
var el = document.getElementById('pw-engine-status');
if (!el) return;
el.textContent = text;
el.classList.toggle('ok', !!ok);
el.classList.toggle('err', ok === false);
};
function start() {
if (global.__PW_MODE === 'game') return;
var host = new PreviewWorkbenchHost();
host.boot();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', start);
} else {
start();
}
})(window);