Skip to main content

zenith_core/ast/node/
anchor.rs

1//! Page-relative 9-point anchor and adjacent-edge placement: the [`Anchor`] and
2//! [`AnchorEdge`] enums and their derivation helpers.
3
4/// One of the nine named page-relative placement anchors.
5///
6/// An anchor supplies BOTH the x and y of a node from the page dimensions;
7/// an explicitly-authored `x` or `y` on the node overrides the corresponding
8/// anchor-derived coordinate. The node's `w` and `h` must be present and in a
9/// px-convertible unit for derivation to succeed; when they are absent or
10/// use a non-px unit the anchor is silently skipped (the node remains incomplete
11/// and the compile step emits the usual `scene.missing_geometry` advisory).
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Anchor {
14    TopLeft,
15    TopCenter,
16    TopRight,
17    CenterLeft,
18    Center,
19    CenterRight,
20    BottomLeft,
21    BottomCenter,
22    BottomRight,
23}
24
25/// Parse a string into an [`Anchor`] variant.
26///
27/// Returns `Some` for the nine recognized names; `None` for any other string.
28/// This is the SINGLE source of truth for the anchor name list — both the
29/// validator (`anchor.unknown_value`) and the scene pre-pass use this function
30/// so the names cannot diverge.
31pub fn parse_anchor(s: &str) -> Option<Anchor> {
32    match s {
33        "top-left" => Some(Anchor::TopLeft),
34        "top-center" => Some(Anchor::TopCenter),
35        "top-right" => Some(Anchor::TopRight),
36        "center-left" => Some(Anchor::CenterLeft),
37        "center" => Some(Anchor::Center),
38        "center-right" => Some(Anchor::CenterRight),
39        "bottom-left" => Some(Anchor::BottomLeft),
40        "bottom-center" => Some(Anchor::BottomCenter),
41        "bottom-right" => Some(Anchor::BottomRight),
42        _ => None,
43    }
44}
45
46/// One of the four adjacent-edge directions for `anchor-edge` placement.
47///
48/// When a node carries both `anchor-sibling` and `anchor-edge`, the node is
49/// placed flush against the named edge of the sibling rather than within the
50/// sibling's box. An optional 9-point `anchor` on the same node controls
51/// cross-axis alignment (e.g. centering horizontally when placed below).
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum AnchorEdge {
54    Above,
55    Below,
56    Before,
57    After,
58}
59
60/// Parse a string into an [`AnchorEdge`] variant.
61///
62/// Returns `Some` for the four recognized names; `None` for any other string.
63/// This is the SINGLE source of truth for the anchor-edge name list — both the
64/// validator (`anchor.unknown_edge`) and the scene pre-pass use this function
65/// so the names cannot diverge.
66pub fn parse_anchor_edge(s: &str) -> Option<AnchorEdge> {
67    match s {
68        "above" => Some(AnchorEdge::Above),
69        "below" => Some(AnchorEdge::Below),
70        "before" => Some(AnchorEdge::Before),
71        "after" => Some(AnchorEdge::After),
72        _ => None,
73    }
74}
75
76/// Derive the `(x, y)` for the given anchor given the page and node dimensions.
77///
78/// `page_w`/`page_h` and `node_w`/`node_h` are all in pixels.
79pub fn anchor_xy(anchor: Anchor, page_w: f64, page_h: f64, node_w: f64, node_h: f64) -> (f64, f64) {
80    match anchor {
81        Anchor::TopLeft => (0.0, 0.0),
82        Anchor::TopCenter => ((page_w - node_w) / 2.0, 0.0),
83        Anchor::TopRight => (page_w - node_w, 0.0),
84        Anchor::CenterLeft => (0.0, (page_h - node_h) / 2.0),
85        Anchor::Center => ((page_w - node_w) / 2.0, (page_h - node_h) / 2.0),
86        Anchor::CenterRight => (page_w - node_w, (page_h - node_h) / 2.0),
87        Anchor::BottomLeft => (0.0, page_h - node_h),
88        Anchor::BottomCenter => ((page_w - node_w) / 2.0, page_h - node_h),
89        Anchor::BottomRight => (page_w - node_w, page_h - node_h),
90    }
91}