1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, OrientableExt};
use relm4::{gtk, send, AppUpdate, Model, RelmApp, Sender, WidgetPlus, Widgets};

use tokio::runtime::Builder;
use tokio::sync::mpsc::{channel, Sender as TokioSender};

struct AppModel {
    counter: u8,
    async_handler: TokioSender<(AsyncHandlerMsg, Sender<AppMsg>)>,
}

#[derive(Debug)]
enum AsyncHandlerMsg {
    IncrementRequest,
    DecrementRequest,
}

#[derive(Debug)]
enum AppMsg {
    Increment,
    Decrement,
}

impl Model for AppModel {
    type Msg = AppMsg;
    type Widgets = AppWidgets;
    type Components = ();
}

impl AppUpdate for AppModel {
    fn update(&mut self, msg: AppMsg, _components: &(), _sender: Sender<AppMsg>) -> bool {
        match msg {
            AppMsg::Increment => {
                self.counter = self.counter.wrapping_add(1);
            }
            AppMsg::Decrement => {
                self.counter = self.counter.wrapping_sub(1);
            }
        }
        true
    }
}

#[relm4::widget]
impl Widgets<AppModel, ()> for AppWidgets {
    view! {
        gtk::ApplicationWindow {
            set_title: Some("Simple app"),
            set_default_width: 300,
            set_default_height: 100,
            set_child = Some(&gtk::Box) {
                set_orientation: gtk::Orientation::Vertical,
                set_margin_all: 5,
                set_spacing: 5,

                append = &gtk::Button {
                    set_label: "Increment",
                    connect_clicked(sender, async_sender) => move |_| {
                        async_sender.blocking_send((AsyncHandlerMsg::IncrementRequest, sender.clone())).unwrap();
                    },
                },
                append = &gtk::Button::with_label("Decrement") {
                    connect_clicked(sender, async_sender) => move |_| {
                        async_sender.blocking_send((AsyncHandlerMsg::DecrementRequest, sender.clone())).unwrap();
                    },
                },
                append = &gtk::Label {
                    set_margin_all: 5,
                    set_label: watch! { &format!("Counter: {}", model.counter) },
                }
            },
        }
    }

    fn pre_init() {
        let async_sender = &model.async_handler;
    }
}

fn main() {
    let (rx, mut tx) = channel::<(AsyncHandlerMsg, Sender<AppMsg>)>(10);

    let rt = Builder::new_multi_thread()
        .worker_threads(8)
        .enable_time()
        .build()
        .unwrap();

    rt.spawn(async move {
        while let Some((msg, sender)) = tx.recv().await {
            tokio::spawn(async move {
                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
                match msg {
                    AsyncHandlerMsg::IncrementRequest => {
                        send!(sender, AppMsg::Increment);
                    }
                    AsyncHandlerMsg::DecrementRequest => {
                        send!(sender, AppMsg::Decrement);
                    }
                }
            });
        }
    });

    let model = AppModel {
        counter: 0,
        async_handler: rx,
    };
    let app = RelmApp::new(model);
    app.run();
}