Files

12 KiB
Raw Permalink Blame History

Button 桌面版实现原理

本文档描述 Button 桌面版的内部实现组织、状态归一、模板接入和维护边界。公共设计与 API 契约见 Button 桌面版架构设计,Token 语义见 Button Token 设计,变化记录见 Button Changelog。

1. 实现定位

Button 的实现目标是把兼容 API、正交 Color + Variant API、图标尺寸、loading、shape、compact、wave 和自定义背景覆层归一为模板可消费的 stable state。实现文档只描述维护者必须理解的内部结构,不逐个复述私有方法。

Button.cs 保留公共属性、事件和接口实现入口;内部 helper 可以按职责拆分,但拆分不能改变 public API、模板契约、伪类或渲染结果。

2. 源码文件结构

主要源码:

  • src/AtomUI.Desktop.Controls/Buttons/Button.cs:Button public API、Avalonia 属性注册、effective state、伪类同步、CompactSpace / Form / Wave 接口实现。
  • src/AtomUI.Desktop.Controls/Buttons/ButtonToken.cs:Button 控件 Token 定义与派生。
  • src/AtomUI.Desktop.Controls/Buttons/Themes/ButtonTheme.axaml:Button 跨平台模板、状态 selector 和主题变量映射。
  • src/AtomUI.Desktop.Controls/Buttons/Themes/DropdownButtonBaseTheme.axaml、DropdownButtonTheme.axaml:DropdownButton 对 Button 图标尺寸和状态语义的跨平台投影。
  • src/AtomUI.Desktop.Controls/Buttons/Themes/IconButtonTheme.axaml:IconButton 默认主题叶子。
  • src/AtomUI.Desktop.Controls/Buttons/Themes/ButtonTheme.cs:主题资源注册辅助。
  • src/AtomUI.Controls/Buttons/ButtonPseudoClass.cs:共享 Button 伪类定义。
  • src/AtomUI.Controls/Buttons/Converters/ButtonIconVisibleConverter.cs:icon 可见性转换辅助。
  • src/AtomUI.Desktop.Controls/DropdownButton/DropdownButton.cs、src/AtomUI.Desktop.Controls/SplitButton/SplitButton.cs、src/AtomUI.Desktop.Controls/Buttons/IconButton.cs、src/AtomUI.Desktop.Controls/Buttons/HyperLinkButton.cs:Button 家族控件。

3. 核心类职责

Button 是状态归一中心。它同时承担 Avalonia Button 基类扩展、AtomUI 主题状态同步、CompactSpace 圆角和边框协同、Form 语义转发、Wave 几何和颜色感知。

ButtonToken 只提供组件级语义值,不保存实例状态。主题变量由 Button internal StyledProperty 承载,使 AXAML selector 和动态资源可以消费状态结果。Button.cs 是颜色状态矩阵的唯一计算所有者;Button 家族 ControlTheme 不得按平台、ButtonType、Danger 或 Ghost 复制一套颜色计算。

Button 家族控件复用 Button 的动作语义。派生控件可以替换模板或增加行为入口,但不得重新解释 ButtonType、Color、Variant、IsDanger、IsGhost、loading 和 disabled 语义。

4. 状态与数据流

Button 状态流:

ButtonType / IsDanger / IsGhost / IsLoading / Shape / SizeType / Icon
IconWidth / IconHeight / IconPlacement
Color? / Variant? / CustomBackground
      ↓
Normalize semantic state
      ↓
EffectiveColor / EffectiveVariant / EffectiveIsDanger / EffectiveIsGhost
EffectiveBorderThickness / EffectiveCornerRadius / WaveSpiritType
      ↓
Pseudo-classes + internal StyledProperty theme variables
      ↓
Shared ControlTheme visual projection

Color 与 Variant 为 nullable,是兼容优先级的关键。归一逻辑必须能区分“用户未设置正交 API”和“用户显式设置默认值”。

自定义背景只影响 normal 状态的视觉覆层可见性。它不参与颜色语义、文字色、边框色、阴影和 wave brush 计算。

兼容 API 与正交 API 使用同一条颜色路径。ButtonType=Text 归一为 Default + Text,保持中性文字语义;需要品牌色 Text 时使用 Primary + Text。两者都由 ConfigureVariantThemeVariables() 生成最终 normal、hover、pressed 主题变量, ControlTheme 只绑定这些变量。

尺寸状态由 SizeType 与 Button 现有布局属性共同决定。Large、Middle、Small 走预设 Token;Custom 走 Middle 默认值,并允许用户通过本地 Height、Padding、FontSize、CornerRadius、IconWidth、IconHeight 等属性覆盖。IconWidth 与 IconHeight 是独立的 Avalonia StyledProperty,允许非正方形尺寸;二者变化必须触发 Button 重新测量。实现不得为 Custom 增加 Button 专属 Custom* 尺寸属性或尺寸聚合对象。

5. 生命周期与模板接入

Button 在静态构造中注册属性、伪类和主题关联,在实例构造中完成需要的状态订阅。模板应用时读取稳定 template part,并把状态同步到视觉节点。

Button 与 DropdownButton 模板都必须让 PART_ButtonIcon 与 PART_LoadingIcon 通过 TemplateBinding 绑定 IconWidth、IconHeight。图标尺寸不需要在 OnApplyTemplate 中查找 part 后手工同步,也不允许由 Gallery 或应用样式通过深层模板 selector 写入;Button 自身属性是尺寸数据流的唯一入口。

维护顺序应遵守控件代码规范:

  1. 字段与常量。
  2. StyledProperty / DirectProperty / RoutedEvent 及其支持字段。
  3. CLR wrapper。
  4. 构造函数与静态构造函数。
  5. 公共方法与接口显式实现。
  6. protected 生命周期方法。
  7. private 状态同步和计算方法。

显示接口实现属于公共能力的低一级入口,应靠近公共方法区域,不应被散落到文件末尾的纯 private 实现区。

6. 交互与事件处理

Button 交互沿 Avalonia Button 基类处理点击、命令、键盘和 enabled 状态。AtomUI 只在状态同步层补充 loading、wave、icon-only、shape、compact 和 custom background 可见性。

Wave 播放条件必须同时考虑 IsWaveSpiritEnabled、IsMotionEnabled、disabled、loading 和 Button 语义状态。Danger 和预设色 wave brush 必须来自 effective semantic state,而不是从 CustomBackground 读取。

Loading 状态影响 loading icon、原 icon 可见性和交互反馈,但不应通过直接禁用控件来模拟。

7. 内部算法与关键流程

关键流程:

  • API 归一:显式 Color + Variant 优先,其次兼容 API 映射,再回退到默认语义。
  • 颜色解析:EffectiveColor 选择 Default、Primary、Danger 或 preset palette;EffectiveVariant 再把该颜色组映射为 Solid、Outlined、Dashed、Filled、Text 或 Link 的最终主题变量。
  • 视觉投影:Button 家族 ControlTheme 只消费 VariantText*、VariantBackground*、VariantBorder* 和 VariantShadow,不重复解释 ButtonType 或语义色阶。
  • 尺寸归一:Large、Middle、Small 映射到对应 Token;Custom 以 Middle Token 作为 Style 默认值,Button 本地尺寸属性保持更高优先级。
  • 图标尺寸:SizeType selector 把 IconWidth、IconHeight 默认映射到 IconSizeLG、IconSize、IconSizeSM;Custom 使用 IconSize。只有 :icononly:loading selector 把这两个默认值切换到对应 OnlyIconSize*,非 loading 的 icon-only 用户图标仍使用普通 IconSize*。
  • 伪类同步:当 public API、content、icon、loading、shape、enabled 或 compact 状态变化时同步模板可见状态。
  • 有效边框:由 Button 类型、variant、enabled、bordered 状态和 compact 状态共同决定。
  • 有效圆角:由 CornerRadius、Shape、SizeType 和 CompactSpace 位置共同决定。
  • 自定义背景:由 CustomBackground、EffectiveVariant、危险态和 enabled 状态决定覆层是否参与显示。
  • Wave 几何:Button 暴露 wave 所需边框和圆角,使 wave 与最终按钮边界一致。

这些流程必须保持 C# 层归一、AXAML 层消费的分工。不得把 API 优先级判断下沉到大量 AXAML selector 组合中。

维护 Custom 尺寸时应优先让主题默认值落在 Button 可覆盖的属性上,让模板内部尺寸节点通过 TemplateBinding 跟随 Button 属性。不得用模板内部固定高度阻断用户在 Button 上设置的本地 Height,也不得通过 /template/ selector 修改 PART_ButtonIcon 或 PART_LoadingIcon 的宽高。

8. 资源、性能与 AOT 边界

Button 主题变量使用 Avalonia 属性和动态资源,不使用反射读取模板状态。Token 资源由 ButtonToken scope 提供,并跟随主题切换。

IconWidth、IconHeight 使用 Avalonia 属性优先级完成 Theme 默认值与 LocalValue 的覆盖,不增加订阅、运行时 part 遍历或状态变化时的视觉对象创建。两个模板 part 共享同一对属性,因此 loading 切换只改变可见性和默认状态映射,不引入尺寸同步副本。

Custom background 覆层是现有模板内的一层视觉节点,启用时不应增加额外控件实例或重建模板。未设置 CustomBackground 时,覆层保持不可见,不应影响默认路径的命中测试、wave 或内容布局。

Button 实现不得引入运行时反射、动态代码生成或非 AOT 友好的资源查找路径。

9. 维护不变量

内部重构必须保持以下不变量:

  • public API、Avalonia 属性字段和 CLR wrapper 名称不变。
  • StyledProperty / DirectProperty / RoutedEvent 与支持字段的定义顺序符合控件代码规范。
  • Button.cs 保留公共属性、事件、方法和接口入口;实现拆分只承载内部逻辑。
  • Color + Variant 优先级和旧 API 映射结果不变。
  • ButtonType=Text 保持 Default + Text 中性语义;Primary + Text 跟随当前主题 ColorPrimary 色阶。
  • Button 家族主题必须共享 C# 计算出的最终颜色变量,不得按平台或兼容 API 复制颜色矩阵。
  • SizeType=Custom 不引入 Button 专属 Custom* 尺寸属性;未设置本地尺寸属性时表现等同 Middle,设置本地属性时由 Avalonia 属性优先级自然覆盖。
  • 主题不得以高于本地值的优先级写入 Custom 默认尺寸。
  • IconWidthProperty、IconHeightProperty 及其 CLR wrapper 是 Button 公共契约,属性变化必须参与 measure invalidation。
  • PART_ButtonIcon、PART_LoadingIcon 的 Width 和 Height 只能通过 TemplateBinding IconWidth/IconHeight 投影;外部样式不得深入模板覆盖尺寸。
  • 普通用户 icon 和非 loading 的 icon-only 用户 icon 保持 IconSize* 默认值;只有 icon-only loading 默认使用 OnlyIconSize*。
  • DropdownButton 继承同一图标尺寸属性与投影规则,OpenIndicator 继续由独立的 DropdownButton 主题尺寸控制;SplitButton 不纳入这一属性继承范围。
  • CustomBackgroundLayer 不成为用户可依赖 template part。
  • wave brush 不从 CustomBackground、模板背景或 hover 背景反推。
  • CompactSpace 圆角和边框折叠行为不变。
  • 同一 Button 家族主题资产必须在 Native 与 Browser 支持宿主下保持同一 API 语义;不得维护 Buttons/Themes/Browser/ 或 BrowserButtonThemes.axaml 形式的平台主题分叉。

10. 测试与验证

验证范围:

  • API 归一:覆盖 ButtonType、IsDanger、Color、Variant、IsGhost 的组合。
  • 尺寸契约:覆盖 SizeType=Large/Middle/Small/Custom;验证 Custom 默认等同 Middle,并验证本地 Height、Padding、FontSize、IconWidth、IconHeight 覆盖 Theme 默认值。
  • 状态同步:覆盖 disabled、loading、hover、pressed、icon-only、circle、round。
  • 图标投影:覆盖 Button、DropdownButton 模板的两个 part,并验证 Browser 注册使用同一套共享主题资产;验证普通 icon-only 仍使用 IconSize*、只有 icon-only loading 使用 OnlyIconSize*,并验证非正方形本地尺寸同时作用于用户 icon 和 loading icon。
  • Wave:覆盖危险态、预设色、custom background 与 disabled / loading 播放条件。
  • Theme:检查 default、primary、dashed、text、link、solid、outlined、filled、danger 和 custom background 视觉;Text 必须覆盖 Default、Primary、Danger 的 normal、hover、pressed 以及主题 Token 动态刷新。
  • 家族控件:检查 DropdownButton 继承图标尺寸 API 且不影响 OpenIndicator,SplitButton 保持复合控件边界,IconButton、HyperLinkButton 保持既有同名 API 语义。
  • 文档改动:运行 git diff --check。