summaryrefslogtreecommitdiff
path: root/src/library/align.rs
blob: f3280065c51da69b68ad2493672920ed5e7f9472 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
use crate::prelude::*;

/// `align`: Align content along the layouting axes.
///
/// # Positional arguments
/// - At most two of `left`, `right`, `top`, `bottom`, `center`.
///
/// When `center` is used as a positional argument, it is automatically inferred
/// which axis it should apply to depending on further arguments, defaulting
/// to the axis, text is set along.
///
/// # Keyword arguments
/// - `horizontal`: Any of `left`, `right` or `center`.
/// - `vertical`: Any of `top`, `bottom` or `center`.
///
/// There may not be two alignment specifications for the same axis.
pub async fn align(mut args: Args, ctx: &mut LayoutContext) -> Value {
    let body = args.find::<SynTree>();
    let first = args.get::<_, Spanned<SpecAlign>>(ctx, 0);
    let second = args.get::<_, Spanned<SpecAlign>>(ctx, 1);
    let hor = args.get::<_, Spanned<SpecAlign>>(ctx, "horizontal");
    let ver = args.get::<_, Spanned<SpecAlign>>(ctx, "vertical");
    args.done(ctx);

    let iter = first
        .into_iter()
        .chain(second.into_iter())
        .map(|align| (align.v.axis(), align))
        .chain(hor.into_iter().map(|align| (Some(SpecAxis::Horizontal), align)))
        .chain(ver.into_iter().map(|align| (Some(SpecAxis::Vertical), align)));

    let aligns = dedup_aligns(ctx, iter);

    Value::Commands(match body {
        Some(tree) => vec![
            SetAlignment(aligns),
            LayoutSyntaxTree(tree),
            SetAlignment(ctx.state.aligns),
        ],
        None => vec![SetAlignment(aligns)],
    })
}

/// Deduplicate alignments and deduce to which axes they apply.
fn dedup_aligns(
    ctx: &mut LayoutContext,
    iter: impl Iterator<Item = (Option<SpecAxis>, Spanned<SpecAlign>)>,
) -> Gen2<GenAlign> {
    let mut aligns = ctx.state.aligns;
    let mut had = Gen2::new(false, false);
    let mut had_center = false;

    for (axis, Spanned { v: align, span }) in iter {
        // Check whether we know which axis this alignment belongs to.
        if let Some(axis) = axis {
            // We know the axis.
            let gen_axis = axis.switch(ctx.state.dirs);
            let gen_align = align.switch(ctx.state.dirs);

            if align.axis().map_or(false, |a| a != axis) {
                ctx.diag(error!(
                    span,
                    "invalid alignment `{}` for {} axis", align, axis,
                ));
            } else if had.get(gen_axis) {
                ctx.diag(error!(span, "duplicate alignment for {} axis", axis));
            } else {
                *aligns.get_mut(gen_axis) = gen_align;
                *had.get_mut(gen_axis) = true;
            }
        } else {
            // We don't know the axis: This has to be a `center` alignment for a
            // positional argument.
            debug_assert_eq!(align, SpecAlign::Center);

            if had.main && had.cross {
                ctx.diag(error!(span, "duplicate alignment"));
            } else if had_center {
                // Both this and the previous one are unspecified `center`
                // alignments. Both axes should be centered.
                aligns = Gen2::new(GenAlign::Center, GenAlign::Center);
                had = Gen2::new(true, true);
            } else {
                had_center = true;
            }
        }

        // If we we know one alignment, we can handle the unspecified `center`
        // alignment.
        if had_center && (had.main || had.cross) {
            if had.main {
                aligns.cross = GenAlign::Center;
                had.cross = true;
            } else {
                aligns.main = GenAlign::Center;
                had.main = true;
            }
            had_center = false;
        }
    }

    // If center has not been flushed by now, it is the only argument and then
    // we default to applying it to the cross axis.
    if had_center {
        aligns.cross = GenAlign::Center;
    }

    aligns
}