Resolver 派生宏

#[derive(Resolver)] 是 Flor 提供的样式解析派生宏。它为样式枚举自动生成 Resolver 类型、链式方法、状态变体支持和响应式更新能力。

这个宏内置控件状态机制,主要用于控件的样式系统。它可以与 class 系统 配合使用,在 on_update_class 中解析类名并更新样式。

控件作者只需要定义样式枚举,派生宏会自动生成所有 Resolver 相关代码。

最简用法

定义一个样式枚举,添加 #[derive(Resolver)]

use flor::view::resolver::Resolver;
use flor::types::Color;

#[derive(Clone, Debug, Resolver)]
pub enum LabelStyle {
    TextColor(Color),
    FontSize(f32),
}

派生宏会自动生成:

  • LabelStyleKey - 样式属性键枚举
  • LabelStyleResolver - Resolver 类型别名
  • LabelStyleResolverExt trait - 链式方法(.text_color(...).font_size(...)
  • LabelStyleComputed - 计算后的样式结构体
  • LabelStyleUpdate - 响应式更新枚举
  • computed_label_style 函数 - 将原始样式值映射到 Computed 结构体

生成的类型

Key 枚举

{EnumName}Key 是样式属性键,用于 Resolver 内部查找:

pub enum LabelStyleKey {
    TextColor,
    FontSize,
}

Resolver 类型别名

{EnumName}Resolver 是完整的 Resolver 类型别名:

pub type LabelStyleResolver = Resolver<
    LabelStyleKey,
    LabelStyle,
    LabelStyleComputed,
    fn(&UnitResolver, &ResolverComputeMap<LabelStyleKey, LabelStyle>) -> LabelStyleComputed
>;

Computed 结构体

{EnumName}Computed 是计算后的样式结构体,所有字段都是 Option

#[derive(Clone, Debug, Default)]
pub struct LabelStyleComputed {
    pub text_color: Option<Color>,
    pub font_size: Option<f32>,
}

绘制时读取 computed 结构体:

fn on_draw(&mut self, ...) {
    let style = self.style_resolver.get_data_clone(ControlState::Normal);
    let color = style.text_color.unwrap_or(Color::BLACK);
    let size = style.font_size.unwrap_or(16.0);
}

链式方法

{EnumName}ResolverExt trait 为 Resolver 提供链式方法。方法名是变体名的 snake_case 形式:

pub trait LabelStyleResolverExt {
    fn text_color(self, value: Color) -> Self;
    fn font_size(self, value: f32) -> Self;
    fn set_text_color(&mut self, value: Color) -> &mut Self;
    fn set_font_size(&mut self, value: f32) -> &mut Self;
}

使用示例:

let resolver = LabelStyleResolver::new(view_id)
    .text_color(Color::RED)
    .font_size(14.0);

状态变体

Resolver 支持按 ControlState 存储不同样式变体。通过 .normal().hover().focus().active().disabled() 切换当前状态:

let resolver = LabelStyleResolver::new(view_id)
    .normal()
    .text_color(Color::BLACK)
    .font_size(16.0)
    .hover()
    .text_color(Color::BLUE)
    .focus()
    .text_color(Color::RED);

绘制时按当前控件状态读取:

let style = resolver.get_data_clone(control_state);

状态继承规则:

  • Hover 继承 Normal 的样式,再覆盖 Hover 变体
  • Focus 继承 Normal 的样式,再覆盖 Focus 变体
  • Active 继承 NormalFocus 的样式,再覆盖 Active 变体
  • Disabled 不继承,只使用 Disabled 变体

StyleBuilder

派生宏默认为控件生成 StyleBuilder trait 实现,允许应用侧通过 .style(...) 方法修改样式:

impl StyleBuilder<LabelStyleResolver> for Label {
    fn style(mut self, style_fn: impl Fn(LabelStyleResolver) -> LabelStyleResolver) -> Self {
        self.style = style_fn(self.style);
        self
    }
}

应用侧使用:

label("标题")
    .style(|s| s.text_color(Color::RED).font_size(24.0));

.style(...) 方法接收一个函数,函数里可以使用 ResolverExt 的链式方法修改样式。

Update 枚举

派生宏默认生成 {EnumName}Update 枚举和 update_view 方法:

pub enum LabelStyleUpdate {
    TextColor(ControlState, Color),
    FontSize(ControlState, f32),
}

impl LabelStyle {
    pub fn update_view<D, F>(
        resolver: &mut LabelStyleResolver,
        update: LabelStyleUpdate
    ) { ... }
}

Update 枚举包含每个样式变体的更新类型,update_view 方法会更新 Resolver 中对应状态的样式值。

配置参数

通过 #[resolver(...)] 属性配置生成内容:

update_view = false

跳过 Update 枚举和 update_view 方法生成:

#[derive(Clone, Debug, Resolver)]
#[resolver(update_view = false)]
pub enum Layout {
    Display(Display),
}

适用于不需要响应式更新的样式类型。

computed = false

跳过 Computed 结构体生成:

#[derive(Clone, Debug, Resolver)]
#[resolver(computed = false, data = taffy::Style)]
pub enum Layout {
    Display(Display),
}

适用于直接使用外部类型(如 taffy::Style)作为计算结果的场景。

computed_fn = false

跳过 computed_xxx 独立函数生成。默认情况下,派生宏会生成一个 pub fn computed_{enum_snake_name}(...) 函数,将枚举的原始样式值映射到 Computed 结构体:

// 自动生成的函数(以 LabelStyle 为例)
pub fn computed_label_style(
    _unit_resolver: &UnitResolver,
    variants: &ResolverComputeMap<LabelStyleKey, LabelStyle>,
) -> LabelStyleComputed {
    let mut computed = LabelStyleComputed::default();
    for (k, v) in variants.iter() {
        match k {
            LabelStyleKey::TextColor => {
                if let LabelStyle::TextColor(val) = v {
                    computed.text_color = Some(val.clone());
                }
            }
            LabelStyleKey::FontSize => {
                if let LabelStyle::FontSize(val) = v {
                    computed.font_size = Some(val.clone());
                }
            }
            _ => {}
        }
    }
    computed
}

这个函数会被传入 new_with_compute_func

LabelStyleResolver::new_with_compute_func(view_id, computed_label_style)

当不需要自动生成 compute 函数时,可以关闭:

#[derive(Clone, Debug, Resolver)]
#[resolver(computed_fn = false)]
pub enum MyStyle {
    Value(f32),
}

注意computed_fn = false 通常搭配 computed = false 使用(如 Layout),此时既不生成 Computed 结构体,也不生成 compute 函数。如果只关闭 computed_fn 但保留 computed,则需要手动编写 compute 函数。

data = Type

指定 Resolver 的数据类型,生成类型别名:

#[derive(Clone, Debug, Resolver)]
#[resolver(data = taffy::Style)]
pub enum Layout {
    Display(Display),
}

生成的 LayoutResolver 会使用 taffy::Style 作为 D 泛型。

builder = false

跳过 StyleBuilder 生成:

#[derive(Clone, Debug, Resolver)]
#[resolver(builder = false)]
pub enum InternalStyle {
    Value(f32),
}

适用于内部样式类型,不需要暴露给应用侧 builder。

default = false

跳过 Resolver 的 Default 实现:

#[derive(Clone, Debug, Resolver)]
#[resolver(default = false)]
pub enum CustomStyle {
    Value(f32),
}

变体属性

skip_attr

跳过某个变体的所有生成:

#[derive(Clone, Debug, Resolver)]
pub enum Style {
    Color(Color),
    #[resolver(skip_attr)]
    InternalDebug(String),  // 不生成 Key、方法等
}

skip_linkfn

跳过链式方法生成,但保留 Key 和其他内容:

#[derive(Clone, Debug, Resolver)]
pub enum Layout {
    #[resolver(skip_linkfn)]
    Size(Size<Dimension>),  // 不生成 .size() 方法
    Display(Display),
}

控件中的实际使用

以下示例展示 Resolver 在控件中的完整使用流程。

构造函数

控件构造时创建 Resolver,可以传入自定义计算函数:

use flor::view::ViewId;
use flor::view::resolver::LabelStyleResolver;

pub struct Label {
    view_id: ViewId,
    title: String,
    style: LabelStyleResolver,
}

impl Label {
    pub fn new(title: String) -> Self {
        let view_id = ViewId::new();

        Self {
            view_id,
            title,
            // 使用 new_with_compute_func 传入计算函数
            style: LabelStyleResolver::new_with_compute_func(view_id, computed_label_style),
        }
    }
}

computed_label_style 函数由派生宏自动生成,它会遍历所有样式变体并填充 LabelStyleComputed

on_measure

测量时按当前控件状态读取样式:

fn on_measure(
    &mut self,
    known_dimensions: Size<Option<f32>>,
    available_space: Size<AvailableSpace>,
    _style: &Style,
    control_state: ControlState,
    render: &mut FlorRenderer,
) -> Result<Size<f32>, Error> {
    // 获取当前状态的样式
    let computed = self.style.get_data_borrow(control_state);

    // 使用样式值进行测量
    let font_size = computed.font_size.unwrap_or(16.0);
    let font_family = computed.font_family.clone().unwrap_or_default();

    // 创建文本格式并测量
    let text_format = render.create_text_format(&font_family)?;
    text_format.set_font_size(font_size);

    let layout_text = render.create_text_layout(self.title.clone(), bounds, text_format)?;
    let (width, height) = layout_text.measure_text()?;

    Ok(Size { width, height })
}

get_data_borrow 返回 RwLock 的映射守卫,避免克隆开销。如果需要多次访问或跨方法传递,使用 get_data_clone

on_draw

绘制时同样按控件状态读取样式:

fn on_draw(
    &mut self,
    render: &mut FlorRenderer,
    control_state: ControlState,
    abs_location: (f32, f32),
    layout: ComputedLayout,
) -> Result<(), Error> {
    let computed = self.style.get_data_borrow(control_state);

    // 读取样式值,使用 unwrap_or 提供默认值
    let text_color = computed.text_color.unwrap_or(Color::BLACK);
    let font_size = computed.font_size.unwrap_or(16.0);

    // 使用样式绘制
    let brush = render.create_solid_color_brush(text_color, None)?;
    // ...
}

on_update_class

on_update_class 是 class 系统的入口。控件在这里解析类名并更新样式:

fn on_update_class(&mut self, control_state: ControlState, class: &str) -> Result<(), Error> {
    // 1. 切换到当前状态
    self.style.switch_control_state(control_state);

    // 2. 解析类名并设置样式
    let class = class.trim();

    if let Some(rest) = class.strip_prefix("text-") {
        // text-red-500 -> 设置颜色
        if let Some(color) = parse_color(rest) {
            self.style.set_text_color(color);
        }
        // text-lg -> 设置字号
        if let Some(size) = self.style.unit_resolver.parse_tw_font_size(rest) {
            self.style.set_font_size(size);
        }
    }

    if let Some(rest) = class.strip_prefix("bg-") {
        // bg-blue-500 -> 设置背景
        if let Some(color) = parse_color(rest) {
            self.style.set_background(Background::Color(color));
        }
    }

    Ok(())
}

关键步骤:

  1. switch_control_state(control_state) - 切换 Resolver 到当前状态
  2. set_xxx(...) - 使用生成的链式方法设置样式值

每个类名匹配成功后直接返回,不匹配则继续尝试其他规则。

on_update_state

响应式更新时处理 LabelStyleUpdate

fn on_update_state(&mut self, state: Box<dyn Any>) {
    // 尝试处理标题更新
    if let Ok(title) = state.downcast::<String>() {
        self.title = *title;
        return;
    }

    // 尝试处理样式更新
    if let Ok(update) = state.downcast::<LabelStyleUpdate>() {
        // 调用宏生成的 update_view 方法
        LabelStyle::update_view(&mut self.style, *update);
    }
}

LabelStyleUpdate 由派生宏自动生成,包含每个样式变体的更新枚举。update_view 方法会更新 Resolver 中对应状态的样式值。