diff options
Diffstat (limited to 'src/gs-star-image.c')
-rw-r--r-- | src/gs-star-image.c | 254 |
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; +} |