feat(island): 参考 WinIsland 重写灵动岛(squircle + 三种底 + 字号系数排版 + 空闲细缝)
参考: 工作目录 WinIsland-master.zip(Rust/Direct2D 的 Windows 动态岛),
逐项提取的设计 token 归档在 docs/winisland-design-tokens.md。
视觉与造型:
- 连续圆角 squircle(新增 island/squircle.ts,超椭圆角,对齐 WinIsland utils/shape.rs),
胶囊半径 = min(圆角, h/2)(满圆角),展开卡 = min(48*圆角/20, w/2, h/2)(WinIsland expanded_island_radius)
- 三种底: 纯黑(WinIsland default)/ 毛玻璃(win.setBackgroundMaterial('acrylic'),等价 HostBackdropBrush)/
主题色渐变;描边 1px 白 alpha .118(纯黑)/.157(玻璃),展开投影 0 2px 3px rgba(0,0,0,.11)
- Apple 系统色: 强调蓝 #0a84ff、警告橙 #ff9f0a、危险红 #ff453a;白字 alpha 分级 .92/.72/.58
- 状态色只做"内层微染 + 描边 + 徽标",不外溢(保持外圈偏红 = 0)
排版(本次重点):
- 基础字号 11–20px,岛内**所有文本** = 基础字号 × 排版系数
(标题 1.08 / 正文 0.95 / 次要 0.78 / 徽标与按钮 0.76 / 胶囊标题 0.92 / 胶囊副题 0.74),
与 WinIsland 的 font_size 模型一致;实测 13→19px 全部文本倍率 1.462(=19/13)
- 图标、按钮高度、间距也由字号推导,改字号不会溢出
形态与设置:
- 尺寸基准对齐 WinIsland 比例(胶囊 216x34 基准、展开 +156/+186 等),卡片 = 窗口 - 2*5px 且水平居中
- 6 个停靠位(顶/底 × 左/中/右,对齐 WinIsland DockPosition)
- 新增"空闲细缝"(WinIsland hidden_width):空闲留一条 6px 宽竖条,来消息自动展开
- 所有设置依旧"改即生效";顺带修掉 hide() 用 skipWindow 绕过 syncWindow 导致细缝不生效的问题
验证(verify:desktop 49 → 53 项,全绿):
- 字号即时生效: 13px→19px 五项文本倍率全部 1.462
- 风格即时生效: 纯黑 fill=rgb(0,0,0)/材质 none;毛玻璃 fill=rgba(10,10,14,.59)/材质 acrylic;主题色 fill=rgb(11,11,15)
- 空闲细缝: 16x36 → 来消息 268x44;关闭后完全隐藏
- 6 锚点、开合不震动(0.00px / 无过冲)、截图 6 张(比例偏差 0.000、外圈偏红 0)全部保持
- verify:e2e 133/133、verify:web 19/19 不受影响;截图归档到 docs/screenshots/island/
工程发现: Windows 对透明窗口有约 36px 最小高度(请求 32px 实际 36px),
空闲细缝高度据此设下限并写入注释与 README。
This commit is contained in:
1 parent
9c9ac69ac6
commit
3f2b135211
17 files changed
+1632
-588
No files matched your search
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* 连续圆角(squircle / Apple 连续曲率圆角)路径生成。
|
||||
*
|
||||
* 参考实现:WinIsland `src/utils/shape.rs` —— 它用八分体(Octant)+ 超椭圆参数表
|
||||
* 构造 `continuous_rounded_rect_path`,而不是普通圆弧圆角;展开卡的半径取
|
||||
* `expanded_island_radius = min(48 * scale, w/2, h/2)`。
|
||||
*
|
||||
* 这里用等价的超椭圆角公式生成 SVG path:
|
||||
* 角点区域按 x = r * (1 - cos^p(t)), y = r * (1 - sin^p(t)) 采样,
|
||||
* 其中 p = 2/n、n 取 4.2 时最接近 Apple 的"连续曲率"观感(普通圆角相当于 n = 2)。
|
||||
* 直边部分保持完全笔直,因此与 WinIsland 的"直边 + 连续圆角"一致。
|
||||
*
|
||||
* 生成的是**像素坐标**路径(w/h 为卡片实际像素尺寸),所以圆角半径恒定、不被拉伸。
|
||||
*/
|
||||
|
||||
/** 超椭圆指数:2 = 标准圆弧圆角,越大越"方"但曲率越连续(Apple 观感约 4~5) */
|
||||
const SUPERELLIPSE_EXPONENT = 4.2;
|
||||
|
||||
/** 每个角的采样段数(越多越平滑;4 个角共 4x 段) */
|
||||
const CORNER_SEGMENTS = 14;
|
||||
|
||||
function cornerPoints(radius: number): { x: number; y: number }[] {
|
||||
const p = 2 / SUPERELLIPSE_EXPONENT;
|
||||
const points: { x: number; y: number }[] = [];
|
||||
for (let index = 0; index <= CORNER_SEGMENTS; index += 1) {
|
||||
const t = (index / CORNER_SEGMENTS) * (Math.PI / 2);
|
||||
const cos = Math.max(0, Math.cos(t));
|
||||
const sin = Math.max(0, Math.sin(t));
|
||||
// t=0 → 角外侧(贴直边),t=π/2 → 角内侧
|
||||
points.push({
|
||||
x: radius * (1 - Math.pow(cos, p)),
|
||||
y: radius * (1 - Math.pow(sin, p)),
|
||||
});
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
const round = (value: number): number => Math.round(value * 100) / 100;
|
||||
|
||||
/**
|
||||
* 生成连续圆角矩形路径。
|
||||
*
|
||||
* @param width 卡片宽度(px)
|
||||
* @param height 卡片高度(px)
|
||||
* @param radius 四角基准半径(px,会被裁剪到 min(w/2, h/2))
|
||||
*/
|
||||
export function squirclePath(width: number, height: number, radius: number): string {
|
||||
const w = Math.max(1, Math.round(width * 100) / 100);
|
||||
const h = Math.max(1, Math.round(height * 100) / 100);
|
||||
const r = Math.max(0, Math.min(radius, w / 2, h / 2));
|
||||
|
||||
if (r <= 0.5) return `M0,0 H${round(w)} V${round(h)} H0 Z`;
|
||||
|
||||
const corner = cornerPoints(r);
|
||||
const parts: string[] = [];
|
||||
|
||||
// 从左上角的"上方直边起点"开始,顺时针绘制
|
||||
parts.push(`M${round(r)},0`);
|
||||
// 上边 → 右上角
|
||||
parts.push(`H${round(w - r)}`);
|
||||
for (const point of corner) parts.push(`L${round(w - r + point.x)},${round(point.y)}`);
|
||||
// 右边 → 右下角
|
||||
parts.push(`V${round(h - r)}`);
|
||||
for (const point of corner) parts.push(`L${round(w - point.y)},${round(h - r + point.x)}`);
|
||||
// 下边 → 左下角
|
||||
parts.push(`H${round(r)}`);
|
||||
for (const point of corner) parts.push(`L${round(r - point.x)},${round(h - point.y)}`);
|
||||
// 左边 → 左上角
|
||||
parts.push(`V${round(r)}`);
|
||||
for (const point of corner) parts.push(`L${round(point.y)},${round(r - point.x)}`);
|
||||
parts.push('Z');
|
||||
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
/** 展开卡半径(与 WinIsland 的 expanded_island_radius 同构:min(48*scale, w/2, h/2)) */
|
||||
export function expandedRadius(width: number, height: number, radius: number): number {
|
||||
return Math.max(0, Math.min(Math.max(radius, 24), width / 2, height / 2));
|
||||
}
|
||||
Reference in new issue
Block a user