use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use crate::conditions::{resolve_conditions, ConditionContext};
use crate::types::UiNode;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TextDirection {
#[default]
Ltr,
Rtl,
}
impl TextDirection {
pub fn parse(s: &str) -> Option<TextDirection> {
match s {
"ltr" => Some(TextDirection::Ltr),
"rtl" => Some(TextDirection::Rtl),
_ => None,
}
}
pub fn as_str(self) -> &'static str {
match self {
TextDirection::Ltr => "ltr",
TextDirection::Rtl => "rtl",
}
}
pub fn of_locale(locale: &str) -> TextDirection {
if crate::locale::text_direction(locale) == "rtl" {
TextDirection::Rtl
} else {
TextDirection::Ltr
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PhysicalAlign {
#[default]
Left,
Right,
Center,
Justify,
}
impl PhysicalAlign {
pub fn as_str(self) -> &'static str {
match self {
PhysicalAlign::Left => "left",
PhysicalAlign::Right => "right",
PhysicalAlign::Center => "center",
PhysicalAlign::Justify => "justify",
}
}
}
fn own_direction(style: Option<&Map<String, Value>>, ctx: Option<&ConditionContext>) -> Option<TextDirection> {
let style = style?;
let d = match ctx {
Some(ctx) => resolve_conditions(style, ctx).get("direction").cloned(),
None => style.get("direction").cloned(),
};
d.as_ref().and_then(Value::as_str).and_then(TextDirection::parse)
}
pub fn node_directions(root: &UiNode, surface: TextDirection, ctx: Option<&ConditionContext>) -> IndexMap<String, TextDirection> {
fn walk(n: &UiNode, inherited: TextDirection, ctx: Option<&ConditionContext>, out: &mut IndexMap<String, TextDirection>) {
let dir = own_direction(n.style.as_ref(), ctx).unwrap_or(inherited);
out.insert(n.id.clone(), dir);
for slot in n.slots.iter().flat_map(|s| s.values()) {
walk(slot, dir, ctx, out);
}
for c in &n.children {
walk(c, dir, ctx, out);
}
}
let mut out = IndexMap::new();
walk(root, surface, ctx, &mut out);
out
}
pub fn physical_text_align(align: Option<&str>, direction: TextDirection) -> PhysicalAlign {
let rtl = direction == TextDirection::Rtl;
match align {
Some("left") => PhysicalAlign::Left,
Some("right") => PhysicalAlign::Right,
Some("center") => PhysicalAlign::Center,
Some("justify") => PhysicalAlign::Justify,
Some("end") => {
if rtl {
PhysicalAlign::Left
} else {
PhysicalAlign::Right
}
}
_ => {
if rtl {
PhysicalAlign::Right
} else {
PhysicalAlign::Left
}
}
}
}
pub fn text_align_of(style: &Map<String, Value>, props: &Map<String, Value>, direction: TextDirection) -> PhysicalAlign {
let align = style.get("textAlign").or_else(|| props.get("align")).and_then(Value::as_str);
physical_text_align(align, direction)
}
pub fn node_text_align(node: &UiNode, direction: TextDirection, ctx: Option<&ConditionContext>) -> PhysicalAlign {
let empty = Map::new();
let style = match (node.style.as_ref(), ctx) {
(Some(s), Some(ctx)) => resolve_conditions(s, ctx),
(Some(s), None) => s.clone(),
(None, _) => empty,
};
text_align_of(&style, &node.props, direction)
}