Resolver Layer Mechanism

Resolver Layer is Flor's mechanism for organizing view configuration. It allows view's layout and style to be written in segments, repeatedly, and overridden on demand, rather than requiring you to assemble all configuration into one block at once.

In usage code, .class(...), .layout(...), .style(...) all use this Layer mechanism. Each call to these builders can be understood as appending one layer configuration to view.

Layer's merge rule is simple:

SituationResult
Different layers wrote different propertiesThese properties will merge and be retained.
Different layers wrote same propertyRegardless of value same or different, the later written layer takes precedence.

That is, you can split class, layout, style to write multiple times, can also mix together. Later calls won't clear previous whole layer configuration; it will only override properties it rewrote.

Mixed Calling

For example first use class to write common layout, then use layout builder to precisely override one property:

use flor::taffy::Display;
use flor::view::builder::{ClassBuilder, LayoutBuilder};
use flor::view::resolver::LayoutResolverExt;
use flor::views;
use flor_lys::div::div;
use flor_lys::label::label;

let panel = div(views![label("Content")])
    .class("hidden p-4")
    .layout(|layout| layout.display(Display::Flex))
    .class("gap-3");

This code can be understood as three layers:

LayerWritten Content
.class("hidden p-4")Write display and padding.
`.layout(layout
.class("gap-3")Write gap.

Final result is: display uses later written Display::Flex, padding comes from p-4, gap comes from gap-3.

Same Property Override

When same property is written multiple times, last written one takes effect. Class is like this:

use flor::view::builder::ClassBuilder;
use flor_lys::label::label;

let item = label("Item")
    .class("p-2")
    .class("p-4");

Final padding is p-4.

Layout builder is also like this:

use flor::taffy::Display;
use flor::view::builder::LayoutBuilder;
use flor::view::resolver::LayoutResolverExt;
use flor_lys::label::label;

let item = label("Item")
    .layout(|layout| layout.display(Display::None))
    .layout(|layout| layout.display(Display::Flex));

Final display is Display::Flex.

Style builder is also same rule:

use flor::view::builder::{ClassBuilder, StyleBuilder};
use flor_lys::label::{label, LabelStyleResolverExt};

let title = label("Title")
    .class("text-lg")
    .style(|style| style.font_size(20.0));

If text-lg and font_size(20.0) both are setting font size, then final font size uses later written 20.0.

Different Property Merge

Later calls only override properties they rewrote. Properties not rewritten will continue to be retained:

use flor::macros::color;
use flor::view::builder::{ClassBuilder, StyleBuilder};
use flor_lys::label::{label, LabelStyleResolverExt};

let title = label("Title")
    .class("text-lg font-bold")
    .style(|style| style.text_color(color!("#2563eb")));

Here .class(...) wrote font size and font weight, .style(...) only wrote text color, so final will simultaneously retain font size, font weight and text color.

State Also Merges by Layer

State prefix is also same rule. Normal state, hover, focus, active, disabled will separately merge their own properties:

use flor::view::builder::ClassBuilder;
use flor_lys::label::label;

let item = label("Save")
    .class("text-slate-700 hover:text-blue-600 hover:font-bold")
    .class("hover:text-red-600");

Final normal state uses text-slate-700. In hover state, font-bold will be retained; text color is overridden by the later hover:text-red-600.

What's the Use

Layer mechanism's purpose is not to make writing more complex, but to let you split configuration to suitable positions.

You can first write a set of base configuration, then later re-specify certain properties:

let item = label("Item")
    .class("p-2 text-slate-700")
    .class("p-4");

Here later p-4 only overrides padding, text-slate-700 is still retained.

You can also hand a certain property individually to reactive signal control, rather than stuffing the entire class string or entire style group into if else concatenation:

use flor::signal::{create_signal, Read};
use flor::taffy::Display;
use flor::view::builder::{ClassBuilder, LayoutBuilder};
use flor::view::resolver::LayoutResolverExt;
use flor_lys::label::label;

let open = create_signal(true);

let panel = label("Details")
    .class("p-4 text-slate-700")
    .layout(move |layout| {
        if open.get() {
            layout.display(Display::Flex)
        } else {
            layout.display(Display::None)
        }
    });

This way, static configuration still writes in .class(...), only the truly changing display goes into reactive .layout(...). If putting all this content into one dynamic string, there will usually be repeated classes, complex branches and hard-to-maintain text concatenation.

Layer mechanism also supports higher-level view encapsulation: handlers can be passed as parameters, chain-configured views can be passed on as return values. Specifically how to wrap base views into composite views or business modules, see Framework DSL.

Maintaining end-user's usage experience is one of the framework's core goals. Layer mechanism is to let application code clearly express "which configurations are base values, which configurations override, which configurations respond to changes", thereby reducing code noise created for implementing interactive states.

Just remember one rule: different properties will merge, same property always uses later write overrides earlier write.