use std::{cell::Cell, rc::Rc};
use std::time::Duration;
use gpui::{
App, ElementId, InteractiveElement as _, IntoElement, MouseButton, MouseDownEvent,
MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, SharedString, Styled as _,
Window, div,
};
use motion::Painter;
const DRAG_FPS: f32 = 60.0;
const DRAG_LEASE: Duration = Duration::from_millis(200);
const THRESHOLD: f64 = 2.0;
#[derive(Clone)]
pub struct Floating {
at: Rc<Cell<Option<Point<Pixels>>>>,
anchor: Rc<Cell<Point<Pixels>>>,
held: Rc<Cell<bool>>,
dragging: Rc<Cell<bool>>,
painter: Painter,
}
impl Floating {
pub fn new(painter: Painter) -> Self {
Self {
at: Rc::new(Cell::new(None)),
anchor: Rc::new(Cell::new(Point::default())),
held: Rc::new(Cell::new(false)),
dragging: Rc::new(Cell::new(false)),
painter,
}
}
pub fn at(&self) -> Option<Point<Pixels>> {
self.at.get()
}
pub fn held(&self) -> bool {
self.held.get()
}
pub fn dragging(&self) -> bool {
self.dragging.get()
}
pub fn move_to(&self, at: Point<Pixels>) {
self.at.set(Some(at));
}
fn sample(&self, home: Point<Pixels>, pointer: Point<Pixels>, cx: &mut App) {
if !self.held.get() {
return;
}
let travelled = pointer - self.anchor.get();
if !self.dragging.get() {
if travelled.magnitude() <= THRESHOLD {
return;
}
self.dragging.set(true);
self.anchor.set(pointer);
self.painter.notify(cx);
return;
}
self.move_to(self.at.get().unwrap_or(home) + travelled);
self.anchor.set(pointer);
self.painter.lease(DRAG_FPS, DRAG_LEASE, cx);
}
}
pub fn panel(
id: impl Into<SharedString>,
state: &Floating,
home: Point<Pixels>,
child: impl IntoElement,
) -> impl IntoElement {
let id = id.into();
let at = state.at.get().unwrap_or(home);
let pressed = {
let state = state.clone();
move |event: &MouseDownEvent, _: &mut Window, cx: &mut App| {
state.held.set(true);
state.anchor.set(event.position);
state.painter.notify(cx);
}
};
let moved = {
let state = state.clone();
move |event: &MouseMoveEvent, _: &mut Window, cx: &mut App| {
state.sample(home, event.position, cx);
}
};
let released = {
let state = state.clone();
move |_: &MouseUpEvent, _: &mut Window, cx: &mut App| {
let held = state.held.replace(false);
let dragging = state.dragging.replace(false);
if held || dragging {
state.painter.notify(cx);
}
}
};
let box_ = div()
.absolute()
.left(at.x)
.top(at.y)
.id(ElementId::from(id.clone()));
let box_ = if state.held() || state.dragging() {
box_.cursor_grabbing()
} else {
box_.cursor_grab()
};
div()
.id(ElementId::from(SharedString::from(format!("{id}-layer"))))
.absolute()
.inset_0()
.child(box_.on_mouse_down(MouseButton::Left, pressed).child(child))
.on_mouse_move(moved)
.on_mouse_up(MouseButton::Left, released.clone())
.on_mouse_up_out(MouseButton::Left, released)
}