Crate zng

source ·
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§

Structs§

  • Start and manage an app process.