grid_factory/
grid_factory.rs

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