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}