1#[cfg(not(target_arch = "wasm32"))]
2use std::time::Instant;
3#[cfg(target_arch = "wasm32")]
4use web_time::Instant;
5
6#[cfg(target_arch = "wasm32")]
7use wgpu::web_sys;
8
9use floem_reactive::SignalUpdate;
10use peniko::kurbo::{Point, Size};
11use std::{collections::HashMap, rc::Rc};
12use winit::{
13 dpi::{LogicalPosition, LogicalSize},
14 event::WindowEvent,
15 event_loop::{ActiveEventLoop, ControlFlow},
16 window::WindowId,
17};
18
19use crate::{
20 action::{Timer, TimerToken},
21 app::{AppEventCallback, AppUpdateEvent, UserEvent, APP_UPDATE_EVENTS},
22 ext_event::EXT_EVENT_HANDLER,
23 inspector::Capture,
24 profiler::{Profile, ProfileEvent},
25 view::View,
26 window::WindowConfig,
27 window_handle::WindowHandle,
28 window_id::process_window_updates,
29 AppEvent,
30};
31
32pub(crate) struct ApplicationHandle {
33 window_handles: HashMap<winit::window::WindowId, WindowHandle>,
34 timers: HashMap<TimerToken, Timer>,
35 pub(crate) event_listener: Option<Box<AppEventCallback>>,
36}
37
38impl ApplicationHandle {
39 pub(crate) fn new() -> Self {
40 Self {
41 window_handles: HashMap::new(),
42 timers: HashMap::new(),
43 event_listener: None,
44 }
45 }
46
47 pub(crate) fn handle_user_event(&mut self, event_loop: &dyn ActiveEventLoop, event: UserEvent) {
48 match event {
49 UserEvent::AppUpdate => {
50 self.handle_update_event(event_loop);
51 }
52 UserEvent::Idle => {
53 self.idle();
54 }
55 UserEvent::QuitApp => {
56 event_loop.exit();
57 }
58 UserEvent::Reopen {
59 has_visible_windows,
60 } => {
61 if let Some(action) = self.event_listener.as_ref() {
62 action(AppEvent::Reopen {
63 has_visible_windows,
64 });
65 }
66 }
67 UserEvent::GpuResourcesUpdate { window_id } => {
68 self.window_handles
69 .get_mut(&window_id)
70 .unwrap()
71 .init_renderer();
72 }
73 }
74 }
75
76 pub(crate) fn handle_update_event(&mut self, event_loop: &dyn ActiveEventLoop) {
77 let events = APP_UPDATE_EVENTS.with(|events| {
78 let mut events = events.borrow_mut();
79 std::mem::take(&mut *events)
80 });
81
82 for event in events {
83 match event {
84 AppUpdateEvent::NewWindow { window_creation } => self.new_window(
85 event_loop,
86 window_creation.view_fn,
87 window_creation.config.unwrap_or_default(),
88 ),
89 AppUpdateEvent::CloseWindow { window_id } => {
90 self.close_window(window_id, event_loop);
91 }
92 AppUpdateEvent::RequestTimer { timer } => {
93 self.request_timer(timer, event_loop);
94 }
95 AppUpdateEvent::CancelTimer { timer } => {
96 self.remove_timer(&timer);
97 }
98 AppUpdateEvent::CaptureWindow { window_id, capture } => {
99 capture.set(self.capture_window(window_id).map(Rc::new));
100 }
101 AppUpdateEvent::ProfileWindow {
102 window_id,
103 end_profile,
104 } => {
105 let handle = self.window_handles.get_mut(&window_id);
106 if let Some(handle) = handle {
107 if let Some(profile) = end_profile {
108 profile.set(handle.profile.take().map(|mut profile| {
109 profile.next_frame();
110 Rc::new(profile)
111 }));
112 } else {
113 handle.profile = Some(Profile::default());
114 }
115 }
116 }
117 AppUpdateEvent::MenuAction { action_id } => {
118 for (_, handle) in self.window_handles.iter_mut() {
119 if handle.app_state.context_menu.contains_key(&action_id)
120 || handle.app_state.window_menu.contains_key(&action_id)
121 {
122 handle.menu_action(&action_id);
123 break;
124 }
125 }
126 }
127 }
128 }
129 }
130
131 pub(crate) fn handle_window_event(
132 &mut self,
133 window_id: winit::window::WindowId,
134 event: WindowEvent,
135 event_loop: &dyn ActiveEventLoop,
136 ) {
137 let window_handle = match self.window_handles.get_mut(&window_id) {
138 Some(window_handle) => window_handle,
139 None => return,
140 };
141
142 let start = window_handle.profile.is_some().then(|| {
143 let name = match event {
144 WindowEvent::ActivationTokenDone { .. } => "ActivationTokenDone",
145 WindowEvent::SurfaceResized(..) => "Resized",
146 WindowEvent::Moved(..) => "Moved",
147 WindowEvent::CloseRequested => "CloseRequested",
148 WindowEvent::Destroyed => "Destroyed",
149 WindowEvent::DroppedFile(_) => "DroppedFile",
150 WindowEvent::HoveredFile(_) => "HoveredFile",
151 WindowEvent::HoveredFileCancelled => "HoveredFileCancelled",
152 WindowEvent::Focused(..) => "Focused",
153 WindowEvent::KeyboardInput { .. } => "KeyboardInput",
154 WindowEvent::ModifiersChanged(..) => "ModifiersChanged",
155 WindowEvent::Ime(..) => "Ime",
156 WindowEvent::PointerMoved { .. } => "PointerMoved",
157 WindowEvent::PointerEntered { .. } => "PointerEntered",
158 WindowEvent::PointerLeft { .. } => "PointerLeft",
159 WindowEvent::MouseWheel { .. } => "MouseWheel",
160 WindowEvent::PointerButton { .. } => "PointerButton",
161 WindowEvent::TouchpadPressure { .. } => "TouchpadPressure",
162 WindowEvent::ScaleFactorChanged { .. } => "ScaleFactorChanged",
163 WindowEvent::ThemeChanged(..) => "ThemeChanged",
164 WindowEvent::Occluded(..) => "Occluded",
165 WindowEvent::RedrawRequested => "RedrawRequested",
166 WindowEvent::PinchGesture { .. } => "PinchGesture",
167 WindowEvent::PanGesture { .. } => "PanGesture",
168 WindowEvent::DoubleTapGesture { .. } => "DoubleTapGesture",
169 WindowEvent::RotationGesture { .. } => "RotationGesture",
170 };
172 (
173 name,
174 Instant::now(),
175 matches!(event, WindowEvent::RedrawRequested),
176 )
177 });
178
179 match event {
180 WindowEvent::ActivationTokenDone { .. } => {}
181 WindowEvent::SurfaceResized(size) => {
182 let size: LogicalSize<f64> = size.to_logical(window_handle.scale);
183 let size = Size::new(size.width, size.height);
184 window_handle.size(size);
185 }
186 WindowEvent::Moved(position) => {
187 let position: LogicalPosition<f64> = position.to_logical(window_handle.scale);
188 let point = Point::new(position.x, position.y);
189 window_handle.position(point);
190 }
191 WindowEvent::CloseRequested => {
192 self.close_window(window_id, event_loop);
193 }
194 WindowEvent::Destroyed => {
195 self.close_window(window_id, event_loop);
196 }
197 WindowEvent::DroppedFile(path) => {
198 window_handle.dropped_file(path);
199 }
200 WindowEvent::HoveredFile(_) => {}
201 WindowEvent::HoveredFileCancelled => {}
202 WindowEvent::Focused(focused) => {
203 window_handle.focused(focused);
204 }
205 WindowEvent::KeyboardInput {
206 event,
207 is_synthetic,
208 ..
209 } => {
210 if !is_synthetic {
211 window_handle.key_event(event);
212 }
213 }
214 WindowEvent::ModifiersChanged(modifiers) => {
215 window_handle.modifiers_changed(modifiers.state());
216 }
217 WindowEvent::Ime(ime) => {
218 window_handle.ime(ime);
219 }
220 WindowEvent::PointerMoved { position, .. } => {
221 let position: LogicalPosition<f64> = position.to_logical(window_handle.scale);
222 let point = Point::new(position.x, position.y);
223 window_handle.pointer_move(point);
224 }
225 WindowEvent::PointerEntered { .. } => {}
226 WindowEvent::PointerLeft { .. } => {
227 window_handle.pointer_leave();
228 }
229 WindowEvent::MouseWheel { delta, .. } => {
230 window_handle.mouse_wheel(delta);
231 }
232 WindowEvent::PointerButton { state, button, .. } => {
233 window_handle.pointer_button(button, state);
234 }
235 WindowEvent::PinchGesture { delta, phase, .. } => {
236 window_handle.pinch_gesture(delta, phase);
237 }
238 WindowEvent::TouchpadPressure { .. } => {}
239 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
240 window_handle.scale(scale_factor);
241 }
242 WindowEvent::ThemeChanged(theme) => {
243 window_handle.os_theme_changed(theme);
244 }
245 WindowEvent::Occluded(_) => {}
246 WindowEvent::RedrawRequested => {
247 window_handle.render_frame();
248 }
249 WindowEvent::PanGesture { .. } => {}
250 WindowEvent::DoubleTapGesture { .. } => {}
251 WindowEvent::RotationGesture { .. } => {} }
255
256 if let Some((name, start, new_frame)) = start {
257 let end = Instant::now();
258
259 if let Some(window_handle) = self.window_handles.get_mut(&window_id) {
260 let profile = window_handle.profile.as_mut().unwrap();
261
262 profile
263 .current
264 .events
265 .push(ProfileEvent { start, end, name });
266
267 if new_frame {
268 profile.next_frame();
269 }
270 }
271 }
272 self.handle_updates_for_all_windows();
273 }
274
275 pub(crate) fn new_window(
276 &mut self,
277 event_loop: &dyn ActiveEventLoop,
278 view_fn: Box<dyn FnOnce(WindowId) -> Box<dyn View>>,
279 #[allow(unused_variables)] WindowConfig {
280 size,
281 min_size,
282 max_size,
283 position,
284 show_titlebar,
285 transparent,
286 fullscreen,
287 window_icon,
288 title,
289 enabled_buttons,
290 resizable,
291 undecorated,
292 undecorated_shadow,
293 window_level,
294 apply_default_theme,
295 mac_os_config,
296 web_config,
297 font_embolden,
298 }: WindowConfig,
299 ) {
300 let logical_size = size.map(|size| LogicalSize::new(size.width, size.height));
301 let logical_min_size = min_size.map(|size| LogicalSize::new(size.width, size.height));
302 let logical_max_size = max_size.map(|size| LogicalSize::new(size.width, size.height));
303
304 let mut window_attributes = winit::window::WindowAttributes::default()
305 .with_visible(false)
306 .with_title(title)
307 .with_decorations(!undecorated)
308 .with_transparent(transparent)
309 .with_fullscreen(fullscreen)
310 .with_window_level(window_level)
311 .with_window_icon(window_icon)
312 .with_resizable(resizable)
313 .with_enabled_buttons(enabled_buttons);
314
315 #[cfg(target_arch = "wasm32")]
316 {
317 use wgpu::web_sys::wasm_bindgen::JsCast;
318 use winit::platform::web::WindowAttributesExtWeb;
319
320 let parent_id = web_config.expect("Specify an id for the canvas.").canvas_id;
321 let doc = web_sys::window()
322 .and_then(|win| win.document())
323 .expect("Couldn't get document.");
324 let canvas = doc
325 .get_element_by_id(&parent_id)
326 .expect("Couldn't get canvas by supplied id.");
327 let canvas = canvas
328 .dyn_into::<web_sys::HtmlCanvasElement>()
329 .expect("Element behind supplied id is not a canvas.");
330
331 if let Some(size) = logical_size {
332 canvas.set_width(size.width as u32);
333 canvas.set_height(size.height as u32);
334 }
335
336 window_attributes = window_attributes.with_canvas(Some(canvas));
337 };
338
339 if let Some(Point { x, y }) = position {
340 window_attributes = window_attributes.with_position(LogicalPosition::new(x, y));
341 }
342
343 if let Some(logical_size) = logical_size {
344 window_attributes = window_attributes.with_surface_size(logical_size);
345 }
346 if let Some(logical_min_size) = logical_min_size {
347 window_attributes = window_attributes.with_min_surface_size(logical_min_size);
348 }
349 if let Some(logical_max_size) = logical_max_size {
350 window_attributes = window_attributes.with_max_surface_size(logical_max_size);
351 }
352
353 #[cfg(not(target_os = "macos"))]
354 if !show_titlebar {
355 window_attributes = window_attributes.with_decorations(false);
356 }
357
358 #[cfg(target_os = "windows")]
359 {
360 use winit::platform::windows::WindowAttributesExtWindows;
361 window_attributes = window_attributes.with_undecorated_shadow(undecorated_shadow);
362 }
363
364 #[cfg(target_os = "macos")]
365 if !show_titlebar {
366 use winit::platform::macos::WindowAttributesExtMacOS;
367 window_attributes = window_attributes
368 .with_movable_by_window_background(false)
369 .with_title_hidden(true)
370 .with_titlebar_transparent(true)
371 .with_fullsize_content_view(true);
372 }
374
375 #[cfg(target_os = "macos")]
376 if undecorated {
377 use winit::platform::macos::WindowAttributesExtMacOS;
378 window_attributes = window_attributes.with_accepts_first_mouse(true);
384 }
385
386 #[cfg(target_os = "macos")]
387 if let Some(mac) = mac_os_config {
388 use winit::platform::macos::WindowAttributesExtMacOS;
389 if let Some(val) = mac.movable_by_window_background {
390 window_attributes = window_attributes.with_movable_by_window_background(val);
391 }
392 if let Some(val) = mac.titlebar_transparent {
393 window_attributes = window_attributes.with_titlebar_transparent(val);
394 }
395 if let Some(val) = mac.titlebar_hidden {
396 window_attributes = window_attributes.with_titlebar_hidden(val);
397 }
398 if let Some(val) = mac.title_hidden {
399 window_attributes = window_attributes.with_title_hidden(val);
400 }
401 if let Some(val) = mac.full_size_content_view {
402 window_attributes = window_attributes.with_fullsize_content_view(val);
403 }
404 if let Some(val) = mac.unified_titlebar {
405 window_attributes = window_attributes.with_unified_titlebar(val);
406 }
407 if let Some(val) = mac.movable {
408 window_attributes = window_attributes.with_movable_by_window_background(val);
409 }
410 if let Some(val) = mac.accepts_first_mouse {
415 window_attributes = window_attributes.with_accepts_first_mouse(val);
416 }
417 if let Some(val) = mac.option_as_alt {
418 window_attributes = window_attributes.with_option_as_alt(val.into());
419 }
420 if let Some(title) = mac.tabbing_identifier {
421 window_attributes = window_attributes.with_tabbing_identifier(title.as_str());
422 }
423 if let Some(disallow_hidpi) = mac.disallow_high_dpi {
424 window_attributes = window_attributes.with_disallow_hidpi(disallow_hidpi);
425 }
426 if let Some(shadow) = mac.has_shadow {
427 window_attributes = window_attributes.with_has_shadow(shadow);
428 }
429 if let Some(hide) = mac.titlebar_buttons_hidden {
430 window_attributes = window_attributes.with_titlebar_buttons_hidden(hide)
431 }
432 if let Some(panel) = mac.panel {
433 window_attributes = window_attributes.with_panel(panel)
434 }
435 }
436
437 let Ok(window) = event_loop.create_window(window_attributes) else {
438 return;
439 };
440 let window_id = window.id();
441 let window_handle = WindowHandle::new(
442 window,
443 view_fn,
444 transparent,
445 apply_default_theme,
446 font_embolden,
447 );
448 self.window_handles.insert(window_id, window_handle);
449 }
450
451 fn close_window(
452 &mut self,
453 window_id: WindowId,
454 #[cfg(target_os = "macos")] _event_loop: &dyn ActiveEventLoop,
455 #[cfg(not(target_os = "macos"))] event_loop: &dyn ActiveEventLoop,
456 ) {
457 if let Some(handle) = self.window_handles.get_mut(&window_id) {
458 handle.window = None;
459 handle.destroy();
460 }
461 self.window_handles.remove(&window_id);
462 #[cfg(not(target_os = "macos"))]
463 if self.window_handles.is_empty() {
464 event_loop.exit();
465 }
466 }
467
468 fn capture_window(&mut self, window_id: WindowId) -> Option<Capture> {
469 self.window_handles
470 .get_mut(&window_id)
471 .map(|handle| handle.capture())
472 }
473
474 pub(crate) fn idle(&mut self) {
475 let ext_events = { std::mem::take(&mut *EXT_EVENT_HANDLER.queue.lock()) };
476
477 for trigger in ext_events {
478 trigger.notify();
479 }
480
481 self.handle_updates_for_all_windows();
482 }
483
484 pub(crate) fn handle_updates_for_all_windows(&mut self) {
485 for (window_id, handle) in self.window_handles.iter_mut() {
486 handle.process_update();
487 while process_window_updates(window_id) {}
488 }
489 }
490
491 fn request_timer(&mut self, timer: Timer, event_loop: &dyn ActiveEventLoop) {
492 self.timers.insert(timer.token, timer);
493 self.fire_timer(event_loop);
494 }
495
496 fn remove_timer(&mut self, timer: &TimerToken) {
497 self.timers.remove(timer);
498 }
499
500 fn fire_timer(&mut self, event_loop: &dyn ActiveEventLoop) {
501 if self.timers.is_empty() {
502 return;
503 }
504
505 let deadline = self.timers.values().map(|timer| timer.deadline).min();
506 if let Some(deadline) = deadline {
507 event_loop.set_control_flow(ControlFlow::WaitUntil(deadline));
508 }
509 }
510
511 pub(crate) fn handle_timer(&mut self, event_loop: &dyn ActiveEventLoop) {
512 let now = Instant::now();
513 let tokens: Vec<TimerToken> = self
514 .timers
515 .iter()
516 .filter_map(|(token, timer)| {
517 if timer.deadline <= now {
518 Some(*token)
519 } else {
520 None
521 }
522 })
523 .collect();
524 if !tokens.is_empty() {
525 for token in tokens {
526 if let Some(timer) = self.timers.remove(&token) {
527 (timer.action)(token);
528 }
529 }
530 self.handle_updates_for_all_windows();
531 }
532 self.fire_timer(event_loop);
533 }
534}