From 4152fbecb6ee8360575aa4c24e9cedf822f159dc Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Wed, 25 Mar 2026 19:59:14 -0700 Subject: Implement vertical and horizontal layouts. Use widget position properly when rendering. Toolbar, buttons and edit bars WIP --- CMakeLists.txt | 1 + include/ui.h | 81 ++++++++++++++++------ src/constants.h | 14 +++- src/layout.c | 192 ++++++++++++++++++++++++++++++++++++++++++++++------ src/render.c | 61 ++++++++++++----- src/ui.c | 1 + src/widget/button.c | 9 ++- src/widget/frame.c | 9 ++- src/widget/label.c | 13 ++-- src/widget/layout.c | 25 +++++++ src/widget/table.c | 13 ++-- src/widget/widget.c | 109 ++++++++++++++++++++++++++++- src/widget/widget.h | 14 +++- 13 files changed, 460 insertions(+), 82 deletions(-) create mode 100644 src/widget/layout.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 043bb2d..96de9a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ add_library(ui src/widget/button.c src/widget/frame.c src/widget/label.c + src/widget/layout.c src/widget/scrollbar.c src/widget/table.c src/widget/widget.c diff --git a/include/ui.h b/include/ui.h index baaa550..b70df59 100644 --- a/include/ui.h +++ b/include/ui.h @@ -42,15 +42,19 @@ typedef int uiWidgetId; /// Widget type. typedef enum uiWidgetType { uiTypeButton, + uiTypeEdit, uiTypeFrame, uiTypeLabel, + uiTypeLayout, uiTypeTable, uiTypeMax, } uiWidgetType; typedef struct uiButton uiButton; +typedef struct uiEdit uiEdit; typedef struct uiFrame uiFrame; typedef struct uiLabel uiLabel; +typedef struct uiLayout uiLayout; typedef struct uiTable uiTable; typedef struct uiWidget uiWidget; @@ -59,13 +63,33 @@ typedef struct uiPtr { uiWidgetType type; union { uiButton* button; + uiEdit* edit; uiFrame* frame; uiLabel* label; + uiLayout* layout; uiTable* table; uiWidget* widget; }; } uiPtr; +/// Direction in which a layout widget lays out its children. +typedef enum uiLayoutDirection { + uiVertical, + uiHorizontal, +} uiLayoutDirection; + +/// Directions in which a widget stretches. +/// +/// Stretch determines how the widget occupies the area of its parent widget. +/// +/// uiStretchNone - the widget has a fixed size. +/// uiStretchX/Y - the widget stretches in the X/Y direction. +typedef enum uiStretch { + uiStretchNone = 0, + uiStretchX = 1, + uiStretchY = 2, +} uiStretch; + /// Mouse button. typedef enum uiMouseButton { uiLMB, @@ -145,6 +169,11 @@ typedef struct uiWidgetEvent { }; } uiWidgetEvent; +/// Common construction parameters for widgets. +typedef struct uiParams { + uiStretch stretch; +} uiParams; + // ----------------------------------------------------------------------------- // Library. @@ -152,63 +181,76 @@ bool uiInit(void); void uiShutdown(void); // ----------------------------------------------------------------------------- -// Widget pointers. +// Widget. uiPtr uiMakeButtonPtr(uiButton*); +uiPtr uiMakeEditPtr(uiEdit*); uiPtr uiMakeFramePtr(uiFrame*); uiPtr uiMakeLabelPtr(uiLabel*); +uiPtr uiMakeLayoutPtr(uiLayout*); uiPtr uiMakeTablePtr(uiTable*); uiPtr uiMakeWidgetPtr(uiWidget*); uiPtr uiNullptr(void); -bool uiIsNullptr(uiPtr ptr); +bool uiIsNullptr(uiPtr); -uiButton* uiGetButtonPtr(uiPtr ptr); -uiFrame* uiGetFramePtr(uiPtr ptr); -uiLabel* uiGetLabelPtr(uiPtr ptr); -uiTable* uiGetTablePtr(uiPtr ptr); - -// ----------------------------------------------------------------------------- -// Widget. +uiButton* uiGetButtonPtr(uiPtr); +uiEdit* uiGetEditPtr(uiPtr); +uiFrame* uiGetFramePtr(uiPtr); +uiLabel* uiGetLabelPtr(uiPtr); +uiLayout* uiGetLayoutPtr(uiPtr); +uiTable* uiGetTablePtr(uiPtr); uiWidgetType uiWidgetGetType(const uiWidget*); -void uiWidgetSetParent(uiPtr child, uiPtr parent); - -// ----------------------------------------------------------------------------- -// Button. -uiButton* uiMakeButton(const char* text); +void uiPrint(uiPtr); // ----------------------------------------------------------------------------- // Frame. uiFrame* uiMakeFrame(void); void uiDestroyFrame(uiFrame**); -void uiResizeFrame(uiFrame*, int width, int height); uiSize uiGetFrameSize(const uiFrame*); +// ----------------------------------------------------------------------------- +// Layout. +uiLayout* uiMakeLayout(uiPtr parent, uiLayoutDirection); + +// ----------------------------------------------------------------------------- +// Button. + +uiButton* uiMakeButton(uiPtr parent, const char* text, const uiParams*); + // ----------------------------------------------------------------------------- // Label. -uiLabel* uiMakeLabel(const char* text); +uiLabel* uiMakeLabel(uiPtr parent, const char* text); const char* uiLabelGetText(const uiLabel*); // ----------------------------------------------------------------------------- // Table. -uiTable* uiMakeTable(int rows, int cols, const char** header); +uiTable* uiMakeTable(uiPtr parent, int rows, int cols, const char** header); void uiTableClear(uiTable*); void uiTableAddRow(uiTable*, const char** row); void uiTableSet(uiTable*, int row, int col, const char* text); const char* uiTableGet(const uiTable*, int row, int col); void uiTableScroll(uiTable*, int row); +// ----------------------------------------------------------------------------- +// Layout. + +/// Lay out the widgets in the frame given the frame's new width and height. +/// +/// This should typically be called whenever the window is resized. +void uiLayOut(uiFrame*, int width, int height); + // ----------------------------------------------------------------------------- // Rendering. void uiRender(const uiFrame*, uiSurface*); // ----------------------------------------------------------------------------- -// UI Events. +// UI and user events. /// Get the widget events. /// Return the number of events in the returned array. @@ -218,9 +260,6 @@ void uiRender(const uiFrame*, uiSurface*); /// therefore report zero widget events. int uiGetEvents(uiWidgetEvent const**); -// ----------------------------------------------------------------------------- -// User input. - /// Send an input event to the UI. /// Return true if the UI requires a redraw. bool uiSendEvent(uiFrame*, const uiInputEvent*); diff --git a/src/constants.h b/src/constants.h index 47babab..408e3d5 100644 --- a/src/constants.h +++ b/src/constants.h @@ -1,7 +1,17 @@ +/* Constants used throughout the library. + * + * All sizes are relative to the font size. Widths are relative to the font + * width; heights are relative to the font height. Other sizes are typically + * relative to font height. + */ #pragma once -// Maximum number of events that can be stored in a single input loop. +/// Maximum number of events that can be stored in a single input loop. #define MaxWidgetEvents 8 -// Width of scroll bars in pixels. +// TODO: Make this relative to the font width. +/// Width of scroll bars in pixels. #define ScrollbarWidth 32 + +/// Button border size relative to font height. +#define ButtonBorderSize 0.1 diff --git a/src/layout.c b/src/layout.c index 5261eb3..3532928 100644 --- a/src/layout.c +++ b/src/layout.c @@ -5,16 +5,59 @@ #include -#define Min(a, b) ((a) < (b) ? (a) : (b)) +static void LayoutWidget(uiWidget* widget, int width, int height); -static void ResizeTable(uiTable* table, int width, int height) { +/// Return the area required to fit the text. +static uiSize GetTextSize(const string* text) { + return (uiSize){ + .width = (int)(g_ui.font->header.glyph_width * string_length(*text)), + .height = (int)g_ui.font->header.glyph_height}; +} + +static void ResizeButton(uiButton* button, int width, int height) { + assert(button); + + // TODO: Define the button's border. But don't store this. Make it a function + // shared between layout.c and render.c. Define it in a new common.h? + + const uiSize minSize = GetTextSize(&button->text); + uiSize size = minSize; + if (button->widget.stretch & uiStretchX) { + size.width = Max(size.width, width); + } + if (button->widget.stretch & uiStretchY) { + size.height = Max(size.height, height); + } + button->widget.rect.width = size.width; + button->widget.rect.height = size.height; +} + +static void ResizeLabel(uiLabel* label, int width, int height) { + assert(label); + + const uiSize minSize = GetTextSize(&label->text); + uiSize size = minSize; + if (label->widget.stretch & uiStretchX) { + size.width = Max(size.width, width); + } + if (label->widget.stretch & uiStretchY) { + size.height = Max(size.height, height); + } + label->widget.rect.width = size.width; + label->widget.rect.height = size.height; +} + +static void LayoutTable(uiTable* table, int width, int height) { assert(table); - if (table->cols == 0) { + if ((table->cols == 0) || (table->rows == 0)) { + table->widget.rect.width = 0; + table->widget.rect.height = 0; return; } - table->height = height; + table->widget.rect.width = width; + table->widget.rect.height = height; // Compute the number of rows that are visible at once. table->num_visible_rows = @@ -121,11 +164,10 @@ static void ResizeTable(uiTable* table, int width, int height) { } // Set scrollbar layout. - scrollbar->width = ScrollbarWidth; - scrollbar->height = table->height; - scrollbar->handle_height = - (int)((double)table->num_visible_rows / (double)table->rows * - (double)table->height); + scrollbar->width = ScrollbarWidth; + scrollbar->height = height; + scrollbar->handle_height = (int)((double)table->num_visible_rows / + (double)table->rows * (double)height); uiTableScroll(table, table->offset); } else { // Scroll bar not visible. scrollbar->width = 0; @@ -135,31 +177,139 @@ static void ResizeTable(uiTable* table, int width, int height) { } } -static void ResizeWidget(uiWidget* widget, int width, int height) { - assert(widget); +static void Layout(uiLayout* layout, int width, int height) { + assert(layout); - widget->rect.width = width; - widget->rect.height = height; + layout->widget.rect.width = width; + layout->widget.rect.height = height; + + // Resizing a layout can get complicated depending on how much flexibility we + // want to support. To start simple: + // 1. Let the layout stretch to occupy the given size. + // 2. For each child, check whether the child has a fixed width/height or + // if it wants to grow. + // 3. Fixed-size widgets get their requested size. + // 4. Variably-sized widgets get the remainder of the space uniformly + // distributed among them. + + // First resize fixed-size widgets and compute free area, if any, to determine + // the size of stretchable widgets along the layout direction. Then resize + // stretchable widgets by uniformly distributing the free area. + switch (layout->direction) { + case uiVertical: { + // Resize fixed-size children and compute free area. + int free_area = height; + int stretchable_count = 0; // Number of stretchable widgets. + list_foreach(layout->widget.children, child, { + if (child->stretch & uiStretchY) { + stretchable_count++; + } else { + LayoutWidget(child, width, free_area); + free_area -= child->rect.height; + } + }); + if (stretchable_count > 0) { + // Resize stretchable children. + const int stretchable_widget_size = free_area / stretchable_count; + list_foreach(layout->widget.children, child, { + if (child->stretch != uiStretchNone) { + LayoutWidget(child, width, stretchable_widget_size); + } else { + LayoutWidget(child, width, height); + } + }); + } + // Now position all widgets inside the layout. + int y = 0; + list_foreach(layout->widget.children, child, { + child->rect.y = y; + y += child->rect.height; + }); + // Layout's width is max of its children. + layout->widget.rect.width = 0; + list_foreach(layout->widget.children, child, { + layout->widget.rect.width = + Max(layout->widget.rect.width, child->rect.width); + }); + break; + } + case uiHorizontal: { + // Resize fixed-size children and compute free area. + int free_area = width; + int stretchable_count = 0; // Number of stretchable widgets. + list_foreach(layout->widget.children, child, { + if (child->stretch & uiStretchX) { + stretchable_count++; + } else { + LayoutWidget(child, free_area, height); + free_area -= child->rect.width; + } + }); + if (stretchable_count > 0) { + // Resize stretchable children. + const int stretchable_size = free_area / stretchable_count; + list_foreach(layout->widget.children, child, { + if (child->stretch != uiStretchNone) { + LayoutWidget(child, stretchable_size, height); + } + }); + } + // Now position all widgets inside the layout. + int x = 0; + list_foreach(layout->widget.children, child, { + child->rect.x = x; + x += child->rect.width; + }); + // Layout's height is max of its children. + layout->widget.rect.height = 0; + list_foreach(layout->widget.children, child, { + layout->widget.rect.height = + Max(layout->widget.rect.height, child->rect.height); + }); + break; + } + } +} + +static void ResizeFrame(uiFrame* frame, int width, int height) { + assert(frame); + + frame->widget.rect.width = width; + frame->widget.rect.height = height; + + list_foreach_mut( + frame->widget.children, child, { LayoutWidget(child, width, height); }); +} + +void uiLayOut(uiFrame* frame, int width, int height) { + assert(frame); + LayoutWidget(&frame->widget, width, height); +} + +static void LayoutWidget(uiWidget* widget, int width, int height) { + assert(widget); switch (widget->type) { + case uiTypeLayout: + Layout((uiLayout*)widget, width, height); + break; case uiTypeButton: + ResizeButton((uiButton*)widget, width, height); break; case uiTypeFrame: - list_foreach_mut( - widget->children, child, { ResizeWidget(child, width, height); }); + ResizeFrame((uiFrame*)widget, width, height); break; case uiTypeLabel: + ResizeLabel((uiLabel*)widget, width, height); + break; + case uiTypeEdit: + // TODO: ResizeEdit() break; case uiTypeTable: - ResizeTable((uiTable*)widget, width, height); + LayoutTable((uiTable*)widget, width, height); break; case uiTypeMax: TRAP(); break; } } - -void uiResizeFrame(uiFrame* frame, int width, int height) { - assert(frame); - ResizeWidget(&frame->widget, width, height); -} diff --git a/src/render.c b/src/render.c index 51112a9..2fcade6 100644 --- a/src/render.c +++ b/src/render.c @@ -19,9 +19,9 @@ static const uiPixel uiPink = {128, 0, 128, 255}; /// We store a subsurface separate from the surface so that we can always check /// whether a given coordinate is within the bounds of the physical surface. typedef struct RenderState { - uiSurface surface; /// Surface of pixels on which the UI is rendered. - uiRect subsurface; /// Subregion where the current UI widget is rendered. - uiPoint pen; /// Current pen position relative to subsurface. + uiSurface surface; ///< Surface of pixels on which the UI is rendered. + uiRect subsurface; ///< Subregion where the current UI widget is rendered. + uiPoint pen; ///< Current pen position relative to subsurface. } RenderState; static void RenderWidget(RenderState* state, const uiWidget* widget); @@ -165,6 +165,13 @@ static void RenderFrame(const uiFrame* frame, RenderState* state) { FillRect(&frame->widget.rect, uiBlack, state); } +/// Render a button. +static void RenderButton(const uiButton* button, RenderState* state) { + assert(button); + assert(state); + RenderText(string_data(button->text), string_length(button->text), state); +} + /// Render a label. static void RenderLabel(const uiLabel* label, RenderState* state) { assert(label); @@ -253,14 +260,43 @@ static void RenderTable(const uiTable* table, RenderState* state) { } } +void uiRender(const uiFrame* frame, uiSurface* surface) { + assert(frame); + assert(surface); + + RenderWidget( + &(RenderState){ + .surface = *surface, + .subsurface = (uiRect){.x = 0, + .y = 0, + .width = surface->width, + .height = surface->height}, + .pen = {.x = 0, .y = 0}, + }, + (const uiWidget*)frame); +} + /// Render a widget. static void RenderWidget(RenderState* state, const uiWidget* widget) { assert(state); assert(widget); + // A widget's position is relative to its parent's position. + // The pen currently points at the parent. Move it to this widget's position + // before rendering it, then render the widget's children using this new + // position. + // The pen's original position must be restored before returning, so save a + // copy here. + const uiPoint pen = state->pen; + state->pen = + (uiPoint){state->pen.x + widget->rect.x, state->pen.y + widget->rect.y}; + // Render this widget. switch (widget->type) { + case uiTypeLayout: + break; case uiTypeButton: + RenderButton((const uiButton*)widget, state); break; case uiTypeFrame: RenderFrame((const uiFrame*)widget, state); @@ -268,6 +304,8 @@ static void RenderWidget(RenderState* state, const uiWidget* widget) { case uiTypeLabel: RenderLabel((const uiLabel*)widget, state); break; + case uiTypeEdit: + break; case uiTypeTable: RenderTable((const uiTable*)widget, state); break; @@ -278,20 +316,7 @@ static void RenderWidget(RenderState* state, const uiWidget* widget) { // Render children. list_foreach(widget->children, child, { RenderWidget(state, child); }); -} -void uiRender(const uiFrame* frame, uiSurface* surface) { - assert(frame); - assert(surface); - - RenderWidget( - &(RenderState){ - .surface = *surface, - .subsurface = (uiRect){.x = 0, - .y = 0, - .width = surface->width, - .height = surface->height}, - .pen = {.x = 0, .y = 0}, - }, - (const uiWidget*)frame); + // Restore the pen. + state->pen = pen; } diff --git a/src/ui.c b/src/ui.c index 4d09584..e281f2f 100644 --- a/src/ui.c +++ b/src/ui.c @@ -8,6 +8,7 @@ bool uiInit(void) { // TODO: Embed the font into the library instead. + // TODO: Better error reporting. const char* font_path = "../ui/fontbaker/NK57.bin"; if (!(g_ui.font = LoadFontAtlas(font_path))) { return false; diff --git a/src/widget/button.c b/src/widget/button.c index f2313fd..d8de266 100644 --- a/src/widget/button.c +++ b/src/widget/button.c @@ -2,18 +2,21 @@ #include "widget.h" -uiButton* uiMakeButton(const char* text) { +uiButton* uiMakeButton(uiPtr parent, const char* text, const uiParams* params) { assert(text); + assert(params); uiButton* button = UI_NEW(uiButton); *button = (uiButton){ .widget = (uiWidget){ - .type = uiTypeButton, - .rect = {0}, + .type = uiTypeButton, + .rect = {0}, + .stretch = params->stretch, }, .text = string_new(text), }; + WidgetSetParent(uiMakeButtonPtr(button), parent); return button; } diff --git a/src/widget/frame.c b/src/widget/frame.c index e1078be..7640e42 100644 --- a/src/widget/frame.c +++ b/src/widget/frame.c @@ -3,8 +3,13 @@ #include "widget.h" uiFrame* uiMakeFrame(void) { - uiFrame* frame = UI_NEW(uiFrame); - frame->widget.type = uiTypeFrame; + uiFrame* frame = UI_NEW(uiFrame); + + *frame = (uiFrame){ + .widget = + (uiWidget){.type = uiTypeFrame, .stretch = uiStretchX | uiStretchY} + }; + return frame; } diff --git a/src/widget/label.c b/src/widget/label.c index 30ca0ec..5c0c00a 100644 --- a/src/widget/label.c +++ b/src/widget/label.c @@ -3,22 +3,21 @@ #include "uiLibrary.h" #include "widget.h" -uiLabel* uiMakeLabel(const char* text) { +uiLabel* uiMakeLabel(uiPtr parent, const char* text) { assert(text); uiLabel* label = UI_NEW(uiLabel); *label = (uiLabel){ .widget = - (uiWidget){ - .type = uiTypeLabel, + (uiWidget){.type = uiTypeLabel, .rect = - (uiRect){ - .width = - (int)strlen(text) * g_ui.font->header.glyph_width, - .height = g_ui.font->header.glyph_height}}, + (uiRect){.width = (int)strlen(text) * + g_ui.font->header.glyph_width, + .height = g_ui.font->header.glyph_height}}, .text = string_new(text), }; + WidgetSetParent(uiMakeLabelPtr(label), parent); return label; } diff --git a/src/widget/layout.c b/src/widget/layout.c new file mode 100644 index 0000000..c529c56 --- /dev/null +++ b/src/widget/layout.c @@ -0,0 +1,25 @@ +#include "widget.h" + +static uiStretch StretchFromDirection(uiLayoutDirection direction) { + switch (direction) { + case uiHorizontal: + return uiStretchX; + case uiVertical: + return uiStretchY; + } + assert(false); + return uiStretchNone; +} + +uiLayout* uiMakeLayout(uiPtr parent, uiLayoutDirection direction) { + uiLayout* layout = UI_NEW(uiLayout); + + *layout = (uiLayout){ + .widget = (uiWidget){.type = uiTypeLayout, + .stretch = StretchFromDirection(direction)}, + .direction = direction, + }; + WidgetSetParent(uiMakeLayoutPtr(layout), parent); + + return layout; +} diff --git a/src/widget/table.c b/src/widget/table.c index e7d412e..d9a6440 100644 --- a/src/widget/table.c +++ b/src/widget/table.c @@ -3,11 +3,12 @@ #define Min(a, b) ((a) < (b) ? (a) : (b)) #define Max(a, b) ((a) > (b) ? (a) : (b)) -uiTable* uiMakeTable(int rows, int cols, const char** header) { +uiTable* uiMakeTable(uiPtr parent, int rows, int cols, const char** header) { uiTable* table = UI_NEW(uiTable); *table = (uiTable){ - .widget = (uiWidget){.type = uiTypeTable}, + .widget = + (uiWidget){.type = uiTypeTable, .stretch = (uiStretchX | uiStretchY)}, .rows = rows, .cols = cols, .widths = (cols > 0) ? calloc(cols, sizeof(int)) : 0, @@ -15,6 +16,7 @@ uiTable* uiMakeTable(int rows, int cols, const char** header) { .cells = (rows * cols > 0) ? calloc(rows, sizeof(uiCell*)) : 0, .flags = {0}, }; + WidgetSetParent(uiMakeTablePtr(table), parent); if (header) { for (int col = 0; col < cols; ++col) { @@ -91,13 +93,14 @@ void SyncScrollbarToTable(uiTable* table) { assert(table); ScrollbarScroll( &table->scrollbar, (int)((double)table->offset / (double)table->rows * - (double)table->height)); + (double)table->widget.rect.height)); } void SyncTableToScrollbar(uiTable* table) { assert(table); - table->offset = (int)((double)table->scrollbar.handle_y / - (double)table->height * (double)table->rows); + table->offset = + (int)((double)table->scrollbar.handle_y / + (double)table->widget.rect.height * (double)table->rows); } const uiCell* TableGetCell(const uiTable* table, int row, int col) { diff --git a/src/widget/widget.c b/src/widget/widget.c index ebcaf10..2c525cc 100644 --- a/src/widget/widget.c +++ b/src/widget/widget.c @@ -2,6 +2,8 @@ #include +#include + // ----------------------------------------------------------------------------- // Widget. @@ -30,14 +32,19 @@ void DestroyWidget(uiWidget** ppWidget) { UI_DEL(ppWidget); } -void uiWidgetSetParent(uiPtr child_, uiPtr parent_) { +void WidgetSetParent(uiPtr child_, uiPtr parent_) { uiWidget* child = child_.widget; uiWidget* parent = parent_.widget; assert(child); assert(parent); - list_add(parent->children, child); + if (!uiIsNullptr(child->parent)) { + list_remove(child->parent.widget->children, child); + } + + list_push(parent->children, child); + child->parent = parent_; } // ----------------------------------------------------------------------------- @@ -58,6 +65,11 @@ uiPtr uiMakeLabelPtr(uiLabel* label) { return (uiPtr){.type = uiTypeLabel, .label = label}; } +uiPtr uiMakeLayoutPtr(uiLayout* layout) { + assert(layout); + return (uiPtr){.type = uiTypeLayout, .layout = layout}; +} + uiPtr uiMakeTablePtr(uiTable* table) { assert(table); return (uiPtr){.type = uiTypeTable, .table = table}; @@ -72,6 +84,8 @@ uiPtr uiMakeWidgetPtr(uiWidget* widget) { return uiMakeFramePtr((uiFrame*)widget); case uiTypeLabel: return uiMakeLabelPtr((uiLabel*)widget); + case uiTypeLayout: + return uiMakeLayoutPtr((uiLayout*)widget); case uiTypeTable: return uiMakeTablePtr((uiTable*)widget); default: @@ -103,8 +117,99 @@ uiLabel* uiGetLabelPtr(uiPtr ptr) { return ptr.label; } +uiLayout* uiGetLayoutPtr(uiPtr ptr) { + assert(ptr.type == uiTypeLayout); + assert(ptr.layout); + return ptr.layout; +} + +uiEdit* uiGetEditPtr(uiPtr ptr) { + assert(ptr.type == uiTypeEdit); + assert(ptr.edit); + return ptr.edit; +} + uiTable* uiGetTablePtr(uiPtr ptr) { assert(ptr.type == uiTypeTable); assert(ptr.table); return ptr.table; } + +typedef struct PrintState { + mstring pad; + mstring rect; +} PrintState; + +static void RectToString(uiRect rect, mstring* out) { + assert(out); + out->length = snprintf( + out->str, sizeof(out->str), "rect{(x:%d, y:%d), (w:%d, h:%d)", rect.x, + rect.y, rect.width, rect.height); +} + +static void uiPrintRec(uiPtr ptr, PrintState* state) { + if (uiIsNullptr(ptr)) { + return; + } + RectToString(ptr.widget->rect, &state->rect); + switch (ptr.type) { + case uiTypeButton: { + const uiButton* button = uiGetButtonPtr(ptr); + printf( + "%sbutton{rect=%s, text=\"%s\"}\n", mstring_cstr(&state->pad), + mstring_cstr(&state->rect), string_cstr(&button->text)); + break; + } + case uiTypeLabel: { + const uiLabel* label = uiGetLabelPtr(ptr); + printf( + "%sbutton{rect=%s, text=\"%s\"}\n", mstring_cstr(&state->pad), + mstring_cstr(&state->rect), string_cstr(&label->text)); + break; + } + case uiTypeLayout: { + const uiLayout* layout = uiGetLayoutPtr(ptr); + const char* direction = ""; + switch (layout->direction) { + case uiHorizontal: + direction = "horizontal"; + break; + case uiVertical: + direction = "vertical"; + break; + } + printf( + "%s%s_layout{rect=%s}\n", mstring_cstr(&state->pad), direction, + mstring_cstr(&state->rect)); + break; + } + case uiTypeFrame: { + printf( + "%sframe{rect=%s}\n", mstring_cstr(&state->pad), + mstring_cstr(&state->rect)); + break; + } + case uiTypeTable: { + const uiTable* table = uiGetTablePtr(ptr); + printf( + "%stable{rect=%s}\n", mstring_cstr(&state->pad), + mstring_cstr(&state->rect)); + break; + } + default: + printf("%swidget\n", mstring_cstr(&state->pad)); + break; + } + const mstring pad = state->pad; + state->pad = mstring_concat(state->pad, mstring_make(" ")); + list_foreach(ptr.widget->children, child, { + uiPrintRec(uiMakeWidgetPtr(child), state); + }); + state->pad = pad; +} + +void uiPrint(uiPtr ptr) { + PrintState state = + (PrintState){.pad = mstring_make_empty(), .rect = mstring_make_empty()}; + uiPrintRec(ptr, &state); +} diff --git a/src/widget/widget.h b/src/widget/widget.h index db11164..7482d38 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -13,9 +13,16 @@ DEF_LIST(Widget, uiWidget*) typedef struct uiWidget { uiWidgetType type; uiRect rect; + uiStretch stretch; + uiPtr parent; Widget_list children; } uiWidget; +typedef struct uiLayout { + uiWidget widget; + uiLayoutDirection direction; +} uiLayout; + typedef struct uiButton { uiWidget widget; string text; @@ -30,6 +37,11 @@ typedef struct uiLabel { string text; } uiLabel; +typedef struct uiEdit { + uiWidget widget; + string text; +} uiEdit; + typedef struct uiScrollbar { int width; int height; // Total height: handle plus scrollable area. @@ -45,7 +57,6 @@ typedef struct uiTable { uiWidget widget; int rows; int cols; - int height; // Height in pixels. int* widths; // Width, in pixels, for each column. uiCell* header; // If non-null, row of 'cols' header cells. uiCell** cells; // Array of 'rows' rows, each of 'cols' cells. @@ -57,6 +68,7 @@ typedef struct uiTable { } flags; } uiTable; +void WidgetSetParent(uiPtr child, uiPtr parent); void DestroyWidget(uiWidget** ppWidget); /// Set the scrollbar handle's y-coordinate, which is clipped to the scrollbar's -- cgit v1.2.3