Module var

Module var 

Source
Expand description

Variables API.

The Var<T> struct represents an observable value. The IntoVar<T> trait is the primary property input kind and the reason properties inputs are 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 kind is const_var, it represents an unchanging value that is shared by cloning. All values of types that implement VarValue automatically convert IntoVar<T> to const var, For this reason you don’t usually need to write const_var(_) when setting properties.

use zng::prelude::*;

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

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

The example above declares a const Var<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 var variable represents a shared value that can be modified.

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 = 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 value 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);
    });
}

§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> type represents a variable that has 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 IntoUiNode, foo: impl IntoVar<Txt>) -> UiNode {
    zng::widget::node::with_context_var(child, FOO_VAR, foo)
}

fn demo() -> 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§

animation
Var animation types and functions.

Macros§

context_var
Declares new ContextVar<T> static items.
expr_var
New variable from an expression with interpolated vars.
flat_expr_var
New variable from an expression with interpolated vars that produces another variable.
impl_from_and_into_var
Implements T: IntoVar<U>, T: IntoValue<U> and optionally U: From<T> without boilerplate.
merge_var
Initializes a new Var<T> with value made by merging multiple other variables.
when_var
Initializes a new conditional var.

Structs§

AnyVar
Variable of any type.
AnyWhenVarBuilder
Type erased when_var! manual builder.
ArcEq
Arc value that implements equality by pointer comparison.
BoxAnyVarValue
Small box for AnyVarValue values.
ContextInitHandle
Identifies the unique context a contextual_var is in.
ContextVar
Represents a named contextual variable.
MergeVarBuilder
Build a merge_var! from any number of input vars of the same type I.
ObservableVec
Represents a Vec<T> that tracks changes when used inside a variable.
OnVarArgs
Arguments for a var event handler.
ResponderVar
Represents a read-write variable used to notify the completion of an async operation.
ResponseVar
Represents a read-only variable used to listen to a one time signal that an async operation has completed.
VARS
Variable updates and animation service.
Var
Variable of type T.
VarCapability
Kinds of interactions allowed by a Var<T> in the current update.
VarEq
Represents a Var<T> as a value inside another variable.
VarHandle
Handle to a variable or animation hook.
VarHandles
Represents a collection of var handles.
VarHookArgs
Arguments for Var::hook.
VarInstanceTag
Unique identifier of a share variable, while it is alive.
VarModify
Mutable reference to a variable value.
VarUpdateId
Represents the last time a variable was mutated or the current update cycle.
WeakAnyVar
Weak reference to a AnyVar.
WeakVar
Weak reference to a Var<T>.

Enums§

Response
Raw value in a ResponseVar.
VecChange
Represents a change in a ObservableVec.

Traits§

AnyVarSubscribe
Extension method to subscribe any widget to a variable.
AnyVarValue
Represents any variable value.
IntoValue
A property value that is not a variable but can be inspected.
IntoVar
A value-to-var conversion that consumes the value.
VarLayout
Extension methods to layout var values.
VarSubscribe
Extension methods to subscribe any widget to a variable or app handlers to a variable.
VarValue
Represents a type that can be a Var<T> value.

Functions§

any_var_derived
New read/write shared reference type-erased variable that has initial value derived from source.
const_var
New immutable variable that stores the value directly.
contextual_var
Create a contextualized variable.
response_done_var
New ResponseVar in the done state.
response_var
New paired ResponderVar and ResponseVar in the waiting state.
var
New read/write shared reference variable.
var_default
New read/write shared reference variable with default initial value.
var_from
New read/write shared reference variable from any type that can convert into it.
var_getter
Variable for getter properties (get_*, actual_*).
var_state
Variable for state properties (is_*, has_*).