use egui::{Rect, Vec2, pos2};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SplitAxis {
Horizontal,
Vertical,
}
impl SplitAxis {
pub(crate) fn along(self, v: Vec2) -> f32 {
match self {
SplitAxis::Horizontal => v.x,
SplitAxis::Vertical => v.y,
}
}
pub(crate) fn vec(self, amount: f32) -> Vec2 {
match self {
SplitAxis::Horizontal => Vec2::new(amount, 0.0),
SplitAxis::Vertical => Vec2::new(0.0, amount),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SplitSize {
Ratio(f32),
FixedFirst(f32),
FixedSecond(f32),
}
impl SplitSize {
fn payload(self) -> f32 {
match self {
SplitSize::Ratio(v) | SplitSize::FixedFirst(v) | SplitSize::FixedSecond(v) => v,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SplitLayout {
pub first: Rect,
pub divider: Rect,
pub second: Rect,
first_size: f32,
available: f32,
}
impl SplitLayout {
pub fn first_size(self) -> f32 {
self.first_size
}
pub fn available(self) -> f32 {
self.available
}
pub fn resized(self, size: SplitSize, delta: f32, minimum: f32) -> Option<SplitSize> {
if !size.payload().is_finite() || !delta.is_finite() || !minimum.is_finite() || minimum < 0.0 {
return None;
}
let space = f64::from(self.available);
if space <= 0.0 {
return None;
}
let low = f64::from(minimum).min(space / 2.0);
let first = (f64::from(self.first_size) + f64::from(delta)).clamp(low, space - low);
Some(match size {
SplitSize::Ratio(_) => SplitSize::Ratio((first / space) as f32),
SplitSize::FixedFirst(_) => SplitSize::FixedFirst(first as f32),
SplitSize::FixedSecond(_) => SplitSize::FixedSecond((space - first) as f32),
})
}
}
pub fn split_rect(rect: Rect, axis: SplitAxis, size: SplitSize, gap: f32, minimum: f32) -> Option<SplitLayout> {
let finite = rect.min.is_finite() && rect.max.is_finite() && rect.width().is_finite() && rect.height().is_finite();
if !finite || rect.min.x > rect.max.x || rect.min.y > rect.max.y {
return None;
}
if !gap.is_finite() || gap < 0.0 || !minimum.is_finite() || minimum < 0.0 || !size.payload().is_finite() {
return None;
}
let (start, end) = match axis {
SplitAxis::Horizontal => (rect.min.x, rect.max.x),
SplitAxis::Vertical => (rect.min.y, rect.max.y),
};
let total = f64::from(end) - f64::from(start);
let gap = f64::from(gap).min(total);
let space = total - gap;
let wanted = match size {
SplitSize::Ratio(r) => space * f64::from(r),
SplitSize::FixedFirst(v) => f64::from(v),
SplitSize::FixedSecond(v) => space - f64::from(v),
};
let low = f64::from(minimum).min(space / 2.0);
let first = wanted.clamp(low, space - low);
let place = |offset: f64| ((f64::from(start) + offset) as f32).clamp(start, end);
let first_end = place(first);
let divider_end = place(first + gap).max(first_end);
let span = |a: f32, b: f32| match axis {
SplitAxis::Horizontal => Rect::from_min_max(pos2(a, rect.min.y), pos2(b, rect.max.y)),
SplitAxis::Vertical => Rect::from_min_max(pos2(rect.min.x, a), pos2(rect.max.x, b)),
};
Some(SplitLayout {
first: span(start, first_end),
divider: span(first_end, divider_end),
second: span(divider_end, end),
first_size: first as f32,
available: space as f32,
})
}