fret_chart/retained/
multi_grid.rs1use std::cell::RefCell;
2use std::collections::BTreeSet;
3use std::rc::Rc;
4
5use delinea::data::DataTable;
6use delinea::engine::ChartEngine;
7use delinea::engine::model::ModelError;
8use delinea::ids::{DatasetId, GridId};
9use delinea::spec::ChartSpec;
10use fret_core::{NodeId, Px, Rect, Size};
11use fret_ui::layout_pass::LayoutPassKind;
12use fret_ui::retained_bridge::{LayoutCx, UiTreeRetainedExt as _, Widget};
13use fret_ui::{UiHost, UiTree};
14
15use super::ChartCanvas;
16
17#[derive(Debug, Clone, Copy)]
18pub struct UniformGrid {
19 pub columns: usize,
20 pub gap: Px,
21}
22
23impl UniformGrid {
24 pub fn new(columns: usize) -> Self {
25 Self {
26 columns: columns.max(1),
27 gap: Px(0.0),
28 }
29 }
30
31 pub fn with_gap(mut self, gap: Px) -> Self {
32 self.gap = gap;
33 self
34 }
35
36 pub fn create_node<H: UiHost>(ui: &mut UiTree<H>, grid: UniformGrid) -> NodeId {
37 ui.create_node_retained(grid)
38 }
39}
40
41impl<H: UiHost> Widget<H> for UniformGrid {
42 fn layout(&mut self, cx: &mut LayoutCx<'_, H>) -> Size {
43 let child_count = cx.children.len();
44 if child_count == 0 {
45 return cx.available;
46 }
47
48 let columns = self.columns.max(1).min(child_count);
49 let rows = child_count.div_ceil(columns);
50
51 let gap = self.gap.0.max(0.0);
52 let total_gap_x = gap * (columns.saturating_sub(1) as f32);
53 let total_gap_y = gap * (rows.saturating_sub(1) as f32);
54
55 let cell_w = ((cx.available.width.0 - total_gap_x) / columns.max(1) as f32).max(0.0);
56 let cell_h = ((cx.available.height.0 - total_gap_y) / rows.max(1) as f32).max(0.0);
57
58 let origin = cx.bounds.origin;
59 let is_final = cx.pass_kind == LayoutPassKind::Final;
60
61 for (i, child) in cx.children.iter().copied().enumerate() {
62 let col = i % columns;
63 let row = i / columns;
64
65 let x = origin.x.0 + (cell_w + gap) * (col as f32);
66 let y = origin.y.0 + (cell_h + gap) * (row as f32);
67
68 let rect = Rect::new(
69 fret_core::Point::new(Px(x), Px(y)),
70 Size::new(Px(cell_w), Px(cell_h)),
71 );
72
73 if is_final {
74 let _ = cx.layout_viewport_root(child, rect);
75 } else {
76 let _ = cx.layout_in(child, rect);
77 }
78 }
79
80 cx.available
81 }
82}
83
84#[derive(Debug, Default, Clone, Copy)]
88pub struct FillStack;
89
90impl FillStack {
91 pub fn create_node<H: UiHost>(ui: &mut UiTree<H>) -> NodeId {
92 ui.create_node_retained(Self)
93 }
94}
95
96impl<H: UiHost> Widget<H> for FillStack {
97 fn layout(&mut self, cx: &mut LayoutCx<'_, H>) -> Size {
98 let is_final = cx.pass_kind == LayoutPassKind::Final;
99 let rect = cx.bounds;
100 for child in cx.children.iter().copied() {
101 if is_final {
102 let _ = cx.layout_viewport_root(child, rect);
103 } else {
104 let _ = cx.layout_in(child, rect);
105 }
106 }
107 cx.available
108 }
109}
110
111#[derive(Debug, Clone)]
112pub struct MultiGridChartCanvasNodes {
113 pub root: NodeId,
114 pub canvases: Vec<(GridId, NodeId)>,
115}
116
117fn collect_grids(spec: &ChartSpec) -> Vec<GridId> {
118 if !spec.grids.is_empty() {
119 return spec.grids.iter().map(|g| g.id).collect();
120 }
121
122 let mut ids: BTreeSet<GridId> = spec.axes.iter().map(|a| a.grid).collect();
123 if ids.is_empty() {
124 ids.insert(GridId::new(1));
125 }
126 ids.into_iter().collect()
127}
128
129pub fn create_multi_grid_chart_canvas_nodes<H: UiHost>(
130 ui: &mut UiTree<H>,
131 spec: ChartSpec,
132 datasets: &[(DatasetId, DataTable)],
133 layout: UniformGrid,
134) -> Result<MultiGridChartCanvasNodes, ModelError> {
135 let grids = collect_grids(&spec);
136 if grids.len() <= 1 {
137 let mut canvas = ChartCanvas::new(spec)?;
138 for (dataset_id, table) in datasets {
139 canvas
140 .engine_mut()
141 .datasets_mut()
142 .insert(*dataset_id, table.clone());
143 }
144 let node = ChartCanvas::create_node(ui, canvas);
145 let root = UniformGrid::create_node(ui, layout);
146 ui.add_child(root, node);
147 return Ok(MultiGridChartCanvasNodes {
148 root,
149 canvases: vec![(grids.first().copied().unwrap_or(GridId::new(1)), node)],
150 });
151 }
152
153 let mut spec = spec;
154 spec.axis_pointer.get_or_insert_with(Default::default);
155 let engine = Rc::new(RefCell::new(ChartEngine::new(spec)?));
156 {
157 let mut engine = engine.borrow_mut();
158 for (dataset_id, table) in datasets {
159 engine.datasets_mut().insert(*dataset_id, table.clone());
160 }
161 }
162
163 let mut canvases: Vec<(GridId, NodeId)> = Vec::with_capacity(grids.len());
164 for grid in grids {
165 let canvas = ChartCanvas::new_grid_view(engine.clone(), grid);
166 let node = ChartCanvas::create_node(ui, canvas);
167 canvases.push((grid, node));
168 }
169
170 let grid_root = UniformGrid::create_node(ui, layout);
171 for (_, node) in &canvases {
172 ui.add_child(grid_root, *node);
173 }
174
175 let overlay = ChartCanvas::create_node(ui, ChartCanvas::new_overlay(engine.clone()));
176
177 let root = FillStack::create_node(ui);
178 ui.add_child(root, grid_root);
179 ui.add_child(root, overlay);
180
181 Ok(MultiGridChartCanvasNodes { root, canvases })
182}