From 92978a10576d52a0f6c9983d3b6afae7c40eff40 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Thu, 12 Mar 2026 15:29:23 -0700 Subject: Support scrolling by dragging scrollbars --- src/input.c | 226 +++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 195 insertions(+), 31 deletions(-) (limited to 'src/input.c') diff --git a/src/input.c b/src/input.c index 4461c8b..6a7f4d0 100644 --- a/src/input.c +++ b/src/input.c @@ -6,9 +6,6 @@ #include -#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)) && @@ -35,17 +32,59 @@ static uiWidget* GetWidgetUnderMouse(uiWidget* parent, uiPoint mouse) { return 0; } -/// Get the table row at the given pixel position. +// ----------------------------------------------------------------------------- +// Scrollbar. + +/// Process a scrollbar mouse button event. +static void MouseButtonScrollbar( + uiScrollbar* scrollbar, const uiMouseButtonEvent* event) { + assert(scrollbar); + assert(event); + + if (event->button_state == uiMouseDown) { + UI_LOG("scroll start"); + // TODO: I don't quite like this global state, but I also don't want to + // store input state inside the widgets. Define a struct for input handling + // and pass it to these functions instead? + g_ui.scroll.scrollbar_handle_y_start = scrollbar->handle_y; + g_ui.scroll.scrolling = true; + } else if (event->button_state == uiMouseUp) { + UI_LOG("scroll end"); + g_ui.scroll.scrolling = false; + } +} + +/// Process a scrollbar mouse move event. +/// +/// Return the amount scrolled relative to the scroll starting position, in +/// pixels. +static int MouseMoveScrollbar( + uiScrollbar* scrollbar, const uiMouseMoveEvent* event) { + assert(scrollbar); + assert(event); + + const int delta = event->mouse_position.y - g_ui.mouse_down.start_point.y; + ScrollbarScroll(scrollbar, g_ui.scroll.scrollbar_handle_y_start + delta); + return delta; +} + +// ----------------------------------------------------------------------------- +// Table. + +/// Get the table row and column at the given pixel position, or whether the +/// scrollbar was hit. static void GetTableRowColAtXy( - const uiTable* table, uiPoint p, int* out_row, int* out_col) { + const uiTable* table, uiPoint p, int* out_row, int* out_col, + bool* out_scrollbar) { assert(table); assert(out_row); assert(out_col); const uiWidget* widget = (uiWidget*)table; - int col = -1; - int row = -1; + int col = -1; + int row = -1; + bool scrollbar = false; if (RectContains(widget->rect, p)) { int x = p.x - widget->rect.x; @@ -55,14 +94,40 @@ static void GetTableRowColAtXy( // 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)) { + // Scrollbar area check. + if ((col >= table->cols) && (row < table->rows)) { + col = -1; + scrollbar = true; + } + // Out of bounds. + else if ((col >= table->cols) || (row >= table->rows)) { col = row = -1; } } - *out_col = col; - *out_row = row; + *out_col = col; + *out_row = row; + *out_scrollbar = scrollbar; +} + +/// Process a table mouse button event. +static void MouseButtonTable(uiTable* table, const uiMouseButtonEvent* event) { + assert(table); + assert(event); + + if (event->button_state == uiMouseDown) { + int row, col; + bool scrollbar; + GetTableRowColAtXy(table, event->mouse_position, &row, &col, &scrollbar); + + if (scrollbar) { + MouseButtonScrollbar(&table->scrollbar, event); + } + } else if ((event->button_state == uiMouseUp) && g_ui.scroll.scrolling) { + // The mouse up event need not happen while the mouse is on the scrollbar, + // so process mouse-up regardless of whether the mouse is. + MouseButtonScrollbar(&table->scrollbar, event); + } } /// Process a table click event. @@ -70,8 +135,9 @@ static void ClickTable(uiTable* table, const uiMouseClickEvent* event) { assert(table); assert(event); - int row, col; - GetTableRowColAtXy(table, event->mouse_position, &row, &col); + int row, col; + bool scrollbar; + GetTableRowColAtXy(table, event->mouse_position, &row, &col, &scrollbar); if ((row != -1) && (col != -1)) { PushWidgetEvent(&(uiWidgetEvent){ @@ -86,14 +152,25 @@ static void ClickTable(uiTable* table, const uiMouseClickEvent* 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)); + + const int row = table->offset - event->scroll_offset; + uiTableScroll(table, row); } -/// Process a scroll event. -static bool ProcessScrollEvent( - uiWidget* widget, const uiMouseScrollEvent* event) { +/// Process a table mouse move event. +static void MouseMoveTable(uiTable* table, const uiMouseMoveEvent* event) { + assert(table); + assert(event); + + if (g_ui.scroll.scrolling) { + MouseMoveScrollbar(&table->scrollbar, event); + SyncTableToScrollbar(table); + } +} + +/// Process a mouse button event. +static bool ProcessMouseButtonEvent( + uiWidget* widget, const uiMouseButtonEvent* event) { assert(widget); assert(event); @@ -101,7 +178,7 @@ static bool ProcessScrollEvent( switch (widget->type) { case uiTypeTable: - ScrollTable((uiTable*)widget, event); + MouseButtonTable((uiTable*)widget, event); processed = true; break; default: @@ -112,7 +189,7 @@ static bool ProcessScrollEvent( } /// Process a click event. -static bool ProcessClickEvent( +static bool ProcessMouseClickEvent( uiWidget* widget, const uiMouseClickEvent* event) { assert(widget); assert(event); @@ -131,21 +208,89 @@ static bool ProcessClickEvent( return processed; } +/// Process a scroll event. +static bool ProcessMouseScrollEvent( + 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 mouse move event. +static bool ProcessMouseMoveEvent( + uiWidget* widget, const uiMouseMoveEvent* event) { + assert(widget); + assert(event); + + bool processed = false; + + switch (widget->type) { + case uiTypeTable: + MouseMoveTable((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; - + // TODO: processed != redraw. The client will redraw if this function + // returns + // true, but whether an event was processed does it imply that the UI needs + // a redraw. + // + // TODO: Also think about limiting redraws in xplorer to like 25fps or + // something in a "battery saving" mode of sorts. bool processed = false; switch (event->type) { case uiEventMouseButton: { const uiMouseButtonEvent* ev = &event->mouse_button; - uiMouseButtonState* prev_state = &g_ui.mouse_button_state[ev->button]; + // Update the mouse button state. + uiMouseButtonState* button_state = &g_ui.mouse_button_state[ev->button]; + const uiMouseButtonState prev_state = *button_state; + *button_state = ev->button_state; + + // If this is a mouse up event and a widget is currently being + // mouse-downed, send the event to that widget. + if ((ev->button_state == uiMouseUp) && + !uiIsNullptr(g_ui.mouse_down.widget)) { + processed = ProcessMouseButtonEvent(g_ui.mouse_down.widget.widget, ev); + g_ui.mouse_down = (uiMouseDownState){0}; + } else { // Mouse down or no widget. + uiWidget* target = + GetWidgetUnderMouse((uiWidget*)frame, ev->mouse_position); + if (target) { + processed = ProcessMouseButtonEvent(target, ev); + if (processed && (ev->button_state == uiMouseDown)) { + g_ui.mouse_down.widget = uiMakeWidgetPtr(target); + g_ui.mouse_down.start_point = ev->mouse_position; + } else { + g_ui.mouse_down = (uiMouseDownState){0}; + } + } + } - if ((*prev_state == uiMouseDown) && (ev->state == uiMouseUp)) { + if ((prev_state == uiMouseDown) && (ev->button_state == uiMouseUp)) { // Click. uiSendEvent( frame, @@ -155,26 +300,45 @@ bool uiSendEvent(uiFrame* frame, const uiInputEvent* event) { .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); + + uiWidget* target = + GetWidgetUnderMouse((uiWidget*)frame, ev->mouse_position); if (target) { - processed = ProcessClickEvent(target, ev); + processed = ProcessMouseClickEvent(target, ev); } break; } case uiEventMouseScroll: { const uiMouseScrollEvent* ev = &event->mouse_scroll; - uiWidget* target = GetWidgetUnderMouse(widget, ev->mouse_position); + + uiWidget* target = + GetWidgetUnderMouse((uiWidget*)frame, ev->mouse_position); if (target) { - processed = ProcessScrollEvent(target, ev); + processed = ProcessMouseScrollEvent(target, ev); } break; } + case uiEventMouseMove: { + const uiMouseMoveEvent* ev = &event->mouse_move; + + // If a widget is currently being moused-down, send the event to that + // widget. + if (!uiIsNullptr(g_ui.mouse_down.widget)) { + processed = ProcessMouseMoveEvent(g_ui.mouse_down.widget.widget, ev); + } else { + uiWidget* target = + GetWidgetUnderMouse((uiWidget*)frame, ev->mouse_position); + if (target) { + processed = ProcessMouseMoveEvent(target, ev); + } + } + + break; + } } return processed; -- cgit v1.2.3