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}