cushy 0.4.0

A wgpu-powered graphical user interface (GUI) library with a reactive data model
Documentation
use std::process::exit;

use cushy::figures::units::Lp;
use cushy::value::{Dynamic, MapEach, Source};
use cushy::widget::{MakeWidget, MakeWidgetWithTag, WidgetTag};
use cushy::widgets::grid::{Grid, GridDimension, GridWidgets};
use cushy::widgets::input::{InputValue, MaskedString};
use cushy::widgets::Expand;
use cushy::Run;

/// This example is the same as login, but it has an explicit tab order to
/// change from the default order (username, password, cancel, log in) to
/// username, password, log in, cancel.
fn main() -> cushy::Result {
    let username = Dynamic::default();
    let password = Dynamic::default();

    let valid =
        (&username, &password).map_each(|(username, password)| validate(username, password));

    let (login_tag, login_id) = WidgetTag::new();
    let (cancel_tag, cancel_id) = WidgetTag::new();
    let (username_tag, username_id) = WidgetTag::new();

    let username_row = ("Username", username.to_input().make_with_tag(username_tag));

    let password_row = ("Password", password.to_input().with_next_focus(login_id));

    let buttons = "Cancel"
        .into_button()
        .on_click(|_| {
            eprintln!("Login cancelled");
            exit(0)
        })
        .make_with_tag(cancel_tag)
        .into_escape()
        .with_next_focus(username_id)
        .and(Expand::empty())
        .and(
            "Log In"
                .into_button()
                .on_click(move |_| {
                    println!("Welcome, {}", username.get());
                    exit(0);
                })
                .make_with_tag(login_tag)
                .with_enabled(valid)
                .into_default()
                .with_next_focus(cancel_id),
        )
        .into_columns();

    Grid::from_rows(GridWidgets::from(username_row).and(password_row))
        .dimensions([
            GridDimension::FitContent,
            GridDimension::Fractional { weight: 1 },
        ])
        .and(buttons)
        .into_rows()
        .contain()
        .width(Lp::points(300)..Lp::points(600))
        .scroll()
        .centered()
        .run()
}

#[allow(clippy::ptr_arg)] // Changing &String to &str breaks type inference
fn validate(username: &String, password: &MaskedString) -> bool {
    !username.is_empty() && !password.is_empty()
}