floem/
app_handle.rs

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                // WindowEvent::MenuAction(..) => "MenuAction",
197            };
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 { .. } => {} // WindowEvent::MenuAction(id) => {
278                                                      //     window_handle.menu_action(id);
279                                                      // }
280        }
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            // .with_traffic_lights_offset(11.0, 16.0);
399        }
400
401        #[cfg(target_os = "macos")]
402        if undecorated {
403            use winit::platform::macos::WindowAttributesExtMacOS;
404            // A palette-style window that will only obtain window focus but
405            // not actually propagate the first mouse click it receives is
406            // very unlikely to be expected behavior - these typically are
407            // used for something that offers a quick choice and are closed
408            // in a single pointer gesture.
409            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((x, y)) = mac.traffic_lights_offset {
437            // TODO
438            // window_attributes = window_attributes.with_traffic_lights_offset(x, y);
439            // }
440            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}