You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3297 lines
94 KiB
3297 lines
94 KiB
|
|
|
|
|
|
#include <stdio.h> |
|
#include <stdbool.h> |
|
#include <stdint.h> |
|
#include <assert.h> |
|
|
|
|
|
#define DEBUG(...) if(ENABLE_DEBUG) {printf(__VA_ARGS__);} |
|
|
|
#define ARRAYLENGTH(X) (sizeof(X)/sizeof((X)[0])) |
|
|
|
|
|
bool my_bool = true; |
|
|
|
|
|
int border_thickness = 0; |
|
float roundedness = 60; |
|
|
|
#include "stb_truetype.h" |
|
|
|
void print_bakedchar(char c, stbtt_bakedchar *b) |
|
{ |
|
printf("%c: %d %d %d %d %.2g %.2g %.2g\n", |
|
c, |
|
b->x0, b->y0, b->x1, b->y1, |
|
b->xoff, b->yoff, b->xadvance); |
|
} |
|
stbtt_bakedchar g_baked_font[96]; |
|
unsigned char *g_font_bitmap; |
|
int g_font_bitmap_width = 512; |
|
int g_font_bitmap_height = 512; |
|
|
|
|
|
|
|
|
|
// --- |
|
|
|
|
|
#define arena_allocate_TYPE(arena, TYPE) ((TYPE*)memory_arena_allocate(arena, sizeof(TYPE), _Alignof(TYPE))) |
|
#define arena_allocate_draw_command(arena) arena_allocate_TYPE(arena, GUI_Draw_Command) |
|
|
|
#include "memory_arena.c" |
|
#include "memory_arena_linux.c" |
|
|
|
|
|
// --- |
|
|
|
#define RDIC_IMPLEMENTATION |
|
#include "../rdic.h" |
|
|
|
typedef struct { |
|
// Top-left corner. |
|
int x0, y0; |
|
// Bottom-Right corner. |
|
int x1, y1; |
|
} GUI_Rectangle; |
|
|
|
// NOTE(Zelaven): I would have loved to do pascal strings here, but I could not |
|
// find any way to pass those as literals in a reasonable way. |
|
// I also use int instead of ssize_t or anything else because of the .*s format |
|
// specifier being an absolute pain. |
|
typedef struct { |
|
int length; |
|
char *cstring; |
|
} GUI_String; |
|
#define GUI_STRING(literal) (GUI_String){.length = sizeof(literal)-1, .cstring = literal} |
|
|
|
typedef uint32_t GUI_Color; |
|
typedef struct { |
|
GUI_Color *pixels; |
|
int width; |
|
int height; |
|
} Pixel_Buffer; |
|
|
|
// TODO(Zelaven): Add a generation to this that you can |
|
// increment so that the nodes that use a style can know |
|
// when up dirty themselves due to style change? |
|
typedef struct { |
|
// TODO(Zelaven): Replace these with GUI_Color. |
|
unsigned char red; |
|
unsigned char green; |
|
unsigned char blue; |
|
|
|
GUI_Color border_color; |
|
int border_thickness; |
|
unsigned char roundedness; |
|
} GUI_Style; |
|
|
|
static inline int point_in_rect(int x, int y, GUI_Rectangle rect) |
|
{ |
|
return |
|
(x >= rect.x0) |
|
&& (y >= rect.y0) |
|
&& (x <= rect.x1) |
|
&& (y <= rect.y1); |
|
} |
|
|
|
|
|
typedef enum { |
|
GUI_SIZERULE_PIXELS, |
|
GUI_SIZERULE_TEXTCONTENT, |
|
GUI_SIZERULE_PERCENTOFPARENT, |
|
GUI_SIZERULE_SUMOFCHILDREN, |
|
//GUI_SIZERULE_SHARES, |
|
} GUI_Size_Rule; |
|
|
|
typedef struct GUI_Size |
|
{ |
|
GUI_Size_Rule size_rule; |
|
int value; |
|
int strictness; |
|
} GUI_Size; |
|
|
|
typedef enum GUI_Axis2 |
|
{ |
|
GUI_AXIS2_X, |
|
GUI_AXIS2_Y, |
|
GUI_AXIS2_COUNT, |
|
} GUI_Axis2; |
|
|
|
typedef enum { |
|
GUI_LAYOUT_NONE, |
|
GUI_LAYOUT_HORIZONTAL, |
|
GUI_LAYOUT_VERTICAL, |
|
} GUI_Layout_Direction; |
|
|
|
typedef struct GUI_Node { |
|
#define RDIC_GUI_NODE_MEMBER_NAME rdic_node |
|
RDIC_Node rdic_node; |
|
GUI_Style *style; |
|
GUI_String text_string; // Optional. |
|
int text_width, text_height; // Optional. Initialized by lazy_init and cached. |
|
Pixel_Buffer *image; |
|
|
|
GUI_String debug_string; |
|
|
|
// NOTE(Zelaven): Useful for tab-navigation and other non-mouse navigation. |
|
GUI_Layout_Direction layout_direction; |
|
GUI_Size semantic_size[GUI_AXIS2_COUNT]; |
|
// TODO(Zelaven): This needs a better name. |
|
// True iff. it (and its children) should generate draw commands. |
|
bool dirty; |
|
|
|
int computed_size[GUI_AXIS2_COUNT]; |
|
GUI_Rectangle rect; // NOTE(Zelaven): I have no better name for this. |
|
} GUI_Node; |
|
#define GUI_NODE_PARENT(node) ((GUI_Node*)(node)->rdic_node.parent) |
|
#define GUI_NODE_SIBLING(node) ((GUI_Node*)(node)->rdic_node.sibling) |
|
#define GUI_NODE_FIRST_CHILD(node) ((GUI_Node*)(node)->rdic_node.first_child) |
|
|
|
|
|
|
|
typedef struct { |
|
GUI_Rectangle rectangle; |
|
GUI_Color color; |
|
GUI_Color border_color; |
|
int border_thickness; |
|
unsigned char roundedness; |
|
GUI_String text; |
|
Pixel_Buffer *image; |
|
} GUI_Draw_Command; |
|
|
|
typedef struct GUI_Subtree GUI_Subtree; |
|
struct GUI_Subtree { |
|
RDIC_Context rdic; |
|
GUI_Subtree *prev; |
|
GUI_Subtree *next; |
|
GUI_Subtree *structural_parent; |
|
|
|
RDIC_Node_Reference relative_to; |
|
int flat_offset_x; |
|
int flat_offset_y; |
|
int offset_relative_node_percentage_x; |
|
int offset_relative_node_percentage_y; |
|
int offset_own_size_percentage_x; |
|
int offset_own_size_percentage_y; |
|
|
|
GUI_Draw_Command *first_draw_command; |
|
int num_draw_commands; |
|
Pixel_Buffer *pixel_buffer; |
|
}; |
|
|
|
|
|
typedef struct { |
|
//RDIC_Context rdic; |
|
|
|
GUI_Subtree *first_subtree; |
|
GUI_Subtree *last_subtree; |
|
GUI_Subtree *current_subtree; |
|
|
|
RDIC_Node_Reference focused_node; |
|
RDIC_Node_Reference hovered_node; |
|
RDIC_Node_Reference top_node_under_cursor; |
|
int mouse_pressed; |
|
int mouse_down; |
|
int mouse_x; |
|
int mouse_y; |
|
|
|
// TODO: An allocator for the nodes. |
|
RDIC_Node *node_freelist; |
|
|
|
Memory_Arena *draw_command_arena; |
|
int num_draw_commands; |
|
} GUI_Context; |
|
|
|
|
|
|
|
|
|
|
|
// --- |
|
|
|
#if 0 |
|
void print_node_tree(GUI_Node *node, int level) |
|
{ |
|
for(int i = 0; i < level; i++) |
|
{ |
|
printf("-"); |
|
} |
|
|
|
GUI_String text = node->debug_string; |
|
printf(" %.*s\n", text.length, text.cstring); |
|
|
|
GUI_Node *child = node->rdic_node.first_child; |
|
while(child != NULL) |
|
{ |
|
print_node_tree(child, level+1); |
|
child = child->rdic_node.sibling; |
|
} |
|
} |
|
#endif |
|
|
|
// --- |
|
|
|
|
|
// NOTE(Zelaven): This won't be used, but it will stay around as a reference for |
|
// the technique. |
|
#if 0 |
|
#define GUIWITH_INNER2(token) defer ## token |
|
#define GUIWITH_INNER(token) GUIWITH_INNER2(token) |
|
/* |
|
* NOTE(Zelaven): This is a "defer loop" macro, fitted as a "with" construct. |
|
* What it is used for is temporarily overwriting a value, keeping the original |
|
* value safe in a loop-local variable. |
|
* The operation functions by declaring the loop variable of an anonymous struct |
|
* type, which contains the temporary variable as well as the actual loop |
|
* variable. |
|
* The temporary is initialized with the contents of the stored value, which is |
|
* then overwritten with the "with" value, and at the end of the loop the |
|
* stored value is restored to contain the contents of the temporary. |
|
* It is important to note that for this to operate correctly, the temporary |
|
* needs to be filled out before the stored value is overwritten, which is |
|
* dependent on the ORDER OF THE STRUCT MEMBERS. |
|
* This is because the overwriting of the stored value is done in the |
|
* initialization of the loop variable part of the struct, and the members are |
|
* initialized in member order, not initializer declaration order. |
|
* I tested this, but both are arranged in the desired order for both reading |
|
* and in the case that it proves to be compiler-dependent. |
|
* (Note that it appears to be consistent across optimization flags) |
|
*/ |
|
#define GUIWITH(storage_variable, TYPE, value_to_use)\ |
|
for(struct {TYPE temp; int i;} GUIWITH_INNER(__LINE__) = {.temp=(storage_variable), .i = ((storage_variable) = (value_to_use), 0)}; !GUIWITH_INNER(__LINE__).i; GUIWITH_INNER(__LINE__).i += 1, ((storage_variable) = GUIWITH_INNER(__LINE__).temp)) |
|
|
|
#define GUI_WITH_REDNESS(context, value) GUIWITH(context->style.red, unsigned char, value) |
|
#define GUI_WITH_BLUENESS(context, value) GUIWITH(context->style.blue, unsigned char, value) |
|
#define GUI_WITH_GREENNESS(context, value) GUIWITH(context->style.green, unsigned char, value) |
|
#define GUI_WITH_STYLE(context, value) GUIWITH(context->style, GUI_Style, value) |
|
|
|
#define GUI_WITH_BORDER_COLOR(context, value) GUIWITH((context)->style.border_color, GUI_Color, (value)) |
|
#define GUI_WITH_BORDER_THICKNESS(context, value) GUIWITH((context)->style.border_thickness, int, (value)) |
|
#define GUI_WITH_ROUNDEDNESS(context, value) GUIWITH((context)->style.roundedness, unsigned char, (value)) |
|
|
|
void fff(GUI_Context *context) |
|
{ |
|
GUI_WITH_REDNESS(context, 222) |
|
printf("%d\n", context->style.red); |
|
GUI_Style style = (GUI_Style){2,2,2,0,0,0}; |
|
GUI_WITH_STYLE(context, style) |
|
GUI_WITH_STYLE(context, ((GUI_Style){2,2,2,0,0,0})) |
|
printf("%d\n", context->style.red); |
|
} |
|
#endif |
|
|
|
|
|
|
|
|
|
// --- |
|
|
|
|
|
// TODO(Zelaven): The mouse coordinates are in the context, so no need to pass |
|
// them as parameters. |
|
#undef ENABLE_DEBUG |
|
#define ENABLE_DEBUG 0 |
|
void gui_apply_input( |
|
GUI_Context *context, |
|
int mouse_x, |
|
int mouse_y) |
|
{ |
|
//GUI_Node *top_node_under_cursor = (GUI_Node*)context->rdic.root; |
|
GUI_Node *top_node_under_cursor = (GUI_Node*)context->first_subtree->rdic.root; |
|
GUI_Node *current_node = (GUI_Node*)top_node_under_cursor->rdic_node.first_child; |
|
int mouse_over_node = 0; |
|
while(current_node != NULL) |
|
{ |
|
mouse_over_node = point_in_rect(mouse_x, mouse_y, current_node->rect); |
|
if(mouse_over_node) |
|
{ |
|
top_node_under_cursor = current_node; |
|
current_node = (GUI_Node*)current_node->rdic_node.first_child; |
|
} |
|
else |
|
{ |
|
current_node = (GUI_Node*)current_node->rdic_node.sibling; |
|
} |
|
} |
|
|
|
context->top_node_under_cursor.node = &top_node_under_cursor->rdic_node; |
|
context->top_node_under_cursor.generation = |
|
top_node_under_cursor->rdic_node.generation; |
|
DEBUG("Top node under cursor: %.*s\n", |
|
top_node_under_cursor->debug_string.length, |
|
top_node_under_cursor->debug_string.cstring); |
|
} |
|
|
|
|
|
RDIC_Node_Reference gui_get_node( |
|
GUI_Context *context, |
|
RDIC_Node_Reference last_reference, |
|
GUI_Layout_Direction direction, |
|
GUI_Style *style, |
|
GUI_Size size[static 2]) |
|
{ |
|
int get_node_flags = 0; |
|
RDIC_Node_Reference new_reference = |
|
rdic_get_node(&context->current_subtree->rdic, last_reference, &get_node_flags); |
|
GUI_Node *node = (GUI_Node*)new_reference.node; |
|
node->layout_direction = direction; |
|
node->style = style; |
|
node->semantic_size[GUI_AXIS2_X] = size[GUI_AXIS2_X]; |
|
node->semantic_size[GUI_AXIS2_Y] = size[GUI_AXIS2_Y]; |
|
|
|
// TODO(Zelaven): |
|
// get_node_flags & RDIC_GET_NODE__OUT_OF_FREELIST |
|
// ^ This should result in an attempt to allocate more nodes for the freelist. |
|
// Then rdic_get_node should be retried. |
|
|
|
if(get_node_flags & RDIC_GET_NODE__NODES_COLLECTED) |
|
{ |
|
GUI_NODE_PARENT(node)->dirty = true; |
|
} |
|
if(get_node_flags & RDIC_GET_NODE__NODE_ALLOCATED) |
|
{ |
|
GUI_NODE_PARENT(node)->dirty = true; |
|
} |
|
node->dirty = (get_node_flags & RDIC_GET_NODE__NODE_ALLOCATED) != 0; |
|
|
|
|
|
return new_reference; |
|
} |
|
|
|
#undef ENABLE_DEBUG |
|
#define ENABLE_DEBUG 0 |
|
// TODO(Zelaven): Make this cause redrawing of root on parameter change. |
|
// Maybe an explicit parameter so it can be based on received events? |
|
// Such as expose events, resize events, etc. |
|
// May also be a good idea because struct-comparing styles each frame wouldn't |
|
// be good for performance, so a force-redraw bool sounds like an always-good |
|
// option. That way you can force-dirty the root when you make a style change, |
|
// but it could also be added at other granularity. |
|
// Or should it be something that is done by just setting |
|
// ref.node->dirty = true? |
|
// That is one option, specifying the dirty flag as a part of the API itself. |
|
// I think I like that option. |
|
RDIC_Node_Reference gui_context_start_frame( |
|
GUI_Context *context, |
|
RDIC_Node_Reference last_root_reference, |
|
int width, int height, |
|
GUI_Layout_Direction layout_direction, |
|
GUI_Style *style) |
|
{ |
|
//context->frame_current_node = context->root; |
|
context->num_draw_commands = 0; |
|
memory_arena_reset(context->draw_command_arena); |
|
|
|
DEBUG("%s: %d %d %d %d\n", __func__, |
|
context->mouse_x, context->mouse_y, |
|
context->mouse_pressed, context->mouse_down); |
|
|
|
static GUI_Subtree background_subtree = {0}; |
|
background_subtree.rdic.node_freelist = context->node_freelist; |
|
context->first_subtree = &background_subtree; |
|
context->last_subtree = &background_subtree; |
|
context->current_subtree = &background_subtree; |
|
|
|
RDIC_Node_Reference new_root_reference = rdic_context_start_frame( |
|
&background_subtree.rdic, |
|
last_root_reference); |
|
GUI_Node *root = (GUI_Node*)new_root_reference.node; |
|
root->dirty = false; |
|
if(!gui_node_references_equal(last_root_reference, new_root_reference)) { |
|
root->dirty = true; |
|
} |
|
if(layout_direction != root->layout_direction) { |
|
root->dirty = true; |
|
} |
|
|
|
gui_apply_input(context, context->mouse_x, context->mouse_y); |
|
|
|
root->debug_string = GUI_STRING("root"); |
|
root->layout_direction = layout_direction; |
|
root->semantic_size[GUI_AXIS2_X] = (GUI_Size) |
|
{.size_rule = GUI_SIZERULE_PIXELS, .value = width, .strictness = 100,}; |
|
root->semantic_size[GUI_AXIS2_Y] = (GUI_Size) |
|
{.size_rule = GUI_SIZERULE_PIXELS, .value = height, .strictness = 100,}; |
|
root->style = style; |
|
|
|
return new_root_reference; |
|
} |
|
void gui_context_finish_frame( |
|
GUI_Context *context) |
|
{ |
|
rdic_context_finish_frame(&context->first_subtree->rdic); |
|
context->node_freelist = context->first_subtree->rdic.node_freelist; |
|
} |
|
|
|
RDIC_Node_Reference gui_push_subtree( |
|
GUI_Context *context, |
|
GUI_Subtree *subtree, |
|
RDIC_Node_Reference last_root_reference, |
|
GUI_Layout_Direction layout_direction, |
|
GUI_Style *style, |
|
GUI_Size size[static 2], |
|
RDIC_Node_Reference relative_to) |
|
{ |
|
context->node_freelist = context->current_subtree->rdic.node_freelist; |
|
subtree->rdic.node_freelist = context->node_freelist; |
|
|
|
context->last_subtree->next = subtree; |
|
subtree->prev = context->last_subtree; |
|
context->last_subtree = subtree; |
|
|
|
subtree->structural_parent = context->current_subtree; |
|
context->current_subtree = subtree; |
|
subtree->relative_to = relative_to; |
|
|
|
RDIC_Node_Reference new_root_reference = rdic_context_start_frame( |
|
&subtree->rdic, |
|
last_root_reference); |
|
GUI_Node *root = (GUI_Node*)new_root_reference.node; |
|
root->dirty = false; |
|
if(!gui_node_references_equal(last_root_reference, new_root_reference)) { |
|
root->dirty = true; |
|
} |
|
if(layout_direction != root->layout_direction) { |
|
root->dirty = true; |
|
} |
|
|
|
root->debug_string = GUI_STRING("gui_push_subtree"); |
|
root->layout_direction = layout_direction; |
|
root->semantic_size[GUI_AXIS2_X] = size[GUI_AXIS2_X]; |
|
root->semantic_size[GUI_AXIS2_Y] = size[GUI_AXIS2_Y]; |
|
root->style = style; |
|
|
|
return new_root_reference; |
|
} |
|
|
|
void gui_pop_subtree( |
|
GUI_Context *context) |
|
{ |
|
context->node_freelist = context->current_subtree->rdic.node_freelist; |
|
context->current_subtree = context->current_subtree->structural_parent; |
|
context->current_subtree->rdic.node_freelist = context->node_freelist; |
|
} |
|
|
|
|
|
void gui_push_parent( |
|
GUI_Context *context, |
|
RDIC_Node_Reference parent) |
|
{ |
|
rdic_push_parent(&context->current_subtree->rdic, parent); |
|
} |
|
|
|
void gui_pop_parent( |
|
GUI_Context *context) |
|
{ |
|
rdic_pop_parent(&context->current_subtree->rdic); |
|
} |
|
|
|
|
|
|
|
// --- |
|
|
|
void gui_layout_calculate_standalone_sizes( |
|
GUI_Node *node, |
|
GUI_Axis2 axis) |
|
{ |
|
switch(node->semantic_size[axis].size_rule) |
|
{ |
|
case GUI_SIZERULE_PIXELS: |
|
{ |
|
node->computed_size[axis] = node->semantic_size[axis].value; |
|
} break; |
|
case GUI_SIZERULE_TEXTCONTENT: |
|
{ |
|
// TODO(Zelaven): Calculate text width. |
|
node->computed_size[axis] = 0; |
|
} break; |
|
default: |
|
{ |
|
//assert(!"Non-standalone size passed to calculate_standalone_sizes."); |
|
} break; |
|
} |
|
if(node->rdic_node.first_child != NULL) { |
|
gui_layout_calculate_standalone_sizes(GUI_NODE_FIRST_CHILD(node), axis); |
|
} |
|
if(node->rdic_node.sibling != NULL) { |
|
gui_layout_calculate_standalone_sizes(GUI_NODE_SIBLING(node), axis); |
|
} |
|
} |
|
void gui_layout_calculate_parent_percent_sizes( |
|
GUI_Node *node, |
|
GUI_Axis2 axis) |
|
{ |
|
if(node->semantic_size[axis].size_rule == GUI_SIZERULE_PERCENTOFPARENT) |
|
{ |
|
int percentage = node->semantic_size[axis].value; |
|
node->computed_size[axis] = |
|
(GUI_NODE_PARENT(node)->computed_size[axis] * percentage) / 100; |
|
} |
|
if(node->rdic_node.first_child != NULL) { |
|
gui_layout_calculate_parent_percent_sizes(GUI_NODE_FIRST_CHILD(node), axis); |
|
} |
|
if(node->rdic_node.sibling != NULL) { |
|
gui_layout_calculate_parent_percent_sizes(GUI_NODE_SIBLING(node), axis); |
|
} |
|
} |
|
|
|
void gui_layout_calculate_screen_rects( |
|
GUI_Node *node, |
|
int x, int y, |
|
GUI_Layout_Direction layout_direction) |
|
{ |
|
int width = node->computed_size[GUI_AXIS2_X]; |
|
int height = node->computed_size[GUI_AXIS2_Y]; |
|
node->rect = (GUI_Rectangle){x, y, x+width, y+height}; |
|
if(node->rdic_node.first_child != NULL) { |
|
gui_layout_calculate_screen_rects( |
|
GUI_NODE_FIRST_CHILD(node), x, y, node->layout_direction); |
|
} |
|
if(node->rdic_node.sibling != NULL) { |
|
int sibling_x = x; |
|
int sibling_y = y; |
|
if(layout_direction == GUI_LAYOUT_HORIZONTAL) |
|
{ |
|
sibling_x += width; |
|
} |
|
if(layout_direction == GUI_LAYOUT_VERTICAL) |
|
{ |
|
sibling_y += height; |
|
} |
|
gui_layout_calculate_screen_rects( |
|
GUI_NODE_SIBLING(node), sibling_x, sibling_y, layout_direction); |
|
} |
|
} |
|
|
|
void gui_layout_nodes(GUI_Node *root) |
|
{ |
|
//GUI_Node *root = (GUI_Node*)context->first_subtree->rdic.root; |
|
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); |
|
} |
|
gui_layout_calculate_screen_rects(root, 0, 0, GUI_LAYOUT_NONE); |
|
} |
|
|
|
|
|
// --- |
|
|
|
|
|
#undef ENABLE_DEBUG |
|
#define ENABLE_DEBUG 0 |
|
void gui_generate_draw_commands_inner( |
|
int x_offset, |
|
int y_offset, |
|
GUI_Node *node, |
|
bool dirty, //TODO(Zelaven): Better name. |
|
Memory_Arena *draw_command_arena, |
|
int *out_num_draw_commands) |
|
{ |
|
DEBUG("%.*s: dirtiness: %b\n", |
|
node->debug_string.length, node->debug_string.cstring, dirty); |
|
if(dirty) { |
|
GUI_Draw_Command *draw_command = |
|
arena_allocate_draw_command(draw_command_arena); |
|
DEBUG("%d, %d, %d, %x, %d, %u\n", node->style->red, node->style->green, node->style->blue, node->style->border_color, node->style->border_thickness, node->style->roundedness); |
|
*draw_command = (GUI_Draw_Command){ |
|
.rectangle = node->rect, |
|
.color = node->style->red<<16 | node->style->green<<8 | node->style->blue, |
|
.border_color = node->style->border_color, |
|
.border_thickness = node->style->border_thickness, |
|
.roundedness = node->style->roundedness, |
|
.text = node->text_string, |
|
.image = node->image, |
|
}; |
|
(*draw_command).rectangle.x0 += x_offset; |
|
(*draw_command).rectangle.x1 += x_offset; |
|
(*draw_command).rectangle.y0 += y_offset; |
|
(*draw_command).rectangle.y1 += y_offset; |
|
*out_num_draw_commands += 1; |
|
} |
|
|
|
GUI_Node *sibling = GUI_NODE_SIBLING(node); |
|
if(sibling != NULL) { |
|
gui_generate_draw_commands_inner( |
|
x_offset, y_offset, |
|
sibling, |
|
dirty || sibling->dirty, |
|
draw_command_arena, out_num_draw_commands); |
|
} |
|
GUI_Node *first_child = GUI_NODE_FIRST_CHILD(node); |
|
if(node->rdic_node.first_child != NULL) { |
|
gui_generate_draw_commands_inner( |
|
x_offset, y_offset, |
|
first_child, |
|
dirty || first_child->dirty, |
|
draw_command_arena, out_num_draw_commands); |
|
} |
|
} |
|
|
|
void gui_generate_draw_commands( |
|
//GUI_Node *root, |
|
GUI_Subtree *subtree, |
|
Memory_Arena *draw_command_arena, |
|
int *out_num_draw_commands) |
|
{ |
|
// 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; |
|
|
|
GUI_Node *root = (GUI_Node*)subtree->rdic.root; |
|
assert(root != NULL); |
|
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; |
|
|
|
assert(draw_command_arena != NULL); |
|
assert(out_num_draw_commands != NULL); |
|
*out_num_draw_commands = 0; |
|
memory_arena_reset(draw_command_arena); |
|
gui_generate_draw_commands_inner( |
|
x_offset, y_offset, |
|
root, root->dirty, |
|
draw_command_arena, out_num_draw_commands); |
|
} |
|
|
|
|
|
|
|
// --- |
|
|
|
|
|
|
|
|
|
RDIC_Node_Reference gui_layout( |
|
GUI_Context *context, |
|
RDIC_Node_Reference last_reference, |
|
GUI_Layout_Direction direction, |
|
GUI_Style *style, |
|
GUI_Size size[static 2]) |
|
{ |
|
RDIC_Node_Reference new_reference = gui_get_node( |
|
context, last_reference, direction, style, size); |
|
GUI_Node *node = (GUI_Node*)new_reference.node; |
|
node->text_string = (GUI_String){0}; |
|
node->debug_string = GUI_STRING("gui_layout"); |
|
return new_reference; |
|
} |
|
|
|
|
|
// --- |
|
|
|
|
|
RDIC_Node_Reference gui_dumb_block( |
|
GUI_Context *context, |
|
RDIC_Node_Reference last_reference, |
|
GUI_Style *style, |
|
GUI_Size size[static 2]) |
|
{ |
|
RDIC_Node_Reference new_reference = gui_get_node( |
|
context, last_reference, GUI_LAYOUT_NONE, style, size); |
|
GUI_Node *node = (GUI_Node*)new_reference.node; |
|
node->text_string = (GUI_String){0}; |
|
node->debug_string = GUI_STRING("gui_dumb_block"); |
|
return new_reference; |
|
} |
|
|
|
#undef ENABLE_DEBUG |
|
#define ENABLE_DEBUG 0 |
|
bool gui_dumb_button( |
|
GUI_Context *context, |
|
RDIC_Node_Reference *last_reference, |
|
GUI_String text, |
|
GUI_Style *style, |
|
GUI_Size size[static 2]) |
|
{ |
|
RDIC_Node_Reference new_reference = gui_get_node( |
|
context, *last_reference, GUI_LAYOUT_NONE, style, size); |
|
GUI_Node *node = (GUI_Node*)new_reference.node; |
|
node->debug_string = text; |
|
node->text_string = text; |
|
|
|
bool clicked = false; |
|
if(gui_node_references_equal( |
|
new_reference, context->top_node_under_cursor)) |
|
{ |
|
DEBUG("%.*s: reference is the top node under cursor.\n", |
|
text.length, text.cstring); |
|
if(context->mouse_pressed) |
|
{ |
|
DEBUG(" Mouse pressed on node\n"); |
|
context->focused_node = new_reference; |
|
} |
|
else if(!context->mouse_down) |
|
{ |
|
DEBUG(" Mouse released on node\n"); |
|
if(gui_node_references_equal(new_reference, context->focused_node)) |
|
{ |
|
DEBUG(" Mouse released over same node as pressed\n"); |
|
context->focused_node.node = NULL; |
|
clicked = true; |
|
} |
|
} |
|
} |
|
else if(gui_node_references_equal(new_reference, context->focused_node) |
|
&& !context->mouse_down) |
|
{ |
|
context->focused_node.node = NULL; |
|
} |
|
|
|
*last_reference = new_reference; |
|
return clicked; |
|
} |
|
|
|
#undef ENABLE_DEBUG |
|
#define ENABLE_DEBUG 0 |
|
bool gui_slider( |
|
GUI_Context *context, |
|
RDIC_Node_Reference *last_reference, |
|
RDIC_Node_Reference *inner_box_reference, |
|
float *value, |
|
GUI_Style *background_style, |
|
GUI_Style *bar_style, |
|
GUI_Size size[static 2]) |
|
{ |
|
RDIC_Node_Reference new_reference = gui_get_node( |
|
context, *last_reference, GUI_LAYOUT_NONE, background_style, size); |
|
GUI_Node *node = (GUI_Node*)new_reference.node; |
|
node->debug_string = GUI_STRING("gui_slider"); |
|
node->text_string = (GUI_String){0}; |
|
|
|
// NOTE(Zelaven): We need a handle to this node, but we set its values later. |
|
gui_push_parent(context, new_reference); |
|
RDIC_Node_Reference new_inner_reference = gui_get_node( |
|
context, *inner_box_reference, GUI_LAYOUT_NONE, bar_style, size); |
|
|
|
bool value_changed = false; |
|
bool new_ref_is_top = gui_node_references_equal( |
|
new_reference, context->top_node_under_cursor); |
|
bool inner_ref_is_top = gui_node_references_equal( |
|
new_inner_reference, context->top_node_under_cursor); |
|
DEBUG("%p\n | %p %p\n | %p %p\n", |
|
context->top_node_under_cursor.node, |
|
last_reference->node, inner_box_reference->node, |
|
new_reference.node, new_inner_reference.node); |
|
if(new_ref_is_top || inner_ref_is_top) |
|
{ |
|
DEBUG("\n%s: reference is the top node under cursor.\n", __func__); |
|
if(context->mouse_pressed) |
|
{ |
|
DEBUG(" Mouse pressed on node\n"); |
|
context->focused_node = new_reference; |
|
} |
|
else if(!context->mouse_down) |
|
{ |
|
DEBUG(" Mouse released on node\n"); |
|
if(gui_node_references_equal(new_reference, context->focused_node)) |
|
{ |
|
DEBUG(" Mouse released over same node as pressed\n"); |
|
context->focused_node.node = NULL; |
|
} |
|
} |
|
} |
|
else if(gui_node_references_equal(new_reference, context->focused_node) |
|
&& !context->mouse_down) |
|
{ |
|
context->focused_node.node = NULL; |
|
} |
|
|
|
if(gui_node_references_equal(new_reference, context->focused_node)) |
|
{ |
|
int last_frame_width = node->computed_size[GUI_AXIS2_X]; |
|
if(last_frame_width == 0) |
|
{ |
|
DEBUG( |
|
" last_frame_width is 0." |
|
" This shouldn't really happen as the user shouldn't be able to focus" |
|
" a node that hasn't been displayed on the screen yet." |
|
" Not going to change the slider value."); |
|
} |
|
else |
|
{ |
|
int offset_x = context->mouse_x - node->rect.x0; |
|
float before_value = *value; |
|
float new_value = ((float)offset_x) / ((float)last_frame_width); |
|
if(new_value < 0.0f) |
|
{ |
|
new_value = 0.0f; |
|
} |
|
else if(new_value >= 1.0f) |
|
{ |
|
new_value = 1.0f; |
|
} |
|
*value = new_value; |
|
DEBUG(" Value before: %f - Value after: %f\n", before_value, *value); |
|
value_changed = (before_value != new_value); |
|
} |
|
} |
|
|
|
// NOTE(Zelaven): Modified by input, so handle input first. |
|
GUI_Node *inner = (GUI_Node*)new_inner_reference.node; |
|
inner->debug_string = GUI_STRING("gui_slider - inner node"); |
|
inner->text_string = (GUI_String){0}; |
|
assert(*value <= 1.0f); |
|
//inner->semantic_size[GUI_AXIS2_X].value = last_frame_width*(*value); |
|
inner->semantic_size[GUI_AXIS2_X].size_rule = GUI_SIZERULE_PERCENTOFPARENT; |
|
inner->semantic_size[GUI_AXIS2_X].value = 100 * (*value); |
|
gui_pop_parent(context); |
|
|
|
|
|
*last_reference = new_reference; |
|
*inner_box_reference = new_inner_reference; |
|
if(value_changed) {node->dirty = true;} |
|
//node->dirty = true; |
|
return value_changed; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
// --- |
|
|
|
|
|
|
|
void test_gui( |
|
GUI_Context *context, |
|
GUI_Rectangle full_gui_rectangle) |
|
{ |
|
static float border_thickness = 0; |
|
float last_border_thickness = border_thickness; |
|
|
|
|
|
|
|
static GUI_Style root_style = {42, 24, 88, 0,0,0}; |
|
static GUI_Style dumb_button_style = {88, 24, 42, 0,0,0}; |
|
static GUI_Size dumb_button_size[2] = { |
|
{GUI_SIZERULE_PIXELS, 42, 100}, |
|
{GUI_SIZERULE_PIXELS, 24, 100}}; |
|
static GUI_Style other_dumb_button_style = {24, 42, 88, 0x0000ffff,0,0}; |
|
|
|
static RDIC_Node_Reference root = {0}; |
|
root = gui_context_start_frame( |
|
context, root, |
|
full_gui_rectangle.x1-full_gui_rectangle.x0, |
|
full_gui_rectangle.y1-full_gui_rectangle.y0, |
|
GUI_LAYOUT_HORIZONTAL, |
|
&root_style); |
|
|
|
static RDIC_Node_Reference dumb_button = {0}; |
|
if(my_bool) |
|
if(gui_dumb_button( |
|
context, |
|
&dumb_button, |
|
GUI_STRING("My Dumb Button"), |
|
&dumb_button_style, |
|
dumb_button_size)) |
|
{ |
|
printf( |
|
"**************\n" |
|
"*** BUTTON ***\n" |
|
"**************\n"); |
|
border_thickness += 1; |
|
if(border_thickness > 21) border_thickness = 21; |
|
printf("border_thickness: %f\n", border_thickness); |
|
my_bool = false; |
|
((GUI_Node*)root.node)->dirty = true; |
|
} |
|
static RDIC_Node_Reference dumb_button2 = {0}; |
|
if(gui_dumb_button( |
|
context, |
|
&dumb_button2, |
|
GUI_STRING("My Other Dumb Button"), |
|
&other_dumb_button_style, |
|
dumb_button_size)) |
|
{ |
|
printf( |
|
"**************\n" |
|
"*** OTHER ***\n" |
|
"**************\n" |
|
); |
|
border_thickness -= 1; |
|
if(border_thickness < 0) border_thickness = 0; |
|
printf("border_thickness: %f\n", border_thickness); |
|
my_bool = true; |
|
} |
|
|
|
if(my_bool) |
|
{ |
|
static GUI_Style layout_style = {100, 0, 100,0,0,0}; |
|
static GUI_Size layout_size[2] = { |
|
{GUI_SIZERULE_PIXELS, 400, 100}, |
|
{GUI_SIZERULE_PIXELS, 400, 100}}; |
|
static RDIC_Node_Reference layout = {0}; |
|
layout = gui_layout( |
|
context, layout, GUI_LAYOUT_VERTICAL, &layout_style, layout_size); |
|
gui_push_parent(context, layout); |
|
|
|
// static variables can be used for isolated styles that: |
|
// 1) are only used within the current scope. |
|
// 2) don't need to be changed at runtime. |
|
static GUI_Style inner_block_2_style = {200, 253, 235, 0,0,0}; |
|
static GUI_Style inner_block_3_style = {235, 253, 200, 0,0,0}; |
|
static GUI_Style inner_block_4_style = {0 , 0, 0, 0,0,0}; |
|
static RDIC_Node_Reference dumb_block2 = {0}; |
|
dumb_block2 = gui_dumb_block( |
|
context, |
|
dumb_block2, |
|
&inner_block_2_style, |
|
dumb_button_size); |
|
static RDIC_Node_Reference dumb_block3 = {0}; |
|
dumb_block3 = gui_dumb_block( |
|
context, |
|
dumb_block3, |
|
&inner_block_3_style, |
|
dumb_button_size); |
|
static RDIC_Node_Reference dumb_block4 = {0}; |
|
dumb_block4 = gui_dumb_block( |
|
context, |
|
dumb_block4, |
|
&inner_block_4_style, |
|
dumb_button_size); |
|
static RDIC_Node_Reference dumb_button3 = {0}; |
|
if(gui_dumb_button( |
|
context, |
|
&dumb_button3, |
|
GUI_STRING("Inner Dumb Button"), |
|
&dumb_button_style, |
|
dumb_button_size)) |
|
{ |
|
printf( |
|
"**************\n" |
|
"*** INNER ***\n" |
|
"**************\n" |
|
); |
|
} |
|
static GUI_Size dumb_block5_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 42, 100}, |
|
{GUI_SIZERULE_PERCENTOFPARENT, 24, 100}}; |
|
static RDIC_Node_Reference dumb_block5 = {0}; |
|
dumb_block5 = gui_dumb_block( |
|
context, |
|
dumb_block5, |
|
&inner_block_4_style, |
|
dumb_block5_size); |
|
gui_pop_parent(context); |
|
} |
|
|
|
static GUI_Style slider_background = {0, 0, 0, 0,0,0}; |
|
static GUI_Style slider_bar = {255, 0, 0, 0,0,0}; |
|
static GUI_Size slider_size[2] = { |
|
{GUI_SIZERULE_PIXELS, 100, 100}, |
|
{GUI_SIZERULE_PIXELS, 40, 100}}; |
|
static RDIC_Node_Reference slider = {0}; |
|
static RDIC_Node_Reference slider_inner = {0}; |
|
static float slider_value = 0.25f; |
|
//slider_bar.blue = 255 * slider_value; |
|
//slider_value += 0.01f; |
|
//if(slider_value >= 1.0f) slider_value = 0.5f; |
|
// NOTE(Zelaven): This line is not good because it causes jankyness when using |
|
// the slider, and the slider behaves properly without it. |
|
// NOTE(Zelaven): This line is actually necessary if the value is writable by |
|
// anything other than the slider. If border_thickness is a float variable, |
|
// then there is no issue, though. |
|
slider_value = ((float)border_thickness) / 21.; |
|
bool slider_value_changed = gui_slider( |
|
context, |
|
&slider, &slider_inner, |
|
&slider_value, |
|
&slider_background, |
|
&slider_bar, |
|
slider_size); |
|
border_thickness = 21.*slider_value; |
|
//printf("Slider value changed?: %b\n", slider_value_changed); |
|
((GUI_Node*)root.node)->dirty |= slider_value_changed; |
|
|
|
static GUI_Style block_after_layout_style = {99, 99, 99, 0,0,0}; |
|
static GUI_Size block_after_layout_size[2] = { |
|
{GUI_SIZERULE_PIXELS, 50, 100}, |
|
{GUI_SIZERULE_PIXELS, 50, 100}}; |
|
static RDIC_Node_Reference dumb_block_after_layout = {0}; |
|
dumb_block_after_layout = gui_dumb_block( |
|
context, |
|
dumb_block_after_layout, |
|
&block_after_layout_style, |
|
block_after_layout_size); |
|
|
|
static GUI_Style my_style = {100, 100, 0, 0, 3, 0}; |
|
static GUI_Size dumb_block_size[2] = { |
|
{GUI_SIZERULE_PIXELS, 50, 100}, |
|
{GUI_SIZERULE_PIXELS, 100, 100}, |
|
}; |
|
my_style.border_thickness = border_thickness; |
|
static RDIC_Node_Reference new_style_gui_dumb_block = {0}; |
|
new_style_gui_dumb_block = gui_dumb_block( |
|
context, |
|
new_style_gui_dumb_block, |
|
&my_style, |
|
dumb_block_size); |
|
|
|
other_dumb_button_style.border_thickness = border_thickness; |
|
if(border_thickness != last_border_thickness) { |
|
((GUI_Node*)root.node)->dirty = true; |
|
} |
|
gui_context_finish_frame(context); |
|
} |
|
|
|
|
|
// Text field at the top, 4x4 button grid below. |
|
// See the nuklear example calculator? |
|
void test_gui__calculator( |
|
GUI_Context *context, |
|
GUI_Rectangle full_gui_rectangle) |
|
{ |
|
(void)full_gui_rectangle; |
|
|
|
static GUI_Style root_style = {0x44,0x44,0x44, 0x00999999,2,0}; |
|
static GUI_Style button_style = {0x33,0x33,0x33, 0x00999999,2,25}; |
|
static GUI_Size button_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 25, 100}, |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}}; |
|
static GUI_Style row_style = {0x44,0x44,0x44, 0,0,0}; |
|
static GUI_Size row_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}, |
|
{GUI_SIZERULE_PERCENTOFPARENT, 25, 100}}; |
|
|
|
static RDIC_Node_Reference root = {0}; |
|
root = gui_context_start_frame( |
|
context, root, |
|
//full_gui_rectangle.x1-full_gui_rectangle.x0, |
|
//full_gui_rectangle.y1-full_gui_rectangle.y0, |
|
300, 400, |
|
GUI_LAYOUT_VERTICAL, |
|
&root_style); |
|
|
|
#if 1 |
|
static RDIC_Node_Reference rows[4] = {0}; |
|
static RDIC_Node_Reference buttons[4][4] = {0}; |
|
GUI_String button_labels[4][4] = { |
|
{GUI_STRING("1"), GUI_STRING("2"), GUI_STRING("3"), GUI_STRING("+")}, |
|
{GUI_STRING("4"), GUI_STRING("5"), GUI_STRING("6"), GUI_STRING("-")}, |
|
{GUI_STRING("7"), GUI_STRING("8"), GUI_STRING("9"), GUI_STRING("*")}, |
|
{GUI_STRING("C"), GUI_STRING("0"), GUI_STRING("="), GUI_STRING("/")}, |
|
}; |
|
for(size_t row = 0; row < ARRAYLENGTH(rows); row++) |
|
{ |
|
rows[row] = gui_layout( |
|
context, rows[row], GUI_LAYOUT_HORIZONTAL, &row_style, row_size); |
|
gui_push_parent(context, rows[row]); |
|
for(size_t i = 0; i < ARRAYLENGTH(buttons[0]); i++) |
|
{ |
|
if(gui_dumb_button( |
|
context, &buttons[row][i], |
|
button_labels[row][i], |
|
&button_style, button_size)) |
|
{ |
|
GUI_String button_label = button_labels[row][i]; |
|
printf("%.*s\n", button_label.length, button_label.cstring); |
|
} |
|
} |
|
gui_pop_parent(context); |
|
} |
|
#else |
|
static RDIC_Node_Reference row1 = {0}; |
|
row1 = gui_layout2( |
|
context, row1, GUI_LAYOUT_HORIZONTAL, &row_style, row_size); |
|
gui_push_parent(context, row1); |
|
static RDIC_Node_Reference row1_buttons[4] = {0}; |
|
char *row1_button_labels[4] = {"1", "2", "3", "+"}; |
|
for(size_t i = 0; i < ARRAYLENGTH(row1_buttons); i++) |
|
{ |
|
if(gui_dumb_button( |
|
context, &row1_buttons[i], |
|
row1_button_labels[i], |
|
&button_style, button_size)) |
|
{ |
|
printf("%s\n", row1_button_labels[i]); |
|
} |
|
} |
|
gui_pop_parent(context); |
|
#endif |
|
|
|
|
|
gui_context_finish_frame(context); |
|
} |
|
|
|
|
|
void test_gui__scrollbars( |
|
GUI_Context *context, |
|
GUI_Rectangle full_gui_rectangle) |
|
{ |
|
(void)full_gui_rectangle; |
|
|
|
static GUI_Style root_style = {0x44,0x44,0x44, 0x00999999,2,0}; |
|
static GUI_Style element_style = {0,0x33,0x33, 0x00009999,2,0}; |
|
static GUI_Size element_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}, |
|
{GUI_SIZERULE_PIXELS, 100, 100}}; |
|
static GUI_Style outer_style = {0x33,0x33,0x33, 0x00999999,2,0}; |
|
static GUI_Size top_bottom_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}, |
|
{GUI_SIZERULE_PERCENTOFPARENT, 25, 100}}; |
|
static GUI_Size middle_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}, |
|
{GUI_SIZERULE_PERCENTOFPARENT, 50, 100}}; |
|
static GUI_Size side_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 25, 100}, |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}}; |
|
static GUI_Size center_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 50, 100}, |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}}; |
|
|
|
static RDIC_Node_Reference root = {0}; |
|
root = gui_context_start_frame( |
|
context, root, |
|
full_gui_rectangle.x1-full_gui_rectangle.x0, |
|
full_gui_rectangle.y1-full_gui_rectangle.y0, |
|
//300, 400, |
|
GUI_LAYOUT_VERTICAL, |
|
&root_style); |
|
|
|
|
|
static RDIC_Node_Reference top = {0}; |
|
static RDIC_Node_Reference middle = {0}; |
|
static RDIC_Node_Reference bottom = {0}; |
|
|
|
top = gui_dumb_block(context, top, &outer_style, top_bottom_size); |
|
|
|
middle = gui_layout( |
|
context, middle, GUI_LAYOUT_HORIZONTAL, &outer_style, middle_size); |
|
gui_push_parent(context, middle); { |
|
static RDIC_Node_Reference left = {0}; |
|
static RDIC_Node_Reference center = {0}; |
|
static RDIC_Node_Reference right = {0}; |
|
|
|
left = gui_dumb_block(context, left, &outer_style, side_size); |
|
center = gui_dumb_block(context, center, &outer_style, center_size); |
|
gui_push_parent(context, center); { |
|
static RDIC_Node_Reference scrollbox_layout = {0}; |
|
static GUI_Size full_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}, |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}}; |
|
scrollbox_layout = gui_layout( |
|
context, scrollbox_layout, GUI_LAYOUT_HORIZONTAL, &outer_style, full_size); |
|
|
|
gui_push_parent(context, scrollbox_layout); { |
|
static RDIC_Node_Reference scrollregion_layout = {0}; |
|
static RDIC_Node_Reference scrollbar_layout = {0}; |
|
static GUI_Size scrollregion_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 90, 100}, |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}}; |
|
static GUI_Size scrollbar_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 10, 100}, |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}}; |
|
scrollregion_layout = gui_layout( |
|
context, scrollregion_layout, GUI_LAYOUT_VERTICAL, |
|
&outer_style, scrollregion_size); |
|
gui_push_parent(context, scrollregion_layout); { |
|
static RDIC_Node_Reference elements[6] = {0}; |
|
for(size_t i = 0; i < ARRAYLENGTH(elements); i++) |
|
{ |
|
elements[i] = gui_dumb_block( |
|
context, elements[i], &element_style, element_size); |
|
} |
|
} gui_pop_parent(context); |
|
scrollbar_layout = gui_layout( |
|
context, scrollbar_layout, GUI_LAYOUT_VERTICAL, |
|
&outer_style, scrollbar_size); |
|
float scroll_position = 0.75f; |
|
int spacer_total = 90; |
|
int spacer_upper = scroll_position * spacer_total; |
|
int spacer_lower = spacer_total - spacer_upper; |
|
gui_push_parent(context, scrollbar_layout); { |
|
static GUI_Size bar_knob_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}, |
|
{GUI_SIZERULE_PERCENTOFPARENT, 10, 100}}; |
|
// NOTE(Zelaven): These sizes are unique to each scrollbar. |
|
static GUI_Size bar_upper_spacer_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}, |
|
{GUI_SIZERULE_PERCENTOFPARENT, 0, 100}}; |
|
static GUI_Size bar_lower_spacer_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}, |
|
{GUI_SIZERULE_PERCENTOFPARENT, 90, 100}}; |
|
bar_upper_spacer_size[GUI_AXIS2_Y].value = spacer_upper; |
|
bar_lower_spacer_size[GUI_AXIS2_Y].value = spacer_lower; |
|
|
|
static RDIC_Node_Reference scrollbar_upper_spacer = {0}; |
|
static RDIC_Node_Reference scrollbar_knob = {0}; |
|
static RDIC_Node_Reference scrollbar_lower_spacer = {0}; |
|
scrollbar_upper_spacer = gui_dumb_block( |
|
context, scrollbar_upper_spacer, &outer_style, bar_upper_spacer_size); |
|
scrollbar_knob = gui_dumb_block( |
|
context, scrollbar_knob, &outer_style, bar_knob_size); |
|
scrollbar_lower_spacer = gui_dumb_block( |
|
context, scrollbar_lower_spacer, &outer_style, bar_lower_spacer_size); |
|
} gui_pop_parent(context); // scrollbar_layout. |
|
#if 0 |
|
float contents_height_ratio = 0.0f; |
|
if(scrollbox_layout.node->computed_size[GUI_AXIS2_Y] > 0) { |
|
float layout_height = |
|
(float)scrollbox_layout.node->computed_size[GUI_AXIS2_Y]; |
|
float contents_height = 600.0f; // TODO(Zelaven): take from scrollregion_layout. (dependency on CHILDRENSUM). |
|
} |
|
#endif |
|
} gui_pop_parent(context); // scrollbox_layout. |
|
} gui_pop_parent(context); // center. |
|
right = gui_dumb_block(context, right, &outer_style, side_size); |
|
} gui_pop_parent(context); // middle. |
|
|
|
bottom = gui_dumb_block(context, bottom, &outer_style, top_bottom_size); |
|
|
|
|
|
gui_context_finish_frame(context); |
|
} |
|
|
|
void test_gui__draw_command_using_sliders( |
|
GUI_Context *context, |
|
GUI_Rectangle full_gui_rectangle) |
|
{ |
|
//static float border_thickness = 0; |
|
|
|
static GUI_Style root_style = {42, 24, 88, 0,0,0}; |
|
static RDIC_Node_Reference root = {0}; |
|
root = gui_context_start_frame( |
|
context, root, |
|
full_gui_rectangle.x1-full_gui_rectangle.x0, |
|
full_gui_rectangle.y1-full_gui_rectangle.y0, |
|
GUI_LAYOUT_VERTICAL, |
|
&root_style); |
|
|
|
static GUI_Size layout_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}, |
|
{GUI_SIZERULE_PERCENTOFPARENT, 50, 100}}; |
|
static RDIC_Node_Reference layout = {0}; |
|
layout = gui_layout( |
|
context, layout, GUI_LAYOUT_VERTICAL, &root_style, layout_size); |
|
gui_push_parent(context, layout); |
|
{ |
|
static GUI_Style slider_background = {0, 0, 0, 0,0,0}; |
|
static GUI_Style slider_bar = {255, 0, 0, 0,0,0}; |
|
static GUI_Size slider_size[2] = { |
|
{GUI_SIZERULE_PIXELS, 200, 100}, |
|
{GUI_SIZERULE_PIXELS, 40, 100}}; |
|
static RDIC_Node_Reference slider = {0}; |
|
static RDIC_Node_Reference slider_inner = {0}; |
|
|
|
static GUI_Style button_style = {200, 0, 0, 0,5,0}; |
|
static GUI_Size button_size[2] = { |
|
{GUI_SIZERULE_PIXELS, 100, 100}, |
|
{GUI_SIZERULE_PIXELS, 40, 100}}; |
|
|
|
static GUI_Style slider_spacer_style = {99, 99, 99, 0,0,0}; |
|
static GUI_Size slider_spacer_size[2] = { |
|
{GUI_SIZERULE_PIXELS, 200, 100}, |
|
{GUI_SIZERULE_PIXELS, 5, 100}}; |
|
|
|
static GUI_Style button_spacer_style = {99, 99, 99, 0,0,0}; |
|
static GUI_Size button_spacer_size[2] = { |
|
{GUI_SIZERULE_PIXELS, 5, 100}, |
|
{GUI_SIZERULE_PIXELS, 40, 100}}; |
|
|
|
static GUI_Size row_layout_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}, |
|
{GUI_SIZERULE_PIXELS, 40, 100}}; |
|
|
|
static RDIC_Node_Reference row1_layout = {0}; |
|
row1_layout = gui_layout( |
|
context, row1_layout, GUI_LAYOUT_HORIZONTAL, &root_style, row_layout_size); |
|
#if 1 |
|
gui_push_parent(context, row1_layout); |
|
{ |
|
static float slider_value = 0.25f; |
|
// NOTE(Zelaven): This line is not good because it causes jankyness when |
|
// using the slider, and the slider behaves properly without it. |
|
// NOTE(Zelaven): This line is actually necessary if the value is writable |
|
// by anything other than the slider. If border_thickness is a float |
|
// variable, then there is no issue, though. |
|
slider_value = ((float)border_thickness) / 51.; |
|
gui_slider(context, &slider, &slider_inner, |
|
&slider_value, &slider_background, &slider_bar, slider_size); |
|
border_thickness = 51.*slider_value; |
|
|
|
static RDIC_Node_Reference spacer1 = {0}; |
|
spacer1 = gui_dumb_block( |
|
context, spacer1, &button_spacer_style, button_spacer_size); |
|
|
|
static RDIC_Node_Reference button_thickness_down = {0}; |
|
if(gui_dumb_button(context, &button_thickness_down, |
|
GUI_STRING("-"), &button_style, button_size)) |
|
{ |
|
border_thickness -= 1; |
|
printf("-\n"); |
|
} |
|
|
|
static RDIC_Node_Reference spacer2 = {0}; |
|
spacer2 = gui_dumb_block( |
|
context, spacer2, &button_spacer_style, button_spacer_size); |
|
|
|
static RDIC_Node_Reference button_thickness_up = {0}; |
|
if(gui_dumb_button(context, &button_thickness_up, |
|
GUI_STRING("+"), &button_style, button_size)) |
|
{ |
|
border_thickness += 1; |
|
printf("+\n"); |
|
} |
|
} |
|
gui_pop_parent(context); |
|
#endif |
|
|
|
static RDIC_Node_Reference slider_spacer = {0}; |
|
slider_spacer = gui_dumb_block( |
|
context, slider_spacer, &slider_spacer_style, slider_spacer_size); |
|
|
|
static RDIC_Node_Reference slider2 = {0}; |
|
static RDIC_Node_Reference slider2_inner = {0}; |
|
static float slider2_value = 0.25f; |
|
slider2_value = ((float)roundedness) / 100.; |
|
gui_slider(context, &slider2, &slider2_inner, |
|
&slider2_value, &slider_background, &slider_bar, slider_size); |
|
roundedness = 100.*slider2_value; |
|
} |
|
gui_pop_parent(context); |
|
|
|
|
|
gui_context_finish_frame(context); |
|
} |
|
|
|
|
|
void test_gui__subtree( |
|
GUI_Context *context, |
|
GUI_Rectangle full_gui_rectangle) |
|
{ |
|
static GUI_Style root_style = {0x44,0x44,0x44, 0x00999999,2,0}; |
|
static GUI_Style element_style = {0,0x33,0x33, 0x00009999,2,0}; |
|
static GUI_Size element_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}, |
|
{GUI_SIZERULE_PIXELS, 100, 100}}; |
|
static GUI_Style outer_style = {0x33,0x33,0x33, 0x00999999,2,0}; |
|
static GUI_Size top_bottom_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}, |
|
{GUI_SIZERULE_PERCENTOFPARENT, 25, 100}}; |
|
static GUI_Size middle_size[2] = { |
|
{GUI_SIZERULE_PERCENTOFPARENT, 100, 100}, |
|
{GUI_SIZERULE_PERCENTOFPARENT, 50, 100}}; |
|
static GUI_Size test_size[2] = { |
|
{GUI_SIZERULE_PIXELS, 200, 100}, |
|
{GUI_SIZERULE_PIXELS, 600, 100}}; |
|
|
|
static RDIC_Node_Reference root = {0}; |
|
root = gui_context_start_frame( |
|
context, root, |
|
full_gui_rectangle.x1-full_gui_rectangle.x0, |
|
full_gui_rectangle.y1-full_gui_rectangle.y0, |
|
GUI_LAYOUT_VERTICAL, |
|
&root_style); |
|
|
|
|
|
static RDIC_Node_Reference top = {0}; |
|
static RDIC_Node_Reference middle = {0}; |
|
static RDIC_Node_Reference bottom = {0}; |
|
|
|
static RDIC_Node_Reference slider = {0}; |
|
static float slider_value = 0.25f; |
|
top = gui_layout( |
|
context, top, GUI_LAYOUT_HORIZONTAL, &outer_style, top_bottom_size); |
|
gui_push_parent(context, top); |
|
{ |
|
static GUI_Style slider_background = {0, 0, 0, 0,0,0}; |
|
static GUI_Style slider_bar = {255, 0, 0, 0,0,0}; |
|
static GUI_Size slider_size[2] = { |
|
{GUI_SIZERULE_PIXELS, 200, 100}, |
|
{GUI_SIZERULE_PIXELS, 40, 100}}; |
|
//static RDIC_Node_Reference slider = {0}; |
|
static RDIC_Node_Reference slider_inner = {0}; |
|
// NOTE(Zelaven): This line is not good because it causes jankyness when |
|
// using the slider, and the slider behaves properly without it. |
|
// NOTE(Zelaven): This line is actually necessary if the value is writable |
|
// by anything other than the slider. If border_thickness is a float |
|
// variable, then there is no issue, though. |
|
gui_slider(context, &slider, &slider_inner, |
|
&slider_value, &slider_background, &slider_bar, slider_size); |
|
} gui_pop_parent(context); // middle. |
|
|
|
middle = gui_dumb_block(context, middle, &outer_style, middle_size); |
|
|
|
static GUI_Subtree middle_subtree = {0}; |
|
static RDIC_Node_Reference middle_subtree_root = {0}; |
|
middle_subtree_root = gui_push_subtree( |
|
context, &middle_subtree, middle_subtree_root, |
|
GUI_LAYOUT_VERTICAL, &element_style, test_size, middle); |
|
{ |
|
static GUI_Color test_image_pixels[10*10] = { |
|
0, ~0, 0, ~0, 0, ~0, 0, ~0, 0, ~0, |
|
~0, 0, ~0, 0, ~0, 0, ~0, 0, ~0, 0, |
|
0, ~0, 0, ~0, 0, ~0, 0, ~0, 0, ~0, |
|
~0, 0, ~0, 0, ~0, 0, ~0, 0, ~0, 0, |
|
0, ~0, 0, ~0, 0, ~0, 0, ~0, 0, ~0, |
|
~0, 0, ~0, 0, ~0, 0, ~0, 0, ~0, 0, |
|
0, ~0, 0, ~0, 0, ~0, 0, ~0, 0, ~0, |
|
~0, 0, ~0, 0, ~0, 0, ~0, 0, ~0, 0, |
|
0, ~0, 0, ~0, 0, ~0, 0, ~0, 0, ~0, |
|
~0, 0, ~0, 0, ~0, 0, ~0, 0, ~0, 0, |
|
}; |
|
static Pixel_Buffer test_image = { |
|
.pixels = test_image_pixels, |
|
.width = 10, |
|
.height = 10, |
|
}; |
|
static RDIC_Node_Reference inner_top = {0}; |
|
inner_top = gui_dumb_block(context, inner_top, &outer_style, element_size); |
|
((GUI_Node*)inner_top.node)->image = &test_image; |
|
} gui_pop_subtree(context); |
|
#if 0 |
|
int middle_height = |
|
((GUI_Node*)middle.node)->computed_size[GUI_AXIS2_Y]; |
|
int subtree_height = |
|
((GUI_Node*)middle_subtree_root.node)->computed_size[GUI_AXIS2_Y]; |
|
int overfill = subtree_height - middle_height; |
|
printf("\n%d = %d - %d\n", overfill, subtree_height, middle_height); |
|
if(overfill >= 0) |
|
{ |
|
printf("?\n"); |
|
middle_subtree.flat_offset_y = -(slider_value * overfill); |
|
printf("%d\n", middle_subtree.flat_offset_x); |
|
} |
|
else |
|
{ |
|
middle_subtree.flat_offset_y = 0; |
|
} |
|
#else |
|
printf("slider value: %f\n", slider_value); |
|
middle_subtree.offset_relative_node_percentage_y = slider_value*100; |
|
middle_subtree.offset_own_size_percentage_y = slider_value*-100; |
|
#endif |
|
((GUI_Node*)middle_subtree_root.node)->dirty = ((GUI_Node*)slider.node)->dirty; |
|
|
|
bottom = gui_dumb_block(context, bottom, &outer_style, top_bottom_size); |
|
|
|
|
|
gui_context_finish_frame(context); |
|
} |
|
|
|
|
|
#if 0 |
|
void test_gui_layout( |
|
GUI_Context *context, |
|
GUI_Rectangle full_gui_rectangle) |
|
{ |
|
static RDIC_Node_Reference root = {0}; |
|
root = gui_context_start_frame( |
|
context, root, |
|
full_gui_rectangle.x1-full_gui_rectangle.x0, |
|
full_gui_rectangle.y1-full_gui_rectangle.y0, |
|
GUI_LAYOUT_HORIZONTAL); |
|
|
|
static RDIC_Node_Reference dumb_block = {0}; |
|
#if 0 |
|
dumb_block = gui_dumb_block( |
|
context, |
|
dumb_block, |
|
(GUI_Style){255, 0, 0,0,0,0}, |
|
50, 50); |
|
#else |
|
if(gui_dumb_button( |
|
context, &dumb_block, "BEFORE Button", (GUI_Style){255, 0, 0,0,0,0}, 50, 50)) |
|
{printf( |
|
"**************\n" |
|
"*** BEFORE ***\n" |
|
"**************\n");} |
|
#endif |
|
|
|
static RDIC_Node_Reference layout = {0}; |
|
layout = gui_layout( |
|
context, |
|
layout, |
|
GUI_LAYOUT_VERTICAL); |
|
gui_push_parent(context, layout); |
|
|
|
static RDIC_Node_Reference dumb_block2 = {0}; |
|
#if 0 |
|
dumb_block2 = gui_dumb_block( |
|
context, |
|
dumb_block2, |
|
(GUI_Style){0, 255, 0,0,0,0}, |
|
100, 50); |
|
#else |
|
if(gui_dumb_button( |
|
context, &dumb_block2, "INSIDE Button", (GUI_Style){0, 255, 0,0,0,0}, 100, 50)) |
|
{printf( |
|
"**************\n" |
|
"*** INSIDE ***\n" |
|
"**************\n");} |
|
#endif |
|
|
|
gui_pop_parent(context); |
|
|
|
static RDIC_Node_Reference dumb_block3 = {0}; |
|
#if 0 |
|
dumb_block_after_layout = gui_dumb_block( |
|
context, |
|
dumb_block3, |
|
(GUI_Style){0, 0, 255,0,0,0}, |
|
150, 50); |
|
#else |
|
if(gui_dumb_button( |
|
context, &dumb_block3, "AFTER Button", (GUI_Style){0, 0, 255,0,0,0}, 150, 50)) |
|
{printf( |
|
"**************\n" |
|
"*** AFTER ***\n" |
|
"**************\n");} |
|
#endif |
|
|
|
gui_context_finish_frame(context); |
|
} |
|
#endif |
|
|
|
// --- |
|
// --- |
|
// --- |
|
|
|
#include <X11/Xlib.h> |
|
#include <X11/Xutil.h> |
|
#include <X11/Xatom.h> |
|
|
|
#define WINDOW_WIDTH 800 |
|
#define WINDOW_HEIGHT 600 |
|
typedef struct X11_Window { |
|
Display *display; |
|
Window root; |
|
Visual *visual; |
|
Colormap colormap; |
|
XWindowAttributes window_attributes; |
|
XSetWindowAttributes set_window_attributes; |
|
Window window; |
|
GC graphics_context; |
|
int screen; |
|
unsigned int width; |
|
unsigned int height; |
|
XImage *screen_image; |
|
|
|
Pixel_Buffer pixel_buffer; |
|
} X11_Window; |
|
|
|
|
|
#include <stdlib.h> |
|
#include <stdarg.h> |
|
#include <stdnoreturn.h> |
|
#include <stdio.h> |
|
noreturn void die(const char *fmt, ...) |
|
{ |
|
va_list args; |
|
va_start(args, fmt); |
|
vfprintf(stderr, fmt, args); |
|
va_end(args); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
void print_binary_long(long value) |
|
{ |
|
int num_bits = sizeof(long)*8; |
|
unsigned long mask = ((unsigned long)1) << (num_bits-1); |
|
while(mask != (unsigned long)0) |
|
{ |
|
int bit = (value & mask) != 0; |
|
printf("%d", bit); |
|
mask >>= 1; |
|
} |
|
} |
|
|
|
|
|
void fill_pixel_buffer( |
|
uint32_t *pixels, |
|
unsigned int width, |
|
unsigned int height, |
|
uint8_t red_offset, |
|
uint8_t blue_offset) |
|
{ |
|
for(unsigned int column = 0; column < width; column++) |
|
{ |
|
for(unsigned int row = 0; row < height; row++) |
|
{ |
|
uint8_t red = (uint8_t)((column+ red_offset) & 0xFF); |
|
uint8_t blue = (uint8_t)((row +blue_offset) & 0xFF); |
|
pixels[column + row*width] = (red<<16) | blue; |
|
} |
|
} |
|
} |
|
void draw_pixel_buffer( |
|
unsigned int width, |
|
unsigned int height, |
|
Display *game_display, |
|
Window game_window, |
|
XImage *game_image, |
|
GC x_graphics_context) |
|
{ |
|
/* |
|
int XPutImage(Display *display, Drawable d, GC gc, XImage *image, int src_x, int src_y, int |
|
dest_x, int dest_y, unsigned int width, unsigned int height); |
|
*/ |
|
|
|
int error = XPutImage( |
|
game_display, |
|
game_window, |
|
x_graphics_context, |
|
game_image, |
|
0, 0, |
|
0, 0, |
|
width, height); |
|
assert(error == 0); |
|
} |
|
|
|
typedef unsigned int uint; |
|
void draw_rect( |
|
uint32_t *pixels, |
|
unsigned int width, |
|
unsigned int height, |
|
uint x0, uint y0, uint x1, uint y1, |
|
uint32_t color) |
|
{ |
|
//x0 = x0 < 0 ? 0 : x0; |
|
//y0 = y0 < 0 ? 0 : y0; |
|
x1 = x1 > width ? width : x1; |
|
y1 = y1 > width ? height : y1; |
|
|
|
for(unsigned int column = x0; column < x1; column++) |
|
{ |
|
for(unsigned int row = y0; row < y1; row++) |
|
{ |
|
pixels[column + row*width] = color; |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
void draw_rect2( |
|
Pixel_Buffer *pixel_buffer, |
|
GUI_Draw_Command *draw_command) |
|
{ |
|
int width = pixel_buffer->width; |
|
int height = pixel_buffer->height; |
|
|
|
int x0 = draw_command->rectangle.x0; |
|
int y0 = draw_command->rectangle.y0; |
|
int x1 = draw_command->rectangle.x1; |
|
int y1 = draw_command->rectangle.y1; |
|
x0 = x0 < 0 ? 0 : x0; |
|
y0 = y0 < 0 ? 0 : y0; |
|
x1 = x1 > width ? width : x1; |
|
y1 = y1 > width ? height : y1; |
|
|
|
for(int column = x0; column < x1; column++) |
|
{ |
|
for(int row = y0; row < y1; row++) |
|
{ |
|
pixel_buffer->pixels[column + row*width] = draw_command->color; |
|
} |
|
} |
|
} |
|
|
|
void draw_rect3( |
|
Pixel_Buffer *pixel_buffer, |
|
GUI_Rectangle rectangle, |
|
GUI_Color color) |
|
{ |
|
int width = pixel_buffer->width; |
|
int height = pixel_buffer->height; |
|
|
|
int x0 = rectangle.x0; |
|
int y0 = rectangle.y0; |
|
int x1 = rectangle.x1; |
|
int y1 = rectangle.y1; |
|
x0 = x0 < 0 ? 0 : x0; |
|
y0 = y0 < 0 ? 0 : y0; |
|
x1 = x1 > width ? width : x1; |
|
y1 = y1 > height ? height : y1; |
|
|
|
for(int column = x0; column < x1; column++) |
|
{ |
|
for(int row = y0; row < y1; row++) |
|
{ |
|
pixel_buffer->pixels[column + row*width] = color; |
|
} |
|
} |
|
} |
|
|
|
#define SET_PIXEL(pixel_buffer, x, y, color) (pixel_buffer)->pixels[(y)*(pixel_buffer)->width + (x)] = (color) |
|
#define GET_PIXEL(pixel_buffer, x, y) (pixel_buffer)->pixels[(y)*(pixel_buffer)->width + (x)] |
|
|
|
void test_draw_horizontal_pixel_line( |
|
Pixel_Buffer *pixel_buffer, |
|
int x0, int x1, int y, |
|
GUI_Color color) |
|
{ |
|
for(int i = x0; i < x1; i++) |
|
{ |
|
SET_PIXEL(pixel_buffer, i, y, color); |
|
} |
|
} |
|
void test_draw_vertical_pixel_line( |
|
Pixel_Buffer *pixel_buffer, |
|
GUI_Color color, |
|
int x, int y0, int y1) |
|
{ |
|
for(int i = y0; i < y1; i++) |
|
{ |
|
SET_PIXEL(pixel_buffer, x, i, color); |
|
} |
|
} |
|
// NOTE(Zelaven): Based on the technique and code for drawing circles |
|
// showcased by Casey here: |
|
// https://www.computerenhance.com/p/efficient-dda-circle-outlines |
|
void gui_draw_rounded_rect( |
|
Pixel_Buffer *pixel_buffer, |
|
GUI_Draw_Command *draw_command) |
|
{ |
|
int roundedness = draw_command->roundedness; |
|
// NOTE(Zelaven): Roundedness is a function of width and height, whichever |
|
// is smaller. |
|
int width = draw_command->rectangle.x1 - draw_command->rectangle.x0; |
|
int height = draw_command->rectangle.y1 - draw_command->rectangle.y0; |
|
short r = 0; |
|
{ |
|
int shortest_dimension = width < height ? width : height; |
|
r = (shortest_dimension * roundedness) / 200; |
|
} |
|
|
|
int Cx = draw_command->rectangle.x0+r; |
|
int Cy = draw_command->rectangle.y0+r; |
|
int R = r; |
|
int R2, X, Y, dY, dX, D; |
|
// TODO(Zelaven): Remove this static limit. |
|
assert(R < 500); |
|
// NOTE(Zelaven): Both of these are used to cache calculations. |
|
// NOTE(Zelaven): This one is also used to prevent redrawing the same pixels. |
|
int Y_to_X_map[500]; |
|
int Y_to_X_map2[500]; |
|
#if 0 |
|
for(size_t i = 0; i < ARRAYLENGTH(Y_to_X_map); i++) |
|
{ |
|
Y_to_X_map[i] = -1; |
|
Y_to_X_map2[i] = -1; |
|
} |
|
#endif |
|
//printf("\n\n"); |
|
R2 = R+R; |
|
Y = R; X = 0; |
|
dX = -2; |
|
dY = R2+R2 - 4; |
|
D = R2 - 1; |
|
|
|
while(X <= Y) |
|
{ |
|
//printf("X=%d, Y=%d\n", X, Y); |
|
//setPixel(Cx - X, Cy - Y); |
|
Y_to_X_map[Y] = X; |
|
//setPixel(Cx - X, Cy + Y); |
|
//setPixel(Cx + X, Cy - Y); |
|
//setPixel(Cx + X, Cy + Y); |
|
//setPixel(Cx - Y, Cy - X); |
|
Y_to_X_map2[X] = Y; |
|
//setPixel(Cx - Y, Cy + X); |
|
//setPixel(Cx + Y, Cy - X); |
|
//setPixel(Cx + Y, Cy + X); |
|
|
|
D += dX; |
|
dX -= 4; |
|
++X; |
|
|
|
// NOTE(casey): Branchless version |
|
int Mask = (D >> 31); |
|
D += dY & Mask; |
|
dY -= 4 & Mask; |
|
Y += Mask; |
|
} |
|
//SET_PIXEL(pixel_buffer, draw_command->rectangle.x0, draw_command->rectangle.y0, draw_command->border_color); |
|
//SET_PIXEL(pixel_buffer, draw_command->rectangle.x1-1, draw_command->rectangle.y1-1, draw_command->border_color); |
|
|
|
// top. |
|
int top_y0 = Cy - R; |
|
int top_y1 = Cy - Y; |
|
//int top_height = top_y1 - top_y0; |
|
for(int y = top_y0; y < top_y1; y++) |
|
{ |
|
int Y_ = Cy - y; |
|
int X_ = Y_to_X_map[Y_]; |
|
//printf("X_: %d\n", X_); |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, |
|
Cx - X_, draw_command->rectangle.x1 - R + X_, y, |
|
draw_command->color); |
|
} |
|
|
|
// lower. |
|
int lower_y0 = Cy - Y; |
|
int lower_y1 = Cy; |
|
//int lower_height = lower_y1 - lower_y0; |
|
//printf("lower: %d to %d\n", lower_y0, lower_y1); |
|
for(int y = lower_y0; y < lower_y1; y++) |
|
{ |
|
int Y_ = Cy - y; |
|
int X_ = Y_to_X_map2[Y_]; |
|
//printf("X_: %d\n", X_); |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, |
|
Cx - X_, draw_command->rectangle.x1 - R + X_, y, |
|
draw_command->color); |
|
} |
|
|
|
// middle. |
|
GUI_Rectangle middle = draw_command->rectangle; |
|
middle.y0 = middle.y0 + r; |
|
middle.y1 -= r; |
|
draw_rect3(pixel_buffer, middle, draw_command->color); |
|
int middle_height = middle.y1-middle.y0; |
|
int Cy1 = Cy + middle_height - 1; |
|
|
|
// lower. |
|
lower_y0 = Cy1 +1; |
|
lower_y1 = Cy1 + Y +1; |
|
//printf("lower: %d = %d = %d - %d\n", lower_height, lower_y1 - lower_y0, lower_y1, lower_y0); |
|
for(int y = lower_y0; y < lower_y1; y++) |
|
{ |
|
int Y_ = y - Cy1; |
|
int X_ = Y_to_X_map2[Y_]; |
|
//printf("X_: %d\n", X_); |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, |
|
Cx - X_, draw_command->rectangle.x1 - R + X_, y, |
|
draw_command->color); |
|
} |
|
|
|
// top. |
|
top_y0 = Cy1 + Y +1; |
|
top_y1 = Cy1 + R +1; |
|
//printf("top: %d = %d = %d - %d\n", top_height, top_y1 - top_y0, top_y1, top_y0); |
|
for(int y = top_y0; y < top_y1; y++) |
|
{ |
|
int Y_ = y - Cy1; |
|
int X_ = Y_to_X_map[Y_]; |
|
//printf("X_: %d\n", X_); |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, |
|
Cx - X_, draw_command->rectangle.x1 - R + X_, y, |
|
draw_command->color); |
|
} |
|
} |
|
void gui_draw_rounded_rect_with_border( |
|
Pixel_Buffer *pixel_buffer, |
|
GUI_Draw_Command *draw_command) |
|
{ |
|
int width = draw_command->rectangle.x1 - draw_command->rectangle.x0; |
|
int height = draw_command->rectangle.y1 - draw_command->rectangle.y0; |
|
int roundedness = draw_command->roundedness; |
|
int border_thickness = draw_command->border_thickness; |
|
if(border_thickness > (height+1)/2) border_thickness = (height+1)/2; |
|
//printf("\n\n"); |
|
//printf("border_thickness: %d\n", border_thickness); |
|
// NOTE(Zelaven): Roundedness is a function of width and height, whichever |
|
// is smaller. |
|
short r = 0; |
|
{ |
|
int shortest_dimension = width < height ? width : height; |
|
r = (shortest_dimension * roundedness) / 200; |
|
} |
|
|
|
int Cx = draw_command->rectangle.x0+r; |
|
int Cy = draw_command->rectangle.y0+r; |
|
int R = r; |
|
int R2, X, Y, dY, dX, D; |
|
// TODO(Zelaven): Remove this static limit. |
|
assert(R < 500); |
|
// NOTE(Zelaven): Both of these are used to cache calculations. |
|
// NOTE(Zelaven): This one is also used to prevent redrawing the same pixels. |
|
int Y_to_X_map[500]; |
|
int Y_to_X_map2[500]; |
|
#if 1 |
|
for(size_t i = 0; i < ARRAYLENGTH(Y_to_X_map); i++) |
|
{ |
|
Y_to_X_map[i] = -1; |
|
Y_to_X_map2[i] = -1; |
|
} |
|
#endif |
|
{ |
|
//printf("\n\n"); |
|
R2 = R+R; |
|
Y = R; X = 0; |
|
dX = -2; |
|
dY = R2+R2 - 4; |
|
D = R2 - 1; |
|
|
|
while(X <= Y) |
|
{ |
|
//printf("X=%d, Y=%d\n", X, Y); |
|
//setPixel(Cx - X, Cy - Y); |
|
Y_to_X_map[Y] = X; |
|
//setPixel(Cx - X, Cy + Y); |
|
//setPixel(Cx + X, Cy - Y); |
|
//setPixel(Cx + X, Cy + Y); |
|
//setPixel(Cx - Y, Cy - X); |
|
Y_to_X_map2[X] = Y; |
|
//setPixel(Cx - Y, Cy + X); |
|
//setPixel(Cx + Y, Cy - X); |
|
//setPixel(Cx + Y, Cy + X); |
|
//if((Cx - X) == (Cx - Y) && (Cy - Y)==(Cy - X)) |
|
// printf("SAMENESS: X: %d , Y: %d\n", X, Y); |
|
|
|
D += dX; |
|
dX -= 4; |
|
++X; |
|
|
|
// NOTE(casey): Branchless version |
|
int Mask = (D >> 31); |
|
D += dY & Mask; |
|
dY -= 4 & Mask; |
|
Y += Mask; |
|
} |
|
} |
|
//printf("R: %d , X: %d , Y: %d\n", R, X, Y); |
|
|
|
// inner circle. |
|
int R_inner = r - border_thickness; |
|
int R2_inner, X_inner, Y_inner, dY_inner, dX_inner, D_inner; |
|
assert(R_inner < 500); |
|
// NOTE(Zelaven): Both of these are used to cache calculations. |
|
// NOTE(Zelaven): This one is also used to prevent redrawing the same pixels. |
|
int Y_to_X_map_inner[500]; |
|
int Y_to_X_map2_inner[500]; |
|
#if 1 |
|
for(size_t i = 0; i < ARRAYLENGTH(Y_to_X_map_inner); i++) |
|
{ |
|
Y_to_X_map_inner[i] = -1; |
|
Y_to_X_map2_inner[i] = -1; |
|
} |
|
#endif |
|
{ |
|
//printf("\n\n"); |
|
R2_inner = R_inner+R_inner; |
|
Y_inner = R_inner; X_inner = 0; |
|
dX_inner = -2; |
|
dY_inner = R2_inner+R2_inner - 4; |
|
D_inner = R2_inner - 1; |
|
|
|
while(X_inner <= Y_inner) |
|
{ |
|
//printf("X_inner=%d, Y_inner=%d\n", X_inner, Y_inner); |
|
Y_to_X_map_inner[Y_inner] = X_inner; |
|
Y_to_X_map2_inner[X_inner] = Y_inner; |
|
//if((Cx - X_inner) == (Cx - Y_inner) && (Cy - Y_inner)==(Cy - X_inner)) |
|
// printf("SAMENESS: X: %d , Y: %d\n", X_inner, Y_inner); |
|
|
|
D_inner += dX_inner; |
|
dX_inner -= 4; |
|
++X_inner; |
|
|
|
// NOTE(casey): Branchless version |
|
int Mask = (D_inner >> 31); |
|
D_inner += dY_inner & Mask; |
|
dY_inner -= 4 & Mask; |
|
Y_inner += Mask; |
|
} |
|
} |
|
//printf("R_inner: %d , X_inner: %d , Y_inner: %d\n", R_inner, X_inner, Y_inner); |
|
|
|
// Drawing. |
|
int y_ = draw_command->rectangle.y0; |
|
int rect_x0 = draw_command->rectangle.x0; |
|
int rect_x1 = draw_command->rectangle.x1; |
|
int draw_guide_lines = 0; |
|
int border_y = y_ + border_thickness; |
|
|
|
#if 0 |
|
- top1 : upper octant of outer, above inner. |
|
- top2 : upper octant of outer, upper octant of inner. |
|
- lower1: lower octant of outer, above inner. |
|
Happens when border is thicker than height of outer-upper-octant. |
|
- lower2: lower octant of outer, upper octant of inner. |
|
- lower3: lower octant of outer, lower octant of inner. |
|
#endif |
|
|
|
// top1. |
|
int top1_y0 = Cy-R; |
|
int top1_ycap = Cy-Y; |
|
//printf("%d %d\n", border_y, top1_ycap); |
|
int top1_y1 = top1_ycap < border_y ? top1_ycap : border_y; |
|
for(int top1_y = top1_y0; top1_y < top1_y1; top1_y++) |
|
{ |
|
int Y_ = Cy - top1_y; |
|
int X_ = Y_to_X_map[Y_]; |
|
//printf("%d = %d - %d\n", Y_, Cy, top1_y); |
|
//printf("X_: %d\n", X_); |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, |
|
Cx - X_, rect_x1 - R + X_, top1_y, |
|
draw_command->border_color); |
|
} |
|
if(draw_guide_lines)test_draw_horizontal_pixel_line( |
|
pixel_buffer, |
|
rect_x0, rect_x1, Cy - top1_ycap-1, |
|
0x00ffffff); |
|
|
|
// top2. |
|
int top2_y0 = Cy-R_inner; |
|
int top2_y1 = Cy - Y; |
|
for(int top2_y = top2_y0; top2_y < top2_y1; top2_y++) |
|
{ |
|
int Y_ = Cy - top2_y; |
|
int X_ = Y_to_X_map[Y_]; |
|
int X_inn = Y_to_X_map_inner[Y_]; |
|
//printf("%d = %d - %d\n", Y_, Cy, top2_y); |
|
//printf("X_: %d , X_inn: %d\n", X_, X_inn); |
|
test_draw_horizontal_pixel_line(pixel_buffer, |
|
Cx - X_, Cx - X_inn, top2_y, |
|
draw_command->border_color); |
|
test_draw_horizontal_pixel_line(pixel_buffer, |
|
Cx - X_inn, rect_x1 - R + X_inn, top2_y, |
|
draw_command->color); |
|
test_draw_horizontal_pixel_line(pixel_buffer, |
|
rect_x1 - R + X_inn, rect_x1 - R + X_, top2_y, |
|
draw_command->border_color); |
|
} |
|
if(draw_guide_lines)test_draw_horizontal_pixel_line( |
|
pixel_buffer, |
|
rect_x0, rect_x1, top2_y1, |
|
0x00ffffff); |
|
|
|
// lower1. |
|
int lower1_y0 = Cy - Y; |
|
int lower1_y1 = border_y; |
|
if(border_y > Cy) lower1_y1 = Cy; |
|
for(int lower1_y = lower1_y0; lower1_y < lower1_y1; lower1_y++) |
|
{ |
|
int Y_ = Cy - lower1_y; |
|
int X_ = Y_to_X_map2[Y_]; |
|
//printf("%d = %d - %d\n", Y_, Cy, lower1_y); |
|
//printf("X_: %d\n", X_); |
|
test_draw_horizontal_pixel_line(pixel_buffer, |
|
Cx - X_, rect_x1 - R + X_, lower1_y, |
|
draw_command->border_color); |
|
} |
|
if(draw_guide_lines)test_draw_horizontal_pixel_line( |
|
pixel_buffer, |
|
draw_command->rectangle.x0, draw_command->rectangle.x1, lower1_y1, |
|
0x00ffffff); |
|
|
|
// lower2. |
|
int lower2_y0 = Cy - Y; |
|
if(border_y > lower2_y0) lower2_y0 = border_y; |
|
if(Y_inner < 0) Y_inner = 0; // Will happen if border_thickness > R. |
|
int lower2_y1 = Cy - Y_inner; |
|
for(int lower2_y = lower2_y0; lower2_y < lower2_y1; lower2_y++) |
|
{ |
|
int Y_ = Cy - lower2_y; |
|
int X_ = Y_to_X_map2[Y_]; |
|
int X_inn = Y_to_X_map_inner[Y_]; |
|
//printf("%d = %d - %d\n", Y_, Cy, lower2_y); |
|
//printf("X_: %d , X_inn: %d\n", X_, X_inn); |
|
int x0 = Cx - X_; int x1 = Cx - X_inn; |
|
int x2 = rect_x1 - R + X_inn; int x3 = rect_x1 - R + X_; |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, x0, x1, lower2_y, |
|
draw_command->border_color); |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, x1, x2, lower2_y, |
|
draw_command->color); |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, x2, x3, lower2_y, |
|
draw_command->border_color); |
|
} |
|
if(draw_guide_lines)test_draw_horizontal_pixel_line( |
|
pixel_buffer, |
|
draw_command->rectangle.x0, draw_command->rectangle.x1, lower2_y1, |
|
0x00ffffff); |
|
|
|
// lower3. |
|
int lower3_y0 = Cy - Y_inner; |
|
int lower3_y1 = Cy; |
|
for(int lower3_y = lower3_y0; lower3_y < lower3_y1; lower3_y++) |
|
{ |
|
int Y_ = Cy - lower3_y; |
|
int X_ = Y_to_X_map2[Y_]; |
|
int X_inn = Y_to_X_map2_inner[Y_]; |
|
//printf("%d = %d - %d\n", Y_, Cy, lower3_y); |
|
//printf("X_: %d , X_inn: %d\n", X_, X_inn); |
|
int x0 = Cx - X_; int x1 = Cx - X_inn; |
|
int x2 = rect_x1 - R + X_inn; int x3 = rect_x1 - R + X_; |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, x0, x1, lower3_y, |
|
draw_command->border_color); |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, x1, x2, lower3_y, |
|
draw_command->color); |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, x2, x3, lower3_y, |
|
draw_command->border_color); |
|
} |
|
if(draw_guide_lines)test_draw_horizontal_pixel_line( |
|
pixel_buffer, |
|
draw_command->rectangle.x0, draw_command->rectangle.x1, lower3_y1, |
|
0x00ffffff); |
|
|
|
|
|
// Middle section. |
|
GUI_Rectangle middle = draw_command->rectangle; |
|
middle.y0 = middle.y0 + r; |
|
middle.y1 -= r; |
|
//printf("%d %d -> %d %d\n", draw_command->rectangle.y0, draw_command->rectangle.y1, middle.y0, middle.y1); |
|
int y = middle.y0; |
|
for(; y < middle.y0 + border_thickness-r; y++) |
|
{ |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, middle.x0, middle.x1, y, |
|
draw_command->border_color); |
|
} |
|
for(; y < middle.y1 - (border_thickness-r) && y < middle.y1; y++) |
|
{ |
|
int x = middle.x0; |
|
for(; x < middle.x0+border_thickness && x < middle.x1; x++) |
|
{SET_PIXEL(pixel_buffer, x, y, draw_command->border_color);} |
|
for(; x < middle.x1-border_thickness; x++) |
|
{SET_PIXEL(pixel_buffer, x, y, draw_command->color);} |
|
for(; x < middle.x1; x++) |
|
{SET_PIXEL(pixel_buffer, x, y, draw_command->border_color);} |
|
} |
|
for(; y < middle.y1; y++) |
|
{ |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, middle.x0, middle.x1, y, |
|
draw_command->border_color); |
|
} |
|
|
|
int middle_height = middle.y1-middle.y0; |
|
int Cy1 = Cy + middle_height -1; |
|
border_y = draw_command->rectangle.y1 - border_thickness; |
|
|
|
//printf("middle_height: %d, Cy1: %d, border_y: %d\n", middle_height, Cy1, border_y); |
|
|
|
// top1. |
|
{ |
|
int border_y = draw_command->rectangle.y1 - border_thickness; |
|
int top1_y1 = Cy1+R + 1; |
|
int top1_ycap = Cy1 + Y +1; |
|
//int top1_y0 = 1 + (border_y > top1_ycap ? border_y : top1_ycap); |
|
int top1_y0 = 0 + (border_y > top1_ycap ? border_y : top1_ycap); |
|
for(int top1_y = top1_y0; top1_y < top1_y1; top1_y++) |
|
{ |
|
int Y_ = top1_y - Cy1; |
|
//printf("%d = %d + %d\n", Y_, Cy1, top1_y); |
|
int X_ = Y_to_X_map[Y_]; |
|
//printf("X_: %d\n", X_); |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, |
|
Cx - X_, rect_x1 - R + X_, top1_y, |
|
draw_command->border_color); |
|
} |
|
|
|
// top2. |
|
int top2_y0 = Cy1 + Y +1; |
|
int top2_y1 = Cy1+R_inner +1; |
|
//printf("top2: %d to %d\n", top2_y0, top2_y1); |
|
for(int top2_y = top2_y0; top2_y < top2_y1; top2_y++) |
|
{ |
|
int Y_ = top2_y - Cy1; |
|
int X_ = Y_to_X_map[Y_]; |
|
int X_inn = Y_to_X_map_inner[Y_]; |
|
//printf("%d = %d - %d\n", Y_, Cy, top2_y); |
|
//printf("X_: %d , X_inn: %d\n", X_, X_inn); |
|
test_draw_horizontal_pixel_line(pixel_buffer, |
|
Cx - X_, Cx - X_inn, top2_y, |
|
draw_command->border_color); |
|
test_draw_horizontal_pixel_line(pixel_buffer, |
|
Cx - X_inn, rect_x1 - R + X_inn, top2_y, |
|
draw_command->color); |
|
test_draw_horizontal_pixel_line(pixel_buffer, |
|
rect_x1 - R + X_inn, rect_x1 - R + X_, top2_y, |
|
draw_command->border_color); |
|
} |
|
|
|
// lower1. |
|
int lower1_y0 = border_y; |
|
int lower1_y1 = Cy1 + Y +1; |
|
if(border_y < (Cy1+1)) lower1_y0 = Cy1+1; |
|
for(int lower1_y = lower1_y0; lower1_y < lower1_y1; lower1_y++) |
|
{ |
|
int Y_ = lower1_y - Cy1; |
|
int X_ = Y_to_X_map2[Y_]; |
|
//printf("%d = %d - %d\n", Y_, Cy, lower1_y); |
|
//printf("X_: %d\n", X_); |
|
test_draw_horizontal_pixel_line(pixel_buffer, |
|
Cx - X_, rect_x1 - R + X_, lower1_y, |
|
draw_command->border_color); |
|
} |
|
|
|
// lower2. |
|
if(Y_inner < 0) Y_inner = 0; // Will happen if border_thickness > R. |
|
int lower2_y0 = Cy1 + Y_inner +1; |
|
int lower2_y1 = Cy1 + Y +1; |
|
if(border_y < lower2_y1) lower2_y1 = border_y; |
|
for(int lower2_y = lower2_y0; lower2_y < lower2_y1; lower2_y++) |
|
{ |
|
int Y_ = lower2_y - Cy1; |
|
int X_ = Y_to_X_map2[Y_]; |
|
int X_inn = Y_to_X_map_inner[Y_]; |
|
//printf("%d = %d - %d\n", Y_, Cy, lower2_y); |
|
//printf("X_: %d , X_inn: %d\n", X_, X_inn); |
|
int x0 = Cx - X_; int x1 = Cx - X_inn; |
|
int x2 = rect_x1 - R + X_inn; int x3 = rect_x1 - R + X_; |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, x0, x1, lower2_y, |
|
draw_command->border_color); |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, x1, x2, lower2_y, |
|
draw_command->color); |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, x2, x3, lower2_y, |
|
draw_command->border_color); |
|
} |
|
|
|
// lower3. |
|
int lower3_y0 = Cy1 +1; |
|
int lower3_y1 = Cy1 + Y_inner+1; |
|
for(int lower3_y = lower3_y0; lower3_y < lower3_y1; lower3_y++) |
|
{ |
|
int Y_ = lower3_y - Cy1; |
|
int X_ = Y_to_X_map2[Y_]; |
|
int X_inn = Y_to_X_map2_inner[Y_]; |
|
//printf("%d = %d - %d\n", Y_, Cy, lower3_y); |
|
//printf("X_: %d , X_inn: %d\n", X_, X_inn); |
|
int x0 = Cx - X_; int x1 = Cx - X_inn; |
|
int x2 = rect_x1 - R + X_inn; int x3 = rect_x1 - R + X_; |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, x0, x1, lower3_y, |
|
draw_command->border_color); |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, x1, x2, lower3_y, |
|
draw_command->color); |
|
test_draw_horizontal_pixel_line( |
|
pixel_buffer, x2, x3, lower3_y, |
|
draw_command->border_color); |
|
} |
|
} |
|
if(draw_guide_lines)test_draw_horizontal_pixel_line( |
|
pixel_buffer, |
|
rect_x0, rect_x1, Cy - top1_ycap-1, |
|
0x00ffffff); |
|
|
|
// Debug. |
|
#if 0 |
|
GUI_Color white = 0x00ffffff; |
|
{R2 = R+R;Y = R; X = 0;dX = -2;dY = R2+R2 - 4;D = R2 - 1; |
|
while(X <= Y){ |
|
//setPixel(Cx - X, Cy - Y); |
|
//setPixel(Cx - X, Cy + Y + middle_height -1); |
|
SET_PIXEL(pixel_buffer, Cx-X, Cy-Y, white); |
|
SET_PIXEL(pixel_buffer, Cx-X, Cy+Y + middle_height -1, white); |
|
//setPixel(Cx + X, Cy - Y); |
|
//setPixel(Cx + X, Cy + Y); |
|
//setPixel(Cx - Y, Cy - X); |
|
//setPixel(Cx - Y, Cy + X + middle_height -1); |
|
SET_PIXEL(pixel_buffer, Cx-Y, Cy-X, white); |
|
SET_PIXEL(pixel_buffer, Cx-Y, Cy+X + middle_height -1, white); |
|
//setPixel(Cx + Y, Cy - X); |
|
//setPixel(Cx + Y, Cy + X); |
|
D += dX;dX -= 4;++X; |
|
int Mask = (D >> 31);D += dY & Mask;dY -= 4 & Mask;Y += Mask;}} |
|
{R2_inner = R_inner+R_inner;Y_inner = R_inner; X_inner = 0;dX_inner = -2;dY_inner = R2_inner+R2_inner - 4;D_inner = R2_inner - 1; |
|
while(X_inner <= Y_inner){ |
|
//setPixel(Cx - X_inner, Cy - Y_inner); |
|
//setPixel(Cx - X_inner, Cy + Y_inner + middle_height-1); |
|
SET_PIXEL(pixel_buffer, Cx-X_inner, Cy-Y_inner, white); |
|
SET_PIXEL(pixel_buffer, Cx-X_inner, Cy+Y_inner + middle_height-1, white); |
|
//setPixel(Cx + X_inner, Cy - Y_inner); |
|
//setPixel(Cx + X_inner, Cy + Y_inner); |
|
//setPixel(Cx - Y_inner, Cy - X_inner); |
|
//setPixel(Cx - Y_inner, Cy + X_inner + middle_height-1); |
|
SET_PIXEL(pixel_buffer, Cx-Y_inner, Cy-X_inner, white); |
|
SET_PIXEL(pixel_buffer, Cx-Y_inner, Cy+X_inner + middle_height-1, white); |
|
//setPixel(Cx + Y_inner, Cy - X_inner); |
|
//setPixel(Cx + Y_inner, Cy + X_inner); |
|
D_inner += dX_inner;dX_inner -= 4;++X_inner; |
|
int Mask = (D_inner >> 31);D_inner += dY_inner & Mask;dY_inner -= 4 & Mask;Y_inner += Mask;}} |
|
|
|
{ |
|
printf("%%%%%%%%%%%%%%%%%%%%\n"); |
|
GUI_Color red = 0x00ff4444; |
|
GUI_Color cyan = 0x0000ffff; |
|
GUI_Color yellow = 0x00ffff00; |
|
int x_ = draw_command->rectangle.x0 + R; |
|
int y_ = draw_command->rectangle.y0; |
|
|
|
int Y_ = Y + 1; |
|
|
|
printf("rect corner: x: %d, y: %d\n", x_ -R, y_); |
|
printf("R: %d, R_inner: %d, Cx: %d, Cy: %d\n", R, R_inner, Cx, Cy); |
|
printf("Y: %d, Y_: %d, Y_inner: %d\n", Y, Y_, Y_inner); |
|
|
|
SET_PIXEL(pixel_buffer, Cx, Cy, white); |
|
|
|
if(0)test_draw_horizontal_pixel_line( |
|
pixel_buffer, draw_command->rectangle.x0, draw_command->rectangle.x1, |
|
Cy-Y, |
|
0x00ffffff); |
|
int top1_height, top2_height, lower1_height, lower2_height, lower3_height; |
|
{ |
|
// top1. |
|
int border_y = y_ + border_thickness; |
|
int top1_y0 = Cy-R; |
|
int top1_y = Cy-Y; |
|
//printf("%d %d\n", border_y, top1_y); |
|
int top1_y1 = top1_y < border_y ? top1_y : border_y; |
|
top1_height = top1_y1 - top1_y0; //debug. |
|
test_draw_vertical_pixel_line(pixel_buffer, cyan, |
|
x_, top1_y0, top1_y1); |
|
|
|
// top2. |
|
int top2_y0 = Cy-R_inner; |
|
int top2_y1 = Cy - Y; |
|
top2_height = top2_y1 - top2_y0; // debug. |
|
test_draw_vertical_pixel_line(pixel_buffer, red, |
|
x_+1, top2_y0, top2_y1); |
|
|
|
// lower1. |
|
int lower1_y0 = Cy - Y; |
|
int lower1_y1 = border_y; |
|
if(border_y > Cy) lower1_y1 = Cy; |
|
lower1_height = lower1_y1 - lower1_y0; //debug. |
|
test_draw_vertical_pixel_line(pixel_buffer, yellow, |
|
x_+2, lower1_y0, lower1_y1); |
|
|
|
// lower2. |
|
int lower2_y0 = Cy - Y; |
|
if(border_y > lower2_y0) lower2_y0 = border_y; |
|
if(Y_inner < 0) Y_inner = 0; // Will happen if border_thickness > R. |
|
int lower2_y1 = Cy - Y_inner; |
|
lower2_height = lower2_y1 - lower2_y0; //debug. |
|
//printf("lower2: %d %d\n", lower2_y0, lower2_y1); |
|
test_draw_vertical_pixel_line(pixel_buffer, red, |
|
x_+3, lower2_y0, lower2_y1); |
|
|
|
// lower3. |
|
int lower3_y0 = Cy - Y_inner; |
|
int lower3_y1 = Cy; |
|
lower3_height = lower3_y1 - lower3_y0; //debug. |
|
test_draw_vertical_pixel_line(pixel_buffer, cyan, |
|
x_+4, lower3_y0, lower3_y1); |
|
} |
|
|
|
|
|
if(0)test_draw_horizontal_pixel_line( |
|
pixel_buffer, draw_command->rectangle.x0, draw_command->rectangle.x1, |
|
Cy1+Y, |
|
0x00ffffff); |
|
{ |
|
int Cy1 = Cy + middle_height -1; |
|
printf("Cy1: %d\n", Cy1); |
|
SET_PIXEL(pixel_buffer, Cx, Cy1, white); |
|
// top1. |
|
int border_y = draw_command->rectangle.y1 - border_thickness; |
|
int top1_y1 = Cy1+R+1; |
|
int top1_ycap = Cy1 + Y +1; |
|
//int top1_y0 = 1 + (border_y > top1_ycap ? border_y : top1_ycap); |
|
int top1_y0 = 0 + (border_y > top1_ycap ? border_y : top1_ycap); |
|
//printf("%d %d\n", border_y, top1_y); |
|
if((top1_y1 - top1_y0) != top1_height) printf("top1 height error.\n"); |
|
test_draw_vertical_pixel_line(pixel_buffer, cyan, |
|
x_, top1_y0, top1_y1); |
|
|
|
// top2. |
|
int top2_y0 = Cy1 + Y +1; |
|
int top2_y1 = Cy1+R_inner+1; |
|
if((top2_y1 - top2_y0) != top2_height) printf("top2 height error.\n"); |
|
test_draw_vertical_pixel_line(pixel_buffer, red, |
|
x_+1, top2_y0, top2_y1); |
|
|
|
// lower1. |
|
int lower1_y0 = border_y; |
|
int lower1_y1 = Cy1 + Y +1; |
|
if(border_y < (Cy1+1)) lower1_y0 = Cy1+1; |
|
if((lower1_y1 - lower1_y0) != lower1_height) printf("lower1 height error.\n"); |
|
test_draw_vertical_pixel_line(pixel_buffer, yellow, |
|
x_+2, lower1_y0, lower1_y1); |
|
|
|
// lower2. |
|
if(Y_inner < 0) Y_inner = 0; // Will happen if border_thickness > R. |
|
int lower2_y0 = Cy1 + Y_inner +1; |
|
int lower2_y1 = Cy1 + Y +1; |
|
if(border_y < lower2_y1) lower2_y1 = border_y; |
|
//printf("lower2: %d %d\n", lower2_y0, lower2_y1); |
|
if((lower2_y1 - lower2_y0) != lower2_height) printf("lower2 height error.\n"); |
|
test_draw_vertical_pixel_line(pixel_buffer, red, |
|
x_+3, lower2_y0, lower2_y1); |
|
|
|
// lower3. |
|
int lower3_y0 = Cy1 +1; |
|
int lower3_y1 = Cy1 + Y_inner+1; |
|
if((lower3_y1 - lower3_y0) != lower3_height) printf("lower3 height error.\n"); |
|
test_draw_vertical_pixel_line(pixel_buffer, cyan, |
|
x_+4, lower3_y0, lower3_y1); |
|
} |
|
|
|
|
|
test_draw_vertical_pixel_line(pixel_buffer, cyan, |
|
draw_command->rectangle.x0+80, y_, y_+border_thickness); |
|
test_draw_vertical_pixel_line(pixel_buffer, red, |
|
draw_command->rectangle.x0+90, y_, y_+height); |
|
} |
|
#endif |
|
} |
|
|
|
void gui_draw_rect( |
|
Pixel_Buffer *pixel_buffer, |
|
GUI_Draw_Command *draw_command) |
|
{ |
|
if(draw_command->roundedness == 0) |
|
{ |
|
// TODO(Zelaven) Don't double-draw on the space covered by the inner |
|
// rectangle. |
|
draw_rect3( |
|
pixel_buffer, draw_command->rectangle, draw_command->border_color); |
|
GUI_Rectangle inner = draw_command->rectangle; |
|
inner.x0 += draw_command->border_thickness; |
|
inner.y0 += draw_command->border_thickness; |
|
inner.x1 -= draw_command->border_thickness; |
|
inner.y1 -= draw_command->border_thickness; |
|
if(inner.x0 < inner.x1 && inner.y0 < inner.y1) |
|
{ |
|
draw_rect3(pixel_buffer, inner, draw_command->color); |
|
} |
|
} |
|
else |
|
{ |
|
if(draw_command->border_thickness == 0) |
|
{ |
|
gui_draw_rounded_rect(pixel_buffer, draw_command); |
|
} |
|
else |
|
{ |
|
gui_draw_rounded_rect_with_border(pixel_buffer, draw_command); |
|
} |
|
} |
|
} |
|
|
|
|
|
int init_x11( |
|
X11_Window *xwindow) |
|
{ |
|
xwindow->display = XOpenDisplay(NULL); |
|
if(xwindow->display == NULL) |
|
{ |
|
return EXIT_FAILURE; |
|
} |
|
|
|
xwindow->root = DefaultRootWindow(xwindow->display); |
|
xwindow->screen = XDefaultScreen(xwindow->display); |
|
xwindow->visual = XDefaultVisual(xwindow->display, xwindow->screen); |
|
xwindow->colormap = XCreateColormap( |
|
xwindow->display, xwindow->root, xwindow->visual, AllocNone); |
|
xwindow->set_window_attributes.colormap = xwindow->colormap; |
|
xwindow->set_window_attributes.event_mask = |
|
StructureNotifyMask | ExposureMask | KeyPressMask | KeyReleaseMask | |
|
ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | |
|
Button1MotionMask | Button3MotionMask | |
|
Button4MotionMask | Button5MotionMask | |
|
PointerMotionMask | KeymapStateMask | EnterWindowMask | LeaveWindowMask; |
|
xwindow->window = XCreateWindow( |
|
xwindow->display, xwindow->root, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, 0, |
|
XDefaultDepth(xwindow->display, xwindow->screen), InputOutput, |
|
xwindow->visual, CWEventMask | CWColormap, &xwindow->set_window_attributes); |
|
|
|
#if 0 |
|
XSelectInput( |
|
xwindow->display, |
|
xwindow->window, |
|
StructureNotifyMask | KeyPressMask | KeyReleaseMask | |
|
ButtonPressMask | ButtonReleaseMask | ButtonMotionMask); |
|
#endif |
|
|
|
XStoreName(xwindow->display, xwindow->window, "X11 Test Program"); |
|
XMapWindow(xwindow->display, xwindow->window); |
|
XGetWindowAttributes( |
|
xwindow->display, xwindow->window, &xwindow->window_attributes); |
|
xwindow->width = (unsigned int)xwindow->window_attributes.width; |
|
xwindow->height = (unsigned int)xwindow->window_attributes.height; |
|
|
|
#if 0 |
|
printf("Initial window dimensions: %u x %u\n", xwindow->width, xwindow->height); |
|
printf("Window depth: %d\n", xwindow->window_attributes.depth); |
|
printf("ALL Event mask: %lx\n", xwindow->window_attributes.all_event_masks); |
|
print_binary_long(xwindow->window_attributes.all_event_masks); printf("\n"); |
|
printf("YOUR Event mask: %lx\n", xwindow->window_attributes.your_event_mask); |
|
print_binary_long(xwindow->window_attributes.your_event_mask); printf("\n"); |
|
#endif |
|
|
|
// TODO: This is an experiment. |
|
#if 0 |
|
{ |
|
int display_width = XDisplayWidth(xwindow->display, xwindow->screen); |
|
int display_height = XDisplayHeight(xwindow->display, xwindow->screen); |
|
int display_width_mm = XDisplayWidthMM(xwindow->display, xwindow->screen); |
|
int display_height_mm = XDisplayHeightMM(xwindow->display, xwindow->screen); |
|
printf("Display Resolution: %d, %d.\n", display_width, display_height); |
|
printf("Display Size: %d, %d.\n", display_width_mm, display_height_mm); |
|
printf("Display DPmm: %d, %d.\n", |
|
display_width / display_width_mm, |
|
display_height / display_height_mm); |
|
printf("Display DPI: %d, %d.\n", |
|
display_width / ((display_width_mm*10)/254), |
|
display_height / ((display_height_mm*10)/254)); |
|
|
|
Screen *screen = XScreenOfDisplay(xwindow->display, xwindow->screen); |
|
int screen_width = XWidthOfScreen(screen); |
|
int screen_height = XHeightOfScreen(screen); |
|
int screen_width_mm = XWidthMMOfScreen(screen); |
|
int screen_height_mm = XHeightMMOfScreen(screen); |
|
printf("Screen Resolution: %d, %d.\n", screen_width, screen_height); |
|
printf("Screen Size: %d, %d.\n", screen_width_mm, screen_height_mm); |
|
printf("Screen DPmm: %d, %d.\n", |
|
screen_width / screen_width_mm, |
|
screen_height / screen_height_mm); |
|
printf("Screen DPI: %d, %d.\n", |
|
screen_width / ((screen_width_mm*10)/254), |
|
screen_height / ((screen_height_mm*10)/254)); |
|
} |
|
#endif |
|
// . |
|
|
|
|
|
#if 0 |
|
XSelectInput( |
|
xwindow->display, |
|
xwindow->window, |
|
StructureNotifyMask | KeyPressMask | KeyReleaseMask | |
|
ButtonPressMask | ButtonReleaseMask | ButtonMotionMask); |
|
#endif |
|
|
|
|
|
|
|
// Discard everything preceding the MapNotify event that tells us that we |
|
// have been properly mapped. |
|
// This requires StructureNotifyMask to be selected. |
|
// https://tronche.com/gui/x/xlib-tutorial/2nd-program-anatomy.html |
|
{ |
|
XEvent e; |
|
e.type = 0; |
|
assert(MapNotify != 0); |
|
while(e.type != MapNotify) |
|
{ |
|
XNextEvent(xwindow->display, &e); |
|
} |
|
} |
|
|
|
// Re-get in case the window manager decides to instantiate the window with a |
|
// different size than the one we ask for. |
|
XGetWindowAttributes( |
|
xwindow->display, xwindow->window, &xwindow->window_attributes); |
|
xwindow->width = (unsigned int)xwindow->window_attributes.width; |
|
xwindow->height = (unsigned int)xwindow->window_attributes.height; |
|
|
|
#if 0 |
|
printf("Window dimensions: %u x %u\n", xwindow->width, xwindow->height); |
|
printf("Window depth: %d\n", xwindow->window_attributes.depth); |
|
printf("ALL Event mask: %lx\n", xwindow->window_attributes.all_event_masks); |
|
print_binary_long(xwindow->window_attributes.all_event_masks); printf("\n"); |
|
printf("YOUR Event mask: %lx\n", xwindow->window_attributes.your_event_mask); |
|
print_binary_long(xwindow->window_attributes.your_event_mask); printf("\n"); |
|
#endif |
|
|
|
uint32_t *pixels = malloc(sizeof(*pixels) * xwindow->width * xwindow->height); |
|
assert(pixels != NULL); |
|
|
|
|
|
xwindow->graphics_context = XDefaultGC(xwindow->display, xwindow->screen); |
|
XSetForeground(xwindow->display, xwindow->graphics_context, 0x123456); |
|
|
|
|
|
|
|
|
|
XImage *game_image = XCreateImage( |
|
xwindow->display, |
|
xwindow->visual, |
|
XDefaultDepth(xwindow->display, xwindow->screen), |
|
// NOTE(Zelaven): I can't find an explanation to what this actually is |
|
// anywhere. All the documentation I can find just assumes that you |
|
// magically know. |
|
ZPixmap, |
|
0, |
|
(char*)pixels, |
|
xwindow->width, xwindow->height, |
|
8, // NOTE(Zelaven): No clue |
|
// NOTE(Zelaven): This is how many bytes there are in a row of pixels. |
|
xwindow->width * sizeof(uint32_t)); |
|
assert(game_image != NULL); |
|
fill_pixel_buffer( |
|
pixels, |
|
xwindow->width, |
|
xwindow->height, |
|
0, |
|
0); |
|
|
|
draw_rect( |
|
pixels, |
|
xwindow->width, |
|
xwindow->height, |
|
20, 40, 25, 50, |
|
0xFFFF00); |
|
|
|
GUI_Draw_Command my_draw_command = { |
|
.rectangle = {60, 60, 100, 90}, |
|
.color = 0xFF00FF, |
|
}; |
|
xwindow->pixel_buffer = (Pixel_Buffer){ |
|
.pixels = (GUI_Color*)pixels, |
|
.width = xwindow->width, |
|
.height = xwindow->height, |
|
}; |
|
draw_rect2( |
|
&xwindow->pixel_buffer, |
|
&my_draw_command); |
|
|
|
xwindow->screen_image = game_image; |
|
return EXIT_SUCCESS; |
|
} |
|
|
|
|
|
|
|
|
|
// --- |
|
// --- |
|
// --- |
|
|
|
|
|
|
|
void init_node_freelist(GUI_Node *nodes, int node_count) |
|
{ |
|
// NOTE(Zelaven): We special-case the last node. |
|
for(int i = 0; i < node_count -1; i++) |
|
{ |
|
nodes[i] = (GUI_Node){0}; |
|
nodes[i].rdic_node = (RDIC_Node){ |
|
.sibling = &((nodes+i+1)->rdic_node), |
|
}; |
|
} |
|
nodes[node_count-1].rdic_node = (RDIC_Node){0}; |
|
} |
|
|
|
GUI_Node g_gui_node_freelist[128]; |
|
|
|
|
|
|
|
#include "size_of_file.c" |
|
|
|
struct File_Read_Result { |
|
unsigned char *data; |
|
size_t size; |
|
} read_file__cruddy( |
|
FILE *file) |
|
{ |
|
struct File_Read_Result retval = {0}; |
|
size_t status = 0; |
|
long filesize = size_of_file(file); |
|
if(filesize == -1) { |
|
status = 1;} |
|
if(status == 0) { |
|
size_t memory_size = filesize; |
|
memory_size = ((memory_size + 4096 - 1) / 4096) * 4096; |
|
void *file_memory = mmap( |
|
NULL, memory_size, |
|
PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); |
|
if(file_memory == (void*)-1) { |
|
//perror("mmap"); |
|
status = 2; |
|
} |
|
else |
|
{ |
|
retval.data = file_memory; |
|
} |
|
} |
|
if(status == 0) { |
|
if(fread(retval.data, filesize, 1, file) != 1) { |
|
status = 3;} |
|
} |
|
|
|
if(status == 0) { |
|
retval.size = (size_t)filesize;} |
|
else { |
|
if(retval.data != NULL) { |
|
free(retval.data); |
|
retval.data = NULL;} |
|
retval.size = status; |
|
} |
|
|
|
return retval; |
|
} |
|
|
|
|
|
void copy_from_atlas( |
|
Pixel_Buffer *pixel_buffer, |
|
int off_x, int off_y, |
|
int x, int y, int w, int h) |
|
{ |
|
draw_rect( |
|
pixel_buffer->pixels, |
|
pixel_buffer->width, |
|
pixel_buffer->height, |
|
off_x, off_y, off_x+w, off_y+h, |
|
0x00ffffff); |
|
for(int j=0; j < h; ++j) { |
|
for(int i=0; i < w; ++i) |
|
{ |
|
int atlas_x = x + i; |
|
int atlas_y = y + j; |
|
int atlas_index = atlas_y * g_font_bitmap_width + atlas_x; |
|
unsigned char atlas_shade = g_font_bitmap[atlas_index]; |
|
int color = (atlas_shade << 16) | (atlas_shade << 8) | atlas_shade; |
|
//printf("%d ", color); |
|
SET_PIXEL(pixel_buffer, x+off_x+i, y+off_y+j, color); |
|
} |
|
} |
|
//printf("\n"); |
|
} |
|
|
|
void blend_glyph( |
|
Pixel_Buffer *pixel_buffer, |
|
stbtt_bakedchar *bakedchar, |
|
int x, |
|
int y) |
|
{ |
|
int glyph_height = bakedchar->y1 - bakedchar->y0; |
|
int glyph_width = bakedchar->x1 - bakedchar->x0; |
|
//printf("%d, %d - %d, %d\n", x, y, glyph_width, glyph_height); |
|
|
|
if(0)draw_rect( |
|
pixel_buffer->pixels, |
|
pixel_buffer->width, |
|
pixel_buffer->height, |
|
x, y, x+glyph_width, y+glyph_height, |
|
0x00ffffff); |
|
|
|
for(int j=0; j < glyph_height; ++j) { |
|
for(int i=0; i < glyph_width; ++i) |
|
{ |
|
//unsigned char font_pixel_shade = glyph_bitmap[j*glyph_size.w+i]; |
|
int atlas_x = bakedchar->x0 + i; |
|
int atlas_y = bakedchar->y0 + j; |
|
int atlas_index = atlas_y * g_font_bitmap_width + atlas_x; |
|
GUI_Color atlas_shade = (GUI_Color)g_font_bitmap[atlas_index]; |
|
//GUI_Color color = (atlas_shade << 16) | (atlas_shade << 8) | atlas_shade; |
|
//GUI_Color color = atlas_shade; |
|
GUI_Color pixel_color = GET_PIXEL(pixel_buffer, x+i, y+j); |
|
//GUI_Color pixel_color = 0x00aa764a; |
|
int red = ((pixel_color & 0x00ff0000) >> 16) * (256-atlas_shade) / 256; |
|
int green = ((pixel_color & 0x0000ff00) >> 8) * (256-atlas_shade) / 256; |
|
int blue = ((pixel_color & 0x000000ff)) * (256-atlas_shade) / 256; |
|
GUI_Color new_color = (red << 16) | (green << 8) | blue; |
|
//GUI_Color new_color = pixel_color * (255-color) / 255; |
|
//GUI_Color new_color = (atlas_shade << 16) | (atlas_shade << 8) | atlas_shade; |
|
//printf("%x %x %x\n", color, pixel_color, new_color); |
|
SET_PIXEL(pixel_buffer, x+i, y+j, new_color); |
|
//*pixel_color = *pixel_color * (255-font_pixel_shade) / 255; |
|
//*pixel_color = font_pixel_shade; |
|
} |
|
} |
|
//printf("\n"); |
|
} |
|
|
|
int text_measure_string_width(char *string, size_t str_length) |
|
{ |
|
int result = 0; |
|
for(size_t i = 0; i < str_length; i++) |
|
{ |
|
int glyph_index = string[i] - 32; |
|
stbtt_bakedchar *glyph = &g_baked_font[glyph_index]; |
|
result += glyph->xadvance; |
|
} |
|
|
|
return result; |
|
} |
|
|
|
#include <limits.h> |
|
struct Text_Y_Bounds {int upper; int lower;} |
|
text_measure_string_y_bounds(char *string, size_t str_length) |
|
{ |
|
struct Text_Y_Bounds result = {.upper = INT_MAX, .lower = INT_MIN}; |
|
for(size_t i = 0; i < str_length; i++) |
|
{ |
|
int glyph_index = string[i] - 32; |
|
stbtt_bakedchar *glyph = &g_baked_font[glyph_index]; |
|
int height = glyph->y1 - glyph->y0; |
|
int offset = glyph->yoff; // This is negative. |
|
int uppermost_pixel = offset; |
|
int lowermost_pixel = height + offset; |
|
//print_bakedchar(string[i], glyph); |
|
if(uppermost_pixel < result.upper) { |
|
result.upper = uppermost_pixel; |
|
} |
|
if(lowermost_pixel > result.lower) { |
|
result.lower = lowermost_pixel; |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
void blend_string( |
|
Pixel_Buffer *pixel_buffer, |
|
GUI_String text, |
|
int x, int y) |
|
{ |
|
int next_x = x; |
|
for(int i = 0; i < text.length; i++) |
|
{ |
|
int glyph_index = text.cstring[i] - 32; |
|
stbtt_bakedchar *g = &g_baked_font[glyph_index]; |
|
blend_glyph( |
|
pixel_buffer, |
|
g, |
|
next_x + g->xoff, y + g->yoff); |
|
next_x += g->xadvance; |
|
} |
|
} |
|
|
|
void gui_draw_text( |
|
Pixel_Buffer *pixel_buffer, |
|
GUI_Draw_Command *draw_command) |
|
{ |
|
if(draw_command->text.cstring == NULL) { |
|
return; |
|
} |
|
//printf("%.*s: %d\n", draw_command->text.length, draw_command->text.cstring, draw_command->text.length); |
|
int text_width = text_measure_string_width( |
|
draw_command->text.cstring, draw_command->text.length); |
|
int width = draw_command->rectangle.x1 - draw_command->rectangle.x0; |
|
int x = draw_command->rectangle.x0 + width/2 - text_width/2; |
|
|
|
struct Text_Y_Bounds y_bounds = text_measure_string_y_bounds( |
|
draw_command->text.cstring, draw_command->text.length); |
|
int y_bounds_middle = (y_bounds.lower + y_bounds.upper)/2; |
|
int height = draw_command->rectangle.y1 - draw_command->rectangle.y0; |
|
int y = draw_command->rectangle.y0 + height/2 - y_bounds_middle; |
|
#if 0 |
|
printf("%.*s: %d, (%d,%d) %d -> %d, %d\n", |
|
draw_command->text.length, draw_command->text.cstring, |
|
text_width, y_bounds.upper, y_bounds.lower, y_bounds_middle, |
|
x, y); |
|
#endif |
|
blend_string( |
|
pixel_buffer, |
|
draw_command->text, |
|
x, y); |
|
#if 0 |
|
SET_PIXEL(pixel_buffer, x, draw_command->rectangle.y0 + height/2, 0x00ff0000); |
|
SET_PIXEL(pixel_buffer, draw_command->rectangle.x0 + width/2, draw_command->rectangle.y0 + height/2, 0x00ffff00); |
|
SET_PIXEL(pixel_buffer, x, y, 0x0000ff00); |
|
#endif |
|
} |
|
|
|
void gui_draw_image( |
|
Pixel_Buffer *pixel_buffer, |
|
GUI_Draw_Command *draw_command) |
|
{ |
|
if(draw_command->image == NULL) { |
|
return; |
|
} |
|
|
|
// TODO(Zelaven): Clipping. |
|
|
|
int off_x = draw_command->rectangle.x0; |
|
int off_y = draw_command->rectangle.y0; |
|
Pixel_Buffer *image = draw_command->image; |
|
int w = image->width; |
|
int h = image->height; |
|
|
|
for(int j=0; j < h; ++j) |
|
{ |
|
for(int i=0; i < w; ++i) |
|
{ |
|
int image_x = i; |
|
int image_y = j; |
|
int image_index = image_y * w + image_x; |
|
GUI_Color pixel = image->pixels[image_index]; |
|
SET_PIXEL(pixel_buffer, off_x+i, off_y+j, pixel); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
#include <unistd.h> // usleep. |
|
int main(void) |
|
{ |
|
void (*gui)(GUI_Context *,GUI_Rectangle); |
|
|
|
// Font initialization. |
|
Memory_Arena font_arena = {0}; |
|
assert(linux_allocate_arena_memory( |
|
&font_arena, |
|
g_font_bitmap_width*g_font_bitmap_height) == 0); |
|
FILE *font_file = fopen("michroma/Michroma-Regular.ttf", "r"); |
|
//FILE *font_file = fopen("/usr/share/fonts/TTF/Roboto-Black.ttf", "r"); |
|
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? |
|
assert(font_data.size > 16); |
|
g_font_bitmap = memory_arena_allocate(&font_arena, sizeof(char), _Alignof(char)); |
|
{ |
|
int bake_font_return_value = stbtt_BakeFontBitmap( |
|
font_data.data, 0, |
|
20.0, |
|
g_font_bitmap, g_font_bitmap_width, g_font_bitmap_height, |
|
32, 96, g_baked_font); |
|
|
|
//printf("bake_font_return_value: %d\n", bake_font_return_value); |
|
if(bake_font_return_value < 1) |
|
{ |
|
return EXIT_FAILURE; |
|
} |
|
} |
|
|
|
|
|
|
|
// GUI initializaiton. |
|
|
|
//gui = test_gui; |
|
//gui = test_gui__calculator; |
|
//gui = test_gui__scrollbars; |
|
//gui = test_gui__draw_command_using_sliders; |
|
gui = test_gui__subtree; |
|
|
|
X11_Window xwindow = {0}; |
|
int xinit_status = init_x11(&xwindow); |
|
if(xinit_status != EXIT_SUCCESS) |
|
{ |
|
return EXIT_FAILURE; |
|
} |
|
|
|
Memory_Arena draw_command_arena = {0}; |
|
assert(linux_allocate_arena_memory( |
|
&draw_command_arena, |
|
1024*4) == 0); |
|
|
|
GUI_Context context = {0}; |
|
#if 0 |
|
init_node_freelist(g_gui_node_freelist, ARRAYLENGTH(g_gui_node_freelist)); |
|
context.rdic.node_freelist = &g_gui_node_freelist[0].rdic_node; |
|
#else |
|
init_node_freelist(g_gui_node_freelist, ARRAYLENGTH(g_gui_node_freelist)); |
|
context.node_freelist = &g_gui_node_freelist[0].rdic_node; |
|
#endif |
|
|
|
context.draw_command_arena = &draw_command_arena; |
|
context.num_draw_commands = 0; |
|
|
|
fill_pixel_buffer( |
|
xwindow.pixel_buffer.pixels, |
|
xwindow.width, |
|
xwindow.height, |
|
0, |
|
0); |
|
GUI_Rectangle screen_rectangle = { |
|
.x0 = 0, .y0 = 0, |
|
.x1 = xwindow.width, .y1 = xwindow.height, |
|
}; |
|
//gui(&context, screen_rectangle); |
|
//gui_layout_nodes(&context); |
|
#if 0 |
|
gui_generate_draw_commands( |
|
context.root, |
|
&draw_command_arena, |
|
&context.num_draw_commands); |
|
GUI_Draw_Command *draw_commands = (GUI_Draw_Command*)(draw_command_arena.memory); |
|
for(int i = 0; i < context.num_draw_commands; i++) |
|
{ |
|
draw_rect2( |
|
&pixel_buffer, |
|
&draw_commands[i]); |
|
} |
|
for(int i = 4; i < 100; i += 5) |
|
{ |
|
pixel_buffer.pixels[i*xwindow.width + 10] = 0x0000FFFF; |
|
} |
|
draw_pixel_buffer( |
|
xwindow.width, |
|
xwindow.height, |
|
xwindow.display, |
|
xwindow.window, |
|
xwindow.screen_image, |
|
xwindow.graphics_context); |
|
#endif |
|
|
|
//print_node_tree(context.rdic.root, 1); |
|
//getc(stdin); |
|
|
|
bool running = true; |
|
while(running) |
|
{ |
|
usleep(2*100000); |
|
context.mouse_pressed = false; |
|
while(XPending(xwindow.display)) |
|
{ |
|
XEvent x_event = {0}; |
|
XNextEvent(xwindow.display, &x_event); |
|
if(XFilterEvent(&x_event, xwindow.window)) |
|
{ |
|
continue; |
|
} |
|
switch(x_event.type) |
|
{ |
|
case ButtonPress: |
|
{ |
|
//printf("Button pressed: %d\n", x_event.xbutton.button); |
|
context.mouse_pressed = true; |
|
context.mouse_down = true; |
|
} break; |
|
case ButtonRelease: |
|
{ |
|
//printf("Button released: %d\n", x_event.xbutton.button); |
|
context.mouse_down = false; |
|
} break; |
|
case MotionNotify: |
|
{ |
|
//printf("Pointer motion: %d, %d\n", x_event.xmotion.x, x_event.xmotion.y); |
|
context.mouse_x = x_event.xmotion.x; |
|
context.mouse_y = x_event.xmotion.y; |
|
} break; |
|
case KeyPress: |
|
{ |
|
KeySym keysym = XLookupKeysym( |
|
&x_event.xkey, |
|
0); // What does 0 mean here? |
|
(void) keysym; |
|
|
|
//printf("Keysym pressed: %s\n", XKeysymToString(keysym)); |
|
//printf("\tkeycode: %d\n", x_event.xkey.keycode); |
|
//printf("\tkeysym code: %ld\n", keysym); |
|
switch(x_event.xkey.keycode) |
|
{ |
|
case 38: break; // a |
|
case 40: break; // d |
|
case 25: break; // w |
|
case 39: break; // s |
|
//case 24: running = false; break; // q |
|
case 24: // q |
|
{ |
|
// Setting the flag here is necessary. |
|
running = false; |
|
// Can I just do this after the loop iteration finishes? |
|
XDestroyWindow(xwindow.display, xwindow.window); |
|
} break; |
|
} |
|
|
|
} break; |
|
case KeyRelease: |
|
{ |
|
KeySym keysym = XLookupKeysym( |
|
&x_event.xkey, |
|
0); // What does 0 mean here? |
|
(void) keysym; |
|
|
|
//printf("Keysym released: %s\n", XKeysymToString(keysym)); |
|
switch(x_event.xkey.keycode) |
|
{ |
|
case 38: break; // a |
|
case 40: break; // d |
|
case 25: break; // w |
|
case 39: break; // s |
|
} |
|
|
|
} break; |
|
case DestroyNotify: |
|
{ |
|
running = false; |
|
} break; |
|
default: |
|
{ |
|
//printf("Unknown event, type: %d\n", x_event.type); |
|
} break; |
|
} |
|
} |
|
|
|
static bool meh = true; |
|
if(running && meh) |
|
{ |
|
gui(&context, screen_rectangle); |
|
//gui_layout_nodes(&context); |
|
#if 1 |
|
for( |
|
GUI_Subtree *current = context.first_subtree; |
|
current != NULL; |
|
current = current->next) |
|
{ |
|
gui_layout_nodes((GUI_Node*)current->rdic.root); |
|
gui_generate_draw_commands( |
|
//(GUI_Node*)current->rdic.root, |
|
current, |
|
&draw_command_arena, |
|
&context.num_draw_commands); |
|
|
|
GUI_Draw_Command *draw_command = |
|
(GUI_Draw_Command*)(draw_command_arena.memory); |
|
for(int i = 0; i < context.num_draw_commands; i++) |
|
{ |
|
#if 0 |
|
if(i > 0) |
|
(draw_command+i)->roundedness = roundedness; |
|
#endif |
|
gui_draw_rect( |
|
&xwindow.pixel_buffer, |
|
draw_command+i); |
|
gui_draw_image( |
|
&xwindow.pixel_buffer, |
|
draw_command+i); |
|
gui_draw_text( |
|
&xwindow.pixel_buffer, |
|
draw_command+i); |
|
} |
|
} |
|
#else |
|
gui_generate_draw_commands( |
|
(GUI_Node*)context.first_subtree->rdic.root, |
|
&draw_command_arena, |
|
&context.num_draw_commands); |
|
|
|
GUI_Draw_Command *draw_command = |
|
(GUI_Draw_Command*)(draw_command_arena.memory); |
|
for(int i = 0; i < context.num_draw_commands; i++) |
|
{ |
|
#if 0 |
|
if(i > 0) |
|
(draw_command+i)->roundedness = roundedness; |
|
#endif |
|
gui_draw_rect( |
|
&xwindow.pixel_buffer, |
|
draw_command+i); |
|
gui_draw_text( |
|
&xwindow.pixel_buffer, |
|
draw_command+i); |
|
} |
|
#endif |
|
#if 0 |
|
GUI_Draw_Command test_draw_command = { |
|
.rectangle = {50, 150, 250, 250}, |
|
.color = 0x00aa8888, |
|
.border_color = 0x00ffaaff, |
|
.border_thickness = border_thickness, |
|
.roundedness = roundedness, |
|
}; |
|
gui_draw_rounded_rect(&xwindow.pixel_buffer, &test_draw_command); |
|
GUI_Draw_Command test_draw_command2 = { |
|
.rectangle = {50, 300, 250, 400}, |
|
.color = 0x00aa8888, |
|
.border_color = 0x00ffaaff, |
|
.border_thickness = border_thickness, |
|
.roundedness = roundedness, |
|
}; |
|
gui_draw_rounded_rect_with_border(&xwindow.pixel_buffer, &test_draw_command2); |
|
//meh = false; |
|
#endif |
|
#if 0 |
|
//print_bakedchar(32+1, &g_baked_font[1]); |
|
blend_glyph( |
|
&xwindow.pixel_buffer, |
|
&g_baked_font[1], |
|
80, 170); |
|
blend_glyph( |
|
&xwindow.pixel_buffer, |
|
&g_baked_font[1], |
|
80+g_baked_font[1].xadvance, 170); |
|
char *text = "This is a text! VA"; |
|
int textlen = sizeof("This is a text! VA")-1; |
|
int text_width = text_measure_string_width(text, textlen); |
|
//printf("Text width: %d\n", text_width); |
|
int next_x = 80; |
|
test_draw_horizontal_pixel_line( |
|
&xwindow.pixel_buffer, |
|
next_x, next_x + text_width, 195, |
|
0); |
|
for(int i = 0; i < textlen; i++) |
|
{ |
|
int glyph_index = text[i] - 32; |
|
stbtt_bakedchar *g = &g_baked_font[glyph_index]; |
|
blend_glyph( |
|
&xwindow.pixel_buffer, |
|
g, |
|
next_x + g->xoff, 195 + g->yoff); |
|
next_x += g->xadvance; |
|
} |
|
blend_string( |
|
&xwindow.pixel_buffer, |
|
//GUI_STRING("This is a text! VA"), |
|
GUI_STRING("a = 19 + 23; 1234567890C+-*/="), |
|
360, 260); |
|
test_draw_horizontal_pixel_line( |
|
&xwindow.pixel_buffer, |
|
360, 360+200, 260, |
|
0x00ff0000); |
|
copy_from_atlas( |
|
&xwindow.pixel_buffer, |
|
300, 100, |
|
0, 0, 512, 100); |
|
//SET_PIXEL(&xwindow.pixel_buffer, 100, 100, 0x00ffffff); |
|
#endif |
|
draw_pixel_buffer( |
|
xwindow.width, |
|
xwindow.height, |
|
xwindow.display, |
|
xwindow.window, |
|
xwindow.screen_image, |
|
xwindow.graphics_context); |
|
|
|
#if 1 |
|
|
|
// NOTE(Zelaven): What does this _really_ do and why would you want it? |
|
//XClearWindow(xwindow.display, xwindow.window); |
|
|
|
|
|
/* |
|
GUI_Draw_Command *draw_command = |
|
(GUI_Draw_Command*)(draw_command_arena.memory); |
|
for(int i = 0; i < context.num_draw_commands; i++) |
|
{ |
|
//xstuff_filled_rect(&xwindow, draw_command+i); |
|
|
|
xstuff_draw_rect( |
|
&xwindow, |
|
draw_command+i); |
|
} |
|
*/ |
|
|
|
//xstuff_blit_window( |
|
// &xwindow); |
|
XFlush(xwindow.display); |
|
#endif |
|
} |
|
|
|
} |
|
|
|
XDestroyImage(xwindow.screen_image); |
|
//XUnmapWindow(xwindow.display, xwindow.window); |
|
//XDestroyWindow(xwindow.display, xwindow.window); |
|
XFreeColormap(xwindow.display, xwindow.colormap); |
|
XCloseDisplay(xwindow.display); |
|
|
|
|
|
//getc(stdin); |
|
return 0; |
|
} |
|
|
|
|
|
|
|
|