From: Alexander Mikhaylenko Date: Wed, 14 Oct 2020 16:05:39 +0500 Subject: Add GtkHdyShadowHelper This is imported from HdyShadowHelper from libhandy 1.0.2. --- gtk/hdy-shadow-helper-private.h | 37 ++++ gtk/hdy-shadow-helper.c | 453 ++++++++++++++++++++++++++++++++++++++++ gtk/meson.build | 2 + 3 files changed, 492 insertions(+) create mode 100644 gtk/hdy-shadow-helper-private.h create mode 100644 gtk/hdy-shadow-helper.c diff --git a/gtk/hdy-shadow-helper-private.h b/gtk/hdy-shadow-helper-private.h new file mode 100644 index 0000000..1b34ddb --- /dev/null +++ b/gtk/hdy-shadow-helper-private.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2019 Alexander Mikhaylenko + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include "gtkenums.h" +#include "gtkwidget.h" +#include "gtkwidgetpath.h" + +G_BEGIN_DECLS + +#define GTK_HDY_TYPE_SHADOW_HELPER (gtk_hdy_shadow_helper_get_type()) + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkStyleContext, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkWidgetPath, gtk_widget_path_unref) + +G_DECLARE_FINAL_TYPE (GtkHdyShadowHelper, gtk_hdy_shadow_helper, GTK, HDY_SHADOW_HELPER, GObject) + +GtkHdyShadowHelper * gtk_hdy_shadow_helper_new (GtkWidget *widget); + +void gtk_hdy_shadow_helper_clear_cache (GtkHdyShadowHelper *self); + +void gtk_hdy_shadow_helper_draw_shadow (GtkHdyShadowHelper *self, + cairo_t *cr, + gint width, + gint height, + gdouble progress, + GtkPanDirection direction); + +G_END_DECLS diff --git a/gtk/hdy-shadow-helper.c b/gtk/hdy-shadow-helper.c new file mode 100644 index 0000000..69583c4 --- /dev/null +++ b/gtk/hdy-shadow-helper.c @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2019 Alexander Mikhaylenko + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" +#include + +#include "hdy-shadow-helper-private.h" + +#include "gtkrender.h" +#include "gtkstylecontext.h" +#include "gtktypebuiltins.h" +#include "gtkwidgetpath.h" +#include "hdy-cairo-private.h" + +#include + +/** + * PRIVATE:gtk-hdy-shadow-helper + * @short_description: Shadow helper used in #GtkHdyLeaflet + * @title: GtkHdyShadowHelper + * @See_also: #GtkHdyLeaflet + * @stability: Private + * + * A helper class for drawing #GtkHdyLeaflet transition shadow. + * + * Since: 0.0.12 + */ + +struct _GtkHdyShadowHelper +{ + GObject parent_instance; + + GtkWidget *widget; + + gboolean is_cache_valid; + + cairo_pattern_t *dimming_pattern; + cairo_pattern_t *shadow_pattern; + cairo_pattern_t *border_pattern; + cairo_pattern_t *outline_pattern; + gint shadow_size; + gint border_size; + gint outline_size; + + GtkPanDirection last_direction; + gint last_width; + gint last_height; + gint last_scale; +}; + +G_DEFINE_TYPE (GtkHdyShadowHelper, gtk_hdy_shadow_helper, G_TYPE_OBJECT); + +enum { + PROP_0, + PROP_WIDGET, + LAST_PROP, +}; + +static GParamSpec *props[LAST_PROP]; + + +static GtkStyleContext * +create_context (GtkHdyShadowHelper *self, + const gchar *name, + GtkPanDirection direction) +{ + g_autoptr(GtkWidgetPath) path = NULL; + GtkStyleContext *context; + gint pos; + const gchar *direction_name; + GEnumClass *enum_class; + + enum_class = g_type_class_ref (GTK_TYPE_PAN_DIRECTION); + direction_name = g_enum_get_value (enum_class, direction)->value_nick; + + path = gtk_widget_path_copy (gtk_widget_get_path (self->widget)); + + pos = gtk_widget_path_append_type (path, GTK_TYPE_WIDGET); + gtk_widget_path_iter_set_object_name (path, pos, name); + + gtk_widget_path_iter_add_class (path, pos, direction_name); + + context = gtk_style_context_new (); + gtk_style_context_set_path (context, path); + + g_type_class_unref (enum_class); + + return context; +} + +static gint +get_element_size (GtkStyleContext *context, + GtkPanDirection direction) +{ + gint width, height; + + gtk_style_context_get (context, + gtk_style_context_get_state (context), + "min-width", &width, + "min-height", &height, + NULL); + + switch (direction) { + case GTK_PAN_DIRECTION_LEFT: + case GTK_PAN_DIRECTION_RIGHT: + return width; + case GTK_PAN_DIRECTION_UP: + case GTK_PAN_DIRECTION_DOWN: + return height; + default: + g_assert_not_reached (); + } + + return 0; +} + +static cairo_pattern_t * +create_element_pattern (GtkStyleContext *context, + gint width, + gint height, + gint scale) +{ + g_autoptr (cairo_surface_t) surface = + cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width * scale, height * scale); + g_autoptr (cairo_t) cr = cairo_create (surface); + cairo_pattern_t *pattern; + + cairo_surface_set_device_scale (surface, scale, scale); + + gtk_render_background (context, cr, 0, 0, width, height); + gtk_render_frame (context, cr, 0, 0, width, height); + + pattern = cairo_pattern_create_for_surface (surface); + + return pattern; +} + +static void +cache_shadow (GtkHdyShadowHelper *self, + gint width, + gint height, + GtkPanDirection direction) +{ + g_autoptr(GtkStyleContext) dim_context = NULL; + g_autoptr(GtkStyleContext) shadow_context = NULL; + g_autoptr(GtkStyleContext) border_context = NULL; + g_autoptr(GtkStyleContext) outline_context = NULL; + gint shadow_size, border_size, outline_size, scale; + + scale = gtk_widget_get_scale_factor (self->widget); + + if (self->last_direction == direction && + self->last_width == width && + self->last_height == height && + self->last_scale == scale && + self->is_cache_valid) + return; + + gtk_hdy_shadow_helper_clear_cache (self); + + dim_context = create_context (self, "dimming", direction); + shadow_context = create_context (self, "shadow", direction); + border_context = create_context (self, "border", direction); + outline_context = create_context (self, "outline", direction); + + shadow_size = get_element_size (shadow_context, direction); + border_size = get_element_size (border_context, direction); + outline_size = get_element_size (outline_context, direction); + + self->dimming_pattern = create_element_pattern (dim_context, width, height, scale); + if (direction == GTK_PAN_DIRECTION_LEFT || direction == GTK_PAN_DIRECTION_RIGHT) { + self->shadow_pattern = create_element_pattern (shadow_context, shadow_size, height, scale); + self->border_pattern = create_element_pattern (border_context, border_size, height, scale); + self->outline_pattern = create_element_pattern (outline_context, outline_size, height, scale); + } else { + self->shadow_pattern = create_element_pattern (shadow_context, width, shadow_size, scale); + self->border_pattern = create_element_pattern (border_context, width, border_size, scale); + self->outline_pattern = create_element_pattern (outline_context, width, outline_size, scale); + } + + self->border_size = border_size; + self->shadow_size = shadow_size; + self->outline_size = outline_size; + + self->is_cache_valid = TRUE; + self->last_direction = direction; + self->last_width = width; + self->last_height = height; + self->last_scale = scale; +} + +static void +gtk_hdy_shadow_helper_dispose (GObject *object) +{ + GtkHdyShadowHelper *self = GTK_HDY_SHADOW_HELPER (object); + + gtk_hdy_shadow_helper_clear_cache (self); + + if (self->widget) + g_clear_object (&self->widget); + + G_OBJECT_CLASS (gtk_hdy_shadow_helper_parent_class)->dispose (object); +} + +static void +gtk_hdy_shadow_helper_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkHdyShadowHelper *self = GTK_HDY_SHADOW_HELPER (object); + + switch (prop_id) { + case PROP_WIDGET: + g_value_set_object (value, self->widget); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtk_hdy_shadow_helper_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkHdyShadowHelper *self = GTK_HDY_SHADOW_HELPER (object); + + switch (prop_id) { + case PROP_WIDGET: + self->widget = GTK_WIDGET (g_object_ref (g_value_get_object (value))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtk_hdy_shadow_helper_class_init (GtkHdyShadowHelperClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gtk_hdy_shadow_helper_dispose; + object_class->get_property = gtk_hdy_shadow_helper_get_property; + object_class->set_property = gtk_hdy_shadow_helper_set_property; + + /** + * GtkHdyShadowHelper:widget: + * + * The widget the shadow will be drawn for. Must not be %NULL + * + * Since: 0.0.11 + */ + props[PROP_WIDGET] = + g_param_spec_object ("widget", + _("Widget"), + _("The widget the shadow will be drawn for"), + GTK_TYPE_WIDGET, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, LAST_PROP, props); +} + +static void +gtk_hdy_shadow_helper_init (GtkHdyShadowHelper *self) +{ +} + +/** + * gtk_hdy_shadow_helper_new: + * + * Creates a new #GtkHdyShadowHelper object. + * + * Returns: The newly created #GtkHdyShadowHelper object + * + * Since: 0.0.12 + */ +GtkHdyShadowHelper * +gtk_hdy_shadow_helper_new (GtkWidget *widget) +{ + return g_object_new (GTK_HDY_TYPE_SHADOW_HELPER, + "widget", widget, + NULL); +} + +/** + * gtk_hdy_shadow_helper_clear_cache: + * @self: a #GtkHdyShadowHelper + * + * Clears shadow cache. This should be used after a transition is done. + * + * Since: 0.0.12 + */ +void +gtk_hdy_shadow_helper_clear_cache (GtkHdyShadowHelper *self) +{ + if (!self->is_cache_valid) + return; + + cairo_pattern_destroy (self->dimming_pattern); + cairo_pattern_destroy (self->shadow_pattern); + cairo_pattern_destroy (self->border_pattern); + cairo_pattern_destroy (self->outline_pattern); + self->border_size = 0; + self->shadow_size = 0; + self->outline_size = 0; + + self->last_direction = 0; + self->last_width = 0; + self->last_height = 0; + self->last_scale = 0; + + self->is_cache_valid = FALSE; +} + +/** + * gtk_hdy_shadow_helper_draw_shadow: + * @self: a #GtkHdyShadowHelper + * @cr: a Cairo context to draw to + * @width: the width of the shadow rectangle + * @height: the height of the shadow rectangle + * @progress: transition progress, changes from 0 to 1 + * @direction: shadow direction + * + * Draws a transition shadow. For caching to work, @width, @height and + * @direction shouldn't change between calls. + * + * Since: 0.0.12 + */ +void +gtk_hdy_shadow_helper_draw_shadow (GtkHdyShadowHelper *self, + cairo_t *cr, + gint width, + gint height, + gdouble progress, + GtkPanDirection direction) +{ + gdouble remaining_distance, shadow_opacity; + gint shadow_size, border_size, outline_size, distance; + + if (progress >= 1) + return; + + cache_shadow (self, width, height, direction); + + shadow_size = self->shadow_size; + border_size = self->border_size; + outline_size = self->outline_size; + + switch (direction) { + case GTK_PAN_DIRECTION_LEFT: + case GTK_PAN_DIRECTION_RIGHT: + distance = width; + break; + case GTK_PAN_DIRECTION_UP: + case GTK_PAN_DIRECTION_DOWN: + distance = height; + break; + default: + g_assert_not_reached (); + } + + remaining_distance = (1 - progress) * (gdouble) distance; + shadow_opacity = 1; + if (remaining_distance < shadow_size) + shadow_opacity = (remaining_distance / shadow_size); + + cairo_save (cr); + + switch (direction) { + case GTK_PAN_DIRECTION_LEFT: + cairo_rectangle (cr, -outline_size, 0, width + outline_size, height); + break; + case GTK_PAN_DIRECTION_RIGHT: + cairo_rectangle (cr, 0, 0, width + outline_size, height); + break; + case GTK_PAN_DIRECTION_UP: + cairo_rectangle (cr, 0, -outline_size, width, height + outline_size); + break; + case GTK_PAN_DIRECTION_DOWN: + cairo_rectangle (cr, 0, 0, width, height + outline_size); + break; + default: + g_assert_not_reached (); + } + cairo_clip (cr); + gdk_window_mark_paint_from_clip (gtk_widget_get_window (self->widget), cr); + + cairo_set_source (cr, self->dimming_pattern); + cairo_paint_with_alpha (cr, 1 - progress); + + switch (direction) { + case GTK_PAN_DIRECTION_RIGHT: + cairo_translate (cr, width - shadow_size, 0); + break; + case GTK_PAN_DIRECTION_DOWN: + cairo_translate (cr, 0, height - shadow_size); + break; + case GTK_PAN_DIRECTION_LEFT: + case GTK_PAN_DIRECTION_UP: + break; + default: + g_assert_not_reached (); + } + + cairo_set_source (cr, self->shadow_pattern); + cairo_paint_with_alpha (cr, shadow_opacity); + + switch (direction) { + case GTK_PAN_DIRECTION_RIGHT: + cairo_translate (cr, shadow_size - border_size, 0); + break; + case GTK_PAN_DIRECTION_DOWN: + cairo_translate (cr, 0, shadow_size - border_size); + break; + case GTK_PAN_DIRECTION_LEFT: + case GTK_PAN_DIRECTION_UP: + break; + default: + g_assert_not_reached (); + } + + cairo_set_source (cr, self->border_pattern); + cairo_paint (cr); + + switch (direction) { + case GTK_PAN_DIRECTION_RIGHT: + cairo_translate (cr, border_size, 0); + break; + case GTK_PAN_DIRECTION_DOWN: + cairo_translate (cr, 0, border_size); + break; + case GTK_PAN_DIRECTION_LEFT: + cairo_translate (cr, -outline_size, 0); + break; + case GTK_PAN_DIRECTION_UP: + cairo_translate (cr, 0, -outline_size); + break; + default: + g_assert_not_reached (); + } + + cairo_set_source (cr, self->outline_pattern); + cairo_paint (cr); + + cairo_restore (cr); +} diff --git a/gtk/meson.build b/gtk/meson.build index 4b3e871..ae3dc94 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -385,6 +385,7 @@ gtk_sources = files( 'gdkpixbufutils.c', 'hdy-animation.c', 'hdy-css.c', + 'hdy-shadow-helper.c', 'hdy-squeezer.c', 'hdy-view-switcher-bar.c', 'hdy-view-switcher-button.c', @@ -400,6 +401,7 @@ gtk_private_type_headers = files( 'hdy-animation-private.h', 'hdy-cairo-private.h', 'hdy-css-private.h', + 'hdy-shadow-helper-private.h', 'hdy-squeezer-private.h', 'hdy-view-switcher-bar-private.h', 'hdy-view-switcher-button-private.h',