Skip to main content

kas_core/event/cx/
send.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Event context: event send / replay
7
8use super::{EventCx, EventState};
9use crate::event::{Command, Event, Scroll, ScrollDelta, Used};
10use crate::messages::Erased;
11use crate::runner::ReadMessage;
12#[allow(unused)]
13use crate::{Events, Tile, Widget, event::ConfigCx};
14use crate::{Id, Node};
15use std::fmt::Debug;
16use std::task::Poll;
17
18impl EventState {
19    /// Send a message to `id`
20    ///
21    /// When calling this method, be aware that some widgets use an inner
22    /// component to handle events, thus calling with the outer widget's `id`
23    /// may not have the desired effect. [`Tile::try_probe`] and
24    /// [`EventCx::next_nav_focus`] are usually able to find the appropriate
25    /// event-handling target.
26    ///
27    /// This uses a tree traversal as with event handling, thus ancestors will
28    /// have a chance to handle an unhandled event and any messages on the stack
29    /// after their child.
30    ///
31    /// ### Special cases sent as an [`Event`]
32    ///
33    /// When `M` is `Command`, this will send [`Event::Command`] to the widget.
34    ///
35    /// When `M` is `ScrollDelta`, this will send [`Event::Scroll`] to the
36    /// widget.
37    ///
38    /// ### Other messages
39    ///
40    /// The message is pushed to the message stack. The target widget may
41    /// [pop](EventCx::try_pop) or [peek](EventCx::try_peek) the message from
42    /// [`Events::handle_messages`].
43    ///
44    /// ### Send target
45    ///
46    /// The target `id` may be under another window. In this case, sending may
47    /// be delayed slightly.
48    ///
49    /// If `id = Id::default()` and a [send target](super::ConfigCx::set_send_target_for)
50    /// has been assigned for `M`, then `msg` will be sent to that target.
51    ///
52    /// If `id` identifies a widget, that widget must be configured but does not
53    /// need to be sized.
54    ///
55    /// If `id` does not resolve a widget, the message is dropped with only a
56    /// DEBUG log message of the form `_replay: $WIDGET cannot find path to $ID`.
57    /// This is not considered an error since it happens frequently (e.g. any
58    /// widget using asynchronous loading sends itself a message; if the view
59    /// changes before loading completes then that widget may no longer be
60    /// accessible or no longer exist).
61    ///
62    /// ### Ordering and failure
63    ///
64    /// Messages sent via `send` and [`send_erased`](Self::send_erased) should
65    /// be received in the same order sent relative to other messages sent from
66    /// the same window via these methods.
67    ///
68    /// Message delivery may fail for widgets not currently visible. This is
69    /// dependent on widgets implementation of [`Widget::child_node`] (e.g. in
70    /// the case of virtual scrolling, the target may have scrolled out of
71    /// range).
72    pub fn send<M: Debug + 'static>(&mut self, id: Id, msg: M) {
73        self.send_erased(id, Erased::new(msg));
74    }
75
76    /// Push a type-erased message to the stack
77    ///
78    /// This is a lower-level variant of [`Self::send`].
79    pub fn send_erased(&mut self, id: Id, msg: Erased) {
80        self.send_queue.push_back((id, msg));
81    }
82
83    /// Send a message to `id` via a [`Future`]
84    ///
85    /// The future is polled after event handling and after drawing and is able
86    /// to wake the event loop. This future is executed on the main thread; for
87    /// high-CPU tasks use [`Self::send_spawn`] instead.
88    ///
89    /// The future must resolve to a message on completion. Its message is then
90    /// sent to `id` via stack traversal identically to [`Self::send`].
91    ///
92    /// ### Cancellation, ordering and failure
93    ///
94    /// Futures passed to these methods should only be cancelled if they fail to
95    /// complete before the window is closed, and even in this case will be
96    /// polled at least once.
97    ///
98    /// Messages sent via `send_async`,
99    /// [`send_async_erased`](Self::send_async_erased) and
100    /// [`send_spawn`](Self::send_spawn) may be received in any order.
101    ///
102    /// Message delivery may fail for widgets not currently visible. This is
103    /// dependent on widgets implementation of [`Widget::child_node`] (e.g. in
104    /// the case of virtual scrolling, the target may have scrolled out of
105    /// range).
106    pub fn send_async<Fut, M>(&mut self, id: Id, fut: Fut)
107    where
108        Fut: IntoFuture<Output = M> + 'static,
109        M: Debug + 'static,
110    {
111        self.send_async_erased(id, async { Erased::new(fut.await) });
112    }
113
114    /// Send a type-erased message to `id` via a [`Future`]
115    ///
116    /// This is a low-level variant of [`Self::send_async`].
117    pub fn send_async_erased<Fut>(&mut self, id: Id, fut: Fut)
118    where
119        Fut: IntoFuture<Output = Erased> + 'static,
120    {
121        let fut = Box::pin(fut.into_future());
122        self.fut_messages.push((id, fut));
123    }
124
125    /// Spawn a task, run on a thread pool
126    ///
127    /// The future is spawned to a thread-pool before the event-handling loop
128    /// sleeps, and is able to wake the loop on completion. Tasks involving
129    /// significant CPU work should use this method over [`Self::send_async`].
130    ///
131    /// This method is simply a wrapper around [`async_global_executor::spawn`]
132    /// and [`Self::send_async`]; if a different multi-threaded executor is
133    /// available, that may be used instead. See also [`async_global_executor`]
134    /// documentation of configuration.
135    #[cfg(feature = "spawn")]
136    pub fn send_spawn<Fut, M>(&mut self, id: Id, fut: Fut)
137    where
138        Fut: IntoFuture<Output = M> + 'static,
139        Fut::IntoFuture: Send,
140        M: Debug + Send + 'static,
141    {
142        self.send_async(id, async_global_executor::spawn(fut.into_future()));
143    }
144}
145
146impl<'a> EventCx<'a> {
147    /// Get the index of the last child visited
148    ///
149    /// This is only used when unwinding (traversing back up the widget tree),
150    /// and returns the index of the child last visited. E.g. when
151    /// [`Events::handle_messages`] is called, this method returns the index of
152    /// the child which submitted the message (or whose descendant did).
153    /// Otherwise this returns `None` (including when the widget itself is the
154    /// submitter of the message).
155    pub fn last_child(&self) -> Option<usize> {
156        self.last_child
157    }
158
159    /// Push a message to the stack
160    ///
161    /// The message is first type-erased by wrapping with [`Erased`],
162    /// then pushed to the stack.
163    ///
164    /// The message may be [popped](EventCx::try_pop) or
165    /// [peeked](EventCx::try_peek) from [`Events::handle_messages`]
166    /// by the widget itself, its parent, or any ancestor.
167    ///
168    /// If not handled during the widget tree traversal and
169    /// [a target is set for `M`](ConfigCx::set_send_target_for) then `msg` is
170    /// sent to this target.
171    pub fn push<M: Debug + 'static>(&mut self, msg: M) {
172        self.push_erased(Erased::new(msg));
173    }
174
175    /// Push a type-erased message to the stack
176    ///
177    /// This is a lower-level variant of [`Self::push`].
178    pub fn push_erased(&mut self, msg: Erased) {
179        self.runner.message_stack_mut().push_erased(msg);
180    }
181
182    /// True if the message stack is non-empty
183    pub fn has_msg(&self) -> bool {
184        self.runner.message_stack().has_any()
185    }
186
187    /// Try popping the last message from the stack with the given type
188    ///
189    /// This method may be called from [`Events::handle_messages`].
190    pub fn try_pop<M: Debug + 'static>(&mut self) -> Option<M> {
191        self.runner.message_stack_mut().try_pop()
192    }
193
194    /// Try observing the last message on the stack without popping
195    ///
196    /// This method may be called from [`Events::handle_messages`].
197    pub fn try_peek<M: Debug + 'static>(&self) -> Option<&M> {
198        self.runner.message_stack().try_peek()
199    }
200
201    /// Debug the last message on the stack, if any
202    pub fn peek_debug(&self) -> Option<&dyn Debug> {
203        self.runner.message_stack().peek_debug()
204    }
205
206    /// Get the message stack operation count
207    ///
208    /// This is incremented every time the message stack is changed, thus can be
209    /// used to test whether a message handler did anything.
210    #[inline]
211    pub fn msg_op_count(&self) -> usize {
212        self.runner.message_stack().get_op_count()
213    }
214
215    /// Drop messages above the stack base.
216    pub(crate) fn drop_unsent(&mut self) {
217        self.runner.message_stack_mut().drop_unsent();
218    }
219
220    /// Set a scroll action
221    ///
222    /// When setting [`Scroll::Rect`], use the widget's own coordinate space.
223    ///
224    /// Note that calling this method has no effect on the widget itself, but
225    /// affects parents via their [`Events::handle_scroll`] method.
226    #[inline]
227    pub fn set_scroll(&mut self, scroll: Scroll) {
228        self.scroll = scroll;
229    }
230
231    pub(crate) fn post_send(&mut self, index: usize) -> Option<Scroll> {
232        self.last_child = Some(index);
233        (self.scroll != Scroll::None).then_some(self.scroll.clone())
234    }
235
236    /// Send a few message types as an Event, replay other messages as if pushed by `id`
237    ///
238    /// Optionally, push `msg` and set `scroll` as if pushed/set by `id`.
239    pub(super) fn send_or_replay(&mut self, mut widget: Node<'_>, id: Id, mut msg: Erased) {
240        if msg.is::<Command>() {
241            let cmd = *msg.downcast().unwrap();
242            if !self.send_event(widget, id, Event::Command(cmd, None)) {
243                match cmd {
244                    Command::Exit => self.runner.exit(),
245                    Command::Close => self.handle_close(),
246                    _ => (),
247                }
248            }
249        } else if msg.is::<ScrollDelta>() {
250            let event = Event::Scroll(*msg.downcast().unwrap());
251            self.send_event(widget, id, event);
252        } else {
253            self.pre_recursion();
254            self.runner.message_stack_mut().set_base();
255            log::trace!(target: "kas_core::event", "replay: id={id}: {msg:?}");
256
257            self.target_is_disabled = false;
258            msg.set_sent();
259            self.push_erased(msg);
260            widget._replay(self, id);
261            self.post_recursion();
262        }
263    }
264
265    /// Replay a scroll action
266    #[cfg(feature = "accesskit")]
267    pub(super) fn replay_scroll(&mut self, mut widget: Node<'_>, id: Id, scroll: Scroll) {
268        log::trace!(target: "kas_core::event", "replay_scroll: id={id}: {scroll:?}");
269        self.pre_recursion();
270        self.scroll = scroll;
271        self.runner.message_stack_mut().set_base();
272
273        self.target_is_disabled = false;
274        widget._replay(self, id);
275        self.post_recursion();
276    }
277
278    // Call Widget::_send; returns true when event is used
279    pub(super) fn send_event(&mut self, mut widget: Node<'_>, mut id: Id, event: Event) -> bool {
280        self.pre_recursion();
281        self.runner.message_stack_mut().set_base();
282        log::trace!(target: "kas_core::event", "send_event: id={id}: {event:?}");
283
284        // TODO(opt): we should be able to use binary search here
285        let mut disabled = false;
286        if !event.pass_when_disabled() {
287            for d in &self.disabled {
288                if d.is_ancestor_of(&id) {
289                    id = d.clone();
290                    disabled = true;
291                }
292            }
293            if disabled {
294                log::trace!(target: "kas_core::event", "target is disabled; sending to ancestor {id}");
295            }
296        }
297        self.target_is_disabled = disabled;
298
299        let used = widget._send(self, id, event) == Used;
300
301        self.post_recursion();
302        used
303    }
304
305    pub(super) fn poll_futures(&mut self) {
306        let mut i = 0;
307        while i < self.state.fut_messages.len() {
308            let (_, fut) = &mut self.cx.state.fut_messages[i];
309            let mut cx = std::task::Context::from_waker(self.runner.waker());
310            match fut.as_mut().poll(&mut cx) {
311                Poll::Pending => {
312                    i += 1;
313                }
314                Poll::Ready(msg) => {
315                    let (id, _) = self.state.fut_messages.remove(i);
316
317                    // Send via queue to support send targets and inter-window sending
318                    self.send_queue.push_back((id, msg));
319                }
320            }
321        }
322    }
323}