use crate::utils::{Position, Rectangle, Size};
#[derive(Clone, Copy, Debug)]
pub enum HAnchor {
Left,
Center,
Right,
Percent(f32),
}
#[derive(Clone, Copy, Debug)]
pub enum VAnchor {
Top,
Middle,
Bottom,
Percent(f32),
}
#[derive(Clone, Copy, Debug)]
pub struct Anchors {
pub h: HAnchor,
pub v: VAnchor,
}
impl Default for Anchors {
fn default() -> Self {
Self {
h: HAnchor::Left,
v: VAnchor::Top,
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct PercentSize {
pub width_pct: f32, pub height_pct: f32, }
#[derive(Clone, Copy, Debug, Default)]
pub struct Margins {
pub left: f32,
pub right: f32,
pub top: f32,
pub bottom: f32,
}
#[derive(Default)]
pub struct LayoutParams {
pub anchors: Anchors,
pub percent: Option<PercentSize>,
pub margins: Margins,
}
pub struct LayoutResult {
pub position: Position,
pub size: Size,
}
pub fn window_bounds(width: f32, height: f32) -> Rectangle {
Rectangle::new(0.0, 0.0, width, height)
}
pub fn layout_node(container: Rectangle, desired: Size, params: LayoutParams) -> LayoutResult {
let size = if let Some(p) = params.percent {
Size {
width: container.width * p.width_pct,
height: container.height * p.height_pct,
}
} else {
desired
};
let content = Rectangle::new(
container.x + params.margins.left,
container.y + params.margins.top,
(container.width - params.margins.left - params.margins.right).max(0.0),
(container.height - params.margins.top - params.margins.bottom).max(0.0),
);
let x = match params.anchors.h {
HAnchor::Left => content.x,
HAnchor::Center => content.x + (content.width - size.width) * 0.5,
HAnchor::Right => content.x + content.width - size.width,
HAnchor::Percent(pct) => {
let pct = pct.clamp(0.0, 1.0);
let center_x = content.x + content.width * pct;
center_x - size.width * 0.5
}
};
let y = match params.anchors.v {
VAnchor::Top => content.y,
VAnchor::Middle => content.y + (content.height - size.height) * 0.5,
VAnchor::Bottom => content.y + content.height - size.height,
VAnchor::Percent(pct) => {
let pct = pct.clamp(0.0, 1.0);
let center_y = content.y + content.height * pct;
center_y - size.height * 0.5
}
};
LayoutResult {
position: Position { x, y },
size,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn center_percent_layout() {
let container = Rectangle::new(0.0, 0.0, 200.0, 100.0);
let desired = Size {
width: 50.0,
height: 20.0,
};
let params = LayoutParams {
anchors: Anchors {
h: HAnchor::Center,
v: VAnchor::Middle,
},
percent: Some(PercentSize {
width_pct: 0.5,
height_pct: 0.5,
}),
margins: Margins::default(),
};
let out = layout_node(container, desired, params);
assert!((out.size.width - 100.0).abs() < 1e-3);
assert!((out.size.height - 50.0).abs() < 1e-3);
assert!((out.position.x - 50.0).abs() < 1e-3);
assert!((out.position.y - 25.0).abs() < 1e-3);
}
#[test]
fn percent_anchor_positioning() {
let container = Rectangle::new(0.0, 0.0, 300.0, 200.0);
let desired = Size {
width: 100.0,
height: 50.0,
};
let params = LayoutParams {
anchors: Anchors {
h: HAnchor::Percent(1.0 / 3.0),
v: VAnchor::Percent(0.5),
},
percent: None,
margins: Margins::default(),
};
let out = layout_node(container, desired, params);
assert!((out.position.x - 50.0).abs() < 1e-3);
assert!((out.position.y - 75.0).abs() < 1e-3);
assert!((out.size.width - 100.0).abs() < 1e-3);
assert!((out.size.height - 50.0).abs() < 1e-3);
}
#[test]
fn percent_anchor_with_margins() {
let container = Rectangle::new(0.0, 0.0, 400.0, 300.0);
let desired = Size {
width: 80.0,
height: 40.0,
};
let params = LayoutParams {
anchors: Anchors {
h: HAnchor::Percent(0.5),
v: VAnchor::Percent(0.75),
},
percent: None,
margins: Margins {
left: 20.0,
right: 20.0,
top: 10.0,
bottom: 10.0,
},
};
let out = layout_node(container, desired, params);
assert!((out.position.x - 160.0).abs() < 1e-3);
assert!((out.position.y - 200.0).abs() < 1e-3);
}
}