|
|
@@ -164,9 +164,9 @@ int curses_active = 1; |
|
|
|
|
|
|
|
/* |
|
|
|
I need to quickly explain how I'm structuring this subprogram. You can think of functions and variables starting with 'curses_*' as tiny standalone library if it's easier. They |
|
|
|
deal with terminal input and output. User only needs to call function 'curses_configure' only once, before doing any rendering, then make an "infinite" loop that'll stop when |
|
|
|
external variable 'curses_active' is equal to zero. In that loop, user can call any 'curses_render_*' function, and at the end call function 'curses_synchronize' only once. So the |
|
|
|
following program structure would look something like this: |
|
|
|
deal with terminal input and output. User only needs to call function 'curses_configure' only once, and necessary amount of 'curses_bind' functions, before doing any rendering, |
|
|
|
then make an "infinite" loop that'll stop when external variable 'curses_active' is equal to zero. In that loop, user can call any amount 'curses_render_*' functions, and at the |
|
|
|
end call function 'curses_synchronize' again, only once. So the following program structure would look something like this: |
|
|
|
|
|
|
|
@C |
|
|
|
// ... |
|
|
@@ -183,6 +183,11 @@ int main (int argc, char * * argv) { |
|
|
|
return (0); |
|
|
|
} |
|
|
|
@ |
|
|
|
|
|
|
|
We don't need to use 'curses_initialize' and the begining of the program, or 'curses_deinitialize' at the end. Also, there are no functions like 'BeginDrawing' or 'EndDrawing', |
|
|
|
with 'Draw*' functions strictly between those two, like you would see in Raylib. We won't cover SDL2 and Raylib in this book, since I think that anyone with minimal knowledge of |
|
|
|
C can efficiently write good programs using those libraries, please don't misunderstand me, those two are good libraries. We'll simply eliminate that "begin" or "end" step in |
|
|
|
our subprogram called 'curses'. |
|
|
|
*/ |
|
|
|
|
|
|
|
static void curses_initialize (void) { // This function will be called when 'curses_configure' is called, automatically. |
|
|
@@ -199,10 +204,10 @@ static void curses_initialize (void) { |
|
|
|
|
|
|
|
curses_new_terminal = curses_old_terminal; // Here we set our raw terminal to be the same as the non-raw one. |
|
|
|
|
|
|
|
curses_new_terminal.c_cc [VMIN] = (unsigned char) 0; |
|
|
|
curses_new_terminal.c_cc [VMIN] = (unsigned char) 0; // Now it's time to modify it to be raw, this essentially means no-echo. |
|
|
|
curses_new_terminal.c_cc [VTIME] = (unsigned char) 1; |
|
|
|
|
|
|
|
curses_new_terminal.c_iflag &= (unsigned int) ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); // Now it's time to modify it to be raw, this essentially means no-echo. |
|
|
|
curses_new_terminal.c_iflag &= (unsigned int) ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); |
|
|
|
curses_new_terminal.c_oflag &= (unsigned int) ~(OPOST); |
|
|
|
curses_new_terminal.c_cflag |= (unsigned int) (CS8); |
|
|
|
curses_new_terminal.c_lflag &= (unsigned int) ~(ECHO | ICANON | IEXTEN | ISIG); |
|
|
@@ -222,16 +227,50 @@ static void curses_deinitialize (void) { |
|
|
|
"tcsetattr: Failed to set default terminal attributes."); |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
Next two functions are also internal, and we'll use them in our other 'curses_*' functions. The goal of extracting that piece of code into separate functions is to eliminate code |
|
|
|
repetition. Copying and pasting the same piece of code everywhere has pros and cons. Pros are that they're very good indication what should be factored out. The best example that |
|
|
|
comes from top of my head is 'echo' function from chapter zero. It'd be boring to use 'out' everywhere, since it also requires the 'size' argument, and I want to just output |
|
|
|
string literals to terminal in most cases. But that doesn't mean that 'out' is useless, as you'll see, we'll use it in 'curses_synchronize' funcion. Cons are, if you really have |
|
|
|
repeating code all over your program, if there's a bug in one of them, there's a bug in all of them. Also, if you'll use some extracted function (also refered to as refactored in |
|
|
|
some cases) only once, it's not a bad thing. We've extracted some code into 'curses_initialize' function, and we call it only once, inside 'curses_configure' function, but the |
|
|
|
intention behind what it does is clear. However, I still believe that procedural code, that's executed line by line, from top to bottom, is best. |
|
|
|
|
|
|
|
Now, we could also implement some error checking functions for 'curses_*' functions. Good idea, when something is wrong, like we want to render a character out of the screen, we |
|
|
|
just print an error message to terminal, right? Well, no. Remember, we're using terminal as a framebuffer (about which we'll talk about a lot more in ray tracing and rasterization |
|
|
|
chapters), so if we just print message there, it'll corrupt our framebuffer. So, we could just write them to some log file. How about binding them, whenever something is wrong, |
|
|
|
we don't just abort the program or stop rendering, but continue running it, while also writing error messages to that same file. Then, when we exit the program, it'll print all |
|
|
|
error messages (if any) normally. Here's how it would look like: |
|
|
|
|
|
|
|
@C |
|
|
|
// Enumeration for log type. |
|
|
|
enum { |
|
|
|
LOG_FAILURE, LOG_WARNING, LOG_SUCCESS, LOG_COUNT |
|
|
|
}; |
|
|
|
|
|
|
|
static void log_in (int type, int condition, char * message); |
|
|
|
static void log_out (char * name); |
|
|
|
|
|
|
|
// Usage example: |
|
|
|
log_in (LOG_FAILURE, condition != 0, "function_name: The kind of error that occured or a general notification."); |
|
|
|
log_out ("file_name.log"); |
|
|
|
@ |
|
|
|
|
|
|
|
To be honest, I consider that a bad idea, because if we render something that's not supposed to be rendered (we pass wrong arguments to any of the 'curses_render_*' functions), |
|
|
|
it'll be obvious when we look at it. So, I wrote commented-out examples below of how we'd do it, but it won't be compiled into the final object file. If we pass a wrong value to |
|
|
|
those functions, they'll be fixed (modified), and give the wrong results, which will be obvious when we look at the screen. Nothing will crash or burn. |
|
|
|
*/ |
|
|
|
|
|
|
|
static char * curses_screen_offset (int x, int y) { |
|
|
|
// log_in (LOG_FAILURE, x <= -1, "curses_screen_offset: X position is below the lower bound."); |
|
|
|
// log_in (LOG_FAILURE, y <= -1, "curses_screen_offset: Y position is below the lower bound."); |
|
|
|
// log_in (LOG_FAILURE, x >= curses_screen_width, "curses_screen_offset: X position is above the upper bound."); |
|
|
|
// log_in (LOG_FAILURE, y >= curses_screen_height, "curses_screen_offset: Y position is above the upper bound."); |
|
|
|
|
|
|
|
limit (& x, 0, curses_screen_width - 1); |
|
|
|
limit (& y, 0, curses_screen_height - 1); |
|
|
|
limit (& x, 0, curses_screen_width - 1); // We're limiting the values of X and Y coordinates to screen dimensions. |
|
|
|
limit (& y, 0, curses_screen_height - 1); // Function 'limit' uses inclusive values for minimum and maximum. |
|
|
|
|
|
|
|
return (& curses_screen [CURSES_REVERT + CURSES_FORMAT * (y * curses_screen_width + x)]); |
|
|
|
return (& curses_screen [CURSES_REVERT + CURSES_FORMAT * (y * curses_screen_width + x)]); // And returning the offset of the screen buffer. |
|
|
|
} |
|
|
|
|
|
|
|
static char * curses_format_character (char character, int colour, int effect) { |
|
|
@@ -239,15 +278,21 @@ static char * curses_format_character (char character, int colour, int effect) { |
|
|
|
// log_in (LOG_FAILURE, colour >= COLOUR_COUNT, "curses_format_character: Colour is invalid enumeration value."); |
|
|
|
// log_in (LOG_FAILURE, effect >= EFFECT_COUNT, "curses_format_character: Effect is invalid enumeration value."); |
|
|
|
|
|
|
|
if (character_is_invisible (character) != 0) { |
|
|
|
if (character_is_invisible (character) != 0) { // Since we don't want to render invisible ASCII characters, we'll just change 'character' to space. |
|
|
|
character = ' '; |
|
|
|
} |
|
|
|
|
|
|
|
colour %= COLOUR_COUNT; |
|
|
|
colour %= COLOUR_COUNT; // Instead of breaking and burning everything, we'll just "round" it down to one of the allowed values. |
|
|
|
effect %= EFFECT_COUNT; |
|
|
|
|
|
|
|
switch (effect) { |
|
|
|
case EFFECT_NORMAL: effect = 0; break; |
|
|
|
switch (effect) { // Effects aren't enumerated nicely as colours, so here we'll use switch statement instead of nested if-else statements. |
|
|
|
case EFFECT_NORMAL: effect = 0; break; // We could break these into any combination of new lines, we'll just show one example below of how else you can do it. |
|
|
|
// case EFFECT_NORMAL: |
|
|
|
// effect = 0; |
|
|
|
// break; |
|
|
|
// case EFFECT_NORMAL: { |
|
|
|
// effect = 0; |
|
|
|
// } break; |
|
|
|
case EFFECT_BOLD: effect = 1; break; |
|
|
|
case EFFECT_ITALIC: effect = 3; break; |
|
|
|
case EFFECT_UNDERLINE: effect = 4; break; |
|
|
@@ -256,48 +301,46 @@ static char * curses_format_character (char character, int colour, int effect) { |
|
|
|
default: effect = 0; break; |
|
|
|
} |
|
|
|
|
|
|
|
curses_format [2] = (char) effect + '0'; |
|
|
|
curses_format [2] = (char) effect + '0'; // Now, here comes ASCII escape sequence magic, this will format the character to have colour and effect. |
|
|
|
curses_format [5] = (char) colour + '0'; |
|
|
|
curses_format [7] = character; |
|
|
|
|
|
|
|
// log_out ("curses.log"); |
|
|
|
|
|
|
|
return (curses_format); |
|
|
|
return (curses_format); // And we return the value (pointer to character) of formatted internal variables. |
|
|
|
} |
|
|
|
|
|
|
|
static void curses_idle (void) { return; } // If you have a lot of short functions that are intended to be in array of function pointers, you can align them like this. |
|
|
|
static void curses_exit (void) { curses_active = 0; } |
|
|
|
static void curses_exit (void) { curses_active = 0; } // And this is our main function for quitting main loop in curses. |
|
|
|
|
|
|
|
/* |
|
|
|
External function definitions, those found in "chapter_2.h" header file. |
|
|
|
*/ |
|
|
|
|
|
|
|
void curses_configure (void) { |
|
|
|
atexit (curses_deinitialize); |
|
|
|
atexit (curses_deinitialize); // Deinitialization is automatically on exit. |
|
|
|
|
|
|
|
curses_initialize (); |
|
|
|
curses_initialize (); // Initializing curses, yaay. |
|
|
|
|
|
|
|
curses_screen = allocate (CURSES_REVERT + CURSES_FORMAT * curses_screen_width * curses_screen_height + CURSES_CURSOR + 1); |
|
|
|
curses_screen = allocate (CURSES_REVERT + CURSES_FORMAT * curses_screen_width * curses_screen_height + CURSES_CURSOR + 1); // We're requesting new memory for framebuffer. |
|
|
|
|
|
|
|
curses_bind (SIGNAL_ESCAPE, curses_exit); |
|
|
|
curses_bind (SIGNAL_ESCAPE, curses_exit); // Binding universal exit key (signal). |
|
|
|
|
|
|
|
terminal_clear (); |
|
|
|
|
|
|
|
string_copy (& curses_screen [0], "\033[H"); |
|
|
|
string_copy (& curses_screen [0], "\033[H"); // ASCII black magic to always clear screen. |
|
|
|
} |
|
|
|
|
|
|
|
void curses_synchronize (void) { |
|
|
|
int signal; |
|
|
|
|
|
|
|
curses_signal = curses_character = signal = 0; |
|
|
|
curses_signal = curses_character = signal = 0; // Reassigning signals to 0. |
|
|
|
|
|
|
|
out (curses_screen, CURSES_REVERT + CURSES_FORMAT * curses_screen_width * curses_screen_height + CURSES_CURSOR); |
|
|
|
out (curses_screen, CURSES_REVERT + CURSES_FORMAT * curses_screen_width * curses_screen_height + CURSES_CURSOR); // We output the entire framebuffer to terminal (present). |
|
|
|
|
|
|
|
in (& signal, 4); |
|
|
|
in (& signal, (int) sizeof (signal)); // We're now checking for user input to modify it. |
|
|
|
|
|
|
|
curses_character = signal; |
|
|
|
curses_character = signal; // We need literal value of 'signal' for text input. |
|
|
|
|
|
|
|
if (signal == '\033') { |
|
|
|
if (signal == '\033') { // And then we modify the actual 'curses_signal'. |
|
|
|
curses_signal |= SIGNAL_ESCAPE; |
|
|
|
} else if (character_is_digit (signal) != 0) { |
|
|
|
curses_signal |= SIGNAL_0 + (int) (signal - '0'); |
|
|
@@ -310,13 +353,17 @@ void curses_synchronize (void) { |
|
|
|
curses_signal = SIGNAL_NONE; |
|
|
|
} |
|
|
|
|
|
|
|
for (signal = 0; signal != curses_action_count; ++signal) { |
|
|
|
if (curses_signal == curses_activator [signal]) { |
|
|
|
curses_action [signal] (); |
|
|
|
for (signal = 0; signal != curses_action_count; ++signal) { // Now, it's time to loop over bound actions. |
|
|
|
if (curses_signal == curses_activator [signal]) { // If we have a bound signal, then: |
|
|
|
curses_action [signal] (); // We execute corresponding action (function). |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
It's still to early to talk about signal binding and unbinding, so I'll skip over them as they are more complex. |
|
|
|
*/ |
|
|
|
|
|
|
|
void curses_bind (int signal, void (* action) (void)) { |
|
|
|
++curses_action_count; |
|
|
|
|
|
|
@@ -336,36 +383,68 @@ void curses_unbind (int signal) { |
|
|
|
--curses_action_count; |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
Finally, we're getting to rendering, something that we can actually see on our screen. There's ASCII escape sequences involved into this, so lets explain them briefly. We |
|
|
|
mentioned in "chapter_2.h" header file about how some of them work. Now, we're extending them, with minimal amount of error checking in order to keep things more clear. Worth |
|
|
|
noting is that not all terminals support all of ASCII escape sequences, but most of them support at least a subset of them. There are many terminals such as ones provided by |
|
|
|
desktop environment (xfce4-terminal, etc.), and independent ones (st, sakura, xterm, etc.), use whatever, it'll (in 99% of cases) work as intended. |
|
|
|
|
|
|
|
If you want to know what rendering truly is, it's just copying certain value to some offset. Nothing more... |
|
|
|
*/ |
|
|
|
|
|
|
|
void curses_render_cursor (int x, int y) { |
|
|
|
// We're expecting that our terminal dimensions (width and height in unit of 'characters') is always less 1000. If it's not, I really don't care. |
|
|
|
// Also, there's the application of "limited" string functions, since we don't want to copy until null character ('\0') is reached. |
|
|
|
// Lastly, if you remember that 'curses_cursor' is string "\033[---;---H", we're just writing two numbers to "---" parts, from 0 to 999 inclusively. |
|
|
|
// We're adding one because ASCII uses 1 ... 1000, and we use 0 ... 999, so it doesn't causes hidden bugs, and realign them to be prefixed with '0' characters. |
|
|
|
// If we have set of values (x = 31, y = 37), then the copied string would look like "\033[031;037H". It's not complex as it may sound. |
|
|
|
// And remember our ASCII table, thing scary thing (\033) is just octal value for number 27, which is CHARACTER_ESCAPE, hence the escape sequences start with it. |
|
|
|
x %= 1000; |
|
|
|
y %= 1000; |
|
|
|
|
|
|
|
string_copy_limit (curses_cursor + 2, string_realign (number_to_string (y + 1), 3, '0'), 3); |
|
|
|
string_copy_limit (curses_cursor + 6, string_realign (number_to_string (x + 1), 3, '0'), 3); |
|
|
|
|
|
|
|
string_copy_limit (& curses_screen [CURSES_REVERT + CURSES_FORMAT * curses_screen_width * curses_screen_height], curses_cursor, CURSES_CURSOR); |
|
|
|
string_copy_limit (& curses_screen [CURSES_REVERT + CURSES_FORMAT * curses_screen_width * curses_screen_height], curses_cursor, CURSES_CURSOR); // Actual rendering. |
|
|
|
} |
|
|
|
|
|
|
|
void curses_render_character (char character, int colour, int effect, int x, int y) { |
|
|
|
if ((x >= curses_screen_width) || (y >= curses_screen_height)) { |
|
|
|
if ((x < 0) || (x > curses_screen_width - 1) || (y < 0) || (y > curses_screen_height - 1)) { // If any of these are true, we don't render. |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
string_copy_limit (curses_screen_offset (x, y), curses_format_character (character, colour, effect), CURSES_FORMAT); |
|
|
|
// Again, lets show some code formatting examples: |
|
|
|
// if ((x < 0) |
|
|
|
// || (y < 0) |
|
|
|
// || (x > curses_screen_width - 1) |
|
|
|
// || (y > curses_screen_height - 1)) { |
|
|
|
// return; |
|
|
|
// } |
|
|
|
// Or, if you don't want to subtract one from values there: |
|
|
|
// if ((x < 0) || |
|
|
|
// (y < 0) || |
|
|
|
// (x >= curses_screen_width) || |
|
|
|
// (y >= curses_screen_height)) { |
|
|
|
// return; |
|
|
|
// } |
|
|
|
// Or if you really hate adding 2 more lines of code and curly braces: |
|
|
|
// if ((x < 0) || (x > curses_screen_width - 1) || (y < 0) || (y > curses_screen_height - 1)) return; |
|
|
|
|
|
|
|
string_copy_limit (curses_screen_offset (x, y), curses_format_character (character, colour, effect), CURSES_FORMAT); // Again, actual rendering, copying a value to offset. |
|
|
|
} |
|
|
|
|
|
|
|
void curses_render_background (char character, int colour, int effect) { |
|
|
|
int x, y; |
|
|
|
int x, y; // We declare local variables like this, I usually don't initialize them right away. |
|
|
|
|
|
|
|
for (y = 0; y != curses_screen_height; ++y) { |
|
|
|
for (x = 0; x != curses_screen_width; ++x) { |
|
|
|
curses_render_character (character, colour, effect, x, y); |
|
|
|
for (y = 0; y != curses_screen_height; ++y) { // Iterating through rows (by height) of our framebuffer ('curses_screen'). |
|
|
|
for (x = 0; x != curses_screen_width; ++x) { // Iterating through columns (by width) of our framebuffer. |
|
|
|
curses_render_character (character, colour, effect, x, y); // Now, we can use function 'curses_render_character' to simplify our life... |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
We've mentioned before, in chapter zero, that you can implement 'string_*' functions by 'string_*_limit', and here's an example of that. |
|
|
|
We've mentioned before, in chapter zero, that you can implement 'string_*' functions by 'string_*_limit', and here's an example of that. Functions below are quite self |
|
|
|
explanatory, so you can do a "homework" of reading them and trying to understand what they'll do. Since I use very verbose naming style, I hope that won't be a problem... |
|
|
|
*/ |
|
|
|
|
|
|
|
int curses_render_string (char * string, int colour, int effect, int x, int y) { |
|
|
@@ -394,7 +473,7 @@ int curses_render_string_limit (char * string, int limit, int colour, int effect |
|
|
|
return (limit); |
|
|
|
} |
|
|
|
|
|
|
|
int curses_render_number_limit (int number, int limit, int colour, int effect, int x, int y) { |
|
|
|
int curses_render_number_limit (int number, int limit, int colour, int effect, int x, int y) { // TO DO (: |
|
|
|
(void) number; |
|
|
|
(void) limit; |
|
|
|
(void) colour; |
|
|
|