Creating Text-based "Graphics" Using ANSI Codes, a Native, Built-In Method

Use ANSI Escape Sequences to Generate Text-Mode Graphics

Let's play with text-mode graphics using ANSI codes. Create a new Rust/Cargo project, from within your projects directory (e.g. "~/projects/RUST/"):

cargo new ansi_experiment

If you do a web-search for something like "ANSI terminal sequences", you'll find lots of information about "printing" certain sequences of characters that instruct the terminal to do special things. For example, we can turn text red. Try the following code in the "main.rs" file of your "ansi_experiment" project, adding the text that is in highlight:

fn main() {

  println!("Hello, world, in Black and white!");
  print!("\x1b[38;5;196m");   // Set foreground color to a red, without adding a newline
  print!("\x1b[48;5;21m");    // Set background color to a blue, without adding a newline
  println!("Hello, world, in Living Color!");

} // end of main()

When you compile and run this program (cargo run), you should see a couple of messages in different colors. We can also erase the screen before printing these messages:

fn main() {

  println!("\x1b[2J");        // Erase the screen

  println!("Hello, world, in Black and white!");
  print!("\x1b[38;5;196m");   // Set foreground color to a red, without adding a newline
  print!("\x1b[48;5;21m");    // Set background color to a blue, without adding a newline
  println!("Hello, world, in Living Color!");

} // end of main()

These ANSI Terminal Sequences (or ANSI Escape Sequences) are signified by a special ESCape code in the front of the various function codes. This ESCape code is "\x1b[" (although you may see it in other formats). Then the code to erase the screen, "2J", is added to the right of that ESCape sequence, and when this Escape-code sequence is printed to the screen, the screen knows that it is getting a special instruction, one that tells it to clear the screen.

Here is another variation, with a couple of other options, just to give you an idea of what can be done. We won't go into this in any more detail.

fn main() {

  const ESC: &str = "\x1b[";

  println!("{ESC}2J");        // Clear the screen

  println!("Hello, world, in Black and white!");
  print!("{ESC}38;5;196m");   // Set foreground color to a red, without adding a newline
  print!("{ESC}48;5;21m");    // Set background color to a blue, without adding a newline
  println!("Hello, world, in Living Color!");

  print!("{ESC}10;20H");      // Move cursor to the tenth line down, 20th column from left edge
  print!("{ESC}5m");          // Turn on blinking
  print!("{ESC}3m");          // And italics
  print!("{ESC}30m{ESC}43m"); // Set black text on yellow background
  println!("Ain't we havin' fun?!");

} // end of main()

Although we can do quite a bit with the ANSI method of creating "graphics" on a text-based terminal, we soon run into problems. For example, there's no easy way to learn the size of the screen with which we're working. Rather than re-invent the wheel trying to solve this problem, it's easier to simply turn to a third-party library that has already solved the problem. The library we'll be using is "curses" (or "ncurses", for "new curses"). We'll do this in the next lesson, Curses "Graphics" for Text-mode Screens. For now, feel free to delete the "~/projects/RUST/ansi_experiment" project directory.