summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2026-03-25 19:59:14 -0700
committer3gg <3gg@shellblade.net>2026-03-25 19:59:14 -0700
commit4152fbecb6ee8360575aa4c24e9cedf822f159dc (patch)
tree9e9b9db0216a37c5867d472a65289502c459691f
parent7778755c20e779554cd654ecdf7404d37b723fcc (diff)
Implement vertical and horizontal layouts. Use widget position properly when rendering. Toolbar, buttons and edit bars WIPmain
-rw-r--r--CMakeLists.txt1
-rw-r--r--include/ui.h81
-rw-r--r--src/constants.h14
-rw-r--r--src/layout.c192
-rw-r--r--src/render.c61
-rw-r--r--src/ui.c1
-rw-r--r--src/widget/button.c9
-rw-r--r--src/widget/frame.c9
-rw-r--r--src/widget/label.c13
-rw-r--r--src/widget/layout.c25
-rw-r--r--src/widget/table.c13
-rw-r--r--src/widget/widget.c109
-rw-r--r--src/widget/widget.h14
13 files changed, 460 insertions, 82 deletions
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
20 src/widget/button.c 20 src/widget/button.c
21 src/widget/frame.c 21 src/widget/frame.c
22 src/widget/label.c 22 src/widget/label.c
23 src/widget/layout.c
23 src/widget/scrollbar.c 24 src/widget/scrollbar.c
24 src/widget/table.c 25 src/widget/table.c
25 src/widget/widget.c 26 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;
42/// Widget type. 42/// Widget type.
43typedef enum uiWidgetType { 43typedef enum uiWidgetType {
44 uiTypeButton, 44 uiTypeButton,
45 uiTypeEdit,
45 uiTypeFrame, 46 uiTypeFrame,
46 uiTypeLabel, 47 uiTypeLabel,
48 uiTypeLayout,
47 uiTypeTable, 49 uiTypeTable,
48 uiTypeMax, 50 uiTypeMax,
49} uiWidgetType; 51} uiWidgetType;
50 52
51typedef struct uiButton uiButton; 53typedef struct uiButton uiButton;
54typedef struct uiEdit uiEdit;
52typedef struct uiFrame uiFrame; 55typedef struct uiFrame uiFrame;
53typedef struct uiLabel uiLabel; 56typedef struct uiLabel uiLabel;
57typedef struct uiLayout uiLayout;
54typedef struct uiTable uiTable; 58typedef struct uiTable uiTable;
55typedef struct uiWidget uiWidget; 59typedef struct uiWidget uiWidget;
56 60
@@ -59,13 +63,33 @@ typedef struct uiPtr {
59 uiWidgetType type; 63 uiWidgetType type;
60 union { 64 union {
61 uiButton* button; 65 uiButton* button;
66 uiEdit* edit;
62 uiFrame* frame; 67 uiFrame* frame;
63 uiLabel* label; 68 uiLabel* label;
69 uiLayout* layout;
64 uiTable* table; 70 uiTable* table;
65 uiWidget* widget; 71 uiWidget* widget;
66 }; 72 };
67} uiPtr; 73} uiPtr;
68 74
75/// Direction in which a layout widget lays out its children.
76typedef enum uiLayoutDirection {
77 uiVertical,
78 uiHorizontal,
79} uiLayoutDirection;
80
81/// Directions in which a widget stretches.
82///
83/// Stretch determines how the widget occupies the area of its parent widget.
84///
85/// uiStretchNone - the widget has a fixed size.
86/// uiStretchX/Y - the widget stretches in the X/Y direction.
87typedef enum uiStretch {
88 uiStretchNone = 0,
89 uiStretchX = 1,
90 uiStretchY = 2,
91} uiStretch;
92
69/// Mouse button. 93/// Mouse button.
70typedef enum uiMouseButton { 94typedef enum uiMouseButton {
71 uiLMB, 95 uiLMB,
@@ -145,6 +169,11 @@ typedef struct uiWidgetEvent {
145 }; 169 };
146} uiWidgetEvent; 170} uiWidgetEvent;
147 171
172/// Common construction parameters for widgets.
173typedef struct uiParams {
174 uiStretch stretch;
175} uiParams;
176
148// ----------------------------------------------------------------------------- 177// -----------------------------------------------------------------------------
149// Library. 178// Library.
150 179
@@ -152,50 +181,55 @@ bool uiInit(void);
152void uiShutdown(void); 181void uiShutdown(void);
153 182
154// ----------------------------------------------------------------------------- 183// -----------------------------------------------------------------------------
155// Widget pointers. 184// Widget.
156 185
157uiPtr uiMakeButtonPtr(uiButton*); 186uiPtr uiMakeButtonPtr(uiButton*);
187uiPtr uiMakeEditPtr(uiEdit*);
158uiPtr uiMakeFramePtr(uiFrame*); 188uiPtr uiMakeFramePtr(uiFrame*);
159uiPtr uiMakeLabelPtr(uiLabel*); 189uiPtr uiMakeLabelPtr(uiLabel*);
190uiPtr uiMakeLayoutPtr(uiLayout*);
160uiPtr uiMakeTablePtr(uiTable*); 191uiPtr uiMakeTablePtr(uiTable*);
161uiPtr uiMakeWidgetPtr(uiWidget*); 192uiPtr uiMakeWidgetPtr(uiWidget*);
162uiPtr uiNullptr(void); 193uiPtr uiNullptr(void);
163bool uiIsNullptr(uiPtr ptr); 194bool uiIsNullptr(uiPtr);
164 195
165uiButton* uiGetButtonPtr(uiPtr ptr); 196uiButton* uiGetButtonPtr(uiPtr);
166uiFrame* uiGetFramePtr(uiPtr ptr); 197uiEdit* uiGetEditPtr(uiPtr);
167uiLabel* uiGetLabelPtr(uiPtr ptr); 198uiFrame* uiGetFramePtr(uiPtr);
168uiTable* uiGetTablePtr(uiPtr ptr); 199uiLabel* uiGetLabelPtr(uiPtr);
169 200uiLayout* uiGetLayoutPtr(uiPtr);
170// ----------------------------------------------------------------------------- 201uiTable* uiGetTablePtr(uiPtr);
171// Widget.
172 202
173uiWidgetType uiWidgetGetType(const uiWidget*); 203uiWidgetType uiWidgetGetType(const uiWidget*);
174void uiWidgetSetParent(uiPtr child, uiPtr parent);
175
176// -----------------------------------------------------------------------------
177// Button.
178 204
179uiButton* uiMakeButton(const char* text); 205void uiPrint(uiPtr);
180 206
181// ----------------------------------------------------------------------------- 207// -----------------------------------------------------------------------------
182// Frame. 208// Frame.
183 209
184uiFrame* uiMakeFrame(void); 210uiFrame* uiMakeFrame(void);
185void uiDestroyFrame(uiFrame**); 211void uiDestroyFrame(uiFrame**);
186void uiResizeFrame(uiFrame*, int width, int height);
187uiSize uiGetFrameSize(const uiFrame*); 212uiSize uiGetFrameSize(const uiFrame*);
188 213
189// ----------------------------------------------------------------------------- 214// -----------------------------------------------------------------------------
215// Layout.
216uiLayout* uiMakeLayout(uiPtr parent, uiLayoutDirection);
217
218// -----------------------------------------------------------------------------
219// Button.
220
221uiButton* uiMakeButton(uiPtr parent, const char* text, const uiParams*);
222
223// -----------------------------------------------------------------------------
190// Label. 224// Label.
191 225
192uiLabel* uiMakeLabel(const char* text); 226uiLabel* uiMakeLabel(uiPtr parent, const char* text);
193const char* uiLabelGetText(const uiLabel*); 227const char* uiLabelGetText(const uiLabel*);
194 228
195// ----------------------------------------------------------------------------- 229// -----------------------------------------------------------------------------
196// Table. 230// Table.
197 231
198uiTable* uiMakeTable(int rows, int cols, const char** header); 232uiTable* uiMakeTable(uiPtr parent, int rows, int cols, const char** header);
199void uiTableClear(uiTable*); 233void uiTableClear(uiTable*);
200void uiTableAddRow(uiTable*, const char** row); 234void uiTableAddRow(uiTable*, const char** row);
201void uiTableSet(uiTable*, int row, int col, const char* text); 235void uiTableSet(uiTable*, int row, int col, const char* text);
@@ -203,12 +237,20 @@ const char* uiTableGet(const uiTable*, int row, int col);
203void uiTableScroll(uiTable*, int row); 237void uiTableScroll(uiTable*, int row);
204 238
205// ----------------------------------------------------------------------------- 239// -----------------------------------------------------------------------------
240// Layout.
241
242/// Lay out the widgets in the frame given the frame's new width and height.
243///
244/// This should typically be called whenever the window is resized.
245void uiLayOut(uiFrame*, int width, int height);
246
247// -----------------------------------------------------------------------------
206// Rendering. 248// Rendering.
207 249
208void uiRender(const uiFrame*, uiSurface*); 250void uiRender(const uiFrame*, uiSurface*);
209 251
210// ----------------------------------------------------------------------------- 252// -----------------------------------------------------------------------------
211// UI Events. 253// UI and user events.
212 254
213/// Get the widget events. 255/// Get the widget events.
214/// Return the number of events in the returned array. 256/// Return the number of events in the returned array.
@@ -218,9 +260,6 @@ void uiRender(const uiFrame*, uiSurface*);
218/// therefore report zero widget events. 260/// therefore report zero widget events.
219int uiGetEvents(uiWidgetEvent const**); 261int uiGetEvents(uiWidgetEvent const**);
220 262
221// -----------------------------------------------------------------------------
222// User input.
223
224/// Send an input event to the UI. 263/// Send an input event to the UI.
225/// Return true if the UI requires a redraw. 264/// Return true if the UI requires a redraw.
226bool uiSendEvent(uiFrame*, const uiInputEvent*); 265bool 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 @@
1/* Constants used throughout the library.
2 *
3 * All sizes are relative to the font size. Widths are relative to the font
4 * width; heights are relative to the font height. Other sizes are typically
5 * relative to font height.
6 */
1#pragma once 7#pragma once
2 8
3// Maximum number of events that can be stored in a single input loop. 9/// Maximum number of events that can be stored in a single input loop.
4#define MaxWidgetEvents 8 10#define MaxWidgetEvents 8
5 11
6// Width of scroll bars in pixels. 12// TODO: Make this relative to the font width.
13/// Width of scroll bars in pixels.
7#define ScrollbarWidth 32 14#define ScrollbarWidth 32
15
16/// Button border size relative to font height.
17#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 @@
5 5
6#include <cassert.h> 6#include <cassert.h>
7 7
8#define Min(a, b) ((a) < (b) ? (a) : (b)) 8static void LayoutWidget(uiWidget* widget, int width, int height);
9 9
10static void ResizeTable(uiTable* table, int width, int height) { 10/// Return the area required to fit the text.
11static 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
17static 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
35static 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
50static 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
138static void ResizeWidget(uiWidget* widget, int width, int height) { 180static 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
274static 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
284void uiLayOut(uiFrame* frame, int width, int height) {
285 assert(frame);
286 LayoutWidget(&frame->widget, width, height);
287}
288
289static 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
162void uiResizeFrame(uiFrame* frame, int width, int height) {
163 assert(frame);
164 ResizeWidget(&frame->widget, width, height);
165}
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};
19/// We store a subsurface separate from the surface so that we can always check 19/// We store a subsurface separate from the surface so that we can always check
20/// whether a given coordinate is within the bounds of the physical surface. 20/// whether a given coordinate is within the bounds of the physical surface.
21typedef struct RenderState { 21typedef struct RenderState {
22 uiSurface surface; /// Surface of pixels on which the UI is rendered. 22 uiSurface surface; ///< Surface of pixels on which the UI is rendered.
23 uiRect subsurface; /// Subregion where the current UI widget is rendered. 23 uiRect subsurface; ///< Subregion where the current UI widget is rendered.
24 uiPoint pen; /// Current pen position relative to subsurface. 24 uiPoint pen; ///< Current pen position relative to subsurface.
25} RenderState; 25} RenderState;
26 26
27static void RenderWidget(RenderState* state, const uiWidget* widget); 27static void RenderWidget(RenderState* state, const uiWidget* widget);
@@ -165,6 +165,13 @@ static void RenderFrame(const uiFrame* frame, RenderState* state) {
165 FillRect(&frame->widget.rect, uiBlack, state); 165 FillRect(&frame->widget.rect, uiBlack, state);
166} 166}
167 167
168/// Render a button.
169static void RenderButton(const uiButton* button, RenderState* state) {
170 assert(button);
171 assert(state);
172 RenderText(string_data(button->text), string_length(button->text), state);
173}
174
168/// Render a label. 175/// Render a label.
169static void RenderLabel(const uiLabel* label, RenderState* state) { 176static void RenderLabel(const uiLabel* label, RenderState* state) {
170 assert(label); 177 assert(label);
@@ -253,14 +260,43 @@ static void RenderTable(const uiTable* table, RenderState* state) {
253 } 260 }
254} 261}
255 262
263void uiRender(const uiFrame* frame, uiSurface* surface) {
264 assert(frame);
265 assert(surface);
266
267 RenderWidget(
268 &(RenderState){
269 .surface = *surface,
270 .subsurface = (uiRect){.x = 0,
271 .y = 0,
272 .width = surface->width,
273 .height = surface->height},
274 .pen = {.x = 0, .y = 0},
275 },
276 (const uiWidget*)frame);
277}
278
256/// Render a widget. 279/// Render a widget.
257static void RenderWidget(RenderState* state, const uiWidget* widget) { 280static void RenderWidget(RenderState* state, const uiWidget* widget) {
258 assert(state); 281 assert(state);
259 assert(widget); 282 assert(widget);
260 283
284 // A widget's position is relative to its parent's position.
285 // The pen currently points at the parent. Move it to this widget's position
286 // before rendering it, then render the widget's children using this new
287 // position.
288 // The pen's original position must be restored before returning, so save a
289 // copy here.
290 const uiPoint pen = state->pen;
291 state->pen =
292 (uiPoint){state->pen.x + widget->rect.x, state->pen.y + widget->rect.y};
293
261 // Render this widget. 294 // Render this widget.
262 switch (widget->type) { 295 switch (widget->type) {
296 case uiTypeLayout:
297 break;
263 case uiTypeButton: 298 case uiTypeButton:
299 RenderButton((const uiButton*)widget, state);
264 break; 300 break;
265 case uiTypeFrame: 301 case uiTypeFrame:
266 RenderFrame((const uiFrame*)widget, state); 302 RenderFrame((const uiFrame*)widget, state);
@@ -268,6 +304,8 @@ static void RenderWidget(RenderState* state, const uiWidget* widget) {
268 case uiTypeLabel: 304 case uiTypeLabel:
269 RenderLabel((const uiLabel*)widget, state); 305 RenderLabel((const uiLabel*)widget, state);
270 break; 306 break;
307 case uiTypeEdit:
308 break;
271 case uiTypeTable: 309 case uiTypeTable:
272 RenderTable((const uiTable*)widget, state); 310 RenderTable((const uiTable*)widget, state);
273 break; 311 break;
@@ -278,20 +316,7 @@ static void RenderWidget(RenderState* state, const uiWidget* widget) {
278 316
279 // Render children. 317 // Render children.
280 list_foreach(widget->children, child, { RenderWidget(state, child); }); 318 list_foreach(widget->children, child, { RenderWidget(state, child); });
281}
282 319
283void uiRender(const uiFrame* frame, uiSurface* surface) { 320 // Restore the pen.
284 assert(frame); 321 state->pen = pen;
285 assert(surface);
286
287 RenderWidget(
288 &(RenderState){
289 .surface = *surface,
290 .subsurface = (uiRect){.x = 0,
291 .y = 0,
292 .width = surface->width,
293 .height = surface->height},
294 .pen = {.x = 0, .y = 0},
295 },
296 (const uiWidget*)frame);
297} 322}
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 @@
8 8
9bool uiInit(void) { 9bool uiInit(void) {
10 // TODO: Embed the font into the library instead. 10 // TODO: Embed the font into the library instead.
11 // TODO: Better error reporting.
11 const char* font_path = "../ui/fontbaker/NK57.bin"; 12 const char* font_path = "../ui/fontbaker/NK57.bin";
12 if (!(g_ui.font = LoadFontAtlas(font_path))) { 13 if (!(g_ui.font = LoadFontAtlas(font_path))) {
13 return false; 14 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 @@
2 2
3#include "widget.h" 3#include "widget.h"
4 4
5uiButton* uiMakeButton(const char* text) { 5uiButton* uiMakeButton(uiPtr parent, const char* text, const uiParams* params) {
6 assert(text); 6 assert(text);
7 assert(params);
7 8
8 uiButton* button = UI_NEW(uiButton); 9 uiButton* button = UI_NEW(uiButton);
9 10
10 *button = (uiButton){ 11 *button = (uiButton){
11 .widget = 12 .widget =
12 (uiWidget){ 13 (uiWidget){
13 .type = uiTypeButton, 14 .type = uiTypeButton,
14 .rect = {0}, 15 .rect = {0},
16 .stretch = params->stretch,
15 }, 17 },
16 .text = string_new(text), 18 .text = string_new(text),
17 }; 19 };
20 WidgetSetParent(uiMakeButtonPtr(button), parent);
18 return button; 21 return button;
19} 22}
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 @@
3#include "widget.h" 3#include "widget.h"
4 4
5uiFrame* uiMakeFrame(void) { 5uiFrame* uiMakeFrame(void) {
6 uiFrame* frame = UI_NEW(uiFrame); 6 uiFrame* frame = UI_NEW(uiFrame);
7 frame->widget.type = uiTypeFrame; 7
8 *frame = (uiFrame){
9 .widget =
10 (uiWidget){.type = uiTypeFrame, .stretch = uiStretchX | uiStretchY}
11 };
12
8 return frame; 13 return frame;
9} 14}
10 15
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 @@
3#include "uiLibrary.h" 3#include "uiLibrary.h"
4#include "widget.h" 4#include "widget.h"
5 5
6uiLabel* uiMakeLabel(const char* text) { 6uiLabel* uiMakeLabel(uiPtr parent, const char* text) {
7 assert(text); 7 assert(text);
8 8
9 uiLabel* label = UI_NEW(uiLabel); 9 uiLabel* label = UI_NEW(uiLabel);
10 10
11 *label = (uiLabel){ 11 *label = (uiLabel){
12 .widget = 12 .widget =
13 (uiWidget){ 13 (uiWidget){.type = uiTypeLabel,
14 .type = uiTypeLabel,
15 .rect = 14 .rect =
16 (uiRect){ 15 (uiRect){.width = (int)strlen(text) *
17 .width = 16 g_ui.font->header.glyph_width,
18 (int)strlen(text) * g_ui.font->header.glyph_width, 17 .height = g_ui.font->header.glyph_height}},
19 .height = g_ui.font->header.glyph_height}},
20 .text = string_new(text), 18 .text = string_new(text),
21 }; 19 };
20 WidgetSetParent(uiMakeLabelPtr(label), parent);
22 return label; 21 return label;
23} 22}
24 23
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 @@
1#include "widget.h"
2
3static uiStretch StretchFromDirection(uiLayoutDirection direction) {
4 switch (direction) {
5 case uiHorizontal:
6 return uiStretchX;
7 case uiVertical:
8 return uiStretchY;
9 }
10 assert(false);
11 return uiStretchNone;
12}
13
14uiLayout* uiMakeLayout(uiPtr parent, uiLayoutDirection direction) {
15 uiLayout* layout = UI_NEW(uiLayout);
16
17 *layout = (uiLayout){
18 .widget = (uiWidget){.type = uiTypeLayout,
19 .stretch = StretchFromDirection(direction)},
20 .direction = direction,
21 };
22 WidgetSetParent(uiMakeLayoutPtr(layout), parent);
23
24 return layout;
25}
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 @@
3#define Min(a, b) ((a) < (b) ? (a) : (b)) 3#define Min(a, b) ((a) < (b) ? (a) : (b))
4#define Max(a, b) ((a) > (b) ? (a) : (b)) 4#define Max(a, b) ((a) > (b) ? (a) : (b))
5 5
6uiTable* uiMakeTable(int rows, int cols, const char** header) { 6uiTable* uiMakeTable(uiPtr parent, int rows, int cols, const char** header) {
7 uiTable* table = UI_NEW(uiTable); 7 uiTable* table = UI_NEW(uiTable);
8 8
9 *table = (uiTable){ 9 *table = (uiTable){
10 .widget = (uiWidget){.type = uiTypeTable}, 10 .widget =
11 (uiWidget){.type = uiTypeTable, .stretch = (uiStretchX | uiStretchY)},
11 .rows = rows, 12 .rows = rows,
12 .cols = cols, 13 .cols = cols,
13 .widths = (cols > 0) ? calloc(cols, sizeof(int)) : 0, 14 .widths = (cols > 0) ? calloc(cols, sizeof(int)) : 0,
@@ -15,6 +16,7 @@ uiTable* uiMakeTable(int rows, int cols, const char** header) {
15 .cells = (rows * cols > 0) ? calloc(rows, sizeof(uiCell*)) : 0, 16 .cells = (rows * cols > 0) ? calloc(rows, sizeof(uiCell*)) : 0,
16 .flags = {0}, 17 .flags = {0},
17 }; 18 };
19 WidgetSetParent(uiMakeTablePtr(table), parent);
18 20
19 if (header) { 21 if (header) {
20 for (int col = 0; col < cols; ++col) { 22 for (int col = 0; col < cols; ++col) {
@@ -91,13 +93,14 @@ void SyncScrollbarToTable(uiTable* table) {
91 assert(table); 93 assert(table);
92 ScrollbarScroll( 94 ScrollbarScroll(
93 &table->scrollbar, (int)((double)table->offset / (double)table->rows * 95 &table->scrollbar, (int)((double)table->offset / (double)table->rows *
94 (double)table->height)); 96 (double)table->widget.rect.height));
95} 97}
96 98
97void SyncTableToScrollbar(uiTable* table) { 99void SyncTableToScrollbar(uiTable* table) {
98 assert(table); 100 assert(table);
99 table->offset = (int)((double)table->scrollbar.handle_y / 101 table->offset =
100 (double)table->height * (double)table->rows); 102 (int)((double)table->scrollbar.handle_y /
103 (double)table->widget.rect.height * (double)table->rows);
101} 104}
102 105
103const uiCell* TableGetCell(const uiTable* table, int row, int col) { 106const 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 @@
2 2
3#include <cassert.h> 3#include <cassert.h>
4 4
5#include <stdio.h>
6
5// ----------------------------------------------------------------------------- 7// -----------------------------------------------------------------------------
6// Widget. 8// Widget.
7 9
@@ -30,14 +32,19 @@ void DestroyWidget(uiWidget** ppWidget) {
30 UI_DEL(ppWidget); 32 UI_DEL(ppWidget);
31} 33}
32 34
33void uiWidgetSetParent(uiPtr child_, uiPtr parent_) { 35void WidgetSetParent(uiPtr child_, uiPtr parent_) {
34 uiWidget* child = child_.widget; 36 uiWidget* child = child_.widget;
35 uiWidget* parent = parent_.widget; 37 uiWidget* parent = parent_.widget;
36 38
37 assert(child); 39 assert(child);
38 assert(parent); 40 assert(parent);
39 41
40 list_add(parent->children, child); 42 if (!uiIsNullptr(child->parent)) {
43 list_remove(child->parent.widget->children, child);
44 }
45
46 list_push(parent->children, child);
47 child->parent = parent_;
41} 48}
42 49
43// ----------------------------------------------------------------------------- 50// -----------------------------------------------------------------------------
@@ -58,6 +65,11 @@ uiPtr uiMakeLabelPtr(uiLabel* label) {
58 return (uiPtr){.type = uiTypeLabel, .label = label}; 65 return (uiPtr){.type = uiTypeLabel, .label = label};
59} 66}
60 67
68uiPtr uiMakeLayoutPtr(uiLayout* layout) {
69 assert(layout);
70 return (uiPtr){.type = uiTypeLayout, .layout = layout};
71}
72
61uiPtr uiMakeTablePtr(uiTable* table) { 73uiPtr uiMakeTablePtr(uiTable* table) {
62 assert(table); 74 assert(table);
63 return (uiPtr){.type = uiTypeTable, .table = table}; 75 return (uiPtr){.type = uiTypeTable, .table = table};
@@ -72,6 +84,8 @@ uiPtr uiMakeWidgetPtr(uiWidget* widget) {
72 return uiMakeFramePtr((uiFrame*)widget); 84 return uiMakeFramePtr((uiFrame*)widget);
73 case uiTypeLabel: 85 case uiTypeLabel:
74 return uiMakeLabelPtr((uiLabel*)widget); 86 return uiMakeLabelPtr((uiLabel*)widget);
87 case uiTypeLayout:
88 return uiMakeLayoutPtr((uiLayout*)widget);
75 case uiTypeTable: 89 case uiTypeTable:
76 return uiMakeTablePtr((uiTable*)widget); 90 return uiMakeTablePtr((uiTable*)widget);
77 default: 91 default:
@@ -103,8 +117,99 @@ uiLabel* uiGetLabelPtr(uiPtr ptr) {
103 return ptr.label; 117 return ptr.label;
104} 118}
105 119
120uiLayout* uiGetLayoutPtr(uiPtr ptr) {
121 assert(ptr.type == uiTypeLayout);
122 assert(ptr.layout);
123 return ptr.layout;
124}
125
126uiEdit* uiGetEditPtr(uiPtr ptr) {
127 assert(ptr.type == uiTypeEdit);
128 assert(ptr.edit);
129 return ptr.edit;
130}
131
106uiTable* uiGetTablePtr(uiPtr ptr) { 132uiTable* uiGetTablePtr(uiPtr ptr) {
107 assert(ptr.type == uiTypeTable); 133 assert(ptr.type == uiTypeTable);
108 assert(ptr.table); 134 assert(ptr.table);
109 return ptr.table; 135 return ptr.table;
110} 136}
137
138typedef struct PrintState {
139 mstring pad;
140 mstring rect;
141} PrintState;
142
143static void RectToString(uiRect rect, mstring* out) {
144 assert(out);
145 out->length = snprintf(
146 out->str, sizeof(out->str), "rect{(x:%d, y:%d), (w:%d, h:%d)", rect.x,
147 rect.y, rect.width, rect.height);
148}
149
150static void uiPrintRec(uiPtr ptr, PrintState* state) {
151 if (uiIsNullptr(ptr)) {
152 return;
153 }
154 RectToString(ptr.widget->rect, &state->rect);
155 switch (ptr.type) {
156 case uiTypeButton: {
157 const uiButton* button = uiGetButtonPtr(ptr);
158 printf(
159 "%sbutton{rect=%s, text=\"%s\"}\n", mstring_cstr(&state->pad),
160 mstring_cstr(&state->rect), string_cstr(&button->text));
161 break;
162 }
163 case uiTypeLabel: {
164 const uiLabel* label = uiGetLabelPtr(ptr);
165 printf(
166 "%sbutton{rect=%s, text=\"%s\"}\n", mstring_cstr(&state->pad),
167 mstring_cstr(&state->rect), string_cstr(&label->text));
168 break;
169 }
170 case uiTypeLayout: {
171 const uiLayout* layout = uiGetLayoutPtr(ptr);
172 const char* direction = "";
173 switch (layout->direction) {
174 case uiHorizontal:
175 direction = "horizontal";
176 break;
177 case uiVertical:
178 direction = "vertical";
179 break;
180 }
181 printf(
182 "%s%s_layout{rect=%s}\n", mstring_cstr(&state->pad), direction,
183 mstring_cstr(&state->rect));
184 break;
185 }
186 case uiTypeFrame: {
187 printf(
188 "%sframe{rect=%s}\n", mstring_cstr(&state->pad),
189 mstring_cstr(&state->rect));
190 break;
191 }
192 case uiTypeTable: {
193 const uiTable* table = uiGetTablePtr(ptr);
194 printf(
195 "%stable{rect=%s}\n", mstring_cstr(&state->pad),
196 mstring_cstr(&state->rect));
197 break;
198 }
199 default:
200 printf("%swidget\n", mstring_cstr(&state->pad));
201 break;
202 }
203 const mstring pad = state->pad;
204 state->pad = mstring_concat(state->pad, mstring_make(" "));
205 list_foreach(ptr.widget->children, child, {
206 uiPrintRec(uiMakeWidgetPtr(child), state);
207 });
208 state->pad = pad;
209}
210
211void uiPrint(uiPtr ptr) {
212 PrintState state =
213 (PrintState){.pad = mstring_make_empty(), .rect = mstring_make_empty()};
214 uiPrintRec(ptr, &state);
215}
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*)
13typedef struct uiWidget { 13typedef struct uiWidget {
14 uiWidgetType type; 14 uiWidgetType type;
15 uiRect rect; 15 uiRect rect;
16 uiStretch stretch;
17 uiPtr parent;
16 Widget_list children; 18 Widget_list children;
17} uiWidget; 19} uiWidget;
18 20
21typedef struct uiLayout {
22 uiWidget widget;
23 uiLayoutDirection direction;
24} uiLayout;
25
19typedef struct uiButton { 26typedef struct uiButton {
20 uiWidget widget; 27 uiWidget widget;
21 string text; 28 string text;
@@ -30,6 +37,11 @@ typedef struct uiLabel {
30 string text; 37 string text;
31} uiLabel; 38} uiLabel;
32 39
40typedef struct uiEdit {
41 uiWidget widget;
42 string text;
43} uiEdit;
44
33typedef struct uiScrollbar { 45typedef struct uiScrollbar {
34 int width; 46 int width;
35 int height; // Total height: handle plus scrollable area. 47 int height; // Total height: handle plus scrollable area.
@@ -45,7 +57,6 @@ typedef struct uiTable {
45 uiWidget widget; 57 uiWidget widget;
46 int rows; 58 int rows;
47 int cols; 59 int cols;
48 int height; // Height in pixels.
49 int* widths; // Width, in pixels, for each column. 60 int* widths; // Width, in pixels, for each column.
50 uiCell* header; // If non-null, row of 'cols' header cells. 61 uiCell* header; // If non-null, row of 'cols' header cells.
51 uiCell** cells; // Array of 'rows' rows, each of 'cols' cells. 62 uiCell** cells; // Array of 'rows' rows, each of 'cols' cells.
@@ -57,6 +68,7 @@ typedef struct uiTable {
57 } flags; 68 } flags;
58} uiTable; 69} uiTable;
59 70
71void WidgetSetParent(uiPtr child, uiPtr parent);
60void DestroyWidget(uiWidget** ppWidget); 72void DestroyWidget(uiWidget** ppWidget);
61 73
62/// Set the scrollbar handle's y-coordinate, which is clipped to the scrollbar's 74/// Set the scrollbar handle's y-coordinate, which is clipped to the scrollbar's