use crate::models::{
ContainerElement, GridElement, HStackElement, Justify, VStackElement, WidgetElement,
ZStackElement,
};
use crate::receipt::SkippedElement;
use serde_json::{json, Value};
use super::el;
use super::style::{
apply_container_style, apply_container_style_to_wrapper, apply_gap_id, flex_width, priority_of,
};
fn distribute(
items: Vec<Value>,
justify: Option<Justify>,
filler: impl Fn() -> Value,
) -> Vec<Value> {
let Some(j) = justify.filter(|j| *j != Justify::Start) else {
return items;
};
let n = items.len();
let mut out = Vec::with_capacity(n * 2 + 1);
let edges = matches!(
j,
Justify::Center | Justify::End | Justify::SpaceAround | Justify::SpaceEvenly
);
if edges {
out.push(filler());
}
for (i, it) in items.into_iter().enumerate() {
if i > 0
&& matches!(
j,
Justify::SpaceBetween | Justify::SpaceAround | Justify::SpaceEvenly
)
{
out.push(filler());
}
out.push(it);
}
if matches!(
j,
Justify::Center | Justify::SpaceAround | Justify::SpaceEvenly
) {
out.push(filler());
}
out
}
pub(super) fn vstack(v: &VStackElement, skipped: &mut Vec<SkippedElement>) -> Value {
let VStackElement {
children,
style,
alignment,
spacing,
justify,
} = v;
let items: Vec<Value> = children
.iter()
.enumerate()
.map(|(i, c)| {
let mut item = el(c, skipped);
if i > 0 {
apply_gap_id(&mut item, *spacing);
}
item
})
.collect();
let items = distribute(
items,
*justify,
|| json!({"type": "Container", "spacing": "None", "height": "stretch", "items": []}),
);
let mut obj = json!({
"type": "Container",
"spacing": "None",
"items": items,
});
match justify {
Some(Justify::Center) => obj["verticalContentAlignment"] = json!("Center"),
Some(Justify::End) => obj["verticalContentAlignment"] = json!("Bottom"),
_ => {}
}
if justify.is_some_and(|j| j != Justify::Start) {
obj["height"] = json!("stretch");
}
apply_container_style(&mut obj, style);
if let Some(a) = alignment {
match a {
crate::models::HorizontalAlignment::Center => {
obj["horizontalAlignment"] = json!("Center");
}
crate::models::HorizontalAlignment::Trailing => {
obj["horizontalAlignment"] = json!("Right");
}
_ => {}
}
}
obj
}
pub(super) fn hstack(h: &HStackElement, skipped: &mut Vec<SkippedElement>) -> Value {
let HStackElement {
children,
spacing,
justify,
style,
..
} = h;
let n = children.len();
let top_priority = children
.iter()
.filter_map(priority_of)
.fold(None, |m: Option<f64>, p| Some(m.map_or(p, |m| m.max(p))));
let columns: Vec<Value> = children
.iter()
.enumerate()
.map(|(i, c)| {
let (width, items) = match c {
WidgetElement::Spacer(_) => (json!("stretch"), json!([])),
_ if i + 1 < n && matches!(children[i + 1], WidgetElement::Spacer(_)) => {
(json!("stretch"), json!([el(c, skipped)]))
}
_ => {
let mut w = flex_width(c);
if w == json!("auto") && super::style::fills_width(c) {
w = json!("stretch");
}
if let Some(top) = top_priority {
if w == json!("auto") && priority_of(c).unwrap_or(0.0) < top {
w = json!("stretch");
}
}
(w, json!([el(c, skipped)]))
}
};
let mut col = json!({
"type": "Column",
"width": width,
"items": items,
});
if i > 0 {
apply_gap_id(&mut col, *spacing);
}
col
})
.collect();
let columns = distribute(
columns,
*justify,
|| json!({"type": "Column", "width": "stretch", "spacing": "None", "items": []}),
);
let set = json!({
"type": "ColumnSet",
"spacing": "None",
"columns": columns,
});
if style.background.is_none() {
return set;
}
let mut obj = json!({"type": "Container", "spacing": "None", "items": [set]});
apply_container_style(&mut obj, style);
obj
}
pub(super) fn container(c: &ContainerElement, skipped: &mut Vec<SkippedElement>) -> Value {
let ContainerElement {
children,
style,
content_alignment,
..
} = c;
let mut obj = json!({
"type": "Container",
"spacing": "None",
"items": children.iter().map(|c| el(c, skipped)).collect::<Vec<_>>(),
});
apply_container_style(&mut obj, style);
if content_alignment
.as_deref()
.map(|a| a.to_ascii_lowercase().contains("center"))
.unwrap_or(false)
{
obj["horizontalAlignment"] = json!("Center");
}
obj
}
pub(super) fn grid(g: &GridElement, skipped: &mut Vec<SkippedElement>) -> Value {
let GridElement {
children,
columns,
spacing,
row_spacing,
..
} = g;
let cols = (*columns).max(1) as usize;
let mut rows = Vec::new();
for (ri, chunk) in children.chunks(cols).enumerate() {
let columns_json: Vec<Value> = chunk
.iter()
.enumerate()
.map(|(ci, c)| {
let mut col = json!({
"type": "Column",
"width": "stretch",
"items": [el(c, skipped)],
});
if ci > 0 {
apply_gap_id(&mut col, *spacing);
}
col
})
.collect();
let mut row = json!({
"type": "ColumnSet",
"spacing": "None",
"columns": columns_json,
});
if ri > 0 {
apply_gap_id(&mut row, row_spacing.or(*spacing));
}
rows.push(row);
}
json!({
"type": "Container",
"spacing": "None",
"items": rows,
})
}
pub(super) fn zstack(
e: &WidgetElement,
z: &ZStackElement,
skipped: &mut Vec<SkippedElement>,
) -> Value {
let ZStackElement {
children,
style,
alignment,
..
} = z;
if let Ok(uri) = crate::rasterize::element_to_png_data_uri(e) {
let mut img = json!({
"type": "Image",
"url": uri,
"size": "Stretch",
"horizontalAlignment": "Center",
});
if let Some((w, h)) = crate::rasterize::zstack_size(children, style) {
img.as_object_mut().unwrap().remove("size");
img["width"] = json!(super::style::px(w));
img["height"] = json!(super::style::px(h));
}
apply_container_style_to_wrapper(&mut img, style);
return img;
}
let mut obj = json!({
"type": "Container",
"spacing": "None",
"items": children.iter().map(|c| el(c, skipped)).collect::<Vec<_>>(),
});
apply_container_style(&mut obj, style);
let centered = alignment
.as_deref()
.map(|a| a.to_ascii_lowercase().contains("center"))
.unwrap_or(false);
if centered {
obj["horizontalAlignment"] = json!("Center");
}
if let Some(items) = obj.get("items").and_then(|i| i.as_array()) {
if items.len() == 2 {
obj["id"] = json!("badge:overlay");
}
}
obj
}