Skip to main content

hadrone_core/
responsive.rs

1//! Breakpoint selection and proportional layout scaling (RGL-style).
2
3use crate::LayoutItem;
4use serde::{Deserialize, Serialize};
5
6/// Describes one responsive breakpoint (usually paired with CSS media queries).
7#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
8pub struct BreakpointSpec {
9    pub name: String,
10    /// Minimum container width in px for this breakpoint to apply.
11    pub min_width_px: i32,
12    pub cols: i32,
13}
14
15/// Pick the breakpoint with the largest `min_width_px` such that `container_width_px >= min_width_px`.
16/// `breakpoints` should be sorted by `min_width_px` ascending.
17pub fn select_breakpoint(
18    breakpoints: &[BreakpointSpec],
19    container_width_px: i32,
20) -> Option<&BreakpointSpec> {
21    let mut best: Option<&BreakpointSpec> = None;
22    for bp in breakpoints {
23        if container_width_px >= bp.min_width_px
24            && best.is_none_or(|b| bp.min_width_px >= b.min_width_px)
25        {
26            best = Some(bp);
27        }
28    }
29    best
30}
31
32/// Scale `x`/`w` when column count changes (React-Grid-Layout style).
33/// `h`/`y` are left unchanged; run a [`crate::Compactor`] afterward if needed.
34pub fn scale_layout_cols(items: &[LayoutItem], from_cols: i32, to_cols: i32) -> Vec<LayoutItem> {
35    if from_cols < 1 || to_cols < 1 || from_cols == to_cols {
36        return items.to_vec();
37    }
38    let ratio = to_cols as f64 / from_cols as f64;
39    items
40        .iter()
41        .map(|it| {
42            let mut n = it.clone();
43            n.x = (it.x as f64 * ratio).round() as i32;
44            n.w = ((it.w as f64 * ratio).round() as i32).max(1);
45            n.x = n.x.max(0).min((to_cols - n.w).max(0));
46            n
47        })
48        .collect()
49}