#![allow(non_snake_case)]
use std::rc::Rc;
use crate::composable;
use crate::modifier::Modifier;
use crate::text_selection::{handle_path_data, HandleKind, HANDLE_TOUCH_PADDING};
use crate::widgets::box_widget::{Box, BoxSpec};
use crate::widgets::popup::Popup;
use crate::PointerInputScope;
use cranpose_foundation::{PointerEvent, PointerEventKind};
use cranpose_ui_graphics::{Brush, Color, DrawScope, Point, Rect, Size, VectorPath};
pub(crate) const HANDLE_LONG_PRESS_TIMEOUT_MS: i64 = 500;
pub(crate) const HANDLE_LONG_PRESS_SLOP_PX: f32 = 12.0;
struct HandleShape {
path_data: String,
box_size: Size,
tip_in_box: Point,
}
fn handle_shape(kind: HandleKind, radius: f32) -> HandleShape {
let pad = HANDLE_TOUCH_PADDING.max(0.0);
let bounds = VectorPath::parse(&handle_path_data(kind, 0.0, 0.0, radius))
.map(|p| p.bounds())
.unwrap_or(Rect {
x: -radius,
y: 0.0,
width: 2.0 * radius,
height: 2.0 * radius,
});
let tip_in_box = Point {
x: pad - bounds.x,
y: pad - bounds.y,
};
let box_size = Size {
width: bounds.width + 2.0 * pad,
height: bounds.height + 2.0 * pad,
};
let path_data = handle_path_data(kind, tip_in_box.x, tip_in_box.y, radius);
HandleShape {
path_data,
box_size,
tip_in_box,
}
}
#[composable]
pub fn SelectionHandle(
kind: HandleKind,
tip: Point,
radius: f32,
color: Color,
on_drag: impl Fn(Point) + 'static,
on_drag_end: impl Fn() + 'static,
on_long_press: impl Fn() + 'static,
) {
let shape = handle_shape(kind, radius);
let anchor = Rect {
x: tip.x - shape.tip_in_box.x,
y: tip.y - shape.tip_in_box.y,
width: 0.0,
height: 0.0,
};
let path_data = shape.path_data;
let box_size = shape.box_size;
let on_drag: Rc<dyn Fn(Point)> = Rc::new(on_drag);
let on_drag_end: Rc<dyn Fn()> = Rc::new(on_drag_end);
let on_long_press: Rc<dyn Fn()> = Rc::new(on_long_press);
Popup(anchor, Point { x: 0.0, y: 0.0 }, move || {
let path_data = path_data.clone();
let on_drag = Rc::clone(&on_drag);
let on_drag_end = Rc::clone(&on_drag_end);
let on_long_press = Rc::clone(&on_long_press);
Box(
Modifier::empty()
.size(box_size)
.draw_behind(move |scope: &mut dyn DrawScope| {
if let Ok(path) = VectorPath::parse(&path_data) {
scope.draw_vector_path(&path, Brush::solid(color));
}
})
.then(selection_handle_pointer_input(
Rc::clone(&on_drag),
Rc::clone(&on_drag_end),
Rc::clone(&on_long_press),
)),
BoxSpec::default(),
|| {},
);
});
}
pub(crate) fn selection_handle_pointer_input(
on_drag: Rc<dyn Fn(Point)>,
on_drag_end: Rc<dyn Fn()>,
on_long_press: Rc<dyn Fn()>,
) -> Modifier {
Modifier::empty().pointer_input((), move |scope: PointerInputScope| {
let on_drag = Rc::clone(&on_drag);
let on_drag_end = Rc::clone(&on_drag_end);
let on_long_press = Rc::clone(&on_long_press);
async move {
scope
.await_pointer_event_scope(|await_scope| async move {
let mut down_time: Option<i64> = None;
let mut down_pos = Point { x: 0.0, y: 0.0 };
let mut long_press_fired = false;
loop {
let event = await_scope.await_pointer_event().await;
match event.kind {
PointerEventKind::Down => {
down_time = event.time_ms;
down_pos = event.global_position;
long_press_fired = false;
on_drag(event.global_position);
event.consume();
}
PointerEventKind::Move => {
on_drag(event.global_position);
maybe_fire_long_press(
&event,
down_time,
down_pos,
&mut long_press_fired,
&on_long_press,
);
event.consume();
}
PointerEventKind::Up => {
maybe_fire_long_press(
&event,
down_time,
down_pos,
&mut long_press_fired,
&on_long_press,
);
on_drag_end();
down_time = None;
event.consume();
}
PointerEventKind::Cancel => {
on_drag_end();
down_time = None;
event.consume();
}
_ => {}
}
}
})
.await;
}
})
}
fn maybe_fire_long_press(
event: &PointerEvent,
down_time: Option<i64>,
down_pos: Point,
long_press_fired: &mut bool,
on_long_press: &Rc<dyn Fn()>,
) {
if *long_press_fired {
return;
}
let (Some(down_ms), Some(now_ms)) = (down_time, event.time_ms) else {
return;
};
if is_handle_long_press(down_ms, now_ms, down_pos, event.global_position) {
*long_press_fired = true;
on_long_press();
}
}
pub(crate) fn is_handle_long_press(
down_ms: i64,
now_ms: i64,
down_pos: Point,
now_pos: Point,
) -> bool {
let dx = now_pos.x - down_pos.x;
let dy = now_pos.y - down_pos.y;
let moved = (dx * dx + dy * dy).sqrt();
now_ms - down_ms >= HANDLE_LONG_PRESS_TIMEOUT_MS && moved <= HANDLE_LONG_PRESS_SLOP_PX
}