kas_core/messages.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//! Standard messages
7//!
8//! These are messages that may be sent via [`EventCx::push`](crate::event::EventCx::push).
9
10#[allow(unused)] use crate::Events;
11use std::any::Any;
12use std::fmt::Debug;
13
14use crate::event::PhysicalKey;
15
16/// Message: activate
17///
18/// Example: a button's label has a keyboard shortcut; this message is sent by the label to
19/// trigger the button.
20///
21/// Payload: the key press which caused this message to be emitted, if any.
22#[derive(Copy, Clone, Debug)]
23pub struct Activate(pub Option<PhysicalKey>);
24
25/// Message: select child
26///
27/// Example: a list supports selection; a child emits this to cause itself to be selected.
28#[derive(Clone, Debug)]
29pub struct Select;
30
31/// A type-erased value
32///
33/// This is vaguely a wrapper over `Box<dyn (Any + Debug)>`, except that Rust
34/// doesn't (yet) support multi-trait objects.
35pub struct Erased {
36 // TODO: use trait_upcasting feature when stable: Box<dyn AnyDebug>
37 // where trait AnyDebug: Any + Debug {}. This replaces the fmt field.
38 any: Box<dyn Any>,
39 #[cfg(debug_assertions)]
40 fmt: String,
41}
42
43impl Erased {
44 /// Construct
45 pub fn new<V: Any + Debug>(v: V) -> Self {
46 #[cfg(debug_assertions)]
47 let fmt = format!("{}::{:?}", std::any::type_name::<V>(), &v);
48 let any = Box::new(v);
49 Erased {
50 #[cfg(debug_assertions)]
51 fmt,
52 any,
53 }
54 }
55
56 /// Returns `true` if the inner type is the same as `T`.
57 pub fn is<T: 'static>(&self) -> bool {
58 self.any.is::<T>()
59 }
60
61 /// Attempt to downcast self to a concrete type.
62 pub fn downcast<T: 'static>(self) -> Result<Box<T>, Box<dyn Any>> {
63 self.any.downcast::<T>()
64 }
65
66 /// Returns some reference to the inner value if it is of type `T`, or `None` if it isn’t.
67 pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
68 self.any.downcast_ref::<T>()
69 }
70}
71
72/// Support debug formatting
73///
74/// Debug builds only. On release builds, a placeholder message is printed.
75impl std::fmt::Debug for Erased {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
77 #[cfg(debug_assertions)]
78 let r = f.write_str(&self.fmt);
79 #[cfg(not(debug_assertions))]
80 let r = f.write_str("[use debug build to see value]");
81 r
82 }
83}
84
85/// Like Erased, but supporting Send
86#[derive(Debug)]
87pub(crate) struct SendErased {
88 any: Box<dyn Any + Send>,
89 #[cfg(debug_assertions)]
90 fmt: String,
91}
92
93impl SendErased {
94 /// Construct
95 pub fn new<V: Any + Send + Debug>(v: V) -> Self {
96 #[cfg(debug_assertions)]
97 let fmt = format!("{}::{:?}", std::any::type_name::<V>(), &v);
98 let any = Box::new(v);
99 SendErased {
100 #[cfg(debug_assertions)]
101 fmt,
102 any,
103 }
104 }
105
106 /// Convert to [`Erased`]
107 pub fn into_erased(self) -> Erased {
108 Erased {
109 any: self.any,
110 #[cfg(debug_assertions)]
111 fmt: self.fmt,
112 }
113 }
114}
115
116/// A type-erased message stack
117///
118/// This is a stack over [`Erased`], with some downcasting methods.
119/// It is a component of [`EventCx`](crate::event::EventCx) and usually only
120/// used through that, thus the interface here is incomplete.
121#[must_use]
122#[derive(Debug, Default)]
123pub struct MessageStack {
124 base: usize,
125 count: usize,
126 stack: Vec<Erased>,
127}
128
129impl MessageStack {
130 /// Construct an empty stack
131 #[inline]
132 pub fn new() -> Self {
133 MessageStack::default()
134 }
135
136 /// Set the "stack base" to the current length
137 ///
138 /// Any messages on the stack before this method is called cannot be removed
139 /// until the base has been reset. This allows multiple widget tree
140 /// traversals with a single stack.
141 #[inline]
142 pub(crate) fn set_base(&mut self) {
143 self.base = self.stack.len();
144 }
145
146 /// Get the current operation count
147 ///
148 /// This is incremented every time the message stack is changed.
149 #[inline]
150 pub(crate) fn get_op_count(&self) -> usize {
151 self.count
152 }
153
154 /// Reset the base; return true if messages are available after reset
155 #[inline]
156 pub(crate) fn reset_and_has_any(&mut self) -> bool {
157 self.base = 0;
158 !self.stack.is_empty()
159 }
160
161 /// True if the stack has messages available
162 #[inline]
163 pub fn has_any(&self) -> bool {
164 self.stack.len() > self.base
165 }
166
167 /// Push a type-erased message to the stack
168 #[inline]
169 pub(crate) fn push_erased(&mut self, msg: Erased) {
170 self.count = self.count.wrapping_add(1);
171 self.stack.push(msg);
172 }
173
174 /// Try popping the last message from the stack with the given type
175 ///
176 /// This method may be called from [`Events::handle_messages`].
177 pub fn try_pop<M: Debug + 'static>(&mut self) -> Option<M> {
178 if self.has_any() && self.stack.last().map(|m| m.is::<M>()).unwrap_or(false) {
179 self.count = self.count.wrapping_add(1);
180 self.stack.pop().unwrap().downcast::<M>().ok().map(|m| *m)
181 } else {
182 None
183 }
184 }
185
186 /// Try observing the last message on the stack without popping
187 ///
188 /// This method may be called from [`Events::handle_messages`].
189 pub fn try_observe<M: Debug + 'static>(&self) -> Option<&M> {
190 if self.has_any() {
191 self.stack.last().and_then(|m| m.downcast_ref::<M>())
192 } else {
193 None
194 }
195 }
196
197 /// Try getting a debug representation of the last message on the stack
198 ///
199 /// Note: this method will always return `None` in release builds.
200 /// This may or may not change in future versions.
201 pub fn try_debug(&self) -> Option<&dyn Debug> {
202 cfg_if::cfg_if! {
203 if #[cfg(debug_assertions)] {
204 if let Some(m) = self.stack.last(){
205 println!("message: {:?}", &m.fmt);
206 } else {
207 println!("empty stack");
208 }
209 self.stack.last().map(|m| &m.fmt as &dyn Debug)
210 } else {
211 println!("release");
212 None
213 }
214 }
215 }
216}
217
218impl Drop for MessageStack {
219 fn drop(&mut self) {
220 for msg in self.stack.drain(..) {
221 log::warn!(target: "kas_core::erased", "unhandled: {msg:?}");
222 }
223 }
224}