use ratatui::layout::{Direction, Rect};
use super::minimal::{self, MinimalOptions};
use super::sidebar::{self, SidebarOptions, SidebarWidth};
use super::split::{self, SplitOptions};
use super::standard::{self, StandardOptions};
use super::types::{LayoutContext, LayoutFn, LayoutProvider, LayoutResult, WidgetSizes};
pub enum LayoutTemplate {
Standard(StandardOptions),
Sidebar(SidebarOptions),
Split(SplitOptions),
Minimal(MinimalOptions),
Custom(Box<dyn LayoutProvider>),
CustomFn(LayoutFn),
}
impl LayoutTemplate {
pub fn standard() -> Self {
Self::Standard(StandardOptions::default())
}
pub fn with_panels() -> Self {
Self::Standard(StandardOptions::default())
}
pub fn with_sidebar(sidebar_widget_id: &'static str, width: impl Into<SidebarWidth>) -> Self {
Self::Sidebar(SidebarOptions {
sidebar_widget_id,
sidebar_width: width.into(),
..Default::default()
})
}
pub fn minimal() -> Self {
Self::Minimal(MinimalOptions::default())
}
pub fn split_horizontal(left_widget_id: &'static str, right_widget_id: &'static str) -> Self {
Self::Split(SplitOptions {
direction: Direction::Horizontal,
first_widget_id: left_widget_id,
second_widget_id: right_widget_id,
..Default::default()
})
}
pub fn split_vertical(top_widget_id: &'static str, bottom_widget_id: &'static str) -> Self {
Self::Split(SplitOptions {
direction: Direction::Vertical,
first_widget_id: top_widget_id,
second_widget_id: bottom_widget_id,
..Default::default()
})
}
pub fn custom<P: LayoutProvider>(provider: P) -> Self {
Self::Custom(Box::new(provider))
}
pub fn custom_fn<F>(f: F) -> Self
where
F: Fn(Rect, &LayoutContext, &WidgetSizes) -> LayoutResult + Send + Sync + 'static,
{
Self::CustomFn(Box::new(f))
}
pub fn compute(&self, ctx: &LayoutContext, sizes: &WidgetSizes) -> LayoutResult {
match self {
Self::Standard(opts) => standard::compute(ctx, sizes, opts),
Self::Sidebar(opts) => sidebar::compute(ctx, sizes, opts),
Self::Split(opts) => split::compute(ctx, sizes, opts),
Self::Minimal(opts) => minimal::compute(ctx, sizes, opts),
Self::Custom(provider) => provider.compute(ctx, sizes),
Self::CustomFn(f) => f(ctx.frame_area, ctx, sizes),
}
}
}
impl Default for LayoutTemplate {
fn default() -> Self {
Self::with_panels()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::layout::helpers;
use crate::themes::Theme;
use ratatui::layout::{Constraint, Rect};
use std::collections::{HashMap, HashSet};
fn test_context(area: Rect) -> LayoutContext<'static> {
static THEME: std::sync::LazyLock<Theme> = std::sync::LazyLock::new(Theme::default);
LayoutContext {
frame_area: area,
show_throbber: false,
input_visual_lines: 1,
theme: &THEME,
active_widgets: HashSet::new(),
}
}
fn test_sizes() -> WidgetSizes {
WidgetSizes {
heights: HashMap::new(),
is_active: HashMap::new(),
}
}
#[test]
fn test_custom_fn_layout() {
let area = Rect::new(0, 0, 80, 24);
let ctx = test_context(area);
let sizes = test_sizes();
let template = LayoutTemplate::custom_fn(|area, _ctx, _sizes| {
let chunks = helpers::vstack(
area,
&[Constraint::Percentage(80), Constraint::Percentage(20)],
);
let mut result = LayoutResult::default();
result.widget_areas.insert("custom_main", chunks[0]);
result.widget_areas.insert("custom_footer", chunks[1]);
result.render_order = vec!["custom_main", "custom_footer"];
result
});
let result = template.compute(&ctx, &sizes);
assert!(result.widget_areas.contains_key("custom_main"));
assert!(result.widget_areas.contains_key("custom_footer"));
}
}