= Relm
Asynchronous, GTK+-based, GUI library, inspired by Elm, written in Rust.
*Relm only works on nightly since it depends on the feature `conservative_impl_trait`.*
*This library is in alpha stage: it has not been thoroughly tested and its API may change at any time.*
image:https://img.shields.io/travis/antoyo/relm.svg[link="https://travis-ci.org/antoyo/relm"]
//image:https://img.shields.io/coveralls/antoyo/relm.svg[link="https://coveralls.io/github/antoyo/relm"]
image:https://img.shields.io/crates/v/relm.svg[link="https://crates.io/crates/relm"]
image:https://img.shields.io/badge/rust-documentation-blue.svg[link="https://docs.rs/relm/"]
image:https://img.shields.io/crates/d/relm.svg[link="https://crates.io/crates/relm"]
image:https://img.shields.io/crates/l/relm.svg[link="LICENSE"]
== Requirements
Since relm is based on GTK+, you need this library on your system in order to use it.
See http://gtk-rs.org/docs-src/requirements[this page] for information on how to install GTK+.
== Usage
First, add this to you `Cargo.toml`:
[source,bash]
----
relm = "0.4.0"
----
Next, add this to your crate:
[source,rust]
----
extern crate gtk;
#[macro_use]
extern crate relm;
#[macro_use]
extern crate relm_derive;
use relm::{Relm, RemoteRelm, Widget};
----
Then, create your model:
[source,rust]
----
#[derive(Clone)]
struct Model {
// …
}
----
The model contains the data related to a `Widget`. It may be updated by the `Widget::update` or the `Widget::update_command` function.
Create your message `enum`:
[source,rust]
----
#[derive(Msg)]
enum Msg {
// …
Quit,
}
----
Messages are sent to both `Widget::update` and `Widget::update_command` to indicate that an event happened. The model can be updated when an event is received.
Create a `struct` which represents a `Widget` which contains the GTK+ widgets (in this case, the main window of the application):
[source,rust]
----
struct Win {
// …
window: Window,
}
----
To make this `struct` a relm `Widget` that can be shown by the library, implement the `Widget` trait:
[source,rust]
----
impl Widget<Msg> for Win {
type Container = Window;
type Model = Model;
// Return the container of this widget.
fn container(&self) -> &Self::Container {
&self.window
}
// Return the initial model.
fn model() -> Model {
Model {
}
}
// The model may be updated when a message is received.
// Widgets may also be updated in this function.
fn update(&mut self, event: Msg, model: &mut Model) {
match event {
Quit => gtk::main_quit(),
}
}
// Create the widgets.
fn view(relm: RemoteRelm<Msg>, _model: &Self::Model) -> Self {
// GTK+ widgets are used normally within a `Widget`.
let window = Window::new(WindowType::Toplevel);
// Connect the signal `delete_event` to send the `Quit` message.
connect!(relm, window, connect_delete_event(_, _) (Some(Quit), Inhibit(false)));
// There is also a `connect!()` macro for GTK+ events that does not need a
// value to be returned in the callback.
window.show_all();
Win {
window: window,
}
}
// The next methods are optional.
// Futures and streams can be connected to send a message when a value is ready.
// However, since the tokio event loop runs in another thread, they cannot be
// connected in the `update` function which is ran in the main thread.
// Thus, they must be added in the `update_command()` method which is ran in
// the tokio thread.
// fn update_command(relm: &Relm<Msg>, event: Msg, model: &mut Model) {
// match event {
// SomeEvent => {
// let future = create_future();
// relm.connect_exec_ignore_err(future, SomeEvent);
// },
// }
// }
// Futures and streams can be connected when the `Widget` is created in the
// `subscriptions()` method.
// fn subscriptions(relm: &Relm<Msg>) {
// let stream = Interval::new(Duration::from_secs(1), relm.handle()).unwrap();
// relm.connect_exec_ignore_err(stream, Tick);
// }
}
----
Finally, show this `Widget` by calling `Relm::run()`:
[source,rust]
----
fn main() {
Relm::run::<Win>().unwrap();
}
----
=== `#[widget]` attribute
For the nightly users, a `#[widget]` attribute is provided to simplify the creation of a widget.
This attribute does the following:
* Provide a `view!` macro to create the widget with a declarative syntax.
* Automatically create the `fn container()`, `type Model` and `type Container` items.
* Automatically insert the call to `Widget::set_property()` in the `update()` function when assigning to an attribute of the model.
* Automatically create the `Widget` `struct`.
Here is an example using this attribute:
[source,rust]
----
#[widget]
impl Widget<Msg> for Win {
fn model() -> Model {
Model {
counter: 0,
}
}
fn update(&mut self, event: Msg, model: &mut Model) {
match event {
// A call to self.label1.set_text() is automatically inserted by the
// attribute every time the model.counter attribute is updated.
Decrement => model.counter -= 1,
Increment => model.counter += 1,
Quit => gtk::main_quit(),
}
}
view! {
gtk::Window {
gtk::Box {
orientation: Vertical,
gtk::Button {
// By default, an event with one paramater is assumed.
clicked => Increment,
// Hence, the previous line is equivalent to:
// clicked(_) => Increment,
label: "+",
},
gtk::Label {
// Bind the text property of this Label to the counter attribute
// of the model.
// Every time the counter attribute is updated, the text property
// will be updated too.
text: &model.counter.to_string(),
},
gtk::Button {
clicked => Decrement,
label: "-",
},
},
// Use a tuple when you want to both send a message and return a value to
// the GTK+ callback.
delete_event(_, _) => (Quit, Inhibit(false)),
}
}
}
----
NOTE: The `struct Win` is now automatically created by the attribute, as are the function `container()` and the types `Model` and `Container`.
You can still provide the method and the types if needed, but you cannot create the `struct`.
[WARNING]
====
Your program might be slower when using this attribute because the code generation is simple.
For instance, the following code
[source,rust]
----
fn update(&mut self, event: Msg, model: &mut Model) {
for _ in 0..100 {
model.counter += 1;
}
}
----
will generate this function:
[source,rust]
----
fn update(&mut self, event: Msg, model: &mut Model) {
for _ in 0..100 {
model.counter += 1;
self.label1.set_text(&model.counter.to_string());
}
}
----
====
[WARNING]
====
Also, the `set_property()` calls are currently only inserted when assigning to an attribute of the model.
For instance, the following code
[source,rust]
----
fn update(&mut self, event: Msg, model: &mut Model) {
model.text.push_str("Text");
}
----
will not work as expected.
Please use the following variation if needed.
[source,rust]
----
fn update(&mut self, event: Msg, model: &mut Model) {
model.text += "Text";
}
----
====
For more information about how you can use relm, you can take a look at the https://github.com/antoyo/relm/tree/master/examples[examples].