diff options
author | Emmanuele Bassi <ebassi@gnome.org> | 2013-09-04 14:27:17 +0100 |
---|---|---|
committer | Emmanuele Bassi <ebassi@gnome.org> | 2013-09-12 12:35:04 +0100 |
commit | d36338c48c350024694af457b76e9c01b4f66a73 (patch) | |
tree | c5a009208f7d437335531f00cc23ef1bd1741d9e /endless | |
parent | f419efd50898862a63c9f31a61115c5b6de79fec (diff) |
Add EosFlexyGrid
A layout manager for flexible grid layouts using the same algorithm of
the Discovery Center. The UI pattern is going to be used in the app
store and other native applications, so it makes sense to have this
widget in the SDK.
Diffstat (limited to 'endless')
-rw-r--r-- | endless/Makefile.am | 6 | ||||
-rw-r--r-- | endless/endless.h | 1 | ||||
-rw-r--r-- | endless/eosenums.h | 17 | ||||
-rw-r--r-- | endless/eosflexygrid-private.h | 21 | ||||
-rw-r--r-- | endless/eosflexygrid.c | 957 | ||||
-rw-r--r-- | endless/eosflexygrid.h | 105 | ||||
-rw-r--r-- | endless/eosflexygridcell.c | 204 |
7 files changed, 1309 insertions, 2 deletions
diff --git a/endless/Makefile.am b/endless/Makefile.am index 1a7f56f..2ad59bf 100644 --- a/endless/Makefile.am +++ b/endless/Makefile.am @@ -34,7 +34,8 @@ endless_private_installed_headers = \ endless/eossplashpagemanager.h \ endless/eostypes.h \ endless/eoswindow.h \ - endless/eosactionmenu-private.h + endless/eosactionmenu-private.h \ + endless/eosflexygrid.h endless_library_sources = \ endless/eosapplication.c \ @@ -47,7 +48,8 @@ endless_library_sources = \ endless/eostopbar.c endless/eostopbar-private.h \ endless/eosactionbutton.c \ endless/eosactionmenu.c \ - endless/eoswindow.c + endless/eoswindow.c \ + endless/eosflexygrid.c endless/eosflexygridcell.c endless/eosflexygrid-private.h # Endless GUI library lib_LTLIBRARIES = libendless-@EOS_SDK_API_VERSION@.la diff --git a/endless/endless.h b/endless/endless.h index afc30e7..a0d4cbd 100644 --- a/endless/endless.h +++ b/endless/endless.h @@ -14,6 +14,7 @@ G_BEGIN_DECLS #include "eostypes.h" #include "eosactionbutton.h" #include "eosapplication.h" +#include "eosflexygrid.h" #include "eospagemanager.h" #include "eossplashpagemanager.h" #include "eoswindow.h" diff --git a/endless/eosenums.h b/endless/eosenums.h index 824c99a..8d50f1b 100644 --- a/endless/eosenums.h +++ b/endless/eosenums.h @@ -7,6 +7,10 @@ #error "Please do not include this header file directly." #endif +#include <glib-object.h> + +G_BEGIN_DECLS + /* Shared typedefs for enumerations */ /* @@ -36,5 +40,18 @@ typedef enum EOS_ACTION_BUTTON_SIZE_NUM_SIZES } EosActionButtonSize; +#define EOS_TYPE_FLEXY_SHAPE (eos_flexy_shape_get_type ()) + +typedef enum +{ + EOS_FLEXY_SHAPE_SMALL, + EOS_FLEXY_SHAPE_MEDIUM_HORIZONTAL, + EOS_FLEXY_SHAPE_MEDIUM_VERTICAL, + EOS_FLEXY_SHAPE_LARGE +} EosFlexyShape; + +GType eos_flexy_shape_get_type (void) G_GNUC_CONST; + +G_END_DECLS #endif /* EOS_ENUMS_H */ diff --git a/endless/eosflexygrid-private.h b/endless/eosflexygrid-private.h new file mode 100644 index 0000000..b7eb448 --- /dev/null +++ b/endless/eosflexygrid-private.h @@ -0,0 +1,21 @@ +/* Copyright 2013 Endless Mobile, Inc. */ + +#ifndef EOS_FLEXY_GRID_PRIVATE_H +#define EOS_FLEXY_GRID_PRIVATE_H + +#include "eosflexygrid.h" + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +void eos_flexy_grid_cell_set_iter (EosFlexyGridCell *cell, + GSequenceIter *iter); +G_GNUC_INTERNAL +GSequenceIter * eos_flexy_grid_cell_get_iter (EosFlexyGridCell *cell); +G_GNUC_INTERNAL +void eos_flexy_grid_cell_set_selected (EosFlexyGridCell *cell, + gboolean selected); + +G_END_DECLS + +#endif /* EOS_FLEXY_GRID_PRIVATE_H */ diff --git a/endless/eosflexygrid.c b/endless/eosflexygrid.c new file mode 100644 index 0000000..87e1b81 --- /dev/null +++ b/endless/eosflexygrid.c @@ -0,0 +1,957 @@ +/* Copyright 2013 Endless Mobile, Inc. */ + +#include "config.h" + +#include "eosflexygrid-private.h" + +#include <glib-object.h> +#include <gtk/gtk.h> + +/*#define VERBOSE 1*/ + +#define DEFAULT_CELL_SIZE 180 +#define DEFAULT_SPACING 15 + +#ifdef VERBOSE +#define DEBUG(x) x +#define CELL_DEBUG_SIZE(_cells,_i) \ + g_print ("Cell[%u] = { .cell = %p, .x = %d, .y = %d, .width = %d, .height = %d }\n", \ + _i, \ + _cells[_i].cell, \ + _cells[_i].cell_x, _cells[_i].cell_y, _cells[_i].cell_width, _cells[_i].cell_height) +#else +#define DEBUG(x) +#define CELL_DEBUG_SIZE(cells,i) +#endif + +typedef struct { + GSequence *children; + + EosFlexyGridSortFunc sort_func; + gpointer sort_data; + GDestroyNotify sort_notify; + + GtkAdjustment *adjustment; + + int cell_size; + int cell_spacing; + + int n_visible_children; + + EosFlexyGridCell *prelight_cell; + EosFlexyGridCell *active_cell; + + guint in_widget : 1; +} EosFlexyGridPrivate; + +enum +{ + CELL_SELECTED, + CELL_ACTIVATED, + + LAST_SIGNAL +}; + +#if GLIB_CHECK_VERSION (2, 37, 5) + +# define EOS_FLEXY_GRID_GET_PRIV(obj) \ + ((EosFlexyGridPrivate *) eos_flexy_grid_get_instance_private ((EosFlexyGrid *) (obj))) + +G_DEFINE_TYPE_WITH_PRIVATE (EosFlexyGrid, eos_flexy_grid, GTK_TYPE_CONTAINER) + +#else + +# define EOS_FLEXY_GRID_GET_PRIV(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EOS_TYPE_FLEXY_GRID, EosFlexyGridPrivate)) + +G_DEFINE_TYPE (EosFlexyGrid, eos_flexy_grid, GTK_TYPE_CONTAINER) + +#endif /* GLIB_CHECK_VERSION (2, 37, 5) */ + +static guint grid_signals[LAST_SIGNAL] = { 0, }; + +static EosFlexyGridCell * +eos_flexy_grid_get_cell_at_coords (EosFlexyGrid *grid, + double x_pos, + double y_pos) +{ + EosFlexyGridPrivate *priv = grid->priv; + GSequenceIter *iter; + + /* naive hit detection */ + for (iter = g_sequence_get_begin_iter (priv->children); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GtkWidget *widget = g_sequence_get (iter); + GtkAllocation allocation; + + gtk_widget_get_allocation (widget, &allocation); + + if (x_pos >= allocation.x && x_pos < allocation.x + allocation.width && + y_pos >= allocation.y && y_pos < allocation.y + allocation.height) + return EOS_FLEXY_GRID_CELL (widget); + } + + return NULL; +} + +static void +eos_flexy_grid_update_cell_prelight (EosFlexyGrid *grid, + EosFlexyGridCell *cell) +{ + EosFlexyGridPrivate *priv = grid->priv; + + if (cell == priv->prelight_cell) + return; + + if (priv->prelight_cell != NULL) + { + gtk_widget_unset_state_flags (GTK_WIDGET (priv->prelight_cell), + GTK_STATE_FLAG_PRELIGHT); + eos_flexy_grid_cell_set_selected (priv->prelight_cell, FALSE); + } + + if (cell != NULL && gtk_widget_is_sensitive (GTK_WIDGET (cell))) + { + priv->prelight_cell = cell; + gtk_widget_set_state_flags (GTK_WIDGET (priv->prelight_cell), + GTK_STATE_FLAG_PRELIGHT, + FALSE); + eos_flexy_grid_cell_set_selected (cell, TRUE); + + g_signal_emit (grid, grid_signals[CELL_SELECTED], 0, cell); + } + else + priv->prelight_cell = NULL; + + gtk_widget_queue_draw (GTK_WIDGET (grid)); +} + +typedef struct _CellRequest +{ + EosFlexyGridCell *cell; + + int cell_x; + int cell_y; + int cell_width; + int cell_height; +} CellRequest; + +static inline void +reset_cells (CellRequest *cells, + guint n_cells) +{ + for (guint i = 0; i < n_cells; i++) + { + cells[i].cell = NULL; + cells[i].cell_x = 0; + cells[i].cell_y = 0; + cells[i].cell_width = 0; + cells[i].cell_height = 0; + } +} + +static inline guint +mark_cell (CellRequest *cells, + guint n_cells, + guint stride, + guint index_, + guint hspan, + guint vspan, + EosFlexyGridCell *cell) +{ + guint i; + + cells[index_].cell = cell; + + i = 0; + while (i++ < vspan - 1) + { + DEBUG (g_print ("Marking vspan cell %u\n", index_ + (stride * i))); + cells[index_ + (stride * i)].cell = cell; + } + + i = 0; + while (i++ < hspan - 1) + { + DEBUG (g_print ("Marking hspan cell %u\n", index_ + i)); + cells[index_ + i].cell = cell; + } + + guint next_slot = index_ + hspan; + while (next_slot < n_cells) + { + DEBUG (g_print ("next_slot: %u (index_: %u + hspan: %u)\n", next_slot, index_, hspan)); + if (cells[next_slot].cell == NULL) + break; + + next_slot += 1; + } + + return next_slot; +} + +static void +eos_flexy_grid_distribute_cell_request (CellRequest *cells, + guint n_cells, + guint n_columns, + EosFlexyGridCell *cell, + guint cur_index, + int cell_size, + guint *next_index) +{ + EosFlexyShape cell_shape = eos_flexy_grid_cell_get_shape (cell); + + switch (cell_shape) + { + case EOS_FLEXY_SHAPE_SMALL: + /* b1 */ + cells[cur_index].cell_width = cell_size; + cells[cur_index].cell_height = cell_size; + cells[cur_index].cell_x = cur_index % n_columns * cell_size; + cells[cur_index].cell_y = cur_index / n_columns * cell_size; + + *next_index = mark_cell (cells, n_cells, n_columns, + cur_index, + 1, 1, + cell); + CELL_DEBUG_SIZE (cells, cur_index); + break; + + case EOS_FLEXY_SHAPE_MEDIUM_HORIZONTAL: + /* b2h */ + cells[cur_index].cell_width = cell_size * 2; + cells[cur_index].cell_height = cell_size; + cells[cur_index].cell_x = cur_index % n_columns * cell_size; + cells[cur_index].cell_y = cur_index / n_columns * cell_size; + + *next_index = mark_cell (cells, n_cells, n_columns, + cur_index, + 2, 1, + cell); + CELL_DEBUG_SIZE (cells, cur_index); + break; + + case EOS_FLEXY_SHAPE_MEDIUM_VERTICAL: + /* b2v */ + cells[cur_index].cell_width = cell_size; + cells[cur_index].cell_height = cell_size * 2; + cells[cur_index].cell_x = cur_index % n_columns * cell_size; + cells[cur_index].cell_y = cur_index / n_columns * cell_size; + + *next_index = mark_cell (cells, n_cells, n_columns, + cur_index, + 1, 2, + cell); + CELL_DEBUG_SIZE (cells, cur_index); + break; + + case EOS_FLEXY_SHAPE_LARGE: + /* b4 */ + cells[cur_index].cell_width = cell_size * 2; + cells[cur_index].cell_height = cell_size * 2; + cells[cur_index].cell_x = cur_index % n_columns * cell_size; + cells[cur_index].cell_y = cur_index / n_columns * cell_size; + + *next_index = mark_cell (cells, n_cells, n_columns, + cur_index, + 2, 2, + cell); + CELL_DEBUG_SIZE (cells, cur_index); + break; + } +} + +static GtkSizeRequestMode +eos_flexy_grid_get_request_mode (GtkWidget *widget) +{ + return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; +} + +static void +eos_flexy_grid_get_preferred_width (GtkWidget *widget, + gint *minimum_width, + gint *natural_width) +{ + EosFlexyGridPrivate *priv = EOS_FLEXY_GRID (widget)->priv; + + int target_column_size = priv->cell_size < 0 ? DEFAULT_CELL_SIZE : priv->cell_size; + + /* minimum width: the biggest possible cell width */ + if (minimum_width != NULL) + *minimum_width = target_column_size * 2; + + int width = 0; + + /* natural width: the maximum width of all the cells on a single row */ + GSequenceIter *iter; + for (iter = g_sequence_get_begin_iter (priv->children); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + EosFlexyGridCell *cell = g_sequence_get (iter); + + if (!gtk_widget_get_visible (GTK_WIDGET (cell))) + continue; + + EosFlexyShape cell_shape = eos_flexy_grid_cell_get_shape (cell); + + switch (cell_shape) + { + case EOS_FLEXY_SHAPE_SMALL: + /* b1 */ + width += target_column_size; + break; + + case EOS_FLEXY_SHAPE_MEDIUM_HORIZONTAL: + /* b2h */ + width += target_column_size * 2; + break; + + case EOS_FLEXY_SHAPE_MEDIUM_VERTICAL: + /* b2v */ + width += target_column_size; + break; + + case EOS_FLEXY_SHAPE_LARGE: + /* b4 */ + width += target_column_size * 2; + break; + } + } + + if (natural_width != NULL) + *natural_width = width; +} + +static void +eos_flexy_grid_get_preferred_width_for_height (GtkWidget *widget, + gint for_height, + gint *minimum_width, + gint *natural_width) +{ + eos_flexy_grid_get_preferred_width (widget, minimum_width, natural_width); +} + +static void +eos_flexy_grid_get_preferred_height_for_width (GtkWidget *widget, + gint for_width, + gint *minimum_height, + gint *natural_height) +{ + EosFlexyGridPrivate *priv = EOS_FLEXY_GRID (widget)->priv; + + int cell_size = priv->cell_size < 0 ? DEFAULT_CELL_SIZE : priv->cell_size; + + /* minimum height: the maximum height of all the cells on a single row */ + if (minimum_height != NULL) + *minimum_height = cell_size * 2; + + int max_row_width = for_width; + int n_columns = MAX (max_row_width / cell_size, 2); + int row_width = 0, row_height = cell_size; + + int height = row_height; + guint row = 1; + + /* natural width: the maximum width of all the cells on a single row */ + GSequenceIter *iter; + for (iter = g_sequence_get_begin_iter (priv->children); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + EosFlexyGridCell *cell = g_sequence_get (iter); + int cell_height; + + if (!gtk_widget_get_visible (GTK_WIDGET (cell))) + continue; + + EosFlexyShape cell_shape = eos_flexy_grid_cell_get_shape (cell); + + switch (cell_shape) + { + case EOS_FLEXY_SHAPE_SMALL: + /* b1 */ + row_width += cell_size; + cell_height = cell_size; + break; + + case EOS_FLEXY_SHAPE_MEDIUM_HORIZONTAL: + /* b2h */ + row_width += cell_size * 2; + cell_height = cell_size; + break; + + case EOS_FLEXY_SHAPE_MEDIUM_VERTICAL: + /* b2v */ + row_width += cell_size; + cell_height = cell_size * 2; + break; + + case EOS_FLEXY_SHAPE_LARGE: + /* b4 */ + row_width += cell_size * 2; + cell_height = cell_size * 2; + break; + } + + if (row_width > max_row_width) + { + height = row * row_height; + + row += 1; + row_width = 0; + } + else + row_height = MAX (row_height, cell_height); + } + + if (natural_height != NULL) + *natural_height = MAX (height, cell_size); +} + +static void +eos_flexy_grid_get_preferred_height (GtkWidget *widget, + gint *minimum_height, + gint *natural_height) +{ + gint natural_width = 0; + + eos_flexy_grid_get_preferred_width (widget, NULL, &natural_width); + eos_flexy_grid_get_preferred_height_for_width (widget, natural_width, minimum_height, natural_height); +} + +static void +eos_flexy_grid_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + gtk_widget_set_allocation (widget, allocation); + + GdkWindow *window = gtk_widget_get_window (widget); + if (window != NULL) + gdk_window_move_resize (window, + allocation->x, + allocation->y, + allocation->width, + allocation->height); + + EosFlexyGridPrivate *priv = EOS_FLEXY_GRID (widget)->priv; + + int target_column_size = priv->cell_size < 0 ? DEFAULT_CELL_SIZE : priv->cell_size; + int available_width = allocation->width; + int available_height = allocation->height; + int cell_size = target_column_size; + int n_columns = MAX (available_width / (cell_size + priv->cell_spacing), 2); + int n_rows = MAX (available_height / (cell_size + priv->cell_spacing), 1); + + guint cur_index = 0, next_index = 0; + guint n_cells = n_columns * n_rows; + CellRequest *cells = g_newa (CellRequest, n_cells); + + reset_cells (cells, n_cells); + + DEBUG (g_print ("Size; %d x %d (cols: %u, rows: %u)\n", + available_width, available_height, + n_columns, + n_rows)); + + GSequenceIter *iter; + for (iter = g_sequence_get_begin_iter (priv->children); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + EosFlexyGridCell *cell = g_sequence_get (iter); + GtkAllocation child_allocation; + + if (!gtk_widget_get_visible (GTK_WIDGET (cell))) + continue; + + if (cur_index > n_cells) + break; + + eos_flexy_grid_distribute_cell_request (cells, n_cells, n_columns, + cell, + cur_index, + cell_size, + &next_index); + + child_allocation.x = cells[cur_index].cell_x + priv->cell_spacing; + child_allocation.y = cells[cur_index].cell_y + priv->cell_spacing; + child_allocation.width = cells[cur_index].cell_width - priv->cell_spacing; + child_allocation.height = cells[cur_index].cell_height - priv->cell_spacing; + + gtk_widget_size_allocate (GTK_WIDGET (cell), &child_allocation); + + DEBUG (g_print ("cur_index: %u, next_index: %u, column: %d, row: %d\n", + cur_index, next_index, + cur_index % n_columns, + cur_index / n_columns)); + + cur_index = next_index; + } +} + +static void +eos_flexy_grid_realize (GtkWidget *widget) +{ + GtkAllocation allocation; + gtk_widget_get_allocation (widget, &allocation); + gtk_widget_set_realized (widget, TRUE); + + GdkWindowAttr attributes = { 0, }; + attributes.x = allocation.x; + attributes.y = allocation.y; + attributes.width = allocation.width; + attributes.height = allocation.height; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.event_mask = gtk_widget_get_events (widget) + | GDK_ENTER_NOTIFY_MASK + | GDK_LEAVE_NOTIFY_MASK + | GDK_POINTER_MOTION_MASK + | GDK_EXPOSURE_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK; + attributes.wclass = GDK_INPUT_OUTPUT; + + GdkWindow *window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, + GDK_WA_X | GDK_WA_Y); + gtk_style_context_set_background (gtk_widget_get_style_context (widget), window); + gdk_window_set_user_data (window, (GObject *) widget); + gtk_widget_set_window (widget, window); /* Passes ownership */ +} + +static gboolean +eos_flexy_grid_draw (GtkWidget *widget, + cairo_t *cr) +{ + GtkAllocation allocation; + gtk_widget_get_allocation (widget, &allocation); + + GtkStyleContext *context; + context = gtk_widget_get_style_context (widget); + gtk_render_background (context, cr, 0, 0, allocation.width, allocation.height); + + GTK_WIDGET_CLASS (eos_flexy_grid_parent_class)->draw (widget, cr); + + return TRUE; +} + +static void +eos_flexy_grid_add (GtkContainer *container, + GtkWidget *child) +{ + eos_flexy_grid_insert (EOS_FLEXY_GRID (container), child, -1); +} + +static void +eos_flexy_grid_remove (GtkContainer *container, + GtkWidget *widget) +{ + if (!EOS_IS_FLEXY_GRID_CELL (widget)) + { + g_critical ("Trying to remove non-child %p", widget); + return; + } + + EosFlexyGridPrivate *priv = EOS_FLEXY_GRID (container)->priv; + + EosFlexyGridCell *cell = EOS_FLEXY_GRID_CELL (widget); + GSequenceIter *iter = eos_flexy_grid_cell_get_iter (cell); + if (iter == NULL || g_sequence_iter_get_sequence (iter) != priv->children) + { + g_critical ("The cell %p is not associated to the grid %p", cell, container); + return; + } + + gboolean was_visible = gtk_widget_get_visible (widget); + + if (cell == priv->prelight_cell) + { + gtk_widget_unset_state_flags (GTK_WIDGET (priv->prelight_cell), + GTK_STATE_FLAG_PRELIGHT); + priv->prelight_cell = NULL; + } + + if (cell == priv->active_cell) + { + gtk_widget_unset_state_flags (GTK_WIDGET (priv->active_cell), + GTK_STATE_FLAG_ACTIVE); + priv->active_cell = NULL; + } + + gtk_widget_unparent (widget); + g_sequence_remove (iter); + + if (was_visible && gtk_widget_get_visible (GTK_WIDGET (container))) + gtk_widget_queue_resize (GTK_WIDGET (container)); +} + +static void +eos_flexy_grid_forall (GtkContainer *container, + gboolean internals, + GtkCallback callback, + gpointer data) +{ + EosFlexyGridPrivate *priv = EOS_FLEXY_GRID (container)->priv; + + GSequenceIter *iter = g_sequence_get_begin_iter (priv->children); + while (!g_sequence_iter_is_end (iter)) + { + GtkWidget *child = g_sequence_get (iter); + iter = g_sequence_iter_next (iter); + + callback (child, data); + } +} + +static GType +eos_flexy_grid_child_type (GtkContainer *container) +{ + return EOS_TYPE_FLEXY_GRID_CELL; +} + +static gboolean +eos_flexy_grid_motion_notify (GtkWidget *widget, + GdkEventMotion *event) +{ + EosFlexyGrid *self = EOS_FLEXY_GRID (widget); + EosFlexyGridPrivate *priv = self->priv; + + GdkWindow *window = gtk_widget_get_window (widget); + GdkWindow *event_window = event->window; + + double relative_x = event->x; + double relative_y = event->y; + double parent_x, parent_y; + + while (event_window != NULL && event_window != window) + { + gdk_window_coords_to_parent (event_window, + relative_x, relative_y, + &parent_x, &parent_y); + relative_x = parent_x; + relative_y = parent_y; + event_window = gdk_window_get_effective_parent (event_window); + } + + EosFlexyGridCell *cell = eos_flexy_grid_get_cell_at_coords (self, relative_x, relative_y); + if (cell != NULL) + eos_flexy_grid_update_cell_prelight (self, cell); + + return GDK_EVENT_PROPAGATE; +} + +static gboolean +eos_flexy_grid_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + if (event->button != GDK_BUTTON_PRIMARY) + return GDK_EVENT_PROPAGATE; + + EosFlexyGrid *self = EOS_FLEXY_GRID (widget); + EosFlexyGridPrivate *priv = self->priv; + + priv->active_cell = NULL; + + GdkWindow *window = gtk_widget_get_window (widget); + GdkWindow *event_window = event->window; + + double relative_x = event->x; + double relative_y = event->y; + double parent_x, parent_y; + + while (event_window != NULL && event_window != window) + { + gdk_window_coords_to_parent (event_window, + relative_x, relative_y, + &parent_x, &parent_y); + relative_x = parent_x; + relative_y = parent_y; + event_window = gdk_window_get_effective_parent (event_window); + } + + EosFlexyGridCell *cell = eos_flexy_grid_get_cell_at_coords (self, relative_x, relative_y); + if (cell != NULL && gtk_widget_is_sensitive (GTK_WIDGET (cell))) + { + if (event->type == GDK_BUTTON_PRESS) + { + priv->active_cell = cell; + gtk_widget_set_state_flags (GTK_WIDGET (priv->active_cell), + GTK_STATE_FLAG_ACTIVE, + FALSE); + gtk_widget_queue_draw (widget); + } + } + + return GDK_EVENT_PROPAGATE; +} + +static gboolean +eos_flexy_grid_button_release (GtkWidget *widget, + GdkEventButton *event) +{ + /* the widget may be destroyed in response to this event */ + g_object_ref (widget); + + if (event->button == GDK_BUTTON_PRIMARY) + { + EosFlexyGrid *grid = EOS_FLEXY_GRID (widget); + EosFlexyGridPrivate *priv = grid->priv; + + if (priv->active_cell != NULL) + { + gtk_widget_unset_state_flags (GTK_WIDGET (priv->active_cell), + GTK_STATE_FLAG_ACTIVE); + g_signal_emit (grid, grid_signals[CELL_ACTIVATED], 0, priv->active_cell); + } + + priv->active_cell = NULL; + gtk_widget_queue_draw (widget); + } + + g_object_unref (widget); + + return GDK_EVENT_PROPAGATE; +} + +static gboolean +eos_flexy_grid_enter_notify (GtkWidget *widget, + GdkEventCrossing *event) +{ + if (event->window != gtk_widget_get_window (widget)) + return GDK_EVENT_PROPAGATE; + + EosFlexyGridPrivate *priv = EOS_FLEXY_GRID (widget)->priv; + + priv->in_widget = TRUE; + + EosFlexyGridCell *cell = eos_flexy_grid_get_cell_at_coords (EOS_FLEXY_GRID (widget), + event->x, + event->y); + eos_flexy_grid_update_cell_prelight (EOS_FLEXY_GRID (widget), cell); +} + +static gboolean +eos_flexy_grid_leave_notify (GtkWidget *widget, + GdkEventCrossing *event) +{ + if (event->window != gtk_widget_get_window (widget)) + return GDK_EVENT_PROPAGATE; + + EosFlexyGridPrivate *priv = EOS_FLEXY_GRID (widget)->priv; + EosFlexyGridCell *cell; + + if (event->detail != GDK_NOTIFY_INFERIOR) + { + priv->in_widget = FALSE; + cell = NULL; + } + else + cell = eos_flexy_grid_get_cell_at_coords (EOS_FLEXY_GRID (widget), + event->x, + event->y); + + eos_flexy_grid_update_cell_prelight (EOS_FLEXY_GRID (widget), cell); + + return GDK_EVENT_PROPAGATE; +} + +static void +eos_flexy_grid_finalize (GObject *gobject) +{ + EosFlexyGridPrivate *priv = EOS_FLEXY_GRID (gobject)->priv; + + if (priv->sort_notify != NULL) + priv->sort_notify (priv->sort_data); + + g_clear_object (&priv->adjustment); + + g_sequence_free (priv->children); + + G_OBJECT_CLASS (eos_flexy_grid_parent_class)->finalize (gobject); +} + +static void +eos_flexy_grid_class_init (EosFlexyGridClass *klass) +{ +#if !GLIB_CHECK_VERSION (2, 37, 5) + g_type_class_add_private (klass, sizeof (EosFlexyGridPrivate)); +#endif + + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = eos_flexy_grid_finalize; + + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + widget_class->get_request_mode = eos_flexy_grid_get_request_mode; + widget_class->get_preferred_width = eos_flexy_grid_get_preferred_width; + widget_class->get_preferred_height = eos_flexy_grid_get_preferred_height; + widget_class->get_preferred_width_for_height = eos_flexy_grid_get_preferred_width_for_height; + widget_class->get_preferred_height_for_width = eos_flexy_grid_get_preferred_height_for_width; + widget_class->size_allocate = eos_flexy_grid_size_allocate; + widget_class->draw = eos_flexy_grid_draw; + widget_class->realize = eos_flexy_grid_realize; + widget_class->motion_notify_event = eos_flexy_grid_motion_notify; + widget_class->button_press_event = eos_flexy_grid_button_press; + widget_class->button_release_event = eos_flexy_grid_button_release; + widget_class->leave_notify_event = eos_flexy_grid_leave_notify; + + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + container_class->add = eos_flexy_grid_add; + container_class->remove = eos_flexy_grid_remove; + container_class->forall = eos_flexy_grid_forall; + container_class->child_type = eos_flexy_grid_child_type; + + grid_signals[CELL_SELECTED] = + g_signal_new (g_intern_static_string ("cell-selected"), + EOS_TYPE_FLEXY_GRID, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EosFlexyGridClass, cell_selected), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + EOS_TYPE_FLEXY_GRID_CELL); + + grid_signals[CELL_ACTIVATED] = + g_signal_new (g_intern_static_string ("cell-activated"), + EOS_TYPE_FLEXY_GRID, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EosFlexyGridClass, cell_activated), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + EOS_TYPE_FLEXY_GRID_CELL); +} + +static void +eos_flexy_grid_init (EosFlexyGrid *self) +{ + EosFlexyGridPrivate *priv = EOS_FLEXY_GRID_GET_PRIV (self); + + priv->children = g_sequence_new (NULL); + + /* we use the same width as the discovery center layout */ + priv->cell_size = -1; + priv->cell_spacing = DEFAULT_SPACING; + + /* XXX: once we depend on GTK 3.10 and GLib 2.38, we should remove this */ + self->priv = priv; + + GtkWidget *widget = GTK_WIDGET (self); + gtk_widget_set_has_window (widget, TRUE); + gtk_widget_set_redraw_on_allocate (widget, TRUE); + + GtkStyleContext *context = gtk_widget_get_style_context (widget); + gtk_style_context_add_class (context, EOS_STYLE_CLASS_FLEXY_GRID); +} + +GtkWidget * +eos_flexy_grid_new (void) +{ + return g_object_new (EOS_TYPE_FLEXY_GRID, NULL); +} + +void +eos_flexy_grid_set_sort_func (EosFlexyGrid *grid, + EosFlexyGridSortFunc sort_func, + gpointer data, + GDestroyNotify notify) +{ + g_return_if_fail (EOS_IS_FLEXY_GRID (grid)); + + EosFlexyGridPrivate *priv = grid->priv; + if (priv->sort_notify != NULL) + priv->sort_notify (priv->sort_data); + + priv->sort_func = sort_func; + priv->sort_data = data; + priv->sort_notify = notify; +} + +void +eos_flexy_grid_set_cell_size (EosFlexyGrid *grid, + int size) +{ + g_return_if_fail (EOS_IS_FLEXY_GRID (grid)); + + EosFlexyGridPrivate *priv = grid->priv; + if (priv->cell_size == size) + return; + + priv->cell_size = size; + + gtk_widget_queue_resize (GTK_WIDGET (grid)); +} + +void +eos_flexy_grid_set_cell_spacing (EosFlexyGrid *grid, + int spacing) +{ + g_return_if_fail (EOS_IS_FLEXY_GRID (grid)); + + EosFlexyGridPrivate *priv = grid->priv; + if (priv->cell_spacing == spacing) + return; + + priv->cell_spacing = spacing; + + gtk_widget_queue_resize (GTK_WIDGET (grid)); +} + +static gint +do_grid_sort (gconstpointer row_a, + gconstpointer row_b, + gpointer data) +{ + EosFlexyGridPrivate *priv = EOS_FLEXY_GRID (data)->priv; + + return priv->sort_func ((EosFlexyGridCell *) row_a, + (EosFlexyGridCell *) row_b, + priv->sort_data); +} + +void +eos_flexy_grid_insert (EosFlexyGrid *grid, + GtkWidget *child, + gint index_) +{ + g_return_if_fail (EOS_IS_FLEXY_GRID (grid)); + g_return_if_fail (EOS_IS_FLEXY_GRID_CELL (child) || GTK_IS_WIDGET (child)); + + EosFlexyGridCell *cell; + + if (EOS_IS_FLEXY_GRID_CELL (child)) + cell = EOS_FLEXY_GRID_CELL (child); + else + { + cell = EOS_FLEXY_GRID_CELL (eos_flexy_grid_cell_new ()); + gtk_container_add (GTK_CONTAINER (cell), child); + gtk_widget_show (GTK_WIDGET (cell)); + } + + EosFlexyGridPrivate *priv = grid->priv; + GSequenceIter *iter; + + if (priv->sort_func != NULL) + { + iter = g_sequence_insert_sorted (priv->children, cell, + do_grid_sort, + grid); + } + else if (index_ == 0) + iter = g_sequence_prepend (priv->children, cell); + else if (index_ == -1) + iter = g_sequence_append (priv->children, cell); + else + { + GSequenceIter *cur_iter = g_sequence_get_iter_at_pos (priv->children, index_); + + iter = g_sequence_insert_before (cur_iter, cell); + } + + eos_flexy_grid_cell_set_iter (cell, iter); + + gtk_widget_set_parent (GTK_WIDGET (cell), GTK_WIDGET (grid)); + gtk_widget_set_child_visible (GTK_WIDGET (cell), TRUE); +} diff --git a/endless/eosflexygrid.h b/endless/eosflexygrid.h new file mode 100644 index 0000000..6739f09 --- /dev/null +++ b/endless/eosflexygrid.h @@ -0,0 +1,105 @@ +/* Copyright 2013 Endless Mobile, Inc. */ + +#ifndef EOS_FLEXY_GRID_H +#define EOS_FLEXY_GRID_H + +#include "eostypes.h" + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define EOS_TYPE_FLEXY_GRID (eos_flexy_grid_get_type ()) +#define EOS_FLEXY_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EOS_TYPE_FLEXY_GRID, EosFlexyGrid)) +#define EOS_IS_FLEXY_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EOS_TYPE_FLEXY_GRID)) +#define EOS_FLEXY_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EOS_TYPE_FLEXY_GRID, EosFlexyGridClass)) +#define EOS_IS_FLEXY_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EOS_TYPE_FLEXY_GRID)) +#define EOS_FLEXY_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EOS_TYPE_FLEXY_GRID, EosFlexyGridClass)) + +#define EOS_TYPE_FLEXY_GRID_CELL (eos_flexy_grid_cell_get_type ()) +#define EOS_FLEXY_GRID_CELL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EOS_TYPE_FLEXY_GRID_CELL, EosFlexyGridCell)) +#define EOS_IS_FLEXY_GRID_CELL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EOS_TYPE_FLEXY_GRID_CELL)) +#define EOS_FLEXY_GRID_CELL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EOS_TYPE_FLEXY_GRID_CELL, EosFlexyGridCellClass)) +#define EOS_IS_FLEXY_GRID_CELL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EOS_TYPE_FLEXY_GRID_CELL)) +#define EOS_FLEXY_GRID_CELL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EOS_TYPE_FLEXY_GRID_CELL, EosFlexyGridCellClass)) + +#define EOS_STYLE_CLASS_FLEXY_GRID "flexy-grid" +#define EOS_STYLE_CLASS_FLEXY_GRID_CELL "flexy-grid-cell" + +typedef struct _EosFlexyGrid EosFlexyGrid; +typedef struct _EosFlexyGridClass EosFlexyGridClass; + +typedef struct _EosFlexyGridCell EosFlexyGridCell; +typedef struct _EosFlexyGridCellClass EosFlexyGridCellClass; + +typedef gint (* EosFlexyGridSortFunc) (EosFlexyGridCell *cell_a, + EosFlexyGridCell *cell_b, + gpointer user_data); + +struct _EosFlexyGrid +{ + /*< private >*/ + GtkContainer parent_instance; + + gpointer priv; +}; + +struct _EosFlexyGridClass +{ + /*< private >*/ + GtkContainerClass parent_class; + + void (* cell_selected) (EosFlexyGrid *grid, + EosFlexyGridCell *cell); + void (* cell_activated) (EosFlexyGrid *grid, + EosFlexyGridCell *cell); + + gpointer _padding[8]; +}; + +EOS_SDK_ALL_API_VERSIONS +GType eos_flexy_grid_get_type (void) G_GNUC_CONST; + +EOS_SDK_ALL_API_VERSIONS +GtkWidget * eos_flexy_grid_new (void); +EOS_SDK_ALL_API_VERSIONS +void eos_flexy_grid_set_cell_size (EosFlexyGrid *grid, + int size); +EOS_SDK_ALL_API_VERSIONS +void eos_flexy_grid_set_cell_spacing (EosFlexyGrid *grid, + int spacing); +EOS_SDK_ALL_API_VERSIONS +void eos_flexy_grid_insert (EosFlexyGrid *grid, + GtkWidget *child, + int index_); + +struct _EosFlexyGridCell +{ + /*< private >*/ + GtkBin parent_instance; + + gpointer priv; +}; + +struct _EosFlexyGridCellClass +{ + /*< private >*/ + GtkBinClass parent_class; + + gpointer _padding[8]; +}; + +EOS_SDK_ALL_API_VERSIONS +GType eos_flexy_grid_cell_get_type (void) G_GNUC_CONST; + +EOS_SDK_ALL_API_VERSIONS +GtkWidget * eos_flexy_grid_cell_new (void); +EOS_SDK_ALL_API_VERSIONS +void eos_flexy_grid_cell_set_shape (EosFlexyGridCell *cell, + EosFlexyShape shape); +EOS_SDK_ALL_API_VERSIONS +EosFlexyShape eos_flexy_grid_cell_get_shape (EosFlexyGridCell *cell); + +G_END_DECLS + +#endif /* EOS_FLEXY_GRID_H */ diff --git a/endless/eosflexygridcell.c b/endless/eosflexygridcell.c new file mode 100644 index 0000000..4cc2627 --- /dev/null +++ b/endless/eosflexygridcell.c @@ -0,0 +1,204 @@ +/* Copyright 2013 Endless Mobile, Inc. */ + +#include "config.h" + +#include "eosflexygrid-private.h" + +#include <glib-object.h> +#include <gtk/gtk.h> + +EOS_DEFINE_ENUM_TYPE (EosFlexyShape, eos_flexy_shape, + EOS_ENUM_VALUE (EOS_FLEXY_SHAPE_SMALL, small) + EOS_ENUM_VALUE (EOS_FLEXY_SHAPE_MEDIUM_HORIZONTAL, medium-horizontal) + EOS_ENUM_VALUE (EOS_FLEXY_SHAPE_MEDIUM_VERTICAL, medium-vertical) + EOS_ENUM_VALUE (EOS_FLEXY_SHAPE_LARGE, large)) + +typedef struct { + EosFlexyShape shape; + + GSequenceIter *iter; + + guint selected : 1; +} EosFlexyGridCellPrivate; + +#if GLIB_CHECK_VERSION (2, 37, 5) + +# define EOS_FLEXY_GRID_CELL_GET_PRIV(obj) \ + ((EosFlexyGridCellPrivate *) eos_flexy_grid_cell_get_instance_private ((EosFlexyGridCell *) (obj))) + +G_DEFINE_TYPE_WITH_PRIVATE (EosFlexyGridCell, eos_flexy_grid_cell, GTK_TYPE_BIN) + +#else + +# define EOS_FLEXY_GRID_CELL_GET_PRIV(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EOS_TYPE_FLEXY_GRID_CELL, EosFlexyGridCellPrivate)) + +G_DEFINE_TYPE (EosFlexyGridCell, eos_flexy_grid_cell, GTK_TYPE_BIN) + +#endif /* GLIB_CHECK_VERSION (2, 37, 5) */ + +enum +{ + PROP_0, + + PROP_SHAPE, + + PROP_LAST +}; + +static GParamSpec *obj_props[PROP_LAST] = { NULL, }; + +static void +eos_flexy_grid_cell_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EosFlexyGridCell *self = EOS_FLEXY_GRID_CELL (gobject); + + switch (prop_id) + { + case PROP_SHAPE: + eos_flexy_grid_cell_set_shape (self, g_value_get_enum (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +} + +static void +eos_flexy_grid_cell_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EosFlexyGridCell *self = EOS_FLEXY_GRID_CELL (gobject); + EosFlexyGridCellPrivate *priv = self->priv; + + switch (prop_id) + { + case PROP_SHAPE: + g_value_set_enum (value, priv->shape); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +} + +static void +eos_flexy_grid_cell_class_init (EosFlexyGridCellClass *klass) +{ +#if !GLIB_CHECK_VERSION (2, 37, 6) + g_type_class_add_private (klass, sizeof (EosFlexyGridCellPrivate)); +#endif + + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + gobject_class->set_property = eos_flexy_grid_cell_set_property; + gobject_class->get_property = eos_flexy_grid_cell_get_property; + + obj_props[PROP_SHAPE] = + g_param_spec_enum ("shape", + "Shape", + "The shape of the cell", + EOS_TYPE_FLEXY_SHAPE, + EOS_FLEXY_SHAPE_SMALL, + G_PARAM_STATIC_STRINGS | + G_PARAM_READWRITE); + + g_object_class_install_properties (gobject_class, PROP_LAST, obj_props); +} + +static void +eos_flexy_grid_cell_init (EosFlexyGridCell *self) +{ + EosFlexyGridCellPrivate *priv = EOS_FLEXY_GRID_CELL_GET_PRIV (self); + + priv->shape = EOS_FLEXY_SHAPE_SMALL; + + self->priv = priv; + + GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (self)); + gtk_style_context_add_class (context, EOS_STYLE_CLASS_FLEXY_GRID_CELL); +} + +GtkWidget * +eos_flexy_grid_cell_new (void) +{ + return g_object_new (EOS_TYPE_FLEXY_GRID_CELL, NULL); +} + +void +eos_flexy_grid_cell_set_shape (EosFlexyGridCell *cell, + EosFlexyShape shape) +{ + EosFlexyGridCellPrivate *priv; + + g_return_if_fail (EOS_IS_FLEXY_GRID_CELL (cell)); + + priv = cell->priv; + if (priv->shape != shape) + { + priv->shape = shape; + + g_object_notify_by_pspec (G_OBJECT (cell), obj_props[PROP_SHAPE]); + } +} + +EosFlexyShape +eos_flexy_grid_cell_get_shape (EosFlexyGridCell *cell) +{ + EosFlexyGridCellPrivate *priv; + + g_return_val_if_fail (EOS_IS_FLEXY_GRID_CELL (cell), EOS_FLEXY_SHAPE_SMALL); + + priv = cell->priv; + + return priv->shape; +} + +/*< private >*/ +void +eos_flexy_grid_cell_set_iter (EosFlexyGridCell *cell, + GSequenceIter *iter) +{ + EosFlexyGridCellPrivate *priv = cell->priv; + + priv->iter = iter; +} + +/*< private >*/ +GSequenceIter * +eos_flexy_grid_cell_get_iter (EosFlexyGridCell *cell) +{ + EosFlexyGridCellPrivate *priv = cell->priv; + + return priv->iter; +} + +/*< private >*/ +void +eos_flexy_grid_cell_set_selected (EosFlexyGridCell *cell, + gboolean selected) +{ + g_return_if_fail (EOS_IS_FLEXY_GRID_CELL (cell)); + + EosFlexyGridCellPrivate *priv = cell->priv; + + selected = !!selected; + if (priv->selected != selected) + { + priv->selected = selected; + } +} + +gboolean +eos_flexy_grid_cell_get_selected (EosFlexyGridCell *cell) +{ + g_return_val_if_fail (EOS_IS_FLEXY_GRID_CELL (cell), FALSE); + + EosFlexyGridCellPrivate *priv = cell->priv; + + return priv->selected; +} |