1use gpui::{px, Pixels, Styled};
2
3#[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}