use std::{borrow::Cow, collections::HashMap};
use color_eyre::eyre::Result;
use ratatui::{layout::Rect, Frame};
use crate::app::{App, AsyncState};
pub mod prelude {
pub use super::*;
pub use color_eyre::Result;
pub use ratatui::{
prelude::*,
widgets::{
Cell, Paragraph, Row, ScrollDirection, Scrollbar,
ScrollbarOrientation, ScrollbarState, Table,
},
};
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ViewID<'a> {
Index(usize),
Named(&'a str),
}
#[derive(Clone, Copy)]
pub enum NavID<'a> {
Index(usize),
Named(&'a str),
}
#[derive(Clone)]
pub enum NavAction<'a> {
Pop,
Push(NavID<'a>),
}
#[derive(Clone)]
pub enum MenuItemAction<'a> {
NavAction(NavAction<'a>),
LoadView(ViewID<'a>),
External(fn(&mut App, &AsyncState) -> Result<()>),
}
pub struct MenuItem<'a> {
name: Cow<'a, str>,
is_default: bool,
action: MenuItemAction<'a>,
}
impl<'a> MenuItem<'a> {
pub fn new(
name: impl Into<Cow<'a, str>>,
action: MenuItemAction<'a>,
) -> Self {
Self {
name: name.into(),
is_default: false,
action,
}
}
pub fn default(mut self) -> Self {
self.is_default = true;
self
}
pub fn name(&self) -> &str {
&self.name
}
pub fn action(&self) -> &MenuItemAction<'a> {
&self.action
}
}
pub struct Nav<'a> {
menu: Vec<MenuItem<'a>>,
default_item: usize,
}
impl<'a> Nav<'a> {
pub fn menu(&self) -> &[MenuItem<'a>] {
&self.menu
}
pub fn default_item_index(&self) -> usize {
self.default_item
}
}
#[derive(PartialEq, Eq)]
pub enum ViewInteractivity {
None,
Scrollable,
Clickables(usize),
}
pub trait View {
fn draw_content(
&self,
app: &App,
state: &AsyncState,
frame: &mut Frame,
area: Rect,
is_focused: bool,
) -> Result<()> {
let _ = (app, state, frame, area, is_focused);
Ok(())
}
fn interactivity(
&self,
app: &App,
state: &AsyncState,
) -> Result<ViewInteractivity> {
let _ = (app, state);
Ok(ViewInteractivity::None)
}
fn click(
&self,
app: &mut App,
state: &AsyncState,
index: usize,
) -> Result<Option<NavAction>> {
let _ = (app, state, index);
Ok(None)
}
}
#[derive(Default)]
pub struct NavContext<'a> {
views: Vec<&'a dyn View>,
named_view_ids: HashMap<&'a str, usize>,
navs: Vec<Nav<'a>>,
named_nav_ids: HashMap<&'a str, usize>,
stack: Vec<NavID<'a>>,
}
impl<'a> NavContext<'a> {
pub fn view<V: View + 'a>(
&mut self,
name: &'a str,
view: &'a V,
) -> ViewID<'a> {
self.views.push(view);
self.named_view_ids.insert(name, self.views.len() - 1);
ViewID::Index(self.views.len() - 1)
}
pub fn nav(
&mut self,
name: &'a str,
menu: impl IntoIterator<Item = MenuItem<'a>>,
) -> NavID<'a> {
let menu = menu.into_iter().collect::<Vec<_>>();
let default_item = menu
.iter()
.enumerate()
.find(|(_, item)| item.is_default)
.map(|(index, _)| index)
.unwrap_or(0);
assert!(!menu.is_empty());
self.navs.push(Nav { menu, default_item });
self.named_nav_ids.insert(name, self.navs.len() - 1);
NavID::Index(self.navs.len() - 1)
}
pub fn push_nav(&mut self, nav: NavID<'a>) {
self.stack.push(nav);
}
pub fn pop_nav(&mut self) {
let _ = self.stack.pop();
}
pub fn top_nav(&self) -> Option<NavID<'a>> {
self.stack.last().copied()
}
pub fn get_view(&self, id: ViewID<'a>) -> &'a dyn View {
self.views[self.get_view_index(id)]
}
pub fn get_nav(&self, id: NavID<'a>) -> &Nav<'a> {
&self.navs[self.get_nav_index(id)]
}
fn get_view_index(&self, id: ViewID<'a>) -> usize {
match id {
ViewID::Index(index) => index,
ViewID::Named(name) => {
self.named_view_ids.get(name).copied().unwrap()
}
}
}
fn get_nav_index(&self, id: NavID<'a>) -> usize {
match id {
NavID::Index(index) => index,
NavID::Named(name) => {
self.named_nav_ids.get(name).copied().unwrap_or_else(|| {
panic!("No nav named '{name}' exists in the context")
})
}
}
}
}