[gnome-software/1131-featured-carousel: 1/21] gs-rounded-bin: Add a new widget for rounding the corners of things
- From: Phaedrus Leeds <mwleeds src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software/1131-featured-carousel: 1/21] gs-rounded-bin: Add a new widget for rounding the corners of things
- Date: Thu, 18 Feb 2021 07:24:44 +0000 (UTC)
commit 2b3c44814b32d825b4e57d95a588f644be3914d6
Author: Philip Withnall <pwithnall endlessos org>
Date: Wed Feb 17 17:44:29 2021 +0000
gs-rounded-bin: Add a new widget for rounding the corners of things
Adapted from
* https://gitlab.gnome.org/GNOME/libhandy/-/blob/1.0.0/src/hdy-window-mixin.c
* https://gitlab.gnome.org/GNOME/fractal/-/blob/c69aacc4/fractal-gtk/src/widgets/clip_container.rs
Many thanks to Alexander Mikhaylenko and Christopher Davis.
Signed-off-by: Philip Withnall <pwithnall endlessos org>
src/gs-rounded-bin.c | 266 +++++++++++++++++++++++++++++++++++++++++++++++++++
src/gs-rounded-bin.h | 23 +++++
src/meson.build | 1 +
3 files changed, 290 insertions(+)
---
diff --git a/src/gs-rounded-bin.c b/src/gs-rounded-bin.c
new file mode 100644
index 000000000..220525f8e
--- /dev/null
+++ b/src/gs-rounded-bin.c
@@ -0,0 +1,266 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2020 Alexander Mikhaylenko
+ * Copyright (C) 2021 Endless OS Foundation, Inc
+ *
+ * Authors:
+ * - Alexander Mikhaylenko <alexm gnome org>
+ * - Philip Withnall <pwithnall endlessos org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+/**
+ * SECTION:gs-rounded-bin
+ * @short_description: A #GtkBin which can clip rounded corners into its child
+ *
+ * #GsRoundedBin is a basic #GtkBin subclass which supports masking the child
+ * widget to apply rounded corners to it. It has no other layout functionality,
+ * and will hopefully eventually be replaced by rounded corner functionality in
+ * GTK 4 itself.
+ *
+ * To use it, set the `border-radius` property in CSS:
+ * |[
+ * path>to>parent>widget>rounded-bin {
+ * border-radius: 12px;
+ * }
+ * ]|
+ *
+ * Adapted from
+ * * https://gitlab.gnome.org/GNOME/libhandy/-/blob/1.0.0/src/hdy-window-mixin.c
+ * * https://gitlab.gnome.org/GNOME/fractal/-/blob/c69aacc4/fractal-gtk/src/widgets/clip_container.rs
+ *
+ * Since: 40
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk/gdk.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "gs-rounded-bin.h"
+
+/* Used to index #GsRoundedBin.masks, and create_masks() uses the numeric
+ * values to determine mask centre points */
+typedef enum {
+ GS_CORNER_TOP_LEFT = 0,
+ GS_CORNER_TOP_RIGHT = 1,
+ GS_CORNER_BOTTOM_LEFT = 2,
+ GS_CORNER_BOTTOM_RIGHT = 3,
+} GsCornerType;
+
+struct _GsRoundedBin
+{
+ GtkBin parent_instance;
+
+ gint last_border_radius;
+ cairo_surface_t *masks[4]; /* (owned) (indexed-by GsCornerType) */
+};
+
+G_DEFINE_TYPE (GsRoundedBin, gs_rounded_bin, GTK_TYPE_BIN)
+
+static void clear_masks (GsRoundedBin *self);
+
+static void
+gs_rounded_bin_init (GsRoundedBin *self)
+{
+ gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+}
+
+static void
+gs_rounded_bin_finalize (GObject *object)
+{
+ GsRoundedBin *self = GS_ROUNDED_BIN (object);
+
+ clear_masks (self);
+
+ G_OBJECT_CLASS (gs_rounded_bin_parent_class)->finalize (object);
+}
+
+static gint
+get_border_radius (GtkStyleContext *ctx)
+{
+ GtkStateFlags state = gtk_style_context_get_state (ctx);
+ gint border_radius;
+ gtk_style_context_get (ctx, state, "border-radius", &border_radius, NULL);
+ return border_radius;
+}
+
+static void
+create_masks (GsRoundedBin *self,
+ cairo_t *cr,
+ gint radius)
+{
+ gdouble scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self));
+ gdouble r = radius;
+
+ clear_masks (self);
+
+ if (radius <= 0.0)
+ return;
+
+ for (gsize i = 0; i < G_N_ELEMENTS (self->masks); i++) {
+ GsCornerType corner = (GsCornerType) i;
+ cairo_surface_t *surface = NULL;
+ cairo_t *mask_ctx = NULL;
+ gdouble mod_val, val;
+
+ surface = cairo_surface_create_similar_image (cairo_get_target (cr),
+ CAIRO_FORMAT_A8,
+ radius * scale_factor,
+ radius * scale_factor);
+
+ mask_ctx = cairo_create (surface);
+ cairo_scale (mask_ctx, scale_factor, scale_factor);
+ cairo_set_source_rgb (mask_ctx, 0.0, 0.0, 0.0);
+ mod_val = (i % 2 == 0) ? r : 0.0;
+ val = (i / 2 == 0) ? r : 0.0;
+ cairo_arc (mask_ctx, mod_val, val, r, 0.0, G_PI * 2.0);
+ cairo_fill (mask_ctx);
+
+ self->masks[corner] = g_steal_pointer (&surface);
+
+ cairo_destroy (mask_ctx);
+ }
+}
+
+static void
+clear_masks (GsRoundedBin *self)
+{
+ for (gsize i = 0; i < G_N_ELEMENTS (self->masks); i++)
+ g_clear_pointer (&self->masks[i], cairo_surface_destroy);
+}
+
+static void
+mask_corner (GsRoundedBin *self,
+ cairo_t *cr,
+ gdouble scale_factor,
+ GsCornerType corner,
+ gdouble x,
+ gdouble y)
+{
+ g_assert (corner >= 0 && corner < G_N_ELEMENTS (self->masks));
+
+ cairo_save (cr);
+ cairo_scale (cr, 1.0 / scale_factor, 1.0 / scale_factor);
+ cairo_mask_surface (cr, self->masks[corner], x * scale_factor, y * scale_factor);
+ cairo_restore (cr);
+}
+
+static gboolean
+gs_rounded_bin_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ GsRoundedBin *self = GS_ROUNDED_BIN (widget);
+ GdkWindow *window;
+ gboolean clip_set = FALSE;
+ GdkRectangle clip = { 0, };
+ GtkStyleContext *ctx;
+ gint width, height, radius;
+ gdouble w, h, r, xy;
+ gint scale_factor;
+ cairo_surface_t *surface = NULL;
+ cairo_t *surface_ctx = NULL;
+
+ window = gtk_widget_get_window (widget);
+ g_assert (window != NULL);
+
+ /* Propagate the draw further if this is an input-only window. */
+ if (!gtk_cairo_should_draw_window (cr, window))
+ return FALSE;
+
+ clip_set = gdk_cairo_get_clip_rectangle (cr, &clip);
+
+ ctx = gtk_widget_get_style_context (widget);
+ width = gtk_widget_get_allocated_width (widget);
+ height = gtk_widget_get_allocated_height (widget);
+ w = width;
+ h = height;
+ radius = get_border_radius (ctx);
+ r = radius;
+ xy = 0.0;
+
+ /* Don’t do any custom drawing if the radius is default. */
+ if (radius <= 0.0)
+ return GTK_WIDGET_CLASS (gs_rounded_bin_parent_class)->draw (widget, cr);
+
+ if (!clip_set) {
+ clip.width = width;
+ clip.height = height;
+ }
+
+ cairo_save (cr);
+
+ scale_factor = gtk_widget_get_scale_factor (widget);
+ if (radius * scale_factor != self->last_border_radius) {
+ create_masks (self, cr, radius);
+ self->last_border_radius = radius * scale_factor;
+ }
+
+ surface = gdk_window_create_similar_surface (window,
+ CAIRO_CONTENT_COLOR_ALPHA,
+ MAX (width, 1),
+ MAX (height, 1));
+ surface_ctx = cairo_create (surface);
+ cairo_surface_set_device_offset (surface, -clip.x, -clip.y);
+
+ if (!gtk_widget_get_app_paintable (widget)) {
+ gtk_render_background (ctx, surface_ctx, xy, xy, w, h);
+ gtk_render_frame (ctx, surface_ctx, xy, xy, w, h);
+ }
+
+ if (gtk_bin_get_child (GTK_BIN (widget)) != NULL) {
+ gtk_container_propagate_draw (GTK_CONTAINER (widget),
+ gtk_bin_get_child (GTK_BIN (widget)),
+ surface_ctx);
+ }
+
+ cairo_set_source_surface (cr, surface, 0.0, 0.0);
+ cairo_rectangle (cr, xy + r, xy, w - r * 2.0, r);
+ cairo_rectangle (cr, xy + r, xy + h - r, w - r * 2.0, r);
+ cairo_rectangle (cr, xy, xy + r, w, h - r * 2.0);
+ cairo_fill (cr);
+
+ if (clip.x < xy + r && clip.y < xy + r) {
+ mask_corner (self, cr, scale_factor, GS_CORNER_TOP_LEFT, xy, xy);
+ }
+
+ if ((clip.x + clip.width) > xy + w - r && clip.y < xy + r) {
+ mask_corner (self, cr, scale_factor, GS_CORNER_TOP_RIGHT, xy + w - r, xy);
+ }
+
+ if (clip.x < xy + r && (clip.y + clip.height) > xy + h - r) {
+ mask_corner (self, cr, scale_factor, GS_CORNER_BOTTOM_LEFT, xy, xy + h - r);
+ }
+
+ if ((clip.x + clip.width) > xy + w - r && (clip.y + clip.height) > xy + h - r) {
+ mask_corner (self, cr, scale_factor, GS_CORNER_BOTTOM_RIGHT, xy + w - r, xy + h - r);
+ }
+
+ cairo_surface_flush (surface);
+
+ cairo_restore (cr);
+
+ cairo_surface_destroy (surface);
+ cairo_destroy (surface_ctx);
+
+ /* Continue propagating the draw signal */
+ return FALSE;
+}
+
+static void
+gs_rounded_bin_class_init (GsRoundedBinClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = gs_rounded_bin_finalize;
+
+ widget_class->draw = gs_rounded_bin_draw;
+
+ gtk_widget_class_set_css_name (widget_class, "rounded-bin");
+}
diff --git a/src/gs-rounded-bin.h b/src/gs-rounded-bin.h
new file mode 100644
index 000000000..5e173f1d6
--- /dev/null
+++ b/src/gs-rounded-bin.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2021 Endless OS Foundation, Inc
+ *
+ * Author: Philip Withnall <pwithnall endlessos org>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_ROUNDED_BIN (gs_rounded_bin_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsRoundedBin, gs_rounded_bin, GS, ROUNDED_BIN, GtkBin)
+
+G_END_DECLS
diff --git a/src/meson.build b/src/meson.build
index 90e036563..14c902f58 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -56,6 +56,7 @@ gnome_software_sources = [
'gs-review-dialog.c',
'gs-review-histogram.c',
'gs-review-row.c',
+ 'gs-rounded-bin.c',
'gs-screenshot-image.c',
'gs-search-page.c',
'gs-shell.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]