Git Product home page Git Product logo

relm's Introduction

Relm

Asynchronous, GTK+-based, GUI library, inspired by Elm, written in Rust.

This library is in beta stage: it has not been thoroughly tested and its API may change at any time.

CI Relm Tutorial blueviolet relm rust documentation blue relm relm:matrix relm Donate Patreon orange

Requirements

Since relm is based on GTK+, you need this library on your system in order to use it.

See this page for information on how to install GTK+.

Usage

First, add this to your Cargo.toml:

gtk = "^0.16.0"
relm = "^0.24.0"
relm-derive = "^0.24.0"

Next, add this to your crate:

use relm::{connect, Relm, Update, Widget};
use gtk::prelude::*;
use gtk::{Window, Inhibit, WindowType};
use relm_derive::Msg;

Then, create your model:

struct Model {
    // …
}

The model contains the data related to a Widget. It may be updated by the Widget::update function.

Create your message enum:

#[derive(Msg)]
enum Msg {
    // …
    Quit,
}

Messages are sent to Widget::update 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) and the model:

struct Win {
    // …
    model: Model,
    window: Window,
}

To make this struct a relm Widget that can be shown by the library, implement the Update and Widget traits:

impl Update for Win {
    // Specify the model used for this widget.
    type Model = Model;
    // Specify the model parameter used to init the model.
    type ModelParam = ();
    // Specify the type of the messages sent to the update function.
    type Msg = Msg;

    // Return the initial model.
    fn model(_: &Relm<Self>, _: ()) -> 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) {
        match event {
            Msg::Quit => gtk::main_quit(),
        }
    }
}

impl Widget for Win {
    // Specify the type of the root widget.
    type Root = Window;

    // Return the root widget.
    fn root(&self) -> Self::Root {
        self.window.clone()
    }

    // Create the widgets.
    fn view(relm: &Relm<Self>, 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(_, _), return (Some(Msg::Quit), Inhibit(false)));
        // There is also a `connect!()` macro for GTK+ events that do not need a
        // value to be returned in the callback.

        window.show_all();

        Win {
            model,
            window,
        }
    }
}

Finally, show this Widget by calling Win::run():

fn main() {
    Win::run(()).unwrap();
}

#[widget] attribute

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 root(), type Msg, type Model, type ModelParam and type Root 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.

  • Update and Widget traits can be implemented at once.

To use this attribute, add the following code:

use relm_derive::widget;

Here is an example using this attribute:

#[derive(Msg)]
pub enum Msg {
    Decrement,
    Increment,
    Quit,
}

pub struct Model {
    counter: u32,
}

#[widget]
impl Widget for Win {
    fn model() -> Model {
        Model {
            counter: 0,
        }
    }

    fn update(&mut self, event: Msg) {
        match event {
            // A call to self.label1.set_text() is automatically inserted by the
            // attribute every time the model.counter attribute is updated.
            Msg::Decrement => self.model.counter -= 1,
            Msg::Increment => self.model.counter += 1,
            Msg::Quit => gtk::main_quit(),
        }
    }

    view! {
        gtk::Window {
            gtk::Box {
                orientation: Vertical,
                gtk::Button {
                    // By default, an event with one paramater is assumed.
                    clicked => Msg::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: &self.model.counter.to_string(),
                },
                gtk::Button {
                    clicked => Msg::Decrement,
                    label: "-",
                },
            },
            // Use a tuple when you want to both send a message and return a value to
            // the GTK+ callback.
            delete_event(_, _) => (Msg::Quit, Inhibit(false)),
        }
    }
}
Note
The struct Win is now automatically created by the attribute, as are the function root() and the associated types Model, ModelParam, Msg and Container. You can still provide the method and the associated types if needed, but you cannot create the struct.
Warning
The #[widget] makes the generated struct public: hence, the corresponding model and message types must be public too.
Warning

Your program might be slower when using this attribute because the code generation is simple. For instance, the following code

fn update(&mut self, event: Msg) {
    for _ in 0..100 {
        self.model.counter += 1;
    }
}

will generate this function:

fn update(&mut self, event: Msg) {
    for _ in 0..100 {
        self.model.counter += 1;
        self.label1.set_text(&self.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

fn update(&mut self, event: Msg) {
    self.model.text.push_str("Text");
}

will not work as expected.

Please use the following variation if needed.

fn update(&mut self, event: Msg) {
    self.model.text += "Text";
}

For more information about how you can use relm, you can take a look at the examples.

Donations

If you appreciate this project and want new features to be implemented, please support me on Patreon.

become a patron button

Projects using relm

  • Yellow Pitaya: A desktop interface for the redpitaya hardware oscilloscope.

  • Game of Life: Conway’s Game of Life Simulator

  • Country Parser: Parses Google location history and displays timeline of countries visited

  • Chessground: An experimental chessboard widget

  • Effitask: Graphical task manager, based on the todo.txt format

  • KS Curve Tracer: A companion app for AD2 curve tracer

  • Cigale: Tool to prepare the timesheet of the tasks you did at work

  • Projectpad: Manage secret credentials and server information as a software developer or sysadmin.

  • TimezoneRS: A GUI application to visually see the time in different time zones

  • Tubefeeder / Pipeline: Watch YouTube, LBRY, and PeerTube videos in one place.

  • Hotwire: Study network traffic of a few popular protocols in a simple way

  • gue: GUI for controlling phillips hue lights

  • MyCitadel Wallet: MyCitadel Cryptocurrency Wallet app

If you want to add your project to this list, please create a pull request.

relm's People

Contributors

antoyo avatar atsjj avatar azanellato avatar chathaway-codes avatar crrodger avatar darkwater avatar deflateawning avatar eijebong avatar emmanueltouzery avatar etrombly avatar euclio avatar gagath avatar guillaumegomez avatar ilya-epifanov avatar jonas-schievink avatar juchiast avatar lordmzte avatar lpil avatar msrd0 avatar niklasf avatar pentamassiv avatar petar-dambovaliev avatar polymeilex avatar repnop avatar rnestler avatar sanpii avatar schmiddiii avatar sunjay avatar zmitchell avatar zzeroo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

relm's Issues

Consider a better way to distinguish between GTK+ and relm widgets in the view! macro

Currently, a widget with a module separator like gtk::Button is considered a gtk widget while one without is considered a relm widget.
This is confusing.
I cannot check for the gtk prefix because other crates can provide gtk widgets like webkit2gtk.

An idea is to require to prepend a symbol in front a relm widgets like @Button.
I don't like this idea since it makes a distinction between gtk and relm widgets.

Critical GTK errors when panic

(.:17800): GLib-CRITICAL **: g_io_channel_shutdown: assertion 'channel != NULL' failed

(.:17800): GLib-CRITICAL **: g_io_channel_unref: assertion 'channel != NULL' failed

Support or switch to Nuklear 2D GUI library

Nuklear is an immediate mode GUI (IMGUI) library.
IMGUI is a non-retained mode of graphical interface programming, the UI is just a function of the current application state, thus the application is not responsible for explicitly managing the UI state and keeping it in sync with the app.
So IMGUI goes well with Elm architecture and able to use a single codebase for multi-platform applications.

Develop a GUI functional testing crate

  • Fix the relm-test crate.
  • Create an Observer astraction.
  • Add useful functions in it.
    • Click any widget (on the center of the widget or at a specific position — test that it works for menus).
    • Double-click.
    • Mouse move.
    • Mouse press and release.
    • Send input (key press) to any widget (one key and a string).
    • Send key press and release.
    • Wait a specific amount of time.
  • Write an example like the one on this article.
  • Add example of clicking/entering text in a relm widget.
  • Allow to act on widgets contained inside relm widgets (like entering text in a child widget of a relm widget).
  • Test all the examples.

readme nightly example not working

Tried to run example with #[widget]

error: cannot find macro `view!` in this scope
  --> src/main.rs:47:5
   |
47 |     view! {
   |  
error[E0412]: cannot find type `Window` in this scope
  --> src/main.rs:22:13
   |
22 |     window: Window,
   |             ^^^^^^ not found in this scope
   |
help: possible candidate is found in another module, you can import it into scope
   |
1  | use gtk::Window;
   |

also:

 --> src/main.rs:5:1
  |
5 | extern crate relm_derive;
  | ^^^^^^^^^^^^^^^^^^^^^^^^^ can't find crate

I used github repo just to be sure but not working

Better error reporting

That would require the ability to set positions (spans) in the proc macro and the ability to spans errors.

Question: how to update model from outside widget and update widget afterward?

Hi,
I'm trying to write an app with Relm and I think I understand well the different examples but they all show the case when the model is created by the widget.

I have data read from the hard drive that I want to display and I'm wondering how to inject them in the widget and make the widget refresh

Thank you for any insight :)

Allow frontends other than gtk

I am interested in an Elm like approach for UI in Rust but with other frontends like curses, and also maybe virtual-dom. Do you think it is possible to separate GTK from relm?

Make the update function return the model.

I was reading the README and saw this:

Your program might be slower when using this attribute because the code generation is simple. For instance, the following code

fn update(&mut self, event: Msg, model: &mut Model) {
   for _ in 0..100 {
       model.counter += 1;
   }
}

will generate this function:

fn update(&mut self, event: Msg, model: &mut Model) {
   for _ in 0..100 {
       model.counter += 1;
       self.label1.set_text(&model.counter.to_string());
   }
}

I think that it is too magical and users of the library aren't going to pay attention to what the readme says (like me) and make that mistake.

And I wondered if instead of borrowing the model (&mut Model), take ownership of it and let the user return a modified one. e.g:

fn update(&mut self, event: Msg, model: Model) -> Model {
    for _ in 0..100 {
        model.counter += 1;
    }
    model
}

Another way to solve it would be creating a do_update! or make_visible! macro, that modifies the widget with the updated model, e.g:

fn update(&mut self, event: Msg, model: &mut Model) {
    for _ in 0..100 {
        model.counter += 1;
    }
    make_visible!(model)
}

Also, nice project!

Strange behavior/bug

I encountered a strange behavior while rewriting my old gtk code using relm.

Consider changing the code in examples/clocknightly.rs like this:

@@ -36,8 +36,6 @@ use gtk::{ContainerExt, Inhibit, Label, WidgetExt, Window, WindowType};
 use relm::{Relm, RemoteRelm, Widget};
 use tokio_core::reactor::Interval;

-use self::Msg::*;
-
 #[derive(SimpleMsg)]
 enum Msg {
     Quit,
@@ -65,7 +63,7 @@ impl Widget for Win {

     fn subscriptions(relm: &Relm<Msg>) {
         let stream = Interval::new(Duration::from_secs(1), relm.handle()).unwrap();
-        relm.connect_exec_ignore_err(stream, Tick);
+        relm.connect_exec_ignore_err(stream, Msg::Tick);
     }

     fn update(&mut self, event: Msg, _model: &mut ()) {
@@ -87,13 +85,13 @@ impl Widget for Win {

         window.show_all();

-        connect!(relm, window, connect_delete_event(_, _) (Some(Quit), Inhibit(false)));
+        connect!(relm, window, connect_delete_event(_, _) (Some(Msg::Quit), Inhibit(false)));

         let mut win = Win {
             label: label,
             window: window,
         };
-        win.update(Tick, &mut ());
+        win.update(Msg::Tick, &mut ());
         win
     }
 }

With the above code, the program does not stop when the window is closed. Using Msg::Tick and Msg::Quit in update() will fix the issue.

Write a tutorial

  • Channels (multithread)
  • Have a tool to automatically tests that the examples work.

Application design question

Hello,

I try to develop an application with a strong model/view segregation. The frontend sends a signal and the Application struct react to its and launches the corresponding backend method. Something like that:

struct Application {
    frontend: ::relm::Component<Window>,
    backend: Backend,
}

impl Application {
    pub fn new(backend: Backend) -> Self {
        Application {
            frontend: ::relm::init().unwrap(),
            backend: backend,
        }
    }

    pub fn run(&self) {
        self.frontend.widget.show_all();
        ::gtk::main();
    }

    fn update(&self, event: Msg)
        match event {
            Msg::Start => self.backend.start(),
        };
    }
}

This example doesn’t works because relm::init is private and I don’t know how can I plug the update method.

The widget-list example can trigger GTK+ errors

To reproduce this issue:

  • Click the Add button twice.
  • Click the Remove button twice.
  • Click the Add button again.
  • Click the + button.

It show the following:

(.:19799): GLib-CRITICAL **: g_io_channel_read_unichar: assertion 'channel->is_readable' failed

Might not be worth fixing since I'll soon switch to futures-gilb.

build error

hi, antoyo
I cloned your project and run

 cargo run --example buttons

but failed. Bellow is the error:

error: failed to run custom build command for `glib-sys v0.3.3`
process didn't exit successfully: `/Users/hon/work/study/rust/relm/target/debug/build/glib-sys-36be551ee2ab1f1d/build-script-build` (exit code: 1)
--- stderr
`"pkg-config" "--libs" "--cflags" "glib-2.0 >= 2.32"` did not exit successfully: exit code: 1
--- stderr
Package glib-2.0 was not found in the pkg-config search path.
Perhaps you should add the directory containing `glib-2.0.pc'
to the PKG_CONFIG_PATH environment variable
No package 'glib-2.0' found


Build failed, waiting for other jobs to finish...
error: build failed

Add a client/server example

With a relm widget showing the list of users to show how to do communication between widgets in a real-world example.

connect! with TreeSelection

Hi,

This might be a dumb question, but based on the examples and documentation I couldn't figure it out, so I am hoping you can help me.

I have a widget which contains a gtk::TreeView. Of course I want something to happen when you select and item in the view, so I created the following code in the view():

let left_selection = left_tree.get_selection();
connect!(relm, left_selection, connect_changed(treesel), {
    let (left_model, iter) = treesel.get_selected().unwrap();
    let value = left_model.get_value(&iter, 0).get::<String>().unwrap();
    println!("connect! Value: {}", value);
    Open(value)
});

Which correctly prints out the "connect!" line to the console when clicked, but the Open(value) event is not sent to/received by the update function:

fn update(&mut self, event: Msg, _model: &mut Model) {
    println!("Update!");
    match event {
        Open(path) => {
              println!("Path {} was selected", path);
        }
        Closed => println!("Closed!"),
    }
}

Neither the "Update!" not the "Path.." string get printed.

Any ideas as to why the event is triggered, but the message is not sent to the update function?

connect! syntax error

Hello,

I would like to connect a signal to a widget and emit a signal for another.

If I understand correctly, this is the first form of the connect! macro.

I wrote:

connect!(acquire, acquire::Signal::Start(source), graph, graph::Signal::SourceStart(format!("{}", source)));

But that doesn’t compile:

error: no rules expected the token `,`
   --> src/application/mod.rs:225:64
    |
225 |         connect!(acquire, acquire::Signal::Start(source), graph, graph::Signal::SourceStart(format!("{}", source)));
    |                                                                ^

Consider integrating the GTK+ models with the relm model

For instance, being able to use a Vec<T> in the relm model instead of using a gtk::ListStore.

This involves some complications because the GTK+ model can be updated from the view, so these changes must be reflected into the Vec<T>.
Also, the GTK+ model can be filtered and sorted.

Allow multiple function heads for the update function

In Haskell, it is possible to have multiple function heads to do pattern matching:

update Quit _ = undefined

update Increment model@(Model counter) = model { counter = counter + 1 }

Having something along the same lines would allow to have a better separation of the different cases.
I imagine something like:

fn update(Quit: Msg, model: &mut Model) {
}

fn update(Increment: Msg, model: &mut Model) {
    model.counter += 1;
}

that could be converted to:

fn update(msg: Msg, model: &mut Model) {
    match msg {
        Quit => (),
        Increment => model.counter += 1,
    }
}

For simplicity, we could disallow having both variations of the update function (if using the version with pattern matching in the parameter, disallow the one with pattern matching in the function body).

Support glade

Idea:

glade_view!("file.glade");

instead of using view! {} when we want to use a UI from a glade file.

Have connect! return the signal handler id

Gtk's connect_* methods return a number that identifies the signal handler that was connected. Using glib::signal::signal_handler_block and _unblock you can temporarily disable and re-enable these handlers.

By removing some ; characters, relm's connect macro can return these ids:

($relm:expr, $widget:expr, $event:ident($($args:pat),*), $msg:expr) => {{
    let stream = $relm.stream().clone();
    $widget.$event(move |$($args),*| {
        stream.emit($msg);
    }) // no ;
}};

I'd open a pull request for this directly, but I'm not sure other macros should get the same change, and if this has any adverse effects.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.