Writing a Simple Notcurses Program

Notcurses is a modern TUI library written by Nick Black (who you may see around the net as dankamongmen or some equally dank designation). As Dank himself will tell you, the older ncurses library is beautifully documented, rigorously tested, and very well supported. It is, however, quite old tracing its history back to at least 1982, and as such lacks some modern features such as thread-safety. I had been using the venerable ncurses for my fireplace program - a use case that it was never designed to handle. Overall ncurses does a good job, but there is occasional tearing and artifacts. Notcurses solved all the graphical glitches beautifully, and performance was noticeably improved. The problem is that just about the only other person using notcurses is Nick Black himself, and while his documentation is extensive, it is mostly source code snippets with few examples. Hopefully this article will provide a nice jumping-off point for the absolute beginner.

Hello, World!

As a simple intro to the library, I decided to write a program that would print a grid of characters line by line and then exit. Before we can do any drawing, notcurses needs to be set up and initialized.

First, the locale is set and the notcurses_options struct is set with all options having a value of zero.

1
2
3
    setlocale(LC_ALL, "");
    notcurses_options ncopt;
    memset(&ncopt, 0, sizeof(ncopt));

Next, we initialize a notcurses context with the zeroed options and the output set to stdout. Then, we grab a reference to the standard plane, the canvas on which our drawing will take place.

1
2
    struct notcurses* nc = notcurses_init(&ncopt, stdout);
    struct ncplane* stdplane = notcurses_stdplane(nc);

And that’s it for setup! Now we get to start drawing. Since we just want to write boring old ASCII characters to the screen, with no color or other special properties, we will use the function ncplane_putsimple_yx. The function takes 4 arguments: the plane on which we are drawing, the y-position, the x-position, and the ASCII printable character. After any modification to the plane, the screen will not update until notcurses_render is called. This is so that the plane can be modified an arbitrary amount before the expensive rendering occurs. In this case we will render after each modification, but this is not always necessary.

1
2
3
4
5
6
7
    for (int i = 0; i < 25; i++){
        for (int j = 0; j < 25; j++){
            ncplane_putsimple_yx(stdplane, i, j, '*');
            notcurses_render(nc);
            usleep(5000);
        }
    }

The above code will fill a 25×25 grid of stars, waiting 50ms before printing each one. Finally, all that’s left is to clean up and exit the program. If we used more than just the standard plane, we would have to destroy every plane we created with ncplane_destroy(), however it is an error to attempt to destroy the standard plane. Therefore all that we are required to do is call notcurses_stop, which takes care of all deallocations and resets the terminal to normal.

Let’s put it all together!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <unistd.h>
#include <locale.h>
#include <notcurses.h>

int main(){
    setlocale(LC_ALL, "");
    notcurses_options ncopt;

    memset(&ncopt, 0, sizeof(ncopt));

    struct notcurses* nc = notcurses_init(&ncopt, stdout);
    struct ncplane* stdplane = notcurses_stdplane(nc);
    for (int i = 0; i < 25; i++){
        for (int j = 0; j < 25; j++){
            ncplane_putsimple_yx(stdplane, i, j, '*');
            notcurses_render(nc);
            usleep(5000);
        }
    }
    sleep(2);
    notcurses_stop(nc);
    return 0;
}

And there you have it! Next, I will talk about adding some color.