#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;

// -----------------------------------------------------------------------------
// Library.

uiLibrary g_ui = {0};

bool uiInit(void) {
  // TODO: Embed the font into the library instead.
  const char* font_path = "../ui/fontbaker/NK57.bin";
  if (!(g_ui.font = LoadFontAtlas(font_path))) {
    return false;
  }

  // TODO: Remove.
  const FontHeader* header     = &g_ui.font->header;
  const int         glyph_size = header->glyph_width * header->glyph_height;
  const int         atlas_size = header->num_glyphs * glyph_size;
  printf("Loaded font: %s\n", font_path);
  printf(
      "Glyph: %dx%d (%d bytes)\n", header->glyph_width, header->glyph_height,
      glyph_size);
  printf(
      "Atlas: %dx%d (%d bytes)\n", header->num_glyphs * header->glyph_width,
      header->glyph_height, atlas_size);

  return true;
}

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;
}