use ratatui::layout::{Constraint, Direction as RatatuiDirection, Layout, Rect};
use crate::SkimOptions;
use crate::tui::options::TuiLayout;
use crate::tui::statusline::InfoDisplay;
use crate::tui::{Direction, Size};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PreviewPlacement {
Left,
Right,
Up,
Down,
None,
}
#[derive(Debug, Clone)]
pub struct LayoutTemplate {
show_header: bool,
preview_placement: PreviewPlacement,
work_layout_reversed: bool,
preview_layout: Option<Layout>,
work_layout: Layout,
}
impl LayoutTemplate {
#[must_use]
pub fn from_options(options: &SkimOptions, header_height: u16) -> Self {
let has_border = options.border.is_some();
let input_rows: u16 = if has_border {
3 } else {
1 + u16::from(options.info.display == InfoDisplay::Default)
};
let show_header = options.header.is_some() || options.header_lines > 0;
let header_rows: u16 = if show_header {
if has_border { header_height + 2 } else { header_height }
} else {
0
};
let preview_visible = (options.preview.is_some() || options.preview_fn.is_some())
&& !options.preview_window.hidden
&& !matches!(options.preview_window.size, Size::Fixed(0));
let (preview_placement, preview_layout) = if preview_visible {
let (preview_c, rest_c) = size_to_constraint(options.preview_window.size);
let placement = match options.preview_window.direction {
Direction::Left => PreviewPlacement::Left,
Direction::Right => PreviewPlacement::Right,
Direction::Up => PreviewPlacement::Up,
Direction::Down => PreviewPlacement::Down,
};
let layout = match placement {
PreviewPlacement::Left => Layout::new(RatatuiDirection::Horizontal, [preview_c, rest_c]),
PreviewPlacement::Right => Layout::new(RatatuiDirection::Horizontal, [rest_c, preview_c]),
PreviewPlacement::Up => Layout::new(RatatuiDirection::Vertical, [preview_c, rest_c]),
PreviewPlacement::Down => Layout::new(RatatuiDirection::Vertical, [rest_c, preview_c]),
PreviewPlacement::None => unreachable!(),
};
(placement, Some(layout))
} else {
(PreviewPlacement::None, None)
};
let non_list_rows = input_rows + header_rows;
let work_layout_reversed = options.layout == TuiLayout::Reverse;
let work_layout = if show_header {
match options.layout {
TuiLayout::Default | TuiLayout::ReverseList => Layout::vertical([
Constraint::Fill(1),
Constraint::Length(header_rows),
Constraint::Length(input_rows),
]),
TuiLayout::Reverse => Layout::vertical([
Constraint::Length(input_rows),
Constraint::Length(header_rows),
Constraint::Fill(1),
]),
}
} else {
match options.layout {
TuiLayout::Default | TuiLayout::ReverseList => Layout::vertical([
Constraint::Fill(1),
Constraint::Length(0),
Constraint::Length(non_list_rows),
]),
TuiLayout::Reverse => Layout::vertical([
Constraint::Length(non_list_rows),
Constraint::Length(0),
Constraint::Fill(1),
]),
}
};
Self {
show_header,
preview_placement,
work_layout_reversed,
preview_layout,
work_layout,
}
}
#[must_use]
pub fn apply(&self, area: Rect) -> AppLayout {
let (work_area, preview_area): (Rect, Option<Rect>) = match &self.preview_layout {
Some(layout) => {
let [a, b]: [Rect; 2] = layout.areas(area);
match self.preview_placement {
PreviewPlacement::Left | PreviewPlacement::Up => (b, Some(a)),
_ => (a, Some(b)),
}
}
None => (area, None),
};
let [slot0, slot1, slot2]: [Rect; 3] = self.work_layout.areas(work_area);
let (list_area, header_slot, input_area) = if self.work_layout_reversed {
(slot2, slot1, slot0)
} else {
(slot0, slot1, slot2)
};
let header_area = if self.show_header { Some(header_slot) } else { None };
AppLayout {
list_area,
input_area,
header_area,
preview_area,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AppLayout {
pub list_area: Rect,
pub input_area: Rect,
pub header_area: Option<Rect>,
pub preview_area: Option<Rect>,
}
impl AppLayout {
#[must_use]
pub fn compute(area: Rect, options: &SkimOptions, header_height: u16) -> Self {
LayoutTemplate::from_options(options, header_height).apply(area)
}
}
fn size_to_constraint(size: Size) -> (Constraint, Constraint) {
match size {
Size::Fixed(n) => (Constraint::Length(n), Constraint::Fill(1)),
Size::Percent(p) => (Constraint::Percentage(p), Constraint::Fill(1)),
Size::Neg(n) => (Constraint::Fill(1), Constraint::Length(n)),
}
}
#[cfg(test)]
#[path = "layout_tests.rs"]
mod tests;