1use std::{
2 borrow::Cow,
3 fmt::{self, Write},
4 ops,
5};
6
7use crate::context::LayoutDirection;
8use zng_var::{
9 animation::{Transitionable, easing::EasingStep},
10 impl_from_and_into_var,
11};
12
13use super::{Factor, Factor2d, FactorPercent, FactorUnits, Point, Px, PxConstraints, PxConstraints2d, PxSize, PxVector};
14
15#[derive(Clone, Copy)]
47pub struct Align {
48 pub x: Factor,
50 pub x_rtl_aware: bool,
52
53 pub y: Factor,
55}
56impl PartialEq for Align {
57 fn eq(&self, other: &Self) -> bool {
58 self.is_fill_x() == other.is_fill_x() && self.is_fill_y() == other.is_fill_y() && self.x == other.x && self.y == other.y
59 }
60}
61impl Default for Align {
62 fn default() -> Self {
64 Align::START
65 }
66}
67impl Align {
68 pub fn x(self, direction: LayoutDirection) -> Factor {
74 let x = if self.x.0.is_finite() { self.x } else { 0.fct() };
75
76 if self.x_rtl_aware && direction.is_rtl() { x.flip() } else { x }
77 }
78
79 pub fn y(self) -> Factor {
86 if self.y.0.is_finite() {
87 self.y
88 } else if self.is_baseline() {
89 1.fct()
90 } else {
91 0.fct()
92 }
93 }
94
95 pub fn xy(self, direction: LayoutDirection) -> Factor2d {
100 Factor2d::new(self.x(direction), self.y())
101 }
102
103 pub fn is_fill_x(self) -> bool {
107 self.x.0.is_infinite() && self.x.0.is_sign_positive()
108 }
109
110 pub fn is_fill_y(self) -> bool {
114 self.y.0.is_infinite() && self.y.0.is_sign_positive()
115 }
116
117 pub fn is_baseline(self) -> bool {
123 self.y.0.is_infinite() && self.y.0.is_sign_negative()
124 }
125
126 pub fn fill_vector(self) -> super::euclid::BoolVector2D {
128 super::euclid::BoolVector2D {
129 x: self.is_fill_x(),
130 y: self.is_fill_y(),
131 }
132 }
133
134 pub fn child_constraints(self, parent_constraints: PxConstraints2d) -> PxConstraints2d {
136 parent_constraints
138 .with_new_min(
139 if self.is_fill_x() { parent_constraints.x.min() } else { Px(0) },
140 if self.is_fill_y() { parent_constraints.y.min() } else { Px(0) },
141 )
142 .with_fill_and(self.is_fill_x(), self.is_fill_y())
143 }
144
145 pub fn child_offset(self, child_size: PxSize, parent_size: PxSize, direction: LayoutDirection) -> PxVector {
149 let mut offset = PxVector::zero();
150 if !self.is_fill_x() {
151 let x = if self.x_rtl_aware && direction.is_rtl() {
152 self.x.flip().0
153 } else {
154 self.x.0
155 };
156
157 offset.x = (parent_size.width - child_size.width) * x;
158 }
159
160 let baseline = self.is_baseline();
161
162 if !self.is_fill_y() {
163 let y = if baseline { 1.0 } else { self.y.0 };
164
165 offset.y = (parent_size.height - child_size.height) * y;
166 }
167 offset
168 }
169
170 pub fn measure(self, child_size: PxSize, parent_constraints: PxConstraints2d) -> PxSize {
174 let size = parent_constraints.fill_size().max(child_size);
175 parent_constraints.clamp_size(size)
176 }
177
178 pub fn measure_x(self, child_width: Px, parent_constraints_x: PxConstraints) -> Px {
180 let width = parent_constraints_x.fill().max(child_width);
181 parent_constraints_x.clamp(width)
182 }
183
184 pub fn measure_y(self, child_height: Px, parent_constraints_y: PxConstraints) -> Px {
186 let height = parent_constraints_y.fill().max(child_height);
187 parent_constraints_y.clamp(height)
188 }
189
190 pub fn layout(self, child_size: PxSize, parent_constraints: PxConstraints2d, direction: LayoutDirection) -> (PxSize, PxVector, bool) {
193 let size = parent_constraints.fill_size().max(child_size);
194 let size = parent_constraints.clamp_size(size);
195
196 let offset = self.child_offset(child_size, size, direction);
197
198 (size, offset, self.is_baseline())
199 }
200}
201impl_from_and_into_var! {
202 fn from<X: Into<Factor>, Y: Into<Factor>>((x, y): (X, Y)) -> Align {
203 Align {
204 x: x.into(),
205 x_rtl_aware: false,
206 y: y.into(),
207 }
208 }
209
210 fn from<X: Into<Factor>, Y: Into<Factor>>((x, rtl, y): (X, bool, Y)) -> Align {
211 Align {
212 x: x.into(),
213 x_rtl_aware: rtl,
214 y: y.into(),
215 }
216 }
217
218 fn from(xy: Factor) -> Align {
219 Align {
220 x: xy,
221 x_rtl_aware: false,
222 y: xy,
223 }
224 }
225
226 fn from(xy: FactorPercent) -> Align {
227 xy.fct().into()
228 }
229}
230macro_rules! named_aligns {
231 ( $($NAME:ident = ($x:expr, $rtl:expr, $y:expr);)+ ) => {named_aligns!{$(
232 [stringify!(($x, $y))] $NAME = ($x, $rtl, $y);
233 )+}};
234
235 ( $([$doc:expr] $NAME:ident = ($x:expr, $rtl:expr, $y:expr);)+ ) => {
236 $(
237 #[doc=$doc]
238 pub const $NAME: Align = Align { x: Factor($x), x_rtl_aware: $rtl, y: Factor($y) };
239 )+
240
241 pub fn name(self) -> Option<&'static str> {
243 $(
244 if self == Self::$NAME {
245 Some(stringify!($NAME))
246 }
247 )else+
248 else {
249 None
250 }
251 }
252
253 pub fn from_name(name: &str) -> Option<Self> {
255 $(
256 if name == stringify!($NAME) {
257 Some(Self::$NAME)
258 }
259 )else+
260 else {
261 None
262 }
263 }
264 };
265}
266impl fmt::Debug for Align {
267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268 if let Some(name) = self.name() {
269 if f.alternate() {
270 write!(f, "Align::{name}")
271 } else {
272 f.write_str(name)
273 }
274 } else {
275 f.debug_struct("Align")
276 .field("x", &self.x)
277 .field("x_rtl_aware", &self.x_rtl_aware)
278 .field("y", &self.y)
279 .finish()
280 }
281 }
282}
283impl fmt::Display for Align {
284 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285 if let Some(name) = self.name() {
286 f.write_str(name)
287 } else {
288 f.write_char('(')?;
289 if self.is_fill_x() {
290 f.write_str("<fill>")?;
291 } else {
292 write!(f, "{}", FactorPercent::from(self.x))?;
293 }
294 f.write_str(", ")?;
295 if self.is_fill_y() {
296 f.write_str("<fill>")?;
297 } else if self.is_baseline() {
298 f.write_str("<baseline>")?;
299 } else {
300 write!(f, "{}", FactorPercent::from(self.x))?;
301 }
302 f.write_char(')')
303 }
304 }
305}
306impl Align {
307 named_aligns! {
308 TOP_START = (0.0, true, 0.0);
309 TOP_LEFT = (0.0, false, 0.0);
310 BOTTOM_START = (0.0, true, 1.0);
311 BOTTOM_LEFT = (0.0, false, 1.0);
312
313 TOP_END = (1.0, true, 0.0);
314 TOP_RIGHT = (1.0, false, 0.0);
315 BOTTOM_END = (1.0, true, 1.0);
316 BOTTOM_RIGHT = (1.0, false, 1.0);
317
318 START = (0.0, true, 0.5);
319 LEFT = (0.0, false, 0.5);
320 END = (1.0, true, 0.5);
321 RIGHT = (1.0, false, 0.5);
322 TOP = (0.5, false, 0.0);
323 BOTTOM = (0.5, false, 1.0);
324
325 CENTER = (0.5, false, 0.5);
326
327 FILL_TOP = (f32::INFINITY, false, 0.0);
328 FILL_BOTTOM = (f32::INFINITY, false, 1.0);
329 FILL_START = (0.0, true, f32::INFINITY);
330 FILL_LEFT = (0.0, false, f32::INFINITY);
331 FILL_RIGHT = (1.0, false, f32::INFINITY);
332 FILL_END = (1.0, true, f32::INFINITY);
333
334 FILL_X = (f32::INFINITY, false, 0.5);
335 FILL_Y = (0.5, false, f32::INFINITY);
336
337 FILL = (f32::INFINITY, false, f32::INFINITY);
338
339 BASELINE_START = (0.0, true, f32::NEG_INFINITY);
340 BASELINE_LEFT = (0.0, false, f32::NEG_INFINITY);
341 BASELINE_CENTER = (0.5, false, f32::NEG_INFINITY);
342 BASELINE_END = (1.0, true, f32::NEG_INFINITY);
343 BASELINE_RIGHT = (1.0, false, f32::NEG_INFINITY);
344
345 BASELINE = (f32::INFINITY, false, f32::NEG_INFINITY);
346 }
347}
348impl_from_and_into_var! {
349 fn from(alignment: Align) -> Point {
351 Point {
352 x: alignment.x.into(),
353 y: alignment.y.into(),
354 }
355 }
356
357 fn from(factor2d: Factor2d) -> Align {
358 Align {
359 x: factor2d.x,
360 x_rtl_aware: false,
361 y: factor2d.y,
362 }
363 }
364}
365
366impl Transitionable for Align {
367 fn lerp(mut self, to: &Self, step: EasingStep) -> Self {
368 let end = step >= 1.fct();
369
370 if end {
371 self.x_rtl_aware = to.x_rtl_aware;
372 }
373
374 if self.x.0.is_finite() && self.y.0.is_finite() {
375 self.x = self.x.lerp(&to.x, step);
376 } else if end {
377 self.x = to.x;
378 }
379
380 if self.y.0.is_finite() && self.y.0.is_finite() {
381 self.y = self.y.lerp(&to.y, step);
382 } else if end {
383 self.y = to.y;
384 }
385
386 self
387 }
388}
389
390impl<S: Into<Factor2d>> ops::Mul<S> for Align {
391 type Output = Self;
392
393 fn mul(mut self, rhs: S) -> Self {
394 self *= rhs;
395 self
396 }
397}
398impl<S: Into<Factor2d>> ops::MulAssign<S> for Align {
399 fn mul_assign(&mut self, rhs: S) {
400 let rhs = rhs.into();
401
402 if self.x.0.is_finite() {
403 self.x *= rhs.x;
404 } else if rhs.x == 0.fct() {
405 self.x = 0.fct();
406 }
407 if self.y.0.is_finite() {
408 self.y *= rhs.y;
409 } else if rhs.y == 0.fct() {
410 self.y = 0.fct()
411 }
412 }
413}
414impl<S: Into<Factor2d>> ops::Div<S> for Align {
415 type Output = Self;
416
417 fn div(mut self, rhs: S) -> Self {
418 self /= rhs;
419 self
420 }
421}
422impl<S: Into<Factor2d>> ops::DivAssign<S> for Align {
423 fn div_assign(&mut self, rhs: S) {
424 let rhs = rhs.into();
425
426 if self.x.0.is_finite() {
427 self.x /= rhs.x;
428 }
429 if self.y.0.is_finite() {
430 self.y /= rhs.y;
431 }
432 }
433}
434
435#[derive(serde::Serialize, serde::Deserialize)]
436#[serde(untagged)]
437enum AlignSerde<'s> {
438 Named(Cow<'s, str>),
439 Unnamed { x: Factor, x_rtl_aware: bool, y: Factor },
440}
441impl serde::Serialize for Align {
442 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
443 where
444 S: serde::Serializer,
445 {
446 if serializer.is_human_readable() {
447 if let Some(name) = self.name() {
448 return AlignSerde::Named(Cow::Borrowed(name)).serialize(serializer);
449 }
450 }
451
452 AlignSerde::Unnamed {
453 x: self.x,
454 x_rtl_aware: self.x_rtl_aware,
455 y: self.y,
456 }
457 .serialize(serializer)
458 }
459}
460impl<'de> serde::Deserialize<'de> for Align {
461 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
462 where
463 D: serde::Deserializer<'de>,
464 {
465 use serde::de::Error;
466
467 match AlignSerde::deserialize(deserializer)? {
468 AlignSerde::Named(n) => match Align::from_name(&n) {
469 Some(a) => Ok(a),
470 None => Err(D::Error::custom("unknown align name")),
471 },
472 AlignSerde::Unnamed { x, x_rtl_aware, y } => Ok(Align { x, x_rtl_aware, y }),
473 }
474 }
475}
476
477#[cfg(test)]
478mod tests {
479 use super::*;
480
481 #[test]
482 fn align_named() {
483 let value = serde_json::to_value(Align::TOP_START).unwrap();
484 assert_eq!(value, serde_json::Value::String("TOP_START".to_owned()));
485
486 let align: Align = serde_json::from_value(value).unwrap();
487 assert_eq!(align, Align::TOP_START);
488 }
489}