shithub: orca

Download patch

ref: e79f7d824b4de73c4490e7847ca1de4eb45a0019
parent: 930d5bb3dcd6554322a7bf0772cb9cfa78645a23
author: cancel <cancel@cancel.fm>
date: Sat Nov 24 07:20:49 EST 2018

Change example to be interactive

--- a/main.c
+++ b/main.c
@@ -2,72 +2,59 @@
 #include <limits.h>
 #include <locale.h>
 #include <ncurses.h>
+#include <stdbool.h>
 #include <stdlib.h>
 
-int main() {
-  // Enable UTF-8 by explicitly initializing our locale before initializing
-  // ncurses.
-  setlocale(LC_ALL, "");
-  // Initialize ncurses
-  initscr();
-  // Allow ncurses to control newline translation. Fine to use with any modern
-  // terminal, and will let ncurses run faster.
-  nonl();
-  // Set interrupt keys (interrupt, break, quit...) to not flush. Helps keep
-  // ncurses state consistent, at the cost of less responsive terminal
-  // interrupt. (This will rarely happen.)
-  intrflush(stdscr, FALSE);
-  // Receive keyboard input immediately, and receive shift, control, etc. as
-  // separate events, instead of combined with individual characters.
-  raw();
-  // Don't echo keyboard input
-  noecho();
-  // Also receive arrow keys, etc.
-  keypad(stdscr, TRUE);
-  // Hide the terminal cursor
-  curs_set(0);
+typedef struct {
+  chtype* buffer;
+  int size_y;
+  int size_x;
+  chtype fill_char;
+} view_state;
 
-  printw("Type any character to fill it in an alternating grid\n");
-  refresh();
-  // 'chtype' is the type of character that ncurses uses. It will be an
-  // ASCII-like value, if that's what the user hit on the keyboard, but
-  // 'chtype' is larger than an 8-bit number and could have something else in
-  // it (some Unicode character, a control character for the terminal, etc.)
-  chtype ch = getch();
-  // We get the dimensions that the terminal is currently set to, so we know
-  // how big of a buffer to allocate. We'll fill the buffer with some
-  // characters after we've allocated it.
-  int term_height = getmaxy(stdscr);
-  int term_width = getmaxx(stdscr);
-  assert(term_height >= 0 && term_width >= 0);
-  // We use 'size_t' when we talk about the size of memory. We also sometimes
-  // use it when looping over indices in an array, but we won't do that this
-  // time, since we already have the terminal width and height as regular ints.
-  size_t term_cells = term_height * term_width;
+void init_view_state(view_state* vs) {
+  vs->buffer = NULL;
+  vs->size_y = 0;
+  vs->size_x = 0;
+  vs->fill_char = '?';
+}
 
-  // 'calloc' uses the C runtime library to give us a chunk of memory that we
-  // can use to do whatever we want. The first argument is the number of things
-  // we'll put into the memory, and the second argument is the size of the
-  // those things. The total amount of memory it gives us back will be (number
-  // of guys * size of guys).
-  //
-  // There is also another function you may have heard of -- malloc -- which
-  // does mostly the same thing. The main differences are that 1) malloc does
-  // not turn all of the memory into zeroes before giving it to us, and 2)
-  // malloc only takes one argument.
-  //
-  // Because malloc doesn't zero the memory for us, you have to make sure that
-  // you always clear (or write to it) yourself before using it. That wouldn't
-  // be a problem in our example, though.
-  //
-  // Because malloc only takes one argument, you have to do the multiplication
-  // yourself, and if you want to be safe about it, you have to check to make
-  // sure the multiplication won't overflow. calloc does that for us.
-  //
-  // sizeof is a special thing that returns the size of an expression or type
-  // *at compile time*.
-  chtype* buff = calloc(term_cells, sizeof(chtype));
+void deinit_view_state(view_state* vs) {
+  // Note that we don't have to check if the buffer was ever actually set to a
+  // non-null pointer: `free` does this for us.
+  free(vs->buffer);
+}
 
+void update_view_state(view_state* vs, int term_height, int term_width,
+                       chtype fill_char) {
+  bool same_dimensions = vs->size_y == term_height && vs->size_x == term_width;
+  bool same_fill_char = vs->fill_char == fill_char;
+  // If nothing has changed, we don't have any work to do.
+  if (same_dimensions && same_fill_char)
+    return;
+  if (!same_dimensions) {
+    // Note that this doesn't check for overflow. In theory that's unsafe, but
+    // really unlikely to happen here.
+    size_t term_cells = term_height * term_width;
+    size_t new_mem_size = term_cells * sizeof(chtype);
+
+    // 'realloc' is like malloc, but it lets you re-use a buffer instead of
+    // having to throw away an old one and create a new one. Oftentimes, the
+    // cost of 'realloc' is cheaper than 'malloc' for the C runtime, and it
+    // reduces memory fragmentation.
+    //
+    // It's called 'realloc', but you can also use it even you're starting out
+    // with a NULL pointer for your buffer.
+    vs->buffer = realloc(vs->buffer, new_mem_size);
+    vs->size_y = term_height;
+    vs->size_x = term_width;
+  }
+  if (!same_fill_char) {
+    vs->fill_char = fill_char;
+  }
+
+  // (Re-)fill the buffer with the new data.
+  chtype* buff = vs->buffer;
   // For each row, in the buffer, fill it with an alternating pattern of spaces
   // and the character the user typed.
   for (int iy = 0; iy < term_height; ++iy) {
@@ -83,14 +70,16 @@
       if ((iy + ix) % 2) {
         line[ix] = ' ';
       } else {
-        line[ix] = ch;
+        line[ix] = fill_char;
       }
     }
   }
+}
 
+void draw_view_state(view_state* vs) {
   // Loop over each row in the buffer, and send the entire row to ncurses all
   // at once. This is the fastest way to draw to the terminal with ncurses.
-  for (int i = 0; i < term_height; ++i) {
+  for (int i = 0; i < vs->size_y; ++i) {
     // Move the cursor directly to the start of the row.
     move(i, 0);
     // Send the entire line at once. If it's too long, it will be truncated
@@ -101,19 +90,60 @@
     // string. If we tried to use addchstr, it would keep trying to read until
     // it got to the end of our buffer, and then past the end of our buffer
     // into unknown memory, because we don't have a null terminator in it.
-    addchnstr(buff + i * term_width, term_width);
+    addchnstr(vs->buffer + i * vs->size_x, vs->size_x);
   }
+}
 
-  // We don't need our buffer anymore. We call `free` to return it back to the
-  // operating system. If we don't do this, and we lose track of our `buff`
-  // pointer, the memory has leaked, and it can't be reclaimed by the OS until
-  // the program is terminated.
-  free(buff);
+int main() {
+  // Enable UTF-8 by explicitly initializing our locale before initializing
+  // ncurses.
+  setlocale(LC_ALL, "");
+  // Initialize ncurses
+  initscr();
+  // Allow ncurses to control newline translation. Fine to use with any modern
+  // terminal, and will let ncurses run faster.
+  nonl();
+  // Set interrupt keys (interrupt, break, quit...) to not flush. Helps keep
+  // ncurses state consistent, at the cost of less responsive terminal
+  // interrupt. (This will rarely happen.)
+  intrflush(stdscr, FALSE);
+  // Receive keyboard input immediately, and receive shift, control, etc. as
+  // separate events, instead of combined with individual characters.
+  raw();
+  // Don't echo keyboard input
+  noecho();
+  // Also receive arrow keys, etc.
+  keypad(stdscr, TRUE);
+  // Hide the terminal cursor
+  curs_set(0);
 
-  // Refresh the terminal to make sure our changes get displayed immediately.
+  view_state vs;
+  init_view_state(&vs);
+
+  printw("Type any character to fill it in an alternating grid, or\ntype '");
+  attron(A_BOLD);
+  printw("q");
+  attroff(A_BOLD);
+  printw("' to quit\n");
   refresh();
-  // Wair for the user's next input before terminating.
-  getch();
+
+  for (;;) {
+    chtype ch = getch();
+    if (ch == 'q')
+      break;
+    // ncurses gives us the special value KEY_RESIZE if the user didn't
+    // actually type anything, but the terminal resized. If that happens to us,
+    // just re-use the fill character from last time.
+    if (ch == KEY_RESIZE)
+      ch = vs.fill_char;
+    int term_height = getmaxy(stdscr);
+    int term_width = getmaxx(stdscr);
+    assert(term_height >= 0 && term_width >= 0);
+    update_view_state(&vs, term_height, term_width, ch);
+    draw_view_state(&vs);
+    refresh();
+  }
+  deinit_view_state(&vs);
   endwin();
   return 0;
 }