From 0e0c1da5ffc205d7385019d0978e10e5042cacfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E4=B8=80=E5=AF=B0?= Date: Sun, 15 Mar 2026 23:32:55 +0800 Subject: [PATCH] fix bug for vnc windows size ratio --- ui/src/ui/app-render.ts | 1 + ui/src/ui/components/claw-computer-panel.ts | 181 ++++++++++++-------- 2 files changed, 106 insertions(+), 76 deletions(-) diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index 105804eaaef..f5671ec8f6d 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -1514,6 +1514,7 @@ export function renderApp(state: AppViewState) { .vncTarget=${state.settings.vncTarget} .password=${state.settings.vncPassword} @close=${() => state.toggleClawComputer()} + @float=${() => state.setClawComputerWidth(0)} style="flex: 1; min-height: 0;" > diff --git a/ui/src/ui/components/claw-computer-panel.ts b/ui/src/ui/components/claw-computer-panel.ts index 3b3d707cc54..9d1cafbb477 100644 --- a/ui/src/ui/components/claw-computer-panel.ts +++ b/ui/src/ui/components/claw-computer-panel.ts @@ -55,6 +55,10 @@ export class ClawComputerPanel extends LitElement { private aspectRatio = 1; // 记录拖拽开始时鼠标相对于拖拽句柄的位置(点 A) private dragAnchor = { x: 0, y: 0 }; + // 拖拽过程中的临时悬浮状态 + private tempIsFloating = false; + // canvas 的比例(用于悬浮模式缩放时保持) + private canvasRatio = 16 / 9; @property({ type: Boolean }) enabled = false; @@ -326,7 +330,8 @@ export class ClawComputerPanel extends LitElement { `; render() { - const screenStyle = this.isFloating + const displayFloating = this.isDragging ? this.tempIsFloating : this.isFloating; + const screenStyle = displayFloating ? `transform: translate(${this.floatingRect.x}px, ${this.floatingRect.y}px); width: ${this.floatingRect.width}px; height: ${this.floatingRect.height}px;` : `transform: translate(${this.dockedOffsetX}px, ${this.dockedOffsetY}px);`; @@ -336,12 +341,12 @@ export class ClawComputerPanel extends LitElement {
${ - this.isFloating + displayFloating ? html`
this.handleResizeStart(e, "top")}>
this.handleResizeStart(e, "bottom")}>
@@ -381,6 +386,7 @@ export class ClawComputerPanel extends LitElement { this.isDragging = true; this.dragStart = { x: e.clientX, y: e.clientY }; + this.tempIsFloating = this.isFloating; // 初始化临时状态 const screen = this.shadowRoot?.querySelector(".screen") as HTMLDivElement | null; const dragHandle = this.shadowRoot?.querySelector(".drag-handle") as HTMLDivElement | null; @@ -425,6 +431,18 @@ export class ClawComputerPanel extends LitElement { width: screenWidth, height: screenHeight, }; + + // 打印尺寸信息,用于调试 + const canvas = this.shadowRoot?.querySelector(".screen canvas") as HTMLCanvasElement | null; + if (canvas) { + console.log("=== 切换到悬浮模式时的尺寸信息 ==="); + console.log("screen 宽度:", screenWidth); + console.log("screen 高度:", screenHeight); + console.log("screen 比例:", screenWidth / screenHeight); + console.log("canvas 宽度:", canvas.clientWidth); + console.log("canvas 高度:", canvas.clientHeight); + console.log("canvas 比例:", canvas.clientWidth / canvas.clientHeight); + } } } @@ -449,35 +467,15 @@ export class ClawComputerPanel extends LitElement { const dx = e.clientX - this.dragStart.x; const dy = e.clientY - this.dragStart.y; - if (this.isFloating) { - let newX = this.initialRect.x + dx; - let newY = this.initialRect.y + dy; - - const windowWidth = window.innerWidth; - const windowHeight = window.innerHeight; - - // 限制悬浮窗口不超出屏幕边界 - newX = Math.max(0, Math.min(newX, windowWidth - this.initialRect.width)); - newY = Math.max(0, Math.min(newY, windowHeight - this.initialRect.height)); - - this.floatingRect = { - x: newX, - y: newY, - width: this.initialRect.width, - height: this.initialRect.height, - }; - } else { - // 检测是否超过 resizable-divider(左边的分割线) - // resizable-divider 就在我们左边,所以用我们自己的左边界作为判断依据 - const hostRect = this.getBoundingClientRect(); - - // 检查鼠标是否超过了我们自己的左边界(即超过了 resizable-divider) - if (e.clientX < hostRect.left) { - // 切换到悬浮模式 - this.isFloating = true; + // 获取我们自己的左边界 + const hostRect = this.getBoundingClientRect(); + // 根据鼠标位置更新临时悬浮状态 + if (e.clientX < hostRect.left) { + // 鼠标在左侧,临时切换到悬浮模式 + if (!this.tempIsFloating) { + this.tempIsFloating = true; // 计算新的悬浮窗口位置,保持鼠标相对于拖拽句柄的位置(点 A) - // 窗口的左上角 = 鼠标当前位置 - 锚点偏移 const newX = e.clientX - this.dragAnchor.x; const newY = e.clientY - this.dragAnchor.y; @@ -498,10 +496,37 @@ export class ClawComputerPanel extends LitElement { // 更新 dragStart,使其从当前位置继续拖拽 this.dragStart = { x: e.clientX, y: e.clientY }; - - return; } + } else { + // 鼠标在右侧,临时切换回停靠模式 + if (this.tempIsFloating) { + this.tempIsFloating = false; + // 重置停靠模式的偏移量 + this.dockedOffsetY = this.initialRect.offsetY; + this.dockedOffsetX = 0; + // 更新 dragStart,使其从当前位置继续拖拽 + this.dragStart = { x: e.clientX, y: e.clientY }; + } + } + if (this.tempIsFloating) { + let newX = this.initialRect.x + dx; + let newY = this.initialRect.y + dy; + + const windowWidth = window.innerWidth; + const windowHeight = window.innerHeight; + + // 限制悬浮窗口不超出屏幕边界 + newX = Math.max(0, Math.min(newX, windowWidth - this.initialRect.width)); + newY = Math.max(0, Math.min(newY, windowHeight - this.initialRect.height)); + + this.floatingRect = { + x: newX, + y: newY, + width: this.initialRect.width, + height: this.initialRect.height, + }; + } else { // 如果没有超过分割线,继续停靠模式的拖拽 // 每次都直接获取当前尺寸 const hostHeight = this.offsetHeight; @@ -546,7 +571,22 @@ export class ClawComputerPanel extends LitElement { } }; - private handleDragEnd = () => { + private handleDragEnd = (e: MouseEvent) => { + // 根据松手位置决定是否永久切换到悬浮模式 + const hostRect = this.getBoundingClientRect(); + if (e.clientX < hostRect.left) { + // 在左侧松手,永久切换到悬浮模式 + this.isFloating = true; + // 触发事件告诉父组件关闭停靠面板 + this.dispatchEvent(new CustomEvent("float", { bubbles: true, composed: true })); + } else { + // 在右侧松手,保持停靠模式 + this.isFloating = false; + // 重置偏移量 + this.dockedOffsetY = this.initialRect.offsetY; + this.dockedOffsetX = 0; + } + this.cleanupDragListeners(); }; @@ -570,6 +610,13 @@ export class ClawComputerPanel extends LitElement { this.isResizing = true; this.resizeEdge = edge; this.dragStart = { x: e.clientX, y: e.clientY }; + + // 每次开始缩放时,获取最新的 canvas 比例 + const canvas = this.shadowRoot?.querySelector(".screen canvas") as HTMLCanvasElement | null; + if (canvas && canvas.clientWidth > 0 && canvas.clientHeight > 0) { + this.canvasRatio = canvas.clientWidth / canvas.clientHeight; + } + this.initialRect = { x: this.floatingRect.x, y: this.floatingRect.y, @@ -604,75 +651,56 @@ export class ClawComputerPanel extends LitElement { const dx = e.clientX - this.dragStart.x; const dy = e.clientY - this.dragStart.y; - // 保持宽高比调整 - // 只有在对角调整时才强制保持宽高比,或者在边缘调整时更新另一个维度? - // 用户说“悬浮模式的缩放我要求是不要改变窗口比例的,斜对角拖拽时以对角为固定点缩放,但是不能任意缩放” - // 所以应该始终保持比例?或者只在对角时? - // “斜对角拖拽时以对角为固定点缩放”暗示对角时必须保持比例。 - // 单边拖拽通常只改变一个维度,这会破坏比例。 - // 如果必须保持比例,单边拖拽也必须同时改变另一个维度。 + // 考虑到窗口有边框和标题栏,实际的 canvas 内容区域要减去这些 padding + // 左右 padding: 5.4px * 2 = 10.8px + // 上下 padding: 32.4px (标题栏) + 5.4px (底边) = 37.8px + const PADDING_X = 10.8; + const PADDING_Y = 37.8; let { x, y, width, height } = this.initialRect; + // 1. 先计算出用户拖拽后的预期宽高 if (this.resizeEdge.includes("right")) { width += dx; - if (!this.resizeEdge.includes("top") && !this.resizeEdge.includes("bottom")) { - height = width / this.aspectRatio; - } } if (this.resizeEdge.includes("left")) { x += dx; width -= dx; - if (!this.resizeEdge.includes("top") && !this.resizeEdge.includes("bottom")) { - height = width / this.aspectRatio; - } } if (this.resizeEdge.includes("bottom")) { height += dy; - if (!this.resizeEdge.includes("left") && !this.resizeEdge.includes("right")) { - width = height * this.aspectRatio; - } } if (this.resizeEdge.includes("top")) { y += dy; height -= dy; - if (!this.resizeEdge.includes("left") && !this.resizeEdge.includes("right")) { - width = height * this.aspectRatio; - } } - // 对角调整时的比例保持 - if ( - (this.resizeEdge.includes("left") || this.resizeEdge.includes("right")) && - (this.resizeEdge.includes("top") || this.resizeEdge.includes("bottom")) - ) { - // 简单处理:基于宽度的变化来调整高度,或者基于高度调整宽度 - // 优先保持宽高比 - if (this.resizeEdge.includes("right")) { - height = width / this.aspectRatio; - } else { - height = width / this.aspectRatio; + // 2. 根据 canvas 的比例强制修正窗口尺寸,确保窗口能仅仅贴合 VNC + let canvasW = width - PADDING_X; + let canvasH = height - PADDING_Y; + + if (this.resizeEdge === "top" || this.resizeEdge === "bottom") { + // 如果只拖拽上下边,以高度为基准计算宽度 + canvasW = canvasH * this.canvasRatio; + width = canvasW + PADDING_X; + if (this.resizeEdge.includes("left")) { + x = this.initialRect.x + (this.initialRect.width - width); } - // 如果是 top,需要重新计算 y + } else { + // 左右边或者对角线,统一以宽度为基准计算高度 + canvasH = canvasW / this.canvasRatio; + height = canvasH + PADDING_Y; if (this.resizeEdge.includes("top")) { y = this.initialRect.y + (this.initialRect.height - height); } } - // 最小尺寸限制 + // 3. 最小尺寸限制 if (width < 200) { width = 200; - height = width / this.aspectRatio; - if (this.resizeEdge.includes("left")) { - x = this.initialRect.x + (this.initialRect.width - width); - } - if (this.resizeEdge.includes("top")) { - y = this.initialRect.y + (this.initialRect.height - height); - } - } - if (height < 150) { - height = 150; - width = height * this.aspectRatio; + canvasW = width - PADDING_X; + canvasH = canvasW / this.canvasRatio; + height = canvasH + PADDING_Y; if (this.resizeEdge.includes("left")) { x = this.initialRect.x + (this.initialRect.width - width); } @@ -680,6 +708,7 @@ export class ClawComputerPanel extends LitElement { y = this.initialRect.y + (this.initialRect.height - height); } } + // 注意:这里不再单独限制 height,因为 height 已经和 width 绑定了 this.floatingRect = { x, y, width, height };