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:
LoveChengke committed 2026-09-12 17:41:05 +08:00
1 parent 9c9ac69ac6
commit 3f2b135211
17 files changed
+1632 -588

No files matched your search

+49 -4
View File
@@ -72,12 +72,17 @@ export const DEFAULT_ISLAND_APPEARANCE = {
width: 268,
radius: 20,
opacity: 1,
accent: '#6cc4ff',
/** 默认强调色采用 Apple 系统蓝(与 WinIsland 的系统色体系一致) */
accent: '#0a84ff',
fontSize: 13,
animations: true,
speed: 1,
position: 'top-center',
alwaysOnTop: true,
/** 纯黑底(WinIsland default 风格) */
style: 'black',
/** 空闲时完全隐藏(与原行为一致;打开后空闲会留一条细缝) */
idleSliver: false,
} as const;
/** 个性化设置项的合法区间(前端滑块与后端校验共用,避免越界导致布局错乱) */
@@ -86,19 +91,59 @@ export const ISLAND_APPEARANCE_RANGES = {
width: { min: 220, max: 420 },
radius: { min: 8, max: 32 },
opacity: { min: 0.4, max: 1 },
fontSize: { min: 11, max: 18 },
fontSize: { min: 11, max: 20 },
speed: { min: 0.5, max: 2 },
} as const;
/** 停靠位置可选值 */
export const ISLAND_POSITIONS = ['top-center', 'top-left', 'top-right', 'bottom-center'] as const;
/** 停靠位置可选值(与 WinIsland 的 DockPosition 一致:顶/底 × 左/中/右) */
export const ISLAND_POSITIONS = [
'top-center',
'top-left',
'top-right',
'bottom-center',
'bottom-left',
'bottom-right',
] as const;
export const ISLAND_POSITION_LABELS: Record<(typeof ISLAND_POSITIONS)[number], string> = {
'top-center': '顶部居中',
'top-left': '左上角',
'top-right': '右上角',
'bottom-center': '底部居中',
'bottom-left': '左下角',
'bottom-right': '右下角',
};
/** 视觉风格可选值(参考 WinIsland 的 island_style) */
export const ISLAND_STYLES = ['black', 'glass', 'tinted'] as const;
export const ISLAND_STYLE_LABELS: Record<(typeof ISLAND_STYLES)[number], string> = {
black: '纯黑(灵动岛默认)',
glass: '毛玻璃(半透明模糊)',
tinted: '主题色渐变',
};
/**
* 空闲细缝尺寸(参考 WinIsland hidden_width = 5:空闲态是一条很窄的圆角柱)。
* 宽度固定 6px,高度取胶囊高度的一部分,视觉上就是一条"小黑条"。
*/
export const ISLAND_SLIVER_WIDTH = 6;
/** 岛内文本的排版系数(相对基础字号,集中在这里方便统一调整) */
export const ISLAND_TYPE_SCALE = {
/** 展开卡标题 */
title: 1.08,
/** 正文 */
body: 0.95,
/** 次要说明 / 元信息 */
meta: 0.78,
/** 徽标 / 按钮 */
badge: 0.76,
/** 胶囊主标题 */
pillTitle: 0.92,
/** 胶囊副标题 */
pillSub: 0.74,
} as const;
/** 默认学期周次上限 */
export const DEFAULT_WEEK_COUNT = 20;
+24 -5
View File
@@ -415,26 +415,45 @@ export interface IslandAppearance {
height: number;
/** 胶囊宽度(px,220~420) */
width: number;
/** 卡片圆角(px,8~32) */
/** 卡片圆角(px,8~32):采用连续圆角(squircle),此值为四角的基准半径 */
radius: number;
/** 整体不透明度(0.4~1) */
opacity: number;
/** 主题色(强调色,用于高亮与按钮) */
accent: string;
/** 基础字号(px,11~18) */
/**
* 基础字号(px,11~20)——参考 WinIsland 的 font_size:
* 岛内**所有文本**都由它乘以各自的排版系数得出,因此改字号会整体等比缩放。
*/
fontSize: number;
/** 是否启用动画(关闭后展开/收起为瞬时) */
animations: boolean;
/** 动画速度倍率(0.5 慢 ~ 2 快) */
speed: number;
/** 显示位置 */
/** 显示位置(顶/底 × 左/中/右,与 WinIsland 的 DockPosition 对齐) */
position: IslandPosition;
/** 是否始终置顶 */
alwaysOnTop: boolean;
/**
* 视觉风格(参考 WinIsland 的 island_style):
* - `black`:纯黑底(默认,WinIsland `default` 风格)
* - `glass`:半透明 + 背景模糊(WinIsland `glass` 风格)
* - `tinted`:强调色渐变(本产品特色,保持与原主题色一致)
*/
style: IslandStyle;
/**
* 空闲时是否保留一条"细缝"(参考 WinIsland 的 hidden_width:空闲态是一条很窄的圆角柱)。
* 关闭时(默认)空闲即完全隐藏,与原行为一致。
*/
idleSliver: boolean;
}
/** 灵动岛停靠位置 */
export type IslandPosition = 'top-center' | 'top-left' | 'top-right' | 'bottom-center';
/** 灵动岛停靠位置(6 个锚点,与 WinIsland 一致) */
export type IslandPosition =
'top-center' | 'top-left' | 'top-right' | 'bottom-center' | 'bottom-left' | 'bottom-right';
/** 灵动岛视觉风格 */
export type IslandStyle = 'black' | 'glass' | 'tinted';
/** 灵动岛当前状态(主进程持有,渲染进程与冒烟测试读取) */
export interface IslandState {