Expand description
Zng is a cross-platform GUI framework, it provides ready made highly customizable widgets, responsive layout, live data binding, easy localization, automatic focus navigation and accessibility, async and multi-threaded tasks, robust multi-process architecture and more.
Zng is pronounced “zing”, or as an initialism: ZNG (Z Nesting Graphics).
Every component of the framework can be extended, you can create new widgets or add properties to existing ones, at a lower level you can introduce new events and services, seamless integrating custom hardware.
§Usage
First add this to your Cargo.toml
:
[dependencies]
zng = { version = "0.12.9", features = ["view_prebuilt"] }
Then create your first app:
use zng::prelude::*;
fn main() {
zng::env::init!();
app();
}
fn app() {
APP.defaults().run_window(async {
Window! {
child_align = Align::CENTER;
child = {
let size = var(28i32);
Button! {
child = Text! {
txt = "Hello World!";
#[easing(200.ms())]
font_size = size.map_into();
};
on_click = hn!(|_| {
let next = size.get() + 10;
size.set(if next > 80 { 28 } else { next });
});
}
};
}
})
}
You can also use a prebuild view and run in the same process, see app
for more details.
§Widgets & Properties
The high-level building blocks of UI.
use zng::prelude::*;
Button! {
child = Text!("Green?");
widget::background_color = colors::GREEN;
on_click = hn!(|_| println!("SUPER GREEN!"));
}
In the example above Button!
and Text!
are widgets and child
, background_color
and on_click
are properties.
Widgets are mostly an aggregation of properties that define an specific function and presentation, most properties are standalone
implementations of an specific behavior or appearance, in the example only child
is implemented by the button widget, the
other two properties can be set in any widget.
Each widget is a dual macro and struct
of the same name, in the documentation only the struct
is visible, when
an struct represents a widget it is tagged with W
. Each properties is declared as a function,
in the documentation property functions are tagged with P
.
Widget instances can be of any type, usually they are an opaque impl UiNode
, some special widgets have an instance type,
the Window!
widget for example has the instance type WindowRoot
. Property instances are always of type impl UiNode
,
each property function takes an impl UiNode
input plus one or more value inputs and returns an impl UiNode
output that
wraps the input node adding the property behavior, the widgets take care of this node chaining nesting each property
instance in the proper order, internally every widget instance is a tree of nested node instances.
Widgets and properties are very versatile, each widget documentation page will promote the properties that the widget implementer explicitly associated with the widget, but that is only a starting point.
use zng::prelude::*;
Wgt! {
layout::align = layout::Align::CENTER;
layout::size = 50;
#[easing(200.ms())]
widget::background_color = colors::RED;
when *#gesture::is_hovered {
widget::background_color = colors::GREEN;
}
}
In the example above an Wgt!
is completely defined by stand-alone properties, align
and size
define
the bounds of the widget, background_color
fills the bounds with color and is_hovered
reacts to pointer interaction.
The example also introduces when
blocks, state properties and the easing
property attribute. State properties
compute an state from the widget, this state can be used to change the value of other properties. When blocks are a powerful
feature of widgets, they declare conditional property values. The easing attribute can be set in any property with transitionable
values to smoothly animate between changes.
The widget
module documentation provides an in-depth explanation of how widgets and properties work.
§Variables
Observable values that glue most of the UI together.
use zng::prelude::*;
let btn_pressed = var(false);
Stack! {
direction = StackDirection::top_to_bottom();
spacing = 10;
children = ui_vec![
Button! {
child = Text! { txt = "Press Me!"; };
gesture::is_pressed = btn_pressed.clone();
},
Text! {
txt = btn_pressed.map(|&b| {
if b {
"Button is pressed!"
} else {
"Button is not pressed."
}.into()
});
}
]
}
The example above binds the pressed state of a widget with the text content of another using a var
. Variables
are the most common property input kind, in the example direction
, spacing
, is_pressed
and txt
all accept
an IntoVar<T>
input that gets converted into a Var<T>
when the property is instantiated.
There are multiple variable types, they can be a simple static value, a shared observable and modifiable value or a contextual value. Variables can also depend on other variables automatically updating when input variables update.
use zng::prelude::*;
fn ui(txt: impl IntoVar<Txt>) -> impl UiNode {
Text!(txt)
}
ui("static value");
let txt = var(Txt::from("dynamic value"));
ui(txt.clone());
txt.set("change applied next update");
let show_txt = var(true);
ui(expr_var!(if *#{show_txt} { #{txt}.clone() } else { Txt::from("") }));
ui(text::FONT_COLOR_VAR.map(|s| formatx!("font color is {s}")));
In the example a var
clone is shared with the UI and a new value is scheduled for the next app update. Variable
updates are batched, during each app update pass every property can observe the current value and schedule modifications to
the value, the modifications are only applied after, potentially causing a new update pass if any value actually changed, see
[var updates] in the var module documentation for more details.
The example also demonstrates the expr_var!
, a read-only observable variable that interpolates other variables, the
value of this variable automatically update when any of the interpolated variables update.
And finally the example demonstrates a context var, FONT_COLOR_VAR
. Context variables get their value from the
environment where they are used, the UI in the example can show different a different text depending on where it is placed.
Context variables are usually encapsulated by properties strongly associated with a widget, most of Text!
properties just
set a context var that affects all text instances in the widget they are placed and descendant widgets.
There are other useful variable types, see the var module module documentation for more details.
§Context
Context or ambient values set on parent widgets affecting descendant widgets.
use zng::prelude::*;
Stack! {
direction = StackDirection::top_to_bottom();
spacing = 10;
text::font_color = colors::RED;
children = ui_vec![
Button! { child = Text!("Text 1"); },
Button! { child = Text!("Text 2"); },
Button! {
child = Text!("Text 3");
text::font_color = colors::GREEN;
},
];
}
In the example above “Text 1” and “Text 2” are rendered in red and “Text 3” is rendered in green. The context
of a widget is important, text::font_color
sets text color in the Stack!
widget and all descendant widgets,
the color is overridden in the third Button!
for the context of that button and descendants, the Text!
widget has a different appearance just by being in a different context.
Note that the text widget can also set the color directly, in the following example the “Text 4” is blue, this value is still contextual, but texts are usually leaf widgets so only the text is affected.
Text! {
txt = "Text 4";
font_color = colors::BLUE;
}
In the example above a context variable defines the text color, but not just variables are contextual, layout units and widget services are also contextual, widget implementers may declare custom contextual values too, see context local in the app module documentation for more details.
§Services
App or contextual value and function providers.
use zng::prelude::*;
use zng::clipboard::CLIPBOARD;
Stack! {
direction = StackDirection::top_to_bottom();
spacing = 10;
children = {
let txt = var(Txt::from(""));
let txt_is_err = var(false);
ui_vec![
Button! {
child = Text!("Paste");
on_click = hn!(txt, txt_is_err, |_| {
match CLIPBOARD.text() {
Ok(p) => if let Some(t) = p {
txt.set(t);
txt_is_err.set(false);
},
Err(e) => {
let t = WIDGET.trace_path();
txt.set(formatx!("error in {t}: {e}"));
txt_is_err.set(true);
}
}
});
},
Text! {
txt;
when *#{txt_is_err} {
font_color = colors::RED;
}
}
]
};
}
The example above uses two services, CLIPBOARD
and WIDGET
. Services are represented
by an unit struct named like a static item, service functionality is available as methods on
this unit struct. Services are contextual, CLIPBOARD
exists on the app context, it can only operate
in app threads, WIDGET
represents the current widget and can only be used inside a widget.
The default app provides multiple services, some common ones are APP
, WINDOWS
, WINDOW
, WIDGET
,
FOCUS
, POPUP
, DATA
and more. Services all follow the same pattern, they are a unit struct named like a static
item, if you see such a type it is a service.
Most services are synchronized with the update cycle. If the service provides a value that value does not change mid-update, all
widgets read the same value in the same update. If the service run some operation it takes requests to run the operation, the
requests are only applied after the current UI update. This is even true for the INSTANT
service that provides the current
time.
§Events & Commands
Targeted messages send from the system to widgets or from one widget to another.
use zng::{prelude::*, clipboard::{on_paste, CLIPBOARD, PASTE_CMD}};
APP.defaults().run_window(async {
let cmd = PASTE_CMD.scoped(WINDOW.id());
let paste_btn = Button! {
child = Text!(cmd.name());
widget::enabled = cmd.is_enabled();
widget::visibility = cmd.has_handlers().map_into();
tooltip = Tip!(Text!(cmd.name_with_shortcut()));
on_click = hn!(|args: &gesture::ClickArgs| {
args.propagation().stop();
cmd.notify();
});
};
let pasted_txt = var(Txt::from(""));
Window! {
on_paste = hn!(pasted_txt, |_| {
if let Some(t) = CLIPBOARD.text().ok().flatten() {
pasted_txt.set(t);
}
});
child = Stack! {
children_align = Align::CENTER;
direction = StackDirection::top_to_bottom();
spacing = 20;
children = ui_vec![paste_btn, Text!(pasted_txt)];
};
}
});
The example above uses events and command events. Events are represented by a static instance
of Event<A>
with name suffix _EVENT
. Events are usually abstracted by
one or more event property, event properties are named with prefix on_
and accept one input of
impl WidgetHandler<A>
. Commands are specialized events represented by a static instance of Command
with name suffix _CMD
. Every command is also an Event<CommandArgs>
, unlike other events it is common
for the command instance to be used directly.
The on_click
property handles the CLICK_EVENT
when the click was done with the primary button and targets
the widget or a descendant of the widget. The hn!
is a widget handler that synchronously handles the event.
See the event
module documentation for details about event propagation, targeting and route. And see
handler
module for other handler types, including async_hn!
that enables async .await
in any event property.
The example above defines a button for the PASTE_CMD
command scoped on the window. Scoped commands are different
instances of Command
, the command scope can be a window or widget ID, the scope is the target of the command and
the context of the command metadata. In the example the button is only visible if the command scope (window) has
a paste handler, the button is only enabled it at least one paste handler on the scope is enabled, the button also
displays the command name and shortcut metadata, and finally on click the button notifies a command event that is
received in on_click
.
Commands enable separation of concerns, the button in the example does not need to know what the window will do on paste,
in fact the button does not even need to know what command it is requesting. Widgets can also be controlled using commands,
the Scroll!
widget for example can be controlled from anywhere else in the app using the scroll::cmd
commands. See
the commands section in the event module documentation for more details.
§Layout
Contextual properties and constraints that affect how a widget is sized and placed on the screen.
use zng::prelude::*;
Container! {
layout::size = (400, 350);
widget::background_color = colors::BLUE.darken(70.pct());
child = Button! {
child = Text!("Text");
layout::align = layout::Align::CENTER;
layout::size = (60.pct(), 70.pct());
};
}
In the example above the container widget sets an exact size using layout::size
with exact units, the
button widget sets a relative size using percentage units and positions itself in the container using layout::align
.
All the layout properties are stand-alone, in the example only the text widget implements layout directly. Layout
properties modify the layout context by setting constraints and defining units, this context is available for all
properties that need it during layout, see the layout
module documentation for more details.
§Error Handling
Recoverable errors handled internally are logged using tracing
, in debug builds tracing events (info, warn and error)
are printed using app::print_tracing
by default if no tracing subscriber is set before the app starts building.
Components always attempt to recover from errors when possible, or at least attempt to contain errors and turn then into a displayable message. The general idea is to at least give the end user a chance to workaround the issue.
Components do not generally attempt to recover from panics, with some notable exceptions. The view-process will attempt to respawn
if it crashes, because all state is safe in the app-process all windows and frames can be recreated, this lets the app survive
some catastrophic video driver errors, like a forced disconnect caused by a driver update. The task::spawn
and related
fire-and-forget task runners will also just log the panic as an error.
The zng::app::crash_handler
is enabled by default, it collect panic backtraces, crash minidumps, show a crash dialog to the user
and restart the app. During development a debug crash dialog is provided, it shows the stdout/stderr, panics stacktrace and
minidumps collected if any non-panic fatal error happens. Note that the crash handler stops debuggers from working, see the
Debugger section of the crash-handler docs on how to automatically disable the crash handler for debugger runs.
§In-Depth Documentation
This crate level documentation only gives an overview required to start making apps using existing widgets and properties.
All top-level modules in this crate contains in-depth documentation about their subject, of particular importance the
app
, widget
, layout
and render
modules should give you a solid understanding of how everything works.
§Cargo Features
See the Cargo Features section in the crate README for Cargo features documentation.
Modules§
- Accessibility service, events and properties.
- ANSI text widget.
- App extensions, context, events and commands API.
- Button widget, styles and properties.
- Checkerboard visual widget.
- Clipboard service, commands and other types.
- Color and gradient types, functions, properties and macros.
- Config service, sources and other types.
- Container widget.
- Data context service and properties.
- Data view widgets and nodes.
- Modal dialog overlay widget and service.
- Process events, external directories and metadata.
- Event and command API.
- Focus service, properties, events and other types.
- Fonts service and text shaping.
- File system watcher service and other types.
- Gesture service, properties, events, shortcuts and other types.
- Grid layout widgets.
- Event handler API.
- Hot reloading instrumentation macros and service.
- Icons service, icon font widget and other types.
- Images service, widget and other types.
- Keyboard service, properties, events and other types.
- Localization service, sources and other types.
- Label widget and properties.
- Window layers.
- Layout service, units and other types.
- Markdown widget, properties and other types.
- Menu widgets, properties and other types.
- Mouse service, properties, events and other types.
- Panel layout widget.
- Pointer capture service, properties, events and other types.
- Popup widget and properties.
- Types for general app development.
- Prelude for declaring new properties and widgets.
- Progress indicator widget, styles and properties.
- Frame builder and other types.
- Rule line widgets and properties.
- Scroll widgets, commands and properties.
- Selectable text widget and properties.
- Slider widget, styles and properties.
- Stack layout widget, nodes and properties.
- Hash-map of type erased values, useful for storing assorted dynamic state.
- Style mix-in and other types.
- Parallel async tasks and async task runners.
- Text widget, properties and other types.
- Text input widget and properties.
- Third party licenses service and types.
- App timers service and other types.
- Tooltip properties and widget.
- Toggle button widget and styles for check box, combo box, radio button and switch button.
- Touch service, properties, events and other types.
- Undo service, commands and other types.
- App update service and other types.
- Variables API.
- View process implementations.
- Widget info, builder and base, UI node and list.
- Window service, widget, events, commands and other types.
- Wrap layout widget and properties.
Macros§
- Async clone move block.
- Async clone move closure.
- Async clone move closure that can only be called once.
- Clone move closure.
Structs§
- Start and manage an app process.