use eframe::egui;
use std::collections::HashMap;
pub(super) fn dimensions(count: usize, layout: &str) -> (usize, usize) {
let count = count.max(1);
match layout {
"columns" => (count, 1),
"grid" => {
let columns = (count as f32).sqrt().ceil() as usize;
(columns, count.div_ceil(columns))
}
_ if count <= 3 => (count, 1),
_ => {
let columns = (count as f32).sqrt().ceil() as usize;
(columns, count.div_ceil(columns))
}
}
}
pub(super) fn window_size(pane: egui::Vec2, count: usize, gap: f32, layout: &str) -> egui::Vec2 {
let (columns, rows) = dimensions(count, layout);
egui::vec2(
pane.x * columns as f32 + gap * columns.saturating_sub(1) as f32,
pane.y * rows as f32 + gap * rows.saturating_sub(1) as f32,
)
}
pub(super) fn pane_rects(
rect: egui::Rect,
count: usize,
gap: f32,
layout: &str,
column_weights: &[f32],
row_weights: &[f32],
) -> Vec<egui::Rect> {
let (columns, rows) = dimensions(count, layout);
let usable_width = (rect.width() - gap * columns.saturating_sub(1) as f32).max(1.0);
let usable_height = (rect.height() - gap * rows.saturating_sub(1) as f32).max(1.0);
let widths: Vec<f32> = column_weights
.iter()
.take(columns)
.map(|weight| usable_width * weight)
.collect();
let heights: Vec<f32> = row_weights
.iter()
.take(rows)
.map(|weight| usable_height * weight)
.collect();
let mut column_offsets = Vec::with_capacity(columns);
let mut row_offsets = Vec::with_capacity(rows);
let mut offset = rect.left();
for width in &widths {
column_offsets.push(offset);
offset += width + gap;
}
offset = rect.top();
for height in &heights {
row_offsets.push(offset);
offset += height + gap;
}
(0..count)
.map(|index| {
let column = index % columns;
let row = index / columns;
egui::Rect::from_min_size(
egui::pos2(column_offsets[column], row_offsets[row]),
egui::vec2(widths[column], heights[row]),
)
})
.collect()
}
pub(super) fn normalized_weights(weights: Option<&[f32]>, count: usize) -> Vec<f32> {
let count = count.max(1);
let equal = vec![1.0 / count as f32; count];
let Some(weights) = weights else {
return equal;
};
if weights.len() != count
|| weights
.iter()
.any(|weight| !weight.is_finite() || *weight <= 0.0)
{
return equal;
}
let sum: f32 = weights.iter().sum();
if !sum.is_finite() || sum <= 0.0 {
return equal;
}
weights.iter().map(|weight| weight / sum).collect()
}
pub(super) fn parse_weight_sets(value: &str) -> HashMap<usize, Vec<f32>> {
value
.split(';')
.filter_map(|set| {
let (count, values) = set.split_once(':')?;
let count = count.parse::<usize>().ok()?;
if count == 0 {
return None;
}
let weights = values
.split(',')
.map(str::parse::<f32>)
.collect::<Result<Vec<_>, _>>()
.ok()?;
if weights.len() != count
|| weights
.iter()
.any(|weight| !weight.is_finite() || *weight <= 0.0)
{
return None;
}
Some((count, normalized_weights(Some(&weights), count)))
})
.collect()
}
pub(super) fn encode_weight_sets(sets: &HashMap<usize, Vec<f32>>) -> String {
let mut entries: Vec<_> = sets.iter().collect();
entries.sort_unstable_by_key(|(count, _)| **count);
entries
.into_iter()
.map(|(count, weights)| {
let values = normalized_weights(Some(weights), *count)
.into_iter()
.map(|weight| format!("{weight:.6}"))
.collect::<Vec<_>>()
.join(",");
format!("{count}:{values}")
})
.collect::<Vec<_>>()
.join(";")
}