relm4/component/async/
builder.rs1use super::super::MessageBroker;
6use super::{AsyncComponent, AsyncComponentParts, AsyncConnector};
7use crate::channel::AsyncComponentSender;
8use crate::{
9 GuardedReceiver, Receiver, RelmContainerExt, RelmWidgetExt, RuntimeSenders, Sender,
10 late_initialization,
11};
12use gtk::glib;
13use gtk::prelude::{GtkWindowExt, NativeDialogExt};
14use std::any;
15use std::marker::PhantomData;
16use tracing::info_span;
17
18#[derive(Debug)]
20pub struct AsyncComponentBuilder<C: AsyncComponent> {
21 pub root: C::Root,
23 priority: glib::Priority,
24
25 pub(super) component: PhantomData<C>,
26}
27
28impl<C: AsyncComponent> Default for AsyncComponentBuilder<C> {
29 fn default() -> Self {
31 Self {
32 root: C::init_root(),
33 priority: glib::Priority::default(),
34 component: PhantomData,
35 }
36 }
37}
38
39impl<C: AsyncComponent> AsyncComponentBuilder<C> {
40 #[must_use]
42 pub fn update_root<F: FnOnce(&mut C::Root)>(mut self, func: F) -> Self {
43 func(&mut self.root);
44 self
45 }
46
47 pub const fn widget(&self) -> &C::Root {
49 &self.root
50 }
51
52 pub fn priority(mut self, priority: glib::Priority) -> Self {
60 self.priority = priority;
61 self
62 }
63}
64
65impl<C: AsyncComponent> AsyncComponentBuilder<C>
66where
67 C::Root: AsRef<gtk::Widget>,
68{
69 #[must_use]
71 pub fn attach_to<T: RelmContainerExt<Child = gtk::Widget>>(self, container: &T) -> Self {
72 container.container_add(self.root.as_ref());
73
74 self
75 }
76}
77
78impl<C: AsyncComponent> AsyncComponentBuilder<C>
79where
80 C::Root: AsRef<gtk::Window> + Clone,
81{
82 #[must_use]
91 pub fn transient_for(self, widget: impl AsRef<gtk::Widget>) -> Self {
92 let widget = widget.as_ref().clone();
93 let root = self.root.clone();
94 late_initialization::register_callback(Box::new(move || {
95 if let Some(window) = widget.toplevel_window() {
96 root.as_ref().set_transient_for(Some(&window));
97 } else {
98 tracing::error!("Couldn't find root of transient widget");
99 }
100 }));
101
102 self
103 }
104}
105
106impl<C: AsyncComponent> AsyncComponentBuilder<C>
107where
108 C::Root: AsRef<gtk::NativeDialog> + Clone,
109{
110 #[must_use]
120 pub fn transient_for_native(self, widget: impl AsRef<gtk::Widget>) -> Self {
121 let widget = widget.as_ref().clone();
122 let root = self.root.clone();
123 late_initialization::register_callback(Box::new(move || {
124 if let Some(window) = widget.toplevel_window() {
125 root.as_ref().set_transient_for(Some(&window));
126 } else {
127 tracing::error!("Couldn't find root of transient widget");
128 }
129 }));
130
131 self
132 }
133}
134
135impl<C: AsyncComponent> AsyncComponentBuilder<C> {
136 pub fn launch(self, payload: C::Init) -> AsyncConnector<C> {
138 let (input_sender, input_receiver) = crate::channel::<C::Input>();
140
141 self.launch_with_input_channel(payload, input_sender, input_receiver)
142 }
143
144 pub fn launch_with_broker(
150 self,
151 payload: C::Init,
152 broker: &MessageBroker<C::Input>,
153 ) -> AsyncConnector<C> {
154 let (input_sender, input_receiver) = broker.get_channel();
155 self.launch_with_input_channel(
156 payload,
157 input_sender,
158 input_receiver.expect("Message broker launched multiple times"),
159 )
160 }
161
162 fn launch_with_input_channel(
163 self,
164 payload: C::Init,
165 input_sender: Sender<C::Input>,
166 input_receiver: Receiver<C::Input>,
167 ) -> AsyncConnector<C> {
168 let Self { root, priority, .. } = self;
169 let temp_widgets = C::init_loading_widgets(root.clone());
170
171 let RuntimeSenders {
172 output_sender,
173 output_receiver,
174 cmd_sender,
175 cmd_receiver,
176 shutdown_notifier,
177 shutdown_recipient,
178 shutdown_on_drop: destroy_on_drop,
179 mut shutdown_event,
180 } = RuntimeSenders::<C::Output, C::CommandOutput>::new();
181
182 let component_sender = AsyncComponentSender::new(
184 input_sender.clone(),
185 output_sender.clone(),
186 cmd_sender,
187 shutdown_recipient,
188 );
189
190 let rt_root = root.clone();
191
192 crate::spawn_local_with_priority(priority, async move {
196 let mut state = C::init(payload, rt_root.clone(), component_sender.clone()).await;
197 drop(temp_widgets);
198
199 let mut cmd = GuardedReceiver::new(cmd_receiver);
200 let mut input = GuardedReceiver::new(input_receiver);
201
202 loop {
203 futures::select!(
204 message = input => {
207 let AsyncComponentParts {
208 model,
209 widgets,
210 } = &mut state;
211
212 let span = info_span!(
213 "update_with_view",
214 input=?message,
215 component=any::type_name::<C>(),
216 id=model.id(),
217 );
218 let _enter = span.enter();
219
220 model.update_with_view(widgets, message, component_sender.clone(), &rt_root).await;
221 }
222
223 message = cmd => {
225 let AsyncComponentParts {
226 model,
227 widgets,
228 } = &mut state;
229
230 let span = info_span!(
231 "update_cmd_with_view",
232 cmd_output=?message,
233 component=any::type_name::<C>(),
234 id=model.id(),
235 );
236 let _enter = span.enter();
237
238 model.update_cmd_with_view(widgets, message, component_sender.clone(), &rt_root).await;
239 }
240
241 _ = shutdown_event => {
243 let AsyncComponentParts {
244 model,
245 widgets,
246 } = &mut state;
247
248 model.shutdown(widgets, output_sender);
249
250 shutdown_notifier.shutdown();
251
252 return;
253 }
254 );
255 }
256 });
257
258 AsyncConnector {
260 widget: root,
261 sender: input_sender,
262 receiver: output_receiver,
263 shutdown_on_drop: destroy_on_drop,
264 }
265 }
266}