factory/
factory.rs

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