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        WindowId,
50    },
51};
52
53use crate::{
54    accessibility::AccessibilityTask,
55    config::{
56        OnCloseHook,
57        WindowConfig,
58    },
59    drivers::GraphicsDriver,
60    plugins::{
61        PluginEvent,
62        PluginHandle,
63        PluginsManager,
64    },
65    renderer::{
66        NativeEvent,
67        NativeWindowEvent,
68        NativeWindowEventAction,
69    },
70};
71
72pub struct AppWindow {
73    pub(crate) runner: Runner,
74    pub(crate) tree: Tree,
75    pub(crate) driver: GraphicsDriver,
76    pub(crate) window: Window,
77    pub(crate) nodes_state: NodesState<NodeId>,
78
79    pub(crate) position: CursorPoint,
80    pub(crate) mouse_state: ElementState,
81    pub(crate) modifiers_state: ModifiersState,
82    pub(crate) just_focused: bool,
83
84    pub(crate) events_receiver: futures_channel::mpsc::UnboundedReceiver<EventsChunk>,
85    pub(crate) events_sender: futures_channel::mpsc::UnboundedSender<EventsChunk>,
86
87    pub(crate) accessibility: AccessibilityTree,
88    pub(crate) accessibility_adapter: accesskit_winit::Adapter,
89    pub(crate) accessibility_tasks_for_next_render: AccessibilityTask,
90
91    pub(crate) process_layout_on_next_render: bool,
92
93    pub(crate) waker: Waker,
94
95    pub(crate) ticker_sender: RenderingTickerSender,
96
97    pub(crate) platform: Platform,
98
99    pub(crate) animation_clock: AnimationClock,
100
101    pub(crate) background: Color,
102
103    pub(crate) dropped_file_paths: Vec<PathBuf>,
104
105    pub(crate) on_close: Option<OnCloseHook>,
106}
107
108impl AppWindow {
109    #[allow(clippy::too_many_arguments)]
110    pub fn new(
111        mut window_config: WindowConfig,
112        active_event_loop: &ActiveEventLoop,
113        event_loop_proxy: &EventLoopProxy<NativeEvent>,
114        plugins: &mut PluginsManager,
115        font_collection: &FontCollection,
116        font_manager: &FontMgr,
117        fallback_fonts: &[Cow<'static, str>],
118        screen_reader: ScreenReader,
119    ) -> Self {
120        let mut window_attributes = Window::default_attributes()
121            .with_resizable(window_config.resizable)
122            .with_window_icon(window_config.icon.take())
123            .with_visible(false)
124            .with_title(window_config.title)
125            .with_decorations(window_config.decorations)
126            .with_transparent(window_config.transparent)
127            .with_inner_size(LogicalSize::<f64>::from(window_config.size));
128
129        if let Some(min_size) = window_config.min_size {
130            window_attributes =
131                window_attributes.with_min_inner_size(LogicalSize::<f64>::from(min_size));
132        }
133        if let Some(max_size) = window_config.max_size {
134            window_attributes =
135                window_attributes.with_max_inner_size(LogicalSize::<f64>::from(max_size));
136        }
137        if let Some(window_attributes_hook) = window_config.window_attributes_hook.take() {
138            window_attributes = window_attributes_hook(window_attributes, active_event_loop);
139        }
140        let (driver, mut window) =
141            GraphicsDriver::new(active_event_loop, window_attributes, &window_config);
142
143        if let Some(window_handle_hook) = window_config.window_handle_hook.take() {
144            window_handle_hook(&mut window);
145        }
146
147        let on_close = window_config.on_close.take();
148
149        let (events_sender, events_receiver) = futures_channel::mpsc::unbounded();
150
151        let mut runner = Runner::new(move || integration(window_config.app.clone()).into_element());
152
153        runner.provide_root_context(|| screen_reader);
154
155        let (mut ticker_sender, ticker) = RenderingTicker::new();
156        ticker_sender.set_overflow(true);
157        runner.provide_root_context(|| ticker);
158
159        let animation_clock = AnimationClock::new();
160        runner.provide_root_context(|| animation_clock.clone());
161
162        runner.provide_root_context(AssetCacher::create);
163        let mut tree = Tree::default();
164
165        let window_size = window.inner_size();
166        let platform = runner.provide_root_context({
167            let event_loop_proxy = event_loop_proxy.clone();
168            let window_id = window.id();
169            let theme = match window.theme() {
170                Some(Theme::Dark) => PreferredTheme::Dark,
171                _ => PreferredTheme::Light,
172            };
173            move || Platform {
174                focused_accessibility_id: State::create(ACCESSIBILITY_ROOT_ID),
175                focused_accessibility_node: State::create(accesskit::Node::new(
176                    accesskit::Role::Window,
177                )),
178                root_size: State::create(Size2D::new(
179                    window_size.width as f32,
180                    window_size.height as f32,
181                )),
182                navigation_mode: State::create(NavigationMode::NotKeyboard),
183                preferred_theme: State::create(theme),
184                sender: Rc::new(move |user_event| {
185                    event_loop_proxy
186                        .send_event(NativeEvent::Window(NativeWindowEvent {
187                            window_id,
188                            action: NativeWindowEventAction::User(user_event),
189                        }))
190                        .unwrap();
191                }),
192            }
193        });
194
195        let clipboard = {
196            if let Ok(handle) = window.display_handle() {
197                #[allow(clippy::match_single_binding)]
198                match handle.as_raw() {
199                    #[cfg(target_os = "linux")]
200                    RawDisplayHandle::Wayland(handle) => {
201                        let (_primary, clipboard) = unsafe {
202                            use freya_clipboard::copypasta::wayland_clipboard;
203
204                            wayland_clipboard::create_clipboards_from_external(
205                                handle.display.as_ptr(),
206                            )
207                        };
208                        let clipboard: Box<dyn ClipboardProvider> = Box::new(clipboard);
209                        Some(clipboard)
210                    }
211                    _ => ClipboardContext::new().ok().map(|c| {
212                        let clipboard: Box<dyn ClipboardProvider> = Box::new(c);
213                        clipboard
214                    }),
215                }
216            } else {
217                None
218            }
219        };
220
221        runner.provide_root_context(|| State::create(clipboard));
222
223        runner.provide_root_context(|| tree.accessibility_generator.clone());
224
225        runner.provide_root_context(|| tree.accessibility_generator.clone());
226
227        plugins.send(
228            PluginEvent::RunnerCreated {
229                runner: &mut runner,
230            },
231            PluginHandle::new(event_loop_proxy),
232        );
233
234        let mutations = runner.sync_and_update();
235        tree.apply_mutations(mutations);
236        tree.measure_layout(
237            (
238                window.inner_size().width as f32,
239                window.inner_size().height as f32,
240            )
241                .into(),
242            font_collection,
243            font_manager,
244            &events_sender,
245            window.scale_factor(),
246            fallback_fonts,
247        );
248
249        let nodes_state = NodesState::default();
250
251        let accessibility_adapter =
252            Adapter::with_event_loop_proxy(active_event_loop, &window, event_loop_proxy.clone());
253
254        window.set_visible(true);
255        window.set_ime_allowed(true);
256
257        struct TreeHandle(EventLoopProxy<NativeEvent>, WindowId);
258
259        impl ArcWake for TreeHandle {
260            fn wake_by_ref(arc_self: &Arc<Self>) {
261                _ = arc_self
262                    .0
263                    .send_event(NativeEvent::Window(NativeWindowEvent {
264                        window_id: arc_self.1,
265                        action: NativeWindowEventAction::PollRunner,
266                    }));
267            }
268        }
269
270        let waker = waker(Arc::new(TreeHandle(event_loop_proxy.clone(), window.id())));
271
272        plugins.send(
273            PluginEvent::WindowCreated {
274                window: &window,
275                font_collection,
276                tree: &tree,
277                animation_clock: &animation_clock,
278                runner: &mut runner,
279            },
280            PluginHandle::new(event_loop_proxy),
281        );
282
283        AppWindow {
284            runner,
285            tree,
286            driver,
287            window,
288            nodes_state,
289
290            mouse_state: ElementState::Released,
291            position: CursorPoint::default(),
292            modifiers_state: ModifiersState::default(),
293            just_focused: false,
294
295            events_receiver,
296            events_sender,
297
298            accessibility: AccessibilityTree::default(),
299            accessibility_adapter,
300            accessibility_tasks_for_next_render: AccessibilityTask::ProcessUpdate { mode: None },
301
302            process_layout_on_next_render: true,
303
304            waker,
305
306            ticker_sender,
307
308            platform,
309
310            animation_clock,
311
312            background: window_config.background,
313
314            dropped_file_paths: Vec::new(),
315
316            on_close,
317        }
318    }
319
320    pub fn window(&self) -> &Window {
321        &self.window
322    }
323
324    pub fn window_mut(&mut self) -> &mut Window {
325        &mut self.window
326    }
327}