Skip to main content

kas_core/runner/
mod.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//! Runner, platforms and backends
7
8mod common;
9mod event_loop;
10mod runner;
11mod shared;
12mod window;
13
14use crate::ConfigAction;
15use crate::messages::Erased;
16use crate::window::{BoxedWindow, PopupDescriptor, WindowId};
17use event_loop::Loop;
18use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
19pub(crate) use shared::RunnerT;
20use shared::Shared;
21use std::fmt::Debug;
22pub use window::Window;
23pub(crate) use window::WindowDataErased;
24
25pub use common::{Error, Platform, Result};
26pub use runner::{ClosedError, PreLaunchState, Proxy};
27
28#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
29#[cfg_attr(docsrs, doc(cfg(internal_doc)))]
30pub use common::{GraphicsFeatures, GraphicsInstance, RunError, WindowSurface};
31
32#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
33#[cfg_attr(docsrs, doc(cfg(internal_doc)))]
34pub extern crate raw_window_handle;
35
36/// Any handle supporting both [`HasDisplayHandle`] and [`HasWindowHandle`]
37pub trait HasDisplayAndWindowHandle: HasDisplayHandle + HasWindowHandle + 'static {}
38impl<W: HasDisplayHandle + HasWindowHandle + 'static> HasDisplayAndWindowHandle for W {}
39
40/// A type-erased message stack
41///
42/// This is a stack over [`Erased`], with some downcasting methods.
43/// It is a component of [`EventCx`](crate::event::EventCx) and usually only
44/// used through that, thus the interface here is incomplete.
45#[must_use]
46#[derive(Debug, Default)]
47pub(crate) struct MessageStack {
48    base: usize,
49    count: usize,
50    stack: Vec<Erased>,
51}
52
53impl MessageStack {
54    /// Construct an empty stack
55    #[inline]
56    pub(crate) fn new() -> Self {
57        MessageStack::default()
58    }
59
60    /// Set the "stack base" to the current length
61    ///
62    /// Any messages on the stack before this method is called cannot be removed
63    /// until the base has been reset. This allows multiple widget tree
64    /// traversals with a single stack.
65    #[inline]
66    pub(crate) fn set_base(&mut self) {
67        self.base = self.stack.len();
68    }
69
70    /// Get the current operation count
71    ///
72    /// This is incremented every time the message stack is changed.
73    #[inline]
74    pub(crate) fn get_op_count(&self) -> usize {
75        self.count
76    }
77
78    /// Reset the base; return true if messages are available after reset
79    #[inline]
80    pub(crate) fn reset_and_has_any(&mut self) -> bool {
81        self.base = 0;
82        !self.stack.is_empty()
83    }
84
85    /// True if the stack has messages available
86    #[inline]
87    pub fn has_any(&self) -> bool {
88        self.stack.len() > self.base
89    }
90
91    /// Push a type-erased message to the stack
92    #[inline]
93    pub(crate) fn push_erased(&mut self, msg: Erased) {
94        self.count = self.count.wrapping_add(1);
95        self.stack.push(msg);
96    }
97
98    /// Drop messages above the stack base.
99    pub(crate) fn drop_unsent(&mut self) {
100        self.stack.drain(self.base..);
101    }
102
103    /// Drop remaining messages and reset
104    pub(crate) fn clear(&mut self) {
105        for msg in self.stack.drain(..) {
106            if msg.is::<crate::event::components::KineticStart>() {
107                // We can safely ignore this message
108                continue;
109            }
110
111            log::warn!(target: "kas_core::erased", "unhandled: {msg:?}");
112        }
113
114        self.base = 0;
115        self.count = 0;
116    }
117}
118
119/// This trait allows peeking and popping messages from the stack
120pub trait ReadMessage {
121    /// Pop a type-erased message from the stack, if non-empty
122    fn pop_erased(&mut self) -> Option<Erased>;
123
124    /// Try popping the last message from the stack with the given type
125    fn try_pop<M: Debug + 'static>(&mut self) -> Option<M>;
126
127    /// Try observing the last message on the stack without popping
128    fn try_peek<M: Debug + 'static>(&self) -> Option<&M>;
129
130    /// Debug the last message on the stack, if any
131    fn peek_debug(&self) -> Option<&dyn Debug>;
132}
133
134impl ReadMessage for MessageStack {
135    #[inline]
136    fn pop_erased(&mut self) -> Option<Erased> {
137        self.count = self.count.wrapping_add(1);
138        self.stack.pop()
139    }
140
141    fn try_pop<M: Debug + 'static>(&mut self) -> Option<M> {
142        if self.has_any() && self.stack.last().map(|m| m.is::<M>()).unwrap_or(false) {
143            self.count = self.count.wrapping_add(1);
144            self.stack.pop().unwrap().downcast::<M>().ok().map(|m| *m)
145        } else {
146            None
147        }
148    }
149
150    fn try_peek<M: Debug + 'static>(&self) -> Option<&M> {
151        if self.has_any() {
152            self.stack.last().and_then(|m| m.downcast_ref::<M>())
153        } else {
154            None
155        }
156    }
157
158    fn peek_debug(&self) -> Option<&dyn Debug> {
159        self.stack.last().map(Erased::debug)
160    }
161}
162
163impl Drop for MessageStack {
164    fn drop(&mut self) {
165        self.clear();
166    }
167}
168
169/// Application state
170///
171/// Kas allows application state to be stored both in the  widget tree (in
172/// `Adapt` nodes and user-defined widgets) and by the application root (shared
173/// across windows). This trait must be implemented by the latter.
174///
175/// When no top-level data is required, use `()` which implements this trait.
176///
177/// TODO: should we pass some type of interface to the runner to these methods?
178/// We could pass a `&mut dyn RunnerT` easily, but that trait is not public.
179pub trait AppData: 'static {
180    /// Handle a message
181    ///
182    /// This is the last message handler. If, traversing the widget tree
183    /// (see [kas::event] module doc), the message stack is not empty, this
184    /// method is called repeatedly until either the message stack is empty or
185    /// the last call did not remove a message from the stack.
186    ///
187    /// If any message is pushed or popped, widgets will be
188    /// [updated](crate::Events#update) on all windows.
189    ///
190    /// Unhandled messages will result in warnings in the log.
191    fn handle_message(&mut self, messages: &mut impl ReadMessage);
192
193    /// Application is being resumed
194    ///
195    /// This method is called on application start on all platforms. It is also
196    /// called when an application is made active again after being
197    /// [suspended](Self::suspended).
198    fn resumed(&mut self) {}
199
200    /// Application is being suspended
201    ///
202    /// The application should ensure any important state is saved.
203    ///
204    /// This method is called when the application has been suspended or is
205    /// about to exit (on Android/iOS/Web platforms, the application may resume
206    /// after this method is called; on other platforms this probably indicates
207    /// imminent closure). Widget state may still exist, but is not live
208    /// (widgets will not process events or messages).
209    fn suspended(&mut self) {}
210
211    /// Memory warning
212    ///
213    /// Some platforms warn applications when available memory is low.
214    /// Applies to: Android, iOS.
215    fn memory_warning(&mut self) {}
216}
217
218impl AppData for () {
219    fn handle_message(&mut self, _: &mut impl ReadMessage) {}
220    fn suspended(&mut self) {}
221}
222
223enum Pending<A: AppData> {
224    Update,
225    ConfigUpdate(ConfigAction),
226    AddPopup(WindowId, WindowId, PopupDescriptor),
227    RepositionPopup(WindowId, PopupDescriptor),
228    AddWindow(WindowId, BoxedWindow<A>),
229    CloseWindow(WindowId),
230    Exit,
231}
232
233#[derive(Debug)]
234enum ProxyAction {
235    CloseAll,
236    Close(WindowId),
237    Message(kas::messages::SendErased),
238    #[cfg(feature = "accesskit")]
239    AccessKit(winit::window::WindowId, accesskit_winit::WindowEvent),
240}
241
242#[cfg(feature = "accesskit")]
243impl From<accesskit_winit::Event> for ProxyAction {
244    fn from(event: accesskit_winit::Event) -> Self {
245        compile_error!("AccessKit is not compatible with the current winit version")
246        // ProxyAction::AccessKit(event.window_id, event.window_event)
247    }
248}
249
250#[cfg(test)]
251mod test {
252    use super::*;
253
254    #[test]
255    fn size_of_pending() {
256        assert_eq!(std::mem::size_of::<Pending<()>>(), 40);
257    }
258}