1use std::ops::Add;
2
3use ecow::{EcoString, eco_format};
4
5use crate::diag::{HintedStrResult, StrResult, bail};
6use crate::foundations::{
7 CastInfo, Content, Fold, FromValue, IntoValue, Reflect, Repr, Resolve, StyleChain,
8 Value, cast, elem, func, scope, ty,
9};
10use crate::layout::{Abs, Axes, Axis, Dir, Side};
11use crate::text::TextElem;
12
13#[elem]
79pub struct AlignElem {
80 #[positional]
93 #[fold]
94 #[default]
95 pub alignment: Alignment,
96
97 #[required]
99 pub body: Content,
100}
101
102#[ty(scope)]
143#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
144pub enum Alignment {
145 H(HAlignment),
146 V(VAlignment),
147 Both(HAlignment, VAlignment),
148}
149
150impl Alignment {
151 pub const fn x(self) -> Option<HAlignment> {
153 match self {
154 Self::H(h) | Self::Both(h, _) => Some(h),
155 Self::V(_) => None,
156 }
157 }
158
159 pub const fn y(self) -> Option<VAlignment> {
161 match self {
162 Self::V(v) | Self::Both(_, v) => Some(v),
163 Self::H(_) => None,
164 }
165 }
166
167 pub fn fix(self, text_dir: Dir) -> Axes<FixedAlignment> {
169 Axes::new(
170 self.x().unwrap_or_default().fix(text_dir),
171 self.y().unwrap_or_default().fix(text_dir),
172 )
173 }
174}
175
176#[scope]
177impl Alignment {
178 pub const START: Self = Alignment::H(HAlignment::Start);
179 pub const LEFT: Self = Alignment::H(HAlignment::Left);
180 pub const CENTER: Self = Alignment::H(HAlignment::Center);
181 pub const RIGHT: Self = Alignment::H(HAlignment::Right);
182 pub const END: Self = Alignment::H(HAlignment::End);
183 pub const TOP: Self = Alignment::V(VAlignment::Top);
184 pub const HORIZON: Self = Alignment::V(VAlignment::Horizon);
185 pub const BOTTOM: Self = Alignment::V(VAlignment::Bottom);
186
187 #[func]
197 pub const fn axis(self) -> Option<Axis> {
198 match self {
199 Self::H(_) => Some(Axis::X),
200 Self::V(_) => Some(Axis::Y),
201 Self::Both(..) => None,
202 }
203 }
204
205 #[func(title = "Inverse")]
214 pub const fn inv(self) -> Alignment {
215 match self {
216 Self::H(h) => Self::H(h.inv()),
217 Self::V(v) => Self::V(v.inv()),
218 Self::Both(h, v) => Self::Both(h.inv(), v.inv()),
219 }
220 }
221}
222
223impl Default for Alignment {
224 fn default() -> Self {
225 HAlignment::default() + VAlignment::default()
226 }
227}
228
229impl Add for Alignment {
230 type Output = StrResult<Self>;
231
232 fn add(self, rhs: Self) -> Self::Output {
233 match (self, rhs) {
234 (Self::H(h), Self::V(v)) | (Self::V(v), Self::H(h)) => Ok(h + v),
235 (Self::H(_), Self::H(_)) => bail!("cannot add two horizontal alignments"),
236 (Self::V(_), Self::V(_)) => bail!("cannot add two vertical alignments"),
237 (Self::H(_), Self::Both(..)) | (Self::Both(..), Self::H(_)) => {
238 bail!("cannot add a horizontal and a 2D alignment")
239 }
240 (Self::V(_), Self::Both(..)) | (Self::Both(..), Self::V(_)) => {
241 bail!("cannot add a vertical and a 2D alignment")
242 }
243 (Self::Both(..), Self::Both(..)) => {
244 bail!("cannot add two 2D alignments")
245 }
246 }
247 }
248}
249
250impl Repr for Alignment {
251 fn repr(&self) -> EcoString {
252 match self {
253 Self::H(h) => h.repr(),
254 Self::V(v) => v.repr(),
255 Self::Both(h, v) => eco_format!("{} + {}", h.repr(), v.repr()),
256 }
257 }
258}
259
260impl Fold for Alignment {
261 fn fold(self, outer: Self) -> Self {
262 match (self, outer) {
263 (Self::H(h), Self::V(v) | Self::Both(_, v)) => Self::Both(h, v),
264 (Self::V(v), Self::H(h) | Self::Both(h, _)) => Self::Both(h, v),
265 _ => self,
266 }
267 }
268}
269
270impl Resolve for Alignment {
271 type Output = Axes<FixedAlignment>;
272
273 fn resolve(self, styles: StyleChain) -> Self::Output {
274 self.fix(styles.resolve(TextElem::dir))
275 }
276}
277
278impl From<Side> for Alignment {
279 fn from(side: Side) -> Self {
280 match side {
281 Side::Left => Self::LEFT,
282 Side::Top => Self::TOP,
283 Side::Right => Self::RIGHT,
284 Side::Bottom => Self::BOTTOM,
285 }
286 }
287}
288
289pub trait FixAlignment {
291 fn fix(self, dir: Dir) -> FixedAlignment;
293}
294
295#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
297pub enum HAlignment {
298 #[default]
299 Start,
300 Left,
301 Center,
302 Right,
303 End,
304}
305
306impl HAlignment {
307 pub const fn inv(self) -> Self {
309 match self {
310 Self::Start => Self::End,
311 Self::Left => Self::Right,
312 Self::Center => Self::Center,
313 Self::Right => Self::Left,
314 Self::End => Self::Start,
315 }
316 }
317}
318
319impl FixAlignment for HAlignment {
320 fn fix(self, dir: Dir) -> FixedAlignment {
321 match (self, dir.is_positive()) {
322 (Self::Start, true) | (Self::End, false) => FixedAlignment::Start,
323 (Self::Left, _) => FixedAlignment::Start,
324 (Self::Center, _) => FixedAlignment::Center,
325 (Self::Right, _) => FixedAlignment::End,
326 (Self::End, true) | (Self::Start, false) => FixedAlignment::End,
327 }
328 }
329}
330
331impl Repr for HAlignment {
332 fn repr(&self) -> EcoString {
333 match self {
334 Self::Start => "start".into(),
335 Self::Left => "left".into(),
336 Self::Center => "center".into(),
337 Self::Right => "right".into(),
338 Self::End => "end".into(),
339 }
340 }
341}
342
343impl Add<VAlignment> for HAlignment {
344 type Output = Alignment;
345
346 fn add(self, rhs: VAlignment) -> Self::Output {
347 Alignment::Both(self, rhs)
348 }
349}
350
351impl From<HAlignment> for Alignment {
352 fn from(align: HAlignment) -> Self {
353 Self::H(align)
354 }
355}
356
357impl TryFrom<Alignment> for HAlignment {
358 type Error = EcoString;
359
360 fn try_from(value: Alignment) -> StrResult<Self> {
361 match value {
362 Alignment::H(h) => Ok(h),
363 v => bail!(
364 "expected `start`, `left`, `center`, `right`, or `end`, found {}",
365 v.repr(),
366 ),
367 }
368 }
369}
370
371impl Resolve for HAlignment {
372 type Output = FixedAlignment;
373
374 fn resolve(self, styles: StyleChain) -> Self::Output {
375 self.fix(styles.resolve(TextElem::dir))
376 }
377}
378
379cast! {
380 HAlignment,
381 self => Alignment::H(self).into_value(),
382 align: Alignment => Self::try_from(align)?,
383}
384
385#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
388pub enum OuterHAlignment {
389 #[default]
390 Start,
391 Left,
392 Right,
393 End,
394}
395
396impl FixAlignment for OuterHAlignment {
397 fn fix(self, dir: Dir) -> FixedAlignment {
398 match (self, dir.is_positive()) {
399 (Self::Start, true) | (Self::End, false) => FixedAlignment::Start,
400 (Self::Left, _) => FixedAlignment::Start,
401 (Self::Right, _) => FixedAlignment::End,
402 (Self::End, true) | (Self::Start, false) => FixedAlignment::End,
403 }
404 }
405}
406
407impl Resolve for OuterHAlignment {
408 type Output = FixedAlignment;
409
410 fn resolve(self, styles: StyleChain) -> Self::Output {
411 self.fix(styles.resolve(TextElem::dir))
412 }
413}
414
415impl From<OuterHAlignment> for HAlignment {
416 fn from(value: OuterHAlignment) -> Self {
417 match value {
418 OuterHAlignment::Start => Self::Start,
419 OuterHAlignment::Left => Self::Left,
420 OuterHAlignment::Right => Self::Right,
421 OuterHAlignment::End => Self::End,
422 }
423 }
424}
425
426impl TryFrom<Alignment> for OuterHAlignment {
427 type Error = EcoString;
428
429 fn try_from(value: Alignment) -> StrResult<Self> {
430 match value {
431 Alignment::H(HAlignment::Start) => Ok(Self::Start),
432 Alignment::H(HAlignment::Left) => Ok(Self::Left),
433 Alignment::H(HAlignment::Right) => Ok(Self::Right),
434 Alignment::H(HAlignment::End) => Ok(Self::End),
435 v => bail!("expected `start`, `left`, `right`, or `end`, found {}", v.repr()),
436 }
437 }
438}
439
440cast! {
441 OuterHAlignment,
442 self => HAlignment::from(self).into_value(),
443 align: Alignment => Self::try_from(align)?,
444}
445
446#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
448pub enum VAlignment {
449 #[default]
450 Top,
451 Horizon,
452 Bottom,
453}
454
455impl VAlignment {
456 pub const fn inv(self) -> Self {
458 match self {
459 Self::Top => Self::Bottom,
460 Self::Horizon => Self::Horizon,
461 Self::Bottom => Self::Top,
462 }
463 }
464
465 pub fn position(self, extent: Abs) -> Abs {
468 match self {
469 Self::Top => Abs::zero(),
470 Self::Horizon => extent / 2.0,
471 Self::Bottom => extent,
472 }
473 }
474}
475
476impl FixAlignment for VAlignment {
477 fn fix(self, _: Dir) -> FixedAlignment {
478 match self {
480 Self::Top => FixedAlignment::Start,
481 Self::Horizon => FixedAlignment::Center,
482 Self::Bottom => FixedAlignment::End,
483 }
484 }
485}
486
487impl Repr for VAlignment {
488 fn repr(&self) -> EcoString {
489 match self {
490 Self::Top => "top".into(),
491 Self::Horizon => "horizon".into(),
492 Self::Bottom => "bottom".into(),
493 }
494 }
495}
496
497impl Add<HAlignment> for VAlignment {
498 type Output = Alignment;
499
500 fn add(self, rhs: HAlignment) -> Self::Output {
501 Alignment::Both(rhs, self)
502 }
503}
504
505impl Resolve for VAlignment {
506 type Output = FixedAlignment;
507
508 fn resolve(self, _: StyleChain) -> Self::Output {
509 self.fix(Dir::TTB)
510 }
511}
512
513impl From<VAlignment> for Alignment {
514 fn from(align: VAlignment) -> Self {
515 Self::V(align)
516 }
517}
518
519impl TryFrom<Alignment> for VAlignment {
520 type Error = EcoString;
521
522 fn try_from(value: Alignment) -> StrResult<Self> {
523 match value {
524 Alignment::V(v) => Ok(v),
525 v => bail!("expected `top`, `horizon`, or `bottom`, found {}", v.repr()),
526 }
527 }
528}
529
530cast! {
531 VAlignment,
532 self => Alignment::V(self).into_value(),
533 align: Alignment => Self::try_from(align)?,
534}
535
536#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
539pub enum OuterVAlignment {
540 #[default]
541 Top,
542 Bottom,
543}
544
545impl FixAlignment for OuterVAlignment {
546 fn fix(self, _: Dir) -> FixedAlignment {
547 match self {
549 Self::Top => FixedAlignment::Start,
550 Self::Bottom => FixedAlignment::End,
551 }
552 }
553}
554
555impl From<OuterVAlignment> for VAlignment {
556 fn from(value: OuterVAlignment) -> Self {
557 match value {
558 OuterVAlignment::Top => Self::Top,
559 OuterVAlignment::Bottom => Self::Bottom,
560 }
561 }
562}
563
564impl TryFrom<Alignment> for OuterVAlignment {
565 type Error = EcoString;
566
567 fn try_from(value: Alignment) -> StrResult<Self> {
568 match value {
569 Alignment::V(VAlignment::Top) => Ok(Self::Top),
570 Alignment::V(VAlignment::Bottom) => Ok(Self::Bottom),
571 v => bail!("expected `top` or `bottom`, found {}", v.repr()),
572 }
573 }
574}
575
576cast! {
577 OuterVAlignment,
578 self => VAlignment::from(self).into_value(),
579 align: Alignment => Self::try_from(align)?,
580}
581
582#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
589pub enum SpecificAlignment<H, V> {
590 H(H),
591 V(V),
592 Both(H, V),
593}
594
595impl<H, V> SpecificAlignment<H, V>
596where
597 H: Default + Copy + FixAlignment,
598 V: Default + Copy + FixAlignment,
599{
600 pub const fn x(self) -> Option<H> {
602 match self {
603 Self::H(h) | Self::Both(h, _) => Some(h),
604 Self::V(_) => None,
605 }
606 }
607
608 pub const fn y(self) -> Option<V> {
610 match self {
611 Self::V(v) | Self::Both(_, v) => Some(v),
612 Self::H(_) => None,
613 }
614 }
615
616 pub fn fix(self, text_dir: Dir) -> Axes<FixedAlignment> {
618 Axes::new(
619 self.x().unwrap_or_default().fix(text_dir),
620 self.y().unwrap_or_default().fix(text_dir),
621 )
622 }
623}
624
625impl<H, V> Resolve for SpecificAlignment<H, V>
626where
627 H: Default + Copy + FixAlignment,
628 V: Default + Copy + FixAlignment,
629{
630 type Output = Axes<FixedAlignment>;
631
632 fn resolve(self, styles: StyleChain) -> Self::Output {
633 self.fix(styles.resolve(TextElem::dir))
634 }
635}
636
637impl<H, V> From<SpecificAlignment<H, V>> for Alignment
638where
639 HAlignment: From<H>,
640 VAlignment: From<V>,
641{
642 fn from(value: SpecificAlignment<H, V>) -> Self {
643 type FromType<H, V> = SpecificAlignment<H, V>;
644 match value {
645 FromType::H(h) => Self::H(HAlignment::from(h)),
646 FromType::V(v) => Self::V(VAlignment::from(v)),
647 FromType::Both(h, v) => Self::Both(HAlignment::from(h), VAlignment::from(v)),
648 }
649 }
650}
651
652impl<H, V> Reflect for SpecificAlignment<H, V>
653where
654 H: Reflect,
655 V: Reflect,
656{
657 fn input() -> CastInfo {
658 Alignment::input()
659 }
660
661 fn output() -> CastInfo {
662 Alignment::output()
663 }
664
665 fn castable(value: &Value) -> bool {
666 H::castable(value) || V::castable(value)
667 }
668}
669
670impl<H, V> IntoValue for SpecificAlignment<H, V>
671where
672 HAlignment: From<H>,
673 VAlignment: From<V>,
674{
675 fn into_value(self) -> Value {
676 Alignment::from(self).into_value()
677 }
678}
679
680impl<H, V> FromValue for SpecificAlignment<H, V>
681where
682 H: Reflect + TryFrom<Alignment, Error = EcoString>,
683 V: Reflect + TryFrom<Alignment, Error = EcoString>,
684{
685 fn from_value(value: Value) -> HintedStrResult<Self> {
686 if Alignment::castable(&value) {
687 let align = Alignment::from_value(value)?;
688 let result = match align {
689 Alignment::H(_) => Self::H(H::try_from(align)?),
690 Alignment::V(_) => Self::V(V::try_from(align)?),
691 Alignment::Both(h, v) => {
692 Self::Both(H::try_from(h.into())?, V::try_from(v.into())?)
693 }
694 };
695 return Ok(result);
696 }
697 Err(Self::error(&value))
698 }
699}
700
701#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
706pub enum FixedAlignment {
707 Start,
708 Center,
709 End,
710}
711
712impl FixedAlignment {
713 pub fn position(self, extent: Abs) -> Abs {
716 match self {
717 Self::Start => Abs::zero(),
718 Self::Center => extent / 2.0,
719 Self::End => extent,
720 }
721 }
722
723 pub const fn inv(self) -> Self {
725 match self {
726 Self::Start => Self::End,
727 Self::Center => Self::Center,
728 Self::End => Self::Start,
729 }
730 }
731}
732
733impl From<Side> for FixedAlignment {
734 fn from(side: Side) -> Self {
735 match side {
736 Side::Left => Self::Start,
737 Side::Top => Self::Start,
738 Side::Right => Self::End,
739 Side::Bottom => Self::End,
740 }
741 }
742}