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}