I'll try to write up some in the official docs but I'll write some here as well. It is a bit complicated but the complexity is what gives such high flexibility.
The style system is centered around a Style struct.
Style internally is just a hashmap (although one from the im crate so it is cheap to clone).
It maps from a StyleKey to Rc<dyn Any>.
StyleKeyholds a pointer (that is used as the hash value) to a static StyleKeyInfo enum which enumerates the different kinds of values that can be in the map.
Which value is in the StyleKeyInfo enum is used to know how to downcast the Rc<dyn Any.
The key types from the StyleKeyInfo are: (these are all of the different things that can be added to a Style).
- Transition,
- Prop(StylePropInfo),
- Selector(StyleSelectors),
- Class(StyleClassInfo),
- ContextMappings,
Transitions and context mappings don't hold any extra information, they are just used to know how to downcast the Rc<dyn Any>.
StyleSelectors is a bit mask of which selectors are active.
StyleClassInfo holds a function pointer that returns the name of the class as a String.
The function pointer is basically used as a vtable for the class.
If classes needed more methods other than name, those methods would be added to StyleClassInfo.
StylePropInfois another vtable, similar to StyleClassInfo and holds function pointers for getting the name of a prop, the props interpolation function from the StylePropValue trait, the associated transition key for the prop, and others.
Props store props.
Transitions store transition values.
Classes, context mappings, and selectors store nested Style maps.
A style can be applied to a view in two different ways.
A single Style can be added to the view_style method of the view trait or multiple Styles can be added by calling style on an IntoView from the Decorators trait.
Calls to style from the decorators trait have a higher precedence than the view_style method, meaning calls to style will override any matching StyleKeyInfo that came from the view_style method.
If you make repeated calls to style from the decorators trait, each will be added separately to the ViewState that is managed by Floem and associated with the ViewId of the view that style was called on.
The ViewState stores a Stack of styles and later calls to style (and thus larger indicies in the style stack) will take precedence over earlier calls.
style from the deocrators trait is reactive and the function that returns the style map with be re-run in response to any reactive updates that it depends on.
If it gets a reactive update, it will have tracked which index into the style stack it had when it was first called and will overrite that index and only that index so that other calls to style are not affected.
A final computed_style is resolved in the style_pass of the View trait.
It first received a Style map that is used as context.
The context is passed down the view tree and carries the inherited properties that were applied to any parent.
Inherited properties include all classes and any prop that has been marked as inherited.
The style first gets the Style (if any) from the view_style method.
Then it gets the style from any calls to style from the decorators trait.
It starts with the first index in the style Stack and applies each successive Style over the combination of any previous ones.
Then the style from the Decorators / ViewState is applied over (overriding any matching props) the style from view_style.
Then any classes that have been applied to the view, and the active selector set are used to resolve nested maps.
Nested maps such as classes and selectors are recursively applied, breadth first. So, deeper / more nested style maps take precendence.
This style map is the combined style of the View.
Finally, the context style is updated using the combined style, applying any style key that is inherited to the context so that the children will have acces to them.
The final computed style of a view will be passed to the style_pass method from the View trait.
Views will store fields that are struct that are prop extractors.
These structs are created using the prop_extractor! macro.
These structs can then be used from in the style_pass to extract props using the read (or read_exact) methods that are created by the prop_extractor macro.
The read methods will take in the combined style for that View and will automatically extract any matching prop values and transitions for those props.
If there is a transition for a prop, the extractor will keep track of the current time and transition state and will set the final extracted value to a properly interpolated value using the state and current time.
You can create custom style props with the style_prop! macro, classes with the style_class! macro, and extractors with the prop_extractor! macro.
You can create custom props.
Doing this allows you to store arbitrary values in the style system.
You can use these to style the view, change it's behavior, update it's state, or anything else.
By implementing the StylePropValue trait for your prop (which you must do) you can
-
optionally set how the prop should be interpolated (allowing you to customize what interpolating means in the context of your prop)
-
optionally provide a
debug_viewfor your prop, which debug view will be used in the Floem inspector. This means that you can customize a complex debug experience for your prop with very little effort (and it really can be any arbitrary view. no restrictions.) -
optionally add a custom implementation of how a prop should be combined with another prop. This is different from interpolation and is useful when you want to specify how properties should override each other. The default implementation just replaces the old value with a new value, but if you have a prop with multiple optional fields, you might want to only replace the fields that have a
Somevalue.
If you create a custom class, you can apply that class to any view, and when the final style for that view is being resolved, if the style has that class as a nested map, it will be applied, overriding any prviously set values.
You can create custom extractors and embed them in your custom views so that you can get out any built in prop, or any of your custom props from the final combined style that is applied to your View.