# Getting Started
A whole app, top to bottom. It's two pages (Home and About) with two buttons —
Tab moves between them, Enter activates, Ctrl+C quits. This is the `minimal`
example; run it with `cd examples/minimal && cargo run`.
It's three files: `app.rs` is everything that talks to the library, `ui.rs` just
draws, `main.rs` is the loop. Start with `app.rs`.
## Your types
A `View` is a page. An `Action` is something that can happen. You define both.
```rust
use tui_pages::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum View {
Home,
About
}
#[derive(Debug, Clone, Copy)]
pub enum Action {
FocusNext,
FocusPrev,
Select,
Quit
}
pub type App = TuiApp<View, Action, (), Handler>; // () = no state in this app
```
## The handler
An action comes in, you say what should happen. You don't move focus or change
pages yourself — you return a `TuiEffect` and the runtime does it.
```rust
pub struct Handler;
impl TuiActionHandler<View, Action, ()> for Handler {
type Error = std::convert::Infallible;
fn handle_action(&mut self, action: Action, ctx: ActionContext<View>, _state: &mut ())
-> Result<ActionOutcome<View>, Self::Error>
{
Ok(match action {
Action::FocusNext => ActionOutcome::effect(TuiEffect::Focus(FocusIntent::Next)),
Action::FocusPrev => ActionOutcome::effect(TuiEffect::Focus(FocusIntent::Prev)),
Action::Quit => ActionOutcome::effect(TuiEffect::Quit),
// Enter means different things depending on where you are.
// `ctx` tells you the page and what's focused.
Action::Select => match (ctx.current_view, ctx.focus) {
(View::Home, Some(FocusTarget::Button(0))) => ActionOutcome::effect(TuiEffect::Navigate(View::About)),
(View::About, Some(FocusTarget::Button(0))) => ActionOutcome::effect(TuiEffect::Navigate(View::Home)),
(_, Some(FocusTarget::Button(1))) => ActionOutcome::effect(TuiEffect::Quit),
_ => ActionOutcome::none(),
},
})
}
}
```
That `match action { … }` is your whole app's logic. Every arm returns an effect.
## The page spec
For each view, say what's on it: the focusable things (two buttons) and which
key modes are active.
```rust
fn page_spec(_view: &View, _state: &(), _focus: Option<&FocusTarget>) -> PageSpec {
PageSpec::new()
.focus(PageFocusBuilder::new().button(0).button(1))
.modes(vec![modes::GENERAL, modes::GLOBAL])
}
```
## Build it
Bind keys to actions and assemble the runtime. `GENERAL` is the normal mode;
`GLOBAL` is always on, so Ctrl+C quits anywhere.
```rust
pub fn build() -> App {
let mut app = TuiPages::builder(View::Home)
.page_fn(page_spec)
.handler(Handler)
.bind(modes::GENERAL, "tab", Action::FocusNext)
.bind(modes::GENERAL, "shift+tab", Action::FocusPrev)
.bind(modes::GENERAL, "enter", Action::Select)
.bind(modes::GLOBAL, "ctrl+c", Action::Quit)
.build();
app.refresh_page(&()); // load the first page's focus before drawing
app
}
```
That's all of `app.rs`. Now the loop in `main.rs`.
## The loop
Draw, read a key, hand it to `handle_key`. When an effect asks to quit, stop.
```rust
fn main() -> anyhow::Result<()> {
let _guard = tui_pages::terminal::enter()?; // raw mode; restores on drop/panic
let mut terminal = Terminal::new(CrosstermBackend::new(std::io::stderr()))?;
let mut tui = app::build();
let mut state = ();
loop {
terminal.draw(|frame| ui::render(frame, *tui.current_view(), tui.focus.current()))?;
if let crossterm::event::Event::Key(key) = crossterm::event::read()? {
if tui.handle_key(key, &mut state)?.quit_requested {
break;
}
}
}
Ok(())
}
```
`ui::render` gets the current view and the focused element and draws them — it
reads state, never changes it. (It's plain ratatui; look at `examples/minimal/src/ui.rs`.)
## That's the loop
Read a key → `handle_key` turns it into an action → your handler returns an
effect → the runtime applies it → draw again.
The rest of the book is just more effects you can return (focus, navigation,
panes) and more you can put in a `PageSpec`.