Files
LanMountainDesktop/docs/component-corner-radius-unification-plan.txt

305 lines
13 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
LanMountainDesktop 组件圆角统一方案
一、我对项目的理解
- 这是一个基于 .NET 10 + Avalonia UI 的跨平台桌面宿主项目。
- 核心形态是“组件化桌面信息看板”:组件可放置到桌面,可编辑、可缩放,既支持自由缩放,也支持等比例缩放。
- 当前圆角体系本身并不是空白:项目已经有统一 token 和全局圆角倍率。
- 圆角 token 生成LanMountainDesktop.Appearance/AppearanceCornerRadiusTokenFactory.cs
- 动态注入资源LanMountainDesktop/Services/AppearanceThemeService.cs
- 基础 token 资源LanMountainDesktop/Styles/GlassModule.axaml
- 宿主组件圆角助手LanMountainDesktop/Views/Components/ComponentChromeCornerRadiusHelper.cs
- 插件圆角上下文LanMountainDesktop.PluginSdk/PluginAppearanceContext.cs
二、为什么现在“设置里同样是 1.0,但每个组件看起来不一样”
根因不是一个,而是几层逻辑叠加造成的。
1. 内置组件和插件组件默认算法不同
- 内置组件宿主默认走 Component token18 * GlobalCornerRadiusScale。
- 入口LanMountainDesktop/Views/Components/DesktopComponentRuntimeRegistry.cs
- 默认 resolverComponentChromeCornerRadiusHelper.ResolveMainRectangleRadiusValue(...)
- 插件组件默认走基于 cellSize 的动态算法:
- LanMountainDesktop.PluginSdk/PluginDesktopComponentRegistration.cs
- 当前默认逻辑约等于Math.Clamp(cellSize * 0.22, 8, 18) * scale
- 这意味着:同样设置为 1.0,内置组件趋向固定圆角,插件组件趋向“随格子大小变化”。
- 结果:用户感知到“同样设置值,但不同组件圆角不一样”。
2. 可见外壳并没有真正统一由宿主接管
- MainWindow.ComponentSystem.cs 里宿主会给 host/contentHost 设置圆角。
- 但大量组件内部还有自己的 RootBorder / CardBorder / 内层卡片,并且也各自设置圆角。
- 一旦“宿主外壳 + 组件根层 + 组件内部卡片”同时存在,用户最终看到的是最内层那个可见边界,而不一定是宿主设的边界。
- 典型现象:
- 某些组件外层 Border 是透明的,真正可见的是里面第二层 CardBorder。
- 某些组件直接在代码里写死 new CornerRadius(...)。
3. 组件内部仍有硬编码圆角
- 例如:
- DailyNewsView.axaml.cs 有 new CornerRadius(4)
- CnrDailyNewsWidget.axaml 有 CornerRadius="21"
- DesktopComponentFailureView.cs 有 12 / 18 / 999 等固定值
- 这些值本身不一定错,但如果它们承担的是“组件主外轮廓”,就会破坏统一性。
4. 等比例缩放组件大量使用 Viewbox视觉边界和内容边界不是一回事
- 例如 LunarCalendarWidget.axaml、DateWidget.axaml、MonthCalendarWidget.axaml、AnalogClockWidget.axaml 等都使用 Viewbox Stretch="Uniform"。
- 这类组件通常是:外层 Border 固定圆角,内部设计稿按 300x300 或类似基准缩放。
- 如果另一些组件是自由布局、自由撑开、非 Viewbox 驱动,那么即便外层半径数值相同,视觉上也会显得不一样。
- 原因是:内容密度、留白、边缘贴合程度不同,会显著影响人眼对圆角大小的判断。
5. 宿主的 visualInset 也在影响观感
- MainWindow.ComponentSystem.cs 里的 GetDesktopComponentVisualInset(...) 会根据组件宽高格子数改变内缩量。
- 宿主目前是“命中范围/编辑范围一套,实际可见内容再缩进去一层”。
- 当不同尺寸组件有不同 inset而组件自己又有独立圆角时视觉上就更容易出现“同 18 看起来不像同 18”的问题。
三、统一方案的核心原则
原则只有一句:
“组件主外轮廓的圆角,只能有一个最终权威来源。”
建议把圆角分成三层:
1. 组件外壳圆角(主轮廓)
2. 组件内部区域圆角(二级卡片)
3. 微元素圆角按钮、标签、图片卡片、chip
其中,只有第 1 层决定“这个组件作为桌面卡片整体看起来有多圆”。
四、推荐的统一规则
A. 统一“组件主外壳圆角”
推荐规则:
- 所有桌面组件,不区分内置/插件,不区分自由缩放/等比缩放,默认主外壳统一使用:
OuterRadius = CornerRadiusTokens.Component
- 也就是当前 token 体系里的 Component 档。
- 在 1.0 设置下,就是 18。
- 这个值只受全局圆角倍率影响,不再受 cellSize 直接影响。
推荐最终规则:
- 标准情况:
OuterRadius = tokens.Component
- 极小组件兜底(仅防止尺寸过小导致圆角挤爆):
OuterRadius = Min(tokens.Component, Min(actualWidth, actualHeight) * 0.18)
- 但这个“极小兜底”只在组件物理尺寸不够时触发;正常组件应保持完全一致。
这条规则的意义:
- 用户调到 1.0,所有组件都会首先落在同一个视觉档位。
- 只有非常小的组件才会被动缩小圆角,避免失真。
- 这样既统一,又不会在边缘尺寸下破版。
B. 插件默认算法必须改成和宿主一致
当前插件默认算法是按 cellSize 算的,这是造成不一致的最直接原因之一。
建议修改:
- 把 PluginDesktopComponentRegistration.cs 里的默认逻辑,从:
appearance.ResolveScaledCornerRadius(Math.Clamp(cellSize * 0.22, 8, 18), 8, 18)
- 改为:
appearance.ResolveCornerRadius(PluginCornerRadiusPreset.Component)
含义:
- 插件如果没有特别声明,就跟内置组件一样,默认使用 Component 档主外壳圆角。
- 只有插件作者明确声明特殊需求时,才允许自定义 resolver。
C. 宿主要成为“唯一外壳提供者”
这是最重要的一条。
建议新增统一壳层,例如:
- DesktopComponentShell
- DesktopComponentChrome
职责:
- 负责组件真正可见的最外层背景、边框、裁剪、主圆角
- 负责统一 padding / glass / border / shadow
- 负责选中态、拖拽态、编辑态的视觉装饰
- 组件内容只负责“内容”,不再负责“卡片主轮廓”
理想结构:
- Host Border / Shell = 真正的可见外轮廓
- Component Root = 尽量透明,不再自己承担主卡片圆角
- Inner Sections = 使用 Sm / Xs / Micro 等 token
也就是说:
- 主外轮廓只在壳层定义一次
- 组件内部不再重复定义一个同等级 RootBorder 当作主卡片
D. 统一内部层级,不要所有层都用 Component
建议把当前 token 真正分层使用:
- Component桌面组件主外壳
- Sm内部小卡片、图片区、内容区块
- Xs按钮、输入框、chip、小容器
- Micro极小 badge / 标签
- Island / Xl / Lg只给岛状栏位、大面板、设置窗口不用于普通桌面组件
落地约束:
- 桌面组件主根层禁止再写 DesignCornerRadiusLg / Xl / Island
- 组件主根层禁止使用硬编码 21 / 16 / 24 / 30 之类值
- 子卡片允许用 Sm / Xs但不能与主壳争夺“主轮廓”角色
E. 等比例缩放与自由缩放分别处理,但外圆角规则相同
1等比例缩放组件Viewbox
- 外壳圆角固定由宿主提供,不参与 Viewbox 缩放。
- Viewbox 只缩放内部设计稿。
- 外壳与内部设计稿之间保留统一“安全留白”。
建议:
- SafeInset = Max(8, OuterRadius * 0.45)
- 对所有 Viewbox 类组件,外层容器 padding 使用统一公式,而不是每个组件自己猜。
2自由缩放组件
- 外壳圆角仍固定由宿主提供。
- 自由缩放只影响内容布局、字号、图表密度、行数和间距,不影响主外壳半径。
- 内容内部若要变化优先变化padding、gap、字号、图标尺寸而不是外圆角。
这样做的结果:
- 缩放方式不同,但最外层的“卡片家族感”一致。
- 用户调的是“整体风格圆角”,不是“每个组件自己的数学公式”。
F. 把 visualInset 从“影响圆角观感”变成“只影响编辑/命中逻辑”
当前 GetDesktopComponentVisualInset(...) 会让不同大小组件看起来边界不同。
建议二选一:
1. 更推荐:
- 让宿主 shell 成为真实可见边界
- visualInset 只服务拖拽/选中/吸附逻辑,不再改变真实可见主卡片的边界层次
2. 如果暂时不重构:
- 把 visualInset 改成固定档位,而不是随 widthCells / heightCells 持续变化
- 例如统一为 6 或 8 的 token 化 inset
目标:
- 组件整体轮廓不要再因为跨度不同而“看起来圆角不同”
五、具体改造建议(按代码位置)
1. 插件默认圆角统一
文件LanMountainDesktop.PluginSdk/PluginDesktopComponentRegistration.cs
建议:
- 默认从“基于 cellSize 动态计算”改成“直接取 Component preset”。
2. 宿主外壳统一
文件LanMountainDesktop/Views/MainWindow.ComponentSystem.cs
重点方法:
- CreateDesktopComponentHost(...)
- GetComponentCornerRadius(...)
- GetDesktopComponentVisualInset(...)
建议:
- 让 contentHost 或新建 shell 成为真实可见主边界
- 背景、边框、裁剪、圆角统一放在 shell
- host 仅保留命中、拖拽、选中装饰
- visualInset 不再影响真实主卡片外观,最多影响编辑态附加层
3. 统一运行时默认 resolver
文件LanMountainDesktop/Views/Components/DesktopComponentRuntimeRegistry.cs
建议:
- 保留 DefaultCornerRadiusResolver 走 Component token
- 新增统一 chrome metrics 输出,供组件内部使用:
- OuterRadius
- InnerRadiusSm
- InnerRadiusXs
- SafeInset
- GlobalCornerRadiusScale
4. 扩展 chrome 上下文
文件:
- LanMountainDesktop.Host.Abstractions/ComponentChromeContext.cs
- LanMountainDesktop/Services/IComponentLibraryService.cs
- LanMountainDesktop.PluginSdk/PluginAppearanceSnapshot.cs如需要
建议新增字段:
- ResolvedOuterCornerRadius
- SafeInset
- ChromePadding
- VisualDensity 或 SizeClass可选
目的:
- 组件不要自己猜“我应该用多少主圆角”
- 它只消费宿主已算好的结果
5. 清理组件内部主轮廓重复定义
重点检查目录:
- LanMountainDesktop/Views/Components/
优先整改对象:
- CnrDailyNewsWidget.axamlRootBorder + CardBorder 双层主轮廓)
- BrowserWidget.axaml.cs内部多层主卡片都取主圆角
- DailyNewsView.axaml / .cs有硬编码子元素圆角
- DesktopComponentFailureView.cs硬编码 12 / 18 / 999
- 使用 Viewbox 的日期、月历、农历、计时器、录音等组件
整改原则:
- 组件根层如果已经处于宿主 shell 内,应尽量透明化
- 只保留内部结构性圆角,不再重复承担主卡片角色
六、我建议采用的“统一视觉规范”
1. 桌面组件主外壳
- 统一Component = 18 * scale
- 默认 1.0 时全部按 18 展示
- 仅极小尺寸时做 min(actualShortSide * 0.18) 兜底
2. 内部板块
- 统一Sm = 14 * scale
- 用于图片区、内嵌卡片、列表块、概览块
3. 交互控件
- 统一Xs = 12 * scale
- 用于普通按钮、输入框、小标签
4. 胶囊按钮 / 圆按钮
- 不走主卡片规则
- 仍允许 half-height例如 32 高就 16 半径)
- 这是合法特例,因为它们不是“桌面组件主外轮廓”
5. 大面板 / 岛 / 设置窗口
- 保持 Lg / Xl / Island
- 不要向桌面普通组件借用这些档位
七、建议的落地步骤
第一阶段:统一规则,不大改组件
- 修改插件默认圆角算法,使插件先与内置组件对齐
- 明确“主外壳 = Component token”这个规范
- 先把所有失败态、默认态、插件兜底视图改成统一档位
- 清理明显硬编码的主轮廓圆角
第二阶段:建立统一 shell
- 抽出 DesktopComponentShell / DesktopComponentChrome
- 宿主统一负责外壳、背景、裁剪、边框、选中态
- 组件内部改成内容优先,外轮廓透明化
第三阶段:分批迁移内置组件
推荐顺序:
- 新闻资讯类
- 日期/日历/时钟类
- 天气类
- 浏览器/复杂卡片类
- 失败态/占位态/插件样例
第四阶段:插件规范升级
- 在 SDK 文档里新增说明:
- 插件主外壳不要自行写死圆角
- 默认使用宿主 Component preset
- 若必须特殊化,说明理由并走显式 CornerRadiusResolver
八、验收标准
改完之后,至少要满足这 5 条:
1. 设置里圆角倍率调成 1.0 时,所有组件主外轮廓处于同一视觉档位。
2. 内置组件与插件组件默认情况下看起来是一套语言。
3. 等比例缩放与自由缩放组件虽然内容布局不同,但卡片外壳风格一致。
4. 组件内部还可以有小圆角层级,但不会再和主外壳打架。
5. 用户只需要理解“全局圆角倍率”,不需要理解不同组件背后的不同公式。
九、我对你这个项目最推荐的最终结论
最推荐的方案不是“继续微调每个组件自己的圆角公式”,而是:
- 用宿主统一接管组件主外壳
- 插件默认改成和宿主同一算法
- 把圆角分成 主外壳 / 内部区块 / 微元素 三层
- 让缩放影响内容,不再影响主外壳圆角
一句话总结:
“统一的不是每个 Border 的数字,而是组件最外层轮廓的控制权。”
十、如果你要我继续往下做,最值得优先做的 3 件事
1. 我先帮你列一份“当前所有组件圆角不统一点位清单(按文件逐个标出来)”
2. 我再给你出一版“可直接改代码的重构方案”,包括新增 DesktopComponentShell 的接口设计
3. 如果你愿意,我可以直接开始改第一批基础代码,把内置组件和插件默认圆角先统一起来