/** * Hide or show an HTML element based on the value. * 根据值隐藏或显示 HTML 元素。 * * @param {HTMLElement} element - The HTML element to hide or show. 要隐藏或显示的 HTML 元素。 * @param {boolean} value - If true, the element is hidden; otherwise, it is shown. 如果为 true,则隐藏元素;否则显示。 */ function hideElement(element, value) { element.style.display = value ? 'none' : 'block'; } /** * get panel frame element * 获取 panel frame 元素 * @param container * @returns {*} */ function getPanelFrameElement(container) { let element = container; while (element) { element = element.parentElement || element.getRootNode().host; if (element && element.tagName === 'PANEL-FRAME' && element.getAttribute('name') === 'inspector') { break; } } return element; } const UI_SECTION_BAR_HEIGHT = 26; /** * 处理展示预览以及操作预览面板的辅助类 * Helper classes that handle the Showcase Preview and Manipulation Preview panels */ class PreviewControl { _gLPreviewConfig = { name: '', method: '', }; _animationId = -1; _isDirty = false; _glPreview = null; // html element _container = null; _image = null; _canvas = null; _resetCamera = null; _viewToggleButton = null; _resizeObserver = null; _observerBind = this._observer.bind(this); _onMouseDownBind = this._onMouseDown.bind(this); _onMouseWheelBind = this._onMouseWheel.bind(this); _onResetCameraBind = this._onResetCamera.bind(this); _onViewToggleBind = this._onViewToggle.bind(this); async _updateToolElements() { if (!this._container) { return; } this.queryPreviewFunction('queryViewToolState') .then(state => { if (!state) { return; } let toolRight = 10; if (state.enableResetCamera) { this._resetCamera = this._container.querySelector('.reset-camera'); if (!this._resetCamera) { this._resetCamera = document.createElement('ui-button'); this._resetCamera.classList.add('reset-camera'); this._container.appendChild(this._resetCamera); this._resetCamera.setAttribute('type', 'icon'); this._resetCamera.setAttribute('tooltip', 'i18n:ENGINE.inspector.preview.resetCameraView'); this._resetCamera.innerHTML = ``; this._resetCamera.addEventListener('click', this._onResetCameraBind); } this._resetCamera.style = ` position: absolute; right: ${toolRight}px; bottom: 10px; height: 24px; width: 24px; `; toolRight += 28; } if (state.enableViewToggle) { this._viewToggleButton = this._container.querySelector('.view-toggle'); if (!this._viewToggleButton) { this._viewToggleButton = document.createElement('ui-button'); this._container.appendChild(this._viewToggleButton); this._viewToggleButton.classList.add('view-toggle'); this._viewToggleButton.setAttribute('type', 'icon'); this._viewToggleButton.setAttribute('tooltip', 'i18n:ENGINE.inspector.preview.viewToggle'); this._viewToggleButton.addEventListener('click', this._onViewToggleBind); } this._viewToggleButton.style = ` position: absolute; right: ${toolRight}px; bottom: 10px; height: 24px; width: 24px; `; this._updateViewToggleIcon(); } }); this._updateViewToggleIcon(); } _updateViewToggleIcon() { if (!this._viewToggleButton) { return; } this.queryPreviewFunction('is2DView').then(is2D => { this._viewToggleButton.innerHTML = ``; }); } constructor(name, method, container) { this._gLPreviewConfig = { name: name, method: method, }; this._container = container; this._createCanvas(); } async queryPreviewFunction(funcName, ...args) { try { return await Editor.Message.request('scene', 'call-preview-function', this._gLPreviewConfig.name, funcName, ...args); } catch (e) { console.error(e); return null; } } async callPreviewFunction(funcName, ...args) { try { const result = await Editor.Message.request('scene', 'call-preview-function', this._gLPreviewConfig.name, funcName, ...args); this.doRefreshDirty(); return result; } catch (e) { console.error(e); return null; } } _createCanvas() { if (!this._container) { return; } // image for dragging inspector-resize-preview to resize the preview view. this._image = document.createElement('div'); this._image.classList.add('image'); this._container.appendChild(this._image); this._image.style = ` height: var(--inspector-footer-preview-height, 200px); position: absolute; overflow: hidden; display: flex; flex: 1; width: 100%; pointer-events: none; `; this._canvas = document.createElement('canvas'); this._canvas.classList.add('canvas'); this._canvas.style = ` flex: 1; `; this._container.appendChild(this._canvas); this._canvas.addEventListener('mousedown', this._onMouseDownBind); this._canvas.addEventListener('wheel', this._onMouseWheelBind); } async init() { const GLPreview = Editor._Module.require('PreviewExtends').default; this._glPreview = new GLPreview(this._gLPreviewConfig.name, this._gLPreviewConfig.method); await this._glPreview.init({ width: this._canvas.clientWidth, height: this._canvas.clientHeight, }); this._resizeObserver = new window.ResizeObserver(this._observerBind); this._resizeObserver.observe(this._image); await this._refresh(); } doRefreshDirty() { this._observer(); void this._refresh(); } close() { this._resizeObserver && this._resizeObserver.unobserve(this._image); cancelAnimationFrame(this._animationId); } _observer() { this._isDirty = true; } /** * Calculate canvas max size to avoid overflow when dragging inspector-resize-preview. * @returns {number} */ panelFrameElement = null; getMaxHeight() { if (!this.panelFrameElement) { this.panelFrameElement = getPanelFrameElement(this._container); } if (!this.panelFrameElement) { return 0; } const $content = this._container.querySelector('.content'); if (!$content) { return this.panelFrameElement.clientHeight * 0.7 - UI_SECTION_BAR_HEIGHT; } else { return this.panelFrameElement.clientHeight * 0.7 - $content.clientHeight - UI_SECTION_BAR_HEIGHT; } } async _refresh() { if (this._isDirty) { try { this._isDirty = false; const canvas = this._canvas; const image = this._image; const width = image.clientWidth; let height = image.clientHeight; const maxHeight = this.getMaxHeight(); if (height >= maxHeight) { height = maxHeight; } if (canvas.width !== width || canvas.height !== height) { canvas.width = width; canvas.height = height; await this._glPreview.initGL(canvas, { width, height, }); await this._glPreview.resizeGL(width, height); } this._updateToolElements(); const info = await this._glPreview.queryPreviewData({ width: canvas.width, height: canvas.height, }); this._glPreview.drawGL(info); } catch (e) { console.warn(e); } } cancelAnimationFrame(this._animationId); this._animationId = requestAnimationFrame(() => { this._refresh(); }); } async _onMouseDown(event) { await this.callPreviewFunction('onMouseDown', { x: event.x, y: event.y, button: event.button, }); const mousemove = async (event) => { await this.callPreviewFunction('onMouseMove', { movementX: event.movementX, movementY: event.movementY, }); this._isDirty = true; }; const mouseup = async (event) => { await this.callPreviewFunction('onMouseUp', { x: event.x, y: event.y, }); document.removeEventListener('mousemove', mousemove); document.removeEventListener('mouseup', mouseup); this._isDirty = false; }; document.addEventListener('mousemove', mousemove); document.addEventListener('mouseup', mouseup); this._isDirty = true; } async _onMouseWheel(event) { await this.callPreviewFunction('onMouseWheel', { wheelDeltaY: event.deltaY, wheelDeltaX: event.deltaX, }); this._isDirty = true; } async _onResetCamera() { await this.callPreviewFunction('resetCameraView'); } async _onViewToggle() { await this.callPreviewFunction('viewToggle'); } } module.exports = { hideElement, PreviewControl, };