use std::{error::Error, fmt::Display, io::Stdout};
use crossterm::event::KeyEvent;
use tui::{backend::CrosstermBackend, layout::Rect, Frame};
use super::{
CursorState, FrameworkClean, FrameworkData, FrameworkDirection, FrameworkHistory, ItemInfo,
State,
};
#[derive(Clone)]
pub struct Framework {
pub selectables: Vec<Vec<(usize, usize)>>,
pub data: FrameworkData,
pub state: State,
pub cursor: CursorState,
pub history: Vec<FrameworkHistory>,
pub frame_area: Option<Rect>,
}
impl Framework {
pub fn clear_history(&mut self) {
self.history.clear();
}
pub fn push_history(&mut self) {
self.history.push(FrameworkHistory {
selectables: self.selectables.clone(),
data: self.data.state.clone(),
state: self.state.clone(),
cursor: self.cursor,
});
}
pub fn pop_history(&mut self) -> Option<FrameworkHistory> {
self.history.pop()
}
pub fn revert_last_history(&mut self) -> Result<(), FrameworkError> {
let history = match self.history.pop() {
None => return Err(FrameworkError::NoSuchSave),
Some(history) => history,
};
self.selectables = history.selectables;
self.data.state = history.data;
self.state = history.state;
self.cursor = history.cursor;
Ok(())
}
pub fn revert_history(&mut self, index: usize) -> Result<(), FrameworkError> {
if index >= self.history.len() {
return Err(FrameworkError::NoSuchSave);
}
let history = self.history.remove(index);
self.selectables = history.selectables;
self.data.state = history.data;
self.state = history.state;
self.cursor = history.cursor;
Ok(())
}
}
impl Framework {
pub fn is_selected(&self) -> bool {
self.cursor.is_selected()
}
pub fn is_hover(&self) -> bool {
self.cursor.is_hover()
}
pub fn is_none(&self) -> bool {
self.cursor.is_none()
}
}
impl Framework {
pub fn new(state: State) -> Self {
Self {
selectables: state.selectables(),
data: FrameworkData::default(),
state,
frame_area: None,
cursor: CursorState::default(),
history: Vec::new(),
}
}
pub fn set_state(&mut self, state: State) {
self.state = state;
self.selectables = self.state.selectables();
}
pub fn render(&mut self, frame: &mut Frame<CrosstermBackend<Stdout>>) {
let area = frame.size();
self.frame_area = Some(area);
let chunks = self.state.get_chunks(area);
let selected = self.cursor.selected(&self.selectables);
let hover = self.cursor.hover(&self.selectables);
self.render_raw(frame, &chunks, selected, hover, false);
self.render_raw(frame, &chunks, selected, hover, true);
}
pub fn render_raw(
&mut self,
frame: &mut Frame<CrosstermBackend<Stdout>>,
chunks: &[Vec<Rect>],
selected: Option<(usize, usize)>,
hover: Option<(usize, usize)>,
popup_render: bool,
) {
let (mut frameworkclean, state) = self.split_clean();
for (y, (row, row_chunks)) in state.0.iter_mut().zip(chunks.iter()).enumerate() {
for (x, (row_item, item_chunk)) in
row.items.iter_mut().zip(row_chunks.iter()).enumerate()
{
row_item.item.render(
frame,
&mut frameworkclean,
*item_chunk,
popup_render,
ItemInfo {
selected: Some((x, y)) == selected,
hover: Some((x, y)) == hover,
x,
y,
},
);
}
}
}
pub fn render_only(&mut self, frame: &mut Frame<CrosstermBackend<Stdout>>, x: usize, y: usize) {
let chunk = self.state.get_chunks(frame.size())[y][x];
let selected = self.cursor.selected(&self.selectables);
let hover = self.cursor.hover(&self.selectables);
self.render_only_raw(frame, x, y, chunk, false, selected, hover);
self.render_only_raw(frame, x, y, chunk, true, selected, hover);
}
pub fn render_only_multiple(
&mut self,
frame: &mut Frame<CrosstermBackend<Stdout>>,
locations: &[(usize, usize)],
) {
let chunks = self.state.get_chunks(frame.size());
let selected = self.cursor.selected(&self.selectables);
let hover = self.cursor.hover(&self.selectables);
locations.iter().for_each(|(x, y)| {
self.render_only_raw(frame, *x, *y, chunks[*y][*x], false, selected, hover);
});
locations.iter().for_each(|(x, y)| {
self.render_only_raw(frame, *x, *y, chunks[*y][*x], true, selected, hover);
});
}
pub fn render_only_raw(
&mut self,
frame: &mut Frame<CrosstermBackend<Stdout>>,
x: usize,
y: usize,
chunk: Rect,
popup_render: bool,
selected: Option<(usize, usize)>,
hover: Option<(usize, usize)>,
) {
let (mut frameworkclean, state) = self.split_clean();
state.get_mut(x, y).render(
frame,
&mut frameworkclean,
chunk,
popup_render,
ItemInfo {
selected: selected == Some((x, y)),
hover: hover == Some((x, y)),
x,
y,
},
)
}
pub fn key_input(&mut self, key: KeyEvent) -> Result<(), Box<dyn Error>> {
let selected = self.cursor.selected(&self.selectables);
let (mut frameworkclean, state) = self.split_clean();
if let Some((x, y)) = selected {
state.get_mut(x, y).key_event(
&mut frameworkclean,
key,
ItemInfo {
selected: true,
hover: false,
x,
y,
},
)?;
}
Ok(())
}
pub fn mouse_event(&mut self, col: u16, row: u16) -> bool {
let chunks = match self.frame_area {
Some(area) => self.state.get_chunks(area),
None => return false,
};
for (row_no, row_selectables) in self.selectables.iter().enumerate() {
for (col_no, &(x, y)) in row_selectables.iter().enumerate() {
let chunk = chunks[y][x];
if !chunk.intersects(Rect::new(col, row, 1, 1)) {
continue;
}
if self.cursor.selected(&self.selectables) == Some((col_no, row_no)) {
let (mut clean, state) = self.split_clean();
return state.get_mut(x, y).mouse_event(
&mut clean,
col - chunk.x,
row - chunk.y,
col,
row,
);
}
if self.cursor.hover(&self.selectables) == Some((col_no, row_no)) {
return self.select().is_ok();
}
self.deselect().ok();
self.cursor = CursorState::to_hover((col_no, row_no));
return true;
}
}
self.deselect().ok();
self.cursor = CursorState::default();
true
}
pub fn load(&mut self) -> Result<(), Box<dyn Error>> {
let selected = self.cursor.selected(&self.selectables);
let hover = self.cursor.hover(&self.selectables);
let (mut frameworkclean, state) = self.split_clean();
for (y, row) in state.0.iter_mut().enumerate() {
for (x, row_item) in row.items.iter_mut().enumerate() {
row_item.item.load_item(
&mut frameworkclean,
ItemInfo {
selected: Some((x, y)) == selected,
hover: Some((x, y)) == hover,
x,
y,
},
)?;
}
}
Ok(())
}
pub fn load_only(&mut self, x: usize, y: usize) -> Result<(), Box<dyn Error>> {
let selected = self.cursor.selected(&self.selectables);
let hover = self.cursor.hover(&self.selectables);
let (mut frameworkclean, state) = self.split_clean();
state.get_mut(x, y).load_item(
&mut frameworkclean,
ItemInfo {
selected: Some((x, y)) == selected,
hover: Some((x, y)) == hover,
x,
y,
},
)
}
pub fn load_only_multiple(&mut self, locations: &[(usize, usize)]) {
let selected = self.cursor.selected(&self.selectables);
let hover = self.cursor.hover(&self.selectables);
let (mut frameworkclean, state) = self.split_clean();
locations.iter().for_each(|(x, y)| {
let _ = state.get_mut(*x, *y).load_item(
&mut frameworkclean,
ItemInfo {
selected: Some((*x, *y)) == selected,
hover: Some((*x, *y)) == hover,
x: *x,
y: *y,
},
);
})
}
}
impl Framework {
pub fn split_clean(&mut self) -> (FrameworkClean, &mut State) {
self.into()
}
}
impl Framework {
pub fn r#move(&mut self, direction: FrameworkDirection) -> Result<(), FrameworkError> {
self.cursor.r#move(direction, &self.selectables)
}
pub fn select(&mut self) -> Result<(), Box<dyn Error>> {
if let Some((x, y)) = self.cursor.hover(&self.selectables) {
let (mut frameworkclean, state) = self.split_clean();
let item = state.get_mut(x, y);
if item.select(&mut frameworkclean) {
self.cursor.select()?;
}
} else {
Err(FrameworkError::CursorStateMismatch)?;
}
Ok(())
}
pub fn deselect(&mut self) -> Result<(), Box<dyn Error>> {
if let Some((x, y)) = self.cursor.selected(&self.selectables) {
let (mut frameworkclean, state) = self.split_clean();
let item = state.get_mut(x, y);
if item.deselect(&mut frameworkclean) {
self.cursor.deselect()?;
}
} else {
Err(FrameworkError::CursorStateMismatch)?;
}
Ok(())
}
}
#[derive(Debug)]
pub enum FrameworkError {
MoveSelected,
CursorStateMismatch,
NoSuchSave,
}
impl Display for FrameworkError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{:?}", self))
}
}
impl Error for FrameworkError {}