[gnome-software/1205-reviews-ratings-are-rounded-to-integer-stars: 78/79] Reviews: Ratings are rounded to integer stars




commit 69df42088fc8c327fbdd8fa8d244f684e3fa99f8
Author: Milan Crha <mcrha redhat com>
Date:   Tue May 25 12:29:42 2021 +0200

    Reviews: Ratings are rounded to integer stars
    
    Introduce and use a new GsStarImage widget, which can show partial ratings.
    
    Closes https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1205

 src/gs-details-page.ui |   1 +
 src/gs-star-image.c    | 238 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/gs-star-image.h    |  22 +++++
 src/gs-star-widget.c   |  34 +++----
 src/gtk-style-hc.css   |   9 +-
 src/gtk-style.css      |   8 +-
 src/meson.build        |   1 +
 7 files changed, 288 insertions(+), 25 deletions(-)
---
diff --git a/src/gs-details-page.ui b/src/gs-details-page.ui
index bafb11d15..b30ea9f04 100644
--- a/src/gs-details-page.ui
+++ b/src/gs-details-page.ui
@@ -144,6 +144,7 @@
                                         <property name="visible">True</property>
                                         <property name="halign">start</property>
                                         <property name="valign">center</property>
+                                        <property name="icon-size">16</property>
                                       </object>
                                     </child>
                                   </object>
diff --git a/src/gs-star-image.c b/src/gs-star-image.c
new file mode 100644
index 000000000..3b61c79a7
--- /dev/null
+++ b/src/gs-star-image.c
@@ -0,0 +1,238 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+/**
+ * SECTION:gs-star-image
+ * @title: GsStarImage
+ * @stability: Unstable
+ * @short_description: Draw a star image, which can be partially filled
+ *
+ * Depending on the #GsStarImage:fraction property, the star image can be
+ * drawn as filled only partially or fully or not at all. This is accomplished
+ * by using a `color` style property for the filled part and a
+ * `star-bg` style property for the unfilled part of the star.
+ * The `background` style property controls the area outside the star.
+ *
+ * Since: 41
+ */
+
+#include "config.h"
+
+#include "gs-star-image.h"
+
+struct _GsStarImage
+{
+       GtkWidget parent_instance;
+
+       gdouble fraction;
+};
+
+G_DEFINE_TYPE (GsStarImage, gs_star_image, GTK_TYPE_WIDGET)
+
+enum {
+       PROP_FRACTION = 1
+};
+
+static void
+gs_star_image_outline_star (cairo_t *cr,
+                           gint x,
+                           gint y,
+                           gint radius,
+                           gint *out_min_x,
+                           gint *out_max_x)
+{
+       /* Coordinates of the vertices of the star,
+        * where (0, 0) is the centre of the star.
+        * These range from -1 to +1 in both dimensions,
+        * and will be scaled to @radius when drawn. */
+       const struct _points {
+               gdouble x, y;
+       } points[] = {
+               {  0.000000, -1.000000 },
+               { -1.000035, -0.424931 },
+               { -0.668055,  0.850680 },
+               {  0.668055,  0.850680 },
+               {  1.000035, -0.424931 }
+       };
+       gint ii, nn = G_N_ELEMENTS (points), xx, yy;
+
+       if (radius <= 0)
+               return;
+
+       cairo_translate (cr, radius, radius);
+
+       xx = points[0].x * radius;
+       yy = points[0].y * radius;
+
+       if (out_min_x)
+               *out_min_x = xx;
+
+       if (out_max_x)
+               *out_max_x = xx;
+
+       cairo_move_to (cr, xx, yy);
+
+       for (ii = 2; ii <= 2 * nn; ii += 2) {
+               xx = points[ii % nn].x * radius;
+               yy = points[ii % nn].y * radius;
+
+               if (out_min_x && *out_min_x > xx)
+                       *out_min_x = xx;
+
+               if (out_max_x && *out_max_x < xx)
+                       *out_max_x = xx;
+
+               cairo_line_to (cr, xx, yy);
+       }
+}
+
+static void
+gs_star_image_get_property (GObject *object,
+                           guint param_id,
+                           GValue *value,
+                           GParamSpec *pspec)
+{
+       switch (param_id) {
+       case PROP_FRACTION:
+               g_value_set_double (value, gs_star_image_get_fraction (GS_STAR_IMAGE (object)));
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               break;
+       }
+}
+
+static void
+gs_star_image_set_property (GObject *object,
+                           guint param_id,
+                           const GValue *value,
+                           GParamSpec *pspec)
+{
+       switch (param_id) {
+       case PROP_FRACTION:
+               gs_star_image_set_fraction (GS_STAR_IMAGE (object), g_value_get_double (value));
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               break;
+       }
+}
+
+static gboolean
+gs_star_image_draw (GtkWidget *widget,
+                   cairo_t *cr)
+{
+       GtkAllocation allocation;
+       gdouble fraction;
+       gint radius;
+
+       fraction = gs_star_image_get_fraction (GS_STAR_IMAGE (widget));
+
+       gtk_widget_get_allocation (widget, &allocation);
+
+       radius = MIN (allocation.width, allocation.height) / 2;
+
+       if (radius > 0) {
+               GtkStyleContext *style_context;
+               GdkRGBA *star_bg = NULL;
+               GdkRGBA star_fg;
+               gint min_x = -radius, max_x = radius;
+
+               gtk_widget_style_get (widget,
+                       "star-bg", &star_bg,
+                       NULL);
+
+               style_context = gtk_widget_get_style_context (widget);
+               gtk_style_context_get_color (style_context,
+                                            gtk_style_context_get_state (style_context),
+                                            &star_fg);
+
+               cairo_save (cr);
+               gs_star_image_outline_star (cr, allocation.x, allocation.y, radius, &min_x, &max_x);
+               cairo_clip (cr);
+               if (star_bg)
+                       gdk_cairo_set_source_rgba (cr, star_bg);
+               else
+                       cairo_set_source_rgb (cr, 0xde / 255.0, 0xdd / 255.0, 0xda / 255.0);
+               cairo_rectangle (cr, -radius, -radius, 2 * radius, 2 * radius);
+               cairo_fill (cr);
+
+               gdk_cairo_set_source_rgba (cr, &star_fg);
+               cairo_rectangle (cr, min_x, -radius, (max_x - min_x) * fraction, 2 * radius);
+               cairo_fill (cr);
+               cairo_restore (cr);
+
+               g_clear_pointer (&star_bg, gdk_rgba_free);
+       }
+
+       return FALSE;
+}
+
+static void
+gs_star_image_class_init (GsStarImageClass *klass)
+{
+       GObjectClass *object_class;
+       GtkWidgetClass *widget_class;
+
+       object_class = G_OBJECT_CLASS (klass);
+       object_class->get_property = gs_star_image_get_property;
+       object_class->set_property = gs_star_image_set_property;
+
+       widget_class = GTK_WIDGET_CLASS (klass);
+       widget_class->draw = gs_star_image_draw;
+
+       g_object_class_install_property (object_class,
+                                        PROP_FRACTION,
+                                        g_param_spec_double ("fraction", NULL, NULL,
+                                                             0.0, 1.0, 1.0,
+                                                             G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY));
+
+       gtk_widget_class_install_style_property (widget_class,
+                                        g_param_spec_boxed ("star-bg", NULL, NULL,
+                                                            GDK_TYPE_RGBA,
+                                                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+       gtk_widget_class_set_css_name (widget_class, "star-image");
+}
+
+static void
+gs_star_image_init (GsStarImage *self)
+{
+       self->fraction = 1.0;
+
+       gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+       gtk_widget_set_size_request (GTK_WIDGET (self), 16, 16);
+}
+
+GtkWidget *
+gs_star_image_new (void)
+{
+       return g_object_new (GS_TYPE_STAR_IMAGE, NULL);
+}
+
+void
+gs_star_image_set_fraction (GsStarImage *self,
+                           gdouble fraction)
+{
+       g_return_if_fail (GS_IS_STAR_IMAGE (self));
+
+       if (self->fraction == fraction)
+               return;
+
+       self->fraction = fraction;
+
+       g_object_notify (G_OBJECT (self), "fraction");
+
+       gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+gdouble
+gs_star_image_get_fraction (GsStarImage *self)
+{
+       g_return_val_if_fail (GS_IS_STAR_IMAGE (self), -1.0);
+
+       return self->fraction;
+}
diff --git a/src/gs-star-image.h b/src/gs-star-image.h
new file mode 100644
index 000000000..f1f99fea0
--- /dev/null
+++ b/src/gs-star-image.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_STAR_IMAGE             (gs_star_image_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsStarImage, gs_star_image, GS, STAR_IMAGE, GtkWidget)
+
+GtkWidget *    gs_star_image_new               (void);
+void           gs_star_image_set_fraction      (GsStarImage *self,
+                                                gdouble fraction);
+gdouble                gs_star_image_get_fraction      (GsStarImage *self);
+
+G_END_DECLS
diff --git a/src/gs-star-widget.c b/src/gs-star-widget.c
index 4485af12f..60747dfe7 100644
--- a/src/gs-star-widget.c
+++ b/src/gs-star-widget.c
@@ -12,6 +12,7 @@
 #include <math.h>
 
 #include "gs-common.h"
+#include "gs-star-image.h"
 #include "gs-star-widget.h"
 
 typedef struct
@@ -20,7 +21,7 @@ typedef struct
        gint             rating;
        guint            icon_size;
        GtkWidget       *box1;
-       GtkImage        *images[5];
+       GtkWidget       *images[5];
 } GsStarWidgetPrivate;
 
 G_DEFINE_TYPE_WITH_PRIVATE (GsStarWidget, gs_star_widget, GTK_TYPE_BIN)
@@ -94,15 +95,18 @@ gs_star_widget_refresh_rating (GsStarWidget *star)
 
        for (guint i = 0; i < G_N_ELEMENTS (priv->images); i++) {
                GtkWidget *im = GTK_WIDGET (priv->images[i]);
-               gboolean enabled;
-
-               /* add fudge factor so we can actually get 5 stars in reality */
-               enabled = priv->rating >= rate_to_star[i] - 10;
-
-               gtk_style_context_add_class (gtk_widget_get_style_context (im),
-                                            enabled ? "star-enabled" : "star-disabled");
-               gtk_style_context_remove_class (gtk_widget_get_style_context (im),
-                                               enabled ? "star-disabled" : "star-enabled");
+               gdouble fraction;
+
+               if (priv->rating >= rate_to_star[i])
+                       fraction = 1.0;
+               else if (!i)
+                       fraction = priv->rating / 20.0;
+               else if (priv->rating > rate_to_star[i - 1])
+                       fraction = (priv->rating - rate_to_star[i - 1]) / 20.0;
+               else
+                       fraction = 0.0;
+
+               gs_star_image_set_fraction (GS_STAR_IMAGE (im), fraction);
        }
 }
 
@@ -122,10 +126,10 @@ gs_star_widget_refresh (GsStarWidget *star)
                GtkWidget *im;
 
                /* create image */
-               im = gtk_image_new_from_icon_name ("starred-symbolic",
-                                                  GTK_ICON_SIZE_DIALOG);
-               gtk_image_set_pixel_size (GTK_IMAGE (im), (gint) priv->icon_size);
-               priv->images[i] = GTK_IMAGE (im);
+               im = gs_star_image_new ();
+               gtk_widget_set_size_request (im, (gint) priv->icon_size, (gint) priv->icon_size);
+
+               priv->images[i] = im;
 
                /* create button */
                if (priv->interactive) {
@@ -274,7 +278,7 @@ gs_star_widget_class_init (GsStarWidgetClass *klass)
                                    "Icon Size",
                                    "Size of icons to use, in pixels",
                                    0, G_MAXUINT, 12,
-                                   G_PARAM_READWRITE);
+                                   G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
 
        /**
         * GsStarWidget:interactive:
diff --git a/src/gtk-style-hc.css b/src/gtk-style-hc.css
index 0f24dbee4..4dabb2c4a 100644
--- a/src/gtk-style-hc.css
+++ b/src/gtk-style-hc.css
@@ -210,18 +210,15 @@ button.star, .button.star {
        outline-offset: -1px;
 }
 
-/* for the review dialog */
-.star-enabled {
+star-image {
        color: #000000;
+       -GsStarImage-star-bg: #777777;
 }
+
 .star-disabled {
        color: #777777;
 }
 
-/* for the app details shell */
-.star-enabled:disabled {
-       color: #000000;
-}
 .star-disabled:disabled {
        color: #777777;
 }
diff --git a/src/gtk-style.css b/src/gtk-style.css
index 4b18fe4a7..5e6d635bd 100644
--- a/src/gtk-style.css
+++ b/src/gtk-style.css
@@ -478,11 +478,11 @@ flowboxchild {
        padding: 0px;
 }
 
-/* for the review dialog */
-.star-enabled,
-.star-enabled:disabled {
-       color: shade(@theme_fg_color, 0.8);
+star-image {
+       color: #e5a50a;
+       -GsStarImage-star-bg: #deddda;
 }
+
 .star-disabled,
 .star-disabled:disabled {
        color: @unfocused_insensitive_color;
diff --git a/src/meson.build b/src/meson.build
index 430eb0c4b..1ffd665b4 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -63,6 +63,7 @@ gnome_software_sources = [
   'gs-shell.c',
   'gs-shell-search-provider.c',
   'gs-sidebar.c',
+  'gs-star-image.c',
   'gs-star-widget.c',
   'gs-summary-tile.c',
   'gs-third-party-repo-row.c',


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