2023-11-13 21:03:29 -05:00
/*
Copyright ( c ) 2023 : Ognjen ' xolatile ' Milan Robovic
Xhartae is free software ! You will redistribute it or modify it under the terms of the GNU General Public License by Free Software Foundation .
And when you do redistribute it or modify it , it will use either version 3 of the License , or ( at yours truly opinion ) any later version .
It is distributed in the hope that it will be useful or harmful , it really depends . . . But no warranty what so ever , seriously . See GNU / GPLv3 .
*/
# ifndef CHAPTER_2_SOURCE
# define CHAPTER_2_SOURCE
# include "chapter_2.h" // We're copying function and variable declarations from this file here. This shouldn't be copied twice, more on that later...
/*
Function ' hello_world_0 ' uses ( rougly said ) a system call instead of a function , but I don ' t even know what will different compilers do to that line of code . What ' s a system call ?
Well , when you ' re writing assembly , you have some general purpose registers , on mine and your machine , there ' s 16 of them , and you move some data in them , then use ' syscall '
instruction , which magically does something , like open or close a file descriptor , read or write to it , but more on that later ! A lot more . . . Important thing to know is that when
you use keyword ' sizeof ' on a string , it ' ll count null terminator , so it ' ll have value 14 , and type ' size_t ' . It ' s provided in < unistd . h > header file .
@ C
write ( // mov rax 1 ; Literal of Linux write system call, defined internally.
STDOUT_FILENO , // mov rdi 1 ; Literal of standard output file descriptor, defined as 'STDOUT_FILENO'.
" Hello world! \n " , // mov rsi X ; Address of our string.
sizeof ( " Hello world! \n " ) // mov rdx [Y] ; Literal of size of our string.
) ; // syscall ; Ask kernel to do some work.
@
We ' ll talk about assembly a lot in future chapters , but you should filter that out of your brain until you learn C well . . .
*/
void hello_world_0 ( void ) {
write ( STDOUT_FILENO , " Hello world! \n " , sizeof ( " Hello world! \n " ) ) ;
}
/*
In function ' hello_world_1 ' we ' re using function ' puts ' this time , which is considered unsafe because it ' ll fail when input string isn ' t null terminated . Keep in mind that it also
prints line feed ( new line ) on the end . Why I call it line feed ? Some terminals and text editors distinguish carriage return ( ' \r ' / 13 / CR ) and line feed ( ' \n ' / 10 / LF ) , and
some of them use both , like old teleprinters used to do , where the name ' tty ' on UNIX - based operating systems comes from .
*/
void hello_world_1 ( void ) {
puts ( " Hello world! " ) ;
}
/*
Now , in function ' hello_world_2 ' and function ' hello_world_3 ' , we ' re using ' printf ' function , which is in my opinion important for debugging . It has many variations , so we ' ll have
a separate chapter only about it . Know that it ' s a variadic function , which means that it can take more than one input argument , and later we ' ll learn to make our own variadic
functions as well . It ' s also a bit unsafe , if you ' re not careful how you use it , but nothing drastic can happen , most compilers catch those kinds of errors .
*/
void hello_world_2 ( void ) {
printf ( " Hello world! \n " ) ;
}
/*
It ' s also worth noting that we ' re using it ' s arguments in function ' hello_world_3 ' , function ' printf ' will print characters one by one from its ' format , until it encounters ' % ' ,
if the next character is ' % ' , it prints one ' % ' , if it ' s ' s ' , then it ' ll print a string , which we provide in the next argument . When compiler optimizations are turned on , it will
change all sorts of things here , since it ' s obvious that we ' re using simple string in this example .
*/
void hello_world_3 ( void ) {
printf ( " %s %s! \n " , " Hello " , " world " ) ;
}
/*
Lastly , we have a global variable , some people like to call it that , and consider it evil . It ' s not evil if you know the pros and cons of using them , which causes intellectual
overhead for certain sort of people . Pros , makes your code shorter and easier to refactor . Cons , it can potentially make your code less safe if you don ' t know what you ' re doing .
You should use them , external variables as I like to call them , when you ' re working on your programs or in a small group , but they can lead to bad things if you ' re working with a
lot of people , someone could change it in some bad place , and cause a bug that ' s difficult to track .
We could initialize it also in any part of our code later , in some function for example , as following :
@ C
// One by one:
hello_world [ 0 ] = hello_world_0 ;
hello_world [ 1 ] = hello_world_1 ;
hello_world [ 2 ] = hello_world_2 ;
hello_world [ 3 ] = hello_world_3 ;
// Or use it for some completely unrelated function if we wanted to, as long as function "kind" is same, they both take no input and no output:
static void unrelated_function ( void ) {
puts ( " I couldn't care less... " ) ;
exit ( EXIT_FAILURE ) ;
}
hello_world [ 2 ] = unrelated_function ;
@
Just know that an array ( s ) of function pointers is my favorite kind of variable for bot AI in simple games , index can be derived from bot state .
*/
void ( * hello_world [ 4 ] ) ( void ) = {
hello_world_0 ,
hello_world_1 ,
hello_world_2 ,
hello_world_3
} ;
/*
Now , we ' ll talk more about cursed functions ! I broke code formatting rules for this , but it ' s worth it , time to learn . Function or variable with ' static ' instead of ' extern '
keyword before it is internal . That means it ' ll only be accessable in this file . That way , I can include file " chapter_2.h " in some other file , and know that I can ' t access or
modify some internal variable within that other file . They can only be accessed or modified in this file ! Lets show some formatting examples below :
@ C
static int my_size = 0 ;
static char * my_name = " Name " ;
static void * my_data = NULL ;
@
Unlike when declaring external variables ( seen in previous header files ) , you need to initialize internal variables to some value , like you see above . You shouldn ' t initialize
external variables , they have separate definition ( initialization in this case ) . You can see them just below , they are initialized without ' extern ' in front of them , and the same
goes for functions declared in " chapter_2.h " , but there ' s another trick to this . . . You can declare internal functions before defining them , so you have 2 options for them :
@ C
// Option A:
2023-11-26 11:58:59 -05:00
static int my_function ( char * name , int size ) ;
// ...
2023-11-13 21:03:29 -05:00
int my_function ( char * name , int size ) {
int data = 0 ;
// Do some work involving function arguments and modify data.
return ( data ) ;
}
// Option B:
static int my_function ( char * name , int size ) {
int data = 0 ;
// Do some work involving function arguments and modify data.
return ( data ) ;
}
@
Okay , external variables you see lower ( without ' static ' ) are all integers . Lets briefly see what types are the internal variables .
2023-11-26 11:58:59 -05:00
- curses_format / curses_cursor : Array of characters ( also known as string ! ) , and their size is already known at the compile time , it ' s inside square braces .
2023-11-13 21:03:29 -05:00
- curses_screen : Pointer to character , we ' ll allocate memory for it later , so it ' ll be a dynamic array of characters , its ' size can change .
- curses_activator : Pointer to integer , we ' ll also allocate memory for it , so we can ' t use square braces for those . Remember that for later .
- curses_action : Strictly speaking , pointer to another pointer to function of signature ' void SOMETHING ( void ) ; ' , but ignore it for now , don ' t get confused .
*/
static char curses_format [ CURSES_FORMAT + 1 ] = " \033 [-;3-m- \033 [0m " ; // Internal variable holding data for rendering single character, using ASCII escape sequences.
static char curses_cursor [ CURSES_CURSOR + 1 ] = " \033 [---;---H " ; // Internal variable holding data for rendering cursor at some position.
static char * curses_screen = NULL ; // We hold all terminal screen data here, you can think of it as an image, but better word is a framebuffer.
static int curses_action_count = 0 ; // Count of defined actions, these are part of event handling system.
static int * curses_activator = NULL ; // Array of action signals, when they are active, action is executed.
static void ( * * curses_action ) ( void ) = NULL ; // Array of function pointers, we use it to set event actions, so that user can interact with the terminal.
static struct termios curses_old_terminal ; // This is <termios.h> magic for making terminal enter and exit the raw mode.
static struct termios curses_new_terminal ;
// We've explained these external variables in header file already.
int curses_character = 0 ;
int curses_signal = SIGNAL_NONE ;
int curses_screen_width = 0 ;
int curses_screen_height = 0 ;
2023-11-20 10:24:58 -05:00
int curses_active = FALSE ;
2023-11-13 21:03:29 -05:00
/*
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 , 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
// ...
int main ( int argc , char * * argv ) {
// ...
curses_configure ( ) ;
while ( curses_active ! = 0 ) { // Notice the negative condition, the loop will stop then the condition is 0, so when 'curses_active' is 0.
// Calling functions 'curses_render_*' and doing other work...
curses_synchronize ( ) ;
}
// ...
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 ' .
*/
2023-11-20 10:24:58 -05:00
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 ; } // And this is our main function for quitting main loop in curses.
2023-11-13 21:03:29 -05:00
static void curses_initialize ( void ) { // This function will be called when 'curses_configure' is called, automatically.
2023-11-20 10:24:58 -05:00
struct winsize screen_dimension ; // We need this ugly structure for our 'ioctl' function to get the dimensions.
2023-11-29 05:22:44 -05:00
int screen_memory , lines ; // And you can use local variables to shorten some lines of code if you want.
2023-11-13 21:03:29 -05:00
fatal_failure ( ioctl ( STDOUT_FILENO , TIOCGWINSZ , & screen_dimension ) = = - 1 , // If function 'ioctl' failed, we immediately aborting the entire program.
" ioctl: Failed to get terminal dimensions. " ) ; // I split those error messages, you can find your own formatting style.
curses_screen_width = ( int ) screen_dimension . ws_col ; // We get the dimensions of terminal window by calling that 'ioctl' function.
curses_screen_height = ( int ) screen_dimension . ws_row ;
fatal_failure ( tcgetattr ( STDIN_FILENO , & curses_old_terminal ) = = - 1 , // Now we need to obtain data for current non-raw terminal to restore it later.
" tcgetattr: Failed to get default terminal attributes. " ) ;
curses_new_terminal = curses_old_terminal ; // Here we set our raw terminal to be the same as the non-raw one.
2023-11-20 10:24:58 -05:00
curses_new_terminal . c_cc [ VMIN ] = ( unsigned char ) 0 ; // Now it's time to modify it to be raw.
2023-11-13 21:03:29 -05:00
curses_new_terminal . c_cc [ VTIME ] = ( unsigned char ) 1 ;
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 ) ;
fatal_failure ( tcsetattr ( STDIN_FILENO , TCSAFLUSH , & curses_new_terminal ) = = - 1 , // Finally, we're passing intormations to our terminal, and it becomes raw.
" tcsetattr: Failed to set reverse terminal attributes. " ) ;
2023-11-20 10:24:58 -05:00
screen_memory = CURSES_FORMAT * curses_screen_width * curses_screen_height ; // This is square area of our terminal, and multiplied by 12, size of FORMAT.
2023-11-29 05:22:44 -05:00
lines = ( curses_screen_height - 1 ) * 2 ; // This is size of line feed and carriage return to avoid word-wrapping.
2023-11-20 10:24:58 -05:00
2023-11-29 05:22:44 -05:00
curses_screen = allocate ( CURSES_REVERT + screen_memory + lines + CURSES_CURSOR + 1 ) ; // We're requesting new memory for framebuffer.
2023-11-20 10:24:58 -05:00
curses_bind ( SIGNAL_ESCAPE , curses_exit ) ; // Binding universal exit key (signal).
string_copy ( & curses_screen [ 0 ] , " \033 [H " ) ; // ASCII black magic to always clear screen.
2023-11-29 05:22:44 -05:00
for ( lines = 1 ; lines < curses_screen_height ; + + lines ) { // Now it's time to put forced line breaks in raw terminal, without the last one.
int skip = CURSES_REVERT + 2 * ( lines - 1 ) ; // We skip first 3 bytes and previously copied amount of line breaks.
int next = lines * CURSES_FORMAT * curses_screen_width ; // And now we offset full width of our terminal, this makes it faster...
string_copy_limit ( curses_screen + skip + next , " \r \n " , string_length ( " \r \n " ) ) ; // And lastly, we copy those line breaks at this offset into our screen buffer.
} // Keep in mind that word-wrapping is slow on some terminals, hence I use this.
2023-11-29 06:56:22 -05:00
// So, what's difference with using these two examples? Really, nothing.
// string_copy (& string [offset], source);
// string_copy ( string + offset , source);
// Unary operator '&' references the variable, returning it's memory address (pointer of its' type).
// Unary operator '*' (not multiplication!) dereferences the variable, returning value found at some memory address (with type).
// Arrays in C are just pointers to the first element of that array, and since they lay next to each other in memory, we can access them by doing:
// array [element] <=> * (array + sizeof (* array) * element)
// So, to explain, we're adding pointer to the first element of that array with size of one element multiplied by index of wanted element, and dereferencing that.
// Since referencing and then immediately dereferencing something does nothing, we can ommit that '& (* variable)' into just 'variable'.
// & array [element] <=> array + sizeof (* array) * element
// In the end, use whatever you like, compiler will make sure to optimize it, since this is a simple optimization process, it won't cause bugs.
2023-11-20 10:24:58 -05:00
terminal_clear ( ) ;
2023-11-13 21:03:29 -05:00
}
static void curses_deinitialize ( void ) { // This function will be only called once, automatically, at the program exit.
curses_screen = deallocate ( curses_screen ) ; // It's important to deallocate all previously allocated memory.
curses_activator = deallocate ( curses_activator ) ;
curses_action = deallocate ( curses_action ) ;
2023-11-29 05:22:44 -05:00
curses_action_count = 0 ; // I just set everthing into default state, so we can use curses multiple times.
curses_character = 0 ; // This way, it's all safe and clean, even with (de)initializing it twice.
2023-11-20 10:24:58 -05:00
curses_signal = SIGNAL_NONE ;
curses_screen_width = 0 ;
curses_screen_height = 0 ;
curses_active = FALSE ;
2023-11-13 21:03:29 -05:00
terminal_clear ( ) ; // This only make things look prettier, we don't have mess when we exit the program.
fatal_failure ( tcsetattr ( STDIN_FILENO , TCSAFLUSH , & curses_old_terminal ) = = - 1 , // Again, if this fails, we're doomed, we're aborting.
" 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
2023-11-26 11:58:59 -05:00
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 . You can use ' curses_configure '
and main loop with ' curses_synchronize ' inside more functions , and it ' ll clean itself after the program exits .
2023-11-13 21:03:29 -05:00
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
2023-11-26 11:58:59 -05:00
error messages ( if any ) normally . It could be done like something below , but since I know what I ' m doing ( for the most part ) , I ' ll just comment them out inside functions , and
leave ' log_in ' and ' log_out ' unimplemented .
2023-11-13 21:03:29 -05:00
@ 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.");
2023-11-29 05:22:44 -05:00
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.
2023-11-13 21:03:29 -05:00
2023-11-29 05:22:44 -05:00
return ( & curses_screen [ CURSES_REVERT + 2 * y + CURSES_FORMAT * ( y * curses_screen_width + x ) ] ) ; // And returning the offset of the screen buffer.
2023-11-13 21:03:29 -05:00
}
static char * curses_format_character ( char character , int colour , int effect ) {
// log_in (LOG_WARNING, character_is_invisible (character), "curses_format_character: Can not format invisible characters.");
// 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 ) { // Since we don't want to render invisible ASCII characters, we'll just change 'character' to space.
character = ' ' ;
}
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 ) { // 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 ;
case EFFECT_BLINK : effect = 5 ; break ;
case EFFECT_REVERSE : effect = 7 ; break ;
default : effect = 0 ; break ;
}
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 ;
return ( curses_format ) ; // And we return the value (pointer to character) of formatted internal variables.
}
/*
External function definitions , those found in " chapter_2.h " header file .
*/
void curses_configure ( void ) {
2023-11-20 10:24:58 -05:00
curses_active = TRUE ; // This is important part, and something I like to do in my standalone libraries.
2023-11-13 21:03:29 -05:00
2023-11-20 10:24:58 -05:00
atexit ( curses_deinitialize ) ; // Deinitialization is automatically executed on exit point of the program, since we called 'atexit' function.
2023-11-13 21:03:29 -05:00
2023-11-20 10:24:58 -05:00
curses_initialize ( ) ; // Initializing curses, finally, yaay.
2023-11-13 21:03:29 -05:00
}
void curses_synchronize ( void ) {
2023-11-29 05:22:44 -05:00
int signal , length ;
2023-11-13 21:03:29 -05:00
curses_signal = curses_character = signal = 0 ; // Reassigning signals to 0.
2023-11-29 05:22:44 -05:00
length = CURSES_REVERT + CURSES_FORMAT * curses_screen_width * curses_screen_height + 2 * ( curses_screen_height - 1 ) + CURSES_CURSOR ;
out ( curses_screen , length ) ; // We output the entire framebuffer to terminal (present).
2023-11-13 21:03:29 -05:00
in ( & signal , ( int ) sizeof ( signal ) ) ; // We're now checking for user input to modify it.
curses_character = signal ; // We need literal value of 'signal' for text input.
if ( signal = = ' \033 ' ) { // And then we modify the actual 'curses_signal'.
2023-11-26 15:11:45 -05:00
curses_signal = SIGNAL_ESCAPE ;
2023-11-13 21:03:29 -05:00
} else if ( character_is_digit ( ( char ) signal ) ! = 0 ) {
curses_signal | = SIGNAL_0 + ( int ) ( signal - ' 0 ' ) ;
} else if ( character_is_lowercase ( ( char ) signal ) ! = 0 ) {
curses_signal | = SIGNAL_A + ( int ) ( signal - ' a ' ) ;
} else if ( character_is_uppercase ( ( char ) signal ) ! = 0 ) {
curses_signal | = SIGNAL_A + ( int ) ( signal - ' A ' ) ;
curses_signal | = SIGNAL_SHIFT ;
2023-11-26 15:11:45 -05:00
} else if ( ( signal = = SIGNAL_ARROW_UP ) | | ( signal = = SIGNAL_ARROW_DOWN ) | | ( signal = = SIGNAL_ARROW_RIGHT ) | | ( signal = = SIGNAL_ARROW_LEFT ) ) {
curses_signal = signal ;
2023-11-13 21:03:29 -05:00
} else {
curses_signal = SIGNAL_NONE ;
}
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).
2023-11-26 15:11:45 -05:00
break ;
2023-11-13 21:03:29 -05:00
}
}
2023-11-20 10:24:58 -05:00
if ( curses_active = = FALSE ) { // Lastly, if we exited curses, we want to deinitialize.
curses_deinitialize ( ) ; // It's no problem if we do it more than once...
}
2023-11-13 21:03:29 -05:00
}
/*
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 ;
curses_activator = reallocate ( curses_activator , curses_action_count * ( int ) sizeof ( * curses_activator ) ) ;
curses_action = reallocate ( curses_action , curses_action_count * ( int ) sizeof ( * curses_action ) ) ;
curses_activator [ curses_action_count - 1 ] = signal ;
curses_action [ curses_action_count - 1 ] = action ;
}
void curses_unbind ( int signal ) {
( void ) signal ;
curses_activator [ curses_action_count - 1 ] = SIGNAL_NONE ;
curses_action [ curses_action_count - 1 ] = curses_idle ;
- - 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.
2023-11-29 05:22:44 -05:00
int offset ;
x % = 1000 ; // I don't care for terminals bigger than this...
2023-11-13 21:03:29 -05:00
y % = 1000 ;
2023-11-29 05:22:44 -05:00
string_copy_limit ( curses_cursor + 2 , string_realign ( number_to_string ( y + 1 ) , 3 , ' 0 ' ) , 3 ) ; // We're copying 0...999 number as string, with 0s.
string_copy_limit ( curses_cursor + 6 , string_realign ( number_to_string ( x + 1 ) , 3 , ' 0 ' ) , 3 ) ; // Those prefix 0s must be used with this.
2023-11-13 21:03:29 -05:00
2023-11-29 05:22:44 -05:00
offset = CURSES_REVERT + 2 * ( curses_screen_height - 1 ) + CURSES_FORMAT * curses_screen_width * curses_screen_height ; // We need to offset it at the end of our screen.
string_copy_limit ( & curses_screen [ offset ] , curses_cursor , CURSES_CURSOR ) ; // And only then copy cursor data into screen.
2023-11-13 21:03:29 -05:00
}
void curses_render_character ( char character , int colour , int effect , int x , int y ) {
// 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;
2023-11-29 05:22:44 -05:00
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 ;
}
2023-11-13 21:03:29 -05:00
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 ; // We declare local variables like this, I usually don't initialize them right away.
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...
}
}
}
2023-11-29 06:56:22 -05:00
void curses_render_rectangle_fill ( char character , int colour , int effect , int x , int y , int width , int height ) {
2023-11-29 05:22:44 -05:00
for ( int j = 0 ; j < height ; + + j ) { // You can declare type of those iterators in for loops.
for ( int i = 0 ; i < width ; + + i ) { // This only works if you're not using ANSI C (C89 / C90) standard.
curses_render_character ( character , colour , effect , x + i , y + j ) ; // Now, we render character by character again...
2023-11-28 15:30:55 -05:00
}
}
}
2023-11-29 06:56:22 -05:00
void curses_render_rectangle_line ( char character , int colour , int effect , int x , int y , int width , int height ) {
for ( int offset = 0 ; offset < width ; + + offset ) { // Now, we only want to render line, rectangle has 4 lines, so we need 2 loops.
curses_render_character ( character , colour , effect , x + offset , y ) ; // First we're rendering horizontal lines, then vertical lines.
curses_render_character ( character , colour , effect , x + offset , y + height - 1 ) ; // We also need to offset X or Y, depending on rectangle width or height.
}
for ( int offset = 0 ; offset < height ; + + offset ) { // Now, we only want to render line, rectangle has 4 lines, so we need 2 loops.
curses_render_character ( character , colour , effect , x , y + offset ) ; // I prefer to use 'offset' instead of 'i' and 'j', but I have no strict rule.
curses_render_character ( character , colour , effect , x + width - 1 , y + offset ) ; // I'm mixing them here, so you can see what you find more readable.
}
}
2023-11-13 21:03:29 -05:00
/*
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 ) { // Keep in mind, we could reimplement this to be very similar to "limited" version.
return ( curses_render_string_limit ( string , string_length ( string ) , colour , effect , x , y ) ) ;
}
int curses_render_number ( int number , int colour , int effect , int x , int y ) { // This is not particularly smart solution, but it's simple to understand.
return ( curses_render_string ( number_to_string ( number ) , colour , effect , x , y ) ) ;
}
int curses_render_string_limit ( char * string , int limit , int colour , int effect , int x , int y ) { // This uses short "fix" for blank characters, but we'll align them better later.
int offset ;
for ( offset = 0 ; offset ! = limit ; + + offset ) {
if ( string [ offset ] = = ' \n ' ) {
2023-11-30 12:26:42 -05:00
x * = 0 ;
2023-11-13 21:03:29 -05:00
y + = 1 ;
} else if ( string [ offset ] = = ' \t ' ) {
x + = 8 ;
} else {
curses_render_character ( string [ offset ] , colour , effect , x , y ) ;
x + = 1 ;
}
}
return ( limit ) ;
}
int curses_render_number_limit ( int number , int limit , int colour , int effect , int x , int y ) { // Finally, this will align the number to the right and limit it as well.
return ( curses_render_string_limit ( number_to_string ( number ) , limit , colour , effect , x , y ) ) ;
}
# endif