use std::cell::Cell;
use std::panic::Location;
use std::rc::Rc;
use gpui::{
App, AppContext as _, Bounds, DragMoveEvent, Element, ElementId, Empty, GlobalElementId,
Hitbox, InteractiveElement, IntoElement, LayoutId, Modifiers, MouseButton, Pixels, Point,
Stateful, StatefulInteractiveElement, Window,
};
const DEFAULT_ID: &str = "gpui_interop::surface";
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct SurfacePoint {
pub x: f32,
pub y: f32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SurfaceButton {
Left,
Right,
Middle,
Other,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct SurfaceModifiers {
pub shift: bool,
pub control: bool,
pub alt: bool,
pub platform: bool,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SurfaceEvent {
PointerDown {
position: SurfacePoint,
button: SurfaceButton,
modifiers: SurfaceModifiers,
},
PointerMove {
position: SurfacePoint,
pressed: Option<SurfaceButton>,
modifiers: SurfaceModifiers,
},
PointerUp {
position: SurfacePoint,
button: SurfaceButton,
modifiers: SurfaceModifiers,
},
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SurfaceInput {
pub event: SurfaceEvent,
pub scale: f32,
}
pub fn surface_input<E>(element: E, handler: impl Fn(SurfaceInput) + 'static) -> impl IntoElement
where
E: InteractiveElement + Element<PrepaintState = Option<Hitbox>> + 'static,
{
let id = Element::id(&element).unwrap_or_else(|| ElementId::from(DEFAULT_ID));
let surface = Rc::new(Surface {
bounds: Cell::new(None),
handler: Rc::new(handler),
});
let inner = attach(element.id(id.clone()), &surface, id);
SurfaceInputElement { inner, surface }
}
struct Surface {
bounds: Cell<Option<Bounds<Pixels>>>,
handler: Rc<dyn Fn(SurfaceInput)>,
}
struct SurfaceDrag {
origin: ElementId,
}
fn drag(cx: &App) -> Option<&SurfaceDrag> {
cx.active_drag_value::<SurfaceDrag>()
}
fn attach<E>(element: Stateful<E>, surface: &Rc<Surface>, id: ElementId) -> Stateful<E>
where
E: InteractiveElement + Element<PrepaintState = Option<Hitbox>> + 'static,
{
let down = surface.clone();
let element = element.on_any_mouse_down(move |event, window, _cx| {
let Some(bounds) = down.bounds.get() else {
return;
};
let button = button(event.button);
let modifiers = modifiers(event.modifiers);
let position = local(window.scale_factor(), bounds.origin, event.position);
deliver(&down, window, position, |position| {
SurfaceEvent::PointerDown {
position,
button,
modifiers,
}
});
});
let hover = surface.clone();
let own = id.clone();
let element = element.on_mouse_move(move |event, window, cx| {
if drag(cx).is_some_and(|drag| drag.origin == own) {
return;
}
let Some(bounds) = hover.bounds.get() else {
return;
};
let pressed = event.pressed_button.map(button);
let modifiers = modifiers(event.modifiers);
let position = local(window.scale_factor(), bounds.origin, event.position);
deliver(&hover, window, position, |position| {
SurfaceEvent::PointerMove {
position,
pressed,
modifiers,
}
});
});
let carried = surface.clone();
let own = id.clone();
let element = element.on_drag_move::<SurfaceDrag>(
move |event: &DragMoveEvent<SurfaceDrag>, window, cx| {
if event.drag(cx).origin != own {
return;
}
let pressed = event.event.pressed_button.map(button);
let modifiers = modifiers(event.event.modifiers);
let position = local(
window.scale_factor(),
event.bounds.origin,
event.event.position,
);
deliver(&carried, window, position, |position| {
SurfaceEvent::PointerMove {
position,
pressed,
modifiers,
}
});
},
);
let started = surface.clone();
let element = element.on_drag(
SurfaceDrag { origin: id.clone() },
move |_value, offset, window, cx| {
let position = local(window.scale_factor(), Point::default(), offset);
let modifiers = modifiers(window.modifiers());
deliver(&started, window, position, |position| {
SurfaceEvent::PointerMove {
position,
pressed: Some(SurfaceButton::Left),
modifiers,
}
});
cx.new(|_| Empty)
},
);
let up = surface.clone();
let element = element.capture_any_mouse_up(move |event, window, _cx| {
let Some(bounds) = up.bounds.get() else {
return;
};
let button = button(event.button);
let modifiers = modifiers(event.modifiers);
let position = local(window.scale_factor(), bounds.origin, event.position);
deliver(&up, window, position, |position| SurfaceEvent::PointerUp {
position,
button,
modifiers,
});
});
let mut element = element;
for released_button in [MouseButton::Left, MouseButton::Right, MouseButton::Middle] {
let released = surface.clone();
let own = id.clone();
element = element.on_mouse_up_out(released_button, move |event, window, cx| {
if drag(cx).is_none_or(|drag| drag.origin != own) {
return;
}
let Some(bounds) = released.bounds.get() else {
return;
};
let button = button(event.button);
let modifiers = modifiers(event.modifiers);
let position = local(window.scale_factor(), bounds.origin, event.position);
deliver(&released, window, position, |position| {
SurfaceEvent::PointerUp {
position,
button,
modifiers,
}
});
});
}
element
}
fn deliver(
surface: &Rc<Surface>,
window: &mut Window,
position: SurfacePoint,
event: impl FnOnce(SurfacePoint) -> SurfaceEvent,
) {
let scale = window.scale_factor();
(surface.handler)(SurfaceInput {
event: event(position),
scale,
});
window.refresh();
}
fn local(scale: f32, origin: Point<Pixels>, position: Point<Pixels>) -> SurfacePoint {
let local = position - origin;
SurfacePoint {
x: local.x.as_f32() * scale,
y: local.y.as_f32() * scale,
}
}
fn button(button: MouseButton) -> SurfaceButton {
match button {
MouseButton::Left => SurfaceButton::Left,
MouseButton::Right => SurfaceButton::Right,
MouseButton::Middle => SurfaceButton::Middle,
MouseButton::Navigate(_) => SurfaceButton::Other,
}
}
fn modifiers(modifiers: Modifiers) -> SurfaceModifiers {
SurfaceModifiers {
shift: modifiers.shift,
control: modifiers.control,
alt: modifiers.alt,
platform: modifiers.platform,
}
}
struct SurfaceInputElement<E> {
inner: E,
surface: Rc<Surface>,
}
impl<E> Element for SurfaceInputElement<E>
where
E: Element<PrepaintState = Option<Hitbox>>,
{
type RequestLayoutState = E::RequestLayoutState;
type PrepaintState = E::PrepaintState;
fn id(&self) -> Option<ElementId> {
self.inner.id()
}
fn source_location(&self) -> Option<&'static Location<'static>> {
self.inner.source_location()
}
fn request_layout(
&mut self,
id: Option<&GlobalElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
self.inner.request_layout(id, window, cx)
}
fn prepaint(
&mut self,
id: Option<&GlobalElementId>,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
cx: &mut App,
) -> Self::PrepaintState {
let prepaint = self.inner.prepaint(id, bounds, request_layout, window, cx);
if let Some(hitbox) = &prepaint {
self.surface.bounds.set(Some(hitbox.bounds));
}
prepaint
}
fn paint(
&mut self,
id: Option<&GlobalElementId>,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
) {
self.inner
.paint(id, bounds, request_layout, prepaint, window, cx);
}
}
impl<E: Element<PrepaintState = Option<Hitbox>>> IntoElement for SurfaceInputElement<E> {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}