summaryrefslogtreecommitdiffstats
path: root/src/gs-star-image.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gs-star-image.c')
-rw-r--r--src/gs-star-image.c254
1 files changed, 254 insertions, 0 deletions
diff --git a/src/gs-star-image.c b/src/gs-star-image.c
new file mode 100644
index 0000000..f5fb14f
--- /dev/null
+++ b/src/gs-star-image.c
@@ -0,0 +1,254 @@
+/* -*- 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. The unfilled part of
+ * the star currently uses a hardcoded colour.
+ * The `background` style property controls the area outside the star.
+ *
+ * Since: 41
+ */
+
+#include "config.h"
+
+#include "gs-star-image.h"
+
+#include <adwaita.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;
+ } small_points[] = {
+ { 0.000000, -1.000000 },
+ { -1.000035, -0.424931 },
+ { -0.668055, 0.850680 },
+ { 0.668055, 0.850680 },
+ { 1.000035, -0.424931 }
+ }, large_points[] = {
+ { 0.000000, -1.000000 },
+ { -1.000035, -0.325033 },
+ { -0.618249, 0.850948 },
+ { 0.618249, 0.850948 },
+ { 1.000035, -0.325033 }
+ }, *points;
+ gint ii, nn = G_N_ELEMENTS (small_points), xx, yy;
+
+ /* Safety check */
+ G_STATIC_ASSERT (G_N_ELEMENTS (small_points) == G_N_ELEMENTS (large_points));
+
+ if (radius <= 0)
+ return;
+
+ /* An arbitrary number, since which the math-precise star looks fine,
+ * while it looks odd for lower sizes. */
+ if (radius * 2 > 20)
+ points = large_points;
+ else
+ points = small_points;
+
+ 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 void
+gs_star_image_snapshot (GtkWidget *widget,
+ GtkSnapshot *snapshot)
+{
+ GtkAllocation allocation;
+ cairo_t *cr;
+ 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;
+
+ cr = gtk_snapshot_append_cairo (snapshot,
+ &GRAPHENE_RECT_INIT (0, 0,
+ gtk_widget_get_width (widget),
+ gtk_widget_get_height (widget)));
+
+ if (radius > 0) {
+ GtkStyleContext *style_context;
+ GdkRGBA star_bg = { 1, 1, 1, 1 };
+ GdkRGBA star_fg;
+ gint min_x = -radius, max_x = radius;
+
+ style_context = gtk_widget_get_style_context (widget);
+ gtk_style_context_get_color (style_context, &star_fg);
+
+ gtk_style_context_lookup_color (style_context, "card_fg_color", &star_bg);
+ if (adw_style_manager_get_high_contrast (adw_style_manager_get_default ()))
+ star_bg.alpha *= 0.4;
+ else
+ star_bg.alpha *= 0.2;
+
+ cairo_save (cr);
+ gs_star_image_outline_star (cr, allocation.x, allocation.y, radius, &min_x, &max_x);
+ cairo_clip (cr);
+ gdk_cairo_set_source_rgba (cr, &star_bg);
+ cairo_rectangle (cr, -radius, -radius, 2 * radius, 2 * radius);
+ cairo_fill (cr);
+
+ gdk_cairo_set_source_rgba (cr, &star_fg);
+ if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+ cairo_rectangle (cr, max_x - (max_x - min_x) * fraction, -radius, (max_x - min_x) * fraction, 2 * radius);
+ else
+ cairo_rectangle (cr, min_x, -radius, (max_x - min_x) * fraction, 2 * radius);
+ cairo_fill (cr);
+ cairo_restore (cr);
+ }
+
+ cairo_destroy (cr);
+}
+
+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->snapshot = gs_star_image_snapshot;
+
+ 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_set_css_name (widget_class, "star-image");
+}
+
+static void
+gs_star_image_init (GsStarImage *self)
+{
+ self->fraction = 1.0;
+
+ 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;
+}