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 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 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 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}