tui-pages 0.7.2

Core for TUI apps with multiple pages
Documentation
use crate::focus::{FocusIntent, FocusTarget, FocusWrap};
use crate::navigation::BufferState;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NavigationEvent<V> {
    NavigateTo(V),
    NextBuffer,
    PreviousBuffer,
    CloseBuffer,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NavigationResult<V> {
    Navigated { to: V },
    Switched { from: V, to: V },
    Closed { closed: V, now_on: V },
    NoChange,
}

pub trait NavigationRouter<V> {
    fn sync_to_view(&mut self, view: &V);
    fn current_view(&self) -> V;
    fn focus_targets(&self) -> Vec<FocusTarget>;
}

#[derive(Debug, Clone, Copy, Default)]
pub struct NavigationCoordinator;

impl NavigationCoordinator {
    pub fn navigate<V, R>(
        event: NavigationEvent<V>,
        router: &mut R,
        buffer: &mut BufferState<V>,
        fallback: V,
        wrap: FocusWrap,
    ) -> (NavigationResult<V>, Option<FocusIntent>)
    where
        V: Clone + PartialEq,
        R: NavigationRouter<V>,
    {
        match event {
            NavigationEvent::NavigateTo(view) => Self::navigate_to(view, router, buffer),
            NavigationEvent::NextBuffer => Self::switch_buffer(true, router, buffer, wrap),
            NavigationEvent::PreviousBuffer => Self::switch_buffer(false, router, buffer, wrap),
            NavigationEvent::CloseBuffer => Self::close_buffer(router, buffer, fallback),
        }
    }

    fn navigate_to<V, R>(
        view: V,
        router: &mut R,
        buffer: &mut BufferState<V>,
    ) -> (NavigationResult<V>, Option<FocusIntent>)
    where
        V: Clone + PartialEq,
        R: NavigationRouter<V>,
    {
        buffer.update_history(view.clone());
        router.sync_to_view(&view);
        (
            NavigationResult::Navigated { to: view },
            Self::focus_intent_for_router(router),
        )
    }

    fn switch_buffer<V, R>(
        forward: bool,
        router: &mut R,
        buffer: &mut BufferState<V>,
        wrap: FocusWrap,
    ) -> (NavigationResult<V>, Option<FocusIntent>)
    where
        V: Clone + PartialEq,
        R: NavigationRouter<V>,
    {
        if buffer.history.len() <= 1 {
            return (NavigationResult::NoChange, None);
        }

        let old_view = buffer.history[buffer.active_index].clone();
        let len = buffer.history.len();
        buffer.active_index = wrap.step(buffer.active_index, len, forward);
        buffer.sync_active_pane_to_active_buffer();

        let new_view = buffer.history[buffer.active_index].clone();
        router.sync_to_view(&new_view);
        (
            NavigationResult::Switched {
                from: old_view,
                to: new_view,
            },
            Self::focus_intent_for_router(router),
        )
    }

    fn close_buffer<V, R>(
        router: &mut R,
        buffer: &mut BufferState<V>,
        fallback: V,
    ) -> (NavigationResult<V>, Option<FocusIntent>)
    where
        V: Clone + PartialEq,
        R: NavigationRouter<V>,
    {
        let Some(closed_view) = buffer.close_active_buffer(fallback) else {
            return (NavigationResult::NoChange, None);
        };
        let new_view = buffer
            .get_active_view()
            .cloned()
            .unwrap_or_else(|| router.current_view());
        buffer.replace_workspace_view(&closed_view, new_view.clone());
        router.sync_to_view(&new_view);
        (
            NavigationResult::Closed {
                closed: closed_view,
                now_on: new_view,
            },
            Self::focus_intent_for_router(router),
        )
    }

    fn focus_intent_for_router<V, R>(router: &R) -> Option<FocusIntent>
    where
        R: NavigationRouter<V>,
    {
        let targets = router.focus_targets();
        if targets.is_empty() {
            None
        } else {
            Some(FocusIntent::RegisterPage(targets))
        }
    }
}