#include #include "event.h" #include "uiLibrary.h" #include "widget/widget.h" #include /// 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; } // ----------------------------------------------------------------------------- // 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, bool* out_scrollbar) { assert(table); assert(out_row); assert(out_col); const uiWidget* widget = (uiWidget*)table; int col = -1; int row = -1; bool scrollbar = false; 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; // 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_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. static void ClickTable(uiTable* table, const uiMouseClickEvent* event) { assert(table); assert(event); int row, col; bool scrollbar; GetTableRowColAtXy(table, event->mouse_position, &row, &col, &scrollbar); 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); const int row = table->offset - event->scroll_offset; uiTableScroll(table, row); } /// 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); bool processed = false; switch (widget->type) { case uiTypeTable: MouseButtonTable((uiTable*)widget, event); processed = true; break; default: break; } return processed; } /// Process a click event. static bool ProcessMouseClickEvent( 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; } /// 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); // 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; // 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->button_state == uiMouseUp)) { // Click. uiSendEvent( frame, &(uiInputEvent){ .type = uiEventMouseClick, .mouse_click = (uiMouseClickEvent){ .button = ev->button, .mouse_position = ev->mouse_position} }); } break; } case uiEventMouseClick: { const uiMouseClickEvent* ev = &event->mouse_click; uiWidget* target = GetWidgetUnderMouse((uiWidget*)frame, ev->mouse_position); if (target) { processed = ProcessMouseClickEvent(target, ev); } break; } case uiEventMouseScroll: { const uiMouseScrollEvent* ev = &event->mouse_scroll; uiWidget* target = GetWidgetUnderMouse((uiWidget*)frame, ev->mouse_position); if (target) { 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; }