zng

Module var

source
Expand description

Variables API.

The Var<T> trait represents an observable value. The IntoVar<T> trait is the primary property input kind and the reason setting properties is so versatile. Variables can be a simple value, a shared reference to a value or a contextual value, some variables are also derived from others and update when the source variable update.

Properties and widgets can subscribe to a variable to update when the variable value changes, this features enables most of the dynamic UI behavior, from binding one widget to another to animation.

§Value

The simplest variable is LocalVar<T>, it represents an unchanging value that is shared by cloning. All values of types that implement VarValue automatically convert IntoVar<T> to this variable type. For this reason you don’t usually need to write it.

use zng::prelude::*;

fn local(size: impl IntoVar<layout::Size>) {
    let size = size.into_var();
    assert!(size.capabilities().is_always_static());
    assert!(size.capabilities().is_always_read_only());
}

local(layout::Size::new(10, 10));
local((10, 10));
local(10);

The example above declares a LocalVar<Size> 3 times with equal value. The (10, 10) and 10 values are type conversions implemented by the Size type. Type conversions are very easy to implement with the help of the impl_from_and_into_var! macro, most of the types used by properties implement conversions that enable a form of shorthand syntax.

§Share & Modify

The ArcVar<T> variable represents a shared value that can be modified, the var function instantiates it.

The example below declares a button that grows taller every click. The variable is shared between the height property and the click handler. On click the height is increased, this schedules an update that applies the new value and notifies all subscribers.

use zng::prelude::*;

let height = var(2.em());
Button! {
    child = Text!("Taller!");
    on_click = hn!(height, |_| { // clone `height` reference for the handler.
        height.set(height.get() + 10); // request an update to a new value.
    });
    layout::align = layout::Align::CENTER;
    layout::height; // set the height (shorthand, variable is same name as property)
}

Note that variable updates don’t happen immediately, in the handler above the variable is still the previous value after the set call, this is done so that all widgets in a single update react to the same value. The variable values is updated at the end of the current update.

use zng::prelude::*;

let number = var(0u8);
Button! {
    child = Text!("Test");
    on_click = async_hn!(number, |_| {
        assert_eq!(number.get(), 0);
        number.set(1);
        assert_eq!(number.get(), 0);

        task::yield_now().await;
        assert_eq!(number.get(), 1);
    });
}

The example above demonstrates the delayed update of a variable.

If multiple widgets set the same variable on the same update only the last value set will be used, widgets update in parallel by default so it is difficult to predict who is the last. The modify method can be used register a closure that can modify the value, this closure will observe the partially updated value that may already be modified by other widgets.

The example below demonstrates how the modify closure observes a value that was just set in the same update cycle.

use zng::prelude::*;

let foo = var(0u8);
Wgt! {
    widget::on_init = async_hn!(foo, |_| {
        foo.set(1);
        assert_eq!(0, foo.get());
        foo.modify(|m| {
            assert_eq!(1, **m);
            *m.to_mut() = 2;
        });
        assert_eq!(0, foo.get());

        foo.wait_update().await;
        assert_eq!(2, foo.get());

        println!("test ok");
    });
}

§Mapping

Variables can be mapped to other types, when the source variable updates the mapping closure re-evaluates and the mapped variable updates, all in the same update cycle, that is both variable will be flagged new at the same time. Mapping can also be bidirectional.

The example below demonstrates a button that updates an integer variable that is mapped to a text.

use zng::prelude::*;

let count = var(0u32);
Button! {
    child = Text!(count.map(|i| match i {
        0 => Txt::from("Click Me!"),
        1 => Txt::from("Clicked 1 time!"),
        n => formatx!("Clicked {n} times!"),
    }));
    on_click = hn!(|_| {
        count.set(count.get() + 1);
    });
}

Variable mapping is specialized for each variable type, a LocalVar<T> will just map once and return another LocalVar<T> for example, the ArcVar<T> on the example creates a new variable and a mapping binding.

§Binding

Two existing variables can be bound, such that one variable update sets the other. The example below rewrites the mapping demo to use a bind_map instead.

use zng::prelude::*;

let count = var(0u32);
let label = var(Txt::from("Click Me!"));
count
    .bind_map(&label, |i| match i {
        1 => Txt::from("Clicked 1 time!"),
        n => formatx!("Clicked {n} times!"),
    })
    .perm();
Button! {
    child = Text!(label);
    on_click = hn!(|_| {
        count.set(count.get() + 1);
    });
}

Note that unlike a map the initial value of the output variable is not updated, only subsequent ones. You can use set_from to update the initial value too.

§Animating

Animation is implemented using variables, at the lowest level VARS.animate is used to register a closure to be called every frame, the closure can set any number of variables, at a higher level the Var::ease and Var::chase methods can be used to animate the value of a variable.

The example below uses Var::easing to animate the window background:

use zng::prelude::*;

let color = var(colors::AZURE.darken(30.pct()));
Window! {
    widget::background_color = color.easing(500.ms(), easing::linear);
    child = Button! {
        layout::align = layout::Align::TOP;
        on_click = hn!(|_|{
            let mut c = color::Hsla::from(color.get());
            c.hue += 60.0;
            color.set(c);
        });
        child = Text!("Change background color");
    }
}

Variables can only be operated by a single animation, when a newer animation or modify affects a variable older animations can no longer affect it, see VARS.animate for more details.

§Response

The ResponseVar<T> is a specialized variable that represents the result of an async task. You can use .await directly in any async handler, but a response var lets you plug a query directly into a property. You can use task::respond to convert any future into a response var, and you can use wait_rsp to convert a response var to a future.

use zng::prelude::*;

let rsp = task::respond(async {
    let url = "https://raw.githubusercontent.com/git/git-scm.com/main/MIT-LICENSE.txt";
    match task::http::get_txt(url).await {
        Ok(t) => t,
        Err(e) => formatx!("{e}"),
    }
});
SelectableText!(rsp.map(|r| {
    use zng::var::Response::*;
    match r {
        Waiting => Txt::from("loading.."),
        Done(t) => t.clone(),
    }
}))

The example above creates a response var from a download future and maps the response to a widget.

A response var is paired with a ResponderVar<T>, you can create a response channel using the response_var function.

§Merge

The merge_var! and expr_var! macros can be used to declare a variable that merges multiple other variable values.

The example below demonstrates the two macros.

use zng::prelude::*;

let a = var(10u32);
let b = var(1u32);

// let merge = expr_var!({
//     let a = *#{a};
//     let b = *#{b.clone()};
//     formatx!("{a} + {b} = {}", a + b)
// });
let merge = merge_var!(a, b.clone(), |&a, &b| {
    formatx!("{a} + {b} = {}", a + b)
});
Button! {
    child = Text!(merge);
    on_click = hn!(|_| b.set(b.get() + 1));
}

§Contextual

The ContextVar<T> variable represents a context depend value, meaning they can produce a different value depending on where they are used. Context vars are declared using the context_var! macro.

The example below declares a context var and a property that sets it. The context var is then used in two texts with two different contexts, the first text will show “Text!”, the second will show “Stack!”.

use zng::prelude::*;

context_var! {
    static FOO_VAR: Txt = "";
}

#[zng::widget::property(CONTEXT, default(FOO_VAR))]
pub fn foo(child: impl UiNode, foo: impl IntoVar<Txt>) -> impl UiNode {
    zng::widget::node::with_context_var(child, FOO_VAR, foo)
}

fn demo() -> impl UiNode {
    Stack! {
        direction = StackDirection::top_to_bottom();
        spacing = 5;
        foo = "Stack!";
        children = ui_vec![
            Text! {
                txt = FOO_VAR;
                foo = "Text!";
            },
            Text!(FOO_VAR),
        ]
    }
}

Context variables have all the same capabilities of other variables if the example if foo is set to a var the context var will be editable, and if FOO_VAR is mapped the mapping variable is also contextual.

§Full API

See zng_var for the full var API.

Modules§

Macros§

Structs§

Enums§

Traits§

Functions§

Type Aliases§