diff --git a/src/gui/views/gcal-month-view-row.c b/src/gui/views/gcal-month-view-row.c index 41f96e16..edc9fef6 100644 --- a/src/gui/views/gcal-month-view-row.c +++ b/src/gui/views/gcal-month-view-row.c @@ -141,8 +141,8 @@ swap_focus_event_data (FocusEventData **dest, */ static FocusEventData * -create_focus_event_data_candidate (GcalMonthViewRow *self, - GtkWidget *child) +create_focus_event_data (GcalMonthViewRow *self, + GtkWidget *child) { graphene_rect_t rect; gboolean is_rtl; @@ -150,9 +150,6 @@ create_focus_event_data_candidate (GcalMonthViewRow *self, g_assert (GTK_IS_WIDGET (child)); - if (!GCAL_IS_EVENT_WIDGET (child) || !gtk_widget_get_child_visible (child)) - return NULL; - if (!gtk_widget_compute_bounds (child, GTK_WIDGET (self), &rect)) g_assert_not_reached (); @@ -162,6 +159,18 @@ create_focus_event_data_candidate (GcalMonthViewRow *self, return focus_event_data_new (child, &rect, focal_x); } +static FocusEventData * +create_focus_event_data_candidate (GcalMonthViewRow *self, + GtkWidget *child) +{ + g_assert (GTK_IS_WIDGET (child)); + + if (!GCAL_IS_EVENT_WIDGET (child) || !gtk_widget_get_child_visible (child)) + return NULL; + + return create_focus_event_data (self, child); +} + static GtkWidget * find_first_event_widget (GcalMonthViewRow *self) { @@ -234,6 +243,90 @@ find_last_event_widget (GcalMonthViewRow *self) GCAL_RETURN (nearest ? g_steal_pointer (&nearest->widget) : NULL); } +static GtkWidget * +find_nearest_horizontal_event_widget (GcalMonthViewRow *self, + FocusEventData *focused, + GtkDirectionType direction) +{ + g_autoptr (FocusEventData) candidate = NULL; + g_autoptr (FocusEventData) nearest = NULL; + GtkWidget *child; + gboolean forward_or_backward; + GtkDirectionType lateral; + gboolean is_rtl; + + GCAL_ENTRY; + + g_assert (focused != NULL); + + is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL; + forward_or_backward = direction == GTK_DIR_TAB_FORWARD || direction == GTK_DIR_TAB_BACKWARD; + + switch (direction) + { + case GTK_DIR_TAB_FORWARD: + lateral = is_rtl ? GTK_DIR_LEFT : GTK_DIR_RIGHT; + break; + + case GTK_DIR_TAB_BACKWARD: + lateral = is_rtl ? GTK_DIR_RIGHT : GTK_DIR_LEFT; + break; + + default: + g_assert (direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT); + lateral = direction; + break; + } + + for (child = gtk_widget_get_first_child (GTK_WIDGET (self)); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + { + g_autoptr (FocusEventData) aux = NULL; + gboolean candidate_intersects_vertically; + gboolean candidate_in_opposite_direction; + + if (!(aux = create_focus_event_data_candidate (self, child))) + continue; + + swap_focus_event_data (&candidate, g_steal_pointer (&aux)); + g_assert (candidate != NULL); + + if (candidate->widget == focused->widget) + continue; + + if (forward_or_backward && + ((lateral == GTK_DIR_LEFT && candidate->focal_x >= focused->focal_x) || + (lateral == GTK_DIR_RIGHT && candidate->focal_x <= focused->focal_x))) + continue; + + candidate_in_opposite_direction = ((lateral == GTK_DIR_LEFT && candidate->focal_x > focused->focal_x) || + (lateral == GTK_DIR_RIGHT && candidate->focal_x < focused->focal_x)); + + candidate_intersects_vertically = + graphene_rect_intersection (&GRAPHENE_RECT_INIT (0, focused->rect.origin.y, 1, focused->rect.size.height), + &GRAPHENE_RECT_INIT (0, candidate->rect.origin.y, 1, candidate->rect.size.height), + NULL); + + if (!forward_or_backward && (!candidate_intersects_vertically || candidate_in_opposite_direction)) + continue; + + if (nearest == NULL) + swap_focus_event_data (&nearest, g_steal_pointer (&candidate)); + else if ((lateral == GTK_DIR_LEFT && candidate->focal_x > nearest->focal_x) || + (lateral == GTK_DIR_RIGHT && candidate->focal_x < nearest->focal_x)) + swap_focus_event_data (&nearest, g_steal_pointer (&candidate)); + else if (candidate->focal_x == nearest->focal_x && + ((direction == GTK_DIR_TAB_FORWARD && candidate->rect.origin.y < nearest->rect.origin.y) || + (direction == GTK_DIR_TAB_BACKWARD && candidate->rect.origin.y > nearest->rect.origin.y))) + swap_focus_event_data (&nearest, g_steal_pointer (&candidate)); + } + + g_assert (nearest == NULL || nearest->widget != focused->widget); + + GCAL_RETURN (nearest ? g_steal_pointer (&nearest->widget) : NULL); +} + static void layout_block_free (gpointer data) { @@ -641,6 +734,32 @@ gcal_month_view_row_focus (GtkWidget *widget, break; } + if (GCAL_IS_EVENT_WIDGET (focused)) + { + g_autoptr (FocusEventData) focus_event_data = create_focus_event_data (self, focused); + + switch (direction) + { + case GTK_DIR_TAB_FORWARD: + case GTK_DIR_TAB_BACKWARD: + if ((new_focus = find_nearest_horizontal_event_widget (self, focus_event_data, direction))) + return gtk_widget_grab_focus (new_focus); + + return FALSE; + + case GTK_DIR_UP: + case GTK_DIR_DOWN: + return FALSE; + + case GTK_DIR_LEFT: + case GTK_DIR_RIGHT: + if ((new_focus = find_nearest_horizontal_event_widget (self, focus_event_data, direction))) + return gtk_widget_grab_focus (new_focus); + else + return FALSE; + } + } + return GTK_WIDGET_CLASS (gcal_month_view_row_parent_class)->focus (widget, direction); }