components/
components.rs

1// Don't show GTK 4.10 deprecations.
2// We can't replace them without raising the GTK requirement to 4.10.
3#![allow(deprecated)]
4
5use std::convert::identity;
6
7use gtk::{glib, prelude::*};
8use relm4::prelude::*;
9
10struct Header;
11
12#[relm4::component]
13impl SimpleComponent for Header {
14    type Init = ();
15    type Input = ();
16    type Output = AppMsg;
17
18    view! {
19        gtk::HeaderBar {
20            #[wrap(Some)]
21            set_title_widget = &gtk::Box {
22                add_css_class: relm4::css::LINKED,
23                append: group = &gtk::ToggleButton {
24                    set_label: "View",
25                    set_active: true,
26                    connect_toggled[sender] => move |btn| {
27                        if btn.is_active() {
28                            sender.output(AppMsg::SetMode(AppMode::View)).unwrap();
29                        }
30                    },
31                },
32                append = &gtk::ToggleButton {
33                    set_label: "Edit",
34                    set_group: Some(&group),
35                    connect_toggled[sender] => move |btn| {
36                        if btn.is_active() {
37                            sender.output(AppMsg::SetMode(AppMode::Edit)).unwrap();
38                        }
39                    },
40                },
41                append = &gtk::ToggleButton {
42                    set_label: "Export",
43                    set_group: Some(&group),
44                    connect_toggled[sender] => move |btn| {
45                        if btn.is_active() {
46                            sender.output(AppMsg::SetMode(AppMode::Export)).unwrap();
47                        }
48                    },
49                },
50            }
51        }
52    }
53
54    fn init(
55        _init: Self::Init,
56        root: Self::Root,
57        sender: ComponentSender<Self>,
58    ) -> ComponentParts<Self> {
59        let model = Header;
60        let widgets = view_output!();
61
62        ComponentParts { model, widgets }
63    }
64}
65
66struct Dialog {
67    hidden: bool,
68}
69
70#[derive(Debug)]
71enum DialogMsg {
72    Show,
73    Accept,
74    Cancel,
75}
76
77struct DialogInit {
78    text: String,
79    secondary_text: Option<String>,
80    accept_text: String,
81    cancel_text: String,
82}
83
84#[relm4::component]
85impl SimpleComponent for Dialog {
86    type Init = DialogInit;
87    type Input = DialogMsg;
88    type Output = AppMsg;
89
90    view! {
91        dialog = gtk::MessageDialog {
92            set_modal: true,
93            set_text: Some(&init.text),
94            set_secondary_text: init.secondary_text.as_deref(),
95            add_button: (&init.accept_text, gtk::ResponseType::Accept),
96            add_button: (&init.cancel_text, gtk::ResponseType::Cancel),
97
98            #[watch]
99            set_visible: !model.hidden,
100
101            connect_response[sender] => move |_, resp| {
102                sender.input(if resp == gtk::ResponseType::Accept {
103                    DialogMsg::Accept
104                } else {
105                    DialogMsg::Cancel
106                });
107            }
108        }
109    }
110
111    fn init(
112        init: Self::Init,
113        root: Self::Root,
114        sender: ComponentSender<Self>,
115    ) -> ComponentParts<Self> {
116        let model = Dialog { hidden: true };
117        let widgets = view_output!();
118
119        ComponentParts { model, widgets }
120    }
121
122    fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
123        match msg {
124            DialogMsg::Show => self.hidden = false,
125            DialogMsg::Accept => {
126                self.hidden = true;
127                sender.output(AppMsg::Close).unwrap();
128            }
129            DialogMsg::Cancel => self.hidden = true,
130        }
131    }
132}
133
134#[derive(Debug)]
135enum AppMode {
136    View,
137    Edit,
138    Export,
139}
140
141#[derive(Debug)]
142enum AppMsg {
143    SetMode(AppMode),
144    CloseRequest,
145    Close,
146}
147
148struct App {
149    mode: AppMode,
150    dialog: Controller<Dialog>,
151    header: Controller<Header>,
152}
153
154#[relm4::component]
155impl SimpleComponent for App {
156    type Init = ();
157    type Input = AppMsg;
158    type Output = ();
159
160    view! {
161        main_window = gtk::ApplicationWindow {
162            set_default_size: (500, 250),
163            set_titlebar: Some(model.header.widget()),
164
165            #[wrap(Some)]
166            set_child = &gtk::Label {
167                #[watch]
168                set_label: &format!("Placeholder for {:?}", model.mode),
169            },
170            connect_close_request[sender] => move |_| {
171                sender.input(AppMsg::CloseRequest);
172                glib::Propagation::Stop
173            }
174        }
175    }
176
177    fn init(
178        _: Self::Init,
179        root: Self::Root,
180        sender: ComponentSender<Self>,
181    ) -> ComponentParts<Self> {
182        let header = Header::builder()
183            .launch(())
184            .forward(sender.input_sender(), identity);
185        let dialog = Dialog::builder()
186            .transient_for(&root)
187            .launch(DialogInit {
188                text: "Do you want to close before saving?".to_string(),
189                secondary_text: Some("All unsaved changes will be lost".to_string()),
190                accept_text: "Close".to_string(),
191                cancel_text: "Cancel".to_string(),
192            })
193            .forward(sender.input_sender(), identity);
194
195        let model = App {
196            mode: AppMode::View,
197            header,
198            dialog,
199        };
200        let widgets = view_output!();
201
202        ComponentParts { model, widgets }
203    }
204
205    fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
206        match msg {
207            AppMsg::SetMode(mode) => {
208                self.mode = mode;
209            }
210            AppMsg::CloseRequest => {
211                self.dialog.emit(DialogMsg::Show);
212            }
213            AppMsg::Close => {
214                relm4::main_application().quit();
215            }
216        }
217    }
218}
219
220fn main() {
221    let app = RelmApp::new("relm4.example.components");
222    app.run::<App>(());
223}