diff options
| -rw-r--r-- | CMakeLists.txt | 1 | ||||
| -rw-r--r-- | include/ui.h | 81 | ||||
| -rw-r--r-- | src/constants.h | 14 | ||||
| -rw-r--r-- | src/layout.c | 192 | ||||
| -rw-r--r-- | src/render.c | 61 | ||||
| -rw-r--r-- | src/ui.c | 1 | ||||
| -rw-r--r-- | src/widget/button.c | 9 | ||||
| -rw-r--r-- | src/widget/frame.c | 9 | ||||
| -rw-r--r-- | src/widget/label.c | 13 | ||||
| -rw-r--r-- | src/widget/layout.c | 25 | ||||
| -rw-r--r-- | src/widget/table.c | 13 | ||||
| -rw-r--r-- | src/widget/widget.c | 109 | ||||
| -rw-r--r-- | src/widget/widget.h | 14 |
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. |
| 43 | typedef enum uiWidgetType { | 43 | typedef 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 | ||
| 51 | typedef struct uiButton uiButton; | 53 | typedef struct uiButton uiButton; |
| 54 | typedef struct uiEdit uiEdit; | ||
| 52 | typedef struct uiFrame uiFrame; | 55 | typedef struct uiFrame uiFrame; |
| 53 | typedef struct uiLabel uiLabel; | 56 | typedef struct uiLabel uiLabel; |
| 57 | typedef struct uiLayout uiLayout; | ||
| 54 | typedef struct uiTable uiTable; | 58 | typedef struct uiTable uiTable; |
| 55 | typedef struct uiWidget uiWidget; | 59 | typedef 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. | ||
| 76 | typedef 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. | ||
| 87 | typedef enum uiStretch { | ||
| 88 | uiStretchNone = 0, | ||
| 89 | uiStretchX = 1, | ||
| 90 | uiStretchY = 2, | ||
| 91 | } uiStretch; | ||
| 92 | |||
| 69 | /// Mouse button. | 93 | /// Mouse button. |
| 70 | typedef enum uiMouseButton { | 94 | typedef 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. | ||
| 173 | typedef struct uiParams { | ||
| 174 | uiStretch stretch; | ||
| 175 | } uiParams; | ||
| 176 | |||
| 148 | // ----------------------------------------------------------------------------- | 177 | // ----------------------------------------------------------------------------- |
| 149 | // Library. | 178 | // Library. |
| 150 | 179 | ||
| @@ -152,50 +181,55 @@ bool uiInit(void); | |||
| 152 | void uiShutdown(void); | 181 | void uiShutdown(void); |
| 153 | 182 | ||
| 154 | // ----------------------------------------------------------------------------- | 183 | // ----------------------------------------------------------------------------- |
| 155 | // Widget pointers. | 184 | // Widget. |
| 156 | 185 | ||
| 157 | uiPtr uiMakeButtonPtr(uiButton*); | 186 | uiPtr uiMakeButtonPtr(uiButton*); |
| 187 | uiPtr uiMakeEditPtr(uiEdit*); | ||
| 158 | uiPtr uiMakeFramePtr(uiFrame*); | 188 | uiPtr uiMakeFramePtr(uiFrame*); |
| 159 | uiPtr uiMakeLabelPtr(uiLabel*); | 189 | uiPtr uiMakeLabelPtr(uiLabel*); |
| 190 | uiPtr uiMakeLayoutPtr(uiLayout*); | ||
| 160 | uiPtr uiMakeTablePtr(uiTable*); | 191 | uiPtr uiMakeTablePtr(uiTable*); |
| 161 | uiPtr uiMakeWidgetPtr(uiWidget*); | 192 | uiPtr uiMakeWidgetPtr(uiWidget*); |
| 162 | uiPtr uiNullptr(void); | 193 | uiPtr uiNullptr(void); |
| 163 | bool uiIsNullptr(uiPtr ptr); | 194 | bool uiIsNullptr(uiPtr); |
| 164 | 195 | ||
| 165 | uiButton* uiGetButtonPtr(uiPtr ptr); | 196 | uiButton* uiGetButtonPtr(uiPtr); |
| 166 | uiFrame* uiGetFramePtr(uiPtr ptr); | 197 | uiEdit* uiGetEditPtr(uiPtr); |
| 167 | uiLabel* uiGetLabelPtr(uiPtr ptr); | 198 | uiFrame* uiGetFramePtr(uiPtr); |
| 168 | uiTable* uiGetTablePtr(uiPtr ptr); | 199 | uiLabel* uiGetLabelPtr(uiPtr); |
| 169 | 200 | uiLayout* uiGetLayoutPtr(uiPtr); | |
| 170 | // ----------------------------------------------------------------------------- | 201 | uiTable* uiGetTablePtr(uiPtr); |
| 171 | // Widget. | ||
| 172 | 202 | ||
| 173 | uiWidgetType uiWidgetGetType(const uiWidget*); | 203 | uiWidgetType uiWidgetGetType(const uiWidget*); |
| 174 | void uiWidgetSetParent(uiPtr child, uiPtr parent); | ||
| 175 | |||
| 176 | // ----------------------------------------------------------------------------- | ||
| 177 | // Button. | ||
| 178 | 204 | ||
| 179 | uiButton* uiMakeButton(const char* text); | 205 | void uiPrint(uiPtr); |
| 180 | 206 | ||
| 181 | // ----------------------------------------------------------------------------- | 207 | // ----------------------------------------------------------------------------- |
| 182 | // Frame. | 208 | // Frame. |
| 183 | 209 | ||
| 184 | uiFrame* uiMakeFrame(void); | 210 | uiFrame* uiMakeFrame(void); |
| 185 | void uiDestroyFrame(uiFrame**); | 211 | void uiDestroyFrame(uiFrame**); |
| 186 | void uiResizeFrame(uiFrame*, int width, int height); | ||
| 187 | uiSize uiGetFrameSize(const uiFrame*); | 212 | uiSize uiGetFrameSize(const uiFrame*); |
| 188 | 213 | ||
| 189 | // ----------------------------------------------------------------------------- | 214 | // ----------------------------------------------------------------------------- |
| 215 | // Layout. | ||
| 216 | uiLayout* uiMakeLayout(uiPtr parent, uiLayoutDirection); | ||
| 217 | |||
| 218 | // ----------------------------------------------------------------------------- | ||
| 219 | // Button. | ||
| 220 | |||
| 221 | uiButton* uiMakeButton(uiPtr parent, const char* text, const uiParams*); | ||
| 222 | |||
| 223 | // ----------------------------------------------------------------------------- | ||
| 190 | // Label. | 224 | // Label. |
| 191 | 225 | ||
| 192 | uiLabel* uiMakeLabel(const char* text); | 226 | uiLabel* uiMakeLabel(uiPtr parent, const char* text); |
| 193 | const char* uiLabelGetText(const uiLabel*); | 227 | const char* uiLabelGetText(const uiLabel*); |
| 194 | 228 | ||
| 195 | // ----------------------------------------------------------------------------- | 229 | // ----------------------------------------------------------------------------- |
| 196 | // Table. | 230 | // Table. |
| 197 | 231 | ||
| 198 | uiTable* uiMakeTable(int rows, int cols, const char** header); | 232 | uiTable* uiMakeTable(uiPtr parent, int rows, int cols, const char** header); |
| 199 | void uiTableClear(uiTable*); | 233 | void uiTableClear(uiTable*); |
| 200 | void uiTableAddRow(uiTable*, const char** row); | 234 | void uiTableAddRow(uiTable*, const char** row); |
| 201 | void uiTableSet(uiTable*, int row, int col, const char* text); | 235 | void uiTableSet(uiTable*, int row, int col, const char* text); |
| @@ -203,12 +237,20 @@ const char* uiTableGet(const uiTable*, int row, int col); | |||
| 203 | void uiTableScroll(uiTable*, int row); | 237 | void 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. | ||
| 245 | void uiLayOut(uiFrame*, int width, int height); | ||
| 246 | |||
| 247 | // ----------------------------------------------------------------------------- | ||
| 206 | // Rendering. | 248 | // Rendering. |
| 207 | 249 | ||
| 208 | void uiRender(const uiFrame*, uiSurface*); | 250 | void 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. |
| 219 | int uiGetEvents(uiWidgetEvent const**); | 261 | int 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. |
| 226 | bool uiSendEvent(uiFrame*, const uiInputEvent*); | 265 | 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 @@ | |||
| 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)) | 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 | } | ||
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. |
| 21 | typedef struct RenderState { | 21 | typedef 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 | ||
| 27 | static void RenderWidget(RenderState* state, const uiWidget* widget); | 27 | static 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. | ||
| 169 | static 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. |
| 169 | static void RenderLabel(const uiLabel* label, RenderState* state) { | 176 | static 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 | ||
| 263 | void 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. |
| 257 | static void RenderWidget(RenderState* state, const uiWidget* widget) { | 280 | static 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 | ||
| 283 | void 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 | } |
| @@ -8,6 +8,7 @@ | |||
| 8 | 8 | ||
| 9 | bool uiInit(void) { | 9 | bool 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 | ||
| 5 | uiButton* uiMakeButton(const char* text) { | 5 | uiButton* 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 | ||
| 5 | uiFrame* uiMakeFrame(void) { | 5 | uiFrame* 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 | ||
| 6 | uiLabel* uiMakeLabel(const char* text) { | 6 | uiLabel* 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 | |||
| 3 | static 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 | |||
| 14 | uiLayout* 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 | ||
| 6 | uiTable* uiMakeTable(int rows, int cols, const char** header) { | 6 | uiTable* 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 | ||
| 97 | void SyncTableToScrollbar(uiTable* table) { | 99 | void 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 | ||
| 103 | const uiCell* TableGetCell(const uiTable* table, int row, int col) { | 106 | 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 @@ | |||
| 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 | ||
| 33 | void uiWidgetSetParent(uiPtr child_, uiPtr parent_) { | 35 | void 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 | ||
| 68 | uiPtr uiMakeLayoutPtr(uiLayout* layout) { | ||
| 69 | assert(layout); | ||
| 70 | return (uiPtr){.type = uiTypeLayout, .layout = layout}; | ||
| 71 | } | ||
| 72 | |||
| 61 | uiPtr uiMakeTablePtr(uiTable* table) { | 73 | uiPtr 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 | ||
| 120 | uiLayout* uiGetLayoutPtr(uiPtr ptr) { | ||
| 121 | assert(ptr.type == uiTypeLayout); | ||
| 122 | assert(ptr.layout); | ||
| 123 | return ptr.layout; | ||
| 124 | } | ||
| 125 | |||
| 126 | uiEdit* uiGetEditPtr(uiPtr ptr) { | ||
| 127 | assert(ptr.type == uiTypeEdit); | ||
| 128 | assert(ptr.edit); | ||
| 129 | return ptr.edit; | ||
| 130 | } | ||
| 131 | |||
| 106 | uiTable* uiGetTablePtr(uiPtr ptr) { | 132 | uiTable* 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 | |||
| 138 | typedef struct PrintState { | ||
| 139 | mstring pad; | ||
| 140 | mstring rect; | ||
| 141 | } PrintState; | ||
| 142 | |||
| 143 | static 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 | |||
| 150 | static 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 | |||
| 211 | void 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*) | |||
| 13 | typedef struct uiWidget { | 13 | typedef 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 | ||
| 21 | typedef struct uiLayout { | ||
| 22 | uiWidget widget; | ||
| 23 | uiLayoutDirection direction; | ||
| 24 | } uiLayout; | ||
| 25 | |||
| 19 | typedef struct uiButton { | 26 | typedef 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 | ||
| 40 | typedef struct uiEdit { | ||
| 41 | uiWidget widget; | ||
| 42 | string text; | ||
| 43 | } uiEdit; | ||
| 44 | |||
| 33 | typedef struct uiScrollbar { | 45 | typedef 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 | ||
| 71 | void WidgetSetParent(uiPtr child, uiPtr parent); | ||
| 60 | void DestroyWidget(uiWidget** ppWidget); | 72 | void 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 |
