floem/
app_handle.rs

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                // WindowEvent::MenuAction(..) => "MenuAction",
171            };
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 { .. } => {} // WindowEvent::MenuAction(id) => {
252                                                      //     window_handle.menu_action(id);
253                                                      // }
254        }
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            // .with_traffic_lights_offset(11.0, 16.0);
373        }
374
375        #[cfg(target_os = "macos")]
376        if undecorated {
377            use winit::platform::macos::WindowAttributesExtMacOS;
378            // A palette-style window that will only obtain window focus but
379            // not actually propagate the first mouse click it receives is
380            // very unlikely to be expected behavior - these typically are
381            // used for something that offers a quick choice and are closed
382            // in a single pointer gesture.
383            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((x, y)) = mac.traffic_lights_offset {
411            // TODO
412            // window_attributes = window_attributes.with_traffic_lights_offset(x, y);
413            // }
414            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}