diff --git a/src/shell-generic-container.c b/src/shell-generic-container.c index a3fc1b61c..382e79b7f 100644 --- a/src/shell-generic-container.c +++ b/src/shell-generic-container.c @@ -163,13 +163,13 @@ shell_generic_container_pick (ClutterActor *actor, } static GList * -shell_generic_container_get_focus_chain (StContainer *container) +shell_generic_container_get_focus_chain (StWidget *widget) { - ShellGenericContainer *self = SHELL_GENERIC_CONTAINER (container); + ShellGenericContainer *self = SHELL_GENERIC_CONTAINER (widget); GList *children, *focus_chain; focus_chain = NULL; - for (children = st_container_get_children_list (container); children; children = children->next) + for (children = st_container_get_children_list (ST_CONTAINER (widget)); children; children = children->next) { ClutterActor *child = children->data; @@ -252,7 +252,7 @@ shell_generic_container_class_init (ShellGenericContainerClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); - StContainerClass *container_class = ST_CONTAINER_CLASS (klass); + StWidgetClass *widget_class = ST_WIDGET_CLASS (klass); gobject_class->finalize = shell_generic_container_finalize; @@ -262,7 +262,7 @@ shell_generic_container_class_init (ShellGenericContainerClass *klass) actor_class->paint = shell_generic_container_paint; actor_class->pick = shell_generic_container_pick; - container_class->get_focus_chain = shell_generic_container_get_focus_chain; + widget_class->get_focus_chain = shell_generic_container_get_focus_chain; /** * ShellGenericContainer::get-preferred-width: diff --git a/src/st/st-container.c b/src/st/st-container.c index c2494079c..50a9a996e 100644 --- a/src/st/st-container.c +++ b/src/st/st-container.c @@ -183,8 +183,9 @@ st_container_get_children_list (StContainer *container) } static GList * -st_container_real_get_focus_chain (StContainer *container) +st_container_get_focus_chain (StWidget *widget) { + StContainer *container = ST_CONTAINER (widget); GList *chain, *children; chain = NULL; @@ -199,23 +200,6 @@ st_container_real_get_focus_chain (StContainer *container) return g_list_reverse (chain); } -/** - * st_container_get_focus_chain: - * @container: An #StContainer - * - * Gets a list of the focusable children of @container, in "Tab" - * order. By default, this returns all visible - * (as in CLUTTER_ACTOR_IS_VISIBLE()) children of @container. - * - * Returns: (element-type Clutter.Actor) (transfer container): - * @container's focusable children - */ -GList * -st_container_get_focus_chain (StContainer *container) -{ - return ST_CONTAINER_GET_CLASS (container)->get_focus_chain (container); -} - static gint sort_z_order (gconstpointer a, gconstpointer b) @@ -649,7 +633,7 @@ st_container_navigate_focus (StWidget *widget, * "first" being determined by @direction.) */ - children = st_container_get_focus_chain (container); + children = st_widget_get_focus_chain (ST_WIDGET (container)); if (direction == GTK_DIR_TAB_FORWARD || direction == GTK_DIR_TAB_BACKWARD) { @@ -752,7 +736,6 @@ st_container_class_init (StContainerClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); StWidgetClass *widget_class = ST_WIDGET_CLASS (klass); - StContainerClass *container_class = ST_CONTAINER_CLASS (klass); g_type_class_add_private (klass, sizeof (StContainerPrivate)); @@ -760,7 +743,6 @@ st_container_class_init (StContainerClass *klass) actor_class->get_paint_volume = st_container_get_paint_volume; + widget_class->get_focus_chain = st_container_get_focus_chain; widget_class->navigate_focus = st_container_navigate_focus; - - container_class->get_focus_chain = st_container_real_get_focus_chain; } diff --git a/src/st/st-container.h b/src/st/st-container.h index 023bef921..418ca431d 100644 --- a/src/st/st-container.h +++ b/src/st/st-container.h @@ -47,16 +47,12 @@ struct _StContainer { struct _StContainerClass { StWidgetClass parent_class; - - GList * (*get_focus_chain) (StContainer *container); }; GType st_container_get_type (void) G_GNUC_CONST; void st_container_destroy_children (StContainer *container); -GList * st_container_get_focus_chain (StContainer *container); - /* Only to be used by subclasses of StContainer */ void st_container_move_child (StContainer *container, ClutterActor *actor, diff --git a/src/st/st-widget.c b/src/st/st-widget.c index b8b61b0b4..2a8142709 100644 --- a/src/st/st-widget.c +++ b/src/st/st-widget.c @@ -677,6 +677,12 @@ st_widget_get_paint_volume (ClutterActor *self, return TRUE; } +static GList * +st_widget_real_get_focus_chain (StWidget *widget) +{ + return clutter_actor_get_children (CLUTTER_ACTOR (widget)); +} + static void st_widget_class_init (StWidgetClass *klass) @@ -711,6 +717,7 @@ st_widget_class_init (StWidgetClass *klass) klass->style_changed = st_widget_real_style_changed; klass->navigate_focus = st_widget_real_navigate_focus; klass->get_accessible_type = st_widget_accessible_get_type; + klass->get_focus_chain = st_widget_real_get_focus_chain; /** * StWidget:pseudo-class: @@ -1581,20 +1588,278 @@ st_widget_get_can_focus (StWidget *widget) return widget->priv->can_focus; } +/* filter @children to contain only only actors that overlap @rbox + * when moving in @direction. (Assuming no transformations.) + */ +static GList * +filter_by_position (GList *children, + ClutterActorBox *rbox, + GtkDirectionType direction) +{ + ClutterActorBox cbox; + GList *l, *ret; + ClutterActor *child; + + for (l = children, ret = NULL; l; l = l->next) + { + child = l->data; + clutter_actor_get_allocation_box (child, &cbox); + + /* Filter out children if they are in the wrong direction from + * @rbox, or if they don't overlap it. To account for floating- + * point imprecision, an actor is "down" (etc.) from an another + * actor even if it overlaps it by up to 0.1 pixels. + */ + switch (direction) + { + case GTK_DIR_UP: + if (cbox.y2 > rbox->y1 + 0.1) + continue; + if (cbox.x1 >= rbox->x2 || cbox.x2 <= rbox->x1) + continue; + break; + + case GTK_DIR_DOWN: + if (cbox.y1 < rbox->y2 - 0.1) + continue; + if (cbox.x1 >= rbox->x2 || cbox.x2 <= rbox->x1) + continue; + break; + + case GTK_DIR_LEFT: + if (cbox.x2 > rbox->x1 + 0.1) + continue; + if (cbox.y1 >= rbox->y2 || cbox.y2 <= rbox->y1) + continue; + break; + + case GTK_DIR_RIGHT: + if (cbox.x1 < rbox->x2 - 0.1) + continue; + if (cbox.y1 >= rbox->y2 || cbox.y2 <= rbox->y1) + continue; + break; + + default: + g_return_val_if_reached (NULL); + } + + ret = g_list_prepend (ret, child); + } + + g_list_free (children); + return ret; +} + + +typedef struct { + GtkDirectionType direction; + ClutterActorBox box; +} StWidgetChildSortData; + +static int +sort_by_position (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + ClutterActor *actor_a = (ClutterActor *)a; + ClutterActor *actor_b = (ClutterActor *)b; + StWidgetChildSortData *sort_data = user_data; + GtkDirectionType direction = sort_data->direction; + ClutterActorBox abox, bbox; + int ax, ay, bx, by; + int cmp, fmid; + + /* Determine the relationship, relative to motion in @direction, of + * the center points of the two actors. Eg, for %GTK_DIR_UP, we + * return a negative number if @actor_a's center is below @actor_b's + * center, and postive if vice versa, which will result in an + * overall list sorted bottom-to-top. + */ + + clutter_actor_get_allocation_box (actor_a, &abox); + ax = (int)(abox.x1 + abox.x2) / 2; + ay = (int)(abox.y1 + abox.y2) / 2; + clutter_actor_get_allocation_box (actor_b, &bbox); + bx = (int)(bbox.x1 + bbox.x2) / 2; + by = (int)(bbox.y1 + bbox.y2) / 2; + + switch (direction) + { + case GTK_DIR_UP: + cmp = by - ay; + break; + case GTK_DIR_DOWN: + cmp = ay - by; + break; + case GTK_DIR_LEFT: + cmp = bx - ax; + break; + case GTK_DIR_RIGHT: + cmp = ax - bx; + break; + default: + g_return_val_if_reached (0); + } + + if (cmp) + return cmp; + + /* If two actors have the same center on the axis being sorted, + * prefer the one that is closer to the center of the current focus + * actor on the other axis. Eg, for %GTK_DIR_UP, prefer whichever + * of @actor_a and @actor_b has a horizontal center closest to the + * current focus actor's horizontal center. + * + * (This matches GTK's behavior.) + */ + switch (direction) + { + case GTK_DIR_UP: + case GTK_DIR_DOWN: + fmid = (int)(sort_data->box.x1 + sort_data->box.x2) / 2; + return abs (ax - fmid) - abs (bx - fmid); + case GTK_DIR_LEFT: + case GTK_DIR_RIGHT: + fmid = (int)(sort_data->box.y1 + sort_data->box.y2) / 2; + return abs (ay - fmid) - abs (by - fmid); + default: + g_return_val_if_reached (0); + } +} + static gboolean st_widget_real_navigate_focus (StWidget *widget, ClutterActor *from, GtkDirectionType direction) { - if (widget->priv->can_focus && - CLUTTER_ACTOR (widget) != from) + ClutterActor *widget_actor, *focus_child; + GList *children, *l; + + widget_actor = CLUTTER_ACTOR (widget); + if (from == widget_actor) + return FALSE; + + /* Figure out if @from is a descendant of @widget, and if so, + * set @focus_child to the immediate child of @widget that + * contains (or *is*) @from. + */ + focus_child = from; + while (focus_child && clutter_actor_get_parent (focus_child) != widget_actor) + focus_child = clutter_actor_get_parent (focus_child); + + if (widget->priv->can_focus) { - clutter_actor_grab_key_focus (CLUTTER_ACTOR (widget)); - return TRUE; + if (!focus_child) + { + /* Accept focus from outside */ + clutter_actor_grab_key_focus (widget_actor); + return TRUE; + } + else + { + /* Yield focus from within: since @widget itself is + * focusable we don't allow the focus to be navigated + * within @widget. + */ + return FALSE; + } } + + /* See if we can navigate within @focus_child */ + if (focus_child && ST_IS_WIDGET (focus_child)) + { + if (st_widget_navigate_focus (ST_WIDGET (focus_child), from, direction, FALSE)) + return TRUE; + } + + /* At this point we know that we want to navigate focus to one of + * @widget's immediate children; the next one after @focus_child, + * or the first one if @focus_child is %NULL. (With "next" and + * "first" being determined by @direction.) + */ + + children = st_widget_get_focus_chain (widget); + if (direction == GTK_DIR_TAB_FORWARD || + direction == GTK_DIR_TAB_BACKWARD) + { + if (direction == GTK_DIR_TAB_BACKWARD) + children = g_list_reverse (children); + + if (focus_child) + { + /* Remove focus_child and any earlier children */ + while (children && children->data != focus_child) + children = g_list_delete_link (children, children); + if (children) + children = g_list_delete_link (children, children); + } + } + else /* direction is an arrow key, not tab */ + { + StWidgetChildSortData sort_data; + + /* Compute the allocation box of the previous focused actor, in + * @widget's coordinate space. If there was no previous focus, + * use the coordinates of the appropriate edge of @widget. + * + * Note that all of this code assumes the actors are not + * transformed (or at most, they are all scaled by the same + * amount). If @widget or any of its children is rotated, or + * any child is inconsistently scaled, then the focus chain will + * probably be unpredictable. + */ + if (focus_child) + { + clutter_actor_get_allocation_box (focus_child, &sort_data.box); + } + else + { + clutter_actor_get_allocation_box (CLUTTER_ACTOR (widget), &sort_data.box); + switch (direction) + { + case GTK_DIR_UP: + sort_data.box.y1 = sort_data.box.y2; + break; + case GTK_DIR_DOWN: + sort_data.box.y2 = sort_data.box.y1; + break; + case GTK_DIR_LEFT: + sort_data.box.x1 = sort_data.box.x2; + break; + case GTK_DIR_RIGHT: + sort_data.box.x2 = sort_data.box.x1; + break; + default: + g_warn_if_reached (); + } + } + sort_data.direction = direction; + + if (focus_child) + children = filter_by_position (children, &sort_data.box, direction); + if (children) + children = g_list_sort_with_data (children, sort_by_position, &sort_data); + } + + /* Now try each child in turn */ + for (l = children; l; l = l->next) + { + if (ST_IS_WIDGET (l->data)) + { + if (st_widget_navigate_focus (l->data, from, direction, FALSE)) + { + g_list_free (children); + return TRUE; + } + } + } + + g_list_free (children); return FALSE; } + /** * st_widget_navigate_focus: * @widget: the "top level" container @@ -2087,3 +2352,20 @@ check_labels (StWidgetAccessible *widget_accessible, ATK_OBJECT (widget_accessible)); } } + +/** + * st_widget_get_focus_chain: + * @widget: An #StWidget + * + * Gets a list of the focusable children of @widget, in "Tab" + * order. By default, this returns all visible + * (as in CLUTTER_ACTOR_IS_VISIBLE()) children of @widget. + * + * Returns: (element-type Clutter.Actor) (transfer container): + * @widget's focusable children + */ +GList * +st_widget_get_focus_chain (StWidget *widget) +{ + return ST_WIDGET_GET_CLASS (widget)->get_focus_chain (widget); +} diff --git a/src/st/st-widget.h b/src/st/st-widget.h index 81dc6c665..10becf885 100644 --- a/src/st/st-widget.h +++ b/src/st/st-widget.h @@ -86,6 +86,8 @@ struct _StWidgetClass ClutterActor *from, GtkDirectionType direction); GType (* get_accessible_type) (void); + + GList * (* get_focus_chain) (StWidget *widget); }; GType st_widget_get_type (void) G_GNUC_CONST; @@ -151,6 +153,9 @@ void st_widget_style_changed (StWidget *widg StThemeNode * st_widget_get_theme_node (StWidget *widget); StThemeNode * st_widget_peek_theme_node (StWidget *widget); +GList * st_widget_get_focus_chain (StWidget *widget); + + /* debug methods */ char *st_describe_actor (ClutterActor *actor); void st_set_slow_down_factor (gfloat factor);