Plan C - Animate the Image Across the Screen

Return to Plan C - Clean Up the Image

Complete Code Listing to this Point

Plan C - Animate the Image Across the Screen

We already have most of the code we need to animate the image across the screen in the "draw_d51()" function in the "drawing.rs" file. The most significant thing to be concerned about is that the function is named for the D51, and should be more generic. Let's rename the function:

drawing.rs
/* drawing.rs */
...
pub fn draw_d51image(delay: u64, fly: bool, oops: bool) {
    // Start ncurses, initializing "stdscr()".
    initscr();
...
    endwin();
} // end of draw_d51image()

All of the code for initializing ncurses remains the same:

drawing.rs
...
pub fn draw_image(delay: u64, fly: bool, oops: bool) {
    // Start ncurses, initializing "stdscr()".
    initscr();

    // Don't echo keypresses to screen.
    noecho();

    // Turn off the display of the cursor.
    curs_set(CURSOR_VISIBILITY::CURSOR_INVISIBLE);

    // Get the screen dimensions.
    let mut screen_height = 0;
    let mut screen_width = 0;
    getmaxyx(stdscr(), &mut screen_height, &mut screen_width);
...

And then we work on actually processing the vectored image.

We need to get the image from "main()" to "draw_image()":

main.rs
/* main.rs */

mod drawing;
mod get_options;
mod images;

use drawing::*;
use get_options::*;

fn main() {
    let (speed, fly, kind, oops, image_string) = parse_opts(); // Get the program options from the command-line, if any.
    // draw_d51(speed, fly, oops);
    let image_vecvec = string_to_stringvec(&image_string);
    // temporary code to test-print the vector
    for each_vec in image_vecvec {
        println!("{}", each_vec);
    }
    draw_image(speed, fly, kind, oops, image_vecvec);
} // end of main()
drawing.rs
...
pub fn draw_image(delay: u64, fly: bool, kind: String, oops: bool, image_vecvec: Vec<Vec<String>>) {
    // Start ncurses, initializing "stdscr()".
    initscr();

    // Don't echo keypresses to screen.
    noecho();

    // Turn off the display of the cursor.
    curs_set(CURSOR_VISIBILITY::CURSOR_INVISIBLE);

    // Get the screen dimensions.
    let mut screen_height = 0;
    let mut screen_width = 0;
    getmaxyx(stdscr(), &mut screen_height, &mut screen_width);

    let d51: Vec<Vec<String>> = get_d51();
    let height = d51[0].len() as i32; // How tall is the D51?
    let mut row = screen_height - height; // Put the wheels at the bottom of the screen.
    let count_of_inner_vecs: usize = d51.len(); // How many animation cel frames in this image?
    let length: i32 = d51[0][0].len() as i32; // How long (width in screen columns/chars) is the train?
    let ms = Duration::from_millis(delay); // This is the "delay" converted to a format for the "sleep()" function below.
...

And we need to change the references to "d51" in the "draw_image()" function to "image_vecvec":

drawing.rs
...
    let height = d51image_vecvec[0].len() as i32; // How tall is the D51image?
    let mut row = screen_height - height; // Put the wheels at the bottom of the screen.
    let count_of_inner_vecs: usize = d51image_vecvec.len(); // How many animation cel frames in this image?
    let length: i32 = d51image_vecvec[0][0].len() as i32; // How long (width in screen columns/chars) is the train?
    let ms = Duration::from_millis(delay); // This is the "delay" converted to a format for the "sleep()" function below.

    let mut current_inner_vec: usize = count_of_inner_vecs - 1; // Count down from highest-numbered frame; then repeat cycle.

    for col in ((-length)..screen_width).rev() {
        // Count down from right-edge until nose of train drags all of train off left-edge.
        let current_frame = &d51image_vecvec[current_inner_vec]; // Of the various inner_vecs, get the one we're drawing into its own variable.
        let mut line = row; // Keep "row" unaltered unless we're flying; use "line" within loop.
        for each_line in current_frame {
            my_mvaddstr(line, col, &each_line, screen_width, screen_height);
            line = line + 1;
        }
        refresh(); // Necessary to "bring to the forefront" the drawing we just did "in the back staging area".

        /* Pause so our eyes can see the frame. */
        thread::sleep(ms);

        if current_inner_vec > 0 {
            current_inner_vec = current_inner_vec - 1; // Prepare to display next frame.
        } else {
            // we've gotten down to last frame, so ...
            current_inner_vec = count_of_inner_vecs - 1; // ... start over.
            if fly {
                // And check to see if we're flying. If so...
                row -= 1; //... raise the next drawing one row up, and ...
                          // ... erase last (bottom) line drawn, using enough repeated spaces for train's length.
                line = row + height;
                my_mvaddstr(
                    line,
                    col,
                    &" ".repeat(length as usize),
                    screen_width,
                    screen_height,
                );
                // No need to refresh(); next frame will do it for us.
            }
        }
    }

Now if you test the program, you should be able to send the D51, the C51, or Jack, across the screen. You can change their speed. You can even make them fly.

The core of the program is basically finished, but there a few tweaks we might want to work on. For example, we can add a bit of resolution to Jack, and we can add the coal-car image, and we can cause the flying to start back over from the bottom if the image flies out of the top of the screen, and we can add smoke patterns, and we can add the "accident" mode. We'll do some of this work in the next lesson, Add Tweaks.