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