tab_factory/
tab_factory.rs

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