1use crate::{Motion, MotionState, Target, Transition};
3
4use gpui::{
5 AnyElement, App, Bounds, Element, ElementId, GlobalElementId, InspectorElementId, IntoElement,
6 LayoutId, Pixels, ScrollHandle, Styled, Window,
7};
8
9use std::time::Instant;
10
11#[derive(Clone, Copy, Debug)]
13pub enum Axis {
14 X,
16 Y,
18}
19
20pub fn native_scroll_progress(handle: &ScrollHandle, axis: Axis) -> f64 {
23 let offset = handle.offset();
24 let max = handle.max_offset();
25
26 let (offset, max) = match axis {
27 Axis::X => (offset.x, max.x),
28 Axis::Y => (offset.y, max.y),
29 };
30
31 let max = f64::from(max).abs();
32
33 if max == 0. {
34 0.
35 } else {
36 (-f64::from(offset) / max).clamp(0., 1.)
37 }
38}
39
40pub struct ScrollMotion<E> {
45 id: ElementId,
46 child: Option<E>,
47 handle: ScrollHandle,
48 axis: Axis,
49 from: Target,
50 to: Target,
51 transition: Transition,
52}
53
54impl<E> ScrollMotion<E> {
55 pub fn new(
57 id: impl Into<ElementId>,
58 handle: ScrollHandle,
59 axis: Axis,
60 from: Target,
61 to: Target,
62 child: E,
63 ) -> Self {
64 Self {
65 id: id.into(),
66 child: Some(child),
67 handle,
68 axis,
69 from,
70 to,
71 transition: Transition::Instant,
72 }
73 }
74
75 pub fn transition(mut self, transition: Transition) -> Self {
77 self.transition = transition;
78
79 self
80 }
81}
82
83struct History {
84 state: MotionState,
85 epoch: Instant,
86 progress: f64,
87}
88
89impl<E: IntoElement + Styled + 'static> IntoElement for ScrollMotion<E> {
90 type Element = Self;
91
92 fn into_element(self) -> Self {
93 self
94 }
95}
96
97impl<E: IntoElement + Styled + 'static> Element for ScrollMotion<E> {
98 type RequestLayoutState = AnyElement;
99 type PrepaintState = ();
100
101 fn id(&self) -> Option<ElementId> {
102 Some(self.id.clone())
103 }
104
105 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
106 None
107 }
108
109 fn request_layout(
110 &mut self,
111 id: Option<&GlobalElementId>,
112 _: Option<&InspectorElementId>,
113 w: &mut Window,
114 cx: &mut App,
115 ) -> (LayoutId, AnyElement) {
116 let progress = native_scroll_progress(&self.handle, self.axis);
117 let now = cx.background_executor().now();
118 let from = self.from.inherit(self.to);
119 let to = self.to.inherit(self.from);
120
121 let target = Target::from_values(
122 std::array::from_fn(|i| {
123 from.values()[i] + (to.values()[i] - from.values()[i]) * progress
124 }),
125 from.mask() | to.mask(),
126 );
127
128 let frame = w.with_element_state(id.expect("scroll ID"), |state: Option<History>, w| {
129 let mut state = state.unwrap_or_else(|| History {
130 state: MotionState::default(),
131 epoch: now,
132 progress,
133 });
134
135 state.progress = progress;
136 let frame = state.state.sample(
137 Motion::new(target).transition(self.transition),
138 now.saturating_duration_since(state.epoch),
139 cx.reduce_motion(),
140 );
141
142 if frame.is_animating() {
143 w.request_animation_frame();
144 }
145
146 (frame, state)
147 });
148
149 let mut child = frame
150 .apply(self.child.take().expect("one layout"))
151 .into_any_element();
152
153 let layout = child.request_layout(w, cx);
154
155 (layout, child)
156 }
157
158 fn prepaint(
159 &mut self,
160 id: Option<&GlobalElementId>,
161 _: Option<&InspectorElementId>,
162 _: Bounds<Pixels>,
163 child: &mut AnyElement,
164 w: &mut Window,
165 cx: &mut App,
166 ) {
167 child.prepaint(w, cx);
168 let progress = native_scroll_progress(&self.handle, self.axis);
169
170 w.with_element_state(id.expect("scroll ID"), |state: Option<History>, w| {
171 let state = state.expect("layout first");
172
173 if (state.progress - progress).abs() > 1e-9 {
174 w.request_animation_frame();
175 }
176
177 ((), state)
178 });
179 }
180
181 fn paint(
182 &mut self,
183 _: Option<&GlobalElementId>,
184 _: Option<&InspectorElementId>,
185 _: Bounds<Pixels>,
186 child: &mut AnyElement,
187 _: &mut (),
188 w: &mut Window,
189 cx: &mut App,
190 ) {
191 child.paint(w, cx);
192 }
193}