diff options
| author | 3gg <3gg@shellblade.net> | 2026-03-25 19:59:14 -0700 |
|---|---|---|
| committer | 3gg <3gg@shellblade.net> | 2026-03-25 19:59:14 -0700 |
| commit | 4152fbecb6ee8360575aa4c24e9cedf822f159dc (patch) | |
| tree | 9e9b9db0216a37c5867d472a65289502c459691f /src/layout.c | |
| parent | 7778755c20e779554cd654ecdf7404d37b723fcc (diff) | |
Implement vertical and horizontal layouts. Use widget position properly when rendering. Toolbar, buttons and edit bars WIPmain
Diffstat (limited to 'src/layout.c')
| -rw-r--r-- | src/layout.c | 192 |
1 files changed, 171 insertions, 21 deletions
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 @@ | |||
| 5 | 5 | ||
| 6 | #include <cassert.h> | 6 | #include <cassert.h> |
| 7 | 7 | ||
| 8 | #define Min(a, b) ((a) < (b) ? (a) : (b)) | 8 | static void LayoutWidget(uiWidget* widget, int width, int height); |
| 9 | 9 | ||
| 10 | static void ResizeTable(uiTable* table, int width, int height) { | 10 | /// Return the area required to fit the text. |
| 11 | static uiSize GetTextSize(const string* text) { | ||
| 12 | return (uiSize){ | ||
| 13 | .width = (int)(g_ui.font->header.glyph_width * string_length(*text)), | ||
| 14 | .height = (int)g_ui.font->header.glyph_height}; | ||
| 15 | } | ||
| 16 | |||
| 17 | static void ResizeButton(uiButton* button, int width, int height) { | ||
| 18 | assert(button); | ||
| 19 | |||
| 20 | // TODO: Define the button's border. But don't store this. Make it a function | ||
| 21 | // shared between layout.c and render.c. Define it in a new common.h? | ||
| 22 | |||
| 23 | const uiSize minSize = GetTextSize(&button->text); | ||
| 24 | uiSize size = minSize; | ||
| 25 | if (button->widget.stretch & uiStretchX) { | ||
| 26 | size.width = Max(size.width, width); | ||
| 27 | } | ||
| 28 | if (button->widget.stretch & uiStretchY) { | ||
| 29 | size.height = Max(size.height, height); | ||
| 30 | } | ||
| 31 | button->widget.rect.width = size.width; | ||
| 32 | button->widget.rect.height = size.height; | ||
| 33 | } | ||
| 34 | |||
| 35 | static void ResizeLabel(uiLabel* label, int width, int height) { | ||
| 36 | assert(label); | ||
| 37 | |||
| 38 | const uiSize minSize = GetTextSize(&label->text); | ||
| 39 | uiSize size = minSize; | ||
| 40 | if (label->widget.stretch & uiStretchX) { | ||
| 41 | size.width = Max(size.width, width); | ||
| 42 | } | ||
| 43 | if (label->widget.stretch & uiStretchY) { | ||
| 44 | size.height = Max(size.height, height); | ||
| 45 | } | ||
| 46 | label->widget.rect.width = size.width; | ||
| 47 | label->widget.rect.height = size.height; | ||
| 48 | } | ||
| 49 | |||
| 50 | static void LayoutTable(uiTable* table, int width, int height) { | ||
| 11 | assert(table); | 51 | assert(table); |
| 12 | 52 | ||
| 13 | if (table->cols == 0) { | 53 | if ((table->cols == 0) || (table->rows == 0)) { |
| 54 | table->widget.rect.width = 0; | ||
| 55 | table->widget.rect.height = 0; | ||
| 14 | return; | 56 | return; |
| 15 | } | 57 | } |
| 16 | 58 | ||
| 17 | table->height = height; | 59 | table->widget.rect.width = width; |
| 60 | table->widget.rect.height = height; | ||
| 18 | 61 | ||
| 19 | // Compute the number of rows that are visible at once. | 62 | // Compute the number of rows that are visible at once. |
| 20 | table->num_visible_rows = | 63 | table->num_visible_rows = |
| @@ -121,11 +164,10 @@ static void ResizeTable(uiTable* table, int width, int height) { | |||
| 121 | } | 164 | } |
| 122 | 165 | ||
| 123 | // Set scrollbar layout. | 166 | // Set scrollbar layout. |
| 124 | scrollbar->width = ScrollbarWidth; | 167 | scrollbar->width = ScrollbarWidth; |
| 125 | scrollbar->height = table->height; | 168 | scrollbar->height = height; |
| 126 | scrollbar->handle_height = | 169 | scrollbar->handle_height = (int)((double)table->num_visible_rows / |
| 127 | (int)((double)table->num_visible_rows / (double)table->rows * | 170 | (double)table->rows * (double)height); |
| 128 | (double)table->height); | ||
| 129 | uiTableScroll(table, table->offset); | 171 | uiTableScroll(table, table->offset); |
| 130 | } else { // Scroll bar not visible. | 172 | } else { // Scroll bar not visible. |
| 131 | scrollbar->width = 0; | 173 | scrollbar->width = 0; |
| @@ -135,31 +177,139 @@ static void ResizeTable(uiTable* table, int width, int height) { | |||
| 135 | } | 177 | } |
| 136 | } | 178 | } |
| 137 | 179 | ||
| 138 | static void ResizeWidget(uiWidget* widget, int width, int height) { | 180 | static void Layout(uiLayout* layout, int width, int height) { |
| 139 | assert(widget); | 181 | assert(layout); |
| 140 | 182 | ||
| 141 | widget->rect.width = width; | 183 | layout->widget.rect.width = width; |
| 142 | widget->rect.height = height; | 184 | layout->widget.rect.height = height; |
| 185 | |||
| 186 | // Resizing a layout can get complicated depending on how much flexibility we | ||
| 187 | // want to support. To start simple: | ||
| 188 | // 1. Let the layout stretch to occupy the given size. | ||
| 189 | // 2. For each child, check whether the child has a fixed width/height or | ||
| 190 | // if it wants to grow. | ||
| 191 | // 3. Fixed-size widgets get their requested size. | ||
| 192 | // 4. Variably-sized widgets get the remainder of the space uniformly | ||
| 193 | // distributed among them. | ||
| 194 | |||
| 195 | // First resize fixed-size widgets and compute free area, if any, to determine | ||
| 196 | // the size of stretchable widgets along the layout direction. Then resize | ||
| 197 | // stretchable widgets by uniformly distributing the free area. | ||
| 198 | switch (layout->direction) { | ||
| 199 | case uiVertical: { | ||
| 200 | // Resize fixed-size children and compute free area. | ||
| 201 | int free_area = height; | ||
| 202 | int stretchable_count = 0; // Number of stretchable widgets. | ||
| 203 | list_foreach(layout->widget.children, child, { | ||
| 204 | if (child->stretch & uiStretchY) { | ||
| 205 | stretchable_count++; | ||
| 206 | } else { | ||
| 207 | LayoutWidget(child, width, free_area); | ||
| 208 | free_area -= child->rect.height; | ||
| 209 | } | ||
| 210 | }); | ||
| 211 | if (stretchable_count > 0) { | ||
| 212 | // Resize stretchable children. | ||
| 213 | const int stretchable_widget_size = free_area / stretchable_count; | ||
| 214 | list_foreach(layout->widget.children, child, { | ||
| 215 | if (child->stretch != uiStretchNone) { | ||
| 216 | LayoutWidget(child, width, stretchable_widget_size); | ||
| 217 | } else { | ||
| 218 | LayoutWidget(child, width, height); | ||
| 219 | } | ||
| 220 | }); | ||
| 221 | } | ||
| 222 | // Now position all widgets inside the layout. | ||
| 223 | int y = 0; | ||
| 224 | list_foreach(layout->widget.children, child, { | ||
| 225 | child->rect.y = y; | ||
| 226 | y += child->rect.height; | ||
| 227 | }); | ||
| 228 | // Layout's width is max of its children. | ||
| 229 | layout->widget.rect.width = 0; | ||
| 230 | list_foreach(layout->widget.children, child, { | ||
| 231 | layout->widget.rect.width = | ||
| 232 | Max(layout->widget.rect.width, child->rect.width); | ||
| 233 | }); | ||
| 234 | break; | ||
| 235 | } | ||
| 236 | case uiHorizontal: { | ||
| 237 | // Resize fixed-size children and compute free area. | ||
| 238 | int free_area = width; | ||
| 239 | int stretchable_count = 0; // Number of stretchable widgets. | ||
| 240 | list_foreach(layout->widget.children, child, { | ||
| 241 | if (child->stretch & uiStretchX) { | ||
| 242 | stretchable_count++; | ||
| 243 | } else { | ||
| 244 | LayoutWidget(child, free_area, height); | ||
| 245 | free_area -= child->rect.width; | ||
| 246 | } | ||
| 247 | }); | ||
| 248 | if (stretchable_count > 0) { | ||
| 249 | // Resize stretchable children. | ||
| 250 | const int stretchable_size = free_area / stretchable_count; | ||
| 251 | list_foreach(layout->widget.children, child, { | ||
| 252 | if (child->stretch != uiStretchNone) { | ||
| 253 | LayoutWidget(child, stretchable_size, height); | ||
| 254 | } | ||
| 255 | }); | ||
| 256 | } | ||
| 257 | // Now position all widgets inside the layout. | ||
| 258 | int x = 0; | ||
| 259 | list_foreach(layout->widget.children, child, { | ||
| 260 | child->rect.x = x; | ||
| 261 | x += child->rect.width; | ||
| 262 | }); | ||
| 263 | // Layout's height is max of its children. | ||
| 264 | layout->widget.rect.height = 0; | ||
| 265 | list_foreach(layout->widget.children, child, { | ||
| 266 | layout->widget.rect.height = | ||
| 267 | Max(layout->widget.rect.height, child->rect.height); | ||
| 268 | }); | ||
| 269 | break; | ||
| 270 | } | ||
| 271 | } | ||
| 272 | } | ||
| 273 | |||
| 274 | static void ResizeFrame(uiFrame* frame, int width, int height) { | ||
| 275 | assert(frame); | ||
| 276 | |||
| 277 | frame->widget.rect.width = width; | ||
| 278 | frame->widget.rect.height = height; | ||
| 279 | |||
| 280 | list_foreach_mut( | ||
| 281 | frame->widget.children, child, { LayoutWidget(child, width, height); }); | ||
| 282 | } | ||
| 283 | |||
| 284 | void uiLayOut(uiFrame* frame, int width, int height) { | ||
| 285 | assert(frame); | ||
| 286 | LayoutWidget(&frame->widget, width, height); | ||
| 287 | } | ||
| 288 | |||
| 289 | static void LayoutWidget(uiWidget* widget, int width, int height) { | ||
| 290 | assert(widget); | ||
| 143 | 291 | ||
| 144 | switch (widget->type) { | 292 | switch (widget->type) { |
| 293 | case uiTypeLayout: | ||
| 294 | Layout((uiLayout*)widget, width, height); | ||
| 295 | break; | ||
| 145 | case uiTypeButton: | 296 | case uiTypeButton: |
| 297 | ResizeButton((uiButton*)widget, width, height); | ||
| 146 | break; | 298 | break; |
| 147 | case uiTypeFrame: | 299 | case uiTypeFrame: |
| 148 | list_foreach_mut( | 300 | ResizeFrame((uiFrame*)widget, width, height); |
| 149 | widget->children, child, { ResizeWidget(child, width, height); }); | ||
| 150 | break; | 301 | break; |
| 151 | case uiTypeLabel: | 302 | case uiTypeLabel: |
| 303 | ResizeLabel((uiLabel*)widget, width, height); | ||
| 304 | break; | ||
| 305 | case uiTypeEdit: | ||
| 306 | // TODO: ResizeEdit() | ||
| 152 | break; | 307 | break; |
| 153 | case uiTypeTable: | 308 | case uiTypeTable: |
| 154 | ResizeTable((uiTable*)widget, width, height); | 309 | LayoutTable((uiTable*)widget, width, height); |
| 155 | break; | 310 | break; |
| 156 | case uiTypeMax: | 311 | case uiTypeMax: |
| 157 | TRAP(); | 312 | TRAP(); |
| 158 | break; | 313 | break; |
| 159 | } | 314 | } |
| 160 | } | 315 | } |
| 161 | |||
| 162 | void uiResizeFrame(uiFrame* frame, int width, int height) { | ||
| 163 | assert(frame); | ||
| 164 | ResizeWidget(&frame->widget, width, height); | ||
| 165 | } | ||
