Resolver Derive Macro
#[derive(Resolver)] is Flor's style parsing derive macro. It automatically generates Resolver type, chain methods, state variant support and reactive update capability for style enums.
This macro has built-in view state mechanism, mainly used for view style system. It can work with class system, parse class names in on_update_class and update styles.
View authors only need to define style enum, derive macro will automatically generate all Resolver related code.
Minimal Usage
Define a style enum, add #[derive(Resolver)]:
use flor::view::resolver::Resolver;
use flor::types::Color;
#[derive(Clone, Debug, Resolver)]
pub enum LabelStyle {
TextColor(Color),
FontSize(f32),
}
Derive macro will automatically generate:
LabelStyleKey - Style property key enum
LabelStyleResolver - Resolver type alias
LabelStyleResolverExt trait - Chain methods (.text_color(...), .font_size(...))
LabelStyleComputed - Calculated style struct
LabelStyleUpdate - Reactive update enum
computed_label_style function - Maps raw style values to Computed struct
Generated Types
Key Enum
{EnumName}Key is style property key, used for Resolver internal lookup:
pub enum LabelStyleKey {
TextColor,
FontSize,
}
Resolver Type Alias
{EnumName}Resolver is complete Resolver type alias:
pub type LabelStyleResolver = Resolver<
LabelStyleKey,
LabelStyle,
LabelStyleComputed,
fn(&UnitResolver, &ResolverComputeMap<LabelStyleKey, LabelStyle>) -> LabelStyleComputed
>;
Computed Struct
{EnumName}Computed is calculated style struct, all fields are Option:
#[derive(Clone, Debug, Default)]
pub struct LabelStyleComputed {
pub text_color: Option<Color>,
pub font_size: Option<f32>,
}
Read computed struct when drawing:
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);
}
Chain Methods
{EnumName}ResolverExt trait provides chain methods for Resolver. Method name is variant name's snake_case form:
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;
}
Usage example:
let resolver = LabelStyleResolver::new(view_id)
.text_color(Color::RED)
.font_size(14.0);
State Variants
Resolver supports storing different style variants by ControlState. Switch current state through .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);
Read by current view state when drawing:
let style = resolver.get_data_clone(control_state);
State inheritance rules:
Hover inherits Normal styles, then covers Hover variants
Focus inherits Normal styles, then covers Focus variants
Active inherits Normal and Focus styles, then covers Active variants
Disabled doesn't inherit, only uses Disabled variants
StyleBuilder
Derive macro by default generates StyleBuilder trait implementation for view, allows application side to modify styles through .style(...) method:
impl StyleBuilder<LabelStyleResolver> for Label {
fn style(mut self, style_fn: impl Fn(LabelStyleResolver) -> LabelStyleResolver) -> Self {
self.style = style_fn(self.style);
self
}
}
Application side usage:
label("Title")
.style(|s| s.text_color(Color::RED).font_size(24.0));
.style(...) method receives a function, can use ResolverExt's chain methods to modify styles in function.
Update Enum
Derive macro by default generates {EnumName}Update enum and update_view method:
pub enum LabelStyleUpdate {
TextColor(ControlState, Color),
FontSize(ControlState, f32),
}
impl LabelStyle {
pub fn update_view<D, F>(
resolver: &mut LabelStyleResolver,
update: LabelStyleUpdate
) { ... }
}
Update enum contains update type for each style variant, update_view method will update corresponding state's style value in Resolver.
Configuration Parameters
Configure generated content through #[resolver(...)] attribute:
update_view = false
Skip Update enum and update_view method generation:
#[derive(Clone, Debug, Resolver)]
#[resolver(update_view = false)]
pub enum Layout {
Display(Display),
}
Suitable for style types not needing reactive update.
computed = false
Skip Computed struct generation:
#[derive(Clone, Debug, Resolver)]
#[resolver(computed = false, data = taffy::Style)]
pub enum Layout {
Display(Display),
}
Suitable for directly using external type (like taffy::Style) as calculation result.
computed_fn = false
Skip computed_xxx independent function generation. By default, derive macro generates a pub fn computed_{enum_snake_name}(...) function, mapping enum's raw style values to Computed struct:
// Automatically generated function (using LabelStyle as example)
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
}
This function will be passed into new_with_compute_func:
LabelStyleResolver::new_with_compute_func(view_id, computed_label_style)
When don't need to automatically generate compute function, can turn off:
#[derive(Clone, Debug, Resolver)]
#[resolver(computed_fn = false)]
pub enum MyStyle {
Value(f32),
}
Note: computed_fn = false usually paired with computed = false (like Layout), at this time neither generates Computed struct nor compute function. If only turn off computed_fn but keep computed, then need to manually write compute function.
data = Type
Specify Resolver's data type, generate type alias:
#[derive(Clone, Debug, Resolver)]
#[resolver(data = taffy::Style)]
pub enum Layout {
Display(Display),
}
Generated LayoutResolver will use taffy::Style as D generic.
builder = false
Skip StyleBuilder generation:
#[derive(Clone, Debug, Resolver)]
#[resolver(builder = false)]
pub enum InternalStyle {
Value(f32),
}
Suitable for internal style types, don't need to expose to application side builder.
default = false
Skip Resolver's Default implementation:
#[derive(Clone, Debug, Resolver)]
#[resolver(default = false)]
pub enum CustomStyle {
Value(f32),
}
Variant Attributes
skip_attr
Skip all generation for some variant:
#[derive(Clone, Debug, Resolver)]
pub enum Style {
Color(Color),
#[resolver(skip_attr)]
InternalDebug(String), // Don't generate Key, methods etc
}
skip_linkfn
Skip chain method generation, but keep Key and other content:
#[derive(Clone, Debug, Resolver)]
pub enum Layout {
#[resolver(skip_linkfn)]
Size(Size<Dimension>), // Don't generate .size() method
Display(Display),
}
Actual Usage in View
Following example shows Resolver's complete usage flow in view.
Constructor
Create Resolver when view constructs, can pass custom calculation function:
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,
// Use new_with_compute_func to pass calculation function
style: LabelStyleResolver::new_with_compute_func(view_id, computed_label_style),
}
}
}
computed_label_style function is automatically generated by derive macro, it will traverse all style variants and fill LabelStyleComputed.
on_measure
Read style by current view state when measuring:
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> {
// Get current state's style
let computed = self.style.get_data_borrow(control_state);
// Use style values for measurement
let font_size = computed.font_size.unwrap_or(16.0);
let font_family = computed.font_family.clone().unwrap_or_default();
// Create text format and measure
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 returns RwLock's mapping guard, avoids clone overhead. If need multiple accesses or cross-method passing, use get_data_clone.
on_draw
Read style by view state when drawing too:
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);
// Read style values, use unwrap_or to provide default values
let text_color = computed.text_color.unwrap_or(Color::BLACK);
let font_size = computed.font_size.unwrap_or(16.0);
// Use style to draw
let brush = render.create_solid_color_brush(text_color, None)?;
// ...
}
on_update_class
on_update_class is class system's entry. View parses class names and updates styles here:
fn on_update_class(&mut self, control_state: ControlState, class: &str) -> Result<(), Error> {
// 1. Switch to current state
self.style.switch_control_state(control_state);
// 2. Parse class name and set style
let class = class.trim();
if let Some(rest) = class.strip_prefix("text-") {
// text-red-500 -> set color
if let Some(color) = parse_color(rest) {
self.style.set_text_color(color);
}
// text-lg -> set font size
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 -> set background
if let Some(color) = parse_color(rest) {
self.style.set_background(Background::Color(color));
}
}
Ok(())
}
Key steps:
switch_control_state(control_state) - Switch Resolver to current state
set_xxx(...) - Use generated chain methods to set style values
Each class name match success directly returns, if not match then continue trying other rules.
on_update_state
Handle LabelStyleUpdate when reactive update:
fn on_update_state(&mut self, state: Box<dyn Any>) {
// Try to handle title update
if let Ok(title) = state.downcast::<String>() {
self.title = *title;
return;
}
// Try to handle style update
if let Ok(update) = state.downcast::<LabelStyleUpdate>() {
// Call macro-generated update_view method
LabelStyle::update_view(&mut self.style, *update);
}
}
LabelStyleUpdate is automatically generated by derive macro, contains update enum for each style variant. update_view method will update corresponding state's style value in Resolver.