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