arreliny-term 0.1.1

A simple terminal for keeping multiple shells in one window
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(";")
}