Skip to main content

freya_winit/
window.rs

1use std::{
2    borrow::Cow,
3    path::PathBuf,
4    rc::Rc,
5    sync::Arc,
6    task::Waker,
7};
8
9use accesskit_winit::Adapter;
10use freya_clipboard::copypasta::{
11    ClipboardContext,
12    ClipboardProvider,
13};
14use freya_components::{
15    cache::AssetCacher,
16    integration::integration,
17};
18use freya_core::{
19    integration::*,
20    prelude::Color,
21};
22use freya_engine::prelude::{
23    FontCollection,
24    FontMgr,
25};
26use futures_util::task::{
27    ArcWake,
28    waker,
29};
30use ragnarok::NodesState;
31use raw_window_handle::HasDisplayHandle;
32#[cfg(target_os = "linux")]
33use raw_window_handle::RawDisplayHandle;
34use torin::prelude::{
35    CursorPoint,
36    Size2D,
37};
38use winit::{
39    dpi::LogicalSize,
40    event::ElementState,
41    event_loop::{
42        ActiveEventLoop,
43        EventLoopProxy,
44    },
45    keyboard::ModifiersState,
46    window::{
47        Theme,
48        Window,
49        WindowAttributes,
50        WindowId,
51    },
52};
53
54use crate::{
55    accessibility::AccessibilityTask,
56    config::{
57        OnCloseHook,
58        WindowConfig,
59    },
60    drivers::GraphicsDriver,
61    plugins::{
62        PluginEvent,
63        PluginHandle,
64        PluginsManager,
65    },
66    renderer::{
67        NativeEvent,
68        NativeWindowEvent,
69        NativeWindowEventAction,
70    },
71};
72
73pub struct AppWindow {
74    pub(crate) runner: Runner,
75    pub(crate) tree: Tree,
76    pub(crate) driver: GraphicsDriver,
77    pub(crate) window: Window,
78    pub(crate) nodes_state: NodesState<NodeId>,
79
80    pub(crate) position: CursorPoint,
81    pub(crate) mouse_state: ElementState,
82    pub(crate) modifiers_state: ModifiersState,
83    pub(crate) just_focused: bool,
84
85    pub(crate) events_receiver: futures_channel::mpsc::UnboundedReceiver<EventsChunk>,
86    pub(crate) events_sender: futures_channel::mpsc::UnboundedSender<EventsChunk>,
87
88    pub(crate) accessibility: AccessibilityTree,
89    pub(crate) accessibility_adapter: accesskit_winit::Adapter,
90    pub(crate) accessibility_tasks_for_next_render: AccessibilityTask,
91
92    pub(crate) process_layout_on_next_render: bool,
93
94    pub(crate) waker: Waker,
95
96    pub(crate) ticker_sender: RenderingTickerSender,
97
98    pub(crate) platform: Platform,
99
100    pub(crate) animation_clock: AnimationClock,
101
102    pub(crate) background: Color,
103
104    pub(crate) dropped_file_paths: Vec<PathBuf>,
105
106    pub(crate) on_close: Option<OnCloseHook>,
107
108    pub(crate) window_attributes: WindowAttributes,
109    #[cfg(feature = "hotreload")]
110    pub(crate) hot_reload_pending: Arc<std::sync::atomic::AtomicBool>,
111}
112
113impl AppWindow {
114    #[allow(clippy::too_many_arguments)]
115    pub fn new(
116        mut window_config: WindowConfig,
117        active_event_loop: &ActiveEventLoop,
118        event_loop_proxy: &EventLoopProxy<NativeEvent>,
119        plugins: &mut PluginsManager,
120        font_collection: &mut FontCollection,
121        font_manager: &FontMgr,
122        fallback_fonts: &[Cow<'static, str>],
123        screen_reader: ScreenReader,
124    ) -> Self {
125        #[cfg(feature = "hotreload")]
126        let hot_reload_pending = Arc::new(std::sync::atomic::AtomicBool::new(false));
127        let mut window_attributes = Window::default_attributes()
128            .with_resizable(window_config.resizable)
129            .with_window_icon(window_config.icon.take())
130            .with_visible(false)
131            .with_title(window_config.title)
132            .with_decorations(window_config.decorations)
133            .with_transparent(window_config.transparent)
134            .with_inner_size(LogicalSize::<f64>::from(window_config.size));
135
136        if let Some(min_size) = window_config.min_size {
137            window_attributes =
138                window_attributes.with_min_inner_size(LogicalSize::<f64>::from(min_size));
139        }
140        if let Some(max_size) = window_config.max_size {
141            window_attributes =
142                window_attributes.with_max_inner_size(LogicalSize::<f64>::from(max_size));
143        }
144        #[cfg(target_os = "linux")]
145        if let Some(app_id) = window_config.app_id.take() {
146            use winit::platform::wayland::WindowAttributesExtWayland;
147            window_attributes = window_attributes.with_name(&app_id, &app_id);
148        }
149        if let Some(window_attributes_hook) = window_config.window_attributes_hook.take() {
150            window_attributes = window_attributes_hook(window_attributes, active_event_loop);
151        }
152        let (driver, mut window) =
153            GraphicsDriver::new(active_event_loop, window_attributes.clone());
154
155        if let Some(window_handle_hook) = window_config.window_handle_hook.take() {
156            window_handle_hook(&mut window);
157        }
158
159        let on_close = window_config.on_close.take();
160
161        let (events_sender, events_receiver) = futures_channel::mpsc::unbounded();
162
163        let app = window_config.app.clone();
164        let mut runner = Runner::new({
165            let plugins = plugins.clone();
166            move || {
167                let el = integration(app.clone()).into_element();
168                plugins.wrap_root(el)
169            }
170        });
171
172        runner.provide_root_context(|| screen_reader);
173
174        let (mut ticker_sender, ticker) = RenderingTicker::new();
175        ticker_sender.set_overflow(true);
176        runner.provide_root_context(|| ticker);
177
178        let animation_clock = AnimationClock::new();
179        runner.provide_root_context(|| animation_clock.clone());
180
181        runner.provide_root_context(AssetCacher::create);
182        let mut tree = Tree::default();
183
184        let window_size = window.inner_size();
185        let accent_color_preference = accent_color_preference();
186        let platform = runner.provide_root_context({
187            let event_loop_proxy = event_loop_proxy.clone();
188            let window_id = window.id();
189            let theme = match window.theme() {
190                Some(Theme::Dark) => PreferredTheme::Dark,
191                _ => PreferredTheme::Light,
192            };
193            move || Platform {
194                focused_accessibility_id: State::create(ACCESSIBILITY_ROOT_ID),
195                focused_accessibility_node: State::create(accesskit::Node::new(
196                    accesskit::Role::Window,
197                )),
198                root_size: State::create(Size2D::new(
199                    window_size.width as f32,
200                    window_size.height as f32,
201                )),
202                navigation_mode: State::create(NavigationMode::NotKeyboard),
203                preferred_theme: State::create(theme),
204                accent_color: State::create(accent_color_preference.accent_color),
205                sender: Rc::new(move |user_event| {
206                    event_loop_proxy
207                        .send_event(NativeEvent::Window(NativeWindowEvent {
208                            window_id,
209                            action: NativeWindowEventAction::User(user_event),
210                        }))
211                        .unwrap();
212                }),
213            }
214        });
215
216        let clipboard = {
217            if let Ok(handle) = window.display_handle() {
218                #[allow(clippy::match_single_binding)]
219                match handle.as_raw() {
220                    #[cfg(target_os = "linux")]
221                    RawDisplayHandle::Wayland(handle) => {
222                        let (_primary, clipboard) = unsafe {
223                            use freya_clipboard::copypasta::wayland_clipboard;
224
225                            wayland_clipboard::create_clipboards_from_external(
226                                handle.display.as_ptr(),
227                            )
228                        };
229                        let clipboard: Box<dyn ClipboardProvider> = Box::new(clipboard);
230                        Some(clipboard)
231                    }
232                    _ => ClipboardContext::new().ok().map(|c| {
233                        let clipboard: Box<dyn ClipboardProvider> = Box::new(c);
234                        clipboard
235                    }),
236                }
237            } else {
238                None
239            }
240        };
241
242        runner.provide_root_context(|| State::create(clipboard));
243
244        runner.provide_root_context(|| tree.accessibility_generator.clone());
245
246        runner.provide_root_context(|| tree.accessibility_generator.clone());
247
248        runner.provide_root_context(|| font_collection.clone());
249
250        plugins.send(
251            PluginEvent::RunnerCreated {
252                runner: &mut runner,
253            },
254            PluginHandle::new(event_loop_proxy),
255        );
256
257        let mutations = runner.sync_and_update();
258        tree.apply_mutations(mutations);
259        tree.measure_layout(
260            (
261                window.inner_size().width as f32,
262                window.inner_size().height as f32,
263            )
264                .into(),
265            font_collection,
266            font_manager,
267            &events_sender,
268            window.scale_factor(),
269            fallback_fonts,
270        );
271
272        let nodes_state = NodesState::default();
273
274        let accessibility_adapter =
275            Adapter::with_event_loop_proxy(active_event_loop, &window, event_loop_proxy.clone());
276
277        window.set_visible(true);
278
279        struct TreeHandle(EventLoopProxy<NativeEvent>, WindowId);
280
281        impl ArcWake for TreeHandle {
282            fn wake_by_ref(arc_self: &Arc<Self>) {
283                _ = arc_self
284                    .0
285                    .send_event(NativeEvent::Window(NativeWindowEvent {
286                        window_id: arc_self.1,
287                        action: NativeWindowEventAction::PollRunner,
288                    }));
289            }
290        }
291
292        let waker = waker(Arc::new(TreeHandle(event_loop_proxy.clone(), window.id())));
293
294        #[cfg(feature = "hotreload")]
295        {
296            let event_loop_proxy = event_loop_proxy.clone();
297            let window_id = window.id();
298            let hot_reload_pending_handler = hot_reload_pending.clone();
299            freya_core::hotreload::subsecond::register_handler(Arc::new(move || {
300                hot_reload_pending_handler.store(true, std::sync::atomic::Ordering::Release);
301                let _ = event_loop_proxy.send_event(NativeEvent::Window(NativeWindowEvent {
302                    window_id,
303                    action: NativeWindowEventAction::PollRunner,
304                }));
305            }));
306        }
307
308        plugins.send(
309            PluginEvent::WindowCreated {
310                window: &window,
311                font_collection,
312                tree: &tree,
313                animation_clock: &animation_clock,
314                runner: &mut runner,
315                graphics_driver: driver.name(),
316            },
317            PluginHandle::new(event_loop_proxy),
318        );
319
320        AppWindow {
321            runner,
322            tree,
323            driver,
324            window,
325            nodes_state,
326
327            mouse_state: ElementState::Released,
328            position: CursorPoint::default(),
329            modifiers_state: ModifiersState::default(),
330            just_focused: false,
331
332            events_receiver,
333            events_sender,
334
335            accessibility: AccessibilityTree::default(),
336            accessibility_adapter,
337            accessibility_tasks_for_next_render: AccessibilityTask::ProcessUpdate { mode: None },
338
339            process_layout_on_next_render: true,
340
341            waker,
342
343            ticker_sender,
344
345            platform,
346
347            animation_clock,
348
349            background: window_config.background,
350
351            dropped_file_paths: Vec::new(),
352
353            on_close,
354
355            window_attributes,
356
357            #[cfg(feature = "hotreload")]
358            hot_reload_pending,
359        }
360    }
361
362    pub fn window(&self) -> &Window {
363        &self.window
364    }
365
366    pub fn window_mut(&mut self) -> &mut Window {
367        &mut self.window
368    }
369}
370
371fn accent_color_preference() -> mundy::Preferences {
372    use std::sync::OnceLock;
373    static PREFERENCE: OnceLock<mundy::Preferences> = OnceLock::new();
374    *PREFERENCE.get_or_init(|| {
375        mundy::Preferences::once_blocking(
376            mundy::Interest::AccentColor,
377            std::time::Duration::from_millis(200),
378        )
379        .unwrap_or_default()
380    })
381}