Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions openless-all/app/scripts/windows-ui-config.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ if (!/function WindowsResizeHandles\(\)/.test(windowChromeTsx)) {

assertMatch(
windowChromeTsx,
/const MAC_TITLEBAR_HEIGHT = 30;/,
/const MAC_TITLEBAR_HEIGHT = 28;/,
'macOS titlebar spacer should stay visually compact around the native traffic lights',
);
assertMatch(
Expand Down Expand Up @@ -108,27 +108,31 @@ if (!/export function getCapsuleHostMetrics\(\s*os: OS,\s*translationActive: boo
throw new Error('capsule layout should define explicit host metrics separate from the visible pill metrics');
}

if (!/if \(os === 'win'\)\s*\{[\s\S]*?width: 220,[\s\S]*?height: translationActive \? 118 : 84,[\s\S]*?bottomInset: 12,[\s\S]*?badgeGap: 8[\s\S]*?\}/.test(capsuleLayoutTs)) {
if (!/if \(os === 'win'\)\s*\{[\s\S]*?const horizontalInset = 12;[\s\S]*?const pill = getCapsulePillMetrics\(os\);[\s\S]*?width: pill\.width \+ horizontalInset \* 2,[\s\S]*?height: translationActive \? 118 : 84,[\s\S]*?horizontalInset,[\s\S]*?bottomInset: 12,[\s\S]*?badgeGap: 8,[\s\S]*?boxSizing: 'border-box',[\s\S]*?\}/.test(capsuleLayoutTs)) {
throw new Error('windows capsule host metrics should leave room for shadow and badge geometry');
}

if (!/const hostMetrics = getCapsuleHostMetrics\(os,\s*translation\);/.test(capsuleTsx)) {
throw new Error('capsule should derive host metrics from the shared layout contract');
}

if (!/justifyContent:\s*os === 'win' \? 'flex-end' : 'center'/.test(capsuleTsx)) {
throw new Error('windows capsule host should anchor the pill to the bottom instead of centering it inside the larger native host window');
if (!/return\s*\(\s*<div\s*style=\{\{[\s\S]*?width:\s*'100%',[\s\S]*?height:\s*'100%',[\s\S]*?position:\s*'relative',[\s\S]*?display:\s*'flex',[\s\S]*?alignItems:\s*'center',[\s\S]*?justifyContent:\s*'center',[\s\S]*?paddingLeft:\s*hostMetrics\.horizontalInset,[\s\S]*?paddingRight:\s*hostMetrics\.horizontalInset,[\s\S]*?\}\}/.test(capsuleTsx)) {
throw new Error('capsule host should center the pill within the shared layout contract');
}

if (!/paddingLeft:\s*hostMetrics\.horizontalInset,/.test(capsuleTsx) || !/paddingRight:\s*hostMetrics\.horizontalInset,/.test(capsuleTsx)) {
throw new Error('windows capsule host should reserve shared horizontal inset room for shadow geometry');
}

if (!/paddingBottom:\s*os === 'win' \? hostMetrics\.bottomInset : 0/.test(capsuleTsx)) {
throw new Error('windows capsule host should respect the shared bottom inset');
}

if (!/bottom:\s*`\$\{hostMetrics\.bottomInset \+ metrics\.height \+ hostMetrics\.badgeGap\}px`/.test(capsuleTsx)) {
if (!/hostMetrics\.bottomInset \+ metrics\.height \+ hostMetrics\.badgeGap/.test(capsuleTsx)) {
throw new Error('windows translation badge should anchor from the shared host inset instead of a fixed center-based offset');
}

if (!/#\[cfg\(target_os = "windows"\)\][\s\S]*?width: 220\.0[\s\S]*?height: if translation_active \{ 118\.0 \} else \{ 84\.0 \}[\s\S]*?bottom_inset: 12\.0,/.test(libRs)) {
if (!/#\[cfg\(target_os = "windows"\)\][\s\S]*?const WINDOWS_CAPSULE_PILL_WIDTH: f64 = 196\.0;[\s\S]*?const WINDOWS_CAPSULE_SIDE_INSET: f64 = 12\.0;[\s\S]*?width: WINDOWS_CAPSULE_PILL_WIDTH \+ WINDOWS_CAPSULE_SIDE_INSET \* 2\.0,[\s\S]*?height: if translation_active \{ 118\.0 \} else \{ 84\.0 \},[\s\S]*?bottom_inset: 12\.0,/.test(libRs)) {
throw new Error('windows runtime capsule bounds should leave room for the native shadow while keeping a fixed visual pill');
}

Expand Down
6 changes: 5 additions & 1 deletion openless-all/app/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -752,8 +752,12 @@ struct CapsuleWindowBounds {
fn capsule_window_bounds(translation_active: bool) -> CapsuleWindowBounds {
#[cfg(target_os = "windows")]
{
const WINDOWS_CAPSULE_PILL_WIDTH: f64 = 196.0;
const WINDOWS_CAPSULE_SIDE_INSET: f64 = 12.0;
CapsuleWindowBounds {
width: 220.0,
// Keep the existing Windows hitbox width, but express it as
// pill width (196) + symmetric 12px side insets for shadow room.
width: WINDOWS_CAPSULE_PILL_WIDTH + WINDOWS_CAPSULE_SIDE_INSET * 2.0,
height: if translation_active { 118.0 } else { 84.0 },
bottom_inset: 12.0,
}
Expand Down
17 changes: 13 additions & 4 deletions openless-all/app/src/components/Capsule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ function CenterText({ os, kind, text, color = 'var(--ol-ink-3)' }: CenterTextPro
fontSize: 11,
fontWeight: 500,
color,
width: metrics.textWidth,
width: '100%',
maxWidth: metrics.textWidth,
minWidth: 0,
textAlign: 'center',
lineHeight: layout.allowWrap ? 1.2 : 1,
whiteSpace: layout.allowWrap ? 'normal' : 'nowrap',
Expand Down Expand Up @@ -185,7 +187,9 @@ function Pill({ os, state, level, insertedChars, message, onCancel, onConfirm }:
flexDirection: os === 'win' ? 'column' : 'row',
alignItems: 'center',
gap: os === 'win' ? 4 : 6,
width: metrics.textWidth,
width: '100%',
maxWidth: metrics.textWidth,
minWidth: 0,
justifyContent: 'center',
}}
>
Expand All @@ -195,6 +199,7 @@ function Pill({ os, state, level, insertedChars, message, onCancel, onConfirm }:
fontSize: 10.5,
fontWeight: 500,
color: 'var(--ol-ink-2)',
minWidth: 0,
textAlign: 'center',
lineHeight: processingLayout.allowWrap ? 1.15 : 1,
whiteSpace: processingLayout.allowWrap ? 'normal' : 'nowrap',
Expand Down Expand Up @@ -238,6 +243,7 @@ function Pill({ os, state, level, insertedChars, message, onCancel, onConfirm }:
padding: '0 8px',
width: metrics.width,
height: metrics.height,
boxSizing: metrics.boxSizing,
borderRadius: 999,
background: 'rgba(255, 255, 255, 0.62)',
backdropFilter: useBackdrop ? 'blur(28px) saturate(180%)' : 'none',
Expand Down Expand Up @@ -319,7 +325,10 @@ export function Capsule() {
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: os === 'win' ? 'flex-end' : 'center',
justifyContent: 'center',
paddingLeft: hostMetrics.horizontalInset,
paddingRight: hostMetrics.horizontalInset,
boxSizing: hostMetrics.boxSizing,
paddingTop: os === 'win'
? Math.max(0, hostMetrics.height - metrics.height - hostMetrics.bottomInset)
: 0,
Expand All @@ -337,7 +346,7 @@ export function Capsule() {
position: 'absolute',
left: '50%',
// macOS / Linux:胶囊窗口 220×110、pill 居中,badge 锚到 pill 中线上方 21+8。
// Windows:pill 不居中(带 12pt 阴影 inset),用 hostMetrics 量到底部 inset + pill 高 + gap
// Windows:host 比 pill 多出左右 12px / 底部 12px 的阴影空间,pill 仍保持居中
bottom: os === 'win'
? `${hostMetrics.bottomInset + metrics.height + hostMetrics.badgeGap}px`
: 'calc(50% + 21px + 8px)',
Expand Down
28 changes: 28 additions & 0 deletions openless-all/app/src/lib/capsuleLayout.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
getCapsuleHostMetrics,
getCapsuleMessageLayout,
getCapsulePillMetrics,
} from './capsuleLayout.ts';
Expand All @@ -13,11 +14,38 @@ const winMetrics = getCapsulePillMetrics('win');
assertEqual(winMetrics.width, 196, 'windows capsule widens pill');
assertEqual(winMetrics.height, 52, 'windows capsule increases pill height');
assertEqual(winMetrics.textWidth, 104, 'windows capsule keeps side controls clear');
assertEqual(winMetrics.boxSizing, 'border-box', 'windows capsule pill width is an outer border-box metric');

const winHost = getCapsuleHostMetrics('win', false);
assertEqual(winHost.width, 220, 'windows capsule host keeps the current outer hitbox width');
assertEqual(winHost.height, 84, 'windows capsule host keeps regular height');
assertEqual(winHost.horizontalInset, 12, 'windows capsule host keeps symmetric shadow insets');
assertEqual(winHost.boxSizing, 'border-box', 'windows capsule host inset is reserved inside the native width');
assertEqual(
winHost.width,
winMetrics.width + winHost.horizontalInset * 2,
'windows capsule host width derives from pill width plus symmetric side insets',
);
assertEqual(
winHost.width - winHost.horizontalInset * 2,
winMetrics.width,
'windows capsule host keeps the visible pill width after reserving side insets',
);

const winHostWithTranslation = getCapsuleHostMetrics('win', true);
assertEqual(winHostWithTranslation.width, 220, 'windows translation capsule keeps the same outer width');
assertEqual(winHostWithTranslation.height, 118, 'windows translation capsule grows vertically only');
assertEqual(winHostWithTranslation.horizontalInset, 12, 'windows translation capsule keeps symmetric side insets');
assertEqual(winHostWithTranslation.boxSizing, 'border-box', 'windows translation host keeps the same inset-reserving box model');

const macMetrics = getCapsulePillMetrics('mac');
assertEqual(macMetrics.width, 176, 'mac capsule keeps existing pill width');
assertEqual(macMetrics.height, 42, 'mac capsule keeps existing pill height');
assertEqual(macMetrics.textWidth, 84, 'mac capsule keeps existing text slot');
assertEqual(macMetrics.boxSizing, 'border-box', 'mac capsule keeps the existing border-box pill model');

const macHost = getCapsuleHostMetrics('mac', false);
assertEqual(macHost.boxSizing, 'border-box', 'mac capsule host keeps the existing border-box box model');

const winErrorLayout = getCapsuleMessageLayout('win', 'error');
assertEqual(winErrorLayout.lineClamp, 2, 'windows error message allows two lines');
Expand Down
28 changes: 24 additions & 4 deletions openless-all/app/src/lib/capsuleLayout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ export interface CapsulePillMetrics {
width: number;
height: number;
textWidth: number;
boxSizing: 'border-box' | 'content-box';
}

export interface CapsuleHostMetrics {
width: number;
height: number;
horizontalInset: number;
bottomInset: number;
badgeGap: number;
boxSizing: 'border-box' | 'content-box';
}

export interface CapsuleMessageLayout {
Expand All @@ -22,10 +25,11 @@ export interface CapsuleMessageLayout {

export function getCapsulePillMetrics(os: OS): CapsulePillMetrics {
if (os === 'win') {
return { width: 196, height: 52, textWidth: 104 };
// Windows metrics describe the visible outer footprint of the pill.
return { width: 196, height: 52, textWidth: 104, boxSizing: 'border-box' };
}

return { width: 176, height: 42, textWidth: 84 };
return { width: 176, height: 42, textWidth: 84, boxSizing: 'border-box' };
}

// macOS 走 1.2.11 calc 布局,不依赖 host metrics;Windows 端要更大的 host
Expand All @@ -35,9 +39,25 @@ export function getCapsuleHostMetrics(
translationActive: boolean,
): CapsuleHostMetrics {
if (os === 'win') {
return { width: 220, height: translationActive ? 118 : 84, bottomInset: 12, badgeGap: 8 };
const horizontalInset = 12;
const pill = getCapsulePillMetrics(os);
return {
width: pill.width + horizontalInset * 2,
height: translationActive ? 118 : 84,
horizontalInset,
bottomInset: 12,
badgeGap: 8,
boxSizing: 'border-box',
};
}
return { width: 176, height: 42, bottomInset: 0, badgeGap: 8 };
return {
width: 176,
height: 42,
horizontalInset: 0,
bottomInset: 0,
badgeGap: 8,
boxSizing: 'border-box',
};
}

export function getCapsuleMessageLayout(
Expand Down
Loading