From a4294e4a94189dffb1fdf99c9a60d87d77272926 Mon Sep 17 00:00:00 2001
From: 3gg <3gg@shellblade.net>
Date: Sat, 13 Jul 2024 10:52:24 -0700
Subject: Restructure project.

---
 src/constants.h     |   7 +
 src/event.c         |  23 ++
 src/event.h         |   6 +
 src/input.c         | 178 +++++++++++
 src/layout.c        | 146 +++++++++
 src/render.c        | 283 ++++++++++++++++
 src/ui.c            | 906 +---------------------------------------------------
 src/uiLibrary.c     |   3 +
 src/uiLibrary.h     |  16 +
 src/widget/button.c |  19 ++
 src/widget/frame.c  |  19 ++
 src/widget/label.c  |  28 ++
 src/widget/table.c  | 103 ++++++
 src/widget/table.h  |  11 +
 src/widget/widget.c |  93 ++++++
 src/widget/widget.h |  66 ++++
 16 files changed, 1003 insertions(+), 904 deletions(-)
 create mode 100644 src/constants.h
 create mode 100644 src/event.c
 create mode 100644 src/event.h
 create mode 100644 src/input.c
 create mode 100644 src/layout.c
 create mode 100644 src/render.c
 create mode 100644 src/uiLibrary.c
 create mode 100644 src/uiLibrary.h
 create mode 100644 src/widget/button.c
 create mode 100644 src/widget/frame.c
 create mode 100644 src/widget/label.c
 create mode 100644 src/widget/table.c
 create mode 100644 src/widget/table.h
 create mode 100644 src/widget/widget.c
 create mode 100644 src/widget/widget.h

(limited to 'src')

diff --git a/src/constants.h b/src/constants.h
new file mode 100644
index 0000000..457f461
--- /dev/null
+++ b/src/constants.h
@@ -0,0 +1,7 @@
+#pragma once
+
+// Maximum number of events that can be stored in a single input loop.
+#define MaxWidgetEvents 8
+
+// Width of scroll bars in pixels.
+#define ScrollBarWidth 16
diff --git a/src/event.c b/src/event.c
new file mode 100644
index 0000000..9b98ee6
--- /dev/null
+++ b/src/event.c
@@ -0,0 +1,23 @@
+#include "event.h"
+
+#include "constants.h"
+#include "uiLibrary.h"
+
+#include <cassert.h>
+
+void PushWidgetEvent(uiWidgetEvent* event) {
+  assert(event);
+  assert(g_ui.num_widget_events < MaxWidgetEvents);
+
+  g_ui.widget_events[g_ui.num_widget_events++] = *event;
+}
+
+int uiGetEvents(uiWidgetEvent const** ppWidgetEvents) {
+  assert(ppWidgetEvents);
+
+  const int count        = g_ui.num_widget_events;
+  g_ui.num_widget_events = 0;
+
+  *ppWidgetEvents = g_ui.widget_events;
+  return count;
+}
diff --git a/src/event.h b/src/event.h
new file mode 100644
index 0000000..d116fa9
--- /dev/null
+++ b/src/event.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include <ui.h>
+
+/// Push an event into the widget library.
+void PushWidgetEvent(uiWidgetEvent* event);
diff --git a/src/input.c b/src/input.c
new file mode 100644
index 0000000..c4b1be7
--- /dev/null
+++ b/src/input.c
@@ -0,0 +1,178 @@
+#include <ui.h>
+
+#include "event.h"
+#include "uiLibrary.h"
+#include "widget/widget.h"
+
+#include <cassert.h>
+
+#define Max(a, b) ((a) > (b) ? (a) : (b))
+
+/// Return true if the rectangle contains the point.
+static bool RectContains(uiRect rect, uiPoint point) {
+  return (rect.x <= point.x) && (point.x <= (rect.x + rect.width)) &&
+         (rect.y <= point.y) && (point.y <= (rect.y + rect.height));
+}
+
+/// Get the bottom-most widget under the given mouse position.
+static uiWidget* GetWidgetUnderMouse(uiWidget* parent, uiPoint mouse) {
+  assert(parent);
+
+  // First check the children so that the selection is from "most specific" to
+  // "less specific" from the user's perspective.
+  list_foreach(parent->children, child, {
+    uiWidget* target = GetWidgetUnderMouse(child, mouse);
+    if (target != 0) {
+      return target;
+    }
+  });
+
+  if (RectContains(parent->rect, mouse)) {
+    return parent;
+  }
+
+  return 0;
+}
+
+/// Get the table row at the given pixel position.
+static void GetTableRowColAtXy(
+    const uiTable* table, uiPoint p, int* out_row, int* out_col) {
+  assert(table);
+  assert(out_row);
+  assert(out_col);
+
+  const uiWidget* widget = (uiWidget*)table;
+
+  int col = -1;
+  int row = -1;
+
+  if (RectContains(widget->rect, p)) {
+    int x = p.x - widget->rect.x;
+    for (col = 0; (col < table->cols) && (x > table->widths[col]); ++col) {
+      x -= table->widths[col];
+    }
+    // 0 is the header and we want to map the first row to 0, so -1.
+    row = table->offset +
+          ((p.y - widget->rect.y) / g_ui.font->header.glyph_height) - 1;
+    // Out-of-bounds check.
+    if ((col >= table->cols) || (row >= table->rows)) {
+      col = row = -1;
+    }
+  }
+
+  *out_col = col;
+  *out_row = row;
+}
+
+/// Process a table click event.
+static void ClickTable(uiTable* table, const uiMouseClickEvent* event) {
+  assert(table);
+  assert(event);
+
+  int row, col;
+  GetTableRowColAtXy(table, event->mouse_position, &row, &col);
+
+  if ((row != -1) && (col != -1)) {
+    PushWidgetEvent(&(uiWidgetEvent){
+        .type        = uiWidgetEventClick,
+        .widget      = uiMakeTablePtr(table),
+        .table_click = (uiTableClickEvent){.row = row, .col = col}
+    });
+  }
+}
+
+/// Process a table scroll event.
+static void ScrollTable(uiTable* table, const uiMouseScrollEvent* event) {
+  assert(table);
+  assert(event);
+  table->offset = Max(0, table->offset - event->scroll_offset);
+}
+
+/// Process a scroll event.
+static bool ProcessScrollEvent(
+    uiWidget* widget, const uiMouseScrollEvent* event) {
+  assert(widget);
+  assert(event);
+
+  bool processed = false;
+
+  switch (widget->type) {
+  case uiTypeTable:
+    ScrollTable((uiTable*)widget, event);
+    processed = true;
+    break;
+  default:
+    break;
+  }
+
+  return processed;
+}
+
+/// Process a click event.
+static bool ProcessClickEvent(
+    uiWidget* widget, const uiMouseClickEvent* event) {
+  assert(widget);
+  assert(event);
+
+  bool processed = false;
+
+  switch (widget->type) {
+  case uiTypeTable:
+    ClickTable((uiTable*)widget, event);
+    processed = true;
+    break;
+  default:
+    break;
+  }
+
+  return processed;
+}
+
+bool uiSendEvent(uiFrame* frame, const uiInputEvent* event) {
+  assert(frame);
+  assert(event);
+
+  uiWidget* widget = (uiWidget*)frame;
+
+  bool processed = false;
+
+  switch (event->type) {
+  case uiEventMouseButton: {
+    const uiMouseButtonEvent* ev = &event->mouse_button;
+
+    uiMouseButtonState* prev_state = &g_ui.mouse_button_state[ev->button];
+
+    if ((*prev_state == uiMouseDown) && (ev->state == uiMouseUp)) {
+      // Click.
+      uiSendEvent(
+          frame,
+          &(uiInputEvent){
+              .type        = uiEventMouseClick,
+              .mouse_click = (uiMouseClickEvent){
+                                                 .button = ev->button, .mouse_position = ev->mouse_position}
+      });
+    }
+
+    *prev_state = ev->state;
+    break;
+  }
+  case uiEventMouseClick: {
+    const uiMouseClickEvent* ev = &event->mouse_click;
+    uiWidget* target = GetWidgetUnderMouse(widget, ev->mouse_position);
+    if (target) {
+      processed = ProcessClickEvent(target, ev);
+    }
+    break;
+  }
+  case uiEventMouseScroll: {
+    const uiMouseScrollEvent* ev = &event->mouse_scroll;
+    uiWidget* target = GetWidgetUnderMouse(widget, ev->mouse_position);
+    if (target) {
+      processed = ProcessScrollEvent(target, ev);
+    }
+    break;
+  }
+  }
+
+  return processed;
+}
diff --git a/src/layout.c b/src/layout.c
new file mode 100644
index 0000000..9d4b556
--- /dev/null
+++ b/src/layout.c
@@ -0,0 +1,146 @@
+#include <ui.h>
+
+#include "uiLibrary.h"
+#include "widget/table.h"
+#include "widget/widget.h"
+
+#include <cassert.h>
+
+static void ResizeTable(uiTable* table, int width, int height) {
+  assert(table);
+
+  if (table->cols == 0) {
+    return;
+  }
+
+  // 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 uiLabel* label  = (uiLabel*)cell->child;
+    const int      length = (int)string_length(label->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 = GetCell(table, row, col);
+      if (cell->child) {
+        const uiLabel* label  = (uiLabel*)cell->child;
+        const int      length = (int)string_length(label->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.
+  if (table->flags.vertical_overflow) {
+    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);
+    }
+  }
+}
+
+static void ResizeWidget(uiWidget* widget, int width, int height) {
+  assert(widget);
+
+  widget->rect.width  = width;
+  widget->rect.height = height;
+
+  switch (widget->type) {
+  case uiTypeButton:
+    break;
+  case uiTypeFrame:
+    list_foreach_mut(
+        widget->children, child, { ResizeWidget(child, width, height); });
+    break;
+  case uiTypeLabel:
+    break;
+  case uiTypeTable:
+    ResizeTable((uiTable*)widget, width, height);
+    break;
+  case uiTypeMax:
+    TRAP();
+    break;
+  }
+}
+
+void uiResizeFrame(uiFrame* frame, int width, int height) {
+  assert(frame);
+  ResizeWidget(&frame->widget, width, height);
+}
diff --git a/src/render.c b/src/render.c
new file mode 100644
index 0000000..24490c0
--- /dev/null
+++ b/src/render.c
@@ -0,0 +1,283 @@
+#include <ui.h>
+
+#include "uiLibrary.h"
+#include "widget/table.h"
+#include "widget/widget.h"
+
+#include <cassert.h>
+#include <cstring.h>
+
+static const uiPixel uiBlack = {40, 40, 40, 255};
+static const uiPixel uiWhite = {255, 255, 255, 255};
+static const uiPixel uiPink  = {128, 0, 128, 255};
+
+/// Render state.
+///
+/// Render functions are allowed to manipulate the state internally (e.g., the
+/// subsurface), but must leave the state intact before returning, except, of
+/// course, for the rendered pixels.
+///
+/// We store a subsurface separate from the surface so that we can always check
+/// whether a given coordinate is within the bounds of the physical surface.
+typedef struct RenderState {
+  uiSurface surface;    /// Surface of pixels on which the UI is rendered.
+  uiRect    subsurface; /// Subregion where the current UI widget is rendered.
+  uiPoint   pen;        /// Current pen position relative to subsurface.
+} RenderState;
+
+static void RenderWidget(RenderState* state, const uiWidget* widget);
+
+/// Push a new subsurface onto which subsequent UI widgets are rendered.
+void PushSubsurface(
+    RenderState* state, int width, int height, uiRect* original_subsurface,
+    uiPoint* original_pen) {
+  assert(state);
+  assert(original_subsurface);
+  assert(original_pen);
+
+  *original_subsurface = state->subsurface;
+  *original_pen        = state->pen;
+
+  state->subsurface.x      = state->subsurface.x + state->pen.x;
+  state->subsurface.width  = width;
+  state->subsurface.height = height;
+  state->pen.x             = 0;
+}
+
+/// Restore the previous subsurface.
+void PopSubsurface(
+    RenderState* state, const uiRect* original_subsurface,
+    const uiPoint* original_pen) {
+  assert(state);
+  assert(original_subsurface);
+  assert(original_pen);
+
+  state->subsurface = *original_subsurface;
+  state->pen        = *original_pen;
+}
+
+/// Check whether pen + (w,h) is within the surface and subsurface.
+static bool PenInSurface(const RenderState* state, int w, int h) {
+  assert(state);
+
+  // Surface.
+  const bool in_surface =
+      ((state->subsurface.x + state->pen.x + w) < state->surface.width) &&
+      ((state->subsurface.y + state->pen.y + h) < state->surface.height);
+
+  // Subsurface.
+  const bool in_subsurface = ((state->pen.x + w) < state->subsurface.width) &&
+                             ((state->pen.y + h) < state->subsurface.height);
+
+  return in_surface && in_subsurface;
+}
+
+/// Get the pixel at (x,y).
+static uiPixel* SurfaceXy(uiSurface* surface, int x, int y) {
+  assert(surface);
+  assert(x >= 0);
+  assert(y >= 0);
+  assert(x < surface->width);
+  assert(y < surface->height);
+  return surface->pixels + (surface->width * y) + x;
+}
+
+/// Get the pixel at pen + (x,y).
+static uiPixel* PixelXy(RenderState* state, int x, int y) {
+  assert(state);
+  return SurfaceXy(
+      &state->surface, state->subsurface.x + state->pen.x + x,
+      state->subsurface.y + state->pen.y + y);
+}
+
+/// Fill a rectangle with a constant colour.
+static void FillRect(const uiRect* rect, uiPixel colour, RenderState* state) {
+  assert(rect);
+  assert(state);
+  assert(rect->width <= state->subsurface.width);
+  assert(rect->height <= state->subsurface.height);
+
+  for (int y = rect->y; y < rect->y + rect->height; ++y) {
+    uiPixel* pixel = PixelXy(state, rect->x, y);
+    for (int x = rect->x; x < rect->x + rect->width; ++x) {
+      *pixel++ = colour;
+    }
+  }
+}
+
+/// Render a glyph.
+/// The glyph is clamped to the surface's bounds.
+static void RenderGlyph(
+    const FontAtlas* atlas, unsigned char c, RenderState* state) {
+  assert(atlas);
+  assert(state);
+  assert(atlas->header.glyph_width <= state->subsurface.width);
+  assert(atlas->header.glyph_height <= state->subsurface.height);
+
+  const int glyph_width  = atlas->header.glyph_width;
+  const int glyph_height = atlas->header.glyph_height;
+
+  const unsigned char* glyph = FontGetGlyph(atlas, c);
+
+  for (int y = 0; (y < atlas->header.glyph_height) &&
+                  PenInSurface(state, glyph_width - 1, glyph_height - 1);
+       ++y) {
+    for (int x = 0; (x < atlas->header.glyph_width) &&
+                    PenInSurface(state, glyph_width - 1, glyph_height - 1);
+         ++x, ++glyph) {
+      uiPixel* pixel = PixelXy(state, x, y);
+      if (*glyph > 0) {
+        pixel->r = *glyph;
+        pixel->g = *glyph;
+        pixel->b = *glyph;
+        pixel->a = 255;
+      }
+    }
+  }
+}
+
+/// Render text.
+static void RenderText(const char* text, size_t length, RenderState* state) {
+  assert(text);
+  assert(state);
+
+  const FontAtlas* atlas = g_ui.font;
+
+  const int glyph_width  = atlas->header.glyph_width;
+  const int glyph_height = atlas->header.glyph_height;
+
+  // Save the x-pen so that we can restore it after rendering the text.
+  const int x0 = state->pen.x;
+
+  // Truncate the text rendering if it exceeds the subsurface's width or height.
+  const char* c = text;
+  for (size_t i = 0;
+       (i < length) && PenInSurface(state, glyph_width - 1, glyph_height - 1);
+       ++i, ++c, state->pen.x += glyph_width) {
+    RenderGlyph(atlas, *c, state);
+  }
+
+  state->pen.x = x0;
+}
+
+/// Render a frame.
+static void RenderFrame(const uiFrame* frame, RenderState* state) {
+  assert(frame);
+
+  FillRect(&frame->widget.rect, uiBlack, state);
+}
+
+/// Render a label.
+static void RenderLabel(const uiLabel* label, RenderState* state) {
+  assert(label);
+  assert(state);
+
+  RenderText(string_data(label->text), string_length(label->text), state);
+}
+
+/// Render a table.
+static void RenderTable(const uiTable* table, RenderState* state) {
+  assert(table);
+  assert(state);
+
+  const int x0 = state->pen.x;
+  const int y0 = state->pen.y;
+
+  uiRect  original_subsurface = {0};
+  uiPoint original_pen        = {0};
+
+  // Render header.
+  if (table->header) {
+    for (int col = 0; col < table->cols; ++col) {
+      // Crop the column contents to the column width so that one column does
+      // not spill into the next.
+      PushSubsurface(
+          state, table->widths[col], state->subsurface.height,
+          &original_subsurface, &original_pen);
+
+      const uiCell* cell = &table->header[col];
+      RenderWidget(state, cell->child);
+
+      // Reset the original subsurface and pen for subsequent columns.
+      PopSubsurface(state, &original_subsurface, &original_pen);
+
+      // Next column.
+      state->pen.x += table->widths[col];
+    }
+  }
+  state->pen.x = x0;
+  state->pen.y += g_ui.font->header.glyph_height;
+
+  // Render rows.
+  for (int row = table->offset;
+       (row < table->rows) && PenInSurface(state, 0, 0); ++row) {
+    for (int col = 0; (col < table->cols) && PenInSurface(state, 0, 0); ++col) {
+      // Crop the column contents to the column width so that one column does
+      // not spill into the next.
+      PushSubsurface(
+          state, table->widths[col], state->subsurface.height,
+          &original_subsurface, &original_pen);
+
+      state->subsurface.x     = state->subsurface.x + state->pen.x;
+      state->subsurface.width = table->widths[col];
+      state->pen.x            = 0;
+
+      const uiCell* cell = GetCell(table, row, col);
+      RenderWidget(state, cell->child);
+
+      // Reset the original subsurface and pen for subsequent columns.
+      PopSubsurface(state, &original_subsurface, &original_pen);
+
+      // Next column.
+      state->pen.x += table->widths[col];
+    }
+    state->pen.x = x0;
+    state->pen.y += g_ui.font->header.glyph_height;
+  }
+  state->pen.y = y0;
+}
+
+/// Render a widget.
+static void RenderWidget(RenderState* state, const uiWidget* widget) {
+  assert(state);
+  assert(widget);
+
+  // Render this widget.
+  switch (widget->type) {
+  case uiTypeButton:
+    break;
+  case uiTypeFrame:
+    RenderFrame((const uiFrame*)widget, state);
+    break;
+  case uiTypeLabel:
+    RenderLabel((const uiLabel*)widget, state);
+    break;
+  case uiTypeTable:
+    RenderTable((const uiTable*)widget, state);
+    break;
+  case uiTypeMax:
+    TRAP();
+    break;
+  }
+
+  // Render children.
+  list_foreach(widget->children, child, { RenderWidget(state, child); });
+}
+
+void uiRender(const uiFrame* frame, uiSurface* surface) {
+  assert(frame);
+  assert(surface);
+
+  RenderWidget(
+      &(RenderState){
+          .surface = *surface,
+          .subsurface =
+              (uiRect){
+                       .x      = 0,
+                       .y      = 0,
+                       .width  = surface->width,
+                       .height = surface->height},
+          .pen = {.x = 0, .y = 0},
+  },
+      (const uiWidget*)frame);
+}
diff --git a/src/ui.c b/src/ui.c
index e8c8ee2..4d09584 100644
--- a/src/ui.c
+++ b/src/ui.c
@@ -1,87 +1,11 @@
 #include <ui.h>
 
-#include <cassert.h>
-#include <cstring.h>
-#include <font.h>
-#include <list.h>
-
-#include <stdlib.h>
-
-#define Max(a, b) ((a) > (b) ? (a) : (b))
-
-#define MaxWidgetEvents 8
-
-static void* uiAlloc(size_t count, size_t size) {
-  void* mem = calloc(count, size);
-  ASSERT(mem);
-  return mem;
-}
-
-#define UI_NEW(TYPE) (TYPE*)uiAlloc(1, sizeof(TYPE))
-#define UI_DEL(ppWidget)       \
-  {                            \
-    assert(ppWidget);          \
-    void* widget_ = *ppWidget; \
-    if (widget_) {             \
-      free(widget_);           \
-      *ppWidget = 0;           \
-    }                          \
-  }
-
-DEF_LIST(Widget, uiWidget*)
-
-/// Base widget type.
-typedef struct uiWidget {
-  uiWidgetType type;
-  uiRect       rect;
-  Widget_list  children;
-} uiWidget;
-
-/// Button.
-typedef struct uiButton {
-  uiWidget widget;
-  string   text;
-} uiButton;
-
-/// Frame.
-typedef struct uiFrame {
-  uiWidget widget;
-} uiFrame;
-
-/// Label.
-typedef struct uiLabel {
-  uiWidget widget;
-  string   text;
-} uiLabel;
-
-/// Table cell.
-typedef struct uiCell {
-  uiWidget* child;
-} uiCell;
-
-/// Table.
-typedef struct uiTable {
-  uiWidget widget;
-  int      rows;
-  int      cols;
-  int*     widths; // Width, in pixels, for each column.
-  uiCell*  header; // If non-null, row of 'cols' header cells.
-  uiCell** cells;  // Array of 'rows' rows, each of 'cols' cells.
-  int      offset; // Offset into the rows of the table. Units: rows.
-} uiTable;
-
-typedef struct uiLibrary {
-  FontAtlas*         font;
-  uiMouseButtonState mouse_button_state[uiMouseButtonMax];
-  uiWidgetEvent      widget_events[MaxWidgetEvents];
-  int                num_widget_events;
-} uiLibrary;
+#include "uiLibrary.h"
+#include "widget/widget.h"
 
 // -----------------------------------------------------------------------------
 // Library.
 
-uiLibrary g_ui = {0};
-
 bool uiInit(void) {
   // TODO: Embed the font into the library instead.
   const char* font_path = "../ui/fontbaker/NK57.bin";
@@ -105,829 +29,3 @@ bool uiInit(void) {
 }
 
 void uiShutdown(void) {}
-
-// -----------------------------------------------------------------------------
-// Widget pointers.
-
-uiPtr uiMakeButtonPtr(uiButton* button) {
-  assert(button);
-  return (uiPtr){.type = uiTypeButton, .button = button};
-}
-
-uiPtr uiMakeFramePtr(uiFrame* frame) {
-  assert(frame);
-  return (uiPtr){.type = uiTypeFrame, .frame = frame};
-}
-
-uiPtr uiMakeLabelPtr(uiLabel* label) {
-  assert(label);
-  return (uiPtr){.type = uiTypeLabel, .label = label};
-}
-
-uiPtr uiMakeTablePtr(uiTable* table) {
-  assert(table);
-  return (uiPtr){.type = uiTypeTable, .table = table};
-}
-
-static uiPtr uiMakeWidgetPtr(uiWidget* widget) {
-  assert(widget);
-  return (uiPtr){.type = widget->type, .widget = widget};
-}
-
-uiButton* uiGetButtonPtr(uiPtr ptr) {
-  assert(ptr.type == uiTypeButton);
-  assert(ptr.button);
-  return ptr.button;
-}
-
-uiFrame* uiGetFramePtr(uiPtr ptr) {
-  assert(ptr.type == uiTypeFrame);
-  assert(ptr.frame);
-  return ptr.frame;
-}
-
-uiLabel* uiGetLabelPtr(uiPtr ptr) {
-  assert(ptr.type == uiTypeLabel);
-  assert(ptr.label);
-  return ptr.label;
-}
-
-uiTable* uiGetTablePtr(uiPtr ptr) {
-  assert(ptr.type == uiTypeTable);
-  assert(ptr.table);
-  return ptr.table;
-}
-
-// -----------------------------------------------------------------------------
-// Widget.
-
-uiWidgetType uiWidgetGetType(const uiWidget* widget) {
-  assert(widget);
-  return widget->type;
-}
-
-static void DestroyWidget(uiWidget** ppWidget) {
-  assert(ppWidget);
-
-  uiWidget* widget = *ppWidget;
-  if (widget) {
-    list_foreach_mut(widget->children, child, { DestroyWidget(&child); });
-  }
-  UI_DEL(ppWidget);
-}
-
-void uiWidgetSetParent(uiPtr child_, uiPtr parent_) {
-  uiWidget* child  = child_.widget;
-  uiWidget* parent = parent_.widget;
-
-  assert(child);
-  assert(parent);
-
-  list_add(parent->children, child);
-}
-
-// -----------------------------------------------------------------------------
-// Button.
-
-uiButton* uiMakeButton(const char* text) {
-  assert(text);
-
-  uiButton* button = UI_NEW(uiButton);
-
-  *button = (uiButton){
-      .widget =
-          (uiWidget){
-                     .type = uiTypeButton,
-                     .rect = {0},
-                     },
-      .text = string_new(text),
-  };
-  return button;
-}
-
-// -----------------------------------------------------------------------------
-// Label.
-
-uiLabel* uiMakeLabel(const char* text) {
-  assert(text);
-
-  uiLabel* label = UI_NEW(uiLabel);
-
-  *label = (uiLabel){
-      .widget =
-          (uiWidget){
-                     .type = uiTypeLabel,
-                     .rect =
-                  (uiRect){
-                      .width =
-                          (int)strlen(text) * g_ui.font->header.glyph_width,
-                      .height = g_ui.font->header.glyph_height}},
-      .text = string_new(text),
-  };
-  return label;
-}
-
-const char* uiLabelGetText(const uiLabel* label) {
-  assert(label);
-  return string_data(label->text);
-}
-
-// -----------------------------------------------------------------------------
-// Frame.
-
-uiFrame* uiMakeFrame(void) {
-  uiFrame* frame     = UI_NEW(uiFrame);
-  frame->widget.type = uiTypeFrame;
-  return frame;
-}
-
-void uiDestroyFrame(uiFrame** ppFrame) { DestroyWidget((uiWidget**)ppFrame); }
-
-uiSize uiGetFrameSize(const uiFrame* frame) {
-  assert(frame);
-  return (uiSize){
-      .width  = frame->widget.rect.width,
-      .height = frame->widget.rect.height,
-  };
-}
-
-// -----------------------------------------------------------------------------
-// Table.
-
-static const uiCell* GetCell(const uiTable* table, int row, int col) {
-  assert(table);
-  return &table->cells[row][col];
-}
-
-static uiCell* GetCellMut(uiTable* table, int row, int col) {
-  assert(table);
-  return (uiCell*)GetCell(table, row, col);
-}
-
-static uiCell** GetLastRow(uiTable* table) {
-  assert(table);
-  assert(table->rows > 0);
-  return &table->cells[table->rows - 1];
-}
-
-uiTable* uiMakeTable(int rows, int cols, const char** header) {
-  uiTable* table = UI_NEW(uiTable);
-
-  *table = (uiTable){
-      .widget = (uiWidget){.type = uiTypeTable},
-      .rows   = rows,
-      .cols   = cols,
-      .widths = (cols > 0) ? calloc(cols, sizeof(int)) : 0,
-      .header = header ? calloc(cols, sizeof(uiCell)) : 0,
-      .cells  = (rows * cols > 0) ? calloc(rows, sizeof(uiCell*)) : 0,
-  };
-
-  if (header) {
-    for (int col = 0; col < cols; ++col) {
-      table->header[col].child = (uiWidget*)uiMakeLabel(header[col]);
-    }
-  }
-
-  return table;
-}
-
-void uiTableClear(uiTable* table) {
-  assert(table);
-
-  // Free row data.
-  if (table->cells) {
-    for (int row = 0; row < table->rows; ++row) {
-      for (int col = 0; col < table->cols; ++col) {
-        DestroyWidget(&table->cells[row][col].child);
-      }
-      free(table->cells[row]);
-    }
-    free(table->cells);
-    table->cells = 0;
-  }
-  table->rows = 0;
-
-  // Clear row widths.
-  for (int i = 0; i < table->cols; ++i) {
-    table->widths[i] = 0;
-  }
-
-  table->offset = 0;
-}
-
-void uiTableAddRow(uiTable* table, const char** row) {
-  assert(table);
-
-  table->rows++;
-
-  uiCell** cells = realloc(table->cells, table->rows * sizeof(uiCell*));
-  ASSERT(cells);
-  table->cells = cells;
-
-  uiCell** pLastRow = GetLastRow(table);
-  *pLastRow         = calloc(table->cols, sizeof(uiCell));
-  ASSERT(*pLastRow);
-  uiCell* lastRow = *pLastRow;
-
-  for (int col = 0; col < table->cols; ++col) {
-    lastRow[col].child = (uiWidget*)uiMakeLabel(row[col]);
-  }
-}
-
-void uiTableSet(uiTable* table, int row, int col, uiPtr child) {
-  assert(table);
-  assert(child.widget);
-
-  GetCellMut(table, row, col)->child = child.widget;
-}
-
-const uiWidget* uiTableGet(const uiTable* table, int row, int col) {
-  assert(table);
-  return GetCell(table, row, col)->child;
-}
-
-uiWidget* uiTableGetMut(uiTable* table, int row, int col) {
-  assert(table);
-  return GetCellMut(table, row, col)->child;
-}
-
-// -----------------------------------------------------------------------------
-// Layout and resizing.
-
-static void ResizeTable(uiTable* table, int width, int height) {
-  assert(table);
-
-  if (table->cols == 0) {
-    return;
-  }
-
-  // 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 uiLabel* label  = (uiLabel*)cell->child;
-    const int      length = (int)string_length(label->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 = GetCell(table, row, col);
-      if (cell->child) {
-        const uiLabel* label  = (uiLabel*)cell->child;
-        const int      length = (int)string_length(label->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--;
-    }
-  }
-}
-
-static void ResizeWidget(uiWidget* widget, int width, int height) {
-  assert(widget);
-
-  widget->rect.width  = width;
-  widget->rect.height = height;
-
-  switch (widget->type) {
-  case uiTypeButton:
-    break;
-  case uiTypeFrame:
-    list_foreach_mut(
-        widget->children, child, { ResizeWidget(child, width, height); });
-    break;
-  case uiTypeLabel:
-    break;
-  case uiTypeTable:
-    ResizeTable((uiTable*)widget, width, height);
-    break;
-  case uiTypeMax:
-    TRAP();
-    break;
-  }
-}
-
-void uiResizeFrame(uiFrame* frame, int width, int height) {
-  assert(frame);
-  ResizeWidget(&frame->widget, width, height);
-}
-
-// -----------------------------------------------------------------------------
-// Rendering.
-
-static const uiPixel uiBlack = {40, 40, 40, 255};
-static const uiPixel uiWhite = {255, 255, 255, 255};
-static const uiPixel uiPink  = {128, 0, 128, 255};
-
-/// Render state.
-///
-/// Render functions are allowed to manipulate the state internally (e.g., the
-/// subsurface), but must leave the state intact before returning, except, of
-/// course, for the rendered pixels.
-///
-/// We store a subsurface separate from the surface so that we can always check
-/// whether a given coordinate is within the bounds of the physical surface.
-typedef struct RenderState {
-  uiSurface surface;    /// Surface of pixels on which the UI is rendered.
-  uiRect    subsurface; /// Subregion where the current UI widget is rendered.
-  uiPoint   pen;        /// Current pen position relative to subsurface.
-} RenderState;
-
-static void RenderWidget(RenderState* state, const uiWidget* widget);
-
-void PushSubsurface(
-    RenderState* state, int width, int height, uiRect* original_subsurface,
-    uiPoint* original_pen) {
-  assert(state);
-  assert(original_subsurface);
-  assert(original_pen);
-
-  *original_subsurface = state->subsurface;
-  *original_pen        = state->pen;
-
-  state->subsurface.x      = state->subsurface.x + state->pen.x;
-  state->subsurface.width  = width;
-  state->subsurface.height = height;
-  state->pen.x             = 0;
-}
-
-void PopSubsurface(
-    RenderState* state, const uiRect* original_subsurface,
-    const uiPoint* original_pen) {
-  assert(state);
-  assert(original_subsurface);
-  assert(original_pen);
-
-  state->subsurface = *original_subsurface;
-  state->pen        = *original_pen;
-}
-
-/// Checks whether pen + (w,h) is within the surface and subsurface.
-static bool PenInSurface(const RenderState* state, int w, int h) {
-  assert(state);
-
-  // Surface.
-  const bool in_surface =
-      ((state->subsurface.x + state->pen.x + w) < state->surface.width) &&
-      ((state->subsurface.y + state->pen.y + h) < state->surface.height);
-
-  // Subsurface.
-  const bool in_subsurface = ((state->pen.x + w) < state->subsurface.width) &&
-                             ((state->pen.y + h) < state->subsurface.height);
-
-  return in_surface && in_subsurface;
-}
-
-/// Get the pixel at (x,y).
-static uiPixel* SurfaceXy(uiSurface* surface, int x, int y) {
-  assert(surface);
-  assert(x >= 0);
-  assert(y >= 0);
-  assert(x < surface->width);
-  assert(y < surface->height);
-  return surface->pixels + (surface->width * y) + x;
-}
-
-/// Get the pixel at pen + (x,y).
-static uiPixel* PixelXy(RenderState* state, int x, int y) {
-  assert(state);
-  return SurfaceXy(
-      &state->surface, state->subsurface.x + state->pen.x + x,
-      state->subsurface.y + state->pen.y + y);
-}
-
-static void FillRect(const uiRect* rect, uiPixel colour, RenderState* state) {
-  assert(rect);
-  assert(state);
-  assert(rect->width <= state->subsurface.width);
-  assert(rect->height <= state->subsurface.height);
-
-  for (int y = rect->y; y < rect->y + rect->height; ++y) {
-    uiPixel* pixel = PixelXy(state, rect->x, y);
-    for (int x = rect->x; x < rect->x + rect->width; ++x) {
-      *pixel++ = colour;
-    }
-  }
-}
-
-/// Render a glyph.
-/// The glyph is clamped to the surface's bounds.
-static void RenderGlyph(
-    const FontAtlas* atlas, unsigned char c, RenderState* state) {
-  assert(atlas);
-  assert(state);
-  assert(atlas->header.glyph_width <= state->subsurface.width);
-  assert(atlas->header.glyph_height <= state->subsurface.height);
-
-  const int glyph_width  = atlas->header.glyph_width;
-  const int glyph_height = atlas->header.glyph_height;
-
-  const unsigned char* glyph = FontGetGlyph(atlas, c);
-
-  for (int y = 0; (y < atlas->header.glyph_height) &&
-                  PenInSurface(state, glyph_width - 1, glyph_height - 1);
-       ++y) {
-    for (int x = 0; (x < atlas->header.glyph_width) &&
-                    PenInSurface(state, glyph_width - 1, glyph_height - 1);
-         ++x, ++glyph) {
-      uiPixel* pixel = PixelXy(state, x, y);
-      if (*glyph > 0) {
-        pixel->r = *glyph;
-        pixel->g = *glyph;
-        pixel->b = *glyph;
-        pixel->a = 255;
-      }
-    }
-  }
-}
-
-static void RenderText(const char* text, size_t length, RenderState* state) {
-  assert(text);
-  assert(state);
-
-  const FontAtlas* atlas = g_ui.font;
-
-  const int glyph_width  = atlas->header.glyph_width;
-  const int glyph_height = atlas->header.glyph_height;
-
-  // Save the x-pen so that we can restore it after rendering the text.
-  const int x0 = state->pen.x;
-
-  // Truncate the text rendering if it exceeds the subsurface's width or height.
-  const char* c = text;
-  for (size_t i = 0;
-       (i < length) && PenInSurface(state, glyph_width - 1, glyph_height - 1);
-       ++i, ++c, state->pen.x += glyph_width) {
-    RenderGlyph(atlas, *c, state);
-  }
-
-  state->pen.x = x0;
-}
-
-static void RenderFrame(const uiFrame* frame, RenderState* state) {
-  assert(frame);
-
-  FillRect(&frame->widget.rect, uiBlack, state);
-}
-
-static void RenderLabel(const uiLabel* label, RenderState* state) {
-  assert(label);
-  assert(state);
-
-  RenderText(string_data(label->text), string_length(label->text), state);
-}
-
-static void RenderTable(const uiTable* table, RenderState* state) {
-  assert(table);
-  assert(state);
-
-  const int x0 = state->pen.x;
-  const int y0 = state->pen.y;
-
-  uiRect  original_subsurface = {0};
-  uiPoint original_pen        = {0};
-
-  // Render header.
-  if (table->header) {
-    for (int col = 0; col < table->cols; ++col) {
-      // Crop the column contents to the column width so that one column does
-      // not spill into the next.
-      PushSubsurface(
-          state, table->widths[col], state->subsurface.height,
-          &original_subsurface, &original_pen);
-
-      const uiCell* cell = &table->header[col];
-      RenderWidget(state, cell->child);
-
-      // Reset the original subsurface and pen for subsequent columns.
-      PopSubsurface(state, &original_subsurface, &original_pen);
-
-      // Next column.
-      state->pen.x += table->widths[col];
-    }
-  }
-  state->pen.x = x0;
-  state->pen.y += g_ui.font->header.glyph_height;
-
-  // Render rows.
-  for (int row = table->offset;
-       (row < table->rows) && PenInSurface(state, 0, 0); ++row) {
-    for (int col = 0; (col < table->cols) && PenInSurface(state, 0, 0); ++col) {
-      // Crop the column contents to the column width so that one column does
-      // not spill into the next.
-      PushSubsurface(
-          state, table->widths[col], state->subsurface.height,
-          &original_subsurface, &original_pen);
-
-      state->subsurface.x     = state->subsurface.x + state->pen.x;
-      state->subsurface.width = table->widths[col];
-      state->pen.x            = 0;
-
-      const uiCell* cell = GetCell(table, row, col);
-      RenderWidget(state, cell->child);
-
-      // Reset the original subsurface and pen for subsequent columns.
-      PopSubsurface(state, &original_subsurface, &original_pen);
-
-      // Next column.
-      state->pen.x += table->widths[col];
-    }
-    state->pen.x = x0;
-    state->pen.y += g_ui.font->header.glyph_height;
-  }
-  state->pen.y = y0;
-}
-
-static void RenderWidget(RenderState* state, const uiWidget* widget) {
-  assert(state);
-  assert(widget);
-
-  // Render this widget.
-  switch (widget->type) {
-  case uiTypeButton:
-    break;
-  case uiTypeFrame:
-    RenderFrame((const uiFrame*)widget, state);
-    break;
-  case uiTypeLabel:
-    RenderLabel((const uiLabel*)widget, state);
-    break;
-  case uiTypeTable:
-    RenderTable((const uiTable*)widget, state);
-    break;
-  case uiTypeMax:
-    TRAP();
-    break;
-  }
-
-  // Render children.
-  list_foreach(widget->children, child, { RenderWidget(state, child); });
-}
-
-void uiRender(const uiFrame* frame, uiSurface* surface) {
-  assert(frame);
-  assert(surface);
-
-  RenderWidget(
-      &(RenderState){
-          .surface = *surface,
-          .subsurface =
-              (uiRect){
-                       .x      = 0,
-                       .y      = 0,
-                       .width  = surface->width,
-                       .height = surface->height},
-          .pen = {.x = 0, .y = 0},
-  },
-      (const uiWidget*)frame);
-}
-
-// -----------------------------------------------------------------------------
-// UI Events.
-
-static void PushWidgetEvent(uiWidgetEvent* event) {
-  assert(event);
-  assert(g_ui.num_widget_events < MaxWidgetEvents);
-
-  g_ui.widget_events[g_ui.num_widget_events++] = *event;
-}
-
-int uiGetEvents(uiWidgetEvent const** ppWidgetEvents) {
-  assert(ppWidgetEvents);
-
-  const int count        = g_ui.num_widget_events;
-  g_ui.num_widget_events = 0;
-
-  *ppWidgetEvents = g_ui.widget_events;
-  return count;
-}
-
-// -----------------------------------------------------------------------------
-// User input.
-
-static bool RectContains(uiRect rect, uiPoint point) {
-  return (rect.x <= point.x) && (point.x <= (rect.x + rect.width)) &&
-         (rect.y <= point.y) && (point.y <= (rect.y + rect.height));
-}
-
-static uiWidget* GetWidgetUnderMouse(uiWidget* parent, uiPoint mouse) {
-  assert(parent);
-
-  // First check the children so that the selection is from "most specific" to
-  // "less specific" from the user's perspective.
-  list_foreach(parent->children, child, {
-    uiWidget* target = GetWidgetUnderMouse(child, mouse);
-    if (target != 0) {
-      return target;
-    }
-  });
-
-  if (RectContains(parent->rect, mouse)) {
-    return parent;
-  }
-
-  return 0;
-}
-
-static void GetTableRowColAtXy(
-    const uiTable* table, uiPoint p, int* out_row, int* out_col) {
-  assert(table);
-  assert(out_row);
-  assert(out_col);
-
-  const uiWidget* widget = (uiWidget*)table;
-
-  int col = -1;
-  int row = -1;
-
-  if (RectContains(widget->rect, p)) {
-    int x = p.x - widget->rect.x;
-    for (col = 0; (col < table->cols) && (x > table->widths[col]); ++col) {
-      x -= table->widths[col];
-    }
-    // 0 is the header and we want to map the first row to 0, so -1.
-    row = table->offset +
-          ((p.y - widget->rect.y) / g_ui.font->header.glyph_height) - 1;
-    // Out-of-bounds check.
-    if ((col >= table->cols) || (row >= table->rows)) {
-      col = row = -1;
-    }
-  }
-
-  *out_col = col;
-  *out_row = row;
-}
-
-static void ClickTable(uiTable* table, const uiMouseClickEvent* event) {
-  assert(table);
-  assert(event);
-
-  int row, col;
-  GetTableRowColAtXy(table, event->mouse_position, &row, &col);
-
-  if ((row != -1) && (col != -1)) {
-    PushWidgetEvent(&(uiWidgetEvent){
-        .type        = uiWidgetEventClick,
-        .widget      = uiMakeTablePtr(table),
-        .table_click = (uiTableClickEvent){.row = row, .col = col}
-    });
-  }
-}
-
-static void ScrollTable(uiTable* table, const uiMouseScrollEvent* event) {
-  assert(table);
-  assert(event);
-  table->offset = Max(0, table->offset - event->scroll_offset);
-}
-
-static bool ProcessScrollEvent(
-    uiWidget* widget, const uiMouseScrollEvent* event) {
-  assert(widget);
-  assert(event);
-
-  bool processed = false;
-
-  switch (widget->type) {
-  case uiTypeTable:
-    ScrollTable((uiTable*)widget, event);
-    processed = true;
-    break;
-  default:
-    break;
-  }
-
-  return processed;
-}
-
-static bool ProcessClickEvent(
-    uiWidget* widget, const uiMouseClickEvent* event) {
-  assert(widget);
-  assert(event);
-
-  bool processed = false;
-
-  switch (widget->type) {
-  case uiTypeTable:
-    ClickTable((uiTable*)widget, event);
-    processed = true;
-    break;
-  default:
-    break;
-  }
-
-  return processed;
-}
-
-bool uiSendEvent(uiFrame* frame, const uiInputEvent* event) {
-  assert(frame);
-  assert(event);
-
-  uiWidget* widget = (uiWidget*)frame;
-
-  bool processed = false;
-
-  switch (event->type) {
-  case uiEventMouseButton: {
-    const uiMouseButtonEvent* ev = &event->mouse_button;
-
-    uiMouseButtonState* prev_state = &g_ui.mouse_button_state[ev->button];
-
-    if ((*prev_state == uiMouseDown) && (ev->state == uiMouseUp)) {
-      // Click.
-      uiSendEvent(
-          frame,
-          &(uiInputEvent){
-              .type        = uiEventMouseClick,
-              .mouse_click = (uiMouseClickEvent){
-                                                 .button = ev->button, .mouse_position = ev->mouse_position}
-      });
-    }
-
-    *prev_state = ev->state;
-    break;
-  }
-  case uiEventMouseClick: {
-    const uiMouseClickEvent* ev = &event->mouse_click;
-    uiWidget* target = GetWidgetUnderMouse(widget, ev->mouse_position);
-    if (target) {
-      processed = ProcessClickEvent(target, ev);
-    }
-    break;
-  }
-  case uiEventMouseScroll: {
-    const uiMouseScrollEvent* ev = &event->mouse_scroll;
-    uiWidget* target = GetWidgetUnderMouse(widget, ev->mouse_position);
-    if (target) {
-      processed = ProcessScrollEvent(target, ev);
-    }
-    break;
-  }
-  }
-
-  return processed;
-}
diff --git a/src/uiLibrary.c b/src/uiLibrary.c
new file mode 100644
index 0000000..fbacdcc
--- /dev/null
+++ b/src/uiLibrary.c
@@ -0,0 +1,3 @@
+#include "uiLibrary.h"
+
+uiLibrary g_ui = {0};
diff --git a/src/uiLibrary.h b/src/uiLibrary.h
new file mode 100644
index 0000000..98719d7
--- /dev/null
+++ b/src/uiLibrary.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <ui.h>
+
+#include "constants.h"
+
+#include <font.h>
+
+typedef struct uiLibrary {
+  FontAtlas*         font;
+  uiMouseButtonState mouse_button_state[uiMouseButtonMax];
+  uiWidgetEvent      widget_events[MaxWidgetEvents];
+  int                num_widget_events;
+} uiLibrary;
+
+extern uiLibrary g_ui;
diff --git a/src/widget/button.c b/src/widget/button.c
new file mode 100644
index 0000000..f2313fd
--- /dev/null
+++ b/src/widget/button.c
@@ -0,0 +1,19 @@
+#include <ui.h>
+
+#include "widget.h"
+
+uiButton* uiMakeButton(const char* text) {
+  assert(text);
+
+  uiButton* button = UI_NEW(uiButton);
+
+  *button = (uiButton){
+      .widget =
+          (uiWidget){
+                     .type = uiTypeButton,
+                     .rect = {0},
+                     },
+      .text = string_new(text),
+  };
+  return button;
+}
diff --git a/src/widget/frame.c b/src/widget/frame.c
new file mode 100644
index 0000000..e1078be
--- /dev/null
+++ b/src/widget/frame.c
@@ -0,0 +1,19 @@
+#include <ui.h>
+
+#include "widget.h"
+
+uiFrame* uiMakeFrame(void) {
+  uiFrame* frame     = UI_NEW(uiFrame);
+  frame->widget.type = uiTypeFrame;
+  return frame;
+}
+
+void uiDestroyFrame(uiFrame** ppFrame) { DestroyWidget((uiWidget**)ppFrame); }
+
+uiSize uiGetFrameSize(const uiFrame* frame) {
+  assert(frame);
+  return (uiSize){
+      .width  = frame->widget.rect.width,
+      .height = frame->widget.rect.height,
+  };
+}
diff --git a/src/widget/label.c b/src/widget/label.c
new file mode 100644
index 0000000..30ca0ec
--- /dev/null
+++ b/src/widget/label.c
@@ -0,0 +1,28 @@
+#include <ui.h>
+
+#include "uiLibrary.h"
+#include "widget.h"
+
+uiLabel* uiMakeLabel(const char* text) {
+  assert(text);
+
+  uiLabel* label = UI_NEW(uiLabel);
+
+  *label = (uiLabel){
+      .widget =
+          (uiWidget){
+                     .type = uiTypeLabel,
+                     .rect =
+                  (uiRect){
+                      .width =
+                          (int)strlen(text) * g_ui.font->header.glyph_width,
+                      .height = g_ui.font->header.glyph_height}},
+      .text = string_new(text),
+  };
+  return label;
+}
+
+const char* uiLabelGetText(const uiLabel* label) {
+  assert(label);
+  return string_data(label->text);
+}
diff --git a/src/widget/table.c b/src/widget/table.c
new file mode 100644
index 0000000..7a0ea03
--- /dev/null
+++ b/src/widget/table.c
@@ -0,0 +1,103 @@
+#include "table.h"
+
+#include "widget.h"
+
+const uiCell* GetCell(const uiTable* table, int row, int col) {
+  assert(table);
+  return &table->cells[row][col];
+}
+
+uiCell* GetCellMut(uiTable* table, int row, int col) {
+  assert(table);
+  return (uiCell*)GetCell(table, row, col);
+}
+
+uiCell** GetLastRow(uiTable* table) {
+  assert(table);
+  assert(table->rows > 0);
+  return &table->cells[table->rows - 1];
+}
+
+uiTable* uiMakeTable(int rows, int cols, const char** header) {
+  uiTable* table = UI_NEW(uiTable);
+
+  *table = (uiTable){
+      .widget = (uiWidget){.type = uiTypeTable},
+      .rows   = rows,
+      .cols   = cols,
+      .widths = (cols > 0) ? calloc(cols, sizeof(int)) : 0,
+      .header = header ? calloc(cols, sizeof(uiCell)) : 0,
+      .cells  = (rows * cols > 0) ? calloc(rows, sizeof(uiCell*)) : 0,
+      .flags  = {0},
+  };
+
+  if (header) {
+    for (int col = 0; col < cols; ++col) {
+      table->header[col].child = (uiWidget*)uiMakeLabel(header[col]);
+    }
+  }
+
+  return table;
+}
+
+void uiTableClear(uiTable* table) {
+  assert(table);
+
+  // Free row data.
+  if (table->cells) {
+    for (int row = 0; row < table->rows; ++row) {
+      for (int col = 0; col < table->cols; ++col) {
+        DestroyWidget(&table->cells[row][col].child);
+      }
+      free(table->cells[row]);
+    }
+    free(table->cells);
+    table->cells = 0;
+  }
+  table->rows = 0;
+
+  // Clear row widths.
+  for (int i = 0; i < table->cols; ++i) {
+    table->widths[i] = 0;
+  }
+
+  table->offset = 0;
+
+  table->flags.vertical_overflow = 0;
+}
+
+void uiTableAddRow(uiTable* table, const char** row) {
+  assert(table);
+
+  table->rows++;
+
+  uiCell** cells = realloc(table->cells, table->rows * sizeof(uiCell*));
+  ASSERT(cells);
+  table->cells = cells;
+
+  uiCell** pLastRow = GetLastRow(table);
+  *pLastRow         = calloc(table->cols, sizeof(uiCell));
+  ASSERT(*pLastRow);
+  uiCell* lastRow = *pLastRow;
+
+  for (int col = 0; col < table->cols; ++col) {
+    lastRow[col].child = (uiWidget*)uiMakeLabel(row[col]);
+  }
+}
+
+void uiTableSet(uiTable* table, int row, int col, uiPtr child) {
+  assert(table);
+  assert(child.widget);
+
+  GetCellMut(table, row, col)->child = child.widget;
+}
+
+const uiWidget* uiTableGet(const uiTable* table, int row, int col) {
+  assert(table);
+  return GetCell(table, row, col)->child;
+}
+
+uiWidget* uiTableGetMut(uiTable* table, int row, int col) {
+  assert(table);
+  return GetCellMut(table, row, col)->child;
+}
diff --git a/src/widget/table.h b/src/widget/table.h
new file mode 100644
index 0000000..9f466de
--- /dev/null
+++ b/src/widget/table.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <ui.h>
+
+#include "widget.h"
+
+const uiCell* GetCell(const uiTable* table, int row, int col);
+
+uiCell* GetCellMut(uiTable* table, int row, int col);
+
+uiCell** GetLastRow(uiTable* table);
diff --git a/src/widget/widget.c b/src/widget/widget.c
new file mode 100644
index 0000000..ef79ac4
--- /dev/null
+++ b/src/widget/widget.c
@@ -0,0 +1,93 @@
+#include "widget.h"
+
+#include <cassert.h>
+
+// -----------------------------------------------------------------------------
+// Widget.
+
+#define UI_DEL(ppWidget)       \
+  {                            \
+    assert(ppWidget);          \
+    void* widget_ = *ppWidget; \
+    if (widget_) {             \
+      free(widget_);           \
+      *ppWidget = 0;           \
+    }                          \
+  }
+
+uiWidgetType uiWidgetGetType(const uiWidget* widget) {
+  assert(widget);
+  return widget->type;
+}
+
+void DestroyWidget(uiWidget** ppWidget) {
+  assert(ppWidget);
+
+  uiWidget* widget = *ppWidget;
+  if (widget) {
+    list_foreach_mut(widget->children, child, { DestroyWidget(&child); });
+  }
+  UI_DEL(ppWidget);
+}
+
+void uiWidgetSetParent(uiPtr child_, uiPtr parent_) {
+  uiWidget* child  = child_.widget;
+  uiWidget* parent = parent_.widget;
+
+  assert(child);
+  assert(parent);
+
+  list_add(parent->children, child);
+}
+
+// -----------------------------------------------------------------------------
+// Widget pointers.
+
+uiPtr uiMakeButtonPtr(uiButton* button) {
+  assert(button);
+  return (uiPtr){.type = uiTypeButton, .button = button};
+}
+
+uiPtr uiMakeFramePtr(uiFrame* frame) {
+  assert(frame);
+  return (uiPtr){.type = uiTypeFrame, .frame = frame};
+}
+
+uiPtr uiMakeLabelPtr(uiLabel* label) {
+  assert(label);
+  return (uiPtr){.type = uiTypeLabel, .label = label};
+}
+
+uiPtr uiMakeTablePtr(uiTable* table) {
+  assert(table);
+  return (uiPtr){.type = uiTypeTable, .table = table};
+}
+
+static uiPtr uiMakeWidgetPtr(uiWidget* widget) {
+  assert(widget);
+  return (uiPtr){.type = widget->type, .widget = widget};
+}
+
+uiButton* uiGetButtonPtr(uiPtr ptr) {
+  assert(ptr.type == uiTypeButton);
+  assert(ptr.button);
+  return ptr.button;
+}
+
+uiFrame* uiGetFramePtr(uiPtr ptr) {
+  assert(ptr.type == uiTypeFrame);
+  assert(ptr.frame);
+  return ptr.frame;
+}
+
+uiLabel* uiGetLabelPtr(uiPtr ptr) {
+  assert(ptr.type == uiTypeLabel);
+  assert(ptr.label);
+  return ptr.label;
+}
+
+uiTable* uiGetTablePtr(uiPtr ptr) {
+  assert(ptr.type == uiTypeTable);
+  assert(ptr.table);
+  return ptr.table;
+}
diff --git a/src/widget/widget.h b/src/widget/widget.h
new file mode 100644
index 0000000..a2c96bc
--- /dev/null
+++ b/src/widget/widget.h
@@ -0,0 +1,66 @@
+#pragma once
+
+#include <ui.h>
+
+#include <cstring.h>
+#include <list.h>
+
+#include <stdbool.h>
+
+DEF_LIST(Widget, uiWidget*)
+
+#define UI_NEW(TYPE) (TYPE*)uiAlloc(1, sizeof(TYPE))
+
+static inline void* uiAlloc(size_t count, size_t size) {
+  void* mem = calloc(count, size);
+  ASSERT(mem);
+  return mem;
+}
+
+// -----------------------------------------------------------------------------
+// Widgets.
+
+/// Base widget type.
+typedef struct uiWidget {
+  uiWidgetType type;
+  uiRect       rect;
+  Widget_list  children;
+} uiWidget;
+
+/// Button.
+typedef struct uiButton {
+  uiWidget widget;
+  string   text;
+} uiButton;
+
+/// Frame.
+typedef struct uiFrame {
+  uiWidget widget;
+} uiFrame;
+
+/// Label.
+typedef struct uiLabel {
+  uiWidget widget;
+  string   text;
+} uiLabel;
+
+/// Table cell.
+typedef struct uiCell {
+  uiWidget* child;
+} uiCell;
+
+/// Table.
+typedef struct uiTable {
+  uiWidget widget;
+  int      rows;
+  int      cols;
+  int*     widths; // Width, in pixels, for each column.
+  uiCell*  header; // If non-null, row of 'cols' header cells.
+  uiCell** cells;  // Array of 'rows' rows, each of 'cols' cells.
+  int      offset; // Offset into the rows of the table. Units: rows.
+  struct {
+    bool vertical_overflow : 1; // True if contents overflow vertically.
+  } flags;
+} uiTable;
+
+void DestroyWidget(uiWidget** ppWidget);
-- 
cgit v1.2.3