[libadwaita/wip/exalm/needs-attention: 1/4] Add AdwIndicatorBin




commit 6cc5f80420c45509888b2231128e82dee89630c3
Author: Alexander Mikhaylenko <alexm gnome org>
Date:   Sat Feb 27 21:48:28 2021 +0500

    Add AdwIndicatorBin
    
    This will help do indicator icons in a nice way in the next commit.

 doc/meson.build                            |   1 +
 src/adw-indicator-bin-private.h            |  37 +++
 src/adw-indicator-bin.c                    | 466 +++++++++++++++++++++++++++++
 src/adwaita.gresources.xml                 |   1 +
 src/glsl/mask.glsl                         |  13 +
 src/meson.build                            |   1 +
 src/stylesheet/widgets/_view-switcher.scss |  18 ++
 7 files changed, 537 insertions(+)
---
diff --git a/doc/meson.build b/doc/meson.build
index bbe3105..bf8ab5f 100644
--- a/doc/meson.build
+++ b/doc/meson.build
@@ -12,6 +12,7 @@ private_headers = [
     'adw-enum-value-object-private.h',
     'adw-focus-private.h',
     'adw-gizmo-private.h',
+    'adw-indicator-bin-private.h',
     'adw-main-private.h',
     'adw-preferences-group-private.h',
     'adw-preferences-page-private.h',
diff --git a/src/adw-indicator-bin-private.h b/src/adw-indicator-bin-private.h
new file mode 100644
index 0000000..6904547
--- /dev/null
+++ b/src/adw-indicator-bin-private.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author: Alexander Mikhaylenko <alexander mikhaylenko puri sm>
+ */
+
+#pragma once
+
+#if !defined(_ADWAITA_INSIDE) && !defined(ADWAITA_COMPILATION)
+#error "Only <adwaita.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define ADW_TYPE_INDICATOR_BIN (adw_indicator_bin_get_type())
+
+G_DECLARE_FINAL_TYPE (AdwIndicatorBin, adw_indicator_bin, ADW, INDICATOR_BIN, GtkWidget)
+
+GtkWidget *adw_indicator_bin_new (void);
+
+GtkWidget *adw_indicator_bin_get_child (AdwIndicatorBin *self);
+void       adw_indicator_bin_set_child (AdwIndicatorBin *self,
+                                        GtkWidget       *child);
+
+gboolean adw_indicator_bin_get_show_indicator (AdwIndicatorBin *self);
+void     adw_indicator_bin_set_show_indicator (AdwIndicatorBin *self,
+                                               gboolean         show_indicator);
+
+gboolean adw_indicator_bin_get_contained (AdwIndicatorBin *self);
+void     adw_indicator_bin_set_contained (AdwIndicatorBin *self,
+                                          gboolean         contained);
+
+G_END_DECLS
diff --git a/src/adw-indicator-bin.c b/src/adw-indicator-bin.c
new file mode 100644
index 0000000..364e5e1
--- /dev/null
+++ b/src/adw-indicator-bin.c
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2021 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author: Alexander Mikhaylenko <alexander mikhaylenko puri sm>
+ */
+
+#include "config.h"
+#include "adw-indicator-bin-private.h"
+
+#include "adw-gizmo-private.h"
+
+/**
+ * PRIVATE:adwindicatorbin
+ * @short_description: A helper object for #AdwViewSwitcherButton
+ * @title: AdwIndicatorBin
+ * @stability: Private
+ *
+ * The AdwIndicatorBin widget shows an unread indicator over the child widget
+ * masking it if they overlap.
+ *
+ * Since: 1.0
+ */
+
+struct _AdwIndicatorBin
+{
+  GtkWidget parent_instance;
+
+  GtkWidget *child;
+  gboolean show_indicator;
+  gboolean contained;
+
+  GtkWidget *mask;
+  GtkWidget *indicator;
+
+  GskGLShader *shader;
+  gboolean shader_compiled;
+};
+
+static void adw_indicator_bin_buildable_init (GtkBuildableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (AdwIndicatorBin, adw_indicator_bin, GTK_TYPE_WIDGET,
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, adw_indicator_bin_buildable_init))
+
+static GtkBuildableIface *parent_buildable_iface;
+
+enum {
+  PROP_0,
+  PROP_CHILD,
+  PROP_SHOW_INDICATOR,
+  PROP_CONTAINED,
+  LAST_PROP
+};
+
+static GParamSpec *props[LAST_PROP];
+
+
+static void
+ensure_shader (AdwIndicatorBin *self)
+{
+  GtkNative *native;
+  GskRenderer *renderer;
+  g_autoptr (GError) error = NULL;
+
+  if (self->shader)
+    return;
+
+  self->shader = gsk_gl_shader_new_from_resource ("/org/gnome/Adwaita/glsl/mask.glsl");
+
+  native = gtk_widget_get_native (GTK_WIDGET (self));
+  renderer = gtk_native_get_renderer (native);
+
+  self->shader_compiled = gsk_gl_shader_compile (self->shader, renderer, &error);
+
+  if (error) {
+    /* If shaders aren't supported, the error doesn't matter and we just
+     * silently fall back */
+    if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
+      g_critical ("Couldn't compile shader: %s\n", error->message);
+  }
+}
+
+static void
+adw_indicator_bin_measure (GtkWidget      *widget,
+                           GtkOrientation  orientation,
+                           int             for_size,
+                           int            *min,
+                           int            *nat,
+                           int            *min_baseline,
+                           int            *nat_baseline)
+{
+  AdwIndicatorBin *self = ADW_INDICATOR_BIN (widget);
+
+  if (!self->child) {
+    if (min)
+      *min = 0;
+    if (nat)
+      *nat = 0;
+    if (min_baseline)
+      *min_baseline = -1;
+    if (nat_baseline)
+      *nat_baseline = -1;
+
+    return;
+  }
+
+  gtk_widget_measure (self->child, orientation, for_size,
+                      min, nat, min_baseline, nat_baseline);
+}
+
+static void
+adw_indicator_bin_size_allocate (GtkWidget *widget,
+                                 int        width,
+                                 int        height,
+                                 int        baseline)
+{
+  AdwIndicatorBin *self = ADW_INDICATOR_BIN (widget);
+  GtkRequisition size;
+  float x, y;
+
+  if (self->child)
+    gtk_widget_allocate (self->child, width, height, baseline, NULL);
+
+  gtk_widget_get_preferred_size (self->indicator, NULL, &size);
+
+  if (self->contained) {
+    if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+      x = 0;
+    else
+      x = width - size.width;
+
+    y = 0;
+  } else {
+    if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+      x = -size.width / 2.0f;
+    else
+      x = width - size.width / 2.0f;
+
+    y = -size.height / 2.0f;
+  }
+
+  gtk_widget_allocate (self->mask, size.width, size.height, baseline,
+                       gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (x, y)));
+  gtk_widget_allocate (self->indicator, size.width, size.height, baseline,
+                       gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (x, y)));
+}
+
+static void
+adw_indicator_bin_snapshot (GtkWidget   *widget,
+                            GtkSnapshot *snapshot)
+{
+  AdwIndicatorBin *self = ADW_INDICATOR_BIN (widget);
+
+  if (!self->show_indicator) {
+    if (self->child)
+      gtk_widget_snapshot_child (widget, self->child, snapshot);
+
+    return;
+  }
+
+  if (self->child) {
+    GtkSnapshot *child_snapshot;
+    g_autoptr (GskRenderNode) child_node = NULL;
+
+    child_snapshot = gtk_snapshot_new ();
+    gtk_widget_snapshot_child (widget, self->child, child_snapshot);
+    child_node = gtk_snapshot_free_to_node (child_snapshot);
+
+    ensure_shader (self);
+
+    if (self->shader_compiled) {
+      graphene_rect_t bounds;
+
+      gsk_render_node_get_bounds (child_node, &bounds);
+      gtk_snapshot_push_gl_shader (snapshot, self->shader, &bounds,
+                                   gsk_gl_shader_format_args (self->shader, NULL));
+    }
+
+    gtk_snapshot_append_node (snapshot, child_node);
+
+    if (self->shader_compiled) {
+      gtk_snapshot_gl_shader_pop_texture (snapshot);
+
+      gtk_widget_snapshot_child (widget, self->mask, snapshot);
+      gtk_snapshot_gl_shader_pop_texture (snapshot);
+
+      gtk_snapshot_pop (snapshot);
+    }
+  }
+
+  gtk_widget_snapshot_child (widget, self->indicator, snapshot);
+}
+
+static void
+adw_indicator_bin_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  AdwIndicatorBin *self = ADW_INDICATOR_BIN (object);
+
+  switch (prop_id) {
+  case PROP_CHILD:
+    g_value_set_object (value, adw_indicator_bin_get_child (self));
+    break;
+
+  case PROP_SHOW_INDICATOR:
+    g_value_set_boolean (value, adw_indicator_bin_get_show_indicator (self));
+    break;
+
+  case PROP_CONTAINED:
+    g_value_set_boolean (value, adw_indicator_bin_get_contained (self));
+    break;
+
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+adw_indicator_bin_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  AdwIndicatorBin *self = ADW_INDICATOR_BIN (object);
+
+  switch (prop_id) {
+  case PROP_CHILD:
+    adw_indicator_bin_set_child (self, g_value_get_object (value));
+    break;
+
+  case PROP_SHOW_INDICATOR:
+    adw_indicator_bin_set_show_indicator (self, g_value_get_boolean (value));
+    break;
+
+  case PROP_CONTAINED:
+    adw_indicator_bin_set_contained (self, g_value_get_boolean (value));
+    break;
+
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+adw_indicator_bin_dispose (GObject *object)
+{
+  AdwIndicatorBin *self = ADW_INDICATOR_BIN (object);
+
+  g_clear_object (&self->shader);
+  g_clear_pointer (&self->child, gtk_widget_unparent);
+  g_clear_pointer (&self->mask, gtk_widget_unparent);
+  g_clear_pointer (&self->indicator, gtk_widget_unparent);
+
+  G_OBJECT_CLASS (adw_indicator_bin_parent_class)->dispose (object);
+}
+static void
+adw_indicator_bin_class_init (AdwIndicatorBinClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->get_property = adw_indicator_bin_get_property;
+  object_class->set_property = adw_indicator_bin_set_property;
+  object_class->dispose = adw_indicator_bin_dispose;
+
+  widget_class->measure = adw_indicator_bin_measure;
+  widget_class->size_allocate = adw_indicator_bin_size_allocate;
+  widget_class->snapshot = adw_indicator_bin_snapshot;
+
+  /**
+   * AdwIndicatorBin:child:
+   *
+   * The child widget of the #AdwIndicatorBin.
+   *
+   * Since: 1.0
+   */
+  props[PROP_CHILD] =
+      g_param_spec_object ("child",
+                           "Child",
+                           "The child widget",
+                           GTK_TYPE_WIDGET,
+                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * AdwIndicatorBin:show-indicator:
+   *
+   * Whether to show the indicator.
+   *
+   * Since: 1.0
+   */
+  props[PROP_SHOW_INDICATOR] =
+    g_param_spec_boolean ("show-indicator",
+                          "Show Indicator",
+                          "Whether to show the indicator",
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * AdwIndicatorBin:contained:
+   *
+   * Whether the indicator is centered on the top end corner of the widget or is
+   * at the top end corner but contained in the widget bounds.
+   *
+   * Since: 1.0
+   */
+  props[PROP_CONTAINED] =
+    g_param_spec_boolean ("contained",
+                          "Contained",
+                          "Whether the indicator is contained in the widget bounds",
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  g_object_class_install_properties (object_class, LAST_PROP, props);
+
+  gtk_widget_class_set_css_name (widget_class, "indicatorbin");
+}
+
+static void
+adw_indicator_bin_init (AdwIndicatorBin *self)
+{
+  self->mask = adw_gizmo_new ("mask", NULL, NULL, NULL, NULL, NULL, NULL);
+  gtk_widget_set_can_target (self->mask, FALSE);
+  gtk_widget_set_parent (self->mask, GTK_WIDGET (self));
+
+  self->indicator = adw_gizmo_new ("indicator", NULL, NULL, NULL, NULL, NULL, NULL);
+  gtk_widget_set_can_target (self->indicator, FALSE);
+  gtk_widget_set_parent (self->indicator, GTK_WIDGET (self));
+}
+
+static void
+adw_indicator_bin_buildable_add_child (GtkBuildable *buildable,
+                                       GtkBuilder   *builder,
+                                       GObject      *child,
+                                       const char   *type)
+{
+  if (GTK_IS_WIDGET (child))
+    adw_indicator_bin_set_child (ADW_INDICATOR_BIN (buildable), GTK_WIDGET (child));
+  else
+    parent_buildable_iface->add_child (buildable, builder, child, type);
+}
+
+static void
+adw_indicator_bin_buildable_init (GtkBuildableIface *iface)
+{
+  parent_buildable_iface = g_type_interface_peek_parent (iface);
+
+  iface->add_child = adw_indicator_bin_buildable_add_child;
+}
+
+/**
+ * adw_indicator_bin_new:
+ *
+ * Creates a new #AdwIndicatorBin.
+ *
+ * Returns: a new #AdwIndicatorBin
+ *
+ * Since: 1.0
+ */
+GtkWidget *
+adw_indicator_bin_new (void)
+{
+  return g_object_new (ADW_TYPE_INDICATOR_BIN, NULL);
+}
+
+/**
+ * adw_indicator_bin_get_child:
+ * @self: an #AdwIndicatorBin
+ *
+ * Gets the child widget of @self.
+ *
+ * Returns: (nullable) (transfer none): the child widget of @self
+ *
+ * Since: 1.0
+ */
+GtkWidget *
+adw_indicator_bin_get_child (AdwIndicatorBin *self)
+{
+  g_return_val_if_fail (ADW_IS_INDICATOR_BIN (self), NULL);
+
+  return self->child;
+}
+
+/**
+ * adw_indicator_bin_set_child:
+ * @self: an #AdwIndicatorBin
+ * @child: (nullable): the child widget
+ *
+ * Sets the child widget of @self.
+ *
+ * Since: 1.0
+ */
+void
+adw_indicator_bin_set_child (AdwIndicatorBin *self,
+                             GtkWidget       *child)
+{
+  g_return_if_fail (ADW_IS_INDICATOR_BIN (self));
+  g_return_if_fail (child == NULL || GTK_IS_WIDGET (child));
+
+  if (self->child == child)
+    return;
+
+  if (self->child)
+    gtk_widget_unparent (self->child);
+
+  self->child = child;
+
+  if (self->child)
+    gtk_widget_set_parent (self->child, GTK_WIDGET (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHILD]);
+}
+
+gboolean
+adw_indicator_bin_get_show_indicator (AdwIndicatorBin *self)
+{
+  g_return_val_if_fail (ADW_IS_INDICATOR_BIN (self), FALSE);
+
+  return self->show_indicator;
+}
+
+void
+adw_indicator_bin_set_show_indicator (AdwIndicatorBin *self,
+                                      gboolean         show_indicator)
+{
+  g_return_if_fail (ADW_IS_INDICATOR_BIN (self));
+
+  show_indicator = !!show_indicator;
+
+  if (self->show_indicator == show_indicator)
+    return;
+
+  self->show_indicator = show_indicator;
+
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SHOW_INDICATOR]);
+}
+
+gboolean
+adw_indicator_bin_get_contained (AdwIndicatorBin *self)
+{
+  g_return_val_if_fail (ADW_IS_INDICATOR_BIN (self), FALSE);
+
+  return self->contained;
+}
+
+void
+adw_indicator_bin_set_contained (AdwIndicatorBin *self,
+                                 gboolean         contained)
+{
+  g_return_if_fail (ADW_IS_INDICATOR_BIN (self));
+
+  contained = !!contained;
+
+  if (self->contained == contained)
+    return;
+
+  self->contained = contained;
+
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONTAINED]);
+}
diff --git a/src/adwaita.gresources.xml b/src/adwaita.gresources.xml
index f62d908..64de095 100644
--- a/src/adwaita.gresources.xml
+++ b/src/adwaita.gresources.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <gresources>
   <gresource prefix="/org/gnome/Adwaita">
+    <file>glsl/mask.glsl</file>
     <file preprocess="xml-stripblanks">icons/scalable/actions/adw-expander-arrow-symbolic.svg</file>
     <file preprocess="xml-stripblanks">icons/scalable/status/avatar-default-symbolic.svg</file>
   </gresource>
diff --git a/src/glsl/mask.glsl b/src/glsl/mask.glsl
new file mode 100644
index 0000000..ea01665
--- /dev/null
+++ b/src/glsl/mask.glsl
@@ -0,0 +1,13 @@
+uniform sampler2D u_texture1;
+uniform sampler2D u_texture2;
+
+void mainImage(out vec4 fragColor,
+               in vec2  fragCoord,
+               in vec2  resolution,
+               in vec2  uv)
+{
+  vec4 source = GskTexture(u_texture1, uv);
+  vec4 mask = GskTexture(u_texture2, uv);
+
+  fragColor = source * (1 - mask.w);
+}
diff --git a/src/meson.build b/src/meson.build
index 3198ac4..eed6438 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -132,6 +132,7 @@ src_sources = [
   'adw-focus.c',
   'adw-gizmo.c',
   'adw-header-bar.c',
+  'adw-indicator-bin.c',
   'adw-leaflet.c',
   'adw-main.c',
   'adw-navigation-direction.c',
diff --git a/src/stylesheet/widgets/_view-switcher.scss b/src/stylesheet/widgets/_view-switcher.scss
index 4a53377..1086341 100644
--- a/src/stylesheet/widgets/_view-switcher.scss
+++ b/src/stylesheet/widgets/_view-switcher.scss
@@ -81,3 +81,21 @@ viewswitchertitle viewswitcher {
   margin-left: 12px;
   margin-right: 12px;
 }
+
+/*******************
+ * AdwIndicatorBin *
+ *******************/
+
+indicatorbin {
+  > indicator { margin: 1px; }
+  > mask { padding: 1px; }
+
+  > indicator, > mask {
+    $_indicator_color: #3584e4;
+
+    background: $_indicator_color;
+    min-width: 6px;
+    min-height: 6px;
+    border-radius: 100%;
+  }
+}


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