#view #reactive

declarative

Generic DSL macros for easy view code manipulation

12 releases (6 breaking)

0.7.0 Apr 27, 2024
0.6.0 Oct 4, 2023
0.5.3 Jun 15, 2023
0.4.1 May 31, 2023
0.1.0 May 6, 2023

#223 in GUI

Apache-2.0 OR MIT

25KB
151 lines

declarative

REUSE status On crates.io

A proc-macro library that implements a generic DSL to create complex reactive view code easier to edit and maintain.

To use it, add to your Cargo.toml:

[dependencies.declarative]
version = '0.7.0'

To learn how to use macros, currently the best way is to clone the repository, read the source code of the examples in alphabetical order and run them like this:

cargo run --example EXAMPLE_NAME

The examples depend on gtk-rs, so you should familiarize yourself with gtk-rs a bit before:
https://gtk-rs.org/gtk4-rs/stable/latest/book/

In addition to macro features, the examples also show some usage patterns (templates, components, Elm, etc.). GTK has a pattern of its own due to its object orientation and declarative integrates well, but there is no example about it (it would be verbose and exclusive to GTK, while declarative is not GTK based).

Counter application example

The following is an implementation of the Elm architecture with gtk-rs:

Light theme app screenshot Dark theme app screenshot

use declarative::{block as view, clone, construct};
use gtk::{glib, prelude::*};

enum Msg { Increase, Decrease }

// syntactic sugar for sending messages:
macro_rules! send { [$msg:expr => $tx:expr] => [$tx.send_blocking($msg).unwrap()] }

fn start(app: &gtk::Application) {
    let (tx, rx) = async_channel::bounded(1);
    let mut count = 0; // the state

    view![ gtk::ApplicationWindow window {
        application: app
        title: "My Application"
        titlebar: &gtk::HeaderBar::new()

        child: &_ @ gtk::Box {
            orientation: gtk::Orientation::Vertical
            spacing: 6
            margin_top: 6
            margin_bottom: 6
            margin_start: 6
            margin_end: 6
            ~
            append: &_ @ gtk::Label {
                label: "Count unchanged"
                'bind set_label: &format!("The count is: {count}")
            }
            append: &_ @ gtk::Button {
                label: "Increase" ~
                connect_clicked: clone![tx; move |_| send!(Msg::Increase => tx)]
            }
            append: &_ @ gtk::Button::with_label("Decrease") {
                connect_clicked: move |_| send!(Msg::Decrease => tx)
            }
            'consume refresh = move |count| bindings!()
        }
    } ];

    let update = |count: &mut u8, msg| match msg {
        Msg::Increase => *count = count.wrapping_add(1),
        Msg::Decrease => *count = count.wrapping_sub(1),
    };

    glib::spawn_future_local(async move {
        while let Ok(msg) = rx.recv().await {
            update(&mut count, msg); // the state is updated
            refresh(count); // now the view is refreshed
        }
    });

    window.present()
}

fn main() -> glib::ExitCode {
    let app = gtk::Application::default();
    app.connect_activate(start);
    app.run()
}

To execute, run:

cargo run --example y_readme

Basic maintenance

The following commands must be executed and must not give any problems:

cargo check  -p declarative-macros
cargo clippy -p declarative-macros
cargo test   -p declarative-macros
cargo check
cargo clippy
cargo test
# and now run and check each example

If you need a changelog, maybe the commit log will help (the last ones try to have the most important details).


License

Licensed under either of Apache License, Version 2.0 (Apache-2.0.txt or http://www.apache.org/licenses/LICENSE-2.0) or MIT license (MIT.txt or http://opensource.org/licenses/MIT) at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~0.7–1.1MB
~24K SLoC