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