summaryrefslogtreecommitdiff
path: root/src/input.c
blob: 20551a6c5bc09ebf476a02c8e2140c21c157702d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#include <ui.h>

#include "event.h"
#include "uiLibrary.h"
#include "widget/widget.h"

#include <cassert.h>

#define Min(a, b) ((a) < (b) ? (a) : (b))
#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 =
      Min(table->rows - table->num_visible_rows,
          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;
}