From 569294f2923827c7ac54d9584970c9755d39d1e1 Mon Sep 17 00:00:00 2001 From: Klemen Plestenjak Date: Thu, 21 Dec 2023 15:27:49 +0100 Subject: [PATCH] Basic UI system --- engine/breeze/defines.h | 4 + engine/breeze/ui/ui.c | 430 ++++++++++++++++++++++++++++++++++-- engine/breeze/ui/ui.h | 135 +++++++---- engine/tests/CMakeLists.txt | 3 + engine/tests/ui_test.c | 47 ++++ 5 files changed, 552 insertions(+), 67 deletions(-) create mode 100644 engine/tests/ui_test.c diff --git a/engine/breeze/defines.h b/engine/breeze/defines.h index ea365e6..d6b7a82 100644 --- a/engine/breeze/defines.h +++ b/engine/breeze/defines.h @@ -21,6 +21,10 @@ typedef double f64; #define BZ_ASSERT(e) assert(e) +#define BZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define BZ_MIN(a, b) BZ_MAX(b, a) +#define BZ_ABS(a) ((a >= 0) ? (a) : (-(a))) + #define DEBUG_MODE #ifndef DEBUG_MODE #undef BZ_ASSERT diff --git a/engine/breeze/ui/ui.c b/engine/breeze/ui/ui.c index 7f3c86c..c29f72d 100644 --- a/engine/breeze/ui/ui.c +++ b/engine/breeze/ui/ui.c @@ -1,56 +1,105 @@ #include "ui.h" +#include "../core/logger.h" #include "../memory/memory.h" #include "../util/object_pool.h" #include "../util/string.h" +#include "../util/array.h" +#include #include +typedef struct BzUINode { + BzUINode *parent; + // Children + BzUINode *first; + BzUINode *last; + // Siblings + BzUINode *prev; + BzUINode *next; + + // Key+generation info + BzUIKey key; + u64 lastFrame; + bool changed; + + // Per-frame info provided by builders + BzUILayout layout; + BzUIStyle style; + BzUIFlags flags; + const char *string; + BzUISize semanticSize[BZ_UI_AXIS_COUNT]; + f32 padding[BZ_UI_AXIS_COUNT * 2]; + f32 margin[BZ_UI_AXIS_COUNT * 2]; + + // recomputed every frame + f32 computedPosition[BZ_UI_AXIS_COUNT]; + f32 computedSize[BZ_UI_AXIS_COUNT]; + + BzUIInteraction interaction; +} BzUINode; + typedef struct BzUI { struct { - BzUIKey key; - BzUIWidget *value; - } *widgetMap; - BzObjectPool *widgets; - BzUIWidget **widgetStack; - BzUIWidget *root; + BzUIKey key; + BzUINode *value; + } *nodeMap; + BzObjectPool *nodePool; + BzUINode **nodeStack; + BzUINode *root; + + u64 currFrame; + u64 idCount; // Per-frame } BzUI; BzUIKey bzUIKeyNull() { return 0; } BzUIKey bzUIKeyFromString(const char *str) { + BZ_ASSERT(str); return bzStringDefaultHash(str); } +static void bzUINodeClearLinks(BzUINode *node) { + BZ_ASSERT(node); + node->parent = NULL; + node->first = NULL; + node->last = NULL; + node->prev = NULL; + node->next = NULL; +} + BzUI *bzUICreate() { BzUI *ui = bzAlloc(sizeof(*ui)); - ui->widgetMap = NULL; - ui->widgetStack = NULL; - - ui->widgets = bzObjectPoolCreate(&(BzObjectPoolDesc) { + ui->nodeMap = NULL; + hmdefault(ui->nodeMap, NULL); + ui->nodePool = bzObjectPoolCreate(&(BzObjectPoolDesc) { .objectsPerPage=128, - .objectSize=sizeof(BzUIWidget), + .objectSize=sizeof(BzUINode), }); + ui->nodeStack = bzArrayCreate(BzUINode *, 10); - ui->root = bzObjectPool(ui->widgets); + ui->root = bzObjectPool(ui->nodePool); bzMemSet(ui->root, 0, sizeof(*ui->root)); - hmput(ui->widgetMap, bzUIKeyFromString("##root"), ui->root); + ui->root->key = bzUIKeyFromString("##root"); + + hmput(ui->nodeMap, ui->root->key, ui->root); return ui; } void bzUIDestroy(BzUI *ui) { - hmfree(ui->widgetMap); - ui->widgetMap = NULL; - arrfree(ui->widgetStack); - ui->widgetStack = NULL; - bzObjectPoolDestroy(ui->widgets); - ui->widgets = NULL; + hmfree(ui->nodeMap); + bzObjectPoolDestroy(ui->nodePool); + ui->nodePool = NULL; + bzArrayDestroy(ui->nodeStack); + ui->nodeStack = NULL; bzFree(ui); } void bzUIBegin(BzUI *ui, i32 width, i32 height) { - arrsetlen(ui->widgetStack, 0); - arrpush(ui->widgetStack, ui->root); + bzArrayClear(ui->nodeStack); + bzArrayPush(ui->nodeStack, ui->root); + + bzUINodeClearLinks(ui->root); ui->root->semanticSize[BZ_UI_AXIS_X] = (BzUISize){ .kind = BZ_UI_SIZE_PIXELS, @@ -61,7 +110,344 @@ void bzUIBegin(BzUI *ui, i32 width, i32 height) { .value = height }; + ui->currFrame++; + ui->root->lastFrame = ui->currFrame; + ui->idCount = 1; } -void bzUIEnd(BzUI *ui) { + +static Rectangle getNodeRect(const BzUINode *node) { + return (Rectangle) { + .x = node->computedPosition[BZ_UI_AXIS_X], + .y = node->computedPosition[BZ_UI_AXIS_Y], + .width = node->computedSize[BZ_UI_AXIS_X], + .height = node->computedSize[BZ_UI_AXIS_Y], + }; +} +static void calculateAxisSizePreorder(const BzUIAxis axis, BzUINode *node) { + f32 compSize = 0; + switch (node->semanticSize[axis].kind) { + case BZ_UI_SIZE_PIXELS: + compSize = node->semanticSize[axis].value; + break; + case BZ_UI_SIZE_FIT: + BZ_ASSERT(node->string); + Vector2 size = MeasureTextEx(GetFontDefault(), node->string, node->style.fontSize, 2); + compSize = (axis == BZ_UI_AXIS_X) ? size.x : size.y; + break; + case BZ_UI_SIZE_PARENT_PERCENT: + BZ_ASSERT(node->parent); + compSize = node->parent->computedSize[axis] * node->semanticSize[axis].value; + break; + case BZ_UI_SIZE_NULL: + default: + break; + } + if (node->computedSize[axis] != compSize) { + node->changed = true; + } + node->computedSize[axis] = compSize; +} +static void calculateAxisSizePostorder(const BzUIAxis axis, const BzUINode *node) { + f32 compSize = 0; + switch (node->semanticSize[axis].kind) { + case BZ_UI_SIZE_CHILD_SUM: + BZ_ASSERT(node->first); + for (BzUINode *child = node->first; child; child = child->next) { + compSize += child->computedSize[axis]; + } + break; + case BZ_UI_SIZE_CHILD_MAX: + BZ_ASSERT(node->first); + for (BzUINode *child = node->first; child; child = child->next) { + compSize = BZ_MAX(compSize, child->computedSize[axis]); + } + break; + default: + break; + } +} +static void calculateSizes(BzUINode *node) { + BzUINode *child = node->first; + + calculateAxisSizePreorder(BZ_UI_AXIS_X, node); + calculateAxisSizePreorder(BZ_UI_AXIS_Y, node); + while (child != NULL) { + calculateSizes(child); + child = child->next; + } + calculateAxisSizePostorder(BZ_UI_AXIS_X, node); + calculateAxisSizePostorder(BZ_UI_AXIS_Y, node); +} + +static void calculatePositions(BzUINode *node, f32 x, f32 y); +static void calculatePositionsFlexBox(BzUINode *node) { + BZ_ASSERT(node->layout.type == BZ_UI_LAYOUT_FLEX_BOX); + BzUIFlags flags = node->layout.flags; + + f32 totalMainAxisSize = 0.0f; + + const i32 MAIN_AXIS = (flags & BZ_UI_FLEX_DIR_COLUMN) ? BZ_UI_AXIS_Y : BZ_UI_AXIS_X; + const i32 CROSS_AXIS = (flags & BZ_UI_FLEX_DIR_COLUMN) ? BZ_UI_AXIS_X : BZ_UI_AXIS_Y; + + for (BzUINode *child = node->first; child; child = child->next) { + totalMainAxisSize += child->computedSize[MAIN_AXIS]; + } + + f32 mainAxisTotalSpacing = node->computedSize[MAIN_AXIS] - totalMainAxisSize; + mainAxisTotalSpacing = BZ_MAX(0, mainAxisTotalSpacing); + + // Default: JUSTIFY_START + f32 mainAxisOffset = 0.0f; + if (flags & BZ_UI_FLEX_JUSTIFY_CENTER) { + mainAxisOffset = mainAxisTotalSpacing * 0.5f; + } else if (flags & BZ_UI_FLEX_JUSTIFY_END) { + mainAxisOffset = mainAxisTotalSpacing; + } + + f32 axisOffset[BZ_UI_AXIS_COUNT]; + axisOffset[MAIN_AXIS] = node->computedPosition[MAIN_AXIS] + mainAxisOffset; + + for (BzUINode *child = node->first; child; child = child->next) { + // Default: ALIGN_START + axisOffset[CROSS_AXIS] = node->computedPosition[CROSS_AXIS]; + if (flags & BZ_UI_FLEX_ALIGN_CENTER) { + axisOffset[CROSS_AXIS] += (node->computedSize[CROSS_AXIS] - child->computedSize[CROSS_AXIS]) * 0.5f; + } else if (flags & BZ_UI_FLEX_ALIGN_END) { + axisOffset[CROSS_AXIS] += node->computedSize[CROSS_AXIS] - child->computedSize[CROSS_AXIS]; + } + + calculatePositions(child, axisOffset[BZ_UI_AXIS_X], axisOffset[BZ_UI_AXIS_Y]); + axisOffset[MAIN_AXIS] += child->computedSize[MAIN_AXIS]; + } } +static void calculatePositions(BzUINode *node, f32 x, f32 y) { + node->computedPosition[BZ_UI_AXIS_X] = x; + node->computedPosition[BZ_UI_AXIS_Y] = y; + + BzUINode *child = node->first; + while (child != NULL) { + switch (child->layout.type) { + case BZ_UI_LAYOUT_FLEX_BOX: + calculatePositionsFlexBox(child); + break; + default: + calculatePositions(child, x, y); + break; + } + child = child->next; + } +} + +static void removeNode(BzUI *ui, BzUINode *node) { + BZ_ASSERT(ui); + BZ_ASSERT(node); + + BzUINode *child = node->first; + while (child != NULL) { + removeNode(ui, child); + hmdel(ui->nodeMap, child->key); + child = child->next; + } +} +static void pruneStale(BzUI *ui, BzUINode *node) { + BZ_ASSERT(node); + BzUINode *child = node->first; + + BzUINode *first = NULL; + BzUINode *last = NULL; + + while (child != NULL) { + if (child->lastFrame != node->lastFrame) { + BzUINode *next = child->next; + removeNode(ui, child); + if (child->prev) { + child->prev->next = next; + } + child = next; + } else { + if (!first) { + first = child; + } + pruneStale(ui, child); + child = child->next; + last = child; + } + } + + node->first = first; + node->last = last; +} + +static void updateNodeInteraction(BzUI *ui, BzUINode *node, Vector2 mouse) { + BZ_ASSERT(node); + + bool hovered = CheckCollisionPointRec(mouse, getNodeRect(node)); + node->interaction = (BzUIInteraction) { + .pressed = hovered && IsMouseButtonPressed(MOUSE_BUTTON_LEFT), + .down = hovered && IsMouseButtonDown(MOUSE_BUTTON_LEFT), + .released = hovered && IsMouseButtonReleased(MOUSE_BUTTON_LEFT), + .clicked = hovered && IsMouseButtonReleased(MOUSE_BUTTON_LEFT), + .hovering = hovered + }; + + BzUINode *child = node->first; + while (child != NULL) { + updateNodeInteraction(ui, child, mouse); + child = child->next; + } + + +} + +static void renderNode(BzUI *ui, BzUINode *node) { + BZ_ASSERT(ui); + BZ_ASSERT(node); + + BzUIStyle *style = &node->style; + BzUIInteraction *inter = &node->interaction; + + Rectangle rect = getNodeRect(node); + + if (node->flags & BZ_UI_WIDGET_DRAW_BACKGROUND) { + Color color = style->bgColor; + if (inter->hovering) color = style->bgHoverColor; + if (inter->down) color = style->bgActiveColor; + DrawRectangle(rect.x, rect.y, rect.width, rect.height, color); + } + if (node->flags & BZ_UI_WIDGET_DRAW_BORDER && style->borderWidth > 0) { + Color color = style->borderColor; + if (inter->hovering) color = style->borderHoverColor; + if (inter->down) color = style->borderActiveColor; + DrawRectangleLinesEx(rect, style->borderWidth, color); + } + if (node->flags & BZ_UI_WIDGET_DRAW_TEXT) { + Color color = style->textColor; + if (inter->hovering) color = style->textHoverColor; + if (inter->down) color = style->textActiveColor; + DrawTextEx(GetFontDefault(), node->string, (Vector2){rect.x, rect.y}, style->fontSize, 1, color); + } + + node->changed = false; + + BzUINode *child = node->first; + while (child != NULL) { + renderNode(ui, child); + child = child->next; + } +} + +void bzUIEnd(BzUI *ui) { + pruneStale(ui, ui->root); + calculateSizes(ui->root); + calculatePositions(ui->root, 0, 0); + updateNodeInteraction(ui, ui->root, GetMousePosition()); + renderNode(ui, ui->root); +} + +BzUINode *bzUINodeMake(BzUI *ui, const BzUINodeDesc *desc) { + BzUIKey key = desc->key; + if (key == bzUIKeyNull()) { + key = bzUIKeyFromString(desc->string); + } + BzUINode *node = hmget(ui->nodeMap, key); + if (!node) { + node = bzObjectPool(ui->nodePool); + bzMemSet(node, 0, sizeof(*node)); + hmput(ui->nodeMap, key, node); + node->changed = true; + } + BZ_ASSERT(node); + node->lastFrame = ui->currFrame; + node->key = key; + + BzUINode *parent = bzArrayGet(ui->nodeStack, bzArraySize(ui->nodeStack) - 1); + BZ_ASSERT(parent); + + if (parent->last) { + parent->last->next = node; + node->prev = parent->last; + parent->last = node; + } else { + parent->first = node; + parent->last = node; + } + + node->parent = parent; + + node->flags = desc->flags; + node->string = desc->string; + node->semanticSize[BZ_UI_AXIS_X] = desc->semanticSize[BZ_UI_AXIS_X]; + node->semanticSize[BZ_UI_AXIS_Y] = desc->semanticSize[BZ_UI_AXIS_Y]; + node->layout = desc->layout; + node->style = desc->style; + + return node; +} + +BzUINode *bzUIPushParent(BzUI *ui, BzUINode *node) { + BZ_ASSERT(node); + bzArrayPush(ui->nodeStack, node); + return node; +} +BzUINode *bzUIPopParent(BzUI *ui) { + BZ_ASSERT(bzArraySize(ui->nodeStack) > 1); + BzUINode *node = bzArrayPop(ui->nodeStack); + return node; +} + +void bzUIPushLayout(BzUI *ui, BzUILayout layout) { + BzUINode *node = bzUINodeMake(ui, &(BzUINodeDesc) { + .flags = BZ_UI_WIDGET_NONE, + .string = NULL, + .key = ui->idCount++, + .semanticSize[BZ_UI_AXIS_X] = { + .kind = BZ_UI_SIZE_PARENT_PERCENT, + .value = 1.0f, + }, + .semanticSize[BZ_UI_AXIS_Y] = { + .kind = BZ_UI_SIZE_PARENT_PERCENT, + .value = 1.0f, + }, + .layout = layout + }); + bzUIPushParent(ui, node); +} +void bzUIPopLayout(BzUI *ui) { + BzUINode *last = bzArrayGet(ui->nodeStack, bzArraySize(ui->nodeStack) - 1); + BZ_ASSERT(last->flags == BZ_UI_WIDGET_NONE); + bzUIPopParent(ui); +} + +BzUIInteraction bzUIGetInteraction(BzUI *ui, BzUINode *node) { + BZ_ASSERT(node); + return node->interaction; +} +bool bzUIButton(BzUI *ui, const char *string, BzUIStyle *style) { + BzUIStyle s = { + .fontSize = 24, + .borderWidth = 1, + .borderColor = BLACK, + .borderHoverColor = BLACK, + .borderActiveColor = GRAY, + .textColor = BLACK, + .textHoverColor = RED, + .textActiveColor = ORANGE, + }; + if (style) { + s = *style; + } + BzUINode *node = bzUINodeMake(ui, &(BzUINodeDesc) { + .flags = BZ_UI_WIDGET_CLICKABLE | BZ_UI_WIDGET_DRAW_TEXT | + BZ_UI_WIDGET_DRAW_BORDER | BZ_UI_WIDGET_ALIGN_CENTER, + .string = string, + .semanticSize[BZ_UI_AXIS_X] = (BzUISize) { + .kind = BZ_UI_SIZE_FIT, + }, + .semanticSize[BZ_UI_AXIS_Y] = (BzUISize) { + .kind = BZ_UI_SIZE_FIT, + }, + .style = s, + }); + return bzUIGetInteraction(ui, node).clicked; +} diff --git a/engine/breeze/ui/ui.h b/engine/breeze/ui/ui.h index 8baccb3..0581d7f 100644 --- a/engine/breeze/ui/ui.h +++ b/engine/breeze/ui/ui.h @@ -5,11 +5,16 @@ #include +typedef u32 BzUIFlags; +typedef u32 BzUIKey; + typedef enum BzUISizeKind { BZ_UI_SIZE_NULL, BZ_UI_SIZE_PIXELS, BZ_UI_SIZE_FIT, - BZ_UI_SIZE_REL_PARENT, + BZ_UI_SIZE_PARENT_PERCENT, + BZ_UI_SIZE_CHILD_SUM, + BZ_UI_SIZE_CHILD_MAX, } BzUISizeKind; typedef struct BzUISize { @@ -17,59 +22,94 @@ typedef struct BzUISize { f32 value; } BzUISize; +typedef enum BzUILayoutType { + BZ_UI_LAYOUT_NONE, + BZ_UI_LAYOUT_UNIDIR, + BZ_UI_LAYOUT_FLEX_BOX +} BzUILayoutType; + +enum { + // UNIDIRECTIONAL + BZ_UI_UNIDIR_NONE = 0, + BZ_UI_UNIDIR_ROW = (1 << 0), + BZ_UI_UNIDIR_COLUMN = (1 << 1), + BZ_UI_UNIDIR_WRAP = (1 << 2), + // FLEX BOX + BZ_UI_FLEX_NONE = 0, + BZ_UI_FLEX_DIR_ROW = (1 << 0), + BZ_UI_FLEX_DIR_COLUMN = (1 << 1), + BZ_UI_FLEX_ALIGN_START = (1 << 2), + BZ_UI_FLEX_ALIGN_CENTER = (1 << 3), + BZ_UI_FLEX_ALIGN_END = (1 << 4), + BZ_UI_FLEX_JUSTIFY_START = (1 << 5), + BZ_UI_FLEX_JUSTIFY_CENTER = (1 << 6), + BZ_UI_FLEX_JUSTIFY_END = (1 << 7) +}; + +typedef struct BzUILayout { + BzUILayoutType type; + BzUIFlags flags; +} BzUILayout; + +typedef struct BzUIStyle { + i32 fontSize; + i32 borderWidth; + Color textColor; + Color textHoverColor; + Color textActiveColor; + Color bgColor; + Color bgHoverColor; + Color bgActiveColor; + Color borderColor; + Color borderHoverColor; + Color borderActiveColor; +} BzUIStyle; + + + typedef enum BzUIAxis { BZ_UI_AXIS_X, BZ_UI_AXIS_Y, BZ_UI_AXIS_COUNT, } BzUIAxis; -typedef u32 BzUIWidgetFlags; enum { - BZ_UI_WIDGET_CLICKABLE = (1 << 0), - BZ_UI_WIDGET_VIEW_SCROLL = (1 << 1), - BZ_UI_WIDGET_DRAW_TEXT = (1 << 2), - BZ_UI_WIDGET_DRAW_BORDER = (1 << 3), + BZ_UI_WIDGET_NONE = 0, + BZ_UI_WIDGET_CLICKABLE = (1 << 0), + BZ_UI_WIDGET_DRAW_TEXT = (1 << 1), + BZ_UI_WIDGET_DRAW_BORDER = (1 << 2), + BZ_UI_WIDGET_DRAW_BACKGROUND = (1 << 3), + //BZ_UI_WIDGET_DRAW_SPRITE = (1 << 4), + BZ_UI_WIDGET_ALIGN_HORIZ_START = (1 << 5), + BZ_UI_WIDGET_ALIGN_HORIZ_CENTER = (1 << 6), + BZ_UI_WIDGET_ALIGN_HORIZ_END = (1 << 7), + BZ_UI_WIDGET_ALIGN_VERT_START = (1 << 8), + BZ_UI_WIDGET_ALIGN_VERT_CENTER = (1 << 9), + BZ_UI_WIDGET_ALIGN_VERT_END = (1 << 10), + + BZ_UI_WIDGET_ALIGN_CENTER = BZ_UI_WIDGET_ALIGN_HORIZ_CENTER | BZ_UI_WIDGET_ALIGN_VERT_CENTER, }; -typedef u32 BzUIKey; -typedef struct BzUIWidget BzUIWidget; -typedef struct BzUIWidget { - BzUIWidget *first; - BzUIWidget *last; - BzUIWidget *next; - BzUIWidget *prev; - BzUIWidget *parent; +typedef struct BzUINode BzUINode; - // Key+generation info - BzUIKey key; - u64 lastFrameTouchedIndex; +typedef struct BzUIInteraction { + bool pressed : 1; + bool down : 1; + bool released : 1; + bool clicked : 1; + //bool dragging : 1; + bool hovering : 1; +} BzUIInteraction; - // Per-frame info provided by builders - BzUIWidgetFlags flags; +typedef struct BzUINodeDesc { + BzUIFlags flags; const char *string; + BzUIKey key; BzUISize semanticSize[BZ_UI_AXIS_COUNT]; - - // recomputed every frame - f32 computedRelPosition[BZ_UI_AXIS_COUNT]; - f32 computedSize[BZ_UI_AXIS_COUNT]; - Rectangle rect; -} BzUIWidget; - -typedef struct BzUIResult { - BzUIWidget *widget; - bool pressed; - bool released; - //bool dragging; - bool hovering; -} BzUIResult; - -typedef struct BzUIWidgetDesc { - BzUIWidgetFlags flags; - const char *string; - BzUIKey key; - -} BzUIWidgetDesc; + BzUILayout layout; + BzUIStyle style; +} BzUINodeDesc; typedef struct BzUI BzUI; @@ -84,15 +124,20 @@ void bzUIEnd(BzUI *ui); // Widget construction -BzUIWidget *bzUIWidgetMake(const BzUIWidgetDesc *desc); +BzUINode *bzUINodeMake(BzUI *ui, const BzUINodeDesc *desc); -BzUIWidget *bzUIPushParent(BzUIWidget *widget); -BzUIWidget *bzUIPopParent(); +BzUINode *bzUIPushParent(BzUI *ui, BzUINode *node); +BzUINode *bzUIPopParent(BzUI *ui); -BzUIResult bzUIGetResult(BzUIWidget *widget); +// Widget layout +void bzUIPushLayout(BzUI *ui, BzUILayout layout); +void bzUIPopLayout(BzUI *ui); + +// Widget interaction +BzUIInteraction bzUIGetInteraction(BzUI *ui, BzUINode *node); // UI -bool bzUIButton(const char *string); +bool bzUIButton(BzUI *ui, const char *string, BzUIStyle *style); #endif //BREEZE_UI_CORE_H diff --git a/engine/tests/CMakeLists.txt b/engine/tests/CMakeLists.txt index 06f2770..52a39b1 100644 --- a/engine/tests/CMakeLists.txt +++ b/engine/tests/CMakeLists.txt @@ -19,3 +19,6 @@ target_link_libraries(array_test LINK_PRIVATE Breeze) add_executable(pan_test pan_test.c) target_link_libraries(pan_test LINK_PRIVATE Breeze) + +add_executable(ui_test ui_test.c) +target_link_libraries(ui_test LINK_PRIVATE Breeze) diff --git a/engine/tests/ui_test.c b/engine/tests/ui_test.c new file mode 100644 index 0000000..fc9d63c --- /dev/null +++ b/engine/tests/ui_test.c @@ -0,0 +1,47 @@ +#define BZ_ENTRYPOINT +#include + +#include +#include + +BzUI *ui; + +bool init(int *game) { + rlImGuiSetup(true); + + ui = bzUICreate(); + + return true; +} + +void render(float dt, int *game) { + ClearBackground(WHITE); + + + bzUIBegin(ui, GetScreenWidth(), GetScreenHeight()); + bzUIPushLayout(ui, *&(BzUILayout) { + .type = BZ_UI_LAYOUT_FLEX_BOX, + .flags = BZ_UI_FLEX_DIR_COLUMN | BZ_UI_FLEX_ALIGN_CENTER | BZ_UI_FLEX_JUSTIFY_CENTER, + }); + if (bzUIButton(ui, "Hello world", NULL)) { + bzLogInfo("Hello world"); + } + if (bzUIButton(ui, "foo", NULL)) { + bzLogInfo("foo"); + } + if (bzUIButton(ui, "bar", NULL)) { + bzLogInfo("bar"); + } + bzUIEnd(ui); + + rlImGuiBegin(); + igShowDemoWindow(NULL); + rlImGuiEnd(); +} + +bool bzMain(BzAppDesc *appDesc, int argc, const char **argv) { + appDesc->init = (BzAppInitFunc) init; + appDesc->render = (BzAppRenderFunc) render; + + return true; +}