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 }
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 }
101 fn init(
102 _init: Self::Init,
103 root: Self::Root,
104 sender: ComponentSender<Self>,
105 ) -> ComponentParts<Self> {
106 // We don't have access to the parent window from here
107 // but we can just use the button to set the transient window for the dialog.
108 // Relm4 will get the window later by calling [`WidgetExt::root()`]
109 // on the button once all widgets are connected.
110 let dialog = Dialog::builder()
111 .transient_for(&root)
112 .launch_with_broker((), &DIALOG_BROKER)
113 .forward(sender.input_sender(), identity);
114
115 let model = Button { dialog };
116 let widgets = view_output!();
117 ComponentParts { model, widgets }
118 }
119
120 fn update(&mut self, _msg: Self::Input, _sender: ComponentSender<Self>) {}
121}
122
123#[derive(Debug)]
124enum AppMsg {}
125
126struct App {
127 button: Controller<Button>,
128}
129
130#[relm4::component]
131impl SimpleComponent for App {
132 type Init = ();
133 type Input = AppMsg;
134 type Output = ();
135
136 view! {
137 main_window = gtk::ApplicationWindow {
138 set_default_size: (500, 250),
139 set_child: Some(model.button.widget()),
140 }
141 }
142
143 fn init(
144 _init: Self::Init,
145 root: Self::Root,
146 sender: ComponentSender<Self>,
147 ) -> ComponentParts<Self> {
148 let button = Button::builder()
149 .launch(())
150 .forward(sender.input_sender(), identity);
151 let model = App { button };
152 let widgets = view_output!();
153 ComponentParts { model, widgets }
154 }
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 }
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?
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 }
More examples
277 fn update_cmd(
278 &mut self,
279 msg: Self::CommandOutput,
280 sender: ComponentSender<Self>,
281 _root: &Self::Root,
282 ) {
283 if msg {
284 sender.input(AppMsg::StopGame);
285 } else {
286 let mut counters_guard = self.counters.guard();
287 match rand::random::<u8>() % 3 {
288 0 => {
289 counters_guard.swap(1, 2);
290 }
291 1 => {
292 counters_guard.swap(0, 1);
293 }
294 _ => {
295 let widget = counters_guard.widget();
296 if !widget.select_next_page() {
297 widget.select_previous_page();
298 }
299 }
300 }
301 }
302 }
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 = App { 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(Msg::Increment);
68 }
69 ));
70
71 dec_button.connect_clicked(clone!(
72 #[strong]
73 sender,
74 move |_| {
75 sender.input(Msg::Decrement);
76 }
77 ));
78
79 let widgets = AppWidgets { label };
80
81 ComponentParts { model, widgets }
82 }
68 fn init(
69 _args: Self::Init,
70 root: Self::Root,
71 sender: ComponentSender<Self>,
72 ) -> ComponentParts<Self> {
73 relm4::view! {
74 container = gtk::Box {
75 set_halign: gtk::Align::Center,
76 set_valign: gtk::Align::Center,
77 set_width_request: 300,
78 set_spacing: 12,
79 set_margin_top: 4,
80 set_margin_bottom: 4,
81 set_margin_start: 12,
82 set_margin_end: 12,
83 set_orientation: gtk::Orientation::Horizontal,
84
85 gtk::Box {
86 set_spacing: 4,
87 set_hexpand: true,
88 set_valign: gtk::Align::Center,
89 set_orientation: gtk::Orientation::Vertical,
90
91 append: label = >k::Label {
92 set_xalign: 0.0,
93 set_label: "Find the answer to life:",
94 },
95
96 append: progress = >k::ProgressBar {
97 set_visible: false,
98 },
99 },
100
101 append: button = >k::Button {
102 set_label: "Compute",
103 connect_clicked => Input::Compute,
104 }
105 }
106 }
107
108 root.set_child(Some(&container));
109
110 ComponentParts {
111 model: App::default(),
112 widgets: Widgets {
113 label,
114 button,
115 progress,
116 },
117 }
118 }
93 fn init(
94 counter: Self::Init,
95 root: Self::Root,
96 sender: ComponentSender<Self>,
97 ) -> ComponentParts<Self> {
98 // ============================================================
99 //
100 // You can also use menu! outside of the widget macro.
101 // This is the manual equivalent to the the menu! macro above.
102 //
103 // ============================================================
104 //
105 // relm4::menu! {
106 // main_menu: {
107 // custom: "my_widget",
108 // "Example" => ExampleAction,
109 // "Example2" => ExampleAction,
110 // "Example toggle" => ExampleU8Action(1_u8),
111 // section! {
112 // "Section example" => ExampleAction,
113 // "Example toggle" => ExampleU8Action(1_u8),
114 // },
115 // section! {
116 // "Example" => ExampleAction,
117 // "Example2" => ExampleAction,
118 // "Example Value" => ExampleU8Action(1_u8),
119 // }
120 // }
121 // };
122
123 let model = Self { counter };
124 let widgets = view_output!();
125
126 let app = relm4::main_application();
127 app.set_accelerators_for_action::<ExampleAction>(&["<primary>W"]);
128
129 let action: RelmAction<ExampleAction> = {
130 RelmAction::new_stateless(move |_| {
131 println!("Statelesss action!");
132 sender.input(Msg::Increment);
133 })
134 };
135
136 let action2: RelmAction<ExampleU8Action> =
137 RelmAction::new_stateful_with_target_value(&0, |_, state, _value| {
138 *state ^= 1;
139 dbg!(state);
140 });
141
142 let mut group = RelmActionGroup::<WindowActionGroup>::new();
143 group.add_action(action);
144 group.add_action(action2);
145 group.register_for_widget(&widgets.main_window);
146
147 ComponentParts { model, widgets }
148 }
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: App::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 if let Some((description, button_label, id)) = self.options.last() {
200 let id = *id;
201 relm4::view! {
202 widget = gtk::Box {
203 set_orientation: gtk::Orientation::Horizontal,
204 set_margin_start: 20,
205 set_margin_end: 20,
206 set_margin_top: 8,
207 set_margin_bottom: 8,
208 set_spacing: 24,
209
210 append = >k::Label {
211 set_label: description,
212 set_halign: gtk::Align::Start,
213 set_hexpand: true,
214 set_valign: gtk::Align::Center,
215 set_ellipsize: gtk::pango::EllipsizeMode::End,
216 },
217
218 append: button = >k::Button {
219 set_label: button_label,
220 set_size_group: &widgets.button_sg,
221
222 connect_clicked[sender] => move |_| {
223 sender.output(Output::Clicked(id)).unwrap();
224 }
225 }
226 }
227 }
228
229 widgets.list.append(&widget);
230 widgets.options.push(widget);
231 }
232 }
233 }
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 }
250 fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>, _root: &Self::Root) {
251 match msg {
252 AppMsg::StartGame(index) => {
253 self.start_index = Some(index);
254 sender.command(|sender, _| async move {
255 for i in (1..4).rev() {
256 *GAME_STATE.write() = GameState::Countdown(i);
257 relm4::tokio::time::sleep(Duration::from_millis(1000)).await;
258 }
259 *GAME_STATE.write() = GameState::Running;
260 for _ in 0..20 {
261 relm4::tokio::time::sleep(Duration::from_millis(500)).await;
262 sender.send(false).unwrap();
263 }
264 relm4::tokio::time::sleep(Duration::from_millis(1000)).await;
265 sender.send(true).unwrap();
266 });
267 }
268 AppMsg::StopGame => {
269 *GAME_STATE.write() = GameState::Guessing;
270 }
271 AppMsg::SelectedGuess(index) => {
272 *GAME_STATE.write() = GameState::End(index == self.start_index.take().unwrap());
273 }
274 }
275 }
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()
.