navigation_splitview_with_stack/
navigation_splitview_with_stack.rs

1use relm4::RelmApp;
2
3fn main() {
4    let app = RelmApp::new("relm4.example.navigation_stack");
5    app.run::<app::App>((0, false));
6}
7
8// The main app
9mod app {
10    use crate::{counter::CounterModel, toggler::TogglerModel};
11    use adw::prelude::{AdwApplicationWindowExt, IsA, NavigationPageExt, ToValue};
12    use gtk::glib;
13    use relm4::{
14        Component, ComponentController, ComponentParts, ComponentSender, Controller,
15        SimpleComponent, adw,
16    };
17    use std::convert::identity;
18
19    pub struct App {
20        // Hoding on to these two controllers, as dropping a controller stops the
21        // runtime of a component unless we've detach it before (and it would no
22        // longer be able to process incoming messages). Note that although it is
23        // possible to detach it, this operation should only be done if there is
24        // no practical way to store the controller.
25        _counter: Controller<CounterModel>,
26        _toggler: Controller<TogglerModel>,
27    }
28
29    #[derive(Debug)]
30    pub enum Msg {}
31
32    #[relm4::component(pub)]
33    impl SimpleComponent for App {
34        type Init = (u8, bool);
35        type Input = Msg;
36        type Output = ();
37
38        view! {
39            #[root]
40            adw::ApplicationWindow {
41                #[name(split_view)]
42                adw::NavigationSplitView {
43                    #[wrap(Some)]
44                    set_sidebar = &adw::NavigationPage {
45                        set_title: "Sidebar",
46
47                        #[wrap(Some)]
48                        set_child = &adw::ToolbarView {
49                            add_top_bar = &adw::HeaderBar {},
50
51                            #[wrap(Some)]
52                            set_content = &gtk::StackSidebar {
53                                set_stack: &stack,
54                            },
55                        },
56                    },
57
58                    #[wrap(Some)]
59                    set_content = &adw::NavigationPage {
60                        set_title: "Content",
61
62                        #[wrap(Some)]
63                        set_child = &adw::ToolbarView {
64                            add_top_bar = &adw::HeaderBar {},
65                            set_content: Some(&stack),
66                        }
67                    },
68                },
69
70                add_breakpoint = bp_with_setters(
71                    adw::Breakpoint::new(
72                        adw::BreakpointCondition::new_length(
73                            adw::BreakpointConditionLengthType::MaxWidth,
74                            400.0,
75                            adw::LengthUnit::Sp,
76                        )
77                    ),
78                    &[(&split_view, "collapsed", true)]
79                ),
80            },
81            stack = &gtk::Stack {
82                add_titled: (counter.widget(), None, "Counter"),
83                add_titled: (toggler.widget(), None, "Toggle"),
84                set_vhomogeneous: false,
85            }
86        }
87
88        fn init(
89            init: Self::Init,
90            root: Self::Root,
91            sender: ComponentSender<Self>,
92        ) -> ComponentParts<Self> {
93            let counter = CounterModel::builder()
94                .launch(init.0)
95                .forward(sender.input_sender(), identity);
96            let toggler = TogglerModel::builder()
97                .launch(init.1)
98                .forward(sender.input_sender(), identity);
99
100            let widgets = view_output!();
101
102            let model = App {
103                _counter: counter,
104                _toggler: toggler,
105            };
106
107            widgets.stack.connect_visible_child_notify({
108                let split_view = widgets.split_view.clone();
109                move |_| {
110                    split_view.set_show_content(true);
111                }
112            });
113
114            ComponentParts { model, widgets }
115        }
116
117        fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
118            match msg {}
119        }
120    }
121
122    fn bp_with_setters(
123        bp: adw::Breakpoint,
124        additions: &[(&impl IsA<glib::Object>, &str, impl ToValue)],
125    ) -> adw::Breakpoint {
126        bp.add_setters(additions);
127        bp
128    }
129}
130
131// The Counter page
132mod counter {
133    use crate::app::Msg;
134    use gtk::prelude::{BoxExt, ButtonExt, OrientableExt};
135    use relm4::{ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent};
136
137    pub struct CounterModel {
138        counter: u8,
139    }
140
141    #[derive(Debug)]
142    pub enum CounterMsg {
143        Increment,
144        Decrement,
145    }
146
147    #[relm4::component(pub)]
148    impl SimpleComponent for CounterModel {
149        type Init = u8;
150        type Input = CounterMsg;
151        type Output = Msg;
152
153        view! {
154            #[root]
155            gtk::Box {
156                set_orientation: gtk::Orientation::Vertical,
157                set_spacing: 5,
158                set_margin_all: 5,
159
160                gtk::Button {
161                    set_label: "Increment",
162                    connect_clicked => CounterMsg::Increment
163                },
164
165                gtk::Button::with_label("Decrement") {
166                    connect_clicked => CounterMsg::Decrement
167                },
168
169                gtk::Label {
170                    #[watch]
171                    set_label: &format!("Counter: {}", model.counter),
172                    set_margin_all: 5,
173                }
174            }
175        }
176
177        fn init(
178            init: Self::Init,
179            root: Self::Root,
180            sender: ComponentSender<Self>,
181        ) -> ComponentParts<Self> {
182            let model = CounterModel { counter: init };
183
184            let widgets = view_output!();
185
186            ComponentParts { model, widgets }
187        }
188
189        fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
190            match msg {
191                CounterMsg::Increment => {
192                    self.counter = self.counter.wrapping_add(1);
193                }
194                CounterMsg::Decrement => {
195                    self.counter = self.counter.wrapping_sub(1);
196                }
197            }
198        }
199    }
200}
201
202// The Toggler page
203mod toggler {
204    use crate::app::Msg;
205    use gtk::prelude::{BoxExt, ButtonExt, OrientableExt};
206    use relm4::{ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent};
207
208    pub struct TogglerModel {
209        toggle: bool,
210    }
211
212    #[derive(Debug)]
213    pub enum ToggleMsg {
214        Toggle,
215    }
216
217    #[relm4::component(pub)]
218    impl SimpleComponent for TogglerModel {
219        type Init = bool;
220        type Input = ToggleMsg;
221        type Output = Msg;
222
223        view! {
224            #[root]
225            gtk::Box {
226                set_orientation: gtk::Orientation::Vertical,
227                set_spacing: 5,
228                set_margin_all: 5,
229
230                gtk::ToggleButton {
231                    set_label: "Toggle",
232                    connect_clicked => ToggleMsg::Toggle,
233                },
234
235                gtk::Label {
236                    #[watch]
237                    set_label: &format!("Toggle: {}", model.toggle),
238                    set_margin_all: 5,
239                }
240            }
241        }
242
243        fn init(
244            init: Self::Init,
245            root: Self::Root,
246            sender: ComponentSender<Self>,
247        ) -> ComponentParts<Self> {
248            let model = TogglerModel { toggle: init };
249
250            let widgets = view_output!();
251
252            ComponentParts { model, widgets }
253        }
254
255        fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
256            match msg {
257                ToggleMsg::Toggle => {
258                    self.toggle = !self.toggle;
259                }
260            }
261        }
262    }
263}