scirs2_metrics/visualization/advanced_interactive/
layout.rs1#![allow(clippy::too_many_arguments)]
7#![allow(dead_code)]
8
9use super::core::{LayoutConfig, Position, Size};
10use super::widgets::WidgetConfig;
11use crate::error::{MetricsError, Result};
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15#[derive(Debug)]
17pub struct LayoutManager {
18 config: LayoutConfig,
20 widget_layouts: HashMap<String, WidgetLayout>,
22 container_constraints: ContainerConstraints,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct WidgetLayout {
29 pub widget_id: String,
31 pub position: Position,
33 pub size: Size,
35 pub constraints: LayoutConstraints,
37 pub grid_position: Option<GridPosition>,
39 pub z_index: i32,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct LayoutConstraints {
46 pub min_size: Size,
48 pub max_size: Size,
50 pub aspect_ratio: Option<f64>,
52 pub fixed_width: Option<f64>,
54 pub fixed_height: Option<f64>,
56 pub margin: Margin,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct GridPosition {
63 pub col_start: u32,
65 pub col_span: u32,
67 pub row_start: u32,
69 pub row_span: u32,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct Margin {
76 pub top: f64,
78 pub right: f64,
80 pub bottom: f64,
82 pub left: f64,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct ContainerConstraints {
89 pub width: f64,
91 pub height: f64,
93 pub padding: Margin,
95 pub min_widget_size: Size,
97 pub max_widget_size: Size,
99}
100
101impl LayoutManager {
102 pub fn new(config: LayoutConfig, container_constraints: ContainerConstraints) -> Self {
104 Self {
105 config,
106 widget_layouts: HashMap::new(),
107 container_constraints,
108 }
109 }
110
111 pub fn add_widget(&mut self, widget_config: &WidgetConfig) -> Result<()> {
113 let layout = WidgetLayout {
114 widget_id: widget_config.id.clone(),
115 position: widget_config.position.clone(),
116 size: widget_config.size.clone(),
117 constraints: LayoutConstraints::from_widget_config(widget_config),
118 grid_position: None,
119 z_index: widget_config.z_index,
120 };
121
122 self.widget_layouts.insert(widget_config.id.clone(), layout);
123 self.update_layout()?;
124 Ok(())
125 }
126
127 pub fn remove_widget(&mut self, widget_id: &str) -> Result<()> {
129 self.widget_layouts.remove(widget_id);
130 self.update_layout()?;
131 Ok(())
132 }
133
134 pub fn update_layout(&mut self) -> Result<()> {
136 match &self.config.layout_type {
137 super::core::LayoutType::Grid => self.update_grid_layout(),
138 super::core::LayoutType::Fixed => Ok(()), super::core::LayoutType::Flexbox => self.update_flexbox_layout(),
140 super::core::LayoutType::Masonry => self.update_masonry_layout(),
141 super::core::LayoutType::Custom(_) => self.update_custom_layout(),
142 }
143 }
144
145 fn update_grid_layout(&mut self) -> Result<()> {
147 if let Some(grid_config) = &self.config.grid_config {
148 let cell_width = (self.container_constraints.width
149 - (grid_config.column_gap * (grid_config.columns - 1)) as f64)
150 / grid_config.columns as f64;
151 let cell_height = (self.container_constraints.height
152 - (grid_config.row_gap * (grid_config.rows - 1)) as f64)
153 / grid_config.rows as f64;
154
155 let mut col = 0;
157 let mut row = 0;
158
159 for layout in self.widget_layouts.values_mut() {
160 layout.grid_position = Some(GridPosition {
161 col_start: col,
162 col_span: 1,
163 row_start: row,
164 row_span: 1,
165 });
166
167 layout.position = Position {
168 x: col as f64 * (cell_width + grid_config.column_gap as f64),
169 y: row as f64 * (cell_height + grid_config.row_gap as f64),
170 };
171
172 layout.size = Size {
173 width: cell_width,
174 height: cell_height,
175 };
176
177 col += 1;
178 if col >= grid_config.columns {
179 col = 0;
180 row += 1;
181 }
182 }
183 }
184 Ok(())
185 }
186
187 fn update_flexbox_layout(&mut self) -> Result<()> {
189 let widget_count = self.widget_layouts.len() as f64;
191 if widget_count == 0.0 {
192 return Ok(());
193 }
194
195 let available_width = self.container_constraints.width;
196 let widget_width = available_width / widget_count;
197
198 let mut x = 0.0;
199 for layout in self.widget_layouts.values_mut() {
200 layout.position.x = x;
201 layout.position.y = 0.0;
202 layout.size.width = widget_width;
203 layout.size.height = self.container_constraints.height;
204 x += widget_width;
205 }
206
207 Ok(())
208 }
209
210 fn update_masonry_layout(&mut self) -> Result<()> {
212 let columns = 3; let column_width = self.container_constraints.width / columns as f64;
215 let mut column_heights = vec![0.0; columns];
216
217 for layout in self.widget_layouts.values_mut() {
218 let shortest_col = column_heights
220 .iter()
221 .enumerate()
222 .min_by(|a, b| a.1.partial_cmp(b.1).unwrap())
223 .map(|(i, _)| i)
224 .unwrap_or(0);
225
226 layout.position.x = shortest_col as f64 * column_width;
227 layout.position.y = column_heights[shortest_col];
228 layout.size.width = column_width;
229
230 column_heights[shortest_col] += layout.size.height;
231 }
232
233 Ok(())
234 }
235
236 fn update_custom_layout(&mut self) -> Result<()> {
238 Ok(())
240 }
241
242 pub fn get_widget_layout(&self, widget_id: &str) -> Option<&WidgetLayout> {
244 self.widget_layouts.get(widget_id)
245 }
246
247 pub fn update_container_constraints(
249 &mut self,
250 constraints: ContainerConstraints,
251 ) -> Result<()> {
252 self.container_constraints = constraints;
253 self.update_layout()
254 }
255}
256
257impl LayoutConstraints {
258 pub fn from_widget_config(config: &WidgetConfig) -> Self {
260 Self {
261 min_size: Size {
262 width: 50.0,
263 height: 50.0,
264 },
265 max_size: Size {
266 width: f64::INFINITY,
267 height: f64::INFINITY,
268 },
269 aspect_ratio: None,
270 fixed_width: None,
271 fixed_height: None,
272 margin: Margin::default(),
273 }
274 }
275}
276
277impl Default for LayoutConstraints {
278 fn default() -> Self {
279 Self {
280 min_size: Size {
281 width: 50.0,
282 height: 50.0,
283 },
284 max_size: Size {
285 width: f64::INFINITY,
286 height: f64::INFINITY,
287 },
288 aspect_ratio: None,
289 fixed_width: None,
290 fixed_height: None,
291 margin: Margin::default(),
292 }
293 }
294}
295
296impl Default for Margin {
297 fn default() -> Self {
298 Self {
299 top: 0.0,
300 right: 0.0,
301 bottom: 0.0,
302 left: 0.0,
303 }
304 }
305}
306
307impl Default for ContainerConstraints {
308 fn default() -> Self {
309 Self {
310 width: 1200.0,
311 height: 800.0,
312 padding: Margin::default(),
313 min_widget_size: Size {
314 width: 50.0,
315 height: 50.0,
316 },
317 max_widget_size: Size {
318 width: 500.0,
319 height: 500.0,
320 },
321 }
322 }
323}