slint/internal/core/items/drag_n_drop.rs
Tobias Hunger 749abeabe0 core: Add capture_key_event function into items
... and call that during key event delivery.

Add `capture_key_pressed` and `capture_key_released` callbacks
to `FocusScope` to expose this functionality.

Add a simple test to make sure Interception of key events works as
expected.

Related to: #102, #6390
2025-08-01 20:24:24 +02:00

330 lines
10 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
use super::{
DropEvent, Item, ItemConsts, ItemRc, MouseCursor, PointerEventButton, RenderingResult,
};
use crate::input::{
FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, KeyEvent,
KeyEventResult, MouseEvent,
};
use crate::item_rendering::{CachedRenderingData, ItemRenderer};
use crate::layout::{LayoutInfo, Orientation};
use crate::lengths::{LogicalPoint, LogicalRect, LogicalSize};
#[cfg(feature = "rtti")]
use crate::rtti::*;
use crate::window::WindowAdapter;
use crate::{Callback, Property, SharedString};
use alloc::rc::Rc;
use const_field_offset::FieldOffsets;
use core::cell::Cell;
use core::pin::Pin;
use i_slint_core_macros::*;
pub type DropEventArg = (DropEvent,);
#[repr(C)]
#[derive(FieldOffsets, Default, SlintElement)]
#[pin]
/// The implementation of the `DragArea` element
pub struct DragArea {
pub enabled: Property<bool>,
pub mime_type: Property<SharedString>,
pub data: Property<SharedString>,
pressed: Cell<bool>,
pressed_position: Cell<LogicalPoint>,
pub cached_rendering_data: CachedRenderingData,
}
impl Item for DragArea {
fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
fn layout_info(
self: Pin<&Self>,
_: Orientation,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> LayoutInfo {
LayoutInfo { stretch: 1., ..LayoutInfo::default() }
}
fn input_event_filter_before_children(
self: Pin<&Self>,
event: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> InputEventFilterResult {
if !self.enabled() {
self.cancel();
return InputEventFilterResult::ForwardAndIgnore;
}
match event {
MouseEvent::Pressed { position, button: PointerEventButton::Left, .. } => {
self.pressed_position.set(*position);
self.pressed.set(true);
InputEventFilterResult::ForwardAndInterceptGrab
}
MouseEvent::Exit => {
self.cancel();
InputEventFilterResult::ForwardAndIgnore
}
MouseEvent::Released { button: PointerEventButton::Left, .. } => {
self.pressed.set(false);
InputEventFilterResult::ForwardAndIgnore
}
MouseEvent::Moved { position } => {
if !self.pressed.get() {
InputEventFilterResult::ForwardEvent
} else {
let pressed_pos = self.pressed_position.get();
let dx = (position.x - pressed_pos.x).abs();
let dy = (position.y - pressed_pos.y).abs();
let threshold = super::flickable::DISTANCE_THRESHOLD.get();
if dy > threshold || dx > threshold {
InputEventFilterResult::Intercept
} else {
InputEventFilterResult::ForwardAndInterceptGrab
}
}
}
MouseEvent::Wheel { .. } => InputEventFilterResult::ForwardAndIgnore,
// Not the left button
MouseEvent::Pressed { .. } | MouseEvent::Released { .. } => {
InputEventFilterResult::ForwardAndIgnore
}
MouseEvent::DragMove(..) | MouseEvent::Drop(..) => {
InputEventFilterResult::ForwardAndIgnore
}
}
}
fn input_event(
self: Pin<&Self>,
event: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> InputEventResult {
match event {
MouseEvent::Pressed { .. } => InputEventResult::EventAccepted,
MouseEvent::Exit => {
self.cancel();
InputEventResult::EventIgnored
}
MouseEvent::Released { .. } => {
self.cancel();
InputEventResult::EventIgnored
}
MouseEvent::Moved { position } => {
if !self.pressed.get() || !self.enabled() {
return InputEventResult::EventIgnored;
}
let pressed_pos = self.pressed_position.get();
let dx = (position.x - pressed_pos.x).abs();
let dy = (position.y - pressed_pos.y).abs();
let threshold = super::flickable::DISTANCE_THRESHOLD.get();
let start_drag = dx > threshold || dy > threshold;
if start_drag {
self.pressed.set(false);
InputEventResult::StartDrag
} else {
InputEventResult::EventAccepted
}
}
MouseEvent::Wheel { .. } => InputEventResult::EventIgnored,
MouseEvent::DragMove(..) | MouseEvent::Drop(..) => InputEventResult::EventIgnored,
}
}
fn capture_key_event(
self: Pin<&Self>,
_: &KeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn key_event(
self: Pin<&Self>,
_: &KeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn focus_event(
self: Pin<&Self>,
_: &FocusEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> FocusEventResult {
FocusEventResult::FocusIgnored
}
fn render(
self: Pin<&Self>,
_: &mut &mut dyn ItemRenderer,
_self_rc: &ItemRc,
_size: LogicalSize,
) -> RenderingResult {
RenderingResult::ContinueRenderingChildren
}
fn bounding_rect(
self: core::pin::Pin<&Self>,
_window_adapter: &Rc<dyn WindowAdapter>,
_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 DragArea {
const cached_rendering_data_offset: const_field_offset::FieldOffset<
DragArea,
CachedRenderingData,
> = DragArea::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
}
impl DragArea {
fn cancel(self: Pin<&Self>) {
self.pressed.set(false)
}
}
#[repr(C)]
#[derive(FieldOffsets, Default, SlintElement)]
#[pin]
/// The implementation of the `DropArea` element
pub struct DropArea {
pub enabled: Property<bool>,
pub contains_drag: Property<bool>,
pub can_drop: Callback<DropEventArg, bool>,
pub dropped: Callback<DropEventArg>,
pub cached_rendering_data: CachedRenderingData,
}
impl Item for DropArea {
fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
fn layout_info(
self: Pin<&Self>,
_: Orientation,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> LayoutInfo {
LayoutInfo { stretch: 1., ..LayoutInfo::default() }
}
fn input_event_filter_before_children(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> InputEventFilterResult {
InputEventFilterResult::ForwardEvent
}
fn input_event(
self: Pin<&Self>,
event: &MouseEvent,
window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> InputEventResult {
if !self.enabled() {
return InputEventResult::EventIgnored;
}
match event {
MouseEvent::DragMove(event) => {
let r = Self::FIELD_OFFSETS.can_drop.apply_pin(self).call(&(event.clone(),));
if r {
self.contains_drag.set(true);
if let Some(window_adapter) = window_adapter.internal(crate::InternalToken) {
window_adapter.set_mouse_cursor(MouseCursor::Copy);
}
InputEventResult::EventAccepted
} else {
self.contains_drag.set(false);
InputEventResult::EventIgnored
}
}
MouseEvent::Drop(event) => {
self.contains_drag.set(false);
Self::FIELD_OFFSETS.dropped.apply_pin(self).call(&(event.clone(),));
InputEventResult::EventAccepted
}
MouseEvent::Exit => {
self.contains_drag.set(false);
InputEventResult::EventIgnored
}
_ => InputEventResult::EventIgnored,
}
}
fn capture_key_event(
self: Pin<&Self>,
_: &KeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn key_event(
self: Pin<&Self>,
_: &KeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn focus_event(
self: Pin<&Self>,
_: &FocusEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> FocusEventResult {
FocusEventResult::FocusIgnored
}
fn render(
self: Pin<&Self>,
_: &mut &mut dyn ItemRenderer,
_self_rc: &ItemRc,
_size: LogicalSize,
) -> RenderingResult {
RenderingResult::ContinueRenderingChildren
}
fn bounding_rect(
self: core::pin::Pin<&Self>,
_window_adapter: &Rc<dyn WindowAdapter>,
_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 DropArea {
const cached_rendering_data_offset: const_field_offset::FieldOffset<
DropArea,
CachedRenderingData,
> = DropArea::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
}