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 --- src/layout.c | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 171 insertions(+), 21 deletions(-) (limited to 'src/layout.c') 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); -} -- cgit v1.2.3