use super::{render_trait::Render, widget::widget_type::WidgetType, Layout, Widget};
use crate::{Result, ToDoError};
use tui::{
backend::Backend,
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.clone();
self.layout = self.layout.clone().direction(direction);
}
#[allow(dead_code)]
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,
}
}
#[allow(dead_code)]
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 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
}
}
pub fn actualize_layout(layout: &mut Layout) {
layout.act = Self::find_actual(layout);
}
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
}
}
#[allow(dead_code)]
pub fn select_widget(layout: &mut Layout, widget_type: WidgetType) -> Result<()> {
let mut index_item = 0;
let (index_container, _) = layout
.containers
.iter()
.enumerate()
.find(|(_i_cont, cont)| {
cont.items
.iter()
.enumerate()
.any(|(i_item, item)| match item {
It::Item(item) if item.widget_type() == widget_type => {
index_item = i_item;
true
}
_ => false,
})
})
.ok_or(ToDoError::WidgetDoesNotExist)?;
layout.containers[index_container].act_index = index_item;
layout.act = index_container;
let mut index_container = index_container;
while let Some(index_parent) = layout.containers[index_container].parent {
layout.containers[index_parent].act_index = index_container;
index_container = index_parent;
}
Ok(())
}
pub fn get_active_type(&self) -> Option<WidgetType> {
Some(self.actual()?.widget_type())
}
pub fn render<B: Backend>(&self, f: &mut Frame<B>, 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};
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<()> {
Container::select_widget(&mut layout, widget_type)?;
check_active(&layout, widget_type);
Ok(())
};
check(List)?;
check(Done)?;
check(Project)?;
assert!(
check(Context).is_err(),
"Widget with type Context is not in container."
);
Ok(())
}
#[test]
fn test_next_item() -> Result<()> {
let mut layout = testing_layout();
Container::select_widget(&mut layout, List)?;
assert!(layout.act_mut().next_item());
Container::actualize_layout(&mut layout);
check_active(&layout, Done);
Container::select_widget(&mut layout, Done)?;
assert!(layout.act_mut().next_item());
Container::actualize_layout(&mut layout);
check_active(&layout, Project);
Container::select_widget(&mut layout, 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();
Container::select_widget(&mut layout, 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, 20));
assert_eq!(2, count_widgets(2));
check_chunk(2, 0, Rect::new(10, 0, 10, 10));
check_chunk(2, 1, Rect::new(10, 10, 10, 10));
}
}