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> View<Rend> {
124 pub fn init(
125 config: WindowConfig<Rend>,
126 event_loop: &dyn ActiveEventLoop,
127 proxy: &BlitzShellProxy,
128 ) -> Self {
129 let is_visible = config.attributes.visible;
132 let requested_surface_size = config.attributes.surface_size;
136 let attrs = config.attributes.with_visible(false);
137
138 let winit_window: Arc<dyn Window> = Arc::from(event_loop.create_window(attrs).unwrap());
139 #[cfg(feature = "accessibility")]
140 let accessibility = AccessibilityState::new(&*winit_window, proxy.clone());
141
142 if is_visible {
143 winit_window.set_visible(true);
144 }
145
146 let scale = winit_window.scale_factor() as f32;
149 let mut size = winit_window.surface_size();
150 if (size.width == 0 || size.height == 0)
151 && let Some(requested) = requested_surface_size
152 {
153 size = requested.to_physical(scale as f64);
154 }
155 #[cfg(target_arch = "wasm32")]
160 if size.width == 0 || size.height == 0 {
161 use winit::platform::web::WindowExtWeb;
162 if let Some(canvas) = winit_window.canvas() {
163 let css_w = canvas.offset_width().max(0) as u32;
164 let css_h = canvas.offset_height().max(0) as u32;
165 if css_w > 0 && css_h > 0 {
166 size = winit::dpi::LogicalSize::new(css_w, css_h).to_physical(scale as f64);
167 }
168 }
169 }
170 let safe_area_insets = get_safe_area_insets(&*winit_window);
171 let theme = winit_window.theme().unwrap_or(Theme::Light);
172 let color_scheme = theme_to_color_scheme(theme);
173 let viewport = Viewport::new(size.width, size.height, scale, color_scheme);
174
175 let shell_provider = BlitzShellProvider::new(winit_window.clone(), proxy.clone());
177
178 let mut doc = config.doc;
179 let mut inner = doc.inner_mut();
180 inner.set_viewport(viewport);
181 inner.set_shell_provider(Arc::new(shell_provider));
182
183 let title = inner.find_title_node().map(|node| node.text_content());
187 if let Some(title) = title {
188 winit_window.set_title(&title);
189 }
190
191 drop(inner);
192
193 Self {
194 renderer: config.renderer,
195 waker: None,
196 animation_timer: None,
197 keyboard_modifiers: Default::default(),
198 proxy: proxy.clone(),
199 window: winit_window.clone(),
200 doc,
201 theme_override: None,
202 buttons: MouseEventButtons::None,
203 active_events: Arc::new(AtomicRefCell::new(Vec::new())),
204 safe_area_insets,
205 #[cfg(target_arch = "wasm32")]
206 pending_resize: None,
207 #[cfg(target_arch = "wasm32")]
208 last_resize_at: None,
209 #[cfg(target_arch = "wasm32")]
210 resize_timer_scheduled: false,
211 pointer_pos: Default::default(),
212 is_visible: winit_window.is_visible().unwrap_or(true),
213 #[cfg(feature = "accessibility")]
214 accessibility,
215
216 #[cfg(target_os = "ios")]
217 ios_request_redraw: std::cell::Cell::new(false),
218 }
219 }
220
221 pub fn replace_document(&mut self, new_doc: Box<dyn Document>, retain_scroll_position: bool) {
222 let inner = self.doc.inner();
223 let scroll = inner.viewport_scroll();
224 let viewport = inner.viewport().clone();
225 let shell_provider = inner.shell_provider.clone();
226 drop(inner);
227
228 self.doc = new_doc;
229
230 let mut inner = self.doc.inner_mut();
231 inner.set_viewport(viewport);
232 inner.set_shell_provider(shell_provider);
233 drop(inner);
234
235 self.poll();
236 self.request_redraw();
237
238 if retain_scroll_position {
239 self.doc.inner_mut().set_viewport_scroll(scroll);
240 }
241 }
242
243 pub fn theme_override(&self) -> Option<Theme> {
244 self.theme_override
245 }
246
247 pub fn current_theme(&self) -> Theme {
248 color_scheme_to_theme(self.doc.inner().viewport().color_scheme)
249 }
250
251 pub fn set_theme_override(&mut self, theme: Option<Theme>) {
252 self.theme_override = theme;
253 let theme = theme.or(self.window.theme()).unwrap_or(Theme::Light);
254 self.with_viewport(|v| v.color_scheme = theme_to_color_scheme(theme));
255 }
256
257 pub fn downcast_doc_mut<T: 'static>(&mut self) -> &mut T {
258 (&mut *self.doc as &mut dyn Any)
259 .downcast_mut::<T>()
260 .unwrap()
261 }
262
263 pub fn current_animation_time(&mut self) -> f64 {
264 match &self.animation_timer {
265 Some(start) => Instant::now().duration_since(*start).as_secs_f64(),
266 None => {
267 self.animation_timer = Some(Instant::now());
268 0.0
269 }
270 }
271 }
272}
273
274impl<Rend: WindowRenderer> View<Rend> {
275 pub fn resume(&mut self) {
280 let window_id = self.window_id();
281 let animation_time = self.current_animation_time();
282
283 let (width, height) = {
284 let mut inner = self.doc.inner_mut();
285 inner.resolve(animation_time);
286 inner.viewport().window_size
287 };
288
289 let proxy = self.proxy.clone();
290 self.renderer
291 .resume(Arc::new(self.window.clone()), width, height, move || {
292 proxy.send_event(BlitzShellEvent::ResumeReady { window_id });
293 });
294 }
295
296 pub fn complete_resume(&mut self) -> bool {
300 if !self.renderer.complete_resume() {
301 return false;
302 }
303
304 let window_id = self.window_id();
305
306 let animation_time = self.current_animation_time();
311 let mut inner = self.doc.inner_mut();
312 inner.resolve(animation_time);
313 let (width, height) = inner.viewport().window_size;
314 let scale = inner.viewport().scale_f64();
315 let insets = self.safe_area_insets.to_logical(scale);
316
317 #[cfg(feature = "custom-widget")]
318 inner.can_create_surfaces(&self.renderer as _);
319
320 self.renderer.set_size(width, height);
321
322 self.renderer.render(|scene| {
323 paint_scene(
324 scene,
325 &mut inner,
326 scale,
327 width,
328 height,
329 insets.left,
330 insets.top,
331 )
332 });
333
334 self.waker = Some(create_waker(&self.proxy, window_id));
335 true
336 }
337
338 pub fn suspend(&mut self) {
339 self.waker = None;
340 self.renderer.suspend();
341
342 #[cfg(feature = "custom-widget")]
343 self.doc.inner_mut().destroy_surfaces();
344 }
345
346 pub fn poll(&mut self) -> bool {
347 if let Some(waker) = &self.waker {
348 let cx = std::task::Context::from_waker(waker);
349 if self.doc.poll(Some(cx)) {
350 #[cfg(feature = "accessibility")]
351 {
352 let inner = self.doc.inner();
353 if inner.has_changes() {
354 self.accessibility.update_tree(&inner);
355 }
356 }
357
358 self.request_redraw();
359 return true;
360 }
361 }
362
363 false
364 }
365
366 pub fn request_redraw(&self) {
367 if self.renderer.is_active() {
368 self.window.request_redraw();
369 #[cfg(target_os = "ios")]
370 self.ios_request_redraw.set(true);
371 }
372 }
373
374 pub fn redraw(&mut self) {
375 #[cfg(target_os = "ios")]
376 self.ios_request_redraw.set(false);
377 let animation_time = self.current_animation_time();
378 let is_visible = self.is_visible;
379
380 let mut inner = self.doc.inner_mut();
381 inner.resolve(animation_time);
382
383 #[cfg(feature = "custom-widget")]
385 for id in inner.take_pending_resource_deallocations() {
386 self.renderer.unregister_resource(id);
387 }
388
389 let (width, height) = inner.viewport().window_size;
390 let scale = inner.viewport().scale_f64();
391 let is_animating = inner.is_animating();
392 let is_blocked = inner.has_pending_critical_resources();
393 let insets = self.safe_area_insets.to_logical(scale);
394
395 if !is_blocked && is_visible {
396 self.renderer.render(|scene| {
397 paint_scene(
398 scene,
399 &mut inner,
400 scale,
401 width,
402 height,
403 insets.left,
404 insets.top,
405 )
406 });
407 }
408
409 drop(inner);
410
411 if !is_blocked && is_visible && is_animating {
412 self.request_redraw();
413 }
414 }
415
416 pub fn pointer_coords(&self, position: PhysicalPosition<f64>) -> PointerCoords {
417 let inner = self.doc.inner();
418 let scale = inner.viewport().scale_f64();
419 let LogicalPosition::<f32> {
420 x: screen_x,
421 y: screen_y,
422 } = position.to_logical(scale);
423 let viewport_scroll_offset = inner.viewport_scroll();
424 let client_x = screen_x - (self.safe_area_insets.left as f64 / scale) as f32;
425 let client_y = screen_y - (self.safe_area_insets.top as f64 / scale) as f32;
426 let page_x = client_x + viewport_scroll_offset.x as f32;
427 let page_y = client_y + viewport_scroll_offset.y as f32;
428
429 PointerCoords {
430 screen_x,
431 screen_y,
432 client_x,
433 client_y,
434 page_x,
435 page_y,
436 }
437 }
438
439 pub fn window_id(&self) -> WindowId {
440 self.window.id()
441 }
442
443 fn set_active_pointer(&self, event: &BlitzPointerEvent) {
447 let mut stored = event.clone();
448 stored.active_pointers = Default::default();
449
450 let mut active = self.active_events.borrow_mut();
451 if let Some(existing) = active.iter_mut().find(|e| e.id == stored.id) {
452 *existing = stored;
453 } else {
454 active.push(stored);
455 }
456 }
457
458 fn update_active_pointer(&self, event: &BlitzPointerEvent) {
461 let mut active = self.active_events.borrow_mut();
462 if let Some(existing) = active.iter_mut().find(|e| e.id == event.id) {
463 let mut stored = event.clone();
464 stored.active_pointers = Default::default();
465 *existing = stored;
466 }
467 }
468
469 fn remove_active_pointer(&self, id: BlitzPointerId) -> bool {
471 let mut active = self.active_events.borrow_mut();
472 let len_before = active.len();
473 active.retain(|e| e.id != id);
474 active.len() != len_before
475 }
476
477 #[inline]
478 pub fn with_viewport(&mut self, cb: impl FnOnce(&mut Viewport)) {
479 let mut inner = self.doc.inner_mut();
480 let mut viewport = inner.viewport_mut();
481 cb(&mut viewport);
482 let (width, height) = viewport.window_size;
483 drop(viewport);
484 drop(inner);
485 if width > 0 && height > 0 {
486 let insets = self.safe_area_insets;
487 self.renderer.set_size(
488 width + insets.left + insets.right,
489 height + insets.top + insets.bottom,
490 );
491 self.request_redraw();
492 }
493 }
494
495 #[cfg(feature = "accessibility")]
496 pub fn build_accessibility_tree(&mut self) {
497 let inner = self.doc.inner();
498 self.accessibility.update_tree(&inner);
499 }
500
501 #[cfg(target_arch = "wasm32")]
502 const RESIZE_DEBOUNCE_MS: u32 = 100;
503
504 #[cfg(target_arch = "wasm32")]
505 fn schedule_resize_settle_check(&mut self, delay_ms: u32) {
506 use wasm_bindgen::JsCast;
507 use wasm_bindgen::closure::Closure;
508
509 let proxy = self.proxy.clone();
510 let window_id = self.window_id();
511 let cb = Closure::once_into_js(move || {
512 proxy.send_event(BlitzShellEvent::ResizeSettleCheck { window_id });
513 });
514 if let Some(win) = web_sys::window() {
515 let _ = win.set_timeout_with_callback_and_timeout_and_arguments_0(
516 cb.unchecked_ref(),
517 delay_ms as i32,
518 );
519 self.resize_timer_scheduled = true;
520 }
521 }
522
523 #[cfg(target_arch = "wasm32")]
527 pub fn apply_pending_resize_if_settled(&mut self) {
528 self.resize_timer_scheduled = false;
529 let Some(last) = self.last_resize_at else {
530 return;
531 };
532 let debounce = std::time::Duration::from_millis(Self::RESIZE_DEBOUNCE_MS as u64);
533 let elapsed = web_time::Instant::now().saturating_duration_since(last);
534 if elapsed < debounce {
535 let remaining_ms = (debounce - elapsed).as_millis() as u32;
537 self.schedule_resize_settle_check(remaining_ms);
538 return;
539 }
540 let Some(size) = self.pending_resize.take() else {
541 return;
542 };
543 self.last_resize_at = None;
544
545 let insets = self.safe_area_insets;
546 let width = size.width.saturating_sub(insets.left + insets.right);
547 let height = size.height.saturating_sub(insets.top + insets.bottom);
548 self.with_viewport(|v| v.window_size = (width, height));
549 self.request_redraw();
550 }
551
552 #[cfg(target_os = "macos")]
553 pub fn handle_apple_standard_keybinding(&mut self, command: &str) {
554 use blitz_traits::SmolStr;
555 let event = UiEvent::AppleStandardKeybinding(SmolStr::new(command));
556 self.doc.handle_ui_event(event);
557 }
558
559 pub fn handle_winit_event(&mut self, event: WindowEvent) {
560 #[cfg(feature = "accessibility")]
562 self.accessibility
563 .process_window_event(&*self.window, &event);
564
565 match event {
566 WindowEvent::Destroyed => {}
567 WindowEvent::ActivationTokenDone { .. } => {},
568 WindowEvent::CloseRequested => {
569 }
571 WindowEvent::RedrawRequested => {
572 self.redraw();
573 }
574 WindowEvent::Moved(_) => {}
575 WindowEvent::Occluded(is_occluded) => {
576 self.is_visible = !is_occluded;
577 if self.is_visible {
578 self.request_redraw();
579 }
580 },
581 WindowEvent::SurfaceResized(physical_size) => {
582 self.safe_area_insets = get_safe_area_insets(&*self.window);
583 #[cfg(target_arch = "wasm32")]
587 {
588 self.pending_resize = Some(physical_size);
589 self.last_resize_at = Some(web_time::Instant::now());
590 if !self.resize_timer_scheduled {
591 self.schedule_resize_settle_check(Self::RESIZE_DEBOUNCE_MS);
592 }
593 }
594 #[cfg(not(target_arch = "wasm32"))]
595 {
596 let insets = self.safe_area_insets;
597 let width = physical_size.width - insets.left - insets.right;
598 let height = physical_size.height - insets.top - insets.bottom;
599 self.with_viewport(|v| v.window_size = (width, height));
600 self.request_redraw();
601 }
602 }
603 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
604 self.with_viewport(|v| v.set_hidpi_scale(scale_factor as f32));
605 self.request_redraw();
606 }
607 WindowEvent::ThemeChanged(theme) => {
608 let color_scheme = theme_to_color_scheme(self.theme_override.unwrap_or(theme));
609 let mut inner = self.doc.inner_mut();
610 inner.viewport_mut().color_scheme = color_scheme;
611 }
612 WindowEvent::Ime(ime_event) => {
613 self.doc.handle_ui_event(UiEvent::Ime(winit_ime_to_blitz(ime_event)));
614 self.request_redraw();
615 },
616 WindowEvent::ModifiersChanged(new_state) => {
617 self.keyboard_modifiers = new_state;
619 }
620 WindowEvent::KeyboardInput { event, .. } => {
621 if let PhysicalKey::Code(key_code) = event.physical_key && event.state.is_pressed() {
622 let ctrl = self.keyboard_modifiers.state().control_key();
623 let meta = self.keyboard_modifiers.state().meta_key();
624 let alt = self.keyboard_modifiers.state().alt_key();
625
626 if ctrl | meta {
628 match key_code {
629 KeyCode::Equal => {
630 self.doc.inner_mut().viewport_mut().zoom_by(0.1);
631 },
632 KeyCode::Minus => {
633 self.doc.inner_mut().viewport_mut().zoom_by(-0.1);
634 },
635 KeyCode::Digit0 => {
636 self.doc.inner_mut().viewport_mut().set_zoom(1.0);
637 }
638 _ => {}
639 };
640 }
641
642 if alt {
644 match key_code {
645 KeyCode::KeyD => {
646 let mut inner = self.doc.inner_mut();
647 inner.devtools_mut().toggle_show_layout();
648 drop(inner);
649 self.request_redraw();
650 }
651 KeyCode::KeyH => {
652 let mut inner = self.doc.inner_mut();
653 inner.devtools_mut().toggle_highlight_hover();
654 drop(inner);
655 self.request_redraw();
656 }
657 KeyCode::KeyT => self.doc.inner().print_taffy_tree(),
658 _ => {}
659 };
660 }
661
662 }
663
664 let key_event_data = winit_key_event_to_blitz(&event, self.keyboard_modifiers.state());
666 let event = if event.state.is_pressed() {
667 UiEvent::KeyDown(key_event_data)
668 } else {
669 UiEvent::KeyUp(key_event_data)
670 };
671
672 self.doc.handle_ui_event(event);
673 }
674 WindowEvent::PointerEntered { .. } => {}
675 WindowEvent::PointerLeft { position, primary, kind, .. } => {
676 let id = pointer_kind_to_blitz(&kind);
677
678 if id != BlitzPointerId::Mouse && self.remove_active_pointer(id) {
687 let position = position.unwrap_or(self.pointer_pos);
688 self.pointer_pos = position;
689
690 self.buttons ^= MouseEventButton::Main.into();
692
693 let event = BlitzPointerEvent {
694 id,
695 is_primary: primary,
696 coords: self.pointer_coords(position),
697 button: MouseEventButton::Main,
698 buttons: self.buttons,
699 mods: winit_modifiers_to_kbt_modifiers(self.keyboard_modifiers.state()),
700 details: PointerDetails::default(),
701 element: Default::default(),
702 active_pointers: Arc::clone(&self.active_events),
703 };
704
705 self.doc.handle_ui_event(UiEvent::PointerCancel(event));
706 self.request_redraw();
707 }
708 }
709 WindowEvent::PointerMoved { position, source, primary, .. } => {
710 self.pointer_pos = position;
711 let id = pointer_source_to_blitz(&source);
712 let event = BlitzPointerEvent {
713 id,
714 is_primary: primary,
715 coords: self.pointer_coords(position),
716 button: Default::default(),
717 buttons: self.buttons,
718 mods: winit_modifiers_to_kbt_modifiers(self.keyboard_modifiers.state()),
719 details: pointer_source_to_blitz_details(&source),
720 element: Default::default(),
721 active_pointers: Arc::clone(&self.active_events),
722 };
723 if id != BlitzPointerId::Mouse {
725 self.update_active_pointer(&event);
726 }
727 self.doc.handle_ui_event(UiEvent::PointerMove(event));
728 }
729 WindowEvent::PointerButton { button, state, primary, position, .. } => {
730 let id = button_source_to_blitz(&button);
731 let coords = self.pointer_coords(position);
732 self.pointer_pos = position;
733 let button = match &button {
734 ButtonSource::Mouse(mouse_button) => match mouse_button {
735 MouseButton::Left => MouseEventButton::Main,
736 MouseButton::Right => MouseEventButton::Secondary,
737 MouseButton::Middle => MouseEventButton::Auxiliary,
738 _ => MouseEventButton::Auxiliary,
740 }
741 _ => MouseEventButton::Main,
742 };
743
744 match state {
745 ElementState::Pressed => self.buttons |= button.into(),
746 ElementState::Released => self.buttons ^= button.into(),
747 }
748
749 let pointer_event = BlitzPointerEvent {
750 id,
751 is_primary: primary,
752 coords,
753 button,
754 buttons: self.buttons,
755 mods: winit_modifiers_to_kbt_modifiers(self.keyboard_modifiers.state()),
756
757 details: PointerDetails::default(),
759 element: Default::default(),
760 active_pointers: Arc::clone(&self.active_events),
761 };
762
763 if id != BlitzPointerId::Mouse && state == ElementState::Pressed {
769 self.set_active_pointer(&pointer_event);
770 }
771
772 if id != BlitzPointerId::Mouse {
776 let event = BlitzPointerEvent {
777 id,
778 is_primary: primary,
779 coords,
780 button: Default::default(),
781 buttons: self.buttons,
782 mods: winit_modifiers_to_kbt_modifiers(self.keyboard_modifiers.state()),
783 details: PointerDetails::default(),
784 element: Default::default(),
785 active_pointers: Arc::clone(&self.active_events),
786 };
787 self.doc.handle_ui_event(UiEvent::PointerMove(event));
788 }
789
790 if id != BlitzPointerId::Mouse && state == ElementState::Released {
791 self.remove_active_pointer(id);
792 }
793
794 let event = pointer_event;
795
796 let event = match state {
797 ElementState::Pressed => UiEvent::PointerDown(event),
798 ElementState::Released => UiEvent::PointerUp(event),
799 };
800
801 self.doc.handle_ui_event(event);
802 self.request_redraw();
803 }
804 WindowEvent::MouseWheel { delta, .. } => {
805 let blitz_delta = match delta {
806 winit::event::MouseScrollDelta::LineDelta(x, y) => BlitzWheelDelta::Lines(x as f64, y as f64),
807 winit::event::MouseScrollDelta::PixelDelta(pos) => BlitzWheelDelta::Pixels(pos.x, pos.y),
808 };
809
810 let event = BlitzWheelEvent {
811 delta: blitz_delta,
812 coords: self.pointer_coords(self.pointer_pos),
813 buttons: self.buttons,
814 mods: winit_modifiers_to_kbt_modifiers(self.keyboard_modifiers.state()),
815 element: Default::default()
816 };
817
818 self.doc.handle_ui_event(UiEvent::Wheel(event));
819 }
820 WindowEvent::Focused(_) => {}
821 WindowEvent::TouchpadPressure { .. } => {}
822 WindowEvent::PinchGesture { .. } => {},
823 WindowEvent::PanGesture { .. } => {},
824 WindowEvent::DoubleTapGesture { .. } => {},
825 WindowEvent::RotationGesture { .. } => {},
826 WindowEvent::DragEntered { .. } => {},
827 WindowEvent::DragMoved { .. } => {},
828 WindowEvent::DragDropped { .. } => {},
829 WindowEvent::DragLeft { .. } => {},
830 }
831 }
832}