1use super::super::MessageBroker;
6use super::{Component, ComponentParts, Connector, StateWatcher};
7use crate::{
8 ComponentSender, GuardedReceiver, Receiver, RelmContainerExt, RelmWidgetExt, RuntimeSenders,
9 Sender, late_initialization,
10};
11use gtk::glib;
12use gtk::prelude::{GtkWindowExt, NativeDialogExt};
13use std::any;
14use std::cell::RefCell;
15use std::marker::PhantomData;
16use std::rc::Rc;
17use tracing::info_span;
18
19#[derive(Debug)]
21pub struct ComponentBuilder<C: Component> {
22 pub root: C::Root,
24 priority: glib::Priority,
25
26 pub(super) component: PhantomData<C>,
27}
28
29impl<C: Component> Default for ComponentBuilder<C> {
30 fn default() -> Self {
32 Self {
33 root: C::init_root(),
34 priority: glib::Priority::default(),
35 component: PhantomData,
36 }
37 }
38}
39
40impl<C: Component> ComponentBuilder<C> {
41 #[must_use]
43 pub fn update_root<F: FnOnce(&mut C::Root)>(mut self, func: F) -> Self {
44 func(&mut self.root);
45 self
46 }
47
48 pub const fn widget(&self) -> &C::Root {
50 &self.root
51 }
52
53 pub fn priority(mut self, priority: glib::Priority) -> Self {
61 self.priority = priority;
62 self
63 }
64}
65
66impl<C: Component> ComponentBuilder<C>
67where
68 C::Root: AsRef<gtk::Widget>,
69{
70 #[must_use]
72 pub fn attach_to<T: RelmContainerExt<Child = gtk::Widget>>(self, container: &T) -> Self {
73 container.container_add(self.root.as_ref());
74
75 self
76 }
77}
78
79impl<C: Component> ComponentBuilder<C>
80where
81 C::Root: AsRef<gtk::Window> + Clone,
82{
83 #[must_use]
92 pub fn transient_for(self, widget: impl AsRef<gtk::Widget>) -> Self {
93 let widget = widget.as_ref().clone();
94 let root = self.root.clone();
95 late_initialization::register_callback(Box::new(move || {
96 if let Some(window) = widget.toplevel_window() {
97 root.as_ref().set_transient_for(Some(&window));
98 } else {
99 tracing::error!("Couldn't find root of transient widget");
100 }
101 }));
102
103 self
104 }
105}
106
107impl<C: Component> ComponentBuilder<C>
108where
109 C::Root: AsRef<gtk::NativeDialog> + Clone,
110{
111 #[must_use]
121 pub fn transient_for_native(self, widget: impl AsRef<gtk::Widget>) -> Self {
122 let widget = widget.as_ref().clone();
123 let root = self.root.clone();
124 late_initialization::register_callback(Box::new(move || {
125 if let Some(window) = widget.toplevel_window() {
126 root.as_ref().set_transient_for(Some(&window));
127 } else {
128 tracing::error!("Couldn't find root of transient widget");
129 }
130 }));
131
132 self
133 }
134}
135
136impl<C: Component> ComponentBuilder<C> {
137 pub fn launch(self, payload: C::Init) -> Connector<C> {
139 let (input_sender, input_receiver) = crate::channel::<C::Input>();
141
142 self.launch_with_input_channel(payload, input_sender, input_receiver)
143 }
144
145 pub fn launch_with_broker(
151 self,
152 payload: C::Init,
153 broker: &MessageBroker<C::Input>,
154 ) -> Connector<C> {
155 let (input_sender, input_receiver) = broker.get_channel();
156 self.launch_with_input_channel(
157 payload,
158 input_sender,
159 input_receiver.expect("Message broker launched multiple times"),
160 )
161 }
162
163 fn launch_with_input_channel(
164 self,
165 payload: C::Init,
166 input_sender: Sender<C::Input>,
167 input_receiver: Receiver<C::Input>,
168 ) -> Connector<C> {
169 let Self { root, priority, .. } = self;
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,
179 mut shutdown_event,
180 } = RuntimeSenders::<C::Output, C::CommandOutput>::new();
181
182 let (notifier, notifier_receiver) = crate::channel();
184
185 let component_sender = ComponentSender::new(
187 input_sender.clone(),
188 output_sender.clone(),
189 cmd_sender,
190 shutdown_recipient,
191 );
192
193 let state = Rc::new(RefCell::new(C::init(
195 payload,
196 root.clone(),
197 component_sender.clone(),
198 )));
199 let watcher = StateWatcher {
200 state,
201 notifier,
202 shutdown_on_drop,
203 };
204
205 let rt_state = watcher.state.clone();
206 let rt_root = root.clone();
207
208 crate::spawn_local_with_priority(priority, async move {
212 let mut notifier = GuardedReceiver::new(notifier_receiver);
213 let mut cmd = GuardedReceiver::new(cmd_receiver);
214 let mut input = GuardedReceiver::new(input_receiver);
215 loop {
216 futures::select!(
217 message = input => {
220 let ComponentParts {
221 model,
222 widgets,
223 } = &mut *rt_state.borrow_mut();
224
225 let span = info_span!(
226 "update_with_view",
227 input=?message,
228 component=any::type_name::<C>(),
229 id=model.id(),
230 );
231 let _enter = span.enter();
232
233 model.update_with_view(widgets, message, component_sender.clone(), &rt_root);
234 }
235
236 message = cmd => {
238 let ComponentParts {
239 model,
240 widgets,
241 } = &mut *rt_state.borrow_mut();
242
243 let span = info_span!(
244 "update_cmd_with_view",
245 cmd_output=?message,
246 component=any::type_name::<C>(),
247 id=model.id(),
248 );
249 let _enter = span.enter();
250
251 model.update_cmd_with_view(widgets, message, component_sender.clone(), &rt_root);
252 }
253
254 _ = notifier => {
256 let ComponentParts {
257 model,
258 widgets,
259 } = &mut *rt_state.borrow_mut();
260
261 model.update_view(widgets, component_sender.clone());
262 }
263
264 _ = shutdown_event => {
266 let ComponentParts {
267 model,
268 widgets,
269 } = &mut *rt_state.borrow_mut();
270
271 model.shutdown(widgets, output_sender);
272
273 shutdown_notifier.shutdown();
274
275 return;
276 }
277 );
278 }
279 });
280
281 Connector {
283 state: watcher,
284 widget: root,
285 sender: input_sender,
286 receiver: output_receiver,
287 }
288 }
289}