1use std::{fmt, mem, ops};
2use zng_layout::context::LayoutMask;
3use zng_var::animation::{Transitionable, easing::EasingStep};
4use zng_wgt::prelude::*;
5
6#[derive(Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
28pub struct StackDirection {
29 pub place: Point,
31 pub origin: Point,
33
34 pub is_rtl_aware: bool,
38}
39
40impl fmt::Debug for StackDirection {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 if f.alternate() {
43 f.debug_struct("StackDirection")
44 .field("place", &self.place)
45 .field("origin", &self.origin)
46 .field("is_rtl_aware", &self.is_rtl_aware)
47 .finish()
48 } else if self.is_rtl_aware {
49 write!(f, "({:?}, {:?}, {:?})", self.place, self.origin, self.is_rtl_aware)
50 } else {
51 write!(f, "({:?}, {:?})", self.place, self.origin)
52 }
53 }
54}
55
56impl fmt::Display for StackDirection {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 if let Some(p) = f.precision() {
59 write!(f, "({:.p$}, {:.p$}", self.place, self.origin, p = p)?;
60 } else {
61 write!(f, "({}, {}", self.place, self.origin)?;
62 }
63
64 if self.is_rtl_aware {
65 write!(f, ", {})", self.is_rtl_aware)
66 } else {
67 write!(f, ")")
68 }
69 }
70}
71
72impl StackDirection {
73 pub fn new<P: Into<Point>, O: Into<Point>>(place: P, origin: O, is_rtl_aware: bool) -> Self {
75 Self {
76 place: place.into(),
77 origin: origin.into(),
78 is_rtl_aware,
79 }
80 }
81
82 pub fn left_to_right() -> Self {
86 Self {
87 place: (100.pct(), 0).into(),
88 origin: (0, 0).into(),
89 is_rtl_aware: false,
90 }
91 }
92
93 pub fn right_to_left() -> Self {
97 Self {
98 place: (0, 0).into(),
99 origin: (100.pct(), 0).into(),
100 is_rtl_aware: false,
101 }
102 }
103
104 pub fn start_to_end() -> Self {
112 Self {
113 place: (100.pct(), 0).into(),
114 origin: (0, 0).into(),
115 is_rtl_aware: true,
116 }
117 }
118
119 pub fn end_to_start() -> Self {
127 Self {
128 place: (0, 0).into(),
129 origin: (100.pct(), 0).into(),
130 is_rtl_aware: true,
131 }
132 }
133
134 pub fn top_to_bottom() -> Self {
138 Self {
139 place: (0, 100.pct()).into(),
140 origin: (0, 0).into(),
141 is_rtl_aware: false,
142 }
143 }
144
145 pub fn bottom_to_top() -> Self {
149 Self {
150 place: (0, 0).into(),
151 origin: (0, 100.pct()).into(),
152 is_rtl_aware: false,
153 }
154 }
155
156 pub fn none() -> Self {
164 Self {
165 place: Point::zero(),
166 origin: Point::zero(),
167 is_rtl_aware: false,
168 }
169 }
170
171 pub fn layout(&self, prev_item: PxRect, next_item: PxSize) -> PxVector {
175 if self.is_rtl_aware && LAYOUT.direction().is_rtl() {
176 let mut d = self.clone();
177 mem::swap(&mut d.place.x, &mut d.origin.x);
178 d.is_rtl_aware = false;
179 return d.layout_resolved_rtl(prev_item, next_item);
180 }
181
182 self.layout_resolved_rtl(prev_item, next_item)
183 }
184 pub(crate) fn layout_resolved_rtl(&self, prev_item: PxRect, next_item: PxSize) -> PxVector {
185 let c = LAYOUT.constraints();
186 let place = LAYOUT.with_constraints(c.with_exact_size(prev_item.size), || self.place.layout());
187 let origin = LAYOUT.with_constraints(c.with_exact_size(next_item), || self.origin.layout());
188 prev_item.origin.to_vector() + place.to_vector() - origin.to_vector()
189 }
190
191 pub fn direction_factor(&self, direction: LayoutDirection) -> Factor2d {
195 let size = PxSize::new(Px(1000), Px(1000));
196 let metrics = LayoutMetrics::new(1.fct(), size, Px(1000)).with_direction(direction);
197 let p = LAYOUT.with_context(metrics, || self.layout(PxRect::from_size(size), size));
198
199 fn v(px: Px) -> Factor {
200 (px.0 as f32 / 1000.0).fct()
201 }
202 (v(p.x), v(p.y)).into()
203 }
204
205 pub fn direction_scale(&self) -> Factor2d {
209 let scale = self.direction_factor(LayoutDirection::LTR).abs().yx();
210 if scale.x == 0.fct() && scale.y == 0.fct() {
211 return Factor2d::new(1.0, 1.0);
212 }
213 let angle = scale.y.0.atan2(scale.x.0).to_degrees();
214 if angle <= 20.0 {
215 Factor2d::new(1.0 - angle / 20.0, 0.0)
216 } else if angle >= 70.0 {
217 Factor2d::new(0.0, (angle - 70.0) / 20.0)
218 } else {
219 Factor2d::new(0.0, 0.0)
220 }
221 }
222
223 pub fn affect_mask(&self) -> LayoutMask {
228 self.place.affect_mask() | self.origin.affect_mask()
229 }
230
231 pub fn is_default(&self) -> bool {
235 self.place.is_default() && self.origin.is_default()
236 }
237}
238
239impl_from_and_into_var! {
240 fn from<P: Into<Point>, O: Into<Point>>((origin, size): (P, O)) -> StackDirection {
242 (origin, size, false).into()
243 }
244
245 fn from<P: Into<Point>, O: Into<Point>>((origin, size, rtl_aware): (P, O, bool)) -> StackDirection {
247 StackDirection::new(origin, size, rtl_aware)
248 }
249}
250impl<S: Into<Factor2d>> ops::Mul<S> for StackDirection {
251 type Output = Self;
252
253 fn mul(mut self, rhs: S) -> Self {
254 self *= rhs;
255 self
256 }
257}
258impl<S: Into<Factor2d>> ops::Mul<S> for &StackDirection {
259 type Output = StackDirection;
260
261 fn mul(self, rhs: S) -> Self::Output {
262 self.clone() * rhs
263 }
264}
265impl<S: Into<Factor2d>> ops::MulAssign<S> for StackDirection {
266 fn mul_assign(&mut self, rhs: S) {
267 let rhs = rhs.into();
268 self.place *= rhs;
269 self.origin *= rhs;
270 }
271}
272impl<S: Into<Factor2d>> ops::Div<S> for StackDirection {
273 type Output = Self;
274
275 fn div(mut self, rhs: S) -> Self {
276 self /= rhs;
277 self
278 }
279}
280impl<S: Into<Factor2d>> ops::Div<S> for &StackDirection {
281 type Output = StackDirection;
282
283 fn div(self, rhs: S) -> Self::Output {
284 self.clone() / rhs
285 }
286}
287impl<S: Into<Factor2d>> ops::DivAssign<S> for StackDirection {
288 fn div_assign(&mut self, rhs: S) {
289 let rhs = rhs.into();
290 self.place /= rhs;
291 self.origin /= rhs;
292 }
293}
294impl ops::Add for StackDirection {
295 type Output = Self;
296
297 fn add(mut self, rhs: Self) -> Self {
298 self += rhs;
299 self
300 }
301}
302impl ops::AddAssign for StackDirection {
303 fn add_assign(&mut self, rhs: Self) {
304 self.place += rhs.place;
305 self.origin += rhs.origin;
306 }
307}
308impl ops::Sub for StackDirection {
309 type Output = Self;
310
311 fn sub(mut self, rhs: Self) -> Self {
312 self -= rhs;
313 self
314 }
315}
316impl ops::SubAssign for StackDirection {
317 fn sub_assign(&mut self, rhs: Self) {
318 self.place -= rhs.place;
319 self.origin -= rhs.origin;
320 }
321}
322
323impl Transitionable for StackDirection {
324 fn lerp(self, to: &Self, step: EasingStep) -> Self {
325 Self {
326 place: self.place.lerp(&to.place, step),
327 origin: self.origin.lerp(&to.origin, step),
328 is_rtl_aware: if step < 1.fct() { self.is_rtl_aware } else { to.is_rtl_aware },
329 }
330 }
331}