factory_async/
factory_async.rs

1use std::time::Duration;
2
3use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, OrientableExt, WidgetExt};
4use relm4::factory::{
5    AsyncFactoryComponent, AsyncFactorySender, AsyncFactoryVecDeque, DynamicIndex,
6};
7use relm4::loading_widgets::LoadingWidgets;
8use relm4::{ComponentParts, ComponentSender, RelmApp, RelmWidgetExt, SimpleComponent, view};
9
10#[derive(Debug)]
11struct Counter {
12    value: u8,
13}
14
15#[derive(Debug)]
16enum CounterMsg {
17    Increment,
18    Decrement,
19}
20
21#[derive(Debug)]
22enum CounterOutput {
23    SendFront(DynamicIndex),
24    MoveUp(DynamicIndex),
25    MoveDown(DynamicIndex),
26    Remove(DynamicIndex),
27}
28
29#[relm4::factory(async)]
30impl AsyncFactoryComponent for Counter {
31    type Init = u8;
32    type Input = CounterMsg;
33    type Output = CounterOutput;
34    type CommandOutput = ();
35    type ParentWidget = gtk::Box;
36
37    view! {
38        root = gtk::Box {
39            #[name(label)]
40            gtk::Label {
41                #[watch]
42                set_label: &self.value.to_string(),
43                set_width_chars: 3,
44            },
45
46            #[name(add_button)]
47            gtk::Button {
48                set_label: "+",
49                connect_clicked => CounterMsg::Increment,
50            },
51
52            #[name(remove_button)]
53            gtk::Button {
54                set_label: "-",
55                connect_clicked => CounterMsg::Decrement,
56            },
57
58            #[name(move_up_button)]
59            gtk::Button {
60                set_label: "Up",
61                connect_clicked[sender, index] => move |_| {
62                    sender.output(CounterOutput::MoveUp(index.clone())).unwrap();
63                }
64            },
65
66            #[name(move_down_button)]
67            gtk::Button {
68                set_label: "Down",
69                connect_clicked[sender, index] => move |_| {
70                    sender.output(CounterOutput::MoveDown(index.clone())).unwrap();
71                }
72            },
73
74            #[name(to_front_button)]
75            gtk::Button {
76                set_label: "To Start",
77                connect_clicked[sender, index] => move |_| {
78                    sender.output(CounterOutput::SendFront(index.clone())).unwrap();
79                }
80            },
81
82            gtk::Button {
83                set_label: "Remove",
84                connect_clicked[sender, index] => move |_| {
85                    sender.output(CounterOutput::Remove(index.clone())).unwrap();
86                }
87            }
88        }
89    }
90
91    fn init_loading_widgets(root: Self::Root) -> Option<LoadingWidgets> {
92        view! {
93            #[local]
94            root {
95                set_orientation: gtk::Orientation::Horizontal,
96                set_spacing: 10,
97
98                #[name(spinner)]
99                gtk::Spinner {
100                    start: (),
101                    set_hexpand: true,
102                    set_halign: gtk::Align::Center,
103                    // Reserve vertical space
104                    set_height_request: 34,
105                }
106            }
107        }
108        Some(LoadingWidgets::new(root, spinner))
109    }
110
111    async fn init_model(
112        value: Self::Init,
113        _index: &DynamicIndex,
114        _sender: AsyncFactorySender<Self>,
115    ) -> Self {
116        tokio::time::sleep(Duration::from_secs(1)).await;
117        Self { value }
118    }
119
120    async fn update(&mut self, msg: Self::Input, _sender: AsyncFactorySender<Self>) {
121        tokio::time::sleep(Duration::from_secs(1)).await;
122        match msg {
123            CounterMsg::Increment => {
124                self.value = self.value.wrapping_add(1);
125            }
126            CounterMsg::Decrement => {
127                self.value = self.value.wrapping_sub(1);
128            }
129        }
130    }
131
132    fn shutdown(&mut self, _widgets: &mut Self::Widgets, _output: relm4::Sender<Self::Output>) {
133        println!("Counter with value {} was destroyed", self.value);
134    }
135}
136
137struct App {
138    created_widgets: u8,
139    counters: AsyncFactoryVecDeque<Counter>,
140}
141
142#[derive(Debug)]
143enum AppMsg {
144    AddCounter,
145    RemoveCounter,
146    SendFront(DynamicIndex),
147    MoveUp(DynamicIndex),
148    MoveDown(DynamicIndex),
149    Remove(DynamicIndex),
150}
151
152#[relm4::component]
153impl SimpleComponent for App {
154    type Init = u8;
155    type Input = AppMsg;
156    type Output = ();
157
158    view! {
159        gtk::Window {
160            set_title: Some("Factory example"),
161            set_default_size: (300, 100),
162
163            gtk::Box {
164                set_orientation: gtk::Orientation::Vertical,
165                set_spacing: 5,
166                set_margin_all: 5,
167
168                gtk::Button {
169                    set_label: "Add counter",
170                    connect_clicked => AppMsg::AddCounter,
171                },
172
173                gtk::Button {
174                    set_label: "Remove counter",
175                    connect_clicked => AppMsg::RemoveCounter,
176                },
177
178                #[local_ref]
179                counter_box -> gtk::Box {
180                    set_orientation: gtk::Orientation::Vertical,
181                    set_spacing: 5,
182                }
183            }
184        }
185    }
186
187    fn init(
188        counter: Self::Init,
189        root: Self::Root,
190        sender: ComponentSender<Self>,
191    ) -> ComponentParts<Self> {
192        let counters = AsyncFactoryVecDeque::builder().launch_default().forward(
193            sender.input_sender(),
194            |output| match output {
195                CounterOutput::SendFront(index) => AppMsg::SendFront(index),
196                CounterOutput::MoveUp(index) => AppMsg::MoveUp(index),
197                CounterOutput::MoveDown(index) => AppMsg::MoveDown(index),
198                CounterOutput::Remove(index) => AppMsg::Remove(index),
199            },
200        );
201
202        let model = App {
203            created_widgets: counter,
204            counters,
205        };
206
207        let counter_box = model.counters.widget();
208        let widgets = view_output!();
209
210        ComponentParts { model, widgets }
211    }
212
213    fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
214        let mut counters_guard = self.counters.guard();
215        match msg {
216            AppMsg::AddCounter => {
217                counters_guard.push_back(self.created_widgets);
218                self.created_widgets = self.created_widgets.wrapping_add(1);
219            }
220            AppMsg::RemoveCounter => {
221                counters_guard.pop_back();
222            }
223            AppMsg::SendFront(index) => {
224                counters_guard.move_front(index.current_index());
225            }
226            AppMsg::MoveDown(index) => {
227                let index = index.current_index();
228                let new_index = index + 1;
229                // Already at the end?
230                if new_index < counters_guard.len() {
231                    counters_guard.move_to(index, new_index);
232                }
233            }
234            AppMsg::MoveUp(index) => {
235                let index = index.current_index();
236                // Already at the start?
237                if index != 0 {
238                    counters_guard.move_to(index, index - 1);
239                }
240            }
241            AppMsg::Remove(index) => {
242                counters_guard.remove(index.current_index());
243            }
244        }
245    }
246}
247
248fn main() {
249    let app = RelmApp::new("relm4.example.factory");
250    app.run::<App>(0);
251}