use std::{cell::Cell, rc::Rc};
use gpui::{
AnyElement, App, Bounds, Display, Element, GlobalElementId, InspectorElementId, IntoElement,
LayoutId, Pixels, Point, Position, Size, Style, Window, canvas, div, point, prelude::*, px,
};
use super::{MENU_PAD, SNAP};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Side {
#[default]
Right,
Left,
}
impl Side {
fn other(self) -> Self {
match self {
Side::Right => Side::Left,
Side::Left => Side::Right,
}
}
}
#[derive(Clone, Default)]
pub struct Chain(Rc<Cell<Side>>);
pub fn place(
anchor: Bounds<Pixels>,
size: Size<Pixels>,
viewport: Size<Pixels>,
margin: Pixels,
prefer: Side,
) -> (Point<Pixels>, Side) {
let x = |side: Side| match side {
Side::Right => anchor.right(),
Side::Left => anchor.left() - size.width,
};
let fits = |side: Side| {
let left = x(side);
left >= margin && left + size.width + margin <= viewport.width
};
let side = match (fits(prefer), fits(prefer.other())) {
(false, true) => prefer.other(),
_ => prefer,
};
let mut origin = point(x(side), anchor.origin.y);
if origin.x + size.width > viewport.width {
origin.x -= origin.x + size.width - viewport.width + margin;
}
if origin.x < Pixels::ZERO {
origin.x = margin;
}
if origin.y + size.height > viewport.height {
origin.y -= origin.y + size.height - viewport.height + margin;
}
if origin.y < Pixels::ZERO {
origin.y = margin;
}
(origin, side)
}
pub(super) fn layer(content: AnyElement, chain: &Chain) -> AnyElement {
let anchor = Rc::new(Cell::new(None));
let measure = {
let anchor = anchor.clone();
canvas(
move |bounds, _, _| anchor.set(Some(bounds)),
|_, _, _, _| {},
)
};
div()
.absolute()
.inset_0()
.child(
measure
.absolute()
.top(px(-MENU_PAD))
.left(px(-MENU_PAD))
.right(px(-MENU_PAD))
.h_0(),
)
.child(
div()
.absolute()
.top(px(-MENU_PAD))
.right(px(-MENU_PAD))
.size_0()
.child(
gpui::deferred(Panel {
child: content,
anchor,
chain: chain.clone(),
})
.priority(1),
),
)
.into_any_element()
}
struct Panel {
child: AnyElement,
anchor: Rc<Cell<Option<Bounds<Pixels>>>>,
chain: Chain,
}
impl Element for Panel {
type RequestLayoutState = LayoutId;
type PrepaintState = ();
fn id(&self) -> Option<gpui::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, LayoutId) {
let child = self.child.request_layout(window, cx);
let style = Style {
position: Position::Absolute,
display: Display::Flex,
..Style::default()
};
(window.request_layout(style, [child], cx), child)
}
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
child: &mut LayoutId,
window: &mut Window,
cx: &mut App,
) {
let size = window.layout_bounds(*child).size;
let origin = match self.anchor.get() {
Some(anchor) => {
let margin = px(SNAP) + window.client_inset().unwrap_or(Pixels::ZERO);
let (origin, side) = place(
anchor,
size,
window.viewport_size(),
margin,
self.chain.0.get(),
);
self.chain.0.set(side);
origin
}
None => bounds.origin,
};
let offset = origin - bounds.origin;
window.with_element_offset(point(offset.x.round(), offset.y.round()), |window| {
self.child.prepaint(window, cx);
});
}
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_prepaint: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
) {
self.child.paint(window, cx);
}
}
impl IntoElement for Panel {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}