[gnome-software] Responsive scaling in application categories



commit a8e93c9caa8c481c7fe05f5979dbdd39587e4ddf
Author: Rafal Luzynski <digitalfreak lingonborough com>
Date:   Fri Jan 15 02:06:18 2016 +0100

    Responsive scaling in application categories
    
    Automatically switch between 2 and 3 columns depending on the
    window size. Choose 3 columns when there is enough space to allocate
    at least 270px for each application tile. Tile width limited to 420px.
    
    Implemented with GtkFlowBox, GsFixedSizeBin and some more hacks.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=758669
    
    Signed-off-by: Richard Hughes <richard hughsie com>

 src/app-tile.ui          |    2 +
 src/gs-app-tile.c        |   99 ++++++++++++++++++++++++++++++++++++++++++++++
 src/gs-shell-category.c  |   47 ++++++---------------
 src/gs-shell-category.ui |   34 ++++++++++------
 4 files changed, 136 insertions(+), 46 deletions(-)
---
diff --git a/src/app-tile.ui b/src/app-tile.ui
index 9914c49..dfbfbcc 100644
--- a/src/app-tile.ui
+++ b/src/app-tile.ui
@@ -4,6 +4,8 @@
   <template class="GsAppTile" parent="GtkButton">
     <property name="visible">True</property>
     <property name="hexpand">True</property>
+    <!-- This is the minimum (sic!) width of a tile when the GtkFlowBox parent container switches to 3 
columns -->
+    <property name="preferred-width">270</property>
     <style>
       <class name="view"/>
       <class name="tile"/>
diff --git a/src/gs-app-tile.c b/src/gs-app-tile.c
index faa45d7..9e01f76 100644
--- a/src/gs-app-tile.c
+++ b/src/gs-app-tile.c
@@ -39,10 +39,16 @@ struct _GsAppTile
        GtkWidget       *eventbox;
        GtkWidget       *stack;
        GtkWidget       *stars;
+       gint             preferred_width;
 };
 
 G_DEFINE_TYPE (GsAppTile, gs_app_tile, GTK_TYPE_BUTTON)
 
+enum {
+       PROP_0,
+       PROP_PREFERRED_WIDTH
+};
+
 GsApp *
 gs_app_tile_get_app (GsAppTile *tile)
 {
@@ -173,16 +179,109 @@ static void
 gs_app_tile_init (GsAppTile *tile)
 {
        gtk_widget_set_has_window (GTK_WIDGET (tile), FALSE);
+       tile->preferred_width = -1;
        gtk_widget_init_template (GTK_WIDGET (tile));
        gs_star_widget_set_icon_size (GS_STAR_WIDGET (tile->stars), 12);
 }
 
 static void
+gs_app_tile_get_property (GObject *object,
+                         guint prop_id,
+                         GValue *value,
+                         GParamSpec *pspec)
+{
+       GsAppTile *app_tile = GS_APP_TILE (object);
+
+       switch (prop_id) {
+       case PROP_PREFERRED_WIDTH:
+               g_value_set_int (value, app_tile->preferred_width);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+gs_app_tile_set_property (GObject *object,
+                         guint prop_id,
+                         const GValue *value,
+                         GParamSpec *pspec)
+{
+       GsAppTile *app_tile = GS_APP_TILE (object);
+
+       switch (prop_id) {
+       case PROP_PREFERRED_WIDTH:
+               app_tile->preferred_width = g_value_get_int (value);
+               gtk_widget_queue_resize (GTK_WIDGET (app_tile));
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+gs_app_get_preferred_width (GtkWidget *widget,
+                           gint *min, gint *nat)
+{
+#if GTK_CHECK_VERSION(3,20,0)
+       gint m;
+#else
+       gint m, n;
+#endif
+       GsAppTile *app_tile = GS_APP_TILE (widget);
+
+       if (app_tile->preferred_width < 0) {
+               /* Just retrieve the default values */
+               GTK_WIDGET_CLASS (gs_app_tile_parent_class)->get_preferred_width (widget, min, nat);
+               return;
+       }
+
+/* It's because of some bugs in gtkbutton.c 3.18 and before.
+ * We can remove this when we branch for 3.20 *and* require GTK 3.20. */
+#if GTK_CHECK_VERSION(3,20,0)
+       GTK_WIDGET_CLASS (gs_app_tile_parent_class)->get_preferred_width (widget, &m, NULL);
+#else
+       GTK_WIDGET_CLASS (gs_app_tile_parent_class)->get_preferred_width (widget, &m, &n);
+#endif
+
+       if (min != NULL)
+               *min = m;
+       if (nat != NULL)
+               *nat = MAX (m, app_tile->preferred_width);
+}
+
+static void
 gs_app_tile_class_init (GsAppTileClass *klass)
 {
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 
+       object_class->get_property = gs_app_tile_get_property;
+       object_class->set_property = gs_app_tile_set_property;
+
        widget_class->destroy = gs_app_tile_destroy;
+       widget_class->get_preferred_width = gs_app_get_preferred_width;
+
+       /**
+        * GsAppTile:preferred-width:
+        *
+        * The only purpose of this property is to be retrieved as the
+        * natural width by gtk_widget_get_preferred_width() fooling the
+        * parent #GtkFlowBox container and making it switch to more columns
+        * (children per row) if it is able to place n+1 children in a row
+        * having this specified width.  If this value is less than a minimum
+        * width of this app tile then the minimum is returned instead.  Set
+        * this property to -1 to turn off this feature and return the default
+        * natural width instead.
+        */
+       g_object_class_install_property (object_class, PROP_PREFERRED_WIDTH,
+               g_param_spec_int ("preferred-width",
+                                 "Preferred width",
+                                 "The preferred width of this widget, its only purpose is to trick the 
parent container",
+                                 -1, G_MAXINT, -1,
+                                 G_PARAM_READWRITE));
 
        gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/app-tile.ui");
 
diff --git a/src/gs-shell-category.c b/src/gs-shell-category.c
index 908bfd5..71ecb3b 100644
--- a/src/gs-shell-category.c
+++ b/src/gs-shell-category.c
@@ -38,10 +38,8 @@ struct _GsShellCategory
        GCancellable    *cancellable;
        GsShell         *shell;
        GsCategory      *category;
-       GtkWidget       *col0_placeholder;
-       GtkWidget       *col1_placeholder;
 
-       GtkWidget       *category_detail_grid;
+       GtkWidget       *category_detail_box;
        GtkWidget       *listbox_filter;
        GtkWidget       *scrolledwindow_category;
        GtkWidget       *scrolledwindow_filter;
@@ -95,8 +93,7 @@ gs_shell_category_get_apps_cb (GObject *source_object,
        g_autoptr(GsAppList) list = NULL;
 
        /* show an empty space for no results */
-       gtk_grid_remove_column (GTK_GRID (self->category_detail_grid), 1);
-       gtk_grid_remove_column (GTK_GRID (self->category_detail_grid), 0);
+       gs_container_remove_all (GTK_CONTAINER (self->category_detail_box));
 
        list = gs_plugin_loader_get_category_apps_finish (plugin_loader,
                                                          res,
@@ -112,18 +109,16 @@ gs_shell_category_get_apps_cb (GObject *source_object,
                tile = gs_app_tile_new (app);
                g_signal_connect (tile, "clicked",
                                  G_CALLBACK (app_tile_clicked), self);
-               gtk_grid_attach (GTK_GRID (self->category_detail_grid), tile, (i % 2), i / 2, 1, 1);
+               gtk_container_add (GTK_CONTAINER (self->category_detail_box), tile);
+               gtk_widget_set_can_focus (gtk_widget_get_parent (tile), FALSE);
        }
-
-       if (i == 1)
-               gtk_grid_attach (GTK_GRID (self->category_detail_grid), self->col1_placeholder, 1, 0, 1, 1);
 }
 
 static void
 gs_shell_category_populate_filtered (GsShellCategory *self, GsCategory *subcategory)
 {
        GtkWidget *tile;
-       guint i;
+       guint i, count;
 
        g_assert (subcategory != NULL);
 
@@ -137,17 +132,14 @@ gs_shell_category_populate_filtered (GsShellCategory *self, GsCategory *subcateg
                 gs_category_get_id (self->category),
                 gs_category_get_id (subcategory));
 
-       gtk_grid_remove_column (GTK_GRID (self->category_detail_grid), 1);
-       gtk_grid_remove_column (GTK_GRID (self->category_detail_grid), 0);
-
-       for (i = 0; i < MIN (30, gs_category_get_size (subcategory)); i++) {
+       gs_container_remove_all (GTK_CONTAINER (self->category_detail_box));
+       count = MIN(30, gs_category_get_size (subcategory));
+       for (i = 0; i < count; i++) {
                tile = gs_app_tile_new (NULL);
-               gtk_grid_attach (GTK_GRID (self->category_detail_grid), tile, (i % 2), i / 2, 1, 1);
+               gtk_container_add (GTK_CONTAINER (self->category_detail_box), tile);
+               gtk_widget_set_can_focus (gtk_widget_get_parent (tile), FALSE);
        }
 
-       gtk_grid_attach (GTK_GRID (self->category_detail_grid), self->col0_placeholder, 0, 0, 1, 1);
-       gtk_grid_attach (GTK_GRID (self->category_detail_grid), self->col1_placeholder, 1, 0, 1, 1);
-
        gs_plugin_loader_get_category_apps_async (self->plugin_loader,
                                                  subcategory,
                                                  GS_PLUGIN_REFINE_FLAGS_DEFAULT |
@@ -181,15 +173,12 @@ gs_shell_category_create_filter_list (GsShellCategory *self,
        GsCategory *s;
        g_autoptr(GList) list = NULL;
 
-       gs_container_remove_all (GTK_CONTAINER (self->category_detail_grid));
+       gs_container_remove_all (GTK_CONTAINER (self->category_detail_box));
 
        list = gs_category_get_subcategories (category);
        if (!list)
                return;
 
-       gtk_grid_attach (GTK_GRID (self->category_detail_grid), self->col0_placeholder, 0, 0, 1, 1);
-       gtk_grid_attach (GTK_GRID (self->category_detail_grid), self->col1_placeholder, 1, 0, 1, 1);
-
        gs_container_remove_all (GTK_CONTAINER (self->listbox_filter));
 
        for  (l = list; l; l = l->next) {
@@ -250,12 +239,6 @@ static void
 gs_shell_category_init (GsShellCategory *self)
 {
        gtk_widget_init_template (GTK_WIDGET (self));
-
-       self->col0_placeholder = g_object_ref_sink (gtk_label_new (""));
-       self->col1_placeholder = g_object_ref_sink (gtk_label_new (""));
-
-       gtk_widget_show (self->col0_placeholder);
-       gtk_widget_show (self->col1_placeholder);
 }
 
 static void
@@ -271,8 +254,6 @@ gs_shell_category_dispose (GObject *object)
        g_clear_object (&self->builder);
        g_clear_object (&self->category);
        g_clear_object (&self->plugin_loader);
-       g_clear_object (&self->col0_placeholder);
-       g_clear_object (&self->col1_placeholder);
 
        G_OBJECT_CLASS (gs_shell_category_parent_class)->dispose (object);
 }
@@ -287,7 +268,7 @@ gs_shell_category_class_init (GsShellCategoryClass *klass)
 
        gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/Software/gs-shell-category.ui");
 
-       gtk_widget_class_bind_template_child (widget_class, GsShellCategory, category_detail_grid);
+       gtk_widget_class_bind_template_child (widget_class, GsShellCategory, category_detail_box);
        gtk_widget_class_bind_template_child (widget_class, GsShellCategory, listbox_filter);
        gtk_widget_class_bind_template_child (widget_class, GsShellCategory, scrolledwindow_category);
        gtk_widget_class_bind_template_child (widget_class, GsShellCategory, scrolledwindow_filter);
@@ -312,7 +293,7 @@ key_event (GtkWidget *listbox, GdkEvent *event, GsShellCategory *self)
                                       GTK_SCROLL_PAGE_DOWN, FALSE, &handled);
        else if (keyval == GDK_KEY_Tab ||
                 keyval == GDK_KEY_KP_Tab)
-               gtk_widget_child_focus (self->category_detail_grid, GTK_DIR_TAB_FORWARD);
+               gtk_widget_child_focus (self->category_detail_box, GTK_DIR_TAB_FORWARD);
        else
                return FALSE;
 
@@ -335,7 +316,7 @@ gs_shell_category_setup (GsShellCategory *self,
        g_signal_connect (self->listbox_filter, "row-selected", G_CALLBACK (filter_selected), self);
 
        adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (self->scrolledwindow_category));
-       gtk_container_set_focus_vadjustment (GTK_CONTAINER (self->category_detail_grid), adj);
+       gtk_container_set_focus_vadjustment (GTK_CONTAINER (self->category_detail_box), adj);
 
        g_signal_connect (self->listbox_filter, "key-press-event",
                          G_CALLBACK (key_event), self);
diff --git a/src/gs-shell-category.ui b/src/gs-shell-category.ui
index 9263601..67c96a1 100644
--- a/src/gs-shell-category.ui
+++ b/src/gs-shell-category.ui
@@ -46,20 +46,28 @@
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
                 <child>
-                  <object class="GtkGrid" id="category_detail_grid">
-                    <property name="margin_start">27</property>
-                    <property name="margin_end">27</property>
-                    <property name="margin_top">24</property>
-                    <property name="margin_bottom">24</property>
-                    <property name="halign">fill</property>
+                  <object class="GsFixedSizeBin" id="gs_fixed_bin">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="row_spacing">12</property>
-                    <property name="column_spacing">12</property>
-                    <property name="row_homogeneous">True</property>
-                    <property name="column_homogeneous">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="valign">start</property>
+                    <!-- This is 3*420 plus margins, paddings, CSS borders -->
+                    <property name="preferred-width">1338</property>
+                    <child>
+                      <object class="GtkFlowBox" id="category_detail_box">
+                        <property name="margin_start">24</property>
+                        <property name="margin_end">24</property>
+                        <property name="margin_top">21</property>
+                        <property name="margin_bottom">21</property>
+                        <property name="halign">fill</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="row_spacing">6</property>
+                        <property name="column_spacing">6</property>
+                        <property name="homogeneous">True</property>
+                        <property name="hexpand">True</property>
+                        <property name="valign">start</property>
+                        <property name="min-children-per-line">2</property>
+                        <property name="max-children-per-line">3</property>
+                      </object>
+                    </child>
                   </object>
                 </child>
               </object>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]