Skip to main content

glassy_ui/motion/
style.rs

1use gpui::{px, Pixels, Styled};
2
3/// Animatable visual properties (motion.dev style objects).
4#[derive(Clone, Copy, Debug, Default)]
5pub struct MotionStyle {
6    pub opacity: Option<f32>,
7    pub x: Option<Pixels>,
8    pub y: Option<Pixels>,
9    pub scale: Option<f32>,
10    pub width: Option<Pixels>,
11    pub height: Option<Pixels>,
12}
13
14impl MotionStyle {
15    pub fn new() -> Self {
16        Self::default()
17    }
18
19    pub fn opacity(mut self, value: f32) -> Self {
20        self.opacity = Some(value.clamp(0.0, 1.0));
21        self
22    }
23
24    pub fn x(mut self, value: impl Into<Pixels>) -> Self {
25        self.x = Some(value.into());
26        self
27    }
28
29    pub fn y(mut self, value: impl Into<Pixels>) -> Self {
30        self.y = Some(value.into());
31        self
32    }
33
34    pub fn scale(mut self, value: f32) -> Self {
35        self.scale = Some(value.max(0.0));
36        self
37    }
38
39    pub fn width(mut self, value: impl Into<Pixels>) -> Self {
40        self.width = Some(value.into());
41        self
42    }
43
44    pub fn height(mut self, value: impl Into<Pixels>) -> Self {
45        self.height = Some(value.into());
46        self
47    }
48
49    pub(crate) fn resolve(self, base: ResolvedStyle) -> ResolvedStyle {
50        ResolvedStyle {
51            opacity: self.opacity.unwrap_or(base.opacity),
52            x: self.x.unwrap_or(base.x),
53            y: self.y.unwrap_or(base.y),
54            scale: self.scale.unwrap_or(base.scale),
55            width: self.width.or(base.width),
56            height: self.height.or(base.height),
57        }
58    }
59
60    #[allow(dead_code)]
61    pub(crate) fn merge(self, overlay: MotionStyle) -> MotionStyle {
62        MotionStyle {
63            opacity: overlay.opacity.or(self.opacity),
64            x: overlay.x.or(self.x),
65            y: overlay.y.or(self.y),
66            scale: overlay.scale.or(self.scale),
67            width: overlay.width.or(self.width),
68            height: overlay.height.or(self.height),
69        }
70    }
71}
72
73#[derive(Clone, Copy, Debug)]
74pub(crate) struct ResolvedStyle {
75    pub opacity: f32,
76    pub x: Pixels,
77    pub y: Pixels,
78    pub scale: f32,
79    pub width: Option<Pixels>,
80    pub height: Option<Pixels>,
81}
82
83impl Default for ResolvedStyle {
84    fn default() -> Self {
85        Self {
86            opacity: 1.0,
87            x: px(0.),
88            y: px(0.),
89            scale: 1.0,
90            width: None,
91            height: None,
92        }
93    }
94}
95
96impl ResolvedStyle {
97    pub fn lerp(self, target: Self, t: f32) -> Self {
98        let t = t.clamp(0.0, 1.0);
99        Self {
100            opacity: lerp_f32(self.opacity, target.opacity, t),
101            x: lerp_px(self.x, target.x, t),
102            y: lerp_px(self.y, target.y, t),
103            scale: lerp_f32(self.scale, target.scale, t),
104            width: match (self.width, target.width) {
105                (Some(a), Some(b)) => Some(lerp_px(a, b, t)),
106                (None, Some(b)) => Some(b),
107                (Some(a), None) => Some(a),
108                (None, None) => None,
109            },
110            height: match (self.height, target.height) {
111                (Some(a), Some(b)) => Some(lerp_px(a, b, t)),
112                (None, Some(b)) => Some(b),
113                (Some(a), None) => Some(a),
114                (None, None) => None,
115            },
116        }
117    }
118}
119
120pub(crate) fn lerp_f32(a: f32, b: f32, t: f32) -> f32 {
121    a + (b - a) * t
122}
123
124pub(crate) fn lerp_px(a: Pixels, b: Pixels, t: f32) -> Pixels {
125    let a: f32 = a.into();
126    let b: f32 = b.into();
127    px(a + (b - a) * t)
128}
129
130pub(crate) fn apply_style<E: Styled>(mut el: E, style: ResolvedStyle) -> E {
131    let opacity = if (style.scale - 1.0).abs() > f32::EPSILON {
132        (style.opacity * style.scale.clamp(0.35, 1.25)).clamp(0.0, 1.0)
133    } else {
134        style.opacity.clamp(0.0, 1.0)
135    };
136
137    el = el.opacity(opacity).ml(style.x).mt(style.y);
138    if let Some(w) = style.width {
139        el = el.w(w);
140    }
141    if let Some(h) = style.height {
142        el = el.h(h);
143    }
144    el
145}