use std::{cell::RefCell, rc::Rc};
use gpui::{
AnyElement, App, Axis, ElementId, IntoElement, Pixels, Point, Render,
StatefulInteractiveElement, Window, prelude::*,
};
use motion::Painter;
mod element;
mod state;
pub use element::{Handle, Region};
pub(crate) use state::Gesture;
use state::State;
pub struct Carry<I> {
item: I,
pub(crate) gesture: Rc<Gesture>,
}
impl<I> Carry<I> {
pub fn item(&self) -> &I {
&self.item
}
pub fn claimed(&self) -> bool {
self.gesture.claimed.get()
}
pub fn ghost_bounds(&self, pointer: Point<Pixels>) -> gpui::Bounds<Pixels> {
self.gesture.ghost_bounds(pointer)
}
pub fn host(&self) {
self.gesture.hosted.set(true);
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Drop<R, I> {
pub item: I,
pub from: R,
pub region: R,
pub after: Option<I>,
pub before: Option<I>,
}
#[derive(Clone, Debug)]
pub struct Outside<I> {
pub item: I,
pub position: Point<Pixels>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Feedback {
#[default]
Displace,
Indicator,
}
type Ghost<I> = dyn Fn(&I, &mut Window, &mut App) -> AnyElement;
type Paint = dyn Fn(&mut Window, &mut App) -> AnyElement;
pub struct Domain<R, I>(Rc<RefCell<State<R, I>>>);
impl<R, I> Clone for Domain<R, I> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<R: Clone + PartialEq + 'static, I: Clone + PartialEq + 'static> Domain<R, I> {
pub fn new(painter: Painter) -> Self {
Self(Rc::new(RefCell::new(State::new(painter, None))))
}
pub fn with_ghost(
painter: Painter,
ghost: impl Fn(&I, &mut Window, &mut App) -> AnyElement + 'static,
) -> Self {
Self(Rc::new(RefCell::new(State::new(
painter,
Some(Rc::new(ghost)),
))))
}
pub fn region(
&self,
element_id: impl Into<ElementId>,
id: R,
axis: Axis,
child: impl IntoElement,
) -> Region<R, I> {
self.0.borrow_mut().begin_frame();
Region::new(
self.clone(),
element_id.into(),
id,
axis,
child.into_any_element(),
)
}
pub fn handle<E>(&self, item: I, element: E) -> Handle<R, I>
where
E: StatefulInteractiveElement + IntoElement + 'static,
{
let domain = self.clone();
let carry = Carry {
item: item.clone(),
gesture: Rc::new(Gesture::default()),
};
let element = element.on_drag(carry, move |carry, grab, window, cx| {
domain.start(carry, grab, window, cx)
});
Handle::new(self.clone(), item, element.into_any_element())
}
pub fn fixed(&self, item: I, element: impl IntoElement) -> Handle<R, I> {
Handle::new(self.clone(), item, element.into_any_element())
}
pub fn carries(&self, item: &I) -> bool {
self.0
.borrow()
.live
.as_ref()
.is_some_and(|live| &live.item == item)
}
pub fn cancel(&self, window: &mut Window, cx: &mut App) {
let live = self.0.borrow_mut().end(window, cx);
if live.is_some() {
cx.stop_active_drag(window);
}
}
pub fn carried(&self) -> Option<I> {
self.0.borrow().live.as_ref().map(|live| live.item.clone())
}
pub(crate) fn invalidate(&self) {
self.0.borrow_mut().stale = true;
}
fn start(
&self,
carry: &Carry<I>,
cursor: Point<Pixels>,
window: &mut Window,
cx: &mut App,
) -> gpui::Entity<Ghosted> {
let (grab, ghost) = {
let mut state = self.0.borrow_mut();
let grab = state.start(&carry.item, carry.gesture.clone(), cursor, window, cx);
(grab, state.ghost.clone())
};
let item = carry.item.clone();
let gesture = carry.gesture.clone();
cx.new(|_| Ghosted {
gesture,
shift: cursor - grab,
render: ghost.map(|ghost| {
Rc::new(move |window: &mut Window, cx: &mut App| ghost(&item, window, cx))
as Rc<Paint>
}),
})
}
}
pub struct Ghosted {
gesture: Rc<Gesture>,
shift: Point<Pixels>,
render: Option<Rc<Paint>>,
}
impl Render for Ghosted {
fn render(&mut self, window: &mut Window, cx: &mut gpui::Context<Self>) -> impl IntoElement {
let hosted = self.gesture.hosted.get() && !self.gesture.claimed.get();
match self.render.as_ref().filter(|_| !hosted) {
Some(render) => gpui::div()
.relative()
.left(self.shift.x)
.top(self.shift.y)
.child(render(window, cx))
.into_any_element(),
None => gpui::Empty.into_any_element(),
}
}
}