1use crate::BlitzShellProvider;
2use crate::convert_events::{
3 button_source_to_blitz, color_scheme_to_theme, pointer_kind_to_blitz, pointer_source_to_blitz,
4 pointer_source_to_blitz_details, theme_to_color_scheme, winit_ime_to_blitz,
5 winit_key_event_to_blitz, winit_modifiers_to_kbt_modifiers,
6};
7use crate::event::{BlitzShellEvent, BlitzShellProxy, create_waker};
8use anyrender::WindowRenderer;
9use blitz_dom::Document;
10use blitz_paint::paint_scene;
11use blitz_traits::events::{
12 BlitzPointerEvent, BlitzPointerId, BlitzWheelDelta, BlitzWheelEvent, MouseEventButton,
13 MouseEventButtons, PointerCoords, PointerDetails, UiEvent,
14};
15use blitz_traits::shell::Viewport;
16use winit::dpi::{LogicalPosition, PhysicalInsets, PhysicalPosition};
17use winit::keyboard::PhysicalKey;
18
19use atomic_refcell::AtomicRefCell;
20use std::any::Any;
21use std::sync::Arc;
22use std::task::Waker;
23use web_time::Instant;
24use winit::event::{ButtonSource, ElementState, MouseButton};
25use winit::event_loop::ActiveEventLoop;
26use winit::window::{Theme, WindowAttributes, WindowId};
27use winit::{event::Modifiers, event::WindowEvent, keyboard::KeyCode, window::Window};
28
29#[cfg(feature = "accessibility")]
30use crate::accessibility::AccessibilityState;
31
32#[cfg(target_os = "macos")]
35fn get_safe_area_insets(_window: &dyn Window) -> PhysicalInsets<u32> {
36 Default::default()
37}
38#[cfg(not(target_os = "macos"))]
39fn get_safe_area_insets(window: &dyn Window) -> PhysicalInsets<u32> {
40 window.safe_area()
41}
42
43pub struct WindowConfig<Rend: WindowRenderer> {
44 doc: Box<dyn Document>,
45 pub(crate) attributes: WindowAttributes,
46 renderer: Rend,
47}
48
49impl<Rend: WindowRenderer> WindowConfig<Rend> {
50 pub fn new(doc: Box<dyn Document>, renderer: Rend) -> Self {
51 Self::with_attributes(doc, renderer, WindowAttributes::default())
52 }
53
54 pub fn with_attributes(
55 doc: Box<dyn Document>,
56 renderer: Rend,
57 attributes: WindowAttributes,
58 ) -> Self {
59 WindowConfig {
60 doc,
61 attributes,
62 renderer,
63 }
64 }
65}
66
67pub struct View<Rend: WindowRenderer> {
68 pub doc: Box<dyn Document>,
69
70 pub renderer: Rend,
71 pub waker: Option<Waker>,
72
73 pub proxy: BlitzShellProxy,
74 pub window: Arc<dyn Window>,
75
76 pub theme_override: Option<Theme>,
79 pub keyboard_modifiers: Modifiers,
80 pub buttons: MouseEventButtons,
81 pub pointer_pos: PhysicalPosition<f64>,
82 pub active_events: Arc<AtomicRefCell<Vec<BlitzPointerEvent>>>,
97 pub animation_timer: Option<Instant>,
98 pub is_visible: bool,
99 pub safe_area_insets: PhysicalInsets<u32>,
100
101 #[cfg(target_arch = "wasm32")]
102 pending_resize: Option<winit::dpi::PhysicalSize<u32>>,
103 #[cfg(target_arch = "wasm32")]
104 last_resize_at: Option<web_time::Instant>,
105 #[cfg(target_arch = "wasm32")]
109 resize_timer_scheduled: bool,
110
111 #[cfg(feature = "accessibility")]
112 pub accessibility: AccessibilityState,
114
115 #[cfg(target_os = "ios")]
120 pub ios_request_redraw: std::cell::Cell<bool>,
121}
122
123impl<Rend: WindowRenderer> Drop for View<Rend> {
124 fn drop(&mut self) {
125 self.renderer.suspend();
131 }
132}
133
134impl<Rend: WindowRenderer> View<Rend> {
135 pub fn init(
136 config: WindowConfig<Rend>,
137 event_loop: &dyn ActiveEventLoop,
138 proxy: &BlitzShellProxy,
139 ) -> Self {
140 let is_visible = config.attributes.visible;
143 let requested_surface_size = config.attributes.surface_size;
147 let attrs = config.attributes.with_visible(false);
148
149 let winit_window: Arc<dyn Window> = Arc::from(event_loop.create_window(attrs).unwrap());
150 #[cfg(feature = "accessibility")]
151 let accessibility = AccessibilityState::new(&*winit_window, proxy.clone());
152
153 if is_visible {
154 winit_window.set_visible(true);
155 }
156
157 let scale = winit_window.scale_factor() as f32;
159 let mut size = winit_window.surface_size();
160 if (size.width == 0 || size.height == 0)
161 && let Some(requested) = requested_surface_size
162 {
163 size = requested.to_physical(scale as f64);
164 }
165 #[cfg(target_arch = "wasm32")]
170 if size.width == 0 || size.height == 0 {
171 use winit::platform::web::WindowExtWeb;
172 if let Some(canvas) = winit_window.canvas() {
173 let css_w = canvas.offset_width().max(0) as u32;
174 let css_h = canvas.offset_height().max(0) as u32;
175 if css_w > 0 && css_h > 0 {
176 size = winit::dpi::LogicalSize::new(css_w, css_h).to_physical(scale as f64);
177 }
178 }
179 }
180 let safe_area_insets = get_safe_area_insets(&*winit_window);
181 let theme = winit_window.theme().unwrap_or(Theme::Light);
182 let color_scheme = theme_to_color_scheme(theme);
183 let viewport_width = size
185 .width
186 .saturating_sub(safe_area_insets.left + safe_area_insets.right);
187 let viewport_height = size
188 .height
189 .saturating_sub(safe_area_insets.top + safe_area_insets.bottom);
190 let viewport = Viewport::new(viewport_width, viewport_height, scale, color_scheme);
191
192 let shell_provider = BlitzShellProvider::new(winit_window.clone(), proxy.clone());
194
195 let mut doc = config.doc;
196 let mut inner = doc.inner_mut();
197 inner.set_viewport(viewport);
198 inner.set_shell_provider(Arc::new(shell_provider));
199
200 let title = inner.find_title_node().map(|node| node.text_content());
204 if let Some(title) = title {
205 winit_window.set_title(&title);
206 }
207
208 drop(inner);
209
210 Self {
211 renderer: config.renderer,
212 waker: None,
213 animation_timer: None,
214 keyboard_modifiers: Default::default(),
215 proxy: proxy.clone(),
216 window: winit_window.clone(),
217 doc,
218 theme_override: None,
219 buttons: MouseEventButtons::None,
220 active_events: Arc::new(AtomicRefCell::new(Vec::new())),
221 safe_area_insets,
222 #[cfg(target_arch = "wasm32")]
223 pending_resize: None,
224 #[cfg(target_arch = "wasm32")]
225 last_resize_at: None,
226 #[cfg(target_arch = "wasm32")]
227 resize_timer_scheduled: false,
228 pointer_pos: Default::default(),
229 is_visible: winit_window.is_visible().unwrap_or(true),
230 #[cfg(feature = "accessibility")]
231 accessibility,
232
233 #[cfg(target_os = "ios")]
234 ios_request_redraw: std::cell::Cell::new(false),
235 }
236 }
237
238 pub fn replace_document(&mut self, new_doc: Box<dyn Document>, retain_scroll_position: bool) {
239 let inner = self.doc.inner();
240 let scroll = inner.viewport_scroll();
241 let viewport = inner.viewport().clone();
242 let shell_provider = inner.shell_provider.clone();
243 drop(inner);
244
245 self.doc = new_doc;
246
247 let mut inner = self.doc.inner_mut();
248 inner.set_viewport(viewport);
249 inner.set_shell_provider(shell_provider);
250 drop(inner);
251
252 self.poll();
253 self.request_redraw();
254
255 if retain_scroll_position {
256 self.doc.inner_mut().set_viewport_scroll(scroll);
257 }
258 }
259
260 pub fn theme_override(&self) -> Option<Theme> {
261 self.theme_override
262 }
263
264 pub fn current_theme(&self) -> Theme {
265 color_scheme_to_theme(self.doc.inner().viewport().color_scheme)
266 }
267
268 pub fn set_theme_override(&mut self, theme: Option<Theme>) {
269 self.theme_override = theme;
270 let theme = theme.or(self.window.theme()).unwrap_or(Theme::Light);
271 self.with_viewport(|v| v.color_scheme = theme_to_color_scheme(theme));
272 }
273
274 pub fn downcast_doc_mut<T: 'static>(&mut self) -> &mut T {
275 (&mut *self.doc as &mut dyn Any)
276 .downcast_mut::<T>()
277 .unwrap()
278 }
279
280 pub fn current_animation_time(&mut self) -> f64 {
281 match &self.animation_timer {
282 Some(start) => Instant::now().duration_since(*start).as_secs_f64(),
283 None => {
284 self.animation_timer = Some(Instant::now());
285 0.0
286 }
287 }
288 }
289}
290
291impl<Rend: WindowRenderer> View<Rend> {
292 pub fn resume(&mut self) {
297 let window_id = self.window_id();
298 let animation_time = self.current_animation_time();
299
300 let (width, height) = {
301 let mut inner = self.doc.inner_mut();
302 inner.resolve(animation_time);
303 inner.viewport().window_size
304 };
305
306 let insets = self.safe_area_insets;
308 let width = width + insets.left + insets.right;
309 let height = height + insets.top + insets.bottom;
310
311 let proxy = self.proxy.clone();
312 self.renderer
313 .resume(Arc::new(self.window.clone()), width, height, move || {
314 proxy.send_event(BlitzShellEvent::ResumeReady { window_id });
315 });
316 }
317
318 pub fn complete_resume(&mut self) -> bool {
322 if !self.renderer.complete_resume() {
323 return false;
324 }
325
326 let window_id = self.window_id();
327
328 let animation_time = self.current_animation_time();
333 let mut inner = self.doc.inner_mut();
334 inner.resolve(animation_time);
335 let (width, height) = inner.viewport().window_size;
336 let scale = inner.viewport().scale_f64();
337 let insets = self.safe_area_insets;
338
339 #[cfg(feature = "custom-widget")]
340 inner.can_create_surfaces(&mut self.renderer as _);
341
342 self.renderer.set_size(
344 width + insets.left + insets.right,
345 height + insets.top + insets.bottom,
346 );
347
348 self.renderer.render(|scene| {
349 paint_scene(
350 scene,
351 &mut inner,
352 scale,
353 width,
354 height,
355 insets.left,
356 insets.top,
357 )
358 });
359
360 self.waker = Some(create_waker(&self.proxy, window_id));
361 true
362 }
363
364 pub fn suspend(&mut self) {
365 self.waker = None;
366 self.renderer.suspend();
367
368 #[cfg(feature = "custom-widget")]
369 self.doc.inner_mut().destroy_surfaces();
370 }
371
372 pub fn poll(&mut self) -> bool {
373 if let Some(waker) = &self.waker {
374 let cx = std::task::Context::from_waker(waker);
375 if self.doc.poll(Some(cx)) {
376 #[cfg(feature = "accessibility")]
377 {
378 let inner = self.doc.inner();
379 if inner.has_changes() {
380 self.accessibility.update_tree(&inner);
381 }
382 }
383
384 self.request_redraw();
385 return true;
386 }
387 }
388
389 false
390 }
391
392 pub fn request_redraw(&self) {
393 if self.renderer.is_active() {
394 self.window.request_redraw();
395 #[cfg(target_os = "ios")]
396 self.ios_request_redraw.set(true);
397 }
398 }
399
400 pub fn redraw(&mut self) {
401 #[cfg(target_os = "ios")]
402 self.ios_request_redraw.set(false);
403 let animation_time = self.current_animation_time();
404 let is_visible = self.is_visible;
405
406 let mut inner = self.doc.inner_mut();
407 inner.resolve(animation_time);
408
409 #[cfg(feature = "custom-widget")]
411 for id in inner.take_pending_resource_deallocations() {
412 self.renderer.unregister_resource(id);
413 }
414
415 let (width, height) = inner.viewport().window_size;
416 let scale = inner.viewport().scale_f64();
417 let is_animating = inner.is_animating();
418 let is_blocked = inner.has_pending_critical_resources();
419 let insets = self.safe_area_insets;
420
421 if !is_blocked && is_visible {
422 self.renderer.render(|scene| {
423 paint_scene(
424 scene,
425 &mut inner,
426 scale,
427 width,
428 height,
429 insets.left,
430 insets.top,
431 )
432 });
433 }
434
435 drop(inner);
436
437 if !is_blocked && is_visible && is_animating {
438 self.request_redraw();
439 }
440 }
441
442 pub fn pointer_coords(&self, position: PhysicalPosition<f64>) -> PointerCoords {
443 let inner = self.doc.inner();
444 let scale = inner.viewport().scale_f64();
445 let LogicalPosition::<f32> {
446 x: screen_x,
447 y: screen_y,
448 } = position.to_logical(scale);
449 let viewport_scroll_offset = inner.viewport_scroll();
450 let client_x = screen_x - (self.safe_area_insets.left as f64 / scale) as f32;
451 let client_y = screen_y - (self.safe_area_insets.top as f64 / scale) as f32;
452 let page_x = client_x + viewport_scroll_offset.x as f32;
453 let page_y = client_y + viewport_scroll_offset.y as f32;
454
455 PointerCoords {
456 screen_x,
457 screen_y,
458 client_x,
459 client_y,
460 page_x,
461 page_y,
462 }
463 }
464
465 pub fn window_id(&self) -> WindowId {
466 self.window.id()
467 }
468
469 fn set_active_pointer(&self, event: &BlitzPointerEvent) {
473 let mut stored = event.clone();
474 stored.active_pointers = Default::default();
475
476 let mut active = self.active_events.borrow_mut();
477 if let Some(existing) = active.iter_mut().find(|e| e.id == stored.id) {
478 *existing = stored;
479 } else {
480 active.push(stored);
481 }
482 }
483
484 fn update_active_pointer(&self, event: &BlitzPointerEvent) {
487 let mut active = self.active_events.borrow_mut();
488 if let Some(existing) = active.iter_mut().find(|e| e.id == event.id) {
489 let mut stored = event.clone();
490 stored.active_pointers = Default::default();
491 *existing = stored;
492 }
493 }
494
495 fn remove_active_pointer(&self, id: BlitzPointerId) -> bool {
497 let mut active = self.active_events.borrow_mut();
498 let len_before = active.len();
499 active.retain(|e| e.id != id);
500 active.len() != len_before
501 }
502
503 #[inline]
504 pub fn with_viewport(&mut self, cb: impl FnOnce(&mut Viewport)) {
505 let mut inner = self.doc.inner_mut();
506 let mut viewport = inner.viewport_mut();
507 cb(&mut viewport);
508 let (width, height) = viewport.window_size;
509 drop(viewport);
510 drop(inner);
511 if width > 0 && height > 0 {
512 let insets = self.safe_area_insets;
513 self.renderer.set_size(
514 width + insets.left + insets.right,
515 height + insets.top + insets.bottom,
516 );
517 self.request_redraw();
518 }
519 }
520
521 #[cfg(feature = "accessibility")]
522 pub fn build_accessibility_tree(&mut self) {
523 let inner = self.doc.inner();
524 self.accessibility.update_tree(&inner);
525 }
526
527 #[cfg(target_arch = "wasm32")]
528 const RESIZE_DEBOUNCE_MS: u32 = 100;
529
530 #[cfg(target_arch = "wasm32")]
531 fn schedule_resize_settle_check(&mut self, delay_ms: u32) {
532 use wasm_bindgen::JsCast;
533 use wasm_bindgen::closure::Closure;
534
535 let proxy = self.proxy.clone();
536 let window_id = self.window_id();
537 let cb = Closure::once_into_js(move || {
538 proxy.send_event(BlitzShellEvent::ResizeSettleCheck { window_id });
539 });
540 if let Some(win) = web_sys::window() {
541 let _ = win.set_timeout_with_callback_and_timeout_and_arguments_0(
542 cb.unchecked_ref(),
543 delay_ms as i32,
544 );
545 self.resize_timer_scheduled = true;
546 }
547 }
548
549 #[cfg(target_arch = "wasm32")]
553 pub fn apply_pending_resize_if_settled(&mut self) {
554 self.resize_timer_scheduled = false;
555 let Some(last) = self.last_resize_at else {
556 return;
557 };
558 let debounce = std::time::Duration::from_millis(Self::RESIZE_DEBOUNCE_MS as u64);
559 let elapsed = web_time::Instant::now().saturating_duration_since(last);
560 if elapsed < debounce {
561 let remaining_ms = (debounce - elapsed).as_millis() as u32;
563 self.schedule_resize_settle_check(remaining_ms);
564 return;
565 }
566 let Some(size) = self.pending_resize.take() else {
567 return;
568 };
569 self.last_resize_at = None;
570
571 let insets = self.safe_area_insets;
572 let width = size.width.saturating_sub(insets.left + insets.right);
573 let height = size.height.saturating_sub(insets.top + insets.bottom);
574 self.with_viewport(|v| v.window_size = (width, height));
575 self.request_redraw();
576 }
577
578 #[cfg(target_os = "macos")]
579 pub fn handle_apple_standard_keybinding(&mut self, command: &str) {
580 use blitz_traits::SmolStr;
581 let event = UiEvent::AppleStandardKeybinding(SmolStr::new(command));
582 self.doc.handle_ui_event(event);
583 }
584
585 pub fn handle_winit_event(&mut self, event: WindowEvent) {
586 #[cfg(feature = "accessibility")]
588 self.accessibility
589 .process_window_event(&*self.window, &event);
590
591 match event {
592 WindowEvent::Destroyed => {}
593 WindowEvent::ActivationTokenDone { .. } => {},
594 WindowEvent::CloseRequested => {
595 }
597 WindowEvent::RedrawRequested => {
598 self.redraw();
599 }
600 WindowEvent::Moved(_) => {}
601 WindowEvent::Occluded(is_occluded) => {
602 self.is_visible = !is_occluded;
603 if self.is_visible {
604 self.request_redraw();
605 }
606 },
607 WindowEvent::SurfaceResized(physical_size) => {
608 self.safe_area_insets = get_safe_area_insets(&*self.window);
609 #[cfg(target_arch = "wasm32")]
613 {
614 self.pending_resize = Some(physical_size);
615 self.last_resize_at = Some(web_time::Instant::now());
616 if !self.resize_timer_scheduled {
617 self.schedule_resize_settle_check(Self::RESIZE_DEBOUNCE_MS);
618 }
619 }
620 #[cfg(not(target_arch = "wasm32"))]
621 {
622 let insets = self.safe_area_insets;
623 let width = physical_size.width - insets.left - insets.right;
624 let height = physical_size.height - insets.top - insets.bottom;
625 self.with_viewport(|v| v.window_size = (width, height));
626 self.request_redraw();
627 }
628 }
629 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
630 self.with_viewport(|v| v.set_hidpi_scale(scale_factor as f32));
631 self.request_redraw();
632 }
633 WindowEvent::ThemeChanged(theme) => {
634 let color_scheme = theme_to_color_scheme(self.theme_override.unwrap_or(theme));
635 let mut inner = self.doc.inner_mut();
636 inner.viewport_mut().color_scheme = color_scheme;
637 }
638 WindowEvent::Ime(ime_event) => {
639 self.doc.handle_ui_event(UiEvent::Ime(winit_ime_to_blitz(ime_event)));
640 self.request_redraw();
641 },
642 WindowEvent::ModifiersChanged(new_state) => {
643 self.keyboard_modifiers = new_state;
645 }
646 WindowEvent::KeyboardInput { event, .. } => {
647 if let PhysicalKey::Code(key_code) = event.physical_key && event.state.is_pressed() {
648 let ctrl = self.keyboard_modifiers.state().control_key();
649 let meta = self.keyboard_modifiers.state().meta_key();
650 let alt = self.keyboard_modifiers.state().alt_key();
651
652 if ctrl | meta {
654 match key_code {
655 KeyCode::Equal => {
656 self.doc.inner_mut().viewport_mut().zoom_by(0.1);
657 },
658 KeyCode::Minus => {
659 self.doc.inner_mut().viewport_mut().zoom_by(-0.1);
660 },
661 KeyCode::Digit0 => {
662 self.doc.inner_mut().viewport_mut().set_zoom(1.0);
663 }
664 _ => {}
665 };
666 }
667
668 if alt {
670 match key_code {
671 KeyCode::KeyD => {
672 let mut inner = self.doc.inner_mut();
673 inner.devtools_mut().toggle_show_layout();
674 drop(inner);
675 self.request_redraw();
676 }
677 KeyCode::KeyH => {
678 let mut inner = self.doc.inner_mut();
679 inner.devtools_mut().toggle_highlight_hover();
680 drop(inner);
681 self.request_redraw();
682 }
683 KeyCode::KeyT => self.doc.inner().print_taffy_tree(),
684 _ => {}
685 };
686 }
687
688 }
689
690 let key_event_data = winit_key_event_to_blitz(&event, self.keyboard_modifiers.state());
692 let event = if event.state.is_pressed() {
693 UiEvent::KeyDown(key_event_data)
694 } else {
695 UiEvent::KeyUp(key_event_data)
696 };
697
698 self.doc.handle_ui_event(event);
699 }
700 WindowEvent::PointerEntered { .. } => {}
701 WindowEvent::PointerLeft { position, primary, kind, .. } => {
702 let id = pointer_kind_to_blitz(&kind);
703
704 if id != BlitzPointerId::Mouse && self.remove_active_pointer(id) {
713 let position = position.unwrap_or(self.pointer_pos);
714 self.pointer_pos = position;
715
716 self.buttons ^= MouseEventButton::Main.into();
718
719 let event = BlitzPointerEvent {
720 id,
721 is_primary: primary,
722 coords: self.pointer_coords(position),
723 button: MouseEventButton::Main,
724 buttons: self.buttons,
725 mods: winit_modifiers_to_kbt_modifiers(self.keyboard_modifiers.state()),
726 details: PointerDetails::default(),
727 element: Default::default(),
728 active_pointers: Arc::clone(&self.active_events),
729 };
730
731 self.doc.handle_ui_event(UiEvent::PointerCancel(event));
732 self.request_redraw();
733 }
734 }
735 WindowEvent::PointerMoved { position, source, primary, .. } => {
736 self.pointer_pos = position;
737 let id = pointer_source_to_blitz(&source);
738 let event = BlitzPointerEvent {
739 id,
740 is_primary: primary,
741 coords: self.pointer_coords(position),
742 button: Default::default(),
743 buttons: self.buttons,
744 mods: winit_modifiers_to_kbt_modifiers(self.keyboard_modifiers.state()),
745 details: pointer_source_to_blitz_details(&source),
746 element: Default::default(),
747 active_pointers: Arc::clone(&self.active_events),
748 };
749 if id != BlitzPointerId::Mouse {
751 self.update_active_pointer(&event);
752 }
753 self.doc.handle_ui_event(UiEvent::PointerMove(event));
754 }
755 WindowEvent::PointerButton { button, state, primary, position, .. } => {
756 let id = button_source_to_blitz(&button);
757 let coords = self.pointer_coords(position);
758 self.pointer_pos = position;
759 let button = match &button {
760 ButtonSource::Mouse(mouse_button) => match mouse_button {
761 MouseButton::Left => MouseEventButton::Main,
762 MouseButton::Right => MouseEventButton::Secondary,
763 MouseButton::Middle => MouseEventButton::Auxiliary,
764 _ => MouseEventButton::Auxiliary,
766 }
767 _ => MouseEventButton::Main,
768 };
769
770 match state {
771 ElementState::Pressed => self.buttons |= button.into(),
772 ElementState::Released => self.buttons ^= button.into(),
773 }
774
775 let pointer_event = BlitzPointerEvent {
776 id,
777 is_primary: primary,
778 coords,
779 button,
780 buttons: self.buttons,
781 mods: winit_modifiers_to_kbt_modifiers(self.keyboard_modifiers.state()),
782
783 details: PointerDetails::default(),
785 element: Default::default(),
786 active_pointers: Arc::clone(&self.active_events),
787 };
788
789 if id != BlitzPointerId::Mouse && state == ElementState::Pressed {
795 self.set_active_pointer(&pointer_event);
796 }
797
798 if id != BlitzPointerId::Mouse {
802 let event = BlitzPointerEvent {
803 id,
804 is_primary: primary,
805 coords,
806 button: Default::default(),
807 buttons: self.buttons,
808 mods: winit_modifiers_to_kbt_modifiers(self.keyboard_modifiers.state()),
809 details: PointerDetails::default(),
810 element: Default::default(),
811 active_pointers: Arc::clone(&self.active_events),
812 };
813 self.doc.handle_ui_event(UiEvent::PointerMove(event));
814 }
815
816 if id != BlitzPointerId::Mouse && state == ElementState::Released {
817 self.remove_active_pointer(id);
818 }
819
820 let event = pointer_event;
821
822 let event = match state {
823 ElementState::Pressed => UiEvent::PointerDown(event),
824 ElementState::Released => UiEvent::PointerUp(event),
825 };
826
827 self.doc.handle_ui_event(event);
828 self.request_redraw();
829 }
830 WindowEvent::MouseWheel { delta, .. } => {
831 let blitz_delta = match delta {
832 winit::event::MouseScrollDelta::LineDelta(x, y) => BlitzWheelDelta::Lines(x as f64, y as f64),
833 winit::event::MouseScrollDelta::PixelDelta(pos) => BlitzWheelDelta::Pixels(pos.x, pos.y),
834 };
835
836 let event = BlitzWheelEvent {
837 delta: blitz_delta,
838 coords: self.pointer_coords(self.pointer_pos),
839 buttons: self.buttons,
840 mods: winit_modifiers_to_kbt_modifiers(self.keyboard_modifiers.state()),
841 element: Default::default()
842 };
843
844 self.doc.handle_ui_event(UiEvent::Wheel(event));
845 }
846 WindowEvent::Focused(_) => {}
847 WindowEvent::TouchpadPressure { .. } => {}
848 WindowEvent::PinchGesture { .. } => {},
849 WindowEvent::PanGesture { .. } => {},
850 WindowEvent::DoubleTapGesture { .. } => {},
851 WindowEvent::RotationGesture { .. } => {},
852 WindowEvent::DragEntered { .. } => {},
853 WindowEvent::DragMoved { .. } => {},
854 WindowEvent::DragDropped { .. } => {},
855 WindowEvent::DragLeft { .. } => {},
856 }
857 }
858}