message_broker/
message_broker.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::prelude::{BoxExt, ButtonExt, DialogExt, GtkWindowExt, ToggleButtonExt, WidgetExt};
8use relm4::gtk::prelude::Cast;
9use relm4::{
10    Component, ComponentController, ComponentParts, ComponentSender, Controller, MessageBroker,
11    RelmApp, SimpleComponent,
12};
13
14static HEADER_BROKER: MessageBroker<bool> = MessageBroker::new();
15
16struct Header(bool);
17
18#[relm4::component]
19impl SimpleComponent for Header {
20    type Init = ();
21    type Input = bool;
22    type Output = AppMsg;
23
24    view! {
25        gtk::HeaderBar {
26            #[wrap(Some)]
27            set_title_widget = &gtk::Box {
28                add_css_class: relm4::css::LINKED,
29                append: group = &gtk::ToggleButton {
30                    set_label: "View",
31                    set_active: true,
32                    #[watch]
33                    set_has_frame: model.0,
34                    connect_toggled[sender] => move |btn| {
35                        if btn.is_active() {
36                            sender.output(AppMsg::SetMode(AppMode::View)).unwrap();
37                        }
38                    },
39                },
40                gtk::ToggleButton {
41                    set_label: "Edit",
42                    set_group: Some(&group),
43                    #[watch]
44                    set_has_frame: model.0,
45                    connect_toggled[sender] => move |btn| {
46                        if btn.is_active() {
47                            sender.output(AppMsg::SetMode(AppMode::Edit)).unwrap();
48                        }
49                    },
50                },
51                gtk::ToggleButton {
52                    set_group: Some(&group),
53                    set_label: "Export",
54                    #[watch]
55                    set_has_frame: model.0,
56                    connect_toggled[sender] => move |btn| {
57                        if btn.is_active() {
58                            sender.output(AppMsg::SetMode(AppMode::Export)).unwrap();
59                        }
60                    },
61                },
62            }
63        }
64    }
65
66    fn init(
67        _init: Self::Init,
68        root: Self::Root,
69        sender: ComponentSender<Self>,
70    ) -> ComponentParts<Self> {
71        let model = Header(false);
72        let widgets = view_output!();
73
74        ComponentParts { model, widgets }
75    }
76
77    fn update(&mut self, input: Self::Input, _sender: ComponentSender<Self>) {
78        self.0 = input;
79    }
80}
81
82struct Dialog {
83    hidden: bool,
84}
85
86#[derive(Debug)]
87enum DialogMsg {
88    Show,
89    Accept,
90    Cancel,
91}
92
93#[relm4::component]
94impl SimpleComponent for Dialog {
95    type Init = gtk::Window;
96    type Input = DialogMsg;
97    type Output = AppMsg;
98
99    view! {
100        dialog = gtk::MessageDialog {
101            set_transient_for: Some(&parent_window),
102            set_modal: true,
103            set_text: Some("Do you want frames around the header buttons?"),
104            add_button: ("Yes", gtk::ResponseType::Accept),
105            add_button: ("No", gtk::ResponseType::Cancel),
106
107            #[watch]
108            set_visible: !model.hidden,
109
110            connect_response[sender] => move |_, resp| {
111                sender.input(if resp == gtk::ResponseType::Accept {
112                    // Send a message directly to another component that's
113                    // not the parent component!
114                    HEADER_BROKER.send(true);
115                    DialogMsg::Accept
116                } else {
117                    HEADER_BROKER.send(false);
118                    DialogMsg::Cancel
119                });
120            }
121        }
122    }
123
124    fn init(
125        parent_window: Self::Init,
126        root: Self::Root,
127        sender: ComponentSender<Self>,
128    ) -> ComponentParts<Self> {
129        let model = Dialog { hidden: true };
130
131        let widgets = view_output!();
132
133        ComponentParts { model, widgets }
134    }
135
136    fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
137        match msg {
138            DialogMsg::Show => self.hidden = false,
139            DialogMsg::Cancel | DialogMsg::Accept => self.hidden = true,
140        }
141    }
142}
143
144#[derive(Debug)]
145enum AppMode {
146    View,
147    Edit,
148    Export,
149}
150
151#[derive(Debug)]
152enum AppMsg {
153    SetMode(AppMode),
154    ShowDialog,
155}
156
157struct App {
158    mode: AppMode,
159    dialog: Controller<Dialog>,
160    header: Controller<Header>,
161}
162
163#[relm4::component]
164impl SimpleComponent for App {
165    type Init = ();
166    type Input = AppMsg;
167    type Output = ();
168
169    view! {
170        main_window = gtk::ApplicationWindow {
171            set_default_size: (500, 250),
172            set_titlebar: Some(model.header.widget()),
173
174            #[wrap(Some)]
175            set_child = &gtk::Label {
176                #[watch]
177                set_label: &format!("Placeholder for {:?}", model.mode),
178            },
179            gtk::Button {
180                set_label: "Change header style",
181                connect_clicked => AppMsg::ShowDialog,
182            }
183        }
184    }
185
186    fn init(
187        _init: Self::Init,
188        root: Self::Root,
189        sender: ComponentSender<Self>,
190    ) -> ComponentParts<Self> {
191        let header = Header::builder()
192            .launch_with_broker((), &HEADER_BROKER)
193            .forward(sender.input_sender(), identity);
194
195        let dialog = Dialog::builder()
196            .launch(root.clone().upcast())
197            .forward(sender.input_sender(), identity);
198
199        let model = App {
200            mode: AppMode::View,
201            header,
202            dialog,
203        };
204
205        let widgets = view_output!();
206
207        ComponentParts { model, widgets }
208    }
209
210    fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
211        match msg {
212            AppMsg::SetMode(mode) => {
213                self.mode = mode;
214            }
215            AppMsg::ShowDialog => {
216                self.dialog.emit(DialogMsg::Show);
217            }
218        }
219    }
220}
221
222fn main() {
223    let app = RelmApp::new("relm4.example.message_broker");
224    app.run::<App>(());
225}