tui-pages 0.7.2

Core for TUI apps with multiple pages
Documentation
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use tui_pages::{
    modes, ActionContext, ActionOutcome, FocusIntent, FocusTarget, PageSpec, TuiActionHandler,
    TuiEffect, TuiPages, TuiPagesStatus,
};

#[derive(Debug, Clone, PartialEq, Eq)]
enum View {
    Home,
    Settings,
}

#[derive(Debug, Clone, PartialEq, Eq)]
enum Action {
    Next,
    Settings,
    Quit,
}

#[derive(Default)]
struct State {
    handled: Vec<Action>,
}

#[derive(Clone, Copy)]
struct Handler;

impl TuiActionHandler<View, Action, State> for Handler {
    type Error = std::convert::Infallible;

    fn handle_action(
        &mut self,
        action: Action,
        _ctx: ActionContext<View>,
        state: &mut State,
    ) -> Result<ActionOutcome<View>, Self::Error> {
        state.handled.push(action.clone());
        Ok(match action {
            Action::Next => ActionOutcome::effect(TuiEffect::Focus(FocusIntent::Next)),
            Action::Settings => ActionOutcome::effect(TuiEffect::Navigate(View::Settings)),
            Action::Quit => ActionOutcome::effect(TuiEffect::Quit),
        })
    }
}

fn key(code: KeyCode) -> KeyEvent {
    KeyEvent::new(code, KeyModifiers::empty())
}

#[test]
fn runtime_maps_user_actions_to_library_effects() {
    let pages = |view: &View, _state: &State, _focus: Option<&FocusTarget>| match view {
        View::Home => PageSpec::new()
            .focus_targets(vec![FocusTarget::Button(0), FocusTarget::Button(1)])
            .modes(vec![modes::GENERAL, modes::GLOBAL]),
        View::Settings => PageSpec::new()
            .focus_targets(vec![FocusTarget::Button(0)])
            .modes(vec![modes::GENERAL, modes::GLOBAL]),
    };

    let mut tui = TuiPages::<View, Action, State>::builder(View::Home)
        .pages(pages)
        .handler(Handler)
        .bind(modes::GENERAL, "tab", Action::Next)
        .bind(modes::GENERAL, "s", Action::Settings)
        .command("Quit", ["q", "quit"], Action::Quit)
        .build();

    let mut state = State::default();
    tui.refresh_page(&state);
    assert_eq!(tui.focus.current(), Some(FocusTarget::Button(0)));

    let output = tui.handle_key(key(KeyCode::Tab), &mut state).unwrap();
    assert_eq!(output.status, TuiPagesStatus::ActionHandled);
    assert_eq!(tui.focus.current(), Some(FocusTarget::Button(1)));

    tui.handle_key(key(KeyCode::Char('s')), &mut state).unwrap();
    assert_eq!(tui.current_view(), &View::Settings);
    assert_eq!(tui.focus.current(), Some(FocusTarget::Button(0)));

    let output = tui.submit_command("q", &mut state).unwrap();
    assert!(output.quit_requested);
    assert_eq!(
        state.handled,
        vec![Action::Next, Action::Settings, Action::Quit]
    );
}