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