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