use super::{render_trait::Render, widget::widget_type::WidgetType, Layout, Widget};
use tui::{
layout::{Constraint, Direction, Layout as TuiLayout, Rect},
Frame,
};
#[derive(Debug)]
enum It {
Cont(usize),
Item(Widget),
}
#[derive(Debug)]
pub struct Container {
items: Vec<It>,
layout: TuiLayout,
direction: Direction,
pub parent: Option<usize>,
act_index: usize,
}
impl Container {
pub fn add_widget(&mut self, widget: Widget) {
self.items.push(It::Item(widget));
}
pub fn add_container(containers: &mut Vec<Self>, container: Container) -> usize {
let index = containers.len();
if let Some(parent_index) = container.parent {
containers[parent_index].items.push(It::Cont(index));
}
containers.push(container);
index
}
pub fn set_direction(&mut self, direction: Direction) {
self.direction = direction;
self.layout = self.layout.clone().direction(direction);
}
pub fn get_direction(&self) -> &Direction {
&self.direction
}
pub fn set_constraints(&mut self, constraints: Vec<Constraint>) {
self.layout = self.layout.clone().constraints(constraints);
}
pub fn item_count(&self) -> usize {
self.items.len()
}
pub fn get_index(&self) -> usize {
self.act_index
}
pub fn set_index(&mut self, index: usize) -> bool {
if self.items.len() > index {
self.act_index = index;
true
} else {
false
}
}
pub fn get_widget(&self, index: usize) -> Option<&Widget> {
match &self.items[index] {
It::Item(w) => Some(w),
It::Cont(_) => None,
}
}
pub fn get_widget_mut(&mut self, index: usize) -> Option<&mut Widget> {
match &mut self.items[index] {
It::Item(w) => Some(w),
It::Cont(_) => None,
}
}
pub fn actual(&self) -> Option<&Widget> {
self.get_widget(self.act_index)
}
pub fn actual_mut(&mut self) -> Option<&mut Widget> {
self.get_widget_mut(self.act_index)
}
pub fn actualize_layout(layout: &mut Layout) {
fn find_actual(layout: &Layout) -> usize {
if let It::Cont(mut index) = layout.act().items[layout.act().act_index] {
while let It::Cont(cont) =
&layout.containers[index].items[layout.containers[index].act_index]
{
index = *cont;
}
index
} else {
layout.act
}
}
layout.act = find_actual(layout);
}
pub fn actualize_parents(layout: &mut Layout) {
let mut child_index = layout.act;
while let Some(parent) = layout.containers[child_index].parent {
let cont = &layout.containers[parent];
layout.containers[parent].act_index = cont
.items
.iter()
.position(|w| {
if let It::Cont(cont) = w {
std::ptr::eq(&layout.containers[*cont], &layout.containers[child_index])
} else {
false
}
})
.expect("Child should be in parent container.");
child_index = parent;
}
}
pub fn next_item(&mut self) -> bool {
log::trace!("Next item {}", self.act_index);
if self.items.len() > self.act_index + 1 {
self.act_index += 1;
true
} else {
false
}
}
pub fn previous_item(&mut self) -> bool {
log::trace!("Prev item {}", self.act_index);
if self.act_index > 0 {
self.act_index -= 1;
true
} else {
false
}
}
pub fn get_active_type(&self) -> Option<WidgetType> {
Some(self.actual()?.widget_type())
}
pub fn render(&self, f: &mut Frame, containers: &Vec<Self>) {
self.items.iter().for_each(|cont| match cont {
It::Cont(index) => containers[*index].render(f, containers),
It::Item(widget) => widget.render(f),
});
}
pub fn update_chunk(chunk: Rect, containers: &mut Vec<Self>, index: usize) {
let chunks = containers[index].layout.split(chunk);
for i in 0..containers[index].items.len() {
let index = match &mut containers[index].items[i] {
It::Cont(index) => *index,
It::Item(widget) => {
widget.update_chunk(chunks[i]);
continue;
}
};
Self::update_chunk(chunks[i], containers, index);
}
}
pub fn get_widgets_mut(&mut self) -> impl IntoIterator<Item = &mut Widget> {
self.items.iter_mut().filter_map(|item| {
if let It::Item(w) = item {
Some(w)
} else {
None
}
})
}
}
impl Default for Container {
fn default() -> Self {
Container {
items: Vec::new(),
layout: TuiLayout::default(),
direction: Direction::Vertical,
parent: None,
act_index: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::super::Layout;
use super::*;
use crate::{config::Config, layout::widget::State, todo::ToDo, Result};
use std::sync::{Arc, Mutex};
use tui::layout::Direction::*;
use WidgetType::*;
fn testing_layout() -> Layout {
let todo = Arc::new(Mutex::new(ToDo::default()));
let mut containers: Vec<Container> = Vec::new();
let index = Container::add_container(&mut containers, Container::default());
containers[index].set_direction(Vertical);
containers[index].set_constraints(vec![Constraint::Percentage(30)]);
let mut cont = Container {
parent: Some(index),
..Container::default()
};
cont.set_direction(Horizontal);
cont.set_constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]);
cont.add_widget(Widget::new(WidgetType::List, todo.clone(), &Config::default()).unwrap());
let index = Container::add_container(&mut containers, cont);
let mut cont = Container {
parent: Some(index),
..Container::default()
};
cont.set_direction(Vertical);
cont.set_constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]);
cont.add_widget(Widget::new(WidgetType::Done, todo.clone(), &Config::default()).unwrap());
cont.add_widget(Widget::new(WidgetType::Project, todo, &Config::default()).unwrap());
let index = Container::add_container(&mut containers, cont);
Layout {
containers,
act: index,
}
}
fn check_active(layout: &Layout, widget_type: WidgetType) {
match layout.act().get_active_type() {
Some(active) if active == widget_type => {}
Some(active) => panic!("Active widget must be {:?} not {:?}.", widget_type, active),
None => panic!("Active item is not widget"),
}
}
#[test]
fn test_selecting_widget() -> Result<()> {
let mut layout = testing_layout();
let mut check = |widget_type| -> Result<()> {
layout.select_widget(widget_type);
check_active(&layout, widget_type);
Ok(())
};
check(List)?;
check(Done)?;
check(Project)?;
layout.select_widget(Context);
check_active(&layout, Project);
Ok(())
}
#[test]
fn test_next_item() -> Result<()> {
let mut layout = testing_layout();
layout.select_widget(List);
assert!(layout.act_mut().next_item());
Container::actualize_layout(&mut layout);
check_active(&layout, Done);
layout.select_widget(Done);
assert!(layout.act_mut().next_item());
Container::actualize_layout(&mut layout);
check_active(&layout, Project);
layout.select_widget(List);
assert!(layout.act_mut().next_item());
Container::actualize_layout(&mut layout);
check_active(&layout, Project);
assert!(!layout.act_mut().next_item());
Container::actualize_layout(&mut layout);
assert!(!layout.act_mut().next_item());
Container::actualize_layout(&mut layout);
assert!(!layout.act_mut().next_item());
Container::actualize_layout(&mut layout);
assert_eq!(layout.act().act_index, 1);
check_active(&layout, Project);
Ok(())
}
#[test]
fn test_previous_item() -> Result<()> {
let mut layout = testing_layout();
layout.select_widget(Project);
assert!(layout.act_mut().previous_item());
Container::actualize_layout(&mut layout);
assert!(!layout.act_mut().previous_item());
Container::actualize_layout(&mut layout);
assert!(!layout.act_mut().previous_item());
Container::actualize_layout(&mut layout);
assert!(!layout.act_mut().previous_item());
Container::actualize_layout(&mut layout);
assert_eq!(layout.act().act_index, 0);
check_active(&layout, Done);
Ok(())
}
#[test]
fn test_update_chunk() {
let mut layout = testing_layout();
layout.update_chunk(Rect::new(0, 0, 20, 20));
let count_widgets = |index: usize| -> usize {
layout.containers[index]
.items
.iter()
.filter(|item| match item {
It::Cont(_) => false,
It::Item(_) => true,
})
.count()
};
let check_chunk = |c_index: usize, i_index: usize, rect| {
match &layout.containers[c_index].items[i_index] {
It::Cont(_) => panic!("Cointainer does not hold widget"),
It::Item(widget) => assert_eq!(widget.get_base().chunk, rect),
};
};
assert_eq!(0, count_widgets(0));
assert_eq!(1, count_widgets(1));
check_chunk(1, 0, Rect::new(0, 0, 10, 6));
assert_eq!(2, count_widgets(2));
check_chunk(2, 0, Rect::new(10, 0, 10, 3));
check_chunk(2, 1, Rect::new(10, 3, 10, 3));
}
}