// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use super::{ EventResult, FocusReasonArg, Item, ItemConsts, ItemRc, ItemRendererRef, KeyEventArg, PointerEvent, PointerEventArg, PointerEventButton, PointerEventKind, PointerScrollEvent, PointerScrollEventArg, RenderingResult, VoidArg, }; use crate::api::LogicalPosition; use crate::input::{ FocusEvent, FocusEventResult, FocusReason, InputEventFilterResult, InputEventResult, InternalKeyEvent, KeyEventResult, KeyEventType, Keys, MouseEvent, }; use crate::item_rendering::CachedRenderingData; use crate::items::{ItemTreeVTable, MouseCursorInner}; use crate::layout::{LayoutInfo, Orientation}; use crate::lengths::{LogicalLength, LogicalPoint, LogicalRect, LogicalSize, PointLengths}; use crate::properties::PropertyTracker; #[cfg(feature = "rtti")] use crate::rtti::*; use crate::window::{WindowAdapter, WindowInner}; use crate::{Callback, Coord, Property}; use alloc::{boxed::Box, rc::Rc, vec::Vec}; use const_field_offset::FieldOffsets; use core::cell::Cell; use core::pin::Pin; use i_slint_core_macros::*; use vtable::{VRcMapped, VWeakMapped}; /// The implementation of the `TouchArea` element #[repr(C)] #[derive(FieldOffsets, SlintElement, Default)] #[pin] pub struct TouchArea { pub enabled: Property, /// FIXME: We should annotate this as an "output" property. pub pressed: Property, pub has_hover: Property, /// FIXME: there should be just one property for the point instead of two. /// Could even be merged with pressed in a `Property>` (of course, in the /// implementation item only, for the compiler it would stay separate properties) pub pressed_x: Property, pub pressed_y: Property, /// FIXME: should maybe be as parameter to the mouse event instead. Or at least just one property pub mouse_x: Property, pub mouse_y: Property, pub mouse_cursor: Property, pub clicked: Callback, pub double_clicked: Callback, pub moved: Callback, pub pointer_event: Callback, pub scroll_event: Callback, /// FIXME: remove this pub cached_rendering_data: CachedRenderingData, /// true when we are currently grabbing the mouse grabbed: Cell, } impl Item for TouchArea { fn init(self: Pin<&Self>, _self_rc: &ItemRc) {} fn deinit(self: Pin<&Self>, _window_adapter: &Rc) {} fn layout_info( self: Pin<&Self>, _orientation: Orientation, _cross_axis_constraint: Coord, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> LayoutInfo { LayoutInfo { stretch: 1., ..LayoutInfo::default() } } fn input_event_filter_before_children( self: Pin<&Self>, event: &MouseEvent, window_adapter: &Rc, _self_rc: &ItemRc, cursor: &mut MouseCursorInner, ) -> InputEventFilterResult { if !self.enabled() { self.has_hover.set(false); if self.grabbed.replace(false) { self.pressed.set(false); Self::FIELD_OFFSETS.pointer_event().apply_pin(self).call(&(PointerEvent { button: PointerEventButton::Other, kind: PointerEventKind::Cancel, modifiers: window_adapter.window().0.context().0.modifiers.get().into(), touch_finger_id: 0, },)); } return InputEventFilterResult::ForwardAndIgnore; } if matches!(event, MouseEvent::DragMove { .. } | MouseEvent::Drop { .. }) { // Someone else has the grab, don't handle hover return InputEventFilterResult::ForwardAndIgnore; } if let Some(pos) = event.position() { Self::FIELD_OFFSETS.mouse_x().apply_pin(self).set(pos.x_length()); Self::FIELD_OFFSETS.mouse_y().apply_pin(self).set(pos.y_length()); } let hovering = !matches!(event, MouseEvent::Exit); Self::FIELD_OFFSETS.has_hover().apply_pin(self).set(hovering); if hovering { *cursor = self.mouse_cursor(); } InputEventFilterResult::ForwardAndInterceptGrab } fn input_event( self: Pin<&Self>, event: &MouseEvent, window_adapter: &Rc, self_rc: &ItemRc, _: &mut MouseCursorInner, ) -> InputEventResult { if matches!(event, MouseEvent::Exit) { Self::FIELD_OFFSETS.has_hover().apply_pin(self).set(false); } if !self.enabled() { return InputEventResult::EventIgnored; } match event { MouseEvent::Pressed { position, button, touch_finger_id, .. } => { self.grabbed.set(true); if *button == PointerEventButton::Left { Self::FIELD_OFFSETS.pressed_x().apply_pin(self).set(position.x_length()); Self::FIELD_OFFSETS.pressed_y().apply_pin(self).set(position.y_length()); Self::FIELD_OFFSETS.pressed().apply_pin(self).set(true); } Self::FIELD_OFFSETS.pointer_event().apply_pin(self).call(&(PointerEvent { button: *button, kind: PointerEventKind::Down, modifiers: window_adapter.window().0.context().0.modifiers.get().into(), touch_finger_id: *touch_finger_id, },)); InputEventResult::GrabMouse } MouseEvent::Exit => { Self::FIELD_OFFSETS.pressed().apply_pin(self).set(false); if self.grabbed.replace(false) { Self::FIELD_OFFSETS.pointer_event().apply_pin(self).call(&(PointerEvent { button: PointerEventButton::Other, kind: PointerEventKind::Cancel, modifiers: window_adapter.window().0.context().0.modifiers.get().into(), touch_finger_id: 0, },)); } InputEventResult::EventAccepted } MouseEvent::Released { button, position, click_count, touch_finger_id } => { let geometry = self_rc.geometry(); if *button == PointerEventButton::Left && LogicalRect::new(LogicalPoint::default(), geometry.size).contains(*position) && self.pressed() { Self::FIELD_OFFSETS.clicked().apply_pin(self).call(&()); if (click_count % 2) == 1 { Self::FIELD_OFFSETS.double_clicked().apply_pin(self).call(&()) } } self.grabbed.set(false); if *button == PointerEventButton::Left { Self::FIELD_OFFSETS.pressed().apply_pin(self).set(false); } Self::FIELD_OFFSETS.pointer_event().apply_pin(self).call(&(PointerEvent { button: *button, kind: PointerEventKind::Up, modifiers: window_adapter.window().0.context().0.modifiers.get().into(), touch_finger_id: *touch_finger_id, },)); InputEventResult::EventAccepted } MouseEvent::Moved { touch_finger_id, .. } => { Self::FIELD_OFFSETS.pointer_event().apply_pin(self).call(&(PointerEvent { button: PointerEventButton::Other, kind: PointerEventKind::Move, modifiers: window_adapter.window().0.context().0.modifiers.get().into(), touch_finger_id: *touch_finger_id, },)); if self.grabbed.get() { Self::FIELD_OFFSETS.moved().apply_pin(self).call(&()); InputEventResult::GrabMouse } else { InputEventResult::EventAccepted } } MouseEvent::Wheel { delta_x, delta_y, .. } => { let modifiers = window_adapter.window().0.context().0.modifiers.get().into(); let r = Self::FIELD_OFFSETS.scroll_event().apply_pin(self).call(&( PointerScrollEvent { delta_x: *delta_x, delta_y: *delta_y, modifiers }, )); if self.grabbed.get() { InputEventResult::GrabMouse } else { match r { EventResult::Reject => { // We are ignoring the event, so we will be removed from the item_stack, // therefore we must remove the has_hover flag as there might be a scroll under us. // It will be put back later. Self::FIELD_OFFSETS.has_hover().apply_pin(self).set(false); InputEventResult::EventIgnored } EventResult::Accept => InputEventResult::EventAccepted, } } } MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } => { InputEventResult::EventIgnored } MouseEvent::DragMove { .. } | MouseEvent::Drop { .. } => InputEventResult::EventIgnored, } } fn capture_key_event( self: Pin<&Self>, _: &InternalKeyEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> KeyEventResult { KeyEventResult::EventIgnored } fn key_event( self: Pin<&Self>, _: &InternalKeyEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> KeyEventResult { KeyEventResult::EventIgnored } fn focus_event( self: Pin<&Self>, _: &FocusEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> FocusEventResult { FocusEventResult::FocusIgnored } fn render( self: Pin<&Self>, _backend: &mut ItemRendererRef, _self_rc: &ItemRc, _size: LogicalSize, ) -> RenderingResult { RenderingResult::ContinueRenderingChildren } fn bounding_rect( self: core::pin::Pin<&Self>, _window_adapter: &Rc, _self_rc: &ItemRc, mut geometry: LogicalRect, ) -> LogicalRect { geometry.size = LogicalSize::zero(); geometry } fn clips_children(self: core::pin::Pin<&Self>) -> bool { false } } impl ItemConsts for TouchArea { const cached_rendering_data_offset: const_field_offset::FieldOffset< TouchArea, CachedRenderingData, > = TouchArea::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection(); } impl ItemConsts for KeyBinding { const cached_rendering_data_offset: const_field_offset::FieldOffset< KeyBinding, CachedRenderingData, > = KeyBinding::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection(); } /// The implementation of the `WindowMoveArea` element #[repr(C)] #[derive(FieldOffsets, Default, SlintElement)] #[pin] pub struct WindowMoveArea { pub enabled: Property, pressed: Cell, pressed_position: Cell, pub cached_rendering_data: CachedRenderingData, } impl Item for WindowMoveArea { fn init(self: Pin<&Self>, _self_rc: &ItemRc) {} fn deinit(self: Pin<&Self>, _window_adapter: &Rc) {} fn layout_info( self: Pin<&Self>, _: Orientation, _cross_axis_constraint: Coord, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> LayoutInfo { LayoutInfo { stretch: 1., ..LayoutInfo::default() } } fn input_event_filter_before_children( self: Pin<&Self>, event: &MouseEvent, _window_adapter: &Rc, _self_rc: &ItemRc, _: &mut MouseCursorInner, ) -> InputEventFilterResult { if !self.enabled() { self.pressed.set(false); return InputEventFilterResult::ForwardAndIgnore; } super::drag_n_drop::press_drag_filter(&self.pressed, &self.pressed_position, event) } fn input_event( self: Pin<&Self>, event: &MouseEvent, window_adapter: &Rc, _self_rc: &ItemRc, _: &mut MouseCursorInner, ) -> InputEventResult { match event { MouseEvent::Pressed { .. } => InputEventResult::EventAccepted, MouseEvent::Exit | MouseEvent::Released { .. } => { self.pressed.set(false); InputEventResult::EventIgnored } MouseEvent::Moved { position, .. } => { if !self.pressed.get() || !self.enabled() { return InputEventResult::EventIgnored; } if super::drag_n_drop::exceeds_drag_threshold( self.pressed_position.get(), *position, ) { self.pressed.set(false); if let Some(internal) = window_adapter.internal(crate::InternalToken) { internal.start_window_move(); } } InputEventResult::EventAccepted } MouseEvent::Wheel { .. } => InputEventResult::EventIgnored, MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } => { InputEventResult::EventIgnored } MouseEvent::DragMove { .. } | MouseEvent::Drop { .. } => InputEventResult::EventIgnored, } } fn capture_key_event( self: Pin<&Self>, _: &InternalKeyEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> KeyEventResult { KeyEventResult::EventIgnored } fn key_event( self: Pin<&Self>, _: &InternalKeyEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> KeyEventResult { KeyEventResult::EventIgnored } fn focus_event( self: Pin<&Self>, _: &FocusEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> FocusEventResult { FocusEventResult::FocusIgnored } fn render( self: Pin<&Self>, _: &mut ItemRendererRef, _self_rc: &ItemRc, _size: LogicalSize, ) -> RenderingResult { RenderingResult::ContinueRenderingChildren } fn bounding_rect( self: core::pin::Pin<&Self>, _window_adapter: &Rc, _self_rc: &ItemRc, mut geometry: LogicalRect, ) -> LogicalRect { geometry.size = LogicalSize::zero(); geometry } fn clips_children(self: core::pin::Pin<&Self>) -> bool { false } } impl ItemConsts for WindowMoveArea { const cached_rendering_data_offset: const_field_offset::FieldOffset< WindowMoveArea, CachedRenderingData, > = WindowMoveArea::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection(); } #[repr(C)] #[derive(FieldOffsets, Default, SlintElement)] #[pin] pub struct KeyBinding { pub keys: Property, pub enabled: Property, pub activated: Callback, pub cached_rendering_data: CachedRenderingData, } impl Item for KeyBinding { fn init(self: Pin<&Self>, _self_rc: &ItemRc) {} fn deinit(self: Pin<&Self>, _window_adapter: &Rc) {} fn layout_info( self: Pin<&Self>, _orientation: crate::items::Orientation, _cross_axis_constraint: Coord, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> crate::layout::LayoutInfo { Default::default() } fn input_event_filter_before_children( self: Pin<&Self>, _: &crate::input::MouseEvent, _window_adapter: &Rc, _self_rc: &ItemRc, _: &mut crate::cursor::MouseCursorInner, ) -> crate::input::InputEventFilterResult { Default::default() } fn input_event( self: Pin<&Self>, _: &crate::input::MouseEvent, _window_adapter: &Rc, _self_rc: &ItemRc, _: &mut crate::cursor::MouseCursorInner, ) -> crate::input::InputEventResult { Default::default() } fn capture_key_event( self: Pin<&Self>, _: &InternalKeyEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> crate::input::KeyEventResult { crate::input::KeyEventResult::EventIgnored } fn key_event( self: Pin<&Self>, _: &InternalKeyEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> crate::input::KeyEventResult { Default::default() } fn focus_event( self: Pin<&Self>, _: &crate::input::FocusEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> crate::input::FocusEventResult { Default::default() } fn render( self: Pin<&Self>, _backend: &mut &mut dyn crate::item_rendering::ItemRenderer, _self_rc: &ItemRc, _size: crate::lengths::LogicalSize, ) -> crate::items::RenderingResult { Default::default() } fn bounding_rect( self: Pin<&Self>, _window_adapter: &Rc, _self_rc: &ItemRc, geometry: crate::lengths::LogicalRect, ) -> crate::lengths::LogicalRect { geometry } fn clips_children(self: core::pin::Pin<&Self>) -> bool { false } } /// An optimized ShortcutList that is only initialized when it is /// first accessed. #[repr(C)] #[derive(Default)] // results in a null pointer, which we will initialize on first access pub struct MaybeKeyBindingList(Cell<*const KeyBindingList>); impl MaybeKeyBindingList { fn ensure_init(&self) { // This would be a race condition in Multi-threaded code, but // this type isn't Sync, so this function cannot race with another thread. if self.0.get().is_null() { self.0.set(Box::leak(Box::default())); } } } impl Drop for MaybeKeyBindingList { fn drop(&mut self) { let ptr = self.0.replace(core::ptr::null()); if !ptr.is_null() { // SAFETY: Must be a pointer returned by `Box::leak`, which is guaranteed by `ensure_init`. drop(unsafe { Box::from_raw(ptr as *mut KeyBindingList) }); } } } impl MaybeKeyBindingList { fn deref_pin(self: Pin<&Self>) -> Pin<&KeyBindingList> { self.ensure_init(); // SAFETY: Must be non-null and properly aligned, which is guaranteed by `ensure_init`. unsafe { Pin::new_unchecked(&*self.get_ref().0.get()) } } } #[derive(Default)] #[pin_project::pin_project] pub struct KeyBindingList { found: core::cell::RefCell>>, #[pin] property_tracker: PropertyTracker, } /// A runtime item that exposes key events #[repr(C)] #[derive(FieldOffsets, Default, SlintElement)] #[pin] pub struct FocusScope { pub enabled: Property, pub has_focus: Property, pub focus_on_click: Property, pub focus_on_tab_navigation: Property, pub key_pressed: Callback, pub key_released: Callback, pub capture_key_pressed: Callback, pub capture_key_released: Callback, pub focus_changed_event: Callback, pub focus_gained: Callback, pub focus_lost: Callback, pub key_bindings: MaybeKeyBindingList, /// FIXME: remove this pub cached_rendering_data: CachedRenderingData, } impl FocusScope { fn visit_enabled_key_bindings( self: Pin<&Self>, self_rc: &ItemRc, mut fun: impl FnMut(&VRcMapped) -> Option, ) -> Option { let list = Self::FIELD_OFFSETS.key_bindings().apply_pin(self); let list = list.deref_pin(); list.project_ref().property_tracker.evaluate_if_dirty(|| { let mut found = list.found.borrow_mut(); found.clear(); let mut next = self_rc.first_child(); while let Some(child) = next { if let Some(key_binding) = ItemRc::downcast::(&child) && key_binding.as_pin_ref().enabled() { found.push(VRcMapped::downgrade(&key_binding)); } next = child.next_sibling(); } }); let list = list.found.borrow(); for key_binding in &*list { let Some(shortcut) = key_binding.upgrade() else { crate::debug_log!("Warning: Found a dropped KeyBinding!"); continue; }; if let Some(result) = fun(&shortcut) { return Some(result); } } None } /// Returns the first matching key binding and whether there are multiple matches. fn key_binding_for_event( self: Pin<&Self>, self_rc: &ItemRc, inter_key_event: &InternalKeyEvent, ) -> Option<(VRcMapped, bool)> { let mut first_match = None; let ambiguous = self.visit_enabled_key_bindings(self_rc, |key_binding| { let keys = key_binding.as_pin_ref().keys(); if keys.matches(&inter_key_event.key_event) { match &first_match { Some(key_binding) => { return Some(VRcMapped::clone(key_binding)); } None => { first_match = Some(VRcMapped::clone(key_binding)); } }; } None }); first_match.map(|binding| (VRcMapped::clone(&binding), ambiguous.is_some())) } } impl Item for FocusScope { fn init(self: Pin<&Self>, _self_rc: &ItemRc) {} fn deinit(self: Pin<&Self>, _window_adapter: &Rc) {} fn layout_info( self: Pin<&Self>, _orientation: Orientation, _cross_axis_constraint: Coord, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> LayoutInfo { LayoutInfo { stretch: 1., ..LayoutInfo::default() } } fn input_event_filter_before_children( self: Pin<&Self>, _: &MouseEvent, _window_adapter: &Rc, _self_rc: &ItemRc, _: &mut MouseCursorInner, ) -> InputEventFilterResult { InputEventFilterResult::ForwardEvent } fn input_event( self: Pin<&Self>, event: &MouseEvent, window_adapter: &Rc, self_rc: &ItemRc, _: &mut MouseCursorInner, ) -> InputEventResult { if self.enabled() && self.focus_on_click() && matches!(event, MouseEvent::Pressed { .. }) && !self.has_focus() { WindowInner::from_pub(window_adapter.window()).set_focus_item( self_rc, true, FocusReason::PointerClick, ); InputEventResult::EventAccepted } else { InputEventResult::EventIgnored } } fn capture_key_event( self: Pin<&Self>, event: &InternalKeyEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> KeyEventResult { let r = match event.event_type { KeyEventType::KeyPressed => Self::FIELD_OFFSETS .capture_key_pressed() .apply_pin(self) .call(&(event.key_event.clone(),)), KeyEventType::KeyReleased => Self::FIELD_OFFSETS .capture_key_released() .apply_pin(self) .call(&(event.key_event.clone(),)), KeyEventType::UpdateComposition | KeyEventType::CommitComposition => { EventResult::Reject } }; match r { EventResult::Accept => KeyEventResult::EventAccepted, EventResult::Reject => KeyEventResult::EventIgnored, } } fn key_event( self: Pin<&Self>, event: &InternalKeyEvent, _window_adapter: &Rc, self_rc: &ItemRc, ) -> KeyEventResult { let r = match event.event_type { KeyEventType::KeyPressed => { if let Some((key_binding, ambiguous)) = self.key_binding_for_event(self_rc, event) { if ambiguous { let keys = KeyBinding::FIELD_OFFSETS .keys() .apply_pin(key_binding.as_pin_ref()) .get(); crate::debug_log!( "Warning: Multiple matching KeyBinding elements for keys {:?}!", keys ); } KeyBinding::FIELD_OFFSETS .activated() .apply_pin(key_binding.as_pin_ref()) .call(&()); EventResult::Accept } else { Self::FIELD_OFFSETS .key_pressed() .apply_pin(self) .call(&(event.key_event.clone(),)) } } KeyEventType::KeyReleased => { let binding = self.key_binding_for_event(self_rc, event); if binding.is_some() { // The binding should have already handled the key press, // so we just accept the release event if it matches a binding, // to ensure the events remain "symmetric" in the key-pressed and key-released callbacks. // Either both events appear in the callbacks, or neither do. EventResult::Accept } else { Self::FIELD_OFFSETS .key_released() .apply_pin(self) .call(&(event.key_event.clone(),)) } } KeyEventType::UpdateComposition | KeyEventType::CommitComposition => { EventResult::Reject } }; match r { EventResult::Accept => KeyEventResult::EventAccepted, EventResult::Reject => KeyEventResult::EventIgnored, } } fn focus_event( self: Pin<&Self>, event: &FocusEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> FocusEventResult { if !self.enabled() { return FocusEventResult::FocusIgnored; } match event { FocusEvent::FocusIn(reason) => { match reason { FocusReason::TabNavigation if !self.focus_on_tab_navigation() => { return FocusEventResult::FocusIgnored; } FocusReason::PointerClick if !self.focus_on_click() => { return FocusEventResult::FocusIgnored; } _ => (), }; self.has_focus.set(true); Self::FIELD_OFFSETS.focus_changed_event().apply_pin(self).call(&(*reason,)); Self::FIELD_OFFSETS.focus_gained().apply_pin(self).call(&(*reason,)); } FocusEvent::FocusOut(reason) => { self.has_focus.set(false); Self::FIELD_OFFSETS.focus_changed_event().apply_pin(self).call(&(*reason,)); Self::FIELD_OFFSETS.focus_lost().apply_pin(self).call(&(*reason,)); } } FocusEventResult::FocusAccepted } fn render( self: Pin<&Self>, _backend: &mut ItemRendererRef, _self_rc: &ItemRc, _size: LogicalSize, ) -> RenderingResult { RenderingResult::ContinueRenderingChildren } fn bounding_rect( self: core::pin::Pin<&Self>, _window_adapter: &Rc, _self_rc: &ItemRc, mut geometry: LogicalRect, ) -> LogicalRect { geometry.size = LogicalSize::zero(); geometry } fn clips_children(self: core::pin::Pin<&Self>) -> bool { false } } impl ItemConsts for FocusScope { const cached_rendering_data_offset: const_field_offset::FieldOffset< FocusScope, CachedRenderingData, > = FocusScope::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection(); } #[repr(C)] #[derive(FieldOffsets, Default, SlintElement)] #[pin] pub struct SwipeGestureHandler { pub enabled: Property, pub handle_swipe_left: Property, pub handle_swipe_right: Property, pub handle_swipe_up: Property, pub handle_swipe_down: Property, pub moved: Callback, pub swiped: Callback, pub cancelled: Callback, pub pressed_position: Property, pub current_position: Property, pub swiping: Property, // true when the cursor is pressed down and we haven't cancelled yet for another reason pressed: Cell, // capture_events: Cell, /// FIXME: remove this pub cached_rendering_data: CachedRenderingData, } impl Item for SwipeGestureHandler { fn init(self: Pin<&Self>, _self_rc: &ItemRc) {} fn deinit(self: Pin<&Self>, _window_adapter: &Rc) {} fn layout_info( self: Pin<&Self>, _orientation: Orientation, _cross_axis_constraint: Coord, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> LayoutInfo { LayoutInfo { stretch: 1., ..LayoutInfo::default() } } fn input_event_filter_before_children( self: Pin<&Self>, event: &MouseEvent, _window_adapter: &Rc, _self_rc: &ItemRc, _: &mut MouseCursorInner, ) -> InputEventFilterResult { if !self.enabled() { if self.pressed.get() { self.cancel_impl(); } return InputEventFilterResult::ForwardAndIgnore; } match event { MouseEvent::Pressed { position, button: PointerEventButton::Left, .. } => { Self::FIELD_OFFSETS .pressed_position() .apply_pin(self) .set(crate::lengths::logical_position_to_api(*position)); self.pressed.set(true); InputEventFilterResult::DelayForwarding( super::flickable::FORWARD_DELAY.as_millis() as _ ) } MouseEvent::Exit => { self.cancel_impl(); InputEventFilterResult::ForwardAndIgnore } MouseEvent::Released { button: PointerEventButton::Left, .. } => { if self.swiping() { InputEventFilterResult::Intercept } else { self.pressed.set(false); InputEventFilterResult::ForwardEvent } } MouseEvent::Moved { position, .. } => { if self.swiping() { InputEventFilterResult::Intercept } else if !self.pressed.get() { InputEventFilterResult::ForwardEvent } else if self.is_over_threshold(position) { InputEventFilterResult::Intercept } else { InputEventFilterResult::ForwardAndInterceptGrab } } MouseEvent::Wheel { .. } => InputEventFilterResult::ForwardAndIgnore, // Not the left button MouseEvent::Pressed { .. } | MouseEvent::Released { .. } => { InputEventFilterResult::ForwardAndIgnore } MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } => { InputEventFilterResult::ForwardAndIgnore } MouseEvent::DragMove { .. } | MouseEvent::Drop { .. } => { InputEventFilterResult::ForwardAndIgnore } } } fn input_event( self: Pin<&Self>, event: &MouseEvent, _window_adapter: &Rc, _self_rc: &ItemRc, _: &mut MouseCursorInner, ) -> InputEventResult { match event { MouseEvent::Pressed { .. } => InputEventResult::GrabMouse, MouseEvent::Exit => { self.cancel_impl(); InputEventResult::EventIgnored } MouseEvent::Released { position, .. } => { if !self.pressed.get() && !self.swiping() { return InputEventResult::EventIgnored; } self.current_position.set(crate::lengths::logical_position_to_api(*position)); self.pressed.set(false); if self.swiping() { Self::FIELD_OFFSETS.swiping().apply_pin(self).set(false); Self::FIELD_OFFSETS.swiped().apply_pin(self).call(&()); InputEventResult::EventAccepted } else { InputEventResult::EventIgnored } } MouseEvent::Moved { position, .. } => { if !self.pressed.get() { return InputEventResult::EventAccepted; } self.current_position.set(crate::lengths::logical_position_to_api(*position)); let mut swiping = self.swiping(); if !swiping && self.is_over_threshold(position) { Self::FIELD_OFFSETS.swiping().apply_pin(self).set(true); swiping = true; } Self::FIELD_OFFSETS.moved().apply_pin(self).call(&()); if swiping { InputEventResult::GrabMouse } else { InputEventResult::EventAccepted } } MouseEvent::Wheel { .. } => InputEventResult::EventIgnored, MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } => { InputEventResult::EventIgnored } MouseEvent::DragMove { .. } | MouseEvent::Drop { .. } => InputEventResult::EventIgnored, } } fn capture_key_event( self: Pin<&Self>, _: &InternalKeyEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> KeyEventResult { KeyEventResult::EventIgnored } fn key_event( self: Pin<&Self>, _event: &InternalKeyEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> KeyEventResult { KeyEventResult::EventIgnored } fn focus_event( self: Pin<&Self>, _: &FocusEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> FocusEventResult { FocusEventResult::FocusIgnored } fn render( self: Pin<&Self>, _backend: &mut ItemRendererRef, _self_rc: &ItemRc, _size: LogicalSize, ) -> RenderingResult { RenderingResult::ContinueRenderingChildren } fn bounding_rect( self: core::pin::Pin<&Self>, _window_adapter: &Rc, _self_rc: &ItemRc, mut geometry: LogicalRect, ) -> LogicalRect { geometry.size = LogicalSize::zero(); geometry } fn clips_children(self: core::pin::Pin<&Self>) -> bool { false } } impl ItemConsts for SwipeGestureHandler { const cached_rendering_data_offset: const_field_offset::FieldOffset = Self::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection(); } impl SwipeGestureHandler { pub fn cancel(self: Pin<&Self>, _: &Rc, _: &ItemRc) { self.cancel_impl(); } fn cancel_impl(self: Pin<&Self>) { if !self.pressed.replace(false) { debug_assert!(!self.swiping()); return; } if self.swiping() { Self::FIELD_OFFSETS.swiping().apply_pin(self).set(false); Self::FIELD_OFFSETS.cancelled().apply_pin(self).call(&()); } } fn is_over_threshold(self: Pin<&Self>, position: &LogicalPoint) -> bool { let pressed_pos = self.pressed_position(); let dx = position.x - pressed_pos.x as Coord; let dy = position.y - pressed_pos.y as Coord; let threshold = super::flickable::DISTANCE_THRESHOLD.get(); (self.handle_swipe_down() && dy > threshold && dy > dx.abs() / 2 as Coord) || (self.handle_swipe_up() && dy < -threshold && dy < -dx.abs() / 2 as Coord) || (self.handle_swipe_left() && dx < -threshold && dx < -dy.abs() / 2 as Coord) || (self.handle_swipe_right() && dx > threshold && dx > dy.abs() / 2 as Coord) } } #[cfg(feature = "ffi")] mod ffi { use super::*; #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_swipegesturehandler_cancel( s: Pin<&SwipeGestureHandler>, window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque, self_component: &vtable::VRc, self_index: u32, ) { unsafe { let window_adapter = &*(window_adapter as *const Rc); let self_rc = ItemRc::new(self_component.clone(), self_index); s.cancel(window_adapter, &self_rc); } } /// # Safety /// This must be called using a non-null pointer pointing to a chunk of memory big enough to /// hold a MaybeKeybindingList #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_maybe_key_binding_list_init(list: *mut MaybeKeyBindingList) { unsafe { core::ptr::write(list, MaybeKeyBindingList::default()); } } /// # Safety /// This must be called using a non-null pointer pointing to an initialized MaybeKeybindingList #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_maybe_key_binding_list_free(list: *mut MaybeKeyBindingList) { unsafe { core::ptr::drop_in_place(list) }; } } /// The implementation of the `ScaleRotateGestureHandler` element. /// /// Provides an API surface for platform-recognized pinch gesture events. /// Receives `MouseEvent::PinchGesture` events via the normal mouse event /// tree-walk and exposes cumulative scale, center position, and lifecycle callbacks. #[repr(C)] #[derive(FieldOffsets, Default, SlintElement)] #[pin] pub struct ScaleRotateGestureHandler { pub enabled: Property, // Output properties pub active: Property, /// Cumulative scale factor relative to gesture start. Always 1.0 when the /// gesture starts, then updated as the gesture progresses (e.g., 2.0 means /// doubled, 0.5 means halved). pub scale: Property, /// Cumulative rotation in degrees relative to gesture start. Always 0.0 when /// the gesture starts. pub rotation: Property, pub center: Property, // Callbacks pub started: Callback, pub updated: Callback, pub ended: Callback, pub cancelled: Callback, /// FIXME: remove this pub cached_rendering_data: CachedRenderingData, } impl Item for ScaleRotateGestureHandler { fn init(self: Pin<&Self>, _self_rc: &ItemRc) { self.scale.set(1.0); } fn deinit(self: Pin<&Self>, _window_adapter: &Rc) {} fn layout_info( self: Pin<&Self>, _orientation: Orientation, _cross_axis_constraint: Coord, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> LayoutInfo { LayoutInfo { stretch: 1., ..LayoutInfo::default() } } fn input_event_filter_before_children( self: Pin<&Self>, event: &MouseEvent, _window_adapter: &Rc, _self_rc: &ItemRc, _: &mut MouseCursorInner, ) -> InputEventFilterResult { match event { // Forward gesture events so inner handlers get first shot MouseEvent::PinchGesture { .. } | MouseEvent::RotationGesture { .. } if self.enabled() => { InputEventFilterResult::ForwardEvent } // While a gesture is active, intercept non-gesture events to // prevent Flickable and other items from processing them concurrently. _ if self.active() => InputEventFilterResult::Intercept, _ => InputEventFilterResult::ForwardAndIgnore, } } fn input_event( self: Pin<&Self>, event: &MouseEvent, _window_adapter: &Rc, _self_rc: &ItemRc, _: &mut MouseCursorInner, ) -> InputEventResult { use crate::input::TouchPhase; match event { MouseEvent::PinchGesture { delta, phase, position } => { if !self.enabled() { if self.active() { self.cancel_impl(); } return InputEventResult::EventIgnored; } let new_scale = self.scale() * (1.0 + delta); Self::FIELD_OFFSETS.scale().apply_pin(self).set(new_scale); let center = crate::lengths::logical_position_to_api(*position); Self::FIELD_OFFSETS.center().apply_pin(self).set(center); match phase { TouchPhase::Started => { if !self.active() { Self::FIELD_OFFSETS.active().apply_pin(self).set(true); Self::FIELD_OFFSETS.started().apply_pin(self).call(&()); } InputEventResult::GrabMouse } TouchPhase::Moved => { if !self.active() { return InputEventResult::EventIgnored; } Self::FIELD_OFFSETS.updated().apply_pin(self).call(&()); InputEventResult::GrabMouse } TouchPhase::Ended => self.end_impl(), TouchPhase::Cancelled => { self.cancel_impl(); InputEventResult::EventAccepted } } } MouseEvent::RotationGesture { delta, phase, position } => { if !self.enabled() { return InputEventResult::EventIgnored; } let center = crate::lengths::logical_position_to_api(*position); Self::FIELD_OFFSETS.center().apply_pin(self).set(center); let new_rotation = self.rotation() + delta; Self::FIELD_OFFSETS.rotation().apply_pin(self).set(new_rotation); match phase { TouchPhase::Started => { if !self.active() { Self::FIELD_OFFSETS.active().apply_pin(self).set(true); Self::FIELD_OFFSETS.started().apply_pin(self).call(&()); } InputEventResult::GrabMouse } TouchPhase::Moved => { if !self.active() { return InputEventResult::EventIgnored; } Self::FIELD_OFFSETS.updated().apply_pin(self).call(&()); InputEventResult::GrabMouse } TouchPhase::Ended => self.end_impl(), TouchPhase::Cancelled => { self.cancel_impl(); InputEventResult::EventAccepted } } } // Grab mouse during active gesture to maintain exclusivity. _ if self.active() => InputEventResult::GrabMouse, _ => InputEventResult::EventIgnored, } } fn capture_key_event( self: Pin<&Self>, _: &InternalKeyEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> KeyEventResult { KeyEventResult::EventIgnored } fn key_event( self: Pin<&Self>, _event: &InternalKeyEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> KeyEventResult { KeyEventResult::EventIgnored } fn focus_event( self: Pin<&Self>, _: &FocusEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> FocusEventResult { FocusEventResult::FocusIgnored } fn render( self: Pin<&Self>, _backend: &mut ItemRendererRef, _self_rc: &ItemRc, _size: LogicalSize, ) -> RenderingResult { RenderingResult::ContinueRenderingChildren } fn bounding_rect( self: core::pin::Pin<&Self>, _window_adapter: &Rc, _self_rc: &ItemRc, mut geometry: LogicalRect, ) -> LogicalRect { geometry.size = LogicalSize::zero(); geometry } fn clips_children(self: core::pin::Pin<&Self>) -> bool { false } } impl ItemConsts for ScaleRotateGestureHandler { const cached_rendering_data_offset: const_field_offset::FieldOffset = Self::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection(); } impl ScaleRotateGestureHandler { fn cancel_impl(self: Pin<&Self>) { if !self.active() { return; } Self::FIELD_OFFSETS.active().apply_pin(self).set(false); Self::FIELD_OFFSETS.cancelled().apply_pin(self).call(&()); // Reset after the callback so handlers can read the last known values // to animate back smoothly, matching the pattern where `ended` leaves // scale/rotation at their final values. Self::FIELD_OFFSETS.scale().apply_pin(self).set(1.0); Self::FIELD_OFFSETS.rotation().apply_pin(self).set(0.0); } fn end_impl(self: Pin<&Self>) -> InputEventResult { if !self.active() { return InputEventResult::EventIgnored; } Self::FIELD_OFFSETS.ended().apply_pin(self).call(&()); Self::FIELD_OFFSETS.active().apply_pin(self).set(false); Self::FIELD_OFFSETS.scale().apply_pin(self).set(1.0); Self::FIELD_OFFSETS.rotation().apply_pin(self).set(0.0); InputEventResult::EventAccepted } }