slint/sixtyfps_runtime/corelib/flickable.rs
Simon Hausmann 6ca63aac9c Make it possible to delegate the timer handling to the backend
When building with no_std, the backend can provide the global
instant.
2021-11-30 17:27:55 +01:00

194 lines
8 KiB
Rust

/* LICENSE BEGIN
This file is part of the SixtyFPS Project -- https://sixtyfps.io
Copyright (c) 2021 Olivier Goffart <olivier.goffart@sixtyfps.io>
Copyright (c) 2021 Simon Hausmann <simon.hausmann@sixtyfps.io>
SPDX-License-Identifier: GPL-3.0-only
This file is also available under commercial licensing terms.
Please contact info@sixtyfps.io for more information.
LICENSE END */
//! The implementation details behind the Flickable
use core::time::Duration;
use crate::animations::EasingCurve;
use crate::animations::Instant;
use crate::graphics::Point;
use crate::input::{InputEventFilterResult, InputEventResult, MouseEvent};
use crate::items::PointerEventButton;
use crate::items::{Flickable, PropertyAnimation, Rectangle};
use core::cell::RefCell;
use core::pin::Pin;
/// The distance required before it starts flicking if there is another item intercepting the mouse.
/// FIXME: this is currently physical pixels, but it should be logical
const DISTANCE_THRESHOLD: f32 = 4.;
/// Time required before we stop caring about child event if the mouse hasn't been moved
const DURATION_THRESHOLD: Duration = Duration::from_millis(500);
#[derive(Default, Debug)]
struct FlickableDataInner {
/// The position in which the press was made
pressed_pos: Point,
pressed_time: Option<Instant>,
pressed_viewport_pos: Point,
/// Set to true if the flickable is flicking and capturing all mouse event, not forwarding back to the children
capture_events: bool,
}
#[derive(Default, Debug)]
pub struct FlickableData {
inner: RefCell<FlickableDataInner>,
}
impl FlickableData {
pub fn handle_mouse_filter(
&self,
flick: Pin<&Flickable>,
event: MouseEvent,
) -> InputEventFilterResult {
let mut inner = self.inner.borrow_mut();
match event {
MouseEvent::MousePressed { pos, button: PointerEventButton::left } => {
inner.pressed_pos = pos;
inner.pressed_time = Some(crate::animations::current_tick());
inner.pressed_viewport_pos = Point::new(
(Flickable::FIELD_OFFSETS.viewport + Rectangle::FIELD_OFFSETS.x)
.apply_pin(flick)
.get(),
(Flickable::FIELD_OFFSETS.viewport + Rectangle::FIELD_OFFSETS.y)
.apply_pin(flick)
.get(),
);
if inner.capture_events {
InputEventFilterResult::Intercept
} else {
InputEventFilterResult::ForwardAndInterceptGrab
}
}
MouseEvent::MouseExit
| MouseEvent::MouseReleased { button: PointerEventButton::left, .. } => {
let was_capturing = inner.capture_events;
Self::mouse_released(&mut inner, flick, event);
if was_capturing {
InputEventFilterResult::Intercept
} else {
InputEventFilterResult::ForwardEvent
}
}
MouseEvent::MouseMoved { pos } => {
let do_intercept = inner.capture_events
|| inner.pressed_time.map_or(false, |pressed_time| {
crate::animations::current_tick() - pressed_time < DURATION_THRESHOLD
&& (pos - inner.pressed_pos).square_length()
> DISTANCE_THRESHOLD * DISTANCE_THRESHOLD
});
if do_intercept {
InputEventFilterResult::Intercept
} else if inner.pressed_time.is_some() {
InputEventFilterResult::ForwardAndInterceptGrab
} else {
InputEventFilterResult::ForwardEvent
}
}
MouseEvent::MouseWheel { .. } => InputEventFilterResult::Intercept,
// Not the left button
MouseEvent::MousePressed { .. } | MouseEvent::MouseReleased { .. } => {
InputEventFilterResult::ForwardAndIgnore
}
}
}
pub fn handle_mouse(&self, flick: Pin<&Flickable>, event: MouseEvent) -> InputEventResult {
let mut inner = self.inner.borrow_mut();
match event {
MouseEvent::MousePressed { .. } => {
inner.capture_events = true;
InputEventResult::GrabMouse
}
MouseEvent::MouseExit | MouseEvent::MouseReleased { .. } => {
Self::mouse_released(&mut inner, flick, event);
InputEventResult::EventAccepted
}
MouseEvent::MouseMoved { pos } => {
if inner.pressed_time.is_some() {
inner.capture_events = true;
let new_pos = ensure_in_bound(
flick,
inner.pressed_viewport_pos + (pos - inner.pressed_pos),
);
(Flickable::FIELD_OFFSETS.viewport + Rectangle::FIELD_OFFSETS.x)
.apply_pin(flick)
.set(new_pos.x);
(Flickable::FIELD_OFFSETS.viewport + Rectangle::FIELD_OFFSETS.y)
.apply_pin(flick)
.set(new_pos.y);
InputEventResult::GrabMouse
} else {
inner.capture_events = false;
InputEventResult::EventIgnored
}
}
MouseEvent::MouseWheel { delta, .. } => {
let old_pos = Point::new(
(Flickable::FIELD_OFFSETS.viewport + Rectangle::FIELD_OFFSETS.x)
.apply_pin(flick)
.get(),
(Flickable::FIELD_OFFSETS.viewport + Rectangle::FIELD_OFFSETS.y)
.apply_pin(flick)
.get(),
);
let new_pos = ensure_in_bound(flick, old_pos + delta.to_vector());
(Flickable::FIELD_OFFSETS.viewport + Rectangle::FIELD_OFFSETS.x)
.apply_pin(flick)
.set(new_pos.x);
(Flickable::FIELD_OFFSETS.viewport + Rectangle::FIELD_OFFSETS.y)
.apply_pin(flick)
.set(new_pos.y);
InputEventResult::EventAccepted
}
}
}
fn mouse_released(inner: &mut FlickableDataInner, flick: Pin<&Flickable>, event: MouseEvent) {
if let (Some(pressed_time), Some(pos)) = (inner.pressed_time, event.pos()) {
let dist = pos - inner.pressed_pos;
let speed =
dist / ((crate::animations::current_tick() - pressed_time).as_millis() as f32);
let duration = 100;
let final_pos = ensure_in_bound(
flick,
inner.pressed_viewport_pos + dist + speed * (duration as f32),
);
let anim = PropertyAnimation {
duration,
easing: EasingCurve::CubicBezier([0.0, 0.0, 0.58, 1.0]),
..PropertyAnimation::default()
};
(Flickable::FIELD_OFFSETS.viewport + Rectangle::FIELD_OFFSETS.x)
.apply_pin(flick)
.set_animated_value(final_pos.x, anim.clone());
(Flickable::FIELD_OFFSETS.viewport + Rectangle::FIELD_OFFSETS.y)
.apply_pin(flick)
.set_animated_value(final_pos.y, anim);
}
inner.capture_events = false; // FIXME: should only be set to false once the flick animation is over
inner.pressed_time = None;
}
}
/// Make sure that the point is within the bounds
fn ensure_in_bound(flick: Pin<&Flickable>, p: Point) -> Point {
let w = flick.width();
let h = flick.height();
let vw =
(Flickable::FIELD_OFFSETS.viewport + Rectangle::FIELD_OFFSETS.width).apply_pin(flick).get();
let vh = (Flickable::FIELD_OFFSETS.viewport + Rectangle::FIELD_OFFSETS.height)
.apply_pin(flick)
.get();
let min = Point::new(w - vw, h - vh);
let max = Point::new(0., 0.);
p.max(min).min(max)
}