forked from xolatile/xhartae
Explained functions in chapter 2...
This commit is contained in:
parent
95d48ca3bc
commit
d9195c64a0
@ -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
|
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
|
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,
|
||||||
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
|
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
|
||||||
following program structure would look something like this:
|
end call function 'curses_synchronize' again, only once. So the following program structure would look something like this:
|
||||||
|
|
||||||
@C
|
@C
|
||||||
// ...
|
// ...
|
||||||
@ -183,6 +183,11 @@ int main (int argc, char * * argv) {
|
|||||||
return (0);
|
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.
|
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 = 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_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_oflag &= (unsigned int) ~(OPOST);
|
||||||
curses_new_terminal.c_cflag |= (unsigned int) (CS8);
|
curses_new_terminal.c_cflag |= (unsigned int) (CS8);
|
||||||
curses_new_terminal.c_lflag &= (unsigned int) ~(ECHO | ICANON | IEXTEN | ISIG);
|
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.");
|
"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) {
|
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, 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, 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, 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.");
|
// 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 (& 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);
|
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) {
|
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, 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.");
|
// 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 = ' ';
|
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;
|
effect %= EFFECT_COUNT;
|
||||||
|
|
||||||
switch (effect) {
|
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;
|
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_BOLD: effect = 1; break;
|
||||||
case EFFECT_ITALIC: effect = 3; break;
|
case EFFECT_ITALIC: effect = 3; break;
|
||||||
case EFFECT_UNDERLINE: effect = 4; 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;
|
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 [5] = (char) colour + '0';
|
||||||
curses_format [7] = character;
|
curses_format [7] = character;
|
||||||
|
|
||||||
// log_out ("curses.log");
|
return (curses_format); // And we return the value (pointer to character) of formatted internal variables.
|
||||||
|
|
||||||
return (curses_format);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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_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.
|
External function definitions, those found in "chapter_2.h" header file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void curses_configure (void) {
|
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 ();
|
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) {
|
void curses_synchronize (void) {
|
||||||
int signal;
|
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;
|
curses_signal |= SIGNAL_ESCAPE;
|
||||||
} else if (character_is_digit (signal) != 0) {
|
} else if (character_is_digit (signal) != 0) {
|
||||||
curses_signal |= SIGNAL_0 + (int) (signal - '0');
|
curses_signal |= SIGNAL_0 + (int) (signal - '0');
|
||||||
@ -310,13 +353,17 @@ void curses_synchronize (void) {
|
|||||||
curses_signal = SIGNAL_NONE;
|
curses_signal = SIGNAL_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (signal = 0; signal != curses_action_count; ++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 (curses_signal == curses_activator [signal]) { // If we have a bound signal, then:
|
||||||
curses_action [signal] ();
|
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)) {
|
void curses_bind (int signal, void (* action) (void)) {
|
||||||
++curses_action_count;
|
++curses_action_count;
|
||||||
|
|
||||||
@ -336,36 +383,68 @@ void curses_unbind (int signal) {
|
|||||||
--curses_action_count;
|
--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) {
|
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;
|
x %= 1000;
|
||||||
y %= 1000;
|
y %= 1000;
|
||||||
|
|
||||||
string_copy_limit (curses_cursor + 2, string_realign (number_to_string (y + 1), 3, '0'), 3);
|
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_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) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
// 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);
|
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) {
|
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 (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) {
|
for (x = 0; x != curses_screen_width; ++x) { // Iterating through columns (by width) of our framebuffer.
|
||||||
curses_render_character (character, colour, effect, x, y);
|
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) {
|
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);
|
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) number;
|
||||||
(void) limit;
|
(void) limit;
|
||||||
(void) colour;
|
(void) colour;
|
||||||
|
Loading…
Reference in New Issue
Block a user