1use crate::{LayoutPositionState, MotionValue, Transition};
3
4use gpui::{
5 AnyElement, App, Bounds, Element, ElementId, GlobalElementId, InspectorElementId, IntoElement,
6 LayoutId, Pixels, Styled, Window, point, px,
7};
8
9use std::{cell::RefCell, collections::HashMap, hash::Hash, rc::Rc, time::Instant};
10
11struct Entry {
12 position: LayoutPositionState,
13 destination: [f64; 2],
14 member: Option<GlobalElementId>,
15 epoch: Instant,
16 measured: bool,
17}
18
19#[derive(Clone)]
26pub struct SharedLayout<K>(Rc<RefCell<HashMap<K, Entry>>>);
27
28impl<K> Default for SharedLayout<K> {
29 fn default() -> Self {
30 Self(Rc::new(RefCell::new(HashMap::new())))
31 }
32}
33
34impl<K: Eq + Hash + Clone + 'static> SharedLayout<K> {
35 pub fn remove(&self, key: &K) {
37 self.0.borrow_mut().remove(key);
38 }
39
40 pub fn len(&self) -> usize {
42 self.0.borrow().len()
43 }
44
45 pub fn is_empty(&self) -> bool {
47 self.0.borrow().is_empty()
48 }
49
50 pub fn member<E: IntoElement + Styled + 'static>(
64 &self,
65 key: K,
66 id: impl Into<ElementId>,
67 active: bool,
68 child: E,
69 ) -> SharedElement<K, E> {
70 SharedElement {
71 group: self.clone(),
72 key,
73 id: id.into(),
74 active,
75 child: Some(child),
76 transition: Transition::default(),
77 }
78 }
79}
80
81pub struct SharedElement<K, E> {
83 group: SharedLayout<K>,
84 key: K,
85 id: ElementId,
86 active: bool,
87 child: Option<E>,
88 transition: Transition,
89}
90
91impl<K, E> SharedElement<K, E> {
92 pub fn transition(mut self, transition: Transition) -> Self {
94 self.transition = transition;
95
96 self
97 }
98}
99
100struct Fade {
101 value: MotionValue,
102 epoch: Instant,
103}
104
105impl<K: Eq + Hash + Clone + 'static, E: IntoElement + Styled + 'static> IntoElement
106 for SharedElement<K, E>
107{
108 type Element = Self;
109
110 fn into_element(self) -> Self {
111 self
112 }
113}
114
115impl<K: Eq + Hash + Clone + 'static, E: IntoElement + Styled + 'static> Element
116 for SharedElement<K, E>
117{
118 type RequestLayoutState = AnyElement;
119 type PrepaintState = ();
120
121 fn id(&self) -> Option<ElementId> {
122 Some(self.id.clone())
123 }
124
125 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
126 None
127 }
128
129 fn request_layout(
130 &mut self,
131 id: Option<&GlobalElementId>,
132 _: Option<&InspectorElementId>,
133 w: &mut Window,
134 cx: &mut App,
135 ) -> (LayoutId, AnyElement) {
136 let now = cx.background_executor().now();
137 let id = id.expect("shared ID");
138 let mut entries = self.group.0.borrow_mut();
139
140 let entry = entries.entry(self.key.clone()).or_insert_with(|| Entry {
141 position: LayoutPositionState::default(),
142 destination: [0.; 2],
143 member: None,
144 epoch: now,
145 measured: false,
146 });
147
148 let initial = if self.active && entry.member.is_none() {
149 1.
150 } else {
151 0.
152 };
153
154 if self.active {
155 entry.member = Some(id.clone());
156 }
157
158 drop(entries);
159 let opacity = w.with_element_state(id, |state: Option<Fade>, w| {
160 let mut state = state.unwrap_or_else(|| Fade {
161 value: MotionValue::new(initial),
162 epoch: now,
163 });
164
165 let time = now.saturating_duration_since(state.epoch);
166
167 state
168 .value
169 .animate(if self.active { 1. } else { 0. }, self.transition, time);
170 let frame = state.value.sample(time, cx.reduce_motion());
171
172 if frame.is_animating() {
173 w.request_animation_frame();
174 }
175
176 (frame.value().clamp(0., 1.) as f32, state)
177 });
178
179 let mut child = self
180 .child
181 .take()
182 .expect("one layout")
183 .opacity(opacity)
184 .into_any_element();
185
186 let layout = child.request_layout(w, cx);
187
188 (layout, child)
189 }
190
191 fn prepaint(
192 &mut self,
193 _: Option<&GlobalElementId>,
194 _: Option<&InspectorElementId>,
195 bounds: Bounds<Pixels>,
196 child: &mut AnyElement,
197 w: &mut Window,
198 cx: &mut App,
199 ) {
200 let now = cx.background_executor().now();
201 let mut entries = self.group.0.borrow_mut();
202 let entry = entries.get_mut(&self.key).expect("layout first");
203
204 if !entry.measured && !self.active {
205 drop(entries);
206 child.prepaint(w, cx);
207
208 return;
209 }
210
211 if self.active {
212 entry.measured = true;
213 entry.destination = [f64::from(bounds.origin.x), f64::from(bounds.origin.y)];
214 }
215
216 let frame = entry.position.sample(
217 entry.destination,
218 self.transition,
219 now.saturating_duration_since(entry.epoch),
220 cx.reduce_motion(),
221 );
222
223 if frame.is_animating() {
224 w.request_animation_frame();
225 }
226
227 let presented = frame.position();
228
229 let offset = point(
230 px((presented[0] - f64::from(bounds.origin.x))
231 .clamp(f64::from(f32::MIN), f64::from(f32::MAX)) as f32),
232 px((presented[1] - f64::from(bounds.origin.y))
233 .clamp(f64::from(f32::MIN), f64::from(f32::MAX)) as f32),
234 );
235
236 drop(entries);
237 w.with_element_offset(offset, |w| {
238 child.prepaint(w, cx);
239 });
240 }
241
242 fn paint(
243 &mut self,
244 _: Option<&GlobalElementId>,
245 _: Option<&InspectorElementId>,
246 _: Bounds<Pixels>,
247 child: &mut AnyElement,
248 _: &mut (),
249 w: &mut Window,
250 cx: &mut App,
251 ) {
252 child.paint(w, cx);
253 }
254}