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