fix: rendering in a2ui surface and viewport aligned
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
use crate::a2ui::{Surface, binding_path, bound_value_at, display_value};
|
||||
use iced::widget::{Column, Row, column, radio, text_editor};
|
||||
use iced::widget::{Column, Row, column, radio, responsive, text_editor};
|
||||
use serde_json::Value;
|
||||
use std::collections::{BTreeSet, HashMap, HashSet};
|
||||
use time::{Month, OffsetDateTime};
|
||||
@@ -23,7 +23,6 @@ impl App {
|
||||
.into()
|
||||
});
|
||||
container(content)
|
||||
.max_width(860)
|
||||
.center_x(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.padding(24)
|
||||
@@ -80,8 +79,20 @@ impl App {
|
||||
.get("agentDisplayName")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("A2UI");
|
||||
let content = if surface.components.contains_key("root") {
|
||||
self.a2ui_component(surface, "root", &surface.data, None, BTreeSet::new())
|
||||
let content: Element<'_, Message> = if surface.components.contains_key("root") {
|
||||
responsive(move |size| {
|
||||
scrollable(self.a2ui_component(
|
||||
surface,
|
||||
"root",
|
||||
&surface.data,
|
||||
None,
|
||||
BTreeSet::new(),
|
||||
size.height,
|
||||
))
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
text("Building interactive surface…")
|
||||
.size(13)
|
||||
@@ -118,7 +129,7 @@ impl App {
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(Alignment::Center),
|
||||
scrollable(content).height(Length::Fill),
|
||||
content,
|
||||
]
|
||||
.height(Length::Fill)
|
||||
.spacing(10),
|
||||
@@ -137,6 +148,7 @@ impl App {
|
||||
context: &'a Value,
|
||||
context_path: Option<String>,
|
||||
mut ancestors: BTreeSet<String>,
|
||||
surface_height: f32,
|
||||
) -> Element<'a, Message> {
|
||||
if !ancestors.insert(id.to_owned()) {
|
||||
return text(format!("Cyclic component reference: {id}"))
|
||||
@@ -254,6 +266,7 @@ impl App {
|
||||
context_path,
|
||||
ancestors,
|
||||
Some((component, kind == "Row")),
|
||||
surface_height,
|
||||
),
|
||||
"List" => {
|
||||
let horizontal =
|
||||
@@ -265,6 +278,7 @@ impl App {
|
||||
context_path,
|
||||
ancestors,
|
||||
Some((component, horizontal)),
|
||||
surface_height,
|
||||
);
|
||||
let list = scrollable(content).height(280);
|
||||
if horizontal {
|
||||
@@ -278,7 +292,14 @@ impl App {
|
||||
}
|
||||
"Card" => {
|
||||
let child = component.get("child").and_then(Value::as_str).unwrap_or("");
|
||||
container(self.a2ui_component(surface, child, context, context_path, ancestors))
|
||||
container(self.a2ui_component(
|
||||
surface,
|
||||
child,
|
||||
context,
|
||||
context_path,
|
||||
ancestors,
|
||||
surface_height,
|
||||
))
|
||||
.padding(14)
|
||||
.width(Length::Fill)
|
||||
.style(overview_style)
|
||||
@@ -317,7 +338,14 @@ impl App {
|
||||
.unwrap_or("");
|
||||
column![
|
||||
labels,
|
||||
self.a2ui_component(surface, child, context, context_path, ancestors,)
|
||||
self.a2ui_component(
|
||||
surface,
|
||||
child,
|
||||
context,
|
||||
context_path,
|
||||
ancestors,
|
||||
surface_height,
|
||||
)
|
||||
]
|
||||
.spacing(10)
|
||||
.into()
|
||||
@@ -338,6 +366,7 @@ impl App {
|
||||
context,
|
||||
context_path.clone(),
|
||||
ancestors.clone(),
|
||||
surface_height,
|
||||
))
|
||||
.on_press(Message::A2uiToggleModal(key.0.clone(), key.1.clone()));
|
||||
let _ = (content, context_path, ancestors);
|
||||
@@ -345,8 +374,14 @@ impl App {
|
||||
}
|
||||
"Button" => {
|
||||
let child = component.get("child").and_then(Value::as_str).unwrap_or("");
|
||||
let content =
|
||||
self.a2ui_component(surface, child, context, context_path.clone(), ancestors);
|
||||
let content = self.a2ui_component(
|
||||
surface,
|
||||
child,
|
||||
context,
|
||||
context_path.clone(),
|
||||
ancestors,
|
||||
surface_height,
|
||||
);
|
||||
let button =
|
||||
if component.get("variant").and_then(Value::as_str) == Some("borderless") {
|
||||
button(content).style(button::text)
|
||||
@@ -640,7 +675,7 @@ impl App {
|
||||
};
|
||||
picker.push(choices).into()
|
||||
}
|
||||
"Chart" => research_chart(component, &surface.data, context),
|
||||
"Chart" => research_chart(component, &surface.data, context, surface_height),
|
||||
"Table" => research_table(component, &surface.data, context),
|
||||
"Metric" => research_metric(component, &surface.data, context),
|
||||
"Timeline" => research_timeline(component, &surface.data, context),
|
||||
@@ -654,6 +689,7 @@ impl App {
|
||||
context_path.clone(),
|
||||
ancestors,
|
||||
None,
|
||||
surface_height,
|
||||
);
|
||||
let label = component
|
||||
.get("submitLabel")
|
||||
@@ -687,6 +723,7 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn a2ui_children<'a>(
|
||||
&'a self,
|
||||
surface: &'a Surface,
|
||||
@@ -695,6 +732,7 @@ impl App {
|
||||
context_path: Option<String>,
|
||||
ancestors: BTreeSet<String>,
|
||||
layout: Option<(&serde_json::Map<String, Value>, bool)>,
|
||||
surface_height: f32,
|
||||
) -> Element<'a, Message> {
|
||||
let horizontal = layout.is_some_and(|(_, horizontal)| horizontal);
|
||||
let layout = layout.map(|(component, _)| component);
|
||||
@@ -708,6 +746,7 @@ impl App {
|
||||
context,
|
||||
context_path.clone(),
|
||||
ancestors.clone(),
|
||||
surface_height,
|
||||
),
|
||||
component_weight(surface, child),
|
||||
));
|
||||
@@ -728,6 +767,7 @@ impl App {
|
||||
item,
|
||||
Some(format!("{}/{index}", path.trim_end_matches('/'))),
|
||||
ancestors.clone(),
|
||||
surface_height,
|
||||
)
|
||||
}),
|
||||
component_weight(surface, component_id),
|
||||
@@ -906,8 +946,14 @@ impl App {
|
||||
let surface = self.displayed_a2ui_store().surface(surface_id)?;
|
||||
let component = surface.components.get(component_id)?.as_object()?;
|
||||
let content_id = component.get("content")?.as_str()?;
|
||||
let content =
|
||||
self.a2ui_component(surface, content_id, &surface.data, None, BTreeSet::new());
|
||||
let content = self.a2ui_component(
|
||||
surface,
|
||||
content_id,
|
||||
&surface.data,
|
||||
None,
|
||||
BTreeSet::new(),
|
||||
480.0,
|
||||
);
|
||||
let dialog = container(
|
||||
column![
|
||||
row![
|
||||
@@ -1405,6 +1451,7 @@ fn research_chart<'a>(
|
||||
component: &serde_json::Map<String, Value>,
|
||||
data: &Value,
|
||||
context: &Value,
|
||||
surface_height: f32,
|
||||
) -> Element<'a, Message> {
|
||||
let series = bound_value_at(component.get("series"), data, context);
|
||||
let series = series.as_array().map(Vec::as_slice).unwrap_or(&[]);
|
||||
@@ -1413,7 +1460,7 @@ fn research_chart<'a>(
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("bar");
|
||||
if matches!(chart_type, "pie" | "donut") {
|
||||
return research_pie_chart(component, series, chart_type == "donut");
|
||||
return research_pie_chart(component, series, chart_type == "donut", surface_height);
|
||||
}
|
||||
let max = series
|
||||
.iter()
|
||||
@@ -1464,6 +1511,7 @@ fn research_pie_chart<'a>(
|
||||
component: &serde_json::Map<String, Value>,
|
||||
series: &[Value],
|
||||
donut: bool,
|
||||
surface_height: f32,
|
||||
) -> Element<'a, Message> {
|
||||
let slices = series
|
||||
.iter()
|
||||
@@ -1482,7 +1530,7 @@ fn research_pie_chart<'a>(
|
||||
.iter()
|
||||
.map(|(_, value, _)| *value)
|
||||
.collect::<Vec<_>>();
|
||||
let mut chart = Column::new().spacing(8);
|
||||
let mut chart = Column::new().spacing(8).width(Length::Fill);
|
||||
if let Some(title) = title(component) {
|
||||
chart = chart.push(title);
|
||||
}
|
||||
@@ -1496,35 +1544,56 @@ fn research_pie_chart<'a>(
|
||||
.push(text("No positive values").size(11).color(muted_text()))
|
||||
.into();
|
||||
}
|
||||
chart = chart.push(
|
||||
container(
|
||||
svg(svg::Handle::from_memory(
|
||||
pie_chart_svg(&values, donut).into_bytes(),
|
||||
))
|
||||
.width(180)
|
||||
.height(180),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.align_x(Alignment::Center),
|
||||
);
|
||||
let handle = svg::Handle::from_memory(pie_chart_svg(&values, donut).into_bytes());
|
||||
let total = values.iter().sum::<f64>();
|
||||
let mut legend: Row<'_, Message> = Row::new().spacing(12);
|
||||
for (index, (label, _, value)) in slices.into_iter().enumerate() {
|
||||
chart = chart.push(
|
||||
responsive(move |size| {
|
||||
let (body_height, side, rows_per_column) = pie_chart_layout(size.width, surface_height);
|
||||
let mut legend = Row::new().spacing(14);
|
||||
for (column_index, entries) in slices.chunks(rows_per_column).enumerate() {
|
||||
let mut column = Column::new().spacing(6);
|
||||
for (row_index, (label, _, value)) in entries.iter().enumerate() {
|
||||
let index = column_index * rows_per_column + row_index;
|
||||
let color = chart_color(index);
|
||||
legend = legend.push(
|
||||
column = column.push(
|
||||
row![
|
||||
container(Space::new().width(10).height(10))
|
||||
.style(move |_| container::Style::default().background(color)),
|
||||
text(label).size(11),
|
||||
text(format!("{value} · {:.0}%", values[index] / total * 100.0))
|
||||
.size(10)
|
||||
.color(muted_text()),
|
||||
.style(move |_| { container::Style::default().background(color) }),
|
||||
text(format!(
|
||||
"{label} {value} · {:.0}%",
|
||||
values[index] / total * 100.0
|
||||
))
|
||||
.size(11),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(Alignment::Center),
|
||||
);
|
||||
}
|
||||
chart.push(legend.wrap()).into()
|
||||
legend = legend.push(column);
|
||||
}
|
||||
row![
|
||||
container(svg(handle.clone()).width(side).height(side))
|
||||
.width(side)
|
||||
.align_x(Alignment::Center),
|
||||
container(legend).width(Length::Fill),
|
||||
]
|
||||
.height(body_height)
|
||||
.spacing(18)
|
||||
.align_y(Alignment::Center)
|
||||
.into()
|
||||
})
|
||||
.height(Length::Shrink),
|
||||
);
|
||||
chart.into()
|
||||
}
|
||||
|
||||
fn pie_chart_layout(width: f32, surface_height: f32) -> (f32, f32, usize) {
|
||||
let body_height = (surface_height - 80.0).max(0.0);
|
||||
(
|
||||
body_height,
|
||||
(width * 0.68).min(body_height).min(560.0),
|
||||
((body_height + 6.0) / 20.0).max(1.0) as usize,
|
||||
)
|
||||
}
|
||||
|
||||
fn pie_chart_svg(values: &[f64], donut: bool) -> String {
|
||||
@@ -1952,5 +2021,7 @@ mod tests {
|
||||
let donut = pie_chart_svg(&[41.0], true);
|
||||
assert!(donut.contains("stroke-dasharray"));
|
||||
assert!(donut.contains(">41</text>"));
|
||||
assert_eq!(pie_chart_layout(1_000.0, 400.0), (320.0, 320.0, 16));
|
||||
assert_eq!(pie_chart_layout(1_000.0, 700.0), (620.0, 560.0, 31));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user