use altui_core::{backend::Backend, layout::Rect, Terminal};
use crossterm::event::Event;
use crate::{app_handler::AppHandler, ctxstore::CtxStore, ViewFactory};
pub struct ViewLoop<'a, State, B: Backend> {
pub(crate) factory: ViewFactory<'a, State>,
terminal: &'a mut Terminal<B>,
external_loop: bool,
}
impl<'a, State: AppHandler, B> ViewLoop<'a, State, B>
where
B: Backend,
{
pub fn new(terminal: &'a mut Terminal<B>) -> std::io::Result<Self> {
terminal.autoresize()?;
Ok(Self {
factory: ViewFactory::new(),
terminal,
external_loop: false,
})
}
pub fn set_click_time(&mut self, millis: u64) -> &mut Self {
self.factory.click_time = millis;
self
}
pub fn external_loop(&mut self) -> &mut Self {
self.external_loop = true;
self
}
pub fn vscroll(&mut self, on: bool) -> &mut Self {
self.factory.vscroll = on;
self
}
pub fn basic_navigation(&mut self, navigation: bool) -> &mut Self {
self.factory.basic_navigation = navigation;
self
}
pub fn run(
&mut self,
state: &mut State,
views: impl Fn(&mut ViewFactory<'a, State>, &mut State),
areas: impl Fn(Rect, &mut CtxStore, &mut State),
mut event: impl FnMut() -> std::io::Result<Event>,
event_handler: impl Fn(&mut State, &mut ViewFactory<'a, State>),
) {
if self.factory.first_iteration {
views(&mut self.factory, state);
}
match self.external_loop {
true => self.run_once(state, &areas, &mut event, &event_handler),
false => {
while state.running() {
self.run_once(state, &areas, &mut event, &event_handler)
}
}
}
}
fn run_once(
&mut self,
state: &mut State,
areas: &impl Fn(Rect, &mut CtxStore, &mut State),
event: &mut impl FnMut() -> std::io::Result<Event>,
event_handler: &impl Fn(&mut State, &mut ViewFactory<'a, State>),
) {
self.factory.event = if !self.factory.skip_event {
match event() {
Ok(event) => {
tracing::trace!(
"Altui: hover is {:?}, active is: {:?}\nEvent: {:?}",
state.hover(),
state.active(),
event
);
Some(event)
}
Err(e) => {
tracing::trace!("Altui: {e}");
None
}
}
} else {
if !self.factory.first_iteration {
std::thread::sleep(std::time::Duration::from_millis(self.factory.click_time));
} else {
self.factory.first_iteration = false;
}
self.factory.skip_event = false;
None
};
self.terminal
.draw(|frame| {
self.factory.set_current_area(frame.size());
if !self.factory.skip_event {
if self.factory.basic_navigation {
self.factory.navigation(state);
}
}
if let Some(id) = state.active().or(self.factory.take_last_active()) {
let (i, _, active_view) = self.factory.views.get_full_mut(&id).unwrap();
let active_ctx = &mut self.factory.contexts.contexts[i];
if active_ctx.is_visible() && active_ctx.is_interactive() {
if let Some(event) = self.factory.event.take() {
active_view.on_event(event, active_ctx, state);
}
if active_ctx.is_selectable() {
active_ctx.reset_active();
}
active_view.logic(active_ctx, state);
} else {
active_ctx.reset_hover();
}
} else if let Some(id) = state.hover() {
event_handler(state, &mut self.factory);
let (i, _, hover_view) = self.factory.views.get_full_mut(&id).unwrap();
let hover_ctx = &mut self.factory.contexts.contexts[i];
if hover_ctx.is_visible() && hover_ctx.is_interactive() {
if let Some(event) = self.factory.event.take() {
hover_view.on_event(event, hover_ctx, state);
}
hover_view.logic(hover_ctx, state);
} else {
hover_ctx.reset_hover();
}
} else {
event_handler(state, &mut self.factory);
}
areas(frame.size(), &mut self.factory.contexts, state);
if self.factory.contexts.current_area != self.factory.views.len()
|| self.factory.contexts.current_area != self.factory.contexts.contexts.len()
{
panic!(
"View, states and areas vectors are not sync: views {}, states {}, areas {}",
self.factory.views.len(),
self.factory.contexts.contexts.len(),
self.factory.contexts.current_area
);
} else {
self.factory.contexts.current_area = 0;
}
let _ = self.factory.take_last_active();
self.factory.iteration(frame, state);
self.factory.contexts.layout_cache.current_split = 0;
})
.unwrap();
}
}