use crate::{LayoutPositionState, MotionValue, Transition};
use gpui::{
AnyElement, App, Bounds, Element, ElementId, GlobalElementId, InspectorElementId, IntoElement,
LayoutId, Pixels, Styled, Window, point, px,
};
use std::{cell::RefCell, collections::HashMap, hash::Hash, rc::Rc, time::Instant};
struct Entry {
position: LayoutPositionState,
destination: [f64; 2],
member: Option<GlobalElementId>,
epoch: Instant,
measured: bool,
}
#[derive(Clone)]
pub struct SharedLayout<K>(Rc<RefCell<HashMap<K, Entry>>>);
impl<K> Default for SharedLayout<K> {
fn default() -> Self {
Self(Rc::new(RefCell::new(HashMap::new())))
}
}
impl<K: Eq + Hash + Clone + 'static> SharedLayout<K> {
pub fn remove(&self, key: &K) {
self.0.borrow_mut().remove(key);
}
pub fn len(&self) -> usize {
self.0.borrow().len()
}
pub fn is_empty(&self) -> bool {
self.0.borrow().is_empty()
}
pub fn member<E: IntoElement + Styled + 'static>(
&self,
key: K,
id: impl Into<ElementId>,
active: bool,
child: E,
) -> SharedElement<K, E> {
SharedElement {
group: self.clone(),
key,
id: id.into(),
active,
child: Some(child),
transition: Transition::default(),
}
}
}
pub struct SharedElement<K, E> {
group: SharedLayout<K>,
key: K,
id: ElementId,
active: bool,
child: Option<E>,
transition: Transition,
}
impl<K, E> SharedElement<K, E> {
pub fn transition(mut self, transition: Transition) -> Self {
self.transition = transition;
self
}
}
struct Fade {
value: MotionValue,
epoch: Instant,
}
impl<K: Eq + Hash + Clone + 'static, E: IntoElement + Styled + 'static> IntoElement
for SharedElement<K, E>
{
type Element = Self;
fn into_element(self) -> Self {
self
}
}
impl<K: Eq + Hash + Clone + 'static, E: IntoElement + Styled + 'static> Element
for SharedElement<K, E>
{
type RequestLayoutState = AnyElement;
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, AnyElement) {
let now = cx.background_executor().now();
let id = id.expect("shared ID");
let mut entries = self.group.0.borrow_mut();
let entry = entries.entry(self.key.clone()).or_insert_with(|| Entry {
position: LayoutPositionState::default(),
destination: [0.; 2],
member: None,
epoch: now,
measured: false,
});
let initial = if self.active && entry.member.is_none() {
1.
} else {
0.
};
if self.active {
entry.member = Some(id.clone());
}
drop(entries);
let opacity = w.with_element_state(id, |state: Option<Fade>, w| {
let mut state = state.unwrap_or_else(|| Fade {
value: MotionValue::new(initial),
epoch: now,
});
let time = now.saturating_duration_since(state.epoch);
state
.value
.animate(if self.active { 1. } else { 0. }, self.transition, time);
let frame = state.value.sample(time, cx.reduce_motion());
if frame.is_animating() {
w.request_animation_frame();
}
(frame.value().clamp(0., 1.) as f32, state)
});
let mut child = self
.child
.take()
.expect("one layout")
.opacity(opacity)
.into_any_element();
let layout = child.request_layout(w, cx);
(layout, child)
}
fn prepaint(
&mut self,
_: Option<&GlobalElementId>,
_: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
child: &mut AnyElement,
w: &mut Window,
cx: &mut App,
) {
let now = cx.background_executor().now();
let mut entries = self.group.0.borrow_mut();
let entry = entries.get_mut(&self.key).expect("layout first");
if !entry.measured && !self.active {
drop(entries);
child.prepaint(w, cx);
return;
}
if self.active {
entry.measured = true;
entry.destination = [f64::from(bounds.origin.x), f64::from(bounds.origin.y)];
}
let frame = entry.position.sample(
entry.destination,
self.transition,
now.saturating_duration_since(entry.epoch),
cx.reduce_motion(),
);
if frame.is_animating() {
w.request_animation_frame();
}
let presented = frame.position();
let offset = point(
px((presented[0] - f64::from(bounds.origin.x))
.clamp(f64::from(f32::MIN), f64::from(f32::MAX)) as f32),
px((presented[1] - f64::from(bounds.origin.y))
.clamp(f64::from(f32::MIN), f64::from(f32::MAX)) as f32),
);
drop(entries);
w.with_element_offset(offset, |w| {
child.prepaint(w, cx);
});
}
fn paint(
&mut self,
_: Option<&GlobalElementId>,
_: Option<&InspectorElementId>,
_: Bounds<Pixels>,
child: &mut AnyElement,
_: &mut (),
w: &mut Window,
cx: &mut App,
) {
child.paint(w, cx);
}
}