use crate::simulation::config::{Aspect, TerminalSizeThreshold, WindowPadding};
pub const FRAME_RING_COLS: usize = crate::config_defaults::frame_matte::DEFAULT_COLS + 1;
pub const FRAME_RING_ROWS: usize = crate::config_defaults::frame_matte::DEFAULT_ROWS + 1;
pub enum FallbackMode {
Normal,
EdgeHug,
Fullscreen,
}
pub struct WindowLayout {
pub pad: usize,
pub frame_x: usize,
pub frame_y: usize,
pub frame_w: usize,
pub frame_h: usize,
pub sim_x: usize,
pub sim_y: usize,
pub sim_w: usize,
pub sim_h: usize,
pub fallback: FallbackMode,
}
pub struct Window {
pub aspect: Aspect,
pub padding: WindowPadding,
pub ring_cols: usize,
pub ring_rows: usize,
pub min_sim_size: TerminalSizeThreshold,
pub min_frame_size: TerminalSizeThreshold,
}
impl Default for Window {
fn default() -> Self {
Self {
aspect: Aspect::default(),
padding: WindowPadding::Auto,
ring_cols: FRAME_RING_COLS,
ring_rows: FRAME_RING_ROWS,
min_sim_size: TerminalSizeThreshold {
width: 20,
height: 10,
},
min_frame_size: TerminalSizeThreshold {
width: 12,
height: 6,
},
}
}
}
impl Window {
pub fn compute_rects(&self, term_w: usize, term_h: usize) -> WindowLayout {
let pad = match self.padding {
WindowPadding::Auto => {
let min_dim = term_w.min(term_h);
((min_dim as f32 * 0.05).floor() as usize).max(2)
}
WindowPadding::Fixed(n) => n,
};
let avail_w = term_w.saturating_sub(pad * 2);
let avail_h = term_h.saturating_sub(pad * 2);
let inner_w = avail_w.saturating_sub(self.ring_cols * 2);
let inner_h = avail_h.saturating_sub(self.ring_rows * 2);
let cell_ratio = self.aspect.cell_ratio();
let (sim_w, sim_h) = if inner_w == 0 || inner_h == 0 {
(0, 0)
} else {
let w_from_h = (inner_h as f32 * cell_ratio) as usize;
if w_from_h <= inner_w {
(w_from_h, inner_h)
} else {
(inner_w, (inner_w as f32 / cell_ratio) as usize)
}
};
if sim_w < self.min_frame_size.width || sim_h < self.min_frame_size.height {
return WindowLayout {
pad: 0,
frame_x: 0,
frame_y: 0,
frame_w: term_w,
frame_h: term_h,
sim_x: 0,
sim_y: 0,
sim_w: term_w,
sim_h: term_h,
fallback: FallbackMode::Fullscreen,
};
}
if sim_w < self.min_sim_size.width || sim_h < self.min_sim_size.height {
let eh_sim_w = term_w.saturating_sub(self.ring_cols * 2);
let eh_sim_h = term_h.saturating_sub(self.ring_rows * 2);
return WindowLayout {
pad: 0,
frame_x: 0,
frame_y: 0,
frame_w: term_w,
frame_h: term_h,
sim_x: self.ring_cols,
sim_y: self.ring_rows,
sim_w: eh_sim_w,
sim_h: eh_sim_h,
fallback: FallbackMode::EdgeHug,
};
}
let frame_w = sim_w + self.ring_cols * 2;
let frame_h = sim_h + self.ring_rows * 2;
let frame_x = term_w.saturating_sub(frame_w) / 2;
let frame_y = term_h.saturating_sub(frame_h) / 2;
WindowLayout {
pad,
frame_x,
frame_y,
frame_w,
frame_h,
sim_x: frame_x + self.ring_cols,
sim_y: frame_y + self.ring_rows,
sim_w,
sim_h,
fallback: FallbackMode::Normal,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn default_window() -> Window {
Window::default()
}
#[test]
fn test_auto_padding_minimum() {
let w = default_window();
let layout = w.compute_rects(80, 40);
assert!(layout.pad >= 2);
}
#[test]
fn test_auto_padding_proportional() {
let w = default_window();
let layout = w.compute_rects(200, 100);
assert_eq!(layout.pad, 5); }
#[test]
fn test_fixed_padding() {
let mut w = default_window();
w.padding = WindowPadding::Fixed(3);
let layout = w.compute_rects(80, 40);
assert_eq!(layout.pad, 3);
}
#[test]
fn test_frame_is_inside_padding() {
let w = default_window();
let layout = w.compute_rects(120, 60);
assert!(layout.frame_x >= layout.pad);
assert!(layout.frame_y >= layout.pad);
assert!(layout.frame_x + layout.frame_w <= 120 - layout.pad);
assert!(layout.frame_y + layout.frame_h <= 60 - layout.pad);
}
#[test]
fn test_sim_inside_frame() {
let w = default_window();
let layout = w.compute_rects(120, 60);
assert_eq!(layout.sim_x, layout.frame_x + FRAME_RING_COLS);
assert_eq!(layout.sim_y, layout.frame_y + FRAME_RING_ROWS);
assert_eq!(layout.sim_w, layout.frame_w - FRAME_RING_COLS * 2);
assert_eq!(layout.sim_h, layout.frame_h - FRAME_RING_ROWS * 2);
}
#[test]
fn test_aspect_ratio_respected() {
let w = default_window(); let layout = w.compute_rects(120, 60);
let ratio = layout.sim_w as f32 / layout.sim_h as f32;
assert!((ratio - 3.0).abs() <= 1.0, "ratio was {}", ratio);
}
#[test]
fn test_edge_hug_fallback() {
let mut w = default_window();
w.min_sim_size = TerminalSizeThreshold {
width: 50,
height: 30,
}; w.min_frame_size = TerminalSizeThreshold {
width: 5,
height: 3,
};
let layout = w.compute_rects(30, 15);
assert!(matches!(layout.fallback, FallbackMode::EdgeHug));
assert_eq!(layout.pad, 0);
assert_eq!(layout.frame_x, 0);
}
#[test]
fn test_fullscreen_fallback() {
let mut w = default_window();
w.min_frame_size = TerminalSizeThreshold {
width: 100,
height: 50,
};
let layout = w.compute_rects(20, 10);
assert!(matches!(layout.fallback, FallbackMode::Fullscreen));
assert_eq!(layout.sim_x, 0);
assert_eq!(layout.sim_y, 0);
assert_eq!(layout.sim_w, 20);
assert_eq!(layout.sim_h, 10);
}
}