Compare commits

..
5 Commits
Author SHA1 Message Date
Patrick Jakobsen 12fe6abc25 Updated README 2023-10-19 15:16:07 +02:00
Patrick Jakobsen dd406fe7b4 Fixed borked font bitmap allocation size 2023-10-03 18:49:29 +02:00
Patrick Jakobsen 5be35aa3de Fixed issues cropping up from building -O3 2023-10-03 12:21:35 +02:00
Patrick Jakobsen 5e070521bc Can click button inside scrollable 2023-10-01 16:00:10 +02:00
Patrick Jakobsen f06ac04ea9 . 2023-10-01 14:26:45 +02:00
3 changed files with 134 additions and 16 deletions

No files matched your search

+30 -1
View File
@@ -1,2 +1,31 @@
# RDIC # RDIC - Retained Data, Immediate Code
Serious GUI programming has long been dominated by retained mode APIs, where a tree of GUI objects that hold function pointers for callbacks is built and a central "do the GUI thing" procedure is called, which takes care of handling all the GUI functionality and calls the callbacks as necessary.
A competitor to retained mode GUIs is the immediate mode GUI paradigm, which prioritizes locality of GUI element definition and its behavior.
The specification of GUI elements is done by calling procedures that instantiate them every frame that they should appear on the screen.
For instance, a button is achieved by calling some procedure, e.g. `gui_button`, which would return true if the button was clicked on and false otherwise.
There are tradeoffs in choosing one or the other. Retained mode required you to use inversion of control, which to some is a violation of good programming principles.
Immediate mode is in many regards simpler, but has the downside that it forgoes exposing a data structure to the user at all, and has to use clever techniques to identify the same GUI elements across frames.
The RDIC approach combines retained and immediate techniques.
In retained mode it is normal to use handles into a behind-the-scenes data structure, which is used to make state changes requests in order to manipulate the GUI.
RDIC introduces the use of references into an exposed, retained data structure, where you re-use your references across frames, in an immediate style API.
When passing the same, stable reference to e.g. a button call every frame, it is determined that this is in fact the same button across consecutive frames.
RDIC example:
```C
static RDIC_Reference my_button = {0};
if(button(&my_button, "Button Label")) {
printf("Printed if button is clicked.\n");
}
```
The stable data structure in RDIC can be leveraged for features such as accessibility.
One example of how this data structure is used in the example GUI in this repository is conditional redrawing. It stores a "dirty" flag with each node, which is used to skip over non-"dirty" nodes when generating draw commands. This is very useful for the implementation, which draws using only the CPU.
+1 -1
View File
@@ -1 +1 @@
gcc -g -Wall -Wextra $1 -fsanitize=address,undefined gui.c stb_truetype.o -lX11 -lm gcc -g -Wall -Wextra -Wno-stringop-overflow $1 -fsanitize=address,undefined gui.c stb_truetype.o -lX11 -lm
+103 -14
View File
@@ -386,7 +386,7 @@ for(struct {TYPE temp; int i;} GUIWITH_INNER(__LINE__) = {.temp=(storage_variabl
// TODO(Zelaven): The mouse coordinates are in the context, so no need to pass // TODO(Zelaven): The mouse coordinates are in the context, so no need to pass
// them as parameters. // them as parameters.
#undef ENABLE_DEBUG #undef ENABLE_DEBUG
#define ENABLE_DEBUG 1 #define ENABLE_DEBUG 0
void gui_apply_input( void gui_apply_input(
GUI_Context *context, GUI_Context *context,
int mouse_x, int mouse_x,
@@ -421,6 +421,30 @@ void gui_apply_input(
assert(hit_root_subtree != NULL); assert(hit_root_subtree != NULL);
GUI_Subtree *top_subtree_under_cursor = hit_root_subtree;
GUI_Subtree *current_subtree = GUI_SUBTREE_FIRST_CHILD(top_subtree_under_cursor);
int mouse_over_subtree = 0;
while(current_subtree != NULL)
{
// TODO(Zelaven): Should actually test against the rect-intersect between
// the clipnode and the subtree root. The clipnode is only a maximal bound.
mouse_over_subtree = point_in_rect_with_offset(
mouse_x, mouse_y,
// TODO(Zelaven): Test clipnode valid.
current_subtree->clipnode.node->rect,
hit_layer->offset_x, hit_layer->offset_y);
if(mouse_over_subtree)
{
top_subtree_under_cursor = current_subtree;
current_subtree = GUI_SUBTREE_FIRST_CHILD(current_subtree);
}
else
{
current_subtree = GUI_SUBTREE_SIBLING(current_subtree);
}
}
hit_root_subtree = top_subtree_under_cursor;
GUI_Node *top_node_under_cursor = (GUI_Node*)hit_root_subtree->rdic.root; GUI_Node *top_node_under_cursor = (GUI_Node*)hit_root_subtree->rdic.root;
GUI_Node *current_node = (GUI_Node*)top_node_under_cursor->rdic_node.first_child; GUI_Node *current_node = (GUI_Node*)top_node_under_cursor->rdic_node.first_child;
int mouse_over_node = 0; int mouse_over_node = 0;
@@ -586,7 +610,13 @@ GUI_Subtree_Reference gui_get_subtree(
i = i->sibling) i = i->sibling)
{ {
GUI_Subtree *subtree = (GUI_Subtree*)i; GUI_Subtree *subtree = (GUI_Subtree*)i;
// NOTE(Zelaven): This line falsely triggers -Wstringop-overflow and the
// copy into a temporary variable seemingly fixes it.
// For this reason I simply opted to use -Wno-stringop-overflow, as I
// don't want to have a broken flag enabled.
rdic_cull_subtrees(&subtree->rdic, subtree->rdic.root); rdic_cull_subtrees(&subtree->rdic, subtree->rdic.root);
//RDIC_Context *rdic_context = &subtree->rdic;
//rdic_cull_subtrees(rdic_context, subtree->rdic.root);
} }
} }
@@ -870,8 +900,45 @@ void gui_layout_calculate_screen_rects(
} }
} }
void gui_layout_nodes(GUI_Node *root) void gui_layout_nodes(GUI_Subtree *subtree)
{ {
#if 1
GUI_Node *root = (GUI_Node*)subtree->rdic.root;
assert(root != NULL);
for(GUI_Axis2 axis = 0; axis < GUI_AXIS2_COUNT; axis++)
{
gui_layout_calculate_standalone_sizes(root, axis);
gui_layout_calculate_parent_percent_sizes(root, axis);
}
// TODO(Zelaven): Test reference validity.
// TODO(Zelaven): This probably can be done once and passed in.
int x_offset = 0;
int y_offset = 0;
GUI_Node *relative_node = (GUI_Node*)subtree->relative_to.node;
if(relative_node != NULL){
GUI_Rectangle *relative_rectangle = &relative_node->rect;
x_offset = relative_rectangle->x0;
y_offset = relative_rectangle->y0;
//printf("%.*s: %d, %d\n", relative_node->debug_string.length, relative_node->debug_string.cstring, x_offset, y_offset);
int relative_width = relative_rectangle->x1 - relative_rectangle->x0;
int relative_height = relative_rectangle->y1 - relative_rectangle->y0;
x_offset +=
(relative_width * subtree->offset_relative_node_percentage_x) / 100;
y_offset +=
(relative_height * subtree->offset_relative_node_percentage_y) / 100;
}
x_offset += subtree->flat_offset_x;
y_offset += subtree->flat_offset_y;
x_offset +=
(root->computed_size[GUI_AXIS2_X] * subtree->offset_own_size_percentage_x) / 100;
y_offset +=
(root->computed_size[GUI_AXIS2_Y] * subtree->offset_own_size_percentage_y) / 100;
gui_layout_calculate_screen_rects(root, x_offset, y_offset, GUI_LAYOUT_NONE);
#else
//GUI_Node *root = (GUI_Node*)context->first_subtree->rdic.root; //GUI_Node *root = (GUI_Node*)context->first_subtree->rdic.root;
for(GUI_Axis2 axis = 0; axis < GUI_AXIS2_COUNT; axis++) for(GUI_Axis2 axis = 0; axis < GUI_AXIS2_COUNT; axis++)
{ {
@@ -879,6 +946,7 @@ void gui_layout_nodes(GUI_Node *root)
gui_layout_calculate_parent_percent_sizes(root, axis); gui_layout_calculate_parent_percent_sizes(root, axis);
} }
gui_layout_calculate_screen_rects(root, 0, 0, GUI_LAYOUT_NONE); gui_layout_calculate_screen_rects(root, 0, 0, GUI_LAYOUT_NONE);
#endif
} }
@@ -888,8 +956,8 @@ void gui_layout_nodes(GUI_Node *root)
#undef ENABLE_DEBUG #undef ENABLE_DEBUG
#define ENABLE_DEBUG 0 #define ENABLE_DEBUG 0
void gui_generate_draw_commands_inner( void gui_generate_draw_commands_inner(
int x_offset, //int x_offset,
int y_offset, //int y_offset,
GUI_Node *node, GUI_Node *node,
bool dirty, //TODO(Zelaven): Better name. bool dirty, //TODO(Zelaven): Better name.
Memory_Arena *draw_command_arena, Memory_Arena *draw_command_arena,
@@ -911,10 +979,12 @@ void gui_generate_draw_commands_inner(
.text = node->text_string, .text = node->text_string,
.image = node->image, .image = node->image,
}; };
#if 0
(*draw_command).rectangle.x0 += x_offset; (*draw_command).rectangle.x0 += x_offset;
(*draw_command).rectangle.x1 += x_offset; (*draw_command).rectangle.x1 += x_offset;
(*draw_command).rectangle.y0 += y_offset; (*draw_command).rectangle.y0 += y_offset;
(*draw_command).rectangle.y1 += y_offset; (*draw_command).rectangle.y1 += y_offset;
#endif
*out_num_draw_commands += 1; *out_num_draw_commands += 1;
} }
@@ -922,7 +992,7 @@ void gui_generate_draw_commands_inner(
if(sibling != NULL) { if(sibling != NULL) {
GUI_Node *parent = GUI_NODE_PARENT(node); GUI_Node *parent = GUI_NODE_PARENT(node);
gui_generate_draw_commands_inner( gui_generate_draw_commands_inner(
x_offset, y_offset, //x_offset, y_offset,
sibling, sibling,
parent->dirty || sibling->dirty, parent->dirty || sibling->dirty,
draw_command_arena, out_num_draw_commands); draw_command_arena, out_num_draw_commands);
@@ -930,7 +1000,7 @@ void gui_generate_draw_commands_inner(
GUI_Node *first_child = GUI_NODE_FIRST_CHILD(node); GUI_Node *first_child = GUI_NODE_FIRST_CHILD(node);
if(node->rdic_node.first_child != NULL) { if(node->rdic_node.first_child != NULL) {
gui_generate_draw_commands_inner( gui_generate_draw_commands_inner(
x_offset, y_offset, //x_offset, y_offset,
first_child, first_child,
dirty || first_child->dirty, dirty || first_child->dirty,
draw_command_arena, out_num_draw_commands); draw_command_arena, out_num_draw_commands);
@@ -943,6 +1013,7 @@ void gui_generate_draw_commands(
Memory_Arena *draw_command_arena, Memory_Arena *draw_command_arena,
int *out_num_draw_commands) int *out_num_draw_commands)
{ {
#if 0
// TODO(Zelaven): Test reference validity. // TODO(Zelaven): Test reference validity.
// TODO(Zelaven): This probably can be done once and passed in. // TODO(Zelaven): This probably can be done once and passed in.
int x_offset = 0; int x_offset = 0;
@@ -969,6 +1040,10 @@ void gui_generate_draw_commands(
(root->computed_size[GUI_AXIS2_X] * subtree->offset_own_size_percentage_x) / 100; (root->computed_size[GUI_AXIS2_X] * subtree->offset_own_size_percentage_x) / 100;
y_offset += y_offset +=
(root->computed_size[GUI_AXIS2_Y] * subtree->offset_own_size_percentage_y) / 100; (root->computed_size[GUI_AXIS2_Y] * subtree->offset_own_size_percentage_y) / 100;
#else
GUI_Node *root = (GUI_Node*)subtree->rdic.root;
assert(root != NULL);
#endif
assert(draw_command_arena != NULL); assert(draw_command_arena != NULL);
assert(out_num_draw_commands != NULL); assert(out_num_draw_commands != NULL);
@@ -980,7 +1055,7 @@ void gui_generate_draw_commands(
dirtyness |= clipnode.node->dirty; dirtyness |= clipnode.node->dirty;
} }
gui_generate_draw_commands_inner( gui_generate_draw_commands_inner(
x_offset, y_offset, //x_offset, y_offset,
root, dirtyness, root, dirtyness,
draw_command_arena, out_num_draw_commands); draw_command_arena, out_num_draw_commands);
} }
@@ -1099,7 +1174,7 @@ bool gui_mouseover_box(
} }
#undef ENABLE_DEBUG #undef ENABLE_DEBUG
#define ENABLE_DEBUG 1 #define ENABLE_DEBUG 0
bool gui_slider( bool gui_slider(
GUI_Context *context, GUI_Context *context,
GUI_Node_Reference *last_reference, GUI_Node_Reference *last_reference,
@@ -1892,8 +1967,8 @@ void test_gui__subtree(
context->top_layer.pixel_buffer->width, context->top_layer.pixel_buffer->width,
context->top_layer.pixel_buffer->height, context->top_layer.pixel_buffer->height,
GUI_LAYOUT_HORIZONTAL, &outer_style); GUI_LAYOUT_HORIZONTAL, &outer_style);
context->top_layer.offset_x = 50; context->top_layer.offset_x = 70;
context->top_layer.offset_y = 250; context->top_layer.offset_y = 20;
static GUI_Node_Reference top = {0}; static GUI_Node_Reference top = {0};
@@ -1928,6 +2003,7 @@ void test_gui__subtree(
&button_style, button_size)) &button_style, button_size))
{ {
should_dirty = true; should_dirty = true;
printf("Dirtying middle.\n");
} }
static GUI_Node_Reference dirty_button2 = {0}; static GUI_Node_Reference dirty_button2 = {0};
if(gui_dumb_button( if(gui_dumb_button(
@@ -1944,6 +2020,7 @@ void test_gui__subtree(
&button_style, button_size)) &button_style, button_size))
{ {
tooltip_toggle = !tooltip_toggle; tooltip_toggle = !tooltip_toggle;
printf("Toggling tooltip.\n");
} }
static GUI_Node_Reference dirty_button4 = {0}; static GUI_Node_Reference dirty_button4 = {0};
if( if(
@@ -2000,6 +2077,14 @@ void test_gui__subtree(
static GUI_Node_Reference inner_top = {0}; static GUI_Node_Reference inner_top = {0};
inner_top = gui_dumb_block(context, inner_top, &outer_style, element_size); inner_top = gui_dumb_block(context, inner_top, &outer_style, element_size);
((GUI_Node*)inner_top.node)->image = &test_image; ((GUI_Node*)inner_top.node)->image = &test_image;
static GUI_Node_Reference subtree_button = {0};
if(gui_dumb_button(
context, &subtree_button, GUI_STRING("In subtree"),
&element_style, element_size))
{
printf("Hi from subtree!\n");
}
} gui_pop_subtree(context); } gui_pop_subtree(context);
#endif #endif
#if 0 #if 0
@@ -3652,7 +3737,8 @@ void gui_layout_and_draw_subtree(
GUI_Subtree *subtree, GUI_Subtree *subtree,
Pixel_Buffer *pixel_buffer) Pixel_Buffer *pixel_buffer)
{ {
gui_layout_nodes((GUI_Node*)subtree->rdic.root); //gui_layout_nodes((GUI_Node*)subtree->rdic.root);
gui_layout_nodes(subtree);
gui_generate_draw_commands( gui_generate_draw_commands(
subtree, subtree,
context->draw_command_arena, context->draw_command_arena,
@@ -3761,8 +3847,10 @@ int main(void)
struct File_Read_Result font_data = read_file__cruddy(font_file); struct File_Read_Result font_data = read_file__cruddy(font_file);
// TODO(Zelaven): How much data do I have to make sure that I read at minimum? // TODO(Zelaven): How much data do I have to make sure that I read at minimum?
assert(font_data.size > 16); assert(font_data.size > 16);
g_font_bitmap = memory_arena_allocate(&font_arena, sizeof(char), _Alignof(char));
{ {
int font_bitmap_size = g_font_bitmap_width * g_font_bitmap_height;
g_font_bitmap = memory_arena_allocate(
&font_arena, font_bitmap_size, _Alignof(char));
int bake_font_return_value = stbtt_BakeFontBitmap( int bake_font_return_value = stbtt_BakeFontBitmap(
font_data.data, 0, font_data.data, 0,
20.0, 20.0,
@@ -3817,9 +3905,9 @@ int main(void)
.height = xwindow.pixel_buffer.height, .height = xwindow.pixel_buffer.height,
}; };
Pixel_Buffer top_pixel_buffer = (Pixel_Buffer){ Pixel_Buffer top_pixel_buffer = (Pixel_Buffer){
.pixels = malloc(300*200*sizeof(GUI_Color)), .pixels = malloc(300*400*sizeof(GUI_Color)),
.width = 300, .width = 300,
.height = 200, .height = 400,
}; };
assert(background_pixel_buffer.pixels); assert(background_pixel_buffer.pixels);
assert(top_pixel_buffer.pixels); assert(top_pixel_buffer.pixels);
@@ -3953,6 +4041,7 @@ int main(void)
#if 1 #if 1
gui_layout_and_draw(&context); gui_layout_and_draw(&context);
gui_compose_layers(&context, &xwindow.pixel_buffer); gui_compose_layers(&context, &xwindow.pixel_buffer);
//SET_PIXEL(&xwindow.pixel_buffer, 200, 388, 0x00ff0000);
#else #else
for( for(
GUI_Subtree *current = (GUI_Subtree*)context.background_layer.subtree_rdic.root; GUI_Subtree *current = (GUI_Subtree*)context.background_layer.subtree_rdic.root;