1use indexmap::IndexMap;
11use serde::{Deserialize, Serialize};
12use serde_json::{Map, Value};
13
14use crate::conditions::{resolve_conditions, ConditionContext};
15use crate::types::UiNode;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash, Serialize, Deserialize)]
19#[serde(rename_all = "lowercase")]
20pub enum TextDirection {
21 #[default]
22 Ltr,
23 Rtl,
24}
25
26impl TextDirection {
27 pub fn parse(s: &str) -> Option<TextDirection> {
28 match s {
29 "ltr" => Some(TextDirection::Ltr),
30 "rtl" => Some(TextDirection::Rtl),
31 _ => None,
32 }
33 }
34
35 pub fn as_str(self) -> &'static str {
36 match self {
37 TextDirection::Ltr => "ltr",
38 TextDirection::Rtl => "rtl",
39 }
40 }
41
42 pub fn of_locale(locale: &str) -> TextDirection {
44 if crate::locale::text_direction(locale) == "rtl" {
45 TextDirection::Rtl
46 } else {
47 TextDirection::Ltr
48 }
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash, Serialize, Deserialize)]
54#[serde(rename_all = "lowercase")]
55pub enum PhysicalAlign {
56 #[default]
57 Left,
58 Right,
59 Center,
60 Justify,
61}
62
63impl PhysicalAlign {
64 pub fn as_str(self) -> &'static str {
65 match self {
66 PhysicalAlign::Left => "left",
67 PhysicalAlign::Right => "right",
68 PhysicalAlign::Center => "center",
69 PhysicalAlign::Justify => "justify",
70 }
71 }
72}
73
74fn own_direction(style: Option<&Map<String, Value>>, ctx: Option<&ConditionContext>) -> Option<TextDirection> {
75 let style = style?;
76 let d = match ctx {
77 Some(ctx) => resolve_conditions(style, ctx).get("direction").cloned(),
78 None => style.get("direction").cloned(),
79 };
80 d.as_ref().and_then(Value::as_str).and_then(TextDirection::parse)
81}
82
83pub fn node_directions(root: &UiNode, surface: TextDirection, ctx: Option<&ConditionContext>) -> IndexMap<String, TextDirection> {
88 fn walk(n: &UiNode, inherited: TextDirection, ctx: Option<&ConditionContext>, out: &mut IndexMap<String, TextDirection>) {
89 let dir = own_direction(n.style.as_ref(), ctx).unwrap_or(inherited);
90 out.insert(n.id.clone(), dir);
91 for slot in n.slots.iter().flat_map(|s| s.values()) {
92 walk(slot, dir, ctx, out);
93 }
94 for c in &n.children {
95 walk(c, dir, ctx, out);
96 }
97 }
98 let mut out = IndexMap::new();
99 walk(root, surface, ctx, &mut out);
100 out
101}
102
103pub fn physical_text_align(align: Option<&str>, direction: TextDirection) -> PhysicalAlign {
106 let rtl = direction == TextDirection::Rtl;
107 match align {
108 Some("left") => PhysicalAlign::Left,
109 Some("right") => PhysicalAlign::Right,
110 Some("center") => PhysicalAlign::Center,
111 Some("justify") => PhysicalAlign::Justify,
112 Some("end") => {
113 if rtl {
114 PhysicalAlign::Left
115 } else {
116 PhysicalAlign::Right
117 }
118 }
119 _ => {
120 if rtl {
121 PhysicalAlign::Right
122 } else {
123 PhysicalAlign::Left
124 }
125 }
126 }
127}
128
129pub fn text_align_of(style: &Map<String, Value>, props: &Map<String, Value>, direction: TextDirection) -> PhysicalAlign {
132 let align = style.get("textAlign").or_else(|| props.get("align")).and_then(Value::as_str);
133 physical_text_align(align, direction)
134}
135
136pub fn node_text_align(node: &UiNode, direction: TextDirection, ctx: Option<&ConditionContext>) -> PhysicalAlign {
138 let empty = Map::new();
139 let style = match (node.style.as_ref(), ctx) {
140 (Some(s), Some(ctx)) => resolve_conditions(s, ctx),
141 (Some(s), None) => s.clone(),
142 (None, _) => empty,
143 };
144 text_align_of(&style, &node.props, direction)
145}