use std::{cell::RefCell, rc::Rc};
use gpui::{
AnyElement, App, Axis, Bounds, Element, ElementId, GlobalElementId, InspectorElementId,
IntoElement, LayoutId, Pixels, Point, Render, StatefulInteractiveElement, Window, point,
prelude::*,
};
use motion::Painter;
use theme::Theme;
use crate::surface::{Surface, Surfaced};
pub trait Carried: Styled + IntoElement + Sized {
fn ghost(self, theme: &Theme) -> Surface {
self.surface(theme, theme.carried_surface)
}
}
impl<E: Styled + IntoElement> Carried for E {}
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, origin: Point<Pixels>) {
self.gesture.host(origin);
}
}
#[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())
};
carry.gesture.ghosted.set(ghost.is_some());
let item = carry.item.clone();
let gesture = carry.gesture.clone();
cx.new(|_| Ghosted {
gesture,
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>,
cursor: Point<Pixels>,
grab: 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) => {
let gesture = self.gesture.clone();
gpui::div()
.child(AtGrab {
cursor: self.cursor,
grab: self.grab,
child: gpui::div()
.child(render(window, cx))
.child(
gpui::canvas(
move |bounds, _, _| gesture.shown.set(Some(bounds.origin)),
|_, _, _, _| {},
)
.absolute()
.top_0()
.left_0()
.size_full(),
)
.into_any_element(),
})
.into_any_element()
}
None => gpui::Empty.into_any_element(),
}
}
}
struct AtGrab {
cursor: Point<Pixels>,
grab: Point<Pixels>,
child: AnyElement,
}
impl IntoElement for AtGrab {
type Element = Self;
fn into_element(self) -> Self {
self
}
}
impl Element for AtGrab {
type RequestLayoutState = ();
type PrepaintState = ();
fn id(&self) -> Option<ElementId> {
None
}
fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
None
}
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, ()) {
(self.child.request_layout(window, cx), ())
}
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut (),
window: &mut Window,
cx: &mut App,
) {
let grab = point(
self.grab.x.clamp(Pixels::ZERO, bounds.size.width),
self.grab.y.clamp(Pixels::ZERO, bounds.size.height),
);
window.with_element_offset(self.cursor - grab, |window| self.child.prepaint(window, cx));
}
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut (),
_prepaint: &mut (),
window: &mut Window,
cx: &mut App,
) {
self.child.paint(window, cx);
}
}