St: add keyboard focus navigation support
Add StWidget:can-focus, st_widget_navigate_focus(), and st_container_get_focus_chain(), and implement as needed to allow keyboard navigation of widgets. https://bugzilla.gnome.org/show_bug.cgi?id=621671
This commit is contained in:
parent
58001563d9
commit
d2b968a7df
@ -162,6 +162,25 @@ shell_generic_container_pick (ClutterActor *actor,
|
||||
}
|
||||
}
|
||||
|
||||
static GList *
|
||||
shell_generic_container_get_focus_chain (StContainer *container)
|
||||
{
|
||||
ShellGenericContainer *self = SHELL_GENERIC_CONTAINER (container);
|
||||
GList *children, *focus_chain;
|
||||
|
||||
focus_chain = NULL;
|
||||
for (children = st_container_get_children_list (container); children; children = children->next)
|
||||
{
|
||||
ClutterActor *child = children->data;
|
||||
|
||||
if (CLUTTER_ACTOR_IS_VISIBLE (child) &&
|
||||
!shell_generic_container_get_skip_paint (self, child))
|
||||
focus_chain = g_list_prepend (focus_chain, child);
|
||||
}
|
||||
|
||||
return g_list_reverse (focus_chain);
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_generic_container_get_n_skip_paint:
|
||||
* @self: A #ShellGenericContainer
|
||||
@ -231,6 +250,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);
|
||||
|
||||
gobject_class->finalize = shell_generic_container_finalize;
|
||||
|
||||
@ -240,6 +260,8 @@ 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;
|
||||
|
||||
shell_generic_container_signals[GET_PREFERRED_WIDTH] =
|
||||
g_signal_new ("get-preferred-width",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
|
@ -226,6 +226,28 @@ st_bin_dispose (GObject *gobject)
|
||||
G_OBJECT_CLASS (st_bin_parent_class)->dispose (gobject);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
st_bin_navigate_focus (StWidget *widget,
|
||||
ClutterActor *from,
|
||||
GtkDirectionType direction)
|
||||
{
|
||||
StBinPrivate *priv = ST_BIN (widget)->priv;
|
||||
ClutterActor *bin_actor = CLUTTER_ACTOR (widget);
|
||||
|
||||
if (st_widget_get_can_focus (widget))
|
||||
{
|
||||
if (from && clutter_actor_contains (bin_actor, from))
|
||||
return FALSE;
|
||||
|
||||
clutter_actor_grab_key_focus (bin_actor);
|
||||
return TRUE;
|
||||
}
|
||||
else if (priv->child && ST_IS_WIDGET (priv->child))
|
||||
return st_widget_navigate_focus (ST_WIDGET (priv->child), from, direction, FALSE);
|
||||
else
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
st_bin_set_property (GObject *gobject,
|
||||
guint prop_id,
|
||||
@ -309,6 +331,7 @@ st_bin_class_init (StBinClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
|
||||
StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
|
||||
GParamSpec *pspec;
|
||||
|
||||
g_type_class_add_private (klass, sizeof (StBinPrivate));
|
||||
@ -323,6 +346,8 @@ st_bin_class_init (StBinClass *klass)
|
||||
actor_class->paint = st_bin_paint;
|
||||
actor_class->pick = st_bin_pick;
|
||||
|
||||
widget_class->navigate_focus = st_bin_navigate_focus;
|
||||
|
||||
/**
|
||||
* StBin:child:
|
||||
*
|
||||
|
@ -28,6 +28,8 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "st-container.h"
|
||||
|
||||
#define ST_CONTAINER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),ST_TYPE_CONTAINER, StContainerPrivate))
|
||||
@ -178,6 +180,40 @@ st_container_get_children_list (StContainer *container)
|
||||
return container->priv->children;
|
||||
}
|
||||
|
||||
static GList *
|
||||
st_container_real_get_focus_chain (StContainer *container)
|
||||
{
|
||||
GList *chain, *children;
|
||||
|
||||
chain = NULL;
|
||||
for (children = container->priv->children; children; children = children->next)
|
||||
{
|
||||
ClutterActor *child = children->data;
|
||||
|
||||
if (CLUTTER_ACTOR_IS_VISIBLE (child))
|
||||
chain = g_list_prepend (chain, child);
|
||||
}
|
||||
|
||||
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)
|
||||
@ -389,6 +425,289 @@ st_container_dispose (GObject *object)
|
||||
G_OBJECT_CLASS (st_container_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
switch (direction)
|
||||
{
|
||||
case GTK_DIR_UP:
|
||||
if (cbox.y2 > rbox->y1)
|
||||
continue;
|
||||
if (cbox.x1 >= rbox->x2 || cbox.x2 <= rbox->x1)
|
||||
continue;
|
||||
break;
|
||||
|
||||
case GTK_DIR_DOWN:
|
||||
if (cbox.y1 < rbox->y2)
|
||||
continue;
|
||||
if (cbox.x1 >= rbox->x2 || cbox.x2 <= rbox->x1)
|
||||
continue;
|
||||
break;
|
||||
|
||||
case GTK_DIR_LEFT:
|
||||
if (cbox.x2 > rbox->x1)
|
||||
continue;
|
||||
if (cbox.y1 >= rbox->y2 || cbox.y2 <= rbox->y1)
|
||||
continue;
|
||||
break;
|
||||
|
||||
case GTK_DIR_RIGHT:
|
||||
if (cbox.x1 < rbox->x2)
|
||||
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;
|
||||
} StContainerChildSortData;
|
||||
|
||||
static int
|
||||
sort_by_position (gconstpointer a,
|
||||
gconstpointer b,
|
||||
gpointer user_data)
|
||||
{
|
||||
ClutterActor *actor_a = (ClutterActor *)a;
|
||||
ClutterActor *actor_b = (ClutterActor *)b;
|
||||
StContainerChildSortData *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_container_navigate_focus (StWidget *widget,
|
||||
ClutterActor *from,
|
||||
GtkDirectionType direction)
|
||||
{
|
||||
StContainer *container = ST_CONTAINER (widget);
|
||||
ClutterActor *container_actor, *focus_child;
|
||||
GList *children, *l;
|
||||
|
||||
container_actor = CLUTTER_ACTOR (widget);
|
||||
if (from == container_actor)
|
||||
return FALSE;
|
||||
|
||||
/* Figure out if @from is a descendant of @container, and if so,
|
||||
* set @focus_child to the immediate child of @container that
|
||||
* contains (or *is*) @from.
|
||||
*/
|
||||
focus_child = from;
|
||||
while (focus_child && clutter_actor_get_parent (focus_child) != container_actor)
|
||||
focus_child = clutter_actor_get_parent (focus_child);
|
||||
|
||||
if (st_widget_get_can_focus (widget))
|
||||
{
|
||||
if (!focus_child)
|
||||
{
|
||||
/* Accept focus from outside */
|
||||
clutter_actor_grab_key_focus (container_actor);
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Yield focus from within: since @container itself is
|
||||
* focusable we don't allow the focus to be navigated
|
||||
* within @container.
|
||||
*/
|
||||
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
|
||||
* @container'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_container_get_focus_chain (container);
|
||||
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 */
|
||||
{
|
||||
StContainerChildSortData sort_data;
|
||||
|
||||
/* Compute the allocation box of the previous focused actor, in
|
||||
* @container's coordinate space. If there was no previous focus,
|
||||
* use the coordinates of the appropriate edge of @container.
|
||||
*
|
||||
* Note that all of this code assumes the actors are not
|
||||
* transformed (or at most, they are all scaled by the same
|
||||
* amount). If @container or any of its children is rotated, or
|
||||
* any child is inconsistently scaled, then the focus chain will
|
||||
* probably be unpredictable.
|
||||
*/
|
||||
if (from)
|
||||
{
|
||||
if (from == focus_child)
|
||||
clutter_actor_get_allocation_box (focus_child, &sort_data.box);
|
||||
else
|
||||
{
|
||||
float cx, cy, fx, fy, fw, fh;
|
||||
|
||||
clutter_actor_get_transformed_position (CLUTTER_ACTOR (container), &cx, &cy);
|
||||
clutter_actor_get_transformed_position (from, &fx, &fy);
|
||||
clutter_actor_get_transformed_size (from, &fw, &fh);
|
||||
|
||||
sort_data.box.x1 = fx - cx;
|
||||
sort_data.box.x2 = fx - cx + fw;
|
||||
sort_data.box.y1 = fy - cy;
|
||||
sort_data.box.y2 = fy - cy + fh;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
clutter_actor_get_allocation_box (CLUTTER_ACTOR (container), &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;
|
||||
}
|
||||
|
||||
static void
|
||||
clutter_container_iface_init (ClutterContainerIface *iface)
|
||||
{
|
||||
@ -410,8 +729,14 @@ static void
|
||||
st_container_class_init (StContainerClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
|
||||
StContainerClass *container_class = ST_CONTAINER_CLASS (klass);
|
||||
|
||||
g_type_class_add_private (klass, sizeof (StContainerPrivate));
|
||||
|
||||
object_class->dispose = st_container_dispose;
|
||||
|
||||
widget_class->navigate_focus = st_container_navigate_focus;
|
||||
|
||||
container_class->get_focus_chain = st_container_real_get_focus_chain;
|
||||
}
|
||||
|
@ -50,6 +50,8 @@ struct _StContainer {
|
||||
|
||||
struct _StContainerClass {
|
||||
StWidgetClass parent_class;
|
||||
|
||||
GList * (*get_focus_chain) (StContainer *container);
|
||||
};
|
||||
|
||||
GType st_container_get_type (void) G_GNUC_CONST;
|
||||
@ -57,11 +59,13 @@ GType st_container_get_type (void) G_GNUC_CONST;
|
||||
void st_container_remove_all (StContainer *container);
|
||||
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,
|
||||
int pos);
|
||||
GList *st_container_get_children_list (StContainer *container);
|
||||
GList * st_container_get_children_list (StContainer *container);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
@ -219,6 +219,29 @@ st_entry_style_changed (StWidget *self)
|
||||
ST_WIDGET_CLASS (st_entry_parent_class)->style_changed (self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
st_entry_navigate_focus (StWidget *widget,
|
||||
ClutterActor *from,
|
||||
GtkDirectionType direction)
|
||||
{
|
||||
StEntryPrivate *priv = ST_ENTRY_PRIV (widget);
|
||||
|
||||
/* This is basically the same as st_widget_real_navigate_focus(),
|
||||
* except that widget is behaving as a proxy for priv->entry (which
|
||||
* isn't an StWidget and so has no can-focus flag of its own).
|
||||
*/
|
||||
|
||||
if (from == priv->entry)
|
||||
return FALSE;
|
||||
else if (st_widget_get_can_focus (widget))
|
||||
{
|
||||
clutter_actor_grab_key_focus (priv->entry);
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
st_entry_get_preferred_width (ClutterActor *actor,
|
||||
gfloat for_height,
|
||||
@ -632,6 +655,7 @@ st_entry_class_init (StEntryClass *klass)
|
||||
actor_class->leave_event = st_entry_leave_event;
|
||||
|
||||
widget_class->style_changed = st_entry_style_changed;
|
||||
widget_class->navigate_focus = st_entry_navigate_focus;
|
||||
|
||||
pspec = g_param_spec_object ("clutter-text",
|
||||
"Clutter Text",
|
||||
|
@ -314,27 +314,36 @@ st_overflow_box_allocate (ClutterActor *actor,
|
||||
}
|
||||
|
||||
static void
|
||||
st_overflow_box_internal_paint (StOverflowBox *box)
|
||||
visible_children_iter_init (StOverflowBox *box,
|
||||
GList **iter,
|
||||
int *n)
|
||||
{
|
||||
*iter = st_container_get_children_list (ST_CONTAINER (box));
|
||||
*n = 0;
|
||||
}
|
||||
|
||||
static ClutterActor *
|
||||
visible_children_iter (StOverflowBox *box,
|
||||
GList **iter,
|
||||
int *n)
|
||||
{
|
||||
StOverflowBoxPrivate *priv = box->priv;
|
||||
GList *l, *children;
|
||||
int i;
|
||||
GList *l;
|
||||
|
||||
i = 0;
|
||||
children = st_container_get_children_list (ST_CONTAINER (box));
|
||||
for (l = children; i < priv->n_visible && l; l = l->next)
|
||||
for (l = *iter; *n < priv->n_visible && l; l = l->next)
|
||||
{
|
||||
ClutterActor *child = (ClutterActor*) l->data;
|
||||
|
||||
if (!CLUTTER_ACTOR_IS_VISIBLE (child))
|
||||
continue;
|
||||
if (!clutter_actor_get_fixed_position_set (child))
|
||||
i++;
|
||||
(*n)++;
|
||||
|
||||
clutter_actor_paint (child);
|
||||
*iter = l->next;
|
||||
return child;
|
||||
}
|
||||
|
||||
for (;l; l = l->next)
|
||||
for (; l; l = l->next)
|
||||
{
|
||||
ClutterActor *child = (ClutterActor*) l->data;
|
||||
|
||||
@ -342,8 +351,25 @@ st_overflow_box_internal_paint (StOverflowBox *box)
|
||||
continue;
|
||||
|
||||
if (clutter_actor_get_fixed_position_set (child))
|
||||
clutter_actor_paint (child);
|
||||
{
|
||||
*iter = l->next;
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
st_overflow_box_internal_paint (StOverflowBox *box)
|
||||
{
|
||||
ClutterActor *child;
|
||||
GList *children;
|
||||
int n;
|
||||
|
||||
visible_children_iter_init (box, &children, &n);
|
||||
while ((child = visible_children_iter (box, &children, &n)))
|
||||
clutter_actor_paint (child);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -379,12 +405,29 @@ st_overflow_box_style_changed (StWidget *self)
|
||||
ST_WIDGET_CLASS (st_overflow_box_parent_class)->style_changed (self);
|
||||
}
|
||||
|
||||
static GList *
|
||||
st_overflow_box_get_focus_chain (StContainer *container)
|
||||
{
|
||||
StOverflowBox *box = ST_OVERFLOW_BOX (container);
|
||||
ClutterActor *child;
|
||||
GList *children, *focus_chain;
|
||||
int n;
|
||||
|
||||
focus_chain = NULL;
|
||||
visible_children_iter_init (box, &children, &n);
|
||||
while ((child = visible_children_iter (box, &children, &n)))
|
||||
focus_chain = g_list_prepend (focus_chain, child);
|
||||
|
||||
return g_list_reverse (focus_chain);
|
||||
}
|
||||
|
||||
static void
|
||||
st_overflow_box_class_init (StOverflowBoxClass *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);
|
||||
GParamSpec *pspec;
|
||||
|
||||
g_type_class_add_private (klass, sizeof (StOverflowBoxPrivate));
|
||||
@ -400,6 +443,8 @@ st_overflow_box_class_init (StOverflowBoxClass *klass)
|
||||
|
||||
widget_class->style_changed = st_overflow_box_style_changed;
|
||||
|
||||
container_class->get_focus_chain = st_overflow_box_get_focus_chain;
|
||||
|
||||
pspec = g_param_spec_uint ("min-children",
|
||||
"Min Children",
|
||||
"The actor will request a minimum size large enough to include this many children",
|
||||
|
@ -64,6 +64,7 @@ struct _StWidgetPrivate
|
||||
gboolean draw_border_internal : 1;
|
||||
gboolean track_hover : 1;
|
||||
gboolean hover : 1;
|
||||
gboolean can_focus : 1;
|
||||
|
||||
StTooltip *tooltip;
|
||||
|
||||
@ -93,7 +94,8 @@ enum
|
||||
PROP_HAS_TOOLTIP,
|
||||
PROP_TOOLTIP_TEXT,
|
||||
PROP_TRACK_HOVER,
|
||||
PROP_HOVER
|
||||
PROP_HOVER,
|
||||
PROP_CAN_FOCUS
|
||||
};
|
||||
|
||||
enum
|
||||
@ -113,6 +115,9 @@ G_DEFINE_ABSTRACT_TYPE (StWidget, st_widget, CLUTTER_TYPE_ACTOR);
|
||||
|
||||
static void st_widget_recompute_style (StWidget *widget,
|
||||
StThemeNode *old_theme_node);
|
||||
static gboolean st_widget_real_navigate_focus (StWidget *widget,
|
||||
ClutterActor *from,
|
||||
GtkDirectionType direction);
|
||||
|
||||
static void
|
||||
st_widget_set_property (GObject *gobject,
|
||||
@ -164,6 +169,10 @@ st_widget_set_property (GObject *gobject,
|
||||
st_widget_set_hover (actor, g_value_get_boolean (value));
|
||||
break;
|
||||
|
||||
case PROP_CAN_FOCUS:
|
||||
st_widget_set_can_focus (actor, g_value_get_boolean (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
||||
break;
|
||||
@ -217,6 +226,10 @@ st_widget_get_property (GObject *gobject,
|
||||
g_value_set_boolean (value, priv->hover);
|
||||
break;
|
||||
|
||||
case PROP_CAN_FOCUS:
|
||||
g_value_set_boolean (value, priv->can_focus);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
||||
break;
|
||||
@ -604,6 +617,22 @@ st_widget_leave (ClutterActor *actor,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
st_widget_key_focus_in (ClutterActor *actor)
|
||||
{
|
||||
StWidget *widget = ST_WIDGET (actor);
|
||||
|
||||
st_widget_add_style_pseudo_class (widget, "focus");
|
||||
}
|
||||
|
||||
static void
|
||||
st_widget_key_focus_out (ClutterActor *actor)
|
||||
{
|
||||
StWidget *widget = ST_WIDGET (actor);
|
||||
|
||||
st_widget_remove_style_pseudo_class (widget, "focus");
|
||||
}
|
||||
|
||||
static void
|
||||
st_widget_hide (ClutterActor *actor)
|
||||
{
|
||||
@ -642,9 +671,12 @@ st_widget_class_init (StWidgetClass *klass)
|
||||
|
||||
actor_class->enter_event = st_widget_enter;
|
||||
actor_class->leave_event = st_widget_leave;
|
||||
actor_class->key_focus_in = st_widget_key_focus_in;
|
||||
actor_class->key_focus_out = st_widget_key_focus_out;
|
||||
actor_class->hide = st_widget_hide;
|
||||
|
||||
klass->style_changed = st_widget_real_style_changed;
|
||||
klass->navigate_focus = st_widget_real_navigate_focus;
|
||||
|
||||
/**
|
||||
* StWidget:pseudo-class:
|
||||
@ -777,6 +809,20 @@ st_widget_class_init (StWidgetClass *klass)
|
||||
PROP_HOVER,
|
||||
pspec);
|
||||
|
||||
/**
|
||||
* StWidget:can-focus:
|
||||
*
|
||||
* Whether or not the widget can be focused via keyboard navigation.
|
||||
*/
|
||||
pspec = g_param_spec_boolean ("can-focus",
|
||||
"Can focus",
|
||||
"Whether the widget can be focused via keyboard navigation",
|
||||
FALSE,
|
||||
ST_PARAM_READWRITE);
|
||||
g_object_class_install_property (gobject_class,
|
||||
PROP_CAN_FOCUS,
|
||||
pspec);
|
||||
|
||||
/**
|
||||
* StWidget::style-changed:
|
||||
*
|
||||
@ -1624,6 +1670,113 @@ st_widget_get_hover (StWidget *widget)
|
||||
return widget->priv->hover;
|
||||
}
|
||||
|
||||
/**
|
||||
* st_widget_set_can_focus:
|
||||
* @widget: A #StWidget
|
||||
* @can_focus: %TRUE if the widget can receive keyboard focus
|
||||
* via keyboard navigation
|
||||
*
|
||||
* Marks @widget as being able to receive keyboard focus via
|
||||
* keyboard navigation.
|
||||
*/
|
||||
void
|
||||
st_widget_set_can_focus (StWidget *widget,
|
||||
gboolean can_focus)
|
||||
{
|
||||
StWidgetPrivate *priv;
|
||||
|
||||
g_return_if_fail (ST_IS_WIDGET (widget));
|
||||
|
||||
priv = widget->priv;
|
||||
|
||||
if (priv->can_focus != can_focus)
|
||||
{
|
||||
priv->can_focus = can_focus;
|
||||
g_object_notify (G_OBJECT (widget), "can-focus");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* st_widget_get_can_focus:
|
||||
* @widget: A #StWidget
|
||||
*
|
||||
* Returns the current value of the can-focus property. See
|
||||
* st_widget_set_can_focus() for more information.
|
||||
*
|
||||
* Returns: current value of can-focus on @widget
|
||||
*/
|
||||
gboolean
|
||||
st_widget_get_can_focus (StWidget *widget)
|
||||
{
|
||||
g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
|
||||
|
||||
return widget->priv->can_focus;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
st_widget_real_navigate_focus (StWidget *widget,
|
||||
ClutterActor *from,
|
||||
GtkDirectionType direction)
|
||||
{
|
||||
if (widget->priv->can_focus &&
|
||||
CLUTTER_ACTOR (widget) != from)
|
||||
{
|
||||
clutter_actor_grab_key_focus (CLUTTER_ACTOR (widget));
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* st_widget_navigate_focus:
|
||||
* @widget: the "top level" container
|
||||
* @from: (allow-none): the actor that the focus is coming from
|
||||
* @direction: the direction focus is moving in
|
||||
* @wrap_around: whether focus should wrap around
|
||||
*
|
||||
* Tries to update the keyboard focus within @widget in response to a
|
||||
* keyboard event.
|
||||
*
|
||||
* If @from is a descendant of @widget, this attempts to move the
|
||||
* keyboard focus to the next descendant of @widget (in the order
|
||||
* implied by @direction) that has the #StWidget:can-focus property
|
||||
* set. If @from is %NULL, or outside of @widget, this attempts to
|
||||
* focus either @widget itself, or its first descendant in the order
|
||||
* implied by @direction.
|
||||
*
|
||||
* If a container type is marked #StWidget:can-focus, the expected
|
||||
* behavior is that it will only take up a single slot on the focus
|
||||
* chain as a whole, rather than allowing navigation between its child
|
||||
* actors (or having a distinction between itself being focused and
|
||||
* one of its children being focused).
|
||||
*
|
||||
* Some widget classes might have slightly different behavior from the
|
||||
* above, where that would make more sense.
|
||||
*
|
||||
* If @wrap_around is %TRUE and @from is a child of @widget, but the
|
||||
* widget has no further children that can accept the focus in the
|
||||
* given direction, then st_widget_navigate_focus() will try a second
|
||||
* time, using a %NULL @from, which should cause it to reset the focus
|
||||
* to the first available widget in the given direction.
|
||||
*
|
||||
* Return value: %TRUE if clutter_actor_grab_key_focus() has been
|
||||
* called on an actor. %FALSE if not.
|
||||
*/
|
||||
gboolean
|
||||
st_widget_navigate_focus (StWidget *widget,
|
||||
ClutterActor *from,
|
||||
GtkDirectionType direction,
|
||||
gboolean wrap_around)
|
||||
{
|
||||
g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
|
||||
|
||||
if (ST_WIDGET_GET_CLASS (widget)->navigate_focus (widget, from, direction))
|
||||
return TRUE;
|
||||
if (wrap_around && from && clutter_actor_contains (CLUTTER_ACTOR (widget), from))
|
||||
return ST_WIDGET_GET_CLASS (widget)->navigate_focus (widget, NULL, direction);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
append_actor_text (GString *desc,
|
||||
ClutterActor *actor)
|
||||
|
@ -78,7 +78,10 @@ struct _StWidgetClass
|
||||
ClutterActorClass parent_class;
|
||||
|
||||
/* vfuncs */
|
||||
void (* style_changed) (StWidget *self);
|
||||
void (* style_changed) (StWidget *self);
|
||||
gboolean (* navigate_focus) (StWidget *self,
|
||||
ClutterActor *from,
|
||||
GtkDirectionType direction);
|
||||
};
|
||||
|
||||
GType st_widget_get_type (void) G_GNUC_CONST;
|
||||
@ -137,6 +140,14 @@ StTextDirection st_widget_get_direction (StWidget *self
|
||||
void st_widget_set_direction (StWidget *self,
|
||||
StTextDirection dir);
|
||||
|
||||
void st_widget_set_can_focus (StWidget *widget,
|
||||
gboolean can_focus);
|
||||
gboolean st_widget_get_can_focus (StWidget *widget);
|
||||
gboolean st_widget_navigate_focus (StWidget *widget,
|
||||
ClutterActor *from,
|
||||
GtkDirectionType direction,
|
||||
gboolean wrap_around);
|
||||
|
||||
/* Only to be used by sub-classes of StWidget */
|
||||
void st_widget_style_changed (StWidget *widget);
|
||||
StThemeNode * st_widget_get_theme_node (StWidget *widget);
|
||||
|
Loading…
Reference in New Issue
Block a user