1use gpui_ce_motion::{Motion, MotionValue, Transition, ValueFrame};
21use gpui_kit::{AnyElement, App, Empty, IntoElement, Styled, Window, px};
22
23use std::{
24 ops::Deref,
25 time::{Duration, Instant},
26};
27
28pub use gpui_ce_motion as core;
30
31mod gesture_native;
32mod layout;
33mod measured_size;
34mod motions;
35mod playback;
36mod pop_layout;
37mod reorder_native;
38mod scroll_native;
39mod shared_layout;
40mod style;
41mod svg;
42mod vector;
43
44pub use gesture_native::{GestureElement, GestureState};
45pub use measured_size::{AutoSize, SizeAxis};
46pub use motions::Motions;
47pub use playback::{AnimationState, Timeline};
48pub use pop_layout::{PopElement, PopLayout, PopRequest};
49pub use reorder_native::{ReorderElement, ReorderGroup};
50pub use scroll_native::{Axis, ScrollMotion, native_scroll_progress};
51pub use shared_layout::{SharedElement, SharedLayout};
52pub use style::{StyleFrame, StyleMotionState, UnsupportedStyle, color_to_rgba};
53pub use svg::{SvgFrame, SvgMotionState};
54
55#[derive(Debug, Default)]
56struct Clock(Option<Instant>);
57
58impl Clock {
59 fn tick(&mut self, cx: &App) -> Duration {
60 let now = cx.background_executor().now();
61 let epoch = *self.0.get_or_insert(now);
62
63 now.saturating_duration_since(epoch)
64 }
65
66 fn now(&self, cx: &App) -> Duration {
67 self.0.map_or(Duration::ZERO, |epoch| {
68 cx.background_executor()
69 .now()
70 .saturating_duration_since(epoch)
71 })
72 }
73}
74
75pub use vector::{VectorElement, VectorState};
76
77pub use layout::{LayoutGroup, LayoutPositionElement};
78
79#[derive(Debug, Default)]
81pub struct MotionState {
82 inner: gpui_ce_motion::MotionState,
83 epoch: Option<Instant>,
84}
85
86impl MotionState {
87 pub fn frame(&mut self, motion: Motion, window: &mut Window, cx: &App) -> Frame {
90 let now = cx.background_executor().now();
91 let epoch = *self.epoch.get_or_insert(now);
92
93 let frame = self.inner.sample(
94 motion,
95 now.saturating_duration_since(epoch),
96 cx.reduce_motion(),
97 );
98
99 if frame.is_animating() {
100 window.request_animation_frame();
101 }
102
103 Frame(frame)
104 }
105
106 pub fn sample(&mut self, motion: Motion, now: Duration, reduced_motion: bool) -> Frame {
109 Frame(self.inner.sample(motion, now, reduced_motion))
110 }
111}
112
113#[derive(Clone, Copy, Debug)]
115pub struct Frame(gpui_ce_motion::Frame);
116
117impl From<gpui_ce_motion::Frame> for Frame {
118 fn from(frame: gpui_ce_motion::Frame) -> Self {
119 Self(frame)
120 }
121}
122
123impl Deref for Frame {
124 type Target = gpui_ce_motion::Frame;
125
126 fn deref(&self) -> &Self::Target {
127 &self.0
128 }
129}
130
131impl Frame {
132 pub fn at(target: gpui_ce_motion::Target) -> Self {
134 gpui_ce_motion::Frame::at(target).into()
135 }
136
137 pub fn apply<E: Styled>(self, mut element: E) -> E {
141 let target = self.target();
142
143 if let Some(width) = target.width_px() {
144 element = element.w(px(width));
145 }
146
147 if let Some(height) = target.height_px() {
148 element = element.h(px(height));
149 }
150
151 if let Some(radius) = target.border_radius_px() {
152 element = element.rounded(px(radius));
153 }
154
155 element
156 .relative()
157 .left(px(target.x_px()))
158 .top(px(target.y_px()))
159 .opacity(target.alpha())
160 }
161
162 pub fn render<E: IntoElement>(self, build: impl FnOnce(Self) -> E) -> AnyElement {
165 if self.is_mounted() {
166 build(self).into_any_element()
167 } else {
168 Empty.into_any_element()
169 }
170 }
171}
172
173#[derive(Debug)]
175pub struct ValueState {
176 inner: MotionValue,
177 epoch: Option<Instant>,
178}
179
180impl ValueState {
181 pub fn new(value: f64) -> Self {
183 Self {
184 inner: MotionValue::new(value),
185 epoch: None,
186 }
187 }
188
189 pub fn frame(
191 &mut self,
192 target: f64,
193 transition: Transition,
194 window: &mut Window,
195 cx: &App,
196 ) -> ValueFrame {
197 let clock = cx.background_executor().now();
198 let epoch = *self.epoch.get_or_insert(clock);
199 let now = clock.saturating_duration_since(epoch);
200
201 self.inner.animate(target, transition, now);
202 let frame = self.inner.sample(now, cx.reduce_motion());
203
204 if frame.is_animating() {
205 window.request_animation_frame();
206 }
207
208 frame
209 }
210}
211
212mod text {
213 fn text_color_to_native(color: gpui_ce_motion::Color) -> gpui_kit::Hsla {
214 crate::color_to_rgba(color).into()
215 }
216
217 fn text_color_from_native(color: gpui_kit::Hsla) -> gpui_ce_motion::Color {
218 let color: gpui_kit::Rgba = color.into();
219
220 gpui_ce_motion::Color::rgba(
221 color.r.into(),
222 color.g.into(),
223 color.b.into(),
224 color.a.into(),
225 )
226 }
227
228 gpui_ce_motion::__motion_text_adapter!(gpui_kit, text_color_to_native, text_color_from_native);
229}
230
231pub use gpui_ce_motion::{
232 NumberEffect, Scramble, TextClip, TextError, TextMatch, TextPreset, TextTarget, TextUnit,
233 TextUnitInfo,
234};
235
236pub use text::{NumberElement, NumberMotionState, TextElement, TextMotionState};
237
238mod typed;
239
240pub use typed::{TypedState, TypedTimeline};
241
242mod spatial {
243 gpui_ce_motion::__motion_spatial_adapter!(gpui_kit, crate);
244}
245
246pub use core::ScrollFrame;
247
248pub use spatial::{
249 AnchorElement, GraphState, MotionAnchor, MotionTreeState, ScrollTracker, SharedOverlay,
250 SharedOverlayElement,
251};