use crate::widgets::Rect;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum HorizontalAnchor {
Left,
Center,
Right,
Percent(f32),
Px(f32),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum VerticalAnchor {
Top,
Center,
Bottom,
Percent(f32),
Px(f32),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Pivot {
pub horizontal: HorizontalAnchor,
pub vertical: VerticalAnchor,
}
impl Pivot {
pub const CENTER: Self = Self {
horizontal: HorizontalAnchor::Center,
vertical: VerticalAnchor::Center,
};
pub const TOP_LEFT: Self = Self {
horizontal: HorizontalAnchor::Left,
vertical: VerticalAnchor::Top,
};
pub const TOP: Self = Self {
horizontal: HorizontalAnchor::Center,
vertical: VerticalAnchor::Top,
};
pub const TOP_RIGHT: Self = Self {
horizontal: HorizontalAnchor::Right,
vertical: VerticalAnchor::Top,
};
pub const LEFT: Self = Self {
horizontal: HorizontalAnchor::Left,
vertical: VerticalAnchor::Center,
};
pub const RIGHT: Self = Self {
horizontal: HorizontalAnchor::Right,
vertical: VerticalAnchor::Center,
};
pub const BOTTOM_LEFT: Self = Self {
horizontal: HorizontalAnchor::Left,
vertical: VerticalAnchor::Bottom,
};
pub const BOTTOM: Self = Self {
horizontal: HorizontalAnchor::Center,
vertical: VerticalAnchor::Bottom,
};
pub const BOTTOM_RIGHT: Self = Self {
horizontal: HorizontalAnchor::Right,
vertical: VerticalAnchor::Bottom,
};
pub fn new(horizontal: HorizontalAnchor, vertical: VerticalAnchor) -> Self {
Self {
horizontal,
vertical,
}
}
pub fn percent(x_percent: f32, y_percent: f32) -> Self {
Self {
horizontal: HorizontalAnchor::Percent(x_percent),
vertical: VerticalAnchor::Percent(y_percent),
}
}
pub fn px(x: f32, y: f32) -> Self {
Self {
horizontal: HorizontalAnchor::Px(x),
vertical: VerticalAnchor::Px(y),
}
}
pub fn resolve(&self, bounds: Rect) -> (f32, f32) {
let x = match self.horizontal {
HorizontalAnchor::Left => bounds.x,
HorizontalAnchor::Center => bounds.x + bounds.width / 2.0,
HorizontalAnchor::Right => bounds.x + bounds.width,
HorizontalAnchor::Percent(p) => bounds.x + bounds.width * (p / 100.0),
HorizontalAnchor::Px(px) => bounds.x + px,
};
let y = match self.vertical {
VerticalAnchor::Top => bounds.y,
VerticalAnchor::Center => bounds.y + bounds.height / 2.0,
VerticalAnchor::Bottom => bounds.y + bounds.height,
VerticalAnchor::Percent(p) => bounds.y + bounds.height * (p / 100.0),
VerticalAnchor::Px(px) => bounds.y + px,
};
(x, y)
}
}
impl Default for Pivot {
fn default() -> Self {
Self::CENTER
}
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: f32, b: f32) -> bool {
(a - b).abs() < 1e-5
}
#[test]
fn test_constants() {
let bounds = Rect::new(100.0, 50.0, 200.0, 100.0);
let (x, y) = Pivot::CENTER.resolve(bounds);
assert!(approx_eq(x, 200.0));
assert!(approx_eq(y, 100.0));
let (x, y) = Pivot::TOP_LEFT.resolve(bounds);
assert!(approx_eq(x, 100.0));
assert!(approx_eq(y, 50.0));
let (x, y) = Pivot::TOP.resolve(bounds);
assert!(approx_eq(x, 200.0));
assert!(approx_eq(y, 50.0));
let (x, y) = Pivot::TOP_RIGHT.resolve(bounds);
assert!(approx_eq(x, 300.0));
assert!(approx_eq(y, 50.0));
let (x, y) = Pivot::LEFT.resolve(bounds);
assert!(approx_eq(x, 100.0));
assert!(approx_eq(y, 100.0));
let (x, y) = Pivot::RIGHT.resolve(bounds);
assert!(approx_eq(x, 300.0));
assert!(approx_eq(y, 100.0));
let (x, y) = Pivot::BOTTOM_LEFT.resolve(bounds);
assert!(approx_eq(x, 100.0));
assert!(approx_eq(y, 150.0));
let (x, y) = Pivot::BOTTOM.resolve(bounds);
assert!(approx_eq(x, 200.0));
assert!(approx_eq(y, 150.0));
let (x, y) = Pivot::BOTTOM_RIGHT.resolve(bounds);
assert!(approx_eq(x, 300.0));
assert!(approx_eq(y, 150.0));
}
#[test]
fn test_percent() {
let bounds = Rect::new(0.0, 0.0, 100.0, 200.0);
let origin = Pivot::percent(25.0, 75.0);
let (x, y) = origin.resolve(bounds);
assert!(approx_eq(x, 25.0));
assert!(approx_eq(y, 150.0));
let origin = Pivot::percent(0.0, 0.0);
let (x, y) = origin.resolve(bounds);
assert!(approx_eq(x, 0.0));
assert!(approx_eq(y, 0.0));
let origin = Pivot::percent(100.0, 100.0);
let (x, y) = origin.resolve(bounds);
assert!(approx_eq(x, 100.0));
assert!(approx_eq(y, 200.0));
}
#[test]
fn test_px() {
let bounds = Rect::new(50.0, 100.0, 200.0, 300.0);
let origin = Pivot::px(10.0, 20.0);
let (x, y) = origin.resolve(bounds);
assert!(approx_eq(x, 60.0)); assert!(approx_eq(y, 120.0)); }
#[test]
fn test_default() {
assert_eq!(Pivot::default(), Pivot::CENTER);
}
}