diff --git a/openless-all/app/scripts/windows-ui-config.test.mjs b/openless-all/app/scripts/windows-ui-config.test.mjs index a2224f4b..633a7583 100644 --- a/openless-all/app/scripts/windows-ui-config.test.mjs +++ b/openless-all/app/scripts/windows-ui-config.test.mjs @@ -15,18 +15,39 @@ function assertMatch(source, pattern, name) { const raw = await readFile(new URL('../src-tauri/tauri.conf.json', import.meta.url), 'utf-8'); const config = JSON.parse(raw); const capsuleWindow = config.app.windows.find((window) => window.label === 'capsule'); +const mainWindow = config.app.windows.find((window) => window.label === 'main'); const libRs = await readFile(new URL('../src-tauri/src/lib.rs', import.meta.url), 'utf-8'); const coordinatorRs = await readFile(new URL('../src-tauri/src/coordinator.rs', import.meta.url), 'utf-8'); const capsuleTsx = await readFile(new URL('../src/components/Capsule.tsx', import.meta.url), 'utf-8'); const capsuleLayoutTs = await readFile(new URL('../src/lib/capsuleLayout.ts', import.meta.url), 'utf-8'); +const windowChromeTsx = await readFile(new URL('../src/components/WindowChrome.tsx', import.meta.url), 'utf-8'); +const floatingShellTsx = await readFile(new URL('../src/components/FloatingShell.tsx', import.meta.url), 'utf-8'); if (!capsuleWindow) { throw new Error('capsule window config missing'); } +if (!mainWindow) { + throw new Error('main window config missing'); +} assertEqual(capsuleWindow.width, 220, 'windows capsule config keeps translation-capable width baseline'); assertEqual(capsuleWindow.height, 110, 'windows capsule config keeps translation-capable height baseline'); assertEqual(capsuleWindow.transparent, true, 'capsule window should keep transparent visuals'); assertEqual(capsuleWindow.alwaysOnTop, true, 'capsule window should stay above the focused app while recording'); +assertEqual(mainWindow.decorations, false, 'windows main window should use only custom titlebar'); +assertEqual(mainWindow.visible, false, 'windows main window should stay hidden until the intended first show point'); + +if (!/function WindowsResizeHandles\(\)/.test(windowChromeTsx)) { + throw new Error('windows frameless shell should expose explicit resize handles'); +} + +if (!/startResizeDragging\(direction\)/.test(windowChromeTsx)) { + throw new Error('windows resize handles should delegate edge dragging to Tauri'); +} + +if (!/borderRadius:\s*'var\(--ol-window-console-radius\)'/.test(floatingShellTsx)) { + throw new Error('floating shell should consume the shared window-console radius'); +} + assertMatch( coordinatorRs, /let visible = !matches!\(state,\s*CapsuleState::Idle\);/, diff --git a/openless-all/app/src-tauri/tauri.conf.json b/openless-all/app/src-tauri/tauri.conf.json index f4d9df72..092267c6 100644 --- a/openless-all/app/src-tauri/tauri.conf.json +++ b/openless-all/app/src-tauri/tauri.conf.json @@ -21,7 +21,7 @@ "minWidth": 980, "minHeight": 640, "resizable": true, - "decorations": true, + "decorations": false, "transparent": true, "shadow": true, "hiddenTitle": true, diff --git a/openless-all/app/src/components/FloatingShell.tsx b/openless-all/app/src/components/FloatingShell.tsx index 08ab4932..6e0de024 100644 --- a/openless-all/app/src/components/FloatingShell.tsx +++ b/openless-all/app/src/components/FloatingShell.tsx @@ -255,7 +255,7 @@ function FloatingShellBody({ os, initialTab, initialSettings }: { os: OS; initia flex: 1, minWidth: 0, overflow: 'hidden', background: 'var(--ol-surface)', - borderRadius: os === 'mac' ? 20 : 10, + borderRadius: 'var(--ol-window-console-radius)', border: '0.5px solid rgba(0,0,0,0.06)', boxShadow: '0 1px 0 rgba(255,255,255,0.8) inset, 0 8px 24px -12px rgba(15,17,22,0.10), 0 2px 6px -2px rgba(15,17,22,0.06)', display: 'flex', diff --git a/openless-all/app/src/components/WindowChrome.tsx b/openless-all/app/src/components/WindowChrome.tsx index 7a03dd31..c460b260 100644 --- a/openless-all/app/src/components/WindowChrome.tsx +++ b/openless-all/app/src/components/WindowChrome.tsx @@ -1,16 +1,3 @@ -// WindowChrome.tsx — frosted outer frame + raised inner console pattern. -// The OUTER frame is a translucent shell with a tinted backdrop showing through. -// The INNER content lives in a single raised card that floats above it. -// -// Layout per window: -// ┌─ frosted outer ───────────────────────────────┐ -// │ [titlebar] │ -// │ ┌─ raised console (white, shadow) ─┐ │ -// │ │ sidebar │ main │ │ -// │ └──────────────────────────────────┘ │ -// │ [icon footer] │ -// └───────────────────────────────────────────────┘ - import { useState, type CSSProperties, type ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; import { getCurrentWindow } from '@tauri-apps/api/window'; @@ -31,8 +18,9 @@ export function detectOS(): OS { const MAC_TITLEBAR_HEIGHT = 36; const MAC_SYSTEM_CONTROLS_RESERVED_WIDTH = 80; -const MAC_WINDOW_RADIUS = 20; -const WIN_WINDOW_RADIUS = 0; +export const WIN_TITLEBAR_HEIGHT = 36; +export const WIN_WINDOW_RADIUS = 10; +export const WIN_CONSOLE_RADIUS = 10; const WIN_RESIZE_EDGE = 6; const WIN_RESIZE_CORNER = 14; @@ -53,19 +41,32 @@ interface WindowChromeProps { height?: number | string; } -export function WindowChrome({ os = 'mac', title = 'OpenLess', children, height = 800 }: WindowChromeProps) { +export function WindowChrome({ + os = 'mac', + title = 'OpenLess', + children, + height = 800, +}: WindowChromeProps) { + const shellRadius = os === 'mac' ? 20 : os === 'win' ? WIN_WINDOW_RADIUS : 14; + const consoleRadius = os === 'mac' ? 20 : os === 'win' ? WIN_CONSOLE_RADIUS : 14; + return (
{os === 'win' && } {os === 'win' && } - {/* macOS:三色窗口按钮交给系统绘制和定位。这里只保留顶部拖动区, - 并避开系统按钮热区,防止拖动层吞掉 close/minimize/zoom 点击。 */} {os === 'mac' && (
runWindowsWindowAction(action)} onMouseEnter={() => setHovered(true)} onMouseLeave={() => { setHovered(false); @@ -205,14 +207,13 @@ function WinTitleButton({ title, action, children, tone = 'default' }: WinTitleB }} onMouseDown={() => setPressed(true)} onMouseUp={() => setPressed(false)} - onClick={() => runWindowsWindowAction(action)} > {children} ); } -async function startWindowsResize(direction: ResizeDirection) { +async function startResizeDragging(direction: ResizeDirection) { try { await getCurrentWindow().startResizeDragging(direction); } catch (error) {