use ratatui_core::layout::{Position, Rect, Size};
use ratatui_widgets::block::Block;
use std::borrow::Cow;
use std::collections::HashMap;
use std::hash::Hash;
#[derive(Debug, Clone)]
pub struct GenericLayout<W>
where
W: Eq + Hash + Clone,
{
area: Rect,
page_size: Size,
page_count: usize,
widgets: HashMap<W, usize>,
rwidgets: HashMap<usize, W>,
widget_areas: Vec<Rect>,
labels: Vec<Option<Cow<'static, str>>>,
label_areas: Vec<Rect>,
block_areas: Vec<Rect>,
blocks: Vec<Option<Block<'static>>>,
}
impl<W> Default for GenericLayout<W>
where
W: Eq + Hash + Clone,
{
fn default() -> Self {
Self {
area: Default::default(),
page_size: Size::new(u16::MAX, u16::MAX),
page_count: 1,
widgets: Default::default(),
rwidgets: Default::default(),
widget_areas: Default::default(),
labels: Default::default(),
label_areas: Default::default(),
block_areas: Default::default(),
blocks: Default::default(),
}
}
}
impl<W> GenericLayout<W>
where
W: Eq + Hash + Clone,
{
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(num_widgets: usize, num_blocks: usize) -> Self {
Self {
area: Default::default(),
page_size: Size::new(u16::MAX, u16::MAX),
page_count: Default::default(),
widgets: HashMap::with_capacity(num_widgets),
rwidgets: HashMap::with_capacity(num_widgets),
widget_areas: Vec::with_capacity(num_widgets),
labels: Vec::with_capacity(num_widgets),
label_areas: Vec::with_capacity(num_widgets),
block_areas: Vec::with_capacity(num_blocks),
blocks: Vec::with_capacity(num_blocks),
}
}
pub fn clear(&mut self) {
self.area = Rect::default();
self.page_size = Size::default();
self.page_count = 0;
self.widgets.clear();
self.rwidgets.clear();
self.widget_areas.clear();
self.labels.clear();
self.label_areas.clear();
self.block_areas.clear();
self.blocks.clear();
}
pub fn set_area(&mut self, area: Rect) {
self.area = area;
}
pub fn area(&self) -> Rect {
self.area
}
pub fn area_changed(&self, area: Rect) -> bool {
self.area != area
}
pub fn set_page_size(&mut self, size: Size) {
self.page_size = size;
}
pub fn page_size(&self) -> Size {
self.page_size
}
pub fn size_changed(&self, size: Size) -> bool {
self.page_size != size
}
pub fn set_page_count(&mut self, page_count: usize) {
self.page_count = page_count;
}
pub fn page_count(&self) -> usize {
self.page_count
}
pub fn add(
&mut self, key: W,
area: Rect,
label: Option<Cow<'static, str>>,
label_area: Rect,
) {
let idx = self.widget_areas.len();
self.widgets.insert(key.clone(), idx);
self.rwidgets.insert(idx, key);
self.widget_areas.push(area);
self.labels.push(label);
self.label_areas.push(label_area);
}
pub fn add_block(
&mut self, area: Rect,
block: Option<Block<'static>>,
) {
self.block_areas.push(area);
self.blocks.push(block);
}
#[inline]
pub fn place(mut self, pos: Position) -> Self {
for v in self.widget_areas.iter_mut() {
*v = place(*v, pos);
}
for v in self.label_areas.iter_mut() {
*v = place(*v, pos);
}
for v in self.block_areas.iter_mut() {
*v = place(*v, pos);
}
self
}
pub fn first(&self, page: usize) -> Option<W> {
for (idx, area) in self.widget_areas.iter().enumerate() {
let test = (area.y / self.page_size.height) as usize;
if page == test {
return self.rwidgets.get(&idx).cloned();
}
}
None
}
#[allow(clippy::question_mark)]
pub fn page_of(&self, widget: W) -> Option<usize> {
let Some(idx) = self.try_index_of(widget) else {
return None;
};
Some((self.widget_areas[idx].y / self.page_size.height) as usize)
}
pub fn is_empty(&self) -> bool {
self.widget_areas.is_empty() && self.block_areas.is_empty()
}
pub fn is_endless(&self) -> bool {
self.area.height == u16::MAX
}
#[inline]
pub fn widget_len(&self) -> usize {
self.widgets.len()
}
pub fn try_index_of(&self, widget: W) -> Option<usize> {
self.widgets.get(&widget).copied()
}
pub fn index_of(&self, widget: W) -> usize {
self.widgets.get(&widget).copied().expect("widget")
}
#[inline]
pub fn widget_key(&self, idx: usize) -> W {
self.rwidgets.get(&idx).cloned().expect("valid_idx")
}
#[inline]
pub fn widget_keys(&self) -> impl Iterator<Item = &W> {
self.widgets.keys()
}
#[inline]
pub fn label_for(&self, widget: W) -> Rect {
self.label_areas[self.index_of(widget)]
}
#[inline]
pub fn label(&self, idx: usize) -> Rect {
self.label_areas[idx]
}
#[inline]
pub fn set_label(&mut self, idx: usize, area: Rect) {
self.label_areas[idx] = area;
}
#[inline]
pub fn widget_for(&self, widget: W) -> Rect {
self.widget_areas[self.index_of(widget)]
}
#[inline]
pub fn widget(&self, idx: usize) -> Rect {
self.widget_areas[idx]
}
#[inline]
pub fn set_widget(&mut self, idx: usize, area: Rect) {
self.widget_areas[idx] = area;
}
#[inline]
pub fn try_label_str_for(&self, widget: W) -> &Option<Cow<'static, str>> {
&self.labels[self.index_of(widget)]
}
#[inline]
pub fn try_label_str(&self, idx: usize) -> &Option<Cow<'static, str>> {
&self.labels[idx]
}
#[inline]
pub fn label_str_for(&self, widget: W) -> &str {
self.labels[self.index_of(widget)]
.as_ref()
.map(|v| v.as_ref())
.unwrap_or("")
}
#[inline]
pub fn label_str(&self, idx: usize) -> &str {
self.labels[idx]
.as_ref() .map(|v| v.as_ref())
.unwrap_or("")
}
#[inline]
pub fn set_label_str(&mut self, idx: usize, str: Option<Cow<'static, str>>) {
self.labels[idx] = str;
}
#[inline]
pub fn block_len(&self) -> usize {
self.blocks.len()
}
#[inline]
pub fn block_area(&self, idx: usize) -> Rect {
self.block_areas[idx]
}
#[inline]
pub fn set_block_area(&mut self, idx: usize, area: Rect) {
self.block_areas[idx] = area;
}
#[inline]
pub fn block_area_iter(&self) -> impl Iterator<Item = &Rect> {
self.block_areas.iter()
}
#[inline]
pub fn block(&self, idx: usize) -> &Option<Block<'static>> {
&self.blocks[idx]
}
#[inline]
pub fn set_block(&mut self, idx: usize, block: Option<Block<'static>>) {
self.blocks[idx] = block;
}
pub fn append(&mut self, place: Position, mut layout: GenericLayout<W>) {
layout = layout.place(place);
for (w, idx) in layout.widgets {
let new_idx = self.widget_areas.len();
self.widget_areas.push(layout.widget_areas[idx]);
self.labels.push(layout.labels[idx].take());
self.label_areas.push(layout.label_areas[idx]);
self.widgets.insert(w.clone(), new_idx);
self.rwidgets.insert(new_idx, w);
}
self.block_areas.extend(layout.block_areas);
self.blocks.extend(layout.blocks);
}
}
#[inline]
fn place(mut area: Rect, pos: Position) -> Rect {
area.x += pos.x;
area.y += pos.y;
area
}