use ratatui::layout::{Constraint, Direction, Layout, Rect};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppLayout {
pub main_view: Rect,
pub control_bar: Rect,
pub debug: Option<Rect>,
}
pub fn app_layout(area: Rect, debug_enabled: bool) -> AppLayout {
let mut constraints = vec![Constraint::Fill(1), Constraint::Length(1)];
if debug_enabled {
constraints.push(Constraint::Length(1));
}
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints(constraints)
.split(area);
let main_view = layout[0];
let control_bar_idx = layout.len() - if debug_enabled { 2 } else { 1 };
let control_bar = layout[control_bar_idx];
let debug = if debug_enabled {
Some(layout[layout.len() - 1])
} else {
None
};
AppLayout {
main_view,
control_bar,
debug,
}
}
pub fn centered_rect(r: Rect, percent_x: u16, percent_y: u16) -> Rect {
let popup_layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage((100 - percent_y) / 2),
Constraint::Percentage(percent_y),
Constraint::Percentage((100 - percent_y) / 2),
])
.split(r);
Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2),
])
.split(popup_layout[1])[1]
}
pub fn centered_rect_fixed(r: Rect, width: u16, height: u16) -> Rect {
let w = width.min(r.width);
let h = height.min(r.height);
let x = r.x + r.width.saturating_sub(w) / 2;
let y = r.y + r.height.saturating_sub(h) / 2;
Rect {
x,
y,
width: w,
height: h,
}
}
pub fn centered_rect_with_min(
r: Rect,
percent_x: u16,
percent_y: u16,
min_width: u16,
min_height: u16,
) -> Rect {
let inner = centered_rect(r, percent_x, percent_y);
Rect {
x: inner.x,
y: inner.y,
width: inner.width.max(min_width),
height: inner.height.max(min_height),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_app_layout_minimal() {
let area = Rect::new(0, 0, 100, 50);
let layout = app_layout(area, false);
assert_eq!(layout.main_view.height, 49);
assert_eq!(layout.control_bar.height, 1);
assert_eq!(layout.control_bar.y, 49);
assert_eq!(layout.debug, None);
}
#[test]
fn test_app_layout_with_debug() {
let area = Rect::new(0, 0, 100, 50);
let layout = app_layout(area, true);
assert_eq!(layout.main_view.height, 48);
assert_eq!(layout.control_bar.height, 1);
assert_eq!(layout.control_bar.y, 48);
assert!(layout.debug.is_some());
assert_eq!(layout.debug.unwrap().height, 1);
assert_eq!(layout.debug.unwrap().y, 49);
}
#[test]
fn test_centered_rect_50_50() {
let area = Rect::new(0, 0, 100, 100);
let centered = centered_rect(area, 50, 50);
assert_eq!(centered.width, 50);
assert_eq!(centered.height, 50);
assert_eq!(centered.x, 25);
assert_eq!(centered.y, 25);
}
#[test]
fn test_centered_rect_full_coverage() {
let area = Rect::new(0, 0, 100, 100);
let centered = centered_rect(area, 100, 100);
assert_eq!(centered.width, 100);
assert_eq!(centered.height, 100);
assert_eq!(centered.x, 0);
assert_eq!(centered.y, 0);
}
#[test]
fn test_centered_rect_small_area() {
let area = Rect::new(10, 10, 20, 20);
let centered = centered_rect(area, 50, 50);
assert_eq!(centered.width, 10);
assert_eq!(centered.height, 10);
assert_eq!(centered.x, 15);
assert_eq!(centered.y, 15);
}
#[test]
fn test_centered_rect_with_min_enforcement() {
let area = Rect::new(0, 0, 100, 100);
let centered = centered_rect_with_min(area, 10, 10, 50, 50);
assert!(centered.width >= 50);
assert!(centered.height >= 50);
}
#[test]
fn test_centered_rect_with_min_no_enforcement_needed() {
let area = Rect::new(0, 0, 100, 100);
let centered = centered_rect_with_min(area, 80, 80, 50, 50);
assert_eq!(centered.width, 80);
assert_eq!(centered.height, 80);
}
}