mar 9 gen 2024, 16:00:03, CET

rewrite2
Alessandro Mauri 11 months ago
parent 7909306d7a
commit 28b5ee16fb
  1. 130
      ARCHITECTURE.md
  2. 368
      ugui.c
  3. 24
      ugui.h

@ -90,75 +90,59 @@ multiple +--------v---------+ |
### Layouting ### Layouting
```text Layouting happens in a dynamic grid, when a new element is inserted in a non-floating
parent div manner it reserves a space in the grid, new elements are placed following this grid.
[0,0]->+------------------------------------------------+
| | Every div has two points of origin, one for the row layout and one for the column
| | layout, named origin_r and origin_c respectively
| |
| |
| |
| |
| |
| space_r |
| space_c | begin div1
| | ----+
| | |
| | |
| | |
| | |
| | |
| | |
| | |
+------------------------------------------------+ |
|
|
parent div |
[0,0]->+----------------------+-------------------------+ |
| | | |
| | | |
| | | <---+
| div 1 | | end div1
| | |
| | |
| | |
| | |
+----------------------+ space_r |
| | |
| | | begin div2
| | | ----+
| | | |
| space_c | | |
| | | |
| | | |
| | | |
+----------------------+-------------------------+ |
|
|
parent div |
[0,0]->+----------------------+--------+----------------+ |
| | | | |
| | div 2 | | |
| | | | |
| div 1 +--------+ | |
| |xxxxxxxx| | |
| |xxxxxxxx| | <---+
| |xxxxxxxx| | end div2
| |xxxxxxxx| |
+----------------------+--------+ space_r |
| | |
| | |
| | |
| | |
| space_c | |
| | |
| | |
| | |
+-------------------------------+----------------+
+-+ origin_r is used when the row layout is used and it is used to position the child
|x|: Lost Space elements one next to the other, as such it always points to the top-right edge
+-+ of the last row element
```text
Layout: row
#: lost space
Parent div
x---------------------------------+
|[origin_c] |
|[origin_r] |
| |
| |
| |
| |
| |
| |
| |
+---------------------------------+
Parent div
+-----x---------------------------+
| |[origin_r] |
| E1 | |
| | |
x-----+---------------------------+
|[origin_c] |
| | |
| | |
| | |
| | |
| | |
+-----+---------------------------+
Parent div
+-----+----------+-----x----------+
| | E2 | |[origin_r]|
| E1 +----------+ | |
| |##########| E3 | |
+-----+##########| | |
|################| | |
+----------------x-----+----------+
| [origin_c] |
| | |
| | |
| | |
+----------------+----------------+
``` ```
TODO: handle when the content overflows the div TODO: handle when the content overflows the div
@ -170,6 +154,10 @@ is clipped to the view and scrollbars are shown
- individual elements accept dimensions and the x/y coordinates could be interpreted - individual elements accept dimensions and the x/y coordinates could be interpreted
as offset if the layout is row/column or absolute coordinates if the leayout is floating as offset if the layout is row/column or absolute coordinates if the leayout is floating
A div can be marked resizeable or fixed, and static or dynamic. The difference being
that resizeable adds a resize handle to the div and dynamic lets the content overflow
causing scrollbars to be drawn
### Notes ### Notes
How elements determine if they have focus or not How elements determine if they have focus or not
@ -188,3 +176,9 @@ if(PARENT_HAS_FOCUS()) {
fast path to return fast path to return
} }
``` ```
How to get ids:
1. use a name for each element
2. supply an id for each element
3. use a macro and the line position as id and then hash it
4. use a macro, get the code line and hash it

368
ugui.c

@ -8,6 +8,20 @@
#include "raylib.h" #include "raylib.h"
#include "ugui.h" #include "ugui.h"
#define RGBA(u) \
(UgColor) \
{ \
.r = (u >> 24) & 0xff, .g = (u >> 16) & 0xff, .b = (u >> 8) & 0xff, \
.a = (u >> 0) & 0xff \
}
#define DIV_FILL \
(UgRect) { .x = 0, .y = 0, .w = 0, .h = 0 }
#define STACK_STEP 10
#define MAX_ELEMS 128
#define MAX_CMDS 256
#define ROOT_ID 1
typedef struct _UgCmd { typedef struct _UgCmd {
enum { enum {
CMD_RECT = 0, CMD_RECT = 0,
@ -15,8 +29,8 @@ typedef struct _UgCmd {
union { union {
struct { struct {
int32_t x, y, w, h; UgRect rect;
uint8_t color[4]; // rgba, [0] = r UgColor color;
} rect; } rect;
}; };
} UgCmd; } UgCmd;
@ -58,8 +72,6 @@ int ug_stack_free(UgStack *stack)
return 0; return 0;
} }
#define STACK_STEP 10
int ug_stack_push(UgStack *stack, UgCmd *cmd) int ug_stack_push(UgStack *stack, UgCmd *cmd)
{ {
if (stack == NULL || cmd == NULL) { if (stack == NULL || cmd == NULL) {
@ -100,6 +112,10 @@ int ug_stack_pop(UgStack *stack, UgCmd *cmd)
return 0; return 0;
} }
enum InputEventFlags {
INPUT_CTX_SIZE = 1 << 0, // window size was changed
};
struct _UgCtx { struct _UgCtx {
enum { enum {
row = 0, row = 0,
@ -115,14 +131,29 @@ struct _UgCtx {
int width, height; int width, height;
} size; } size;
// input structure, it describes the events received between frames
struct {
uint32_t flags;
} input;
int div_using; // tree node indicating the current active div int div_using; // tree node indicating the current active div
}; };
int ug_div_begin(UgCtx *ctx, const char *name, UgRect div); // layouting
int ug_layout_row(void);
int ug_layout_column(void);
int ug_layout_floating(void);
int ug_layout_next_row(void);
int ug_layout_next_column(void);
int ug_div_begin(UgCtx *ctx, const char *label, UgRect div);
int ug_div_end(UgCtx *ctx); int ug_div_end(UgCtx *ctx);
int ug_input_window_size(UgCtx *ctx, int width, int height); int ug_input_window_size(UgCtx *ctx, int width, int height);
UgId djb2(const char *str); UgId djb2(const char *str);
int ug_button(UgCtx *ctx, const char *label, UgRect size);
int main(void) int main(void)
{ {
UgCtx ctx; UgCtx ctx;
@ -136,18 +167,21 @@ int main(void)
// Main loop // Main loop
while (!WindowShouldClose()) { while (!WindowShouldClose()) {
// UI handling // Input handling
ug_frame_begin(&ctx);
if (IsWindowResized()) { if (IsWindowResized()) {
width = GetScreenWidth(); width = GetScreenWidth();
height = GetScreenHeight(); height = GetScreenHeight();
ug_input_window_size(&ctx, width, height); ug_input_window_size(&ctx, width, height);
} }
// main div, fill the whole window // UI handling
#define DIV_FILL (UgRect) {.x = 0, .y = 0, .w = -1, .h = -1} ug_frame_begin(&ctx);
// main div, fill the whole window
ug_div_begin(&ctx, "main", DIV_FILL); ug_div_begin(&ctx, "main", DIV_FILL);
ug_button(&ctx, "button0", (UgRect){.w = 100, .h = 16});
ug_div_end(&ctx); ug_div_end(&ctx);
ug_frame_end(&ctx); ug_frame_end(&ctx);
@ -156,26 +190,30 @@ int main(void)
BeginDrawing(); BeginDrawing();
ClearBackground(BLACK); ClearBackground(BLACK);
Rectangle r;
Color c; Color c;
for (UgCmd cmd; ug_stack_pop(&ctx.stack, &cmd) >= 0;) { for (UgCmd cmd; ug_stack_pop(&ctx.stack, &cmd) >= 0;) {
printf("bruh\n");
switch (cmd.type) { switch (cmd.type) {
case CMD_RECT: case CMD_RECT:
printf( //printf(
"rect x=%d y=%d w=%d h=%d\n", // "rect x=%d y=%d w=%d h=%d\n",
cmd.rect.x, // cmd.rect.rect.x,
cmd.rect.y, // cmd.rect.rect.y,
cmd.rect.w, // cmd.rect.rect.w,
cmd.rect.h // cmd.rect.rect.h
); //);
c = (Color) { c = (Color) {
.r = cmd.rect.color[0], .r = cmd.rect.color.r,
.g = cmd.rect.color[1], .g = cmd.rect.color.g,
.b = cmd.rect.color[2], .b = cmd.rect.color.b,
.a = cmd.rect.color[3], .a = cmd.rect.color.a,
}; };
DrawRectangle( DrawRectangle(
cmd.rect.x, cmd.rect.y, cmd.rect.w, cmd.rect.h, c cmd.rect.rect.x,
cmd.rect.rect.y,
cmd.rect.rect.w,
cmd.rect.rect.h,
c
); );
break; break;
default: default:
@ -195,9 +233,6 @@ int main(void)
return 0; return 0;
} }
#define MAX_ELEMS 128
#define MAX_CMDS 256
int ug_init(UgCtx *ctx) int ug_init(UgCtx *ctx)
{ {
if (ctx == NULL) { if (ctx == NULL) {
@ -226,9 +261,80 @@ int ug_destroy(UgCtx *ctx)
return 0; return 0;
} }
int ug_frame_begin(UgCtx *ctx) { return 0; } void print_tree(UgCtx *ctx)
{
printf("ctx->tree: [");
for (int c = -1, x; (x = ug_tree_level_order_it(&ctx->tree, 0, &c)) != -1;) {
printf("%d, ", x);
}
printf("-1]\n");
}
int ug_frame_begin(UgCtx *ctx)
{
if (ctx == NULL) {
return -1;
}
// 1. Create the root div element
UgRect space = {
.x = 0,
.y = 0,
.w = ctx->size.width,
.h = ctx->size.height,
};
UgElem root = {
.id = ROOT_ID,
.type = ETYPE_DIV,
.flags = 0,
.rect = space,
.div =
{
.layout = DIV_LAYOUT_ROW,
.origin_r = {0},
.origin_c = {0},
.color_bg = {0},
},
};
// The root should have the updated flag only if the size of the window
// was changed between frames, this propagates an element size recalculation
// down the element tree
if (ctx->input.flags & INPUT_CTX_SIZE) {
root.flags |= ELEM_UPDATED;
}
// FIXME: check errors
uint32_t cache_idx;
ug_cache_insert(&ctx->cache, &root, &cache_idx);
ctx->div_using = ug_tree_add(&ctx->tree, root.id, 0);
if (ctx->div_using < 0) {
printf("why?\n");
return -1;
}
//print_tree(ctx);
// The root element does not push anything to the stack
// TODO: add a background color taken from a theme or config
return 0;
}
int ug_frame_end(UgCtx *ctx)
{
if (ctx == NULL) {
return -1;
}
// 1. clear the tree
ug_tree_prune(&ctx->tree, 0);
// 2. clear input fields
ctx->input.flags = 0;
int ug_frame_end(UgCtx *ctx) { return 0; } return 0;
}
int ug_input_window_size(UgCtx *ctx, int width, int height) int ug_input_window_size(UgCtx *ctx, int width, int height)
{ {
@ -262,28 +368,34 @@ UgId djb2(const char *str)
return hash; return hash;
} }
int ug_div_begin(UgCtx *ctx, const char *name, UgRect div) int ug_div_begin(UgCtx *ctx, const char *label, UgRect div)
{ {
if (ctx == NULL || name == NULL) { if (ctx == NULL || label == NULL) {
return -1; return -1;
} }
UgId id = djb2(name); UgId id = djb2(label);
// TODO: do layouting if the element is new or the parent has updated
int is_new_elem = 0;
// add the element if it does not exist // add the element if it does not exist
UgElem *c_elem = ug_cache_search(&ctx->cache, id); UgElem *c_elem = ug_cache_search(&ctx->cache, id);
if (c_elem == NULL) { if (c_elem == NULL) {
UgElem elem = { UgElem elem = {0};
.id = id,
.type = ETYPE_DIV,
.rec = (UgRect) {
.x = div.x,
.y = div.y,
.w = div.w >= 0 ? div.w : ctx->size.width,
.h = div.h >= 0 ? div.h : ctx->size.height,
}};
uint32_t c_idx; uint32_t c_idx;
c_elem = ug_cache_insert(&ctx->cache, &elem, &c_idx); c_elem = ug_cache_insert(&ctx->cache, &elem, &c_idx);
is_new_elem = 1;
}
// take a reference to the parent
// FIXME: if the tree held pointers to the elements then no more
// redundant cache search
UgId parent_id = ctx->tree.vector[ctx->div_using];
UgElem *parent = ug_cache_search(&ctx->cache, parent_id);
if (parent == NULL) {
// Error, did you forget to do frame_begin()?
return -1;
} }
// FIXME: why save the id in the tree and not something more direct like // FIXME: why save the id in the tree and not something more direct like
@ -295,22 +407,78 @@ int ug_div_begin(UgCtx *ctx, const char *name, UgRect div)
} }
ctx->div_using = div_node; ctx->div_using = div_node;
// printf( //print_tree(ctx);
// "elem.rect: x=%d x=%d w=%d h=%d\n",
// c_elem->rec.x, // layouting
// c_elem->rec.y, // TODO: do layout
// c_elem->rec.w, if (is_new_elem || parent->flags & ELEM_UPDATED) {
// c_elem->rec.h c_elem->id = id;
// ); c_elem->type = ETYPE_DIV;
// 1. Select the right origin offset
switch (parent->div.layout) {
case DIV_LAYOUT_ROW:
c_elem->rect = (UgRect) {
.x = parent->div.origin_r.x + div.x,
.y = parent->div.origin_r.y + div.y,
};
break;
case DIV_LAYOUT_COLUMN:
c_elem->rect = (UgRect) {
.x = parent->div.origin_c.x + div.x,
.y = parent->div.origin_c.y + div.y,
};
break;
case DIV_LAYOUT_FLOATING:
c_elem->rect = (UgRect) {
.x = div.x,
.y = div.y,
};
break;
default:
// Error
break;
}
// 2. Calculate width & height
// TODO: what about negative values?
c_elem->rect.w = div.w != 0 ? div.w : parent->rect.w;
c_elem->rect.h = div.h != 0 ? div.h : parent->rect.h;
// 3. Mark the element as updated
c_elem->flags |= ELEM_UPDATED;
// 4. Set div information
c_elem->div.layout = parent->div.layout;
c_elem->div.origin_c = (UgPoint) {
.x = c_elem->rect.x,
.y = c_elem->rect.y,
};
c_elem->div.origin_r = c_elem->div.origin_c;
c_elem->div.color_bg = RGBA(0xff0000ff);
// 4. Update the origins of the parent
parent->div.origin_r = (UgPoint) {
.x = c_elem->rect.x + c_elem->rect.w,
.y = c_elem->rect.y,
};
parent->div.origin_c = (UgPoint) {
.x = c_elem->rect.x,
.y = c_elem->rect.y + c_elem->rect.h,
};
} else {
// TODO: check active
// TODO: check resizeable
// TODO: check scrollbars
}
// Add the background to the draw stack
UgCmd cmd = { UgCmd cmd = {
.type = CMD_RECT, .type = CMD_RECT,
.rect = .rect =
{ {
.x = c_elem->rec.x, .rect = c_elem->rect,
.y = c_elem->rec.y, .color = c_elem->div.color_bg,
.w = c_elem->rec.w,
.h = c_elem->rec.h,
.color = {0xff, 0x00, 0x00, 0xff}, // red
}, },
}; };
ug_stack_push(&ctx->stack, &cmd); ug_stack_push(&ctx->stack, &cmd);
@ -324,3 +492,99 @@ int ug_div_end(UgCtx *ctx)
ctx->div_using = ug_tree_parentof(&ctx->tree, ctx->div_using); ctx->div_using = ug_tree_parentof(&ctx->tree, ctx->div_using);
return 0; return 0;
} }
int ug_button(UgCtx *ctx, const char *label, UgRect size)
{
if (ctx == NULL || label == NULL) {
return -1;
}
UgId id = djb2(label);
// TODO: do layouting if the element is new or the parent has updated
int is_new_elem = 0;
// add the element if it does not exist
UgElem *c_elem = ug_cache_search(&ctx->cache, id);
if (c_elem == NULL) {
UgElem elem = {0};
uint32_t c_idx;
c_elem = ug_cache_insert(&ctx->cache, &elem, &c_idx);
is_new_elem = 1;
}
// take a reference to the parent
// FIXME: if the tree held pointers to the elements then no more
// redundant cache search
UgId parent_id = ctx->tree.vector[ctx->div_using];
UgElem *parent = ug_cache_search(&ctx->cache, parent_id);
if (parent == NULL) {
// Error, did you forget to do frame_begin()?
return -1;
}
// if the element is new or the parent was updated then redo layout
if (is_new_elem || parent->flags & ELEM_UPDATED) {
c_elem->id = id;
c_elem->type = ETYPE_BUTTON;
// 1. Select the right origin offset
switch (parent->div.layout) {
case DIV_LAYOUT_ROW:
c_elem->rect = (UgRect) {
.x = parent->div.origin_r.x + size.x,
.y = parent->div.origin_r.y + size.y,
};
break;
case DIV_LAYOUT_COLUMN:
c_elem->rect = (UgRect) {
.x = parent->div.origin_c.x + size.x,
.y = parent->div.origin_c.y + size.y,
};
break;
case DIV_LAYOUT_FLOATING:
c_elem->rect = (UgRect) {
.x = size.x,
.y = size.y,
};
break;
default:
// Error
break;
}
// 2. Calculate width & height
// TODO: what about negative values?
c_elem->rect.w = size.w != 0 ? size.w : parent->rect.w;
c_elem->rect.h = size.h != 0 ? size.h : parent->rect.h;
/// Not a div, so not needed
// 3. Mark the element as updated
// 4. Set div information
// 4. Update the origins of the parent
parent->div.origin_r = (UgPoint) {
.x = c_elem->rect.x + c_elem->rect.w,
.y = c_elem->rect.y,
};
parent->div.origin_c = (UgPoint) {
.x = c_elem->rect.x,
.y = c_elem->rect.y + c_elem->rect.h,
};
} else {
// TODO: Check for interactions
}
// Draw the button
UgCmd cmd = {
.type = CMD_RECT,
.rect =
{
.rect = c_elem->rect,
.color = RGBA(0x0000ffff),
},
};
ug_stack_push(&ctx->stack, &cmd);
return 0;
}

@ -20,15 +20,35 @@ typedef uint64_t UgId;
typedef enum { typedef enum {
ETYPE_NONE = 0, ETYPE_NONE = 0,
ETYPE_DIV, ETYPE_DIV,
ETYPE_BUTTON,
} UgElemType; } UgElemType;
enum UgElemFlags {
ELEM_UPDATED = 1 << 0,
};
typedef struct { typedef struct {
UgId id; UgId id;
UgRect rec; uint32_t flags;
UgRect rect;
union { union {
uint32_t type_int; uint32_t type_int;
UgElemType type; UgElemType type;
}; };
// type-specific fields
union {
struct {
enum {
DIV_LAYOUT_ROW = 0,
DIV_LAYOUT_COLUMN,
DIV_LAYOUT_FLOATING,
} layout;
UgPoint origin_r, origin_c;
UgColor color_bg;
} div; // Div
};
} UgElem; } UgElem;
// TODO: add a packed flag // TODO: add a packed flag
@ -67,11 +87,9 @@ void ug_cache_free(UgElemCache *cache);
UgElem *ug_cache_search(UgElemCache *cache, UgId id); UgElem *ug_cache_search(UgElemCache *cache, UgId id);
UgElem *ug_cache_insert(UgElemCache *cache, const UgElem *g, uint32_t *index); UgElem *ug_cache_insert(UgElemCache *cache, const UgElem *g, uint32_t *index);
int ug_init(UgCtx *ctx); int ug_init(UgCtx *ctx);
int ug_destroy(UgCtx *ctx); int ug_destroy(UgCtx *ctx);
int ug_frame_begin(UgCtx *ctx); int ug_frame_begin(UgCtx *ctx);
int ug_frame_end(UgCtx *ctx); int ug_frame_end(UgCtx *ctx);
#endif // _UGUI_H #endif // _UGUI_H

Loading…
Cancel
Save