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