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§
- Var animation types and functions.
Macros§
- Declares new
ContextVar
static items. - New variable from an expression with interpolated vars.
- Implements
T: IntoVar<U>
,T: IntoValue<U>
and optionallyU: From<T>
without boilerplate. - Initializes a new
Var
with value made by merging multiple other variables. - Initializes a new conditional var.
Structs§
- Manually build a
ArcWhenVar<T>
from type erased parts. - See
Var::cow
. - Arc value that implements equality by pointer comparison.
- Reference counted read/write variable.
- See
when_var!
. - Identifies the unique context a
ContextualizedVar
is in. - Represents another variable in a context.
- Represents a variable that delays initialization until the first usage.
- Represents a single value as
Var<T>
. - Build a merge-var from any number of input vars of the same type
I
. - Represents a
Vec<T>
that tracks changes when used inside a variable. - Arguments for a var event handler.
- See
Var::read_only
. - Args for
Var::trace_value
. - Variable updates and animation service.
- Kinds of interactions allowed by a
Var<T>
in the current update. - Handle to a variable hook.
- Represents a collection of var handles.
- Arguments for
Var::hook
. - Represents the current value in a
Var::modify
handler. - Represents an
AnyVar
pointer that can be used for comparison. - Represents the last time a variable was mutated or the current update cycle.
- Weak reference to a
ArcVar<T>
. - Weak var that upgrades to an uninitialized
ContextualizedVar<T, S>
. - Weak
ReadOnlyVar<T>
. - Weak reference to a
ArcWhenVar<T>
.
Enums§
- Raw value in a
ResponseVar
. - Represents a change in a
ObservableVec
.
Traits§
- Methods of
Var<T>
that are object safe. - Extension method to subscribe any widget to a variable.
- Trait implemented for all
VarValue
types. - Represents a weak reference to an
AnyVar
. - A property value that is not a variable but can be inspected.
- A value-to-var conversion that consumes the value.
- Represents an observable value.
- Extension methods to layout var values.
- Extension methods to subscribe any widget to a variable or app handlers to a variable.
- Represents a type that can be a
Var<T>
value. - Represents a weak reference to a
Var<T>
.
Functions§
- Variable for getter properties (
get_*
,actual_*
). - New
ResponseVar
in the done state. - New paired
ResponderVar
andResponseVar
in the waiting state. - Variable for state properties (
is_*
,has_*
). - New ref counted read/write variable with initial
value
. - New ref counted read/write variable with default initial value.
- New ref counted read/write variable with initial value converted from
source
.
Type Aliases§
- Represents a type erased boxed var.
- Represents a weak reference to a
BoxedAnyVar
. - Represents a
Var<T>
boxed. - Represents a weak reference to a
BoxedVar<T>
. - Read-only
ArcVar<T>
. - Context var that is always read-only, even if it is representing a read-write var.
- Variable used to notify the completion of an async operation.
- Variable used to listen to a one time signal that an async operation has completed.