# `declarative`
[![REUSE status]][reuse] [![On crates.io]][crate.io]
[REUSE status]: https://api.reuse.software/badge/github.com/ejaa3/declarative
[reuse]: https://api.reuse.software/info/github.com/ejaa3/declarative
[On crates.io]: https://img.shields.io/crates/v/declarative.svg?color=6081D4
[crate.io]: https://crates.io/crates/declarative
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:
~~~ 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:
~~~ bash
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](light.png)
![Dark theme app screenshot](dark.png)
~~~ rust
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: >k::Application) {
let (tx, rx) = async_channel::bounded(1);
let mut count = 0; // the state
view![ gtk::ApplicationWindow window {
application: app
title: "My Application"
titlebar: >k::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:
~~~ bash
cargo run --example y_readme
~~~
## Basic maintenance
The following commands must be executed and must not give any problems:
~~~ bash
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.
[DSL]: https://en.wikipedia.org/wiki/Domain-specific_language
[gtk-rs]: https://gtk-rs.org