#include #include "uiLibrary.h" #include "widget/widget.h" #include static void LayoutWidget(uiWidget* widget, 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) || (table->rows == 0)) { table->widget.rect.width = 0; table->widget.rect.height = 0; return; } table->widget.rect.width = width; table->widget.rect.height = height; // Compute the number of rows that are visible at once. table->num_visible_rows = Min(table->rows, height / g_ui.font->header.glyph_height); assert(table->num_visible_rows <= table->rows); // Determine if there is vertical overflow. This determines whether we need to // render a scroll bar, in which case room must be made for it. table->flags.vertical_overflow = (table->rows * g_ui.font->header.glyph_height) > table->widget.rect.height; // Surface width: W. // Columns: N // // First, find the minimum width of each column based on their contents. // // If the sum of column widths < N, then distribute the extra space first // among the smallest columns and building up towards the larger. // // If the sum of column widths > N, subtract from the largest column first and // move towards the smaller ones to distribute the space as evenly as // possible. // Find the minimum width for each column. int* widths = table->widths; // Header. for (int col = 0; col < table->cols; ++col) { const uiCell* cell = &table->header[col]; const int length = (int)string_length(cell->text); widths[col] = length; } // Table contents. for (int row = 0; row < table->rows; ++row) { for (int col = 0; col < table->cols; ++col) { const uiCell* cell = TableGetCell(table, row, col); const int length = (int)string_length(cell->text); widths[col] = length > widths[col] ? length : widths[col]; } } // Multiply string lengths times glyph width to compute pixel size. for (int col = 0; col < table->cols; ++col) { widths[col] *= g_ui.font->header.glyph_width; } // Find the sum of widths. int used_width = 0; for (int col = 0; col < table->cols; ++col) { used_width += widths[col]; } // Pad if available width is larger than sum of widths. if (used_width < width) { // Divide evenly among columns. // const int extra = width - used_width; // const int pad = extra / table->cols; // const int mod = extra % table->cols; // for (int col = 0; col < table->cols; ++col) { // table->widths[col] += pad + (col < mod ? 1 : 0); // } int extra = width - used_width; while (extra > 0) { // Find smallest column. int smallest = 0; for (int col = 1; col < table->cols; ++col) { if (widths[col] < widths[smallest]) { smallest = col; } } // Pad it and subtract from the budget. widths[smallest] += 1; extra--; } } // Shrink if available width is smaller than the sum of widths. else if (used_width > width) { int deficit = used_width - width; while (deficit > 0) { // Find largest column. int largest = 0; for (int col = 1; col < table->cols; ++col) { if (widths[col] > widths[largest]) { largest = col; } } // Shrink it and subtract from the deficit. widths[largest] -= 1; deficit--; } } // Now make room for the scroll bar, if necessary. uiScrollbar* scrollbar = &table->scrollbar; if (table->flags.vertical_overflow) { // Subtract room from table columns. const int offset = ScrollbarWidth / table->cols; const int remainder = ScrollbarWidth % table->cols; for (int col = 0; col < table->cols; ++col) { table->widths[col] -= offset + (col < remainder ? 1 : 0); assert(table->widths[col] >= 0); } // Set scrollbar layout. 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; scrollbar->height = 0; scrollbar->handle_height = 0; scrollbar->handle_y = 0; } } static void Layout(uiLayout* layout, int width, int height) { assert(layout); 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: ResizeFrame((uiFrame*)widget, width, height); break; case uiTypeLabel: ResizeLabel((uiLabel*)widget, width, height); break; case uiTypeEdit: // TODO: ResizeEdit() break; case uiTypeTable: LayoutTable((uiTable*)widget, width, height); break; case uiTypeMax: TRAP(); break; } }