#325 catsay TUI
Building the text UI catsay example from Practical Rust Projects, learning about making text UI programs with Rust.
Notes
The text-base user interface catsay example from Practical Rust Projects is used to demonstrate techniques for making text UI programs with Rust.
Building catsay
Start a new project:
$ cargo new --bin catsay
    Creating binary (application) `catsay` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
$ cd catsay
$ cargo run
   Compiling catsay v0.1.0 (/Users/paulgallagher/MyGithub/tardate/LittleCodingKata/rust/catsay-tui/catsay)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.49s
     Running `target/debug/catsay`
Hello, world!
Step 1: setup the app framework with cursive
The example uses the Cursive crate. Cursive is a TUI (Text User Interface) library for rust. It uses the crossterm backend by default, but other backends are available.
Use the Cursive crate.
Add cursive = "0.11.2" to Cargo.toml, and update the code:
extern crate cursive;
use cursive::Cursive;
fn main() {
    let mut siv = Cursive::default();
    siv.run();
}
When invoked with cargo run, displays a simple blue screen.
Step 2: showing a dialog box
Seems I need to explicitly enable the cross term backend to get cursive to display anything. I want to use the multiplatform crossterm rather than ncurses used in the book (so I can easily run on macOS).
Update the cursive dependency and enable crossterm: cursive = { version = "0.21", features = ["crossterm-backend"] }.
extern crate cursive;
use cursive::views::TextView;
fn main() {
    let mut siv = cursive::crossterm();
    let cat_text = "{message}
\\
 \\
   /\\_/\\
  ( {eye}.{eye} )
   > ^ <";
    let cat_template = cat_text.replace("{message}", "Meow!");
    let cat_template = cat_template.replace("{eye}", "o");
    siv.add_layer(TextView::new(cat_template));
    siv.run();
}
Working nicely with cargo run:

Step 3: handling simple keyboard inputs
Add a global callback to handle key events - in this case ESC key
use cursive::event::Key;
...
siv.add_global_callback(Key::Esc, |s| s.quit());
Step 4: adding a dialog box
Wrap the TextView with a Dialog box
use cursive::views::{Dialog, TextView};
...
siv.add_layer(
    Dialog::around(TextView::new(cat_template))
        .title("The cat says...")
        .button("OK", |s| s.quit())
);

Step 5: multi-step dialog box
First dialog is to accept options for the cat, then show the cat accordingly.
extern crate cursive;
use cursive::traits::Nameable;
use cursive::views::{Checkbox, Dialog, EditView, ListView, TextView};
use cursive::event::Key;
use cursive::Cursive;
struct CatsayOptions<'a> {
    message: &'a str,
    dead: bool,
}
fn input_step(siv: &mut Cursive) {
    siv.add_layer(
        Dialog::new()
            .title("Please fill out the form for the cat")
            .content(
                ListView::new()
                    .child("Message:", EditView::new().with_name("message"))
                    .child("Dead?", Checkbox::new().with_name("dead")),
            )
            .button("OK", |s| {
                let message = s
                    .call_on_name("message", |t: &mut EditView| t.get_content())
                    .unwrap();
                let is_dead = s
                    .call_on_name("dead", |t: &mut Checkbox| t.is_checked())
                    .unwrap();
                let options = CatsayOptions {
                    message: &message,
                    dead: is_dead,
                };
                result_step(s, &options)
            }),
    );
}
fn result_step(siv: &mut Cursive, options: &CatsayOptions) {
    let eye = if options.dead { "x" } else { "o" };
    let cat_text = format!(
        "{message}
 \\
  \\
    /\\_/\\
   ( {eye}.{eye} )
    > ^ <",
        message = options.message,
        eye = eye
    );
    siv.pop_layer();
    siv.add_layer(
        Dialog::around(TextView::new(cat_text))
            .title("The cat says...")
            .button("OK", |s| s.quit()),
    );
}
fn main() {
    let mut siv = cursive::crossterm();
    input_step(&mut siv);
    siv.add_global_callback(Key::Esc, |s| s.quit()); // listen for ESC key and quit
    siv.run();
}
now on cargo run there’s a two step interaction:


Note: code has been updated to use
Nameable trait instead of Identifiable.
These were changed as part of the Cursive “great renaming”.
