pub struct ComponentSender<C: Component> { /* private fields */ }Expand description
Contains senders to send and receive messages from a Component.
Implementations§
Source§impl<C: Component> ComponentSender<C>
impl<C: Component> ComponentSender<C>
Sourcepub fn input_sender(&self) -> &Sender<C::Input>
pub fn input_sender(&self) -> &Sender<C::Input>
Retrieve the sender for input messages.
Useful to forward inputs from another component. If you just need to send input messages,
input() is more concise.
Examples found in repository?
88 fn init(
89 _: Self::Init,
90 root: Self::Root,
91 sender: ComponentSender<Self>,
92 ) -> ComponentParts<Self> {
93 let model = App {
94 counter: 0,
95 worker: AsyncHandler::builder()
96 .detach_worker(())
97 .forward(sender.input_sender(), identity),
98 };
99
100 let widgets = view_output!();
101
102 ComponentParts { model, widgets }
103 }More examples
77 fn init(
78 _init: Self::Init,
79 root: Self::Root,
80 sender: ComponentSender<Self>,
81 ) -> ComponentParts<Self> {
82 let factory_box = gtk::Box::default();
83
84 let counters = FactoryVecDeque::builder()
85 .launch(factory_box.clone())
86 .forward(sender.input_sender(), |output| output);
87
88 let model = App {
89 counters,
90 created_counters: 0,
91 entry: gtk::EntryBuffer::default(),
92 };
93
94 let widgets = view_output!();
95
96 ComponentParts { model, widgets }
97 }140 fn init(
141 _: Self::Init,
142 root: Self::Root,
143 sender: ComponentSender<Self>,
144 ) -> ComponentParts<Self> {
145 let tasks =
146 FactoryVecDeque::builder()
147 .launch_default()
148 .forward(sender.input_sender(), |output| match output {
149 TaskOutput::Delete(index) => AppMsg::DeleteEntry(index),
150 });
151
152 let model = App { tasks };
153
154 let task_list_box = model.tasks.widget();
155 let widgets = view_output!();
156
157 ComponentParts { model, widgets }
158 }62 fn init(
63 _: Self::Init,
64 root: Self::Root,
65 sender: ComponentSender<Self>,
66 ) -> ComponentParts<Self> {
67 let model = App {
68 combo_row: SimpleComboRow::builder()
69 .launch(SimpleComboRow {
70 variants: vec!["Variant 1", "Variant 2"],
71 active_index: None,
72 })
73 .forward(sender.input_sender(), AppMsg::Selected),
74 selected_variant: 0,
75 };
76
77 let combo_row = model.combo_row.widget();
78 let widgets = view_output!();
79
80 ComponentParts { model, widgets }
81 }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 }45 fn init(
46 _: Self::Init,
47 root: Self::Root,
48 sender: ComponentSender<Self>,
49 ) -> ComponentParts<Self> {
50 let open_button = OpenButton::builder()
51 .launch(OpenButtonSettings {
52 dialog_settings: OpenDialogSettings::default(),
53 icon: None,
54 text: "Open file",
55 recently_opened_files: Some(".recent_files"),
56 max_recent_files: 10,
57 })
58 .forward(sender.input_sender(), AppMsg::Open);
59 let model = App { open_button };
60
61 let widgets = view_output!();
62
63 ComponentParts { model, widgets }
64 }- relm4/examples/transient_dialog.rs
- relm4-components/examples/combo_box.rs
- relm4/examples/tab_factory.rs
- relm4/examples/grid_factory.rs
- relm4/examples/factory_async.rs
- relm4/examples/factory.rs
- relm4/examples/components.rs
- relm4/examples/navigation_splitview_with_stack.rs
- relm4/examples/tab_game.rs
- relm4-components/examples/file_dialogs.rs
- relm4/examples/drop_sub_components.rs
- relm4-components/examples/alert.rs
- relm4/examples/state_management.rs
Sourcepub fn output_sender(&self) -> &Sender<C::Output>
pub fn output_sender(&self) -> &Sender<C::Output>
Retrieve the sender for output messages.
Useful to forward outputs from another component. If you just need to send output messages,
output() is more concise.
Sourcepub fn command_sender(&self) -> &Sender<C::CommandOutput>
pub fn command_sender(&self) -> &Sender<C::CommandOutput>
Retrieve the sender for command output messages.
Useful to forward outputs from another component. If you just need to send output messages,
command() is more concise.
Sourcepub fn input(&self, message: C::Input)
pub fn input(&self, message: C::Input)
Emit an input to the component.
Examples found in repository?
77 fn bind(&mut self, widgets: &mut Self::Widgets, _root: &mut Self::Root) {
78 widgets.label.set_label(self.name);
79 let name = self.name;
80 let button_click_handler_id = widgets.button.connect_clicked(glib::clone!(
81 #[strong(rename_to = sender)]
82 self.sender,
83 move |_btn| {
84 // Use the cloned sender to send a message
85 sender.input(Msg::Print(name));
86 }
87 ));
88 self.button_click_handler_id
89 .replace(button_click_handler_id);
90 }More examples
87 fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
88 match msg {
89 Msg::Activate => {
90 self.activated = "Active";
91 let toast = adw::Toast::builder()
92 .title("Activated")
93 .button_label("Cancel")
94 .timeout(0)
95 .build();
96 toast.connect_button_clicked(move |this| {
97 this.dismiss();
98 sender.input(Msg::Cancel);
99 });
100 self.toaster.add_toast(toast);
101 }
102 Msg::Cancel => self.activated = "Idle",
103 }
104 }273 fn update_cmd(
274 &mut self,
275 msg: Self::CommandOutput,
276 sender: ComponentSender<Self>,
277 _root: &Self::Root,
278 ) {
279 if msg {
280 sender.input(AppMsg::StopGame);
281 } else {
282 let mut counters_guard = self.counters.guard();
283 match rand::random::<u8>() % 3 {
284 0 => {
285 counters_guard.swap(1, 2);
286 }
287 1 => {
288 counters_guard.swap(0, 1);
289 }
290 _ => {
291 let widget = counters_guard.widget();
292 if !widget.select_next_page() {
293 widget.select_previous_page();
294 }
295 }
296 }
297 }
298 }55 fn init(
56 _init: Self::Init,
57 root: Self::Root,
58 sender: ComponentSender<Self>,
59 ) -> ComponentParts<Self> {
60 let menu_model = gtk::gio::Menu::new();
61 menu_model.append(Some("Stateless"), Some(&ExampleAction::action_name()));
62
63 let model = Self { counter: 0 };
64
65 let widgets = view_output!();
66
67 let app = relm4::main_application();
68 app.set_accelerators_for_action::<ExampleAction>(&["<primary>W"]);
69
70 let action: RelmAction<ExampleAction> = RelmAction::new_stateless(move |_| {
71 println!("Statelesss action!");
72 sender.input(Msg::Increment);
73 });
74
75 let action2: RelmAction<ExampleU8Action> =
76 RelmAction::new_stateful_with_target_value(&0, |_, state, value| {
77 println!("Stateful action -> state: {state}, value: {value}");
78 *state += value;
79 });
80
81 let mut group = RelmActionGroup::<WindowActionGroup>::new();
82 group.add_action(action);
83 group.add_action(action2);
84 group.register_for_widget(&widgets.main_window);
85
86 ComponentParts { model, widgets }
87 }39 fn init(
40 counter: Self::Init,
41 window: Self::Root,
42 sender: ComponentSender<Self>,
43 ) -> ComponentParts<Self> {
44 let model = AppModel { counter };
45
46 let vbox = gtk::Box::builder()
47 .orientation(gtk::Orientation::Vertical)
48 .spacing(5)
49 .build();
50
51 let inc_button = gtk::Button::with_label("Increment");
52 let dec_button = gtk::Button::with_label("Decrement");
53
54 let label = gtk::Label::new(Some(&format!("Counter: {}", model.counter)));
55 label.set_margin_all(5);
56
57 window.set_child(Some(&vbox));
58 vbox.set_margin_all(5);
59 vbox.append(&inc_button);
60 vbox.append(&dec_button);
61 vbox.append(&label);
62
63 inc_button.connect_clicked(clone!(
64 #[strong]
65 sender,
66 move |_| {
67 sender.input(AppMsg::Increment);
68 }
69 ));
70
71 dec_button.connect_clicked(clone!(
72 #[strong]
73 sender,
74 move |_| {
75 sender.input(AppMsg::Decrement);
76 }
77 ));
78
79 let widgets = AppWidgets { label };
80
81 ComponentParts { model, widgets }
82 }93 fn init(
94 _: Self::Init,
95 root: Self::Root,
96 sender: ComponentSender<Self>,
97 ) -> ComponentParts<Self> {
98 let open_dialog = OpenDialog::builder()
99 .transient_for_native(&root)
100 .launch(OpenDialogSettings::default())
101 .forward(sender.input_sender(), |response| match response {
102 OpenDialogResponse::Accept(path) => Input::OpenResponse(path),
103 OpenDialogResponse::Cancel => Input::Ignore,
104 });
105
106 let save_dialog = SaveDialog::builder()
107 .transient_for_native(&root)
108 .launch(SaveDialogSettings::default())
109 .forward(sender.input_sender(), |response| match response {
110 SaveDialogResponse::Accept(path) => Input::SaveResponse(path),
111 SaveDialogResponse::Cancel => Input::Ignore,
112 });
113
114 let model = App {
115 open_dialog,
116 save_dialog,
117 buffer: gtk::TextBuffer::new(None),
118 file_name: None,
119 message: None,
120 };
121
122 let widgets = view_output!();
123
124 sender.input(Input::ShowMessage(String::from(
125 "A simple text editor showing the usage of\n<b>OpenFileDialog</b> and <b>SaveFileDialog</b> components.\n\nStart by clicking <b>Open</b> on the header bar.",
126 )));
127
128 ComponentParts { model, widgets }
129 }
130
131 fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
132 match message {
133 Input::OpenRequest => self.open_dialog.emit(OpenDialogMsg::Open),
134 Input::OpenResponse(path) => match std::fs::read_to_string(&path) {
135 Ok(contents) => {
136 self.buffer.set_text(&contents);
137 self.file_name = Some(
138 path.file_name()
139 .expect("The path has no file name")
140 .to_str()
141 .expect("Cannot convert file name to string")
142 .to_string(),
143 );
144 }
145 Err(e) => sender.input(Input::ShowMessage(e.to_string())),
146 },
147 Input::SaveRequest => self
148 .save_dialog
149 .emit(SaveDialogMsg::SaveAs(self.file_name.clone().unwrap())),
150 Input::SaveResponse(path) => match std::fs::write(
151 &path,
152 self.buffer
153 .text(&self.buffer.start_iter(), &self.buffer.end_iter(), false),
154 ) {
155 Ok(_) => {
156 sender.input(Input::ShowMessage(format!(
157 "File saved successfully at {path:?}"
158 )));
159 self.buffer.set_text("");
160 self.file_name = None;
161 }
162 Err(e) => sender.input(Input::ShowMessage(e.to_string())),
163 },
164 Input::ShowMessage(message) => {
165 self.message = Some(message);
166 }
167 Input::ResetMessage => {
168 self.message = None;
169 }
170 Input::Ignore => {}
171 }
172 }Sourcepub fn output(&self, message: C::Output) -> Result<(), C::Output>
pub fn output(&self, message: C::Output) -> Result<(), C::Output>
Examples found in repository?
More examples
126 fn init(
127 title: Self::Init,
128 root: Self::Root,
129 sender: ComponentSender<Self>,
130 ) -> ComponentParts<Self> {
131 // Request the caller to reload its options.
132 sender.output(Output::Reload).unwrap();
133
134 let label = gtk::Label::builder().label(title).margin_top(24).build();
135
136 let list = gtk::ListBox::builder()
137 .halign(gtk::Align::Center)
138 .margin_bottom(24)
139 .margin_top(24)
140 .selection_mode(gtk::SelectionMode::None)
141 .build();
142
143 root.append(&label);
144 root.append(&list);
145
146 ComponentParts {
147 model: AppModel::default(),
148 widgets: Widgets {
149 list,
150 button_sg: gtk::SizeGroup::new(gtk::SizeGroupMode::Both),
151 options: Default::default(),
152 },
153 }
154 }
155
156 fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>, _root: &Self::Root) {
157 match message {
158 Input::AddSetting {
159 description,
160 button,
161 id,
162 } => {
163 self.options.push((description, button, id));
164 }
165
166 Input::Clear => {
167 self.options.clear();
168
169 // Perform this async operation.
170 sender.oneshot_command(async move {
171 tokio::time::sleep(std::time::Duration::from_secs(2)).await;
172 CmdOut::Reload
173 });
174 }
175
176 Input::Reload => {
177 sender.output(Output::Reload).unwrap();
178 }
179 }
180 }
181
182 fn update_cmd(
183 &mut self,
184 message: Self::CommandOutput,
185 sender: ComponentSender<Self>,
186 _root: &Self::Root,
187 ) {
188 match message {
189 CmdOut::Reload => {
190 sender.output(Output::Reload).unwrap();
191 }
192 }
193 }
194
195 fn update_view(&self, widgets: &mut Self::Widgets, sender: ComponentSender<Self>) {
196 if self.options.is_empty() && !widgets.options.is_empty() {
197 widgets.list.remove_all();
198 } else if self.options.len() != widgets.options.len()
199 && let Some((description, button_label, id)) = self.options.last()
200 {
201 let id = *id;
202 relm4::view! {
203 widget = gtk::Box {
204 set_orientation: gtk::Orientation::Horizontal,
205 set_margin_start: 20,
206 set_margin_end: 20,
207 set_margin_top: 8,
208 set_margin_bottom: 8,
209 set_spacing: 24,
210
211 append = >k::Label {
212 set_label: description,
213 set_halign: gtk::Align::Start,
214 set_hexpand: true,
215 set_valign: gtk::Align::Center,
216 set_ellipsize: gtk::pango::EllipsizeMode::End,
217 },
218
219 append: button = >k::Button {
220 set_label: button_label,
221 set_size_group: &widgets.button_sg,
222
223 connect_clicked[sender] => move |_| {
224 sender.output(Output::Clicked(id)).unwrap();
225 }
226 }
227 }
228 }
229
230 widgets.list.append(&widget);
231 widgets.options.push(widget);
232 }
233 }289 fn update(&mut self, input: DocumentInput, sender: ComponentSender<Self>) {
290 match input {
291 DocumentInput::Save(path) => {
292 println!("Save as JSON to {path:?}");
293
294 // TODO in a real app you would report any errors from saving the document
295 if let Ok(json) = serde_json::to_string(&self.model) {
296 std::fs::write(path, json).unwrap();
297 }
298 }
299 DocumentInput::Open(path) => {
300 println!("Open tasks document at {path:?}");
301
302 if let Ok(json) = std::fs::read_to_string(path)
303 && let Ok(new_model) = serde_json::from_str(&json)
304 {
305 // update the data model
306 self.model = new_model;
307
308 // refresh the view from the data model
309 let _ = sender.output(DocumentOutput::Cleared);
310
311 for (task_index, task) in self.model.tasks.iter().enumerate() {
312 let _ = sender.output(DocumentOutput::AddedTask);
313
314 let task_name = task.name.clone();
315 let _ =
316 sender.output(DocumentOutput::ChangedTaskName(task_index, task_name));
317
318 for tag in &task.tags {
319 let tag_name = tag.name.clone();
320 let _ = sender.output(DocumentOutput::AddedTag(task_index, tag_name));
321 }
322 }
323 }
324 }
325 DocumentInput::Clear => {
326 self.model.tasks.clear();
327
328 let _ = sender.output(DocumentOutput::Cleared);
329 }
330 DocumentInput::AddTask => {
331 self.model.tasks.push(TaskModel::default());
332
333 let _ = sender.output(DocumentOutput::AddedTask);
334 }
335 DocumentInput::DeleteTask(index) => {
336 self.model.tasks.remove(index.current_index());
337
338 let _ = sender.output(DocumentOutput::DeletedTask(index.current_index()));
339 }
340 DocumentInput::ChangeTaskName(index, name) => {
341 if let Some(task) = self.model.tasks.get_mut(index.current_index()) {
342 task.name.clone_from(&name);
343 }
344
345 // We don't technically need to send an event, because gtk::Entry updates itself
346 // this is just to make the example consistent.
347 let _ = sender.output(DocumentOutput::ChangedTaskName(index.current_index(), name));
348 }
349 DocumentInput::AddTag(task_index, name) => {
350 if let Some(task) = self.model.tasks.get_mut(task_index.current_index()) {
351 task.tags.push(TagModel { name: name.clone() })
352 }
353
354 let _ = sender.output(DocumentOutput::AddedTag(task_index.current_index(), name));
355 }
356 DocumentInput::DeleteTag(task_index, tag_index) => {
357 if let Some(task) = self.model.tasks.get_mut(task_index.current_index()) {
358 task.tags.remove(tag_index.current_index());
359 }
360
361 let _ = sender.output(DocumentOutput::DeletedTag(
362 task_index.current_index(),
363 tag_index.current_index(),
364 ));
365 }
366 }
367 }Sourcepub fn command<Cmd, Fut>(&self, cmd: Cmd)where
Cmd: FnOnce(Sender<C::CommandOutput>, ShutdownReceiver) -> Fut + Send + 'static,
Fut: Future<Output = ()> + Send,
pub fn command<Cmd, Fut>(&self, cmd: Cmd)where
Cmd: FnOnce(Sender<C::CommandOutput>, ShutdownReceiver) -> Fut + Send + 'static,
Fut: Future<Output = ()> + Send,
Spawns an asynchronous command.
You can bind the the command to the lifetime of the component
by using a ShutdownReceiver.
Examples found in repository?
114 fn init(
115 _: Self::Init,
116 root: Self::Root,
117 sender: ComponentSender<Self>,
118 ) -> ComponentParts<Self> {
119 let model = App {
120 width: 100.0,
121 height: 100.0,
122 points: Vec::new(),
123 handler: DrawHandler::new(),
124 };
125
126 let area = model.handler.drawing_area();
127 let widgets = view_output!();
128
129 sender.command(|out, shutdown| {
130 shutdown
131 .register(async move {
132 loop {
133 tokio::time::sleep(Duration::from_millis(20)).await;
134 out.send(UpdatePointsMsg).unwrap();
135 }
136 })
137 .drop_on_shutdown()
138 });
139
140 ComponentParts { model, widgets }
141 }More examples
120 fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>, _root: &Self::Root) {
121 match message {
122 Input::Compute => {
123 self.computing = true;
124 sender.command(|out, shutdown| {
125 shutdown
126 // Performs this operation until a shutdown is triggered
127 .register(async move {
128 let mut progress = 0.0;
129
130 while progress < 1.0 {
131 out.send(CmdOut::Progress(progress)).unwrap();
132 progress += 0.1;
133 tokio::time::sleep(std::time::Duration::from_millis(333)).await;
134 }
135
136 out.send(CmdOut::Finished(Ok("42".into()))).unwrap();
137 })
138 // Perform task until a shutdown interrupts it
139 .drop_on_shutdown()
140 // Wrap into a `Pin<Box<Future>>` for return
141 .boxed()
142 });
143 }
144 }
145 }246 fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>, _root: &Self::Root) {
247 match msg {
248 AppMsg::StartGame(index) => {
249 self.start_index = Some(index);
250 sender.command(|sender, _| async move {
251 for i in (1..4).rev() {
252 *GAME_STATE.write() = GameState::Countdown(i);
253 relm4::tokio::time::sleep(Duration::from_millis(1000)).await;
254 }
255 *GAME_STATE.write() = GameState::Running;
256 for _ in 0..20 {
257 relm4::tokio::time::sleep(Duration::from_millis(500)).await;
258 sender.send(false).unwrap();
259 }
260 relm4::tokio::time::sleep(Duration::from_millis(1000)).await;
261 sender.send(true).unwrap();
262 });
263 }
264 AppMsg::StopGame => {
265 *GAME_STATE.write() = GameState::Guessing;
266 }
267 AppMsg::SelectedGuess(index) => {
268 *GAME_STATE.write() = GameState::End(index == self.start_index.take().unwrap());
269 }
270 }
271 }Sourcepub fn spawn_command<Cmd>(&self, cmd: Cmd)
pub fn spawn_command<Cmd>(&self, cmd: Cmd)
Spawns a synchronous command.
This is particularly useful for CPU-intensive background jobs that need to run on a thread-pool in the background.
If you expect the component to be dropped while the command is running take care while sending messages!
Sourcepub fn oneshot_command<Fut>(&self, future: Fut)
pub fn oneshot_command<Fut>(&self, future: Fut)
Spawns a future that will be dropped as soon as the factory component is shut down.
Essentially, this is a simpler version of Self::command().
Examples found in repository?
156 fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>, _root: &Self::Root) {
157 match message {
158 Input::AddSetting {
159 description,
160 button,
161 id,
162 } => {
163 self.options.push((description, button, id));
164 }
165
166 Input::Clear => {
167 self.options.clear();
168
169 // Perform this async operation.
170 sender.oneshot_command(async move {
171 tokio::time::sleep(std::time::Duration::from_secs(2)).await;
172 CmdOut::Reload
173 });
174 }
175
176 Input::Reload => {
177 sender.output(Output::Reload).unwrap();
178 }
179 }
180 }More examples
141 fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>, root: &Self::Root) {
142 match msg {
143 AppMsg::StartSearch => {
144 self.searching = true;
145
146 let stream = Dialog::builder()
147 .transient_for(root)
148 .launch(())
149 .into_stream();
150 sender.oneshot_command(async move {
151 // Use the component as stream
152 let result = stream.recv_one().await;
153
154 if let Some(search) = result {
155 let response =
156 reqwest::get(format!("https://duckduckgo.com/lite/?q={search}"))
157 .await
158 .unwrap();
159 let response_text = response.text().await.unwrap();
160
161 // Extract the url of the first search result.
162 if let Some(url) = response_text.split("<a rel=\"nofollow\" href=\"").nth(1)
163 {
164 let url = url.split('\"').next().unwrap().replace("amp;", "");
165 Some(format!("https:{url}"))
166 } else {
167 None
168 }
169 } else {
170 None
171 }
172 });
173 }
174 }
175 }Sourcepub fn spawn_oneshot_command<Cmd>(&self, cmd: Cmd)
pub fn spawn_oneshot_command<Cmd>(&self, cmd: Cmd)
Spawns a synchronous command that will be dropped as soon as the factory component is shut down.
Essentially, this is a simpler version of Self::spawn_command().