use crate::models::{ListElement, ProgressElement, ProgressStyle, WidgetElement};
use crate::receipt::SkippedElement;
use serde_json::{json, Value};
use super::media::rasterized_or_skip;
use super::style::{ac_color, approx_hex_semantic, color_hex};
pub(super) fn progress(
e: &WidgetElement,
p: &ProgressElement,
skipped: &mut Vec<SkippedElement>,
) -> Value {
if let Some(f) = super::time_fraction(p.start_at, p.end_at) {
let mut q = p.clone();
q.total = if q.total <= 0.0 { 1.0 } else { q.total };
q.value = f * q.total;
(q.start_at, q.end_at) = (None, None);
return progress(&WidgetElement::Progress(q.clone()), &q, skipped);
}
if matches!(p.bar_style, Some(ProgressStyle::Circular)) {
return rasterized_or_skip(e, skipped);
}
if p.track_color.is_some() || p.thickness.is_some() {
if let Ok(uri) = crate::rasterize::element_to_png_data_uri(e) {
let mut items = Vec::new();
if let Some(l) = &p.label {
items.push(progress_label(l, p));
}
items.push(json!({"type": "Image", "url": uri, "size": "Stretch"}));
return json!({"type": "Container", "items": items});
}
}
let ProgressElement {
value,
total,
label,
tint,
..
} = p;
let total = if *total <= 0.0 { 1.0 } else { *total };
let pct = ((*value / total) * 100.0).clamp(0.0, 100.0) as u32;
let rest = 100u32.saturating_sub(pct);
let mut items = Vec::new();
if let Some(l) = label {
items.push(progress_label(l, p));
}
let fill_hex = color_hex(tint.as_ref());
let fill_style = approx_hex_semantic(tint.as_ref())
.unwrap_or("Good")
.to_ascii_lowercase();
let fill = fill_hex.clone().unwrap_or_else(|| "#4caf50".into());
let solid = |hex: &str| {
crate::rasterize::solid_png_data_uri(hex).map(|u| json!({ "url": u, "fillMode": "repeat" }))
};
let mut filled = json!({
"type": "Column",
"width": pct.max(1),
"minHeight": "6px",
"items": []
});
match solid(&fill) {
Some(bg) => filled["backgroundImage"] = bg,
None => filled["style"] = json!(fill_style),
}
if let Some(ref hex) = fill_hex {
filled["id"] = json!(format!("fill:{hex}"));
}
let mut track = json!({
"type": "Column",
"width": rest.max(1),
"spacing": "None",
"minHeight": "6px",
"items": []
});
if let Some(bg) = solid(&format!("{}40", &fill[..7.min(fill.len())])) {
track["backgroundImage"] = bg;
}
items.push(json!({
"type": "ColumnSet",
"spacing": "Small",
"columns": [filled, track],
}));
json!({
"type": "Container",
"items": items,
})
}
fn progress_label(l: &str, p: &ProgressElement) -> Value {
let ProgressElement { tint, color, .. } = p;
let mut tb = json!({
"type": "TextBlock",
"text": l,
"size": "Small",
"wrap": true,
});
if let Some(c) = ac_color(color.as_ref())
.or_else(|| ac_color(tint.as_ref()))
.or_else(|| approx_hex_semantic(color.as_ref()))
.or_else(|| approx_hex_semantic(tint.as_ref()))
{
tb["color"] = json!(c);
}
tb
}
pub(super) fn gauge(e: &WidgetElement, skipped: &mut Vec<SkippedElement>) -> Value {
if let WidgetElement::Gauge(g) = e {
if let Some(f) = super::time_fraction(g.start_at, g.end_at) {
let mut g = g.clone();
let (lo, hi) = (g.min.unwrap_or(0.0), g.max.unwrap_or(1.0));
g.value = lo + f * (hi - lo);
return rasterized_or_skip(&WidgetElement::Gauge(g), skipped);
}
}
rasterized_or_skip(e, skipped)
}
pub(super) fn chart(e: &WidgetElement, skipped: &mut Vec<SkippedElement>) -> Value {
rasterized_or_skip(e, skipped)
}
pub(super) fn list(l: &ListElement) -> Value {
let ListElement { items, .. } = l;
let rows: Vec<Value> = items
.iter()
.map(|it| {
let (mark, mark_color) = match it.checked {
Some(true) => ("✓", "Good"),
Some(false) => ("○", "Light"),
None => (" ", "Light"),
};
let mut mark_cell = json!({
"type": "TableCell",
"verticalContentAlignment": "Center",
"items": [{
"type": "TextBlock",
"text": mark,
"wrap": false,
"size": "Small",
"color": mark_color,
"horizontalAlignment": "Center",
}]
});
let mut text_cell = json!({
"type": "TableCell",
"verticalContentAlignment": "Center",
"items": [{
"type": "TextBlock",
"text": it.text,
"wrap": true,
"size": "Small",
"horizontalAlignment": "Left",
"color": "Light",
}]
});
if let Some(ref a) = it.action {
let action = json!({
"type": "Action.Execute",
"verb": a,
"data": { "payload": it.payload },
});
text_cell["selectAction"] = action.clone();
mark_cell["selectAction"] = action;
}
json!({
"type": "TableRow",
"cells": [mark_cell, text_cell],
})
})
.collect();
json!({
"type": "Table",
"firstRowAsHeaders": false,
"showGridLines": false,
"columns": [
{ "width": 1 },
{ "width": 5 }
],
"rows": rows,
})
}