use crate::core::{LayoutPositionState, Transition};
use gpui_kit::{
AnyElement, App, AvailableSpace, Bounds, Display, Element, ElementId, GlobalElementId,
InspectorElementId, IntoElement, LayoutId, Pixels, Point, Style, Window, point, px, size,
};
use std::{cell::Cell, rc::Rc, time::Instant};
#[derive(Clone, Debug, Default)]
pub struct PopLayout {
origin: Rc<Cell<Point<Pixels>>>,
}
impl PopLayout {
pub fn container<E: IntoElement + 'static>(
&self,
id: impl Into<ElementId>,
child: E,
) -> PopElement<E> {
PopElement {
id: id.into(),
child: Some(child),
scope: self.clone(),
present: None,
transition: Transition::default(),
}
}
pub fn item<E: IntoElement + 'static>(
&self,
id: impl Into<ElementId>,
present: bool,
child: E,
) -> PopElement<E> {
PopElement {
id: id.into(),
child: Some(child),
scope: self.clone(),
present: Some(present),
transition: Transition::default(),
}
}
}
pub struct PopElement<E> {
id: ElementId,
child: Option<E>,
scope: PopLayout,
present: Option<bool>,
transition: Transition,
}
impl<E> PopElement<E> {
pub fn transition(mut self, transition: Transition) -> Self {
self.transition = transition;
self
}
}
struct History {
bounds: Option<Bounds<Pixels>>,
position: LayoutPositionState,
epoch: Instant,
}
struct OriginScope {
origin: Rc<Cell<Point<Pixels>>>,
previous: Point<Pixels>,
}
impl Drop for OriginScope {
fn drop(&mut self) {
self.origin.set(self.previous);
}
}
pub struct PopRequest {
child: AnyElement,
bounds: Option<Bounds<Pixels>>,
}
impl<E: IntoElement + 'static> IntoElement for PopElement<E> {
type Element = Self;
fn into_element(self) -> Self {
self
}
}
impl<E: IntoElement + 'static> Element for PopElement<E> {
type RequestLayoutState = PopRequest;
type PrepaintState = ();
fn id(&self) -> Option<ElementId> {
Some(self.id.clone())
}
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
None
}
fn request_layout(
&mut self,
id: Option<&GlobalElementId>,
_: Option<&InspectorElementId>,
w: &mut Window,
cx: &mut App,
) -> (LayoutId, PopRequest) {
let mut child = self.child.take().expect("one layout").into_any_element();
let now = cx.background_executor().now();
let old_bounds =
w.with_element_state(id.expect("pop ID"), |history: Option<History>, _| {
let h = history.unwrap_or_else(|| History {
bounds: None,
position: LayoutPositionState::default(),
epoch: now,
});
(h.bounds, h)
});
let layout = if self.present == Some(false) {
let style = Style {
display: Display::None,
..Default::default()
};
w.request_layout(style, [], cx)
} else {
child.request_layout(w, cx)
};
(
layout,
PopRequest {
child,
bounds: old_bounds,
},
)
}
fn prepaint(
&mut self,
id: Option<&GlobalElementId>,
_: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
request: &mut PopRequest,
w: &mut Window,
cx: &mut App,
) {
if self.present.is_none() {
let _scope = OriginScope {
previous: self.scope.origin.replace(bounds.origin),
origin: self.scope.origin.clone(),
};
request.child.prepaint(w, cx);
return;
}
let now = cx.background_executor().now();
if self.present == Some(false) {
if let Some(old) = request.bounds {
w.with_element_state(id.expect("pop ID"), |history: Option<History>, _| {
let mut h = history.expect("history initialized during layout");
h.position.sample(
[f64::from(old.origin.x), f64::from(old.origin.y)],
Transition::Instant,
now.saturating_duration_since(h.epoch),
true,
);
((), h)
});
request.child.prepaint_as_root(
self.scope.origin.get() + old.origin,
size(
AvailableSpace::Definite(old.size.width),
AvailableSpace::Definite(old.size.height),
),
w,
cx,
);
}
} else {
let origin = bounds.origin - self.scope.origin.get();
let frame = w.with_element_state(id.expect("pop ID"), |history: Option<History>, w| {
let mut h = history.expect("history initialized during layout");
let frame = h.position.sample(
[f64::from(origin.x), f64::from(origin.y)],
self.transition,
now.saturating_duration_since(h.epoch),
cx.reduce_motion(),
);
let [x, y] = frame
.position()
.map(|v| v.clamp(f32::MIN as f64, f32::MAX as f64) as f32);
h.bounds = Some(Bounds {
origin: point(px(x), px(y)),
size: bounds.size,
});
if frame.is_animating() {
w.request_animation_frame();
}
(frame, h)
});
let [x, y] = frame
.offset()
.map(|v| v.clamp(f32::MIN as f64, f32::MAX as f64) as f32);
w.with_element_offset(point(px(x), px(y)), |w| request.child.prepaint(w, cx));
}
}
fn paint(
&mut self,
_: Option<&GlobalElementId>,
_: Option<&InspectorElementId>,
_: Bounds<Pixels>,
request: &mut PopRequest,
_: &mut (),
w: &mut Window,
cx: &mut App,
) {
if self.present != Some(false) || request.bounds.is_some() {
request.child.paint(w, cx);
}
}
}