Basic UI system
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 <raymath.h>
|
||||
#include <stb_ds.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -5,11 +5,16 @@
|
||||
|
||||
#include <raylib.h>
|
||||
|
||||
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
|
||||
|
||||
@@ -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)
|
||||
|
||||
47
engine/tests/ui_test.c
Normal file
47
engine/tests/ui_test.c
Normal file
@@ -0,0 +1,47 @@
|
||||
#define BZ_ENTRYPOINT
|
||||
#include <breeze.h>
|
||||
|
||||
#include <raylib.h>
|
||||
#include <rlImGui.h>
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user