1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use super::{
5 EventResult, Item, ItemConsts, ItemRc, ItemRendererRef, KeyEventArg, MouseCursor, PointerEvent,
6 PointerEventArg, PointerEventButton, PointerEventKind, PointerScrollEvent,
7 PointerScrollEventArg, RenderingResult, VoidArg,
8};
9use crate::api::LogicalPosition;
10use crate::input::{
11 FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, KeyEvent,
12 KeyEventResult, KeyEventType, MouseEvent,
13};
14use crate::item_rendering::CachedRenderingData;
15use crate::layout::{LayoutInfo, Orientation};
16use crate::lengths::{LogicalLength, LogicalPoint, LogicalRect, LogicalSize, PointLengths};
17#[cfg(feature = "rtti")]
18use crate::rtti::*;
19use crate::window::{WindowAdapter, WindowInner};
20use crate::{Callback, Coord, Property};
21use alloc::rc::Rc;
22use const_field_offset::FieldOffsets;
23use core::cell::Cell;
24use core::pin::Pin;
25use i_slint_core_macros::*;
26
27/// The implementation of the `TouchArea` element
28#[repr(C)]
29#[derive(FieldOffsets, SlintElement, Default)]
30#[pin]
31pub struct TouchArea {
32 pub enabled: Property<bool>,
33 /// FIXME: We should annotate this as an "output" property.
34 pub pressed: Property<bool>,
35 pub has_hover: Property<bool>,
36 /// FIXME: there should be just one property for the point instead of two.
37 /// Could even be merged with pressed in a `Property<Option<Point>>` (of course, in the
38 /// implementation item only, for the compiler it would stay separate properties)
39 pub pressed_x: Property<LogicalLength>,
40 pub pressed_y: Property<LogicalLength>,
41 /// FIXME: should maybe be as parameter to the mouse event instead. Or at least just one property
42 pub mouse_x: Property<LogicalLength>,
43 pub mouse_y: Property<LogicalLength>,
44 pub mouse_cursor: Property<MouseCursor>,
45 pub clicked: Callback<VoidArg>,
46 pub double_clicked: Callback<VoidArg>,
47 pub moved: Callback<VoidArg>,
48 pub pointer_event: Callback<PointerEventArg>,
49 pub scroll_event: Callback<PointerScrollEventArg, EventResult>,
50 /// FIXME: remove this
51 pub cached_rendering_data: CachedRenderingData,
52 /// true when we are currently grabbing the mouse
53 grabbed: Cell<bool>,
54}
55
56impl Item for TouchArea {
57 fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
58
59 fn layout_info(
60 self: Pin<&Self>,
61 _orientation: Orientation,
62 _window_adapter: &Rc<dyn WindowAdapter>,
63 ) -> LayoutInfo {
64 LayoutInfo { stretch: 1., ..LayoutInfo::default() }
65 }
66
67 fn input_event_filter_before_children(
68 self: Pin<&Self>,
69 event: MouseEvent,
70 window_adapter: &Rc<dyn WindowAdapter>,
71 _self_rc: &ItemRc,
72 ) -> InputEventFilterResult {
73 if !self.enabled() {
74 self.has_hover.set(false);
75 if self.grabbed.replace(false) {
76 self.pressed.set(false);
77 Self::FIELD_OFFSETS.pointer_event.apply_pin(self).call(&(PointerEvent {
78 button: PointerEventButton::Other,
79 kind: PointerEventKind::Cancel,
80 modifiers: window_adapter.window().0.modifiers.get().into(),
81 },));
82 }
83 return InputEventFilterResult::ForwardAndIgnore;
84 }
85 if let Some(pos) = event.position() {
86 Self::FIELD_OFFSETS.mouse_x.apply_pin(self).set(pos.x_length());
87 Self::FIELD_OFFSETS.mouse_y.apply_pin(self).set(pos.y_length());
88 }
89 let hovering = !matches!(event, MouseEvent::Exit);
90 Self::FIELD_OFFSETS.has_hover.apply_pin(self).set(hovering);
91 if hovering {
92 if let Some(x) = window_adapter.internal(crate::InternalToken) {
93 x.set_mouse_cursor(self.mouse_cursor());
94 }
95 }
96 InputEventFilterResult::ForwardAndInterceptGrab
97 }
98
99 fn input_event(
100 self: Pin<&Self>,
101 event: MouseEvent,
102 window_adapter: &Rc<dyn WindowAdapter>,
103 self_rc: &ItemRc,
104 ) -> InputEventResult {
105 if matches!(event, MouseEvent::Exit) {
106 Self::FIELD_OFFSETS.has_hover.apply_pin(self).set(false);
107 if let Some(x) = window_adapter.internal(crate::InternalToken) {
108 x.set_mouse_cursor(MouseCursor::Default);
109 }
110 }
111 if !self.enabled() {
112 return InputEventResult::EventIgnored;
113 }
114
115 match event {
116 MouseEvent::Pressed { position, button, .. } => {
117 self.grabbed.set(true);
118 if button == PointerEventButton::Left {
119 Self::FIELD_OFFSETS.pressed_x.apply_pin(self).set(position.x_length());
120 Self::FIELD_OFFSETS.pressed_y.apply_pin(self).set(position.y_length());
121 Self::FIELD_OFFSETS.pressed.apply_pin(self).set(true);
122 }
123 Self::FIELD_OFFSETS.pointer_event.apply_pin(self).call(&(PointerEvent {
124 button,
125 kind: PointerEventKind::Down,
126 modifiers: window_adapter.window().0.modifiers.get().into(),
127 },));
128
129 InputEventResult::GrabMouse
130 }
131 MouseEvent::Exit => {
132 Self::FIELD_OFFSETS.pressed.apply_pin(self).set(false);
133 if self.grabbed.replace(false) {
134 Self::FIELD_OFFSETS.pointer_event.apply_pin(self).call(&(PointerEvent {
135 button: PointerEventButton::Other,
136 kind: PointerEventKind::Cancel,
137 modifiers: window_adapter.window().0.modifiers.get().into(),
138 },));
139 }
140
141 InputEventResult::EventAccepted
142 }
143
144 MouseEvent::Released { button, position, click_count } => {
145 let geometry = self_rc.geometry();
146 if button == PointerEventButton::Left
147 && LogicalRect::new(LogicalPoint::default(), geometry.size).contains(position)
148 && self.pressed()
149 {
150 Self::FIELD_OFFSETS.clicked.apply_pin(self).call(&());
151 if (click_count % 2) == 1 {
152 Self::FIELD_OFFSETS.double_clicked.apply_pin(self).call(&())
153 }
154 }
155
156 self.grabbed.set(false);
157 if button == PointerEventButton::Left {
158 Self::FIELD_OFFSETS.pressed.apply_pin(self).set(false);
159 }
160 Self::FIELD_OFFSETS.pointer_event.apply_pin(self).call(&(PointerEvent {
161 button,
162 kind: PointerEventKind::Up,
163 modifiers: window_adapter.window().0.modifiers.get().into(),
164 },));
165
166 InputEventResult::EventAccepted
167 }
168 MouseEvent::Moved { .. } => {
169 Self::FIELD_OFFSETS.pointer_event.apply_pin(self).call(&(PointerEvent {
170 button: PointerEventButton::Other,
171 kind: PointerEventKind::Move,
172 modifiers: window_adapter.window().0.modifiers.get().into(),
173 },));
174 if self.grabbed.get() {
175 Self::FIELD_OFFSETS.moved.apply_pin(self).call(&());
176 InputEventResult::GrabMouse
177 } else {
178 InputEventResult::EventAccepted
179 }
180 }
181 MouseEvent::Wheel { delta_x, delta_y, .. } => {
182 let modifiers = window_adapter.window().0.modifiers.get().into();
183 let r = Self::FIELD_OFFSETS
184 .scroll_event
185 .apply_pin(self)
186 .call(&(PointerScrollEvent { delta_x, delta_y, modifiers },));
187 if self.grabbed.get() {
188 InputEventResult::GrabMouse
189 } else {
190 match r {
191 EventResult::Reject => {
192 // We are ignoring the event, so we will be removed from the item_stack,
193 // therefore we must remove the has_hover flag as there might be a scroll under us.
194 // It will be put back later.
195 Self::FIELD_OFFSETS.has_hover.apply_pin(self).set(false);
196 InputEventResult::EventIgnored
197 }
198 EventResult::Accept => InputEventResult::EventAccepted,
199 }
200 }
201 }
202 }
203 }
204
205 fn key_event(
206 self: Pin<&Self>,
207 _: &KeyEvent,
208 _window_adapter: &Rc<dyn WindowAdapter>,
209 _self_rc: &ItemRc,
210 ) -> KeyEventResult {
211 KeyEventResult::EventIgnored
212 }
213
214 fn focus_event(
215 self: Pin<&Self>,
216 _: &FocusEvent,
217 _window_adapter: &Rc<dyn WindowAdapter>,
218 _self_rc: &ItemRc,
219 ) -> FocusEventResult {
220 FocusEventResult::FocusIgnored
221 }
222
223 fn render(
224 self: Pin<&Self>,
225 _backend: &mut ItemRendererRef,
226 _self_rc: &ItemRc,
227 _size: LogicalSize,
228 ) -> RenderingResult {
229 RenderingResult::ContinueRenderingChildren
230 }
231
232 fn bounding_rect(
233 self: core::pin::Pin<&Self>,
234 _window_adapter: &Rc<dyn WindowAdapter>,
235 _self_rc: &ItemRc,
236 mut geometry: LogicalRect,
237 ) -> LogicalRect {
238 geometry.size = LogicalSize::zero();
239 geometry
240 }
241
242 fn clips_children(self: core::pin::Pin<&Self>) -> bool {
243 false
244 }
245}
246
247impl ItemConsts for TouchArea {
248 const cached_rendering_data_offset: const_field_offset::FieldOffset<
249 TouchArea,
250 CachedRenderingData,
251 > = TouchArea::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
252}
253
254/// A runtime item that exposes key
255#[repr(C)]
256#[derive(FieldOffsets, Default, SlintElement)]
257#[pin]
258pub struct FocusScope {
259 pub enabled: Property<bool>,
260 pub has_focus: Property<bool>,
261 pub key_pressed: Callback<KeyEventArg, EventResult>,
262 pub key_released: Callback<KeyEventArg, EventResult>,
263 pub focus_changed_event: Callback<VoidArg>,
264 /// FIXME: remove this
265 pub cached_rendering_data: CachedRenderingData,
266}
267
268impl Item for FocusScope {
269 fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
270
271 fn layout_info(
272 self: Pin<&Self>,
273 _orientation: Orientation,
274 _window_adapter: &Rc<dyn WindowAdapter>,
275 ) -> LayoutInfo {
276 LayoutInfo { stretch: 1., ..LayoutInfo::default() }
277 }
278
279 fn input_event_filter_before_children(
280 self: Pin<&Self>,
281 _: MouseEvent,
282 _window_adapter: &Rc<dyn WindowAdapter>,
283 _self_rc: &ItemRc,
284 ) -> InputEventFilterResult {
285 InputEventFilterResult::ForwardEvent
286 }
287
288 fn input_event(
289 self: Pin<&Self>,
290 event: MouseEvent,
291 window_adapter: &Rc<dyn WindowAdapter>,
292 self_rc: &ItemRc,
293 ) -> InputEventResult {
294 if self.enabled() && matches!(event, MouseEvent::Pressed { .. }) && !self.has_focus() {
295 WindowInner::from_pub(window_adapter.window()).set_focus_item(self_rc, true);
296 InputEventResult::EventAccepted
297 } else {
298 InputEventResult::EventIgnored
299 }
300 }
301
302 fn key_event(
303 self: Pin<&Self>,
304 event: &KeyEvent,
305 _window_adapter: &Rc<dyn WindowAdapter>,
306 _self_rc: &ItemRc,
307 ) -> KeyEventResult {
308 let r = match event.event_type {
309 KeyEventType::KeyPressed => {
310 Self::FIELD_OFFSETS.key_pressed.apply_pin(self).call(&(event.clone(),))
311 }
312 KeyEventType::KeyReleased => {
313 Self::FIELD_OFFSETS.key_released.apply_pin(self).call(&(event.clone(),))
314 }
315 KeyEventType::UpdateComposition | KeyEventType::CommitComposition => {
316 EventResult::Reject
317 }
318 };
319 match r {
320 EventResult::Accept => KeyEventResult::EventAccepted,
321 EventResult::Reject => KeyEventResult::EventIgnored,
322 }
323 }
324
325 fn focus_event(
326 self: Pin<&Self>,
327 event: &FocusEvent,
328 _window_adapter: &Rc<dyn WindowAdapter>,
329 _self_rc: &ItemRc,
330 ) -> FocusEventResult {
331 if !self.enabled() {
332 return FocusEventResult::FocusIgnored;
333 }
334
335 match event {
336 FocusEvent::FocusIn | FocusEvent::WindowReceivedFocus => {
337 self.has_focus.set(true);
338 Self::FIELD_OFFSETS.focus_changed_event.apply_pin(self).call(&());
339 }
340 FocusEvent::FocusOut | FocusEvent::WindowLostFocus => {
341 self.has_focus.set(false);
342 Self::FIELD_OFFSETS.focus_changed_event.apply_pin(self).call(&());
343 }
344 }
345 FocusEventResult::FocusAccepted
346 }
347
348 fn render(
349 self: Pin<&Self>,
350 _backend: &mut ItemRendererRef,
351 _self_rc: &ItemRc,
352 _size: LogicalSize,
353 ) -> RenderingResult {
354 RenderingResult::ContinueRenderingChildren
355 }
356
357 fn bounding_rect(
358 self: core::pin::Pin<&Self>,
359 _window_adapter: &Rc<dyn WindowAdapter>,
360 _self_rc: &ItemRc,
361 mut geometry: LogicalRect,
362 ) -> LogicalRect {
363 geometry.size = LogicalSize::zero();
364 geometry
365 }
366
367 fn clips_children(self: core::pin::Pin<&Self>) -> bool {
368 false
369 }
370}
371
372impl ItemConsts for FocusScope {
373 const cached_rendering_data_offset: const_field_offset::FieldOffset<
374 FocusScope,
375 CachedRenderingData,
376 > = FocusScope::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
377}
378
379#[repr(C)]
380#[derive(FieldOffsets, Default, SlintElement)]
381#[pin]
382pub struct SwipeGestureHandler {
383 pub enabled: Property<bool>,
384 pub handle_swipe_left: Property<bool>,
385 pub handle_swipe_right: Property<bool>,
386 pub handle_swipe_up: Property<bool>,
387 pub handle_swipe_down: Property<bool>,
388
389 pub moved: Callback<VoidArg>,
390 pub swiped: Callback<VoidArg>,
391 pub cancelled: Callback<VoidArg>,
392
393 pub pressed_position: Property<LogicalPosition>,
394 pub current_position: Property<LogicalPosition>,
395 pub swiping: Property<bool>,
396
397 // true when the cursor is pressed down and we haven't cancelled yet for another reason
398 pressed: Cell<bool>,
399 // capture_events: Cell<bool>,
400 /// FIXME: remove this
401 pub cached_rendering_data: CachedRenderingData,
402}
403
404impl Item for SwipeGestureHandler {
405 fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
406
407 fn layout_info(
408 self: Pin<&Self>,
409 _orientation: Orientation,
410 _window_adapter: &Rc<dyn WindowAdapter>,
411 ) -> LayoutInfo {
412 LayoutInfo { stretch: 1., ..LayoutInfo::default() }
413 }
414
415 fn input_event_filter_before_children(
416 self: Pin<&Self>,
417 event: MouseEvent,
418 _window_adapter: &Rc<dyn WindowAdapter>,
419 _self_rc: &ItemRc,
420 ) -> InputEventFilterResult {
421 if !self.enabled() {
422 if self.pressed.get() {
423 self.cancel_impl();
424 }
425 return InputEventFilterResult::ForwardAndIgnore;
426 }
427
428 match event {
429 MouseEvent::Pressed { position, button: PointerEventButton::Left, .. } => {
430 Self::FIELD_OFFSETS
431 .pressed_position
432 .apply_pin(self)
433 .set(crate::lengths::logical_position_to_api(position));
434 self.pressed.set(true);
435 InputEventFilterResult::DelayForwarding(
436 super::flickable::FORWARD_DELAY.as_millis() as _
437 )
438 }
439 MouseEvent::Exit => {
440 self.cancel_impl();
441 InputEventFilterResult::ForwardAndIgnore
442 }
443 MouseEvent::Released { button: PointerEventButton::Left, .. } => {
444 if self.swiping() {
445 InputEventFilterResult::Intercept
446 } else {
447 self.pressed.set(false);
448 InputEventFilterResult::ForwardEvent
449 }
450 }
451 MouseEvent::Moved { position } => {
452 if self.swiping() {
453 InputEventFilterResult::Intercept
454 } else if !self.pressed.get() {
455 InputEventFilterResult::ForwardEvent
456 } else {
457 let pressed_pos = self.pressed_position();
458 let dx = position.x - pressed_pos.x as Coord;
459 let dy = position.y - pressed_pos.y as Coord;
460 let threshold = super::flickable::DISTANCE_THRESHOLD.get();
461 if (self.handle_swipe_down() && dy > threshold)
462 || (self.handle_swipe_up() && dy < -threshold)
463 || (self.handle_swipe_left() && dx < -threshold)
464 || (self.handle_swipe_right() && dx > threshold)
465 {
466 InputEventFilterResult::Intercept
467 } else {
468 InputEventFilterResult::ForwardAndInterceptGrab
469 }
470 }
471 }
472 MouseEvent::Wheel { .. } => InputEventFilterResult::ForwardAndIgnore,
473 // Not the left button
474 MouseEvent::Pressed { .. } | MouseEvent::Released { .. } => {
475 InputEventFilterResult::ForwardAndIgnore
476 }
477 }
478 }
479
480 fn input_event(
481 self: Pin<&Self>,
482 event: MouseEvent,
483 _window_adapter: &Rc<dyn WindowAdapter>,
484 _self_rc: &ItemRc,
485 ) -> InputEventResult {
486 match event {
487 MouseEvent::Pressed { .. } => InputEventResult::GrabMouse,
488 MouseEvent::Exit => {
489 self.cancel_impl();
490 InputEventResult::EventIgnored
491 }
492 MouseEvent::Released { position, .. } => {
493 if !self.pressed.get() && !self.swiping() {
494 return InputEventResult::EventIgnored;
495 }
496 self.current_position.set(crate::lengths::logical_position_to_api(position));
497 self.pressed.set(false);
498 if self.swiping() {
499 Self::FIELD_OFFSETS.swiping.apply_pin(self).set(false);
500 Self::FIELD_OFFSETS.swiped.apply_pin(self).call(&());
501 InputEventResult::EventAccepted
502 } else {
503 InputEventResult::EventIgnored
504 }
505 }
506 MouseEvent::Moved { position } => {
507 if !self.pressed.get() {
508 return InputEventResult::EventIgnored;
509 }
510 self.current_position.set(crate::lengths::logical_position_to_api(position));
511 if !self.swiping() {
512 let pressed_pos = self.pressed_position();
513 let dx = position.x - pressed_pos.x as Coord;
514 let dy = position.y - pressed_pos.y as Coord;
515 let threshold = super::flickable::DISTANCE_THRESHOLD.get();
516 let start_swipe = (self.handle_swipe_down() && dy > threshold)
517 || (self.handle_swipe_up() && dy < -threshold)
518 || (self.handle_swipe_left() && dx < -threshold)
519 || (self.handle_swipe_right() && dx > threshold);
520
521 if start_swipe {
522 Self::FIELD_OFFSETS.swiping.apply_pin(self).set(true);
523 }
524 }
525 Self::FIELD_OFFSETS.moved.apply_pin(self).call(&());
526 InputEventResult::GrabMouse
527 }
528 MouseEvent::Wheel { .. } => InputEventResult::EventIgnored,
529 }
530 }
531
532 fn key_event(
533 self: Pin<&Self>,
534 _event: &KeyEvent,
535 _window_adapter: &Rc<dyn WindowAdapter>,
536 _self_rc: &ItemRc,
537 ) -> KeyEventResult {
538 KeyEventResult::EventIgnored
539 }
540
541 fn focus_event(
542 self: Pin<&Self>,
543 _: &FocusEvent,
544 _window_adapter: &Rc<dyn WindowAdapter>,
545 _self_rc: &ItemRc,
546 ) -> FocusEventResult {
547 FocusEventResult::FocusIgnored
548 }
549
550 fn render(
551 self: Pin<&Self>,
552 _backend: &mut ItemRendererRef,
553 _self_rc: &ItemRc,
554 _size: LogicalSize,
555 ) -> RenderingResult {
556 RenderingResult::ContinueRenderingChildren
557 }
558
559 fn bounding_rect(
560 self: core::pin::Pin<&Self>,
561 _window_adapter: &Rc<dyn WindowAdapter>,
562 _self_rc: &ItemRc,
563 mut geometry: LogicalRect,
564 ) -> LogicalRect {
565 geometry.size = LogicalSize::zero();
566 geometry
567 }
568
569 fn clips_children(self: core::pin::Pin<&Self>) -> bool {
570 false
571 }
572}
573
574impl ItemConsts for SwipeGestureHandler {
575 const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
576 Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
577}
578
579impl SwipeGestureHandler {
580 pub fn cancel(self: Pin<&Self>, _: &Rc<dyn WindowAdapter>, _: &ItemRc) {
581 self.cancel_impl();
582 }
583
584 fn cancel_impl(self: Pin<&Self>) {
585 if !self.pressed.replace(val:false) {
586 debug_assert!(!self.swiping());
587 return;
588 }
589 if self.swiping() {
590 Self::FIELD_OFFSETS.swiping.apply_pin(self).set(false);
591 Self::FIELD_OFFSETS.cancelled.apply_pin(self).call(&());
592 }
593 }
594}
595
596#[cfg(feature = "ffi")]
597#[no_mangle]
598pub unsafe extern "C" fn slint_swipegesturehandler_cancel(
599 s: Pin<&SwipeGestureHandler>,
600 window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
601 self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
602 self_index: u32,
603) {
604 let window_adapter: &Rc = &*(window_adapter as *const Rc<dyn WindowAdapter>);
605 let self_rc: ItemRc = ItemRc::new(item_tree:self_component.clone(), self_index);
606 s.cancel(window_adapter, &self_rc);
607}
608