kas_core/runner/
runner.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`] and supporting elements
7
8use super::{AppData, GraphicsInstance, Platform, ProxyAction, Result, Shared};
9use crate::config::{Config, ConfigFactory};
10#[allow(unused)] use crate::event::ConfigCx;
11use crate::theme::Theme;
12use crate::window::{WindowId, WindowIdFactory};
13use std::cell::RefCell;
14use std::rc::Rc;
15use winit::event_loop::{EventLoop, EventLoopProxy};
16
17/// State used to launch the UI
18///
19/// This is a low-level type; it is recommended to instead use
20/// [`Runner`](https://docs.rs/kas/latest/kas/runner/struct.Runner.html).
21#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
22#[cfg_attr(docsrs, doc(cfg(internal_doc)))]
23pub struct PreLaunchState {
24    config: Rc<RefCell<Config>>,
25    config_writer: Option<Box<dyn FnMut(&Config)>>,
26    el: EventLoop<ProxyAction>,
27    platform: Platform,
28    window_id_factory: WindowIdFactory,
29}
30
31impl PreLaunchState {
32    /// Construct
33    pub fn new<C: ConfigFactory>(config: C) -> Result<Self> {
34        let mut cf = config;
35        let config = cf.read_config()?;
36        config.borrow_mut().init();
37
38        let el = EventLoop::with_user_event().build()?;
39        let platform = Platform::new(&el);
40        Ok(PreLaunchState {
41            config,
42            config_writer: cf.writer(),
43            el,
44            platform,
45            window_id_factory: Default::default(),
46        })
47    }
48
49    /// Access config
50    #[inline]
51    pub fn config(&self) -> &Rc<RefCell<Config>> {
52        &self.config
53    }
54
55    /// Generate a [`WindowId`]
56    #[inline]
57    pub fn next_window_id(&mut self) -> WindowId {
58        self.window_id_factory.make_next()
59    }
60
61    /// Get the platform
62    #[inline]
63    pub fn platform(&self) -> Platform {
64        self.platform
65    }
66
67    /// Create a proxy which can be used to update the UI from another thread
68    pub fn create_proxy(&self) -> Proxy {
69        Proxy(self.el.create_proxy())
70    }
71
72    /// Run the main loop
73    pub fn run<Data: AppData, G: GraphicsInstance, T: Theme<G::Shared>>(
74        self,
75        data: Data,
76        graphical: G,
77        theme: T,
78        windows: Vec<Box<super::Window<Data, G, T>>>,
79    ) -> Result<()> {
80        let shared = Shared::<Data, _, _>::new(
81            self.platform,
82            graphical,
83            theme,
84            self.config,
85            self.config_writer,
86            create_waker(&self.el),
87            #[cfg(feature = "accesskit")]
88            Proxy(self.el.create_proxy()),
89            self.window_id_factory,
90        )?;
91
92        let mut l = super::Loop::new(windows, shared, data);
93        self.el.run_app(&mut l)?;
94        Ok(())
95    }
96}
97
98impl Platform {
99    /// Get platform
100    #[allow(clippy::needless_return)]
101    fn new(_el: &EventLoop<ProxyAction>) -> Platform {
102        // Logic copied from winit::platform_impl module.
103
104        #[cfg(target_os = "windows")]
105        return Platform::Windows;
106
107        #[cfg(any(
108            target_os = "linux",
109            target_os = "dragonfly",
110            target_os = "freebsd",
111            target_os = "netbsd",
112            target_os = "openbsd"
113        ))]
114        {
115            cfg_if::cfg_if! {
116                if #[cfg(all(feature = "wayland", feature = "x11"))] {
117                    use winit::platform::wayland::EventLoopExtWayland;
118                    return if _el.is_wayland() {
119                        Platform::Wayland
120                    } else {
121                        Platform::X11
122                    };
123                } else if #[cfg(feature = "wayland")] {
124                    return Platform::Wayland;
125                } else if #[cfg(feature = "x11")] {
126                    return Platform::X11;
127                } else {
128                    compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
129                }
130            }
131        }
132
133        #[cfg(target_os = "macos")]
134        return Platform::MacOS;
135
136        #[cfg(target_os = "android")]
137        return Platform::Android;
138
139        #[cfg(target_os = "ios")]
140        return Platform::IOS;
141
142        #[cfg(target_arch = "wasm32")]
143        return Platform::Web;
144
145        // Otherwise platform is unsupported!
146    }
147}
148
149/// Create a waker
150///
151/// This waker may be used by a [`Future`](std::future::Future) to revive
152/// event handling.
153fn create_waker(el: &EventLoop<ProxyAction>) -> std::task::Waker {
154    use std::sync::{Arc, Mutex};
155    use std::task::{RawWaker, RawWakerVTable, Waker};
156
157    // NOTE: Proxy is Send but not Sync. Mutex<T> is Sync for T: Send.
158    // We wrap with Arc which is a Sync type supporting Clone and into_raw.
159    type Data = Mutex<Proxy>;
160    let proxy = Proxy(el.create_proxy());
161    let a: Arc<Data> = Arc::new(Mutex::new(proxy));
162    let data = Arc::into_raw(a);
163
164    const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
165
166    unsafe fn clone(data: *const ()) -> RawWaker {
167        unsafe {
168            let a = Arc::from_raw(data as *const Data);
169            let c = Arc::into_raw(a.clone());
170            let _do_not_drop = Arc::into_raw(a);
171            RawWaker::new(c as *const (), &VTABLE)
172        }
173    }
174    unsafe fn wake(data: *const ()) {
175        unsafe {
176            let a = Arc::from_raw(data as *const Data);
177            a.lock().unwrap().wake_async();
178        }
179    }
180    unsafe fn wake_by_ref(data: *const ()) {
181        unsafe {
182            let a = Arc::from_raw(data as *const Data);
183            a.lock().unwrap().wake_async();
184            let _do_not_drop = Arc::into_raw(a);
185        }
186    }
187    unsafe fn drop(data: *const ()) {
188        unsafe {
189            let _ = Arc::from_raw(data as *const Data);
190        }
191    }
192
193    let raw_waker = RawWaker::new(data as *const (), &VTABLE);
194    unsafe { Waker::from_raw(raw_waker) }
195}
196
197/// A proxy allowing control of a UI from another thread.
198///
199/// Created by [`Runner::create_proxy`](https://docs.rs/kas/latest/kas/runner/struct.Runner.html#method.create_proxy).
200#[derive(Clone)]
201pub struct Proxy(pub(super) EventLoopProxy<ProxyAction>);
202
203/// Error type returned by [`Proxy`] functions.
204///
205/// This error occurs only if the [`Runner`](https://docs.rs/kas/latest/kas/runner/struct.Runner.html) already terminated.
206pub struct ClosedError;
207
208impl Proxy {
209    /// Close a specific window.
210    pub fn close(&self, id: WindowId) -> std::result::Result<(), ClosedError> {
211        self.0
212            .send_event(ProxyAction::Close(id))
213            .map_err(|_| ClosedError)
214    }
215
216    /// Close all windows and terminate the UI.
217    pub fn close_all(&self) -> std::result::Result<(), ClosedError> {
218        self.0
219            .send_event(ProxyAction::CloseAll)
220            .map_err(|_| ClosedError)
221    }
222
223    /// Send a message to [`AppData`] or a set recipient
224    ///
225    /// This is similar to [`EventCx::push`](crate::event::EventCx::push),
226    /// but can only be handled by top-level [`AppData`] or a recipient set
227    /// using [`ConfigCx::set_send_target_for`].
228    pub fn push<M: std::fmt::Debug + Send + 'static>(
229        &mut self,
230        msg: M,
231    ) -> std::result::Result<(), ClosedError> {
232        self.0
233            .send_event(ProxyAction::Message(kas::messages::SendErased::new(msg)))
234            .map_err(|_| ClosedError)
235    }
236
237    /// Wake async methods
238    fn wake_async(&self) {
239        // ignore error: if the loop closed the future has been dropped
240        let _ = self.0.send_event(ProxyAction::WakeAsync);
241    }
242}