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