[gtk+] notebook: Use CSS nodes for arrows



commit 4802b515e4ddba2492ef0a21f9d435c458f78e7a
Author: Matthias Clasen <mclasen redhat com>
Date:   Sun Nov 8 15:01:09 2015 -0500

    notebook: Use CSS nodes for arrows
    
    This converts the drawing of scroll arrows to use separate CSS
    nodes.

 gtk/gtknotebook.c              |  275 +++++++++++++++++++++++++++++++++-------
 gtk/theme/Adwaita/_common.scss |   37 +++---
 2 files changed, 248 insertions(+), 64 deletions(-)
---
diff --git a/gtk/gtknotebook.c b/gtk/gtknotebook.c
index b11ce39..9e1d711 100644
--- a/gtk/gtknotebook.c
+++ b/gtk/gtknotebook.c
@@ -44,6 +44,7 @@
 #include "gtktypebuiltins.h"
 #include "gtkwidgetpath.h"
 #include "gtkcssnodeprivate.h"
+#include "gtkcssstylepropertyprivate.h"
 #include "gtkstylecontextprivate.h"
 #include "gtkwidgetprivate.h"
 #include "a11y/gtknotebookaccessible.h"
@@ -102,10 +103,12 @@
  * ├── header.top
  * │   ├── [action widget]
  * │   ├── tabs
+ * │   │   ├── [arrow]
  * │   │   ├── tab
  * ┊   ┊   ┊   ╰── [tab label]
- * │   │   ╰── tab[.reorderable-page]
- * │   │       ╰── [tab label]
+ * │   │   ├── tab[.reorderable-page]
+ * │   │   │   ╰── [tab label]
+ * │   │   ╰── [arrow]
  * │   ╰── [action widget]
  * │
  * ├── [child]
@@ -118,7 +121,8 @@
  * contains one subnode per tab with name tab.
  *
  * If action widgets are present, their CSS nodes are placed next
- * to the tabs node.
+ * to the tabs node. If the notebook is scrollable, CSS nodes with
+ * name arrow are placed as first and last child of the tabs node.
  *
  * The main node gets the .frame style class when rendering the
  * background of a notebook with border. It gets the .header
@@ -171,6 +175,7 @@ struct _GtkNotebookPrivate
 
   GtkCssNode                *header_node;
   GtkCssNode                *tabs_node;
+  GtkCssNode                *arrow_node[4];
 
   GList         *children;
   GList         *first_tab;             /* The first tab visible (for scrolling notebooks) */
@@ -237,11 +242,11 @@ enum {
 
 typedef enum
 {
-  ARROW_NONE,
   ARROW_LEFT_BEFORE,
   ARROW_RIGHT_BEFORE,
   ARROW_LEFT_AFTER,
-  ARROW_RIGHT_AFTER
+  ARROW_RIGHT_AFTER,
+  ARROW_NONE
 } GtkNotebookArrow;
 
 typedef enum
@@ -1206,6 +1211,29 @@ gtk_notebook_class_init (GtkNotebookClass *class)
 }
 
 static void
+node_style_changed_cb (GtkCssNode  *node,
+                       GtkCssStyle *old_style,
+                       GtkCssStyle *new_style,
+                       GtkWidget   *widget)
+{
+  GtkBitmask *changes;
+  static GtkBitmask *affects_size = NULL;
+
+  if (G_UNLIKELY (affects_size == NULL))
+    affects_size = _gtk_css_style_property_get_mask_affecting (GTK_CSS_AFFECTS_SIZE | GTK_CSS_AFFECTS_CLIP);
+
+  changes = _gtk_bitmask_new ();
+  changes = gtk_css_style_add_difference (changes, old_style, new_style);
+
+  if (_gtk_bitmask_intersects (changes, affects_size))
+    gtk_widget_queue_resize (widget);
+  else
+    gtk_widget_queue_draw (widget);
+
+  _gtk_bitmask_free (changes);
+}
+
+static void
 gtk_notebook_init (GtkNotebook *notebook)
 {
   GtkNotebookPrivate *priv;
@@ -1228,7 +1256,7 @@ gtk_notebook_init (GtkNotebook *notebook)
   priv->show_border = TRUE;
   priv->tab_pos = GTK_POS_TOP;
   priv->scrollable = FALSE;
-  priv->in_child = 0;
+  priv->in_child = ARROW_NONE;
   priv->click_child = 0;
   priv->button = 0;
   priv->need_timer = 0;
@@ -1265,12 +1293,14 @@ gtk_notebook_init (GtkNotebook *notebook)
   gtk_css_node_add_class (priv->header_node, g_quark_from_static_string (GTK_STYLE_CLASS_TOP));
   gtk_css_node_set_parent (priv->header_node, widget_node);
   gtk_css_node_set_state (priv->header_node, gtk_css_node_get_state (widget_node));
+  g_signal_connect_object (priv->header_node, "style-changed", G_CALLBACK (node_style_changed_cb), notebook, 
0);
   g_object_unref (priv->header_node);
 
   priv->tabs_node = gtk_css_node_new ();
   gtk_css_node_set_name (priv->tabs_node, I_("tabs"));
   gtk_css_node_set_parent (priv->tabs_node, priv->header_node);
   gtk_css_node_set_state (priv->tabs_node, gtk_css_node_get_state (widget_node));
+  g_signal_connect_object (priv->tabs_node, "style-changed", G_CALLBACK (node_style_changed_cb), notebook, 
0);
   g_object_unref (priv->tabs_node);
 
 }
@@ -2918,7 +2948,7 @@ gtk_notebook_button_press (GtkWidget      *widget,
     return FALSE;
 
   arrow = gtk_notebook_get_arrow (notebook, x, y);
-  if (arrow)
+  if (arrow != ARROW_NONE)
     return gtk_notebook_arrow_button_press (notebook, arrow, event->button);
 
   if (priv->menu && gdk_event_triggers_context_menu ((GdkEvent *) event))
@@ -3332,9 +3362,9 @@ gtk_notebook_leave_notify (GtkWidget        *widget,
           tab_prelight (notebook, (GdkEvent *)event);
         }
 
-      if (priv->in_child)
+      if (priv->in_child != ARROW_NONE)
         {
-          priv->in_child = 0;
+          priv->in_child = ARROW_NONE;
           gtk_notebook_redraw_arrows (notebook);
         }
     }
@@ -3576,11 +3606,86 @@ gtk_notebook_grab_notify (GtkWidget *widget,
 }
 
 static void
+update_tab_state (GtkNotebook *notebook)
+{
+  GtkNotebookPrivate *priv = notebook->priv;
+  GtkStateFlags state, tab_state;
+  GList *l;
+
+  state = gtk_widget_get_state_flags (GTK_WIDGET (notebook));
+
+  state = state & ~GTK_STATE_FLAG_FOCUSED;
+
+  if (priv->header_node)
+    gtk_css_node_set_state (priv->header_node, state);
+  if (priv->tabs_node)
+    gtk_css_node_set_state (priv->header_node, state);
+
+  for (l = priv->children; l; l = l->next)
+    {
+      GtkNotebookPage *page = l->data;
+
+      tab_state = state & ~(GTK_STATE_FLAG_ACTIVE | GTK_STATE_FLAG_PRELIGHT);
+
+      if (page == priv->cur_page)
+        tab_state |= GTK_STATE_FLAG_ACTIVE;
+      if (page == priv->prelight_tab)
+        tab_state |= GTK_STATE_FLAG_PRELIGHT;
+
+      gtk_css_node_set_state (page->cssnode, tab_state);
+    }
+}
+
+static void
+update_arrow_state (GtkNotebook *notebook)
+{
+  GtkNotebookPrivate *priv = notebook->priv;
+  gint i;
+  GtkStateFlags state;
+  gboolean is_rtl, left;
+
+  is_rtl = gtk_widget_get_direction (GTK_WIDGET (notebook)) == GTK_TEXT_DIR_RTL;
+
+  for (i = 0; i < 4; i++)
+    {
+      if (priv->arrow_node[i] == NULL)
+        continue;
+
+      state = gtk_widget_get_state_flags (GTK_WIDGET (notebook));
+      state &= ~GTK_STATE_FLAG_FOCUSED;
+
+
+      left = (ARROW_IS_LEFT (i) && !is_rtl) ||
+             (!ARROW_IS_LEFT (i) && is_rtl);
+
+      if (priv->focus_tab &&
+          !gtk_notebook_search_page (notebook, priv->focus_tab,
+                                     left ? STEP_PREV : STEP_NEXT, TRUE))
+        {
+          state |= GTK_STATE_FLAG_INSENSITIVE;
+        }
+      else if (priv->in_child == i)
+        {
+          state |= GTK_STATE_FLAG_PRELIGHT;
+          if (priv->click_child == i)
+            state |= GTK_STATE_FLAG_ACTIVE;
+        }
+
+      gtk_css_node_set_state (priv->arrow_node[i], state);
+    }
+}
+
+static void
 gtk_notebook_state_flags_changed (GtkWidget     *widget,
                                   GtkStateFlags  previous_state)
 {
+  GtkNotebook *notebook = GTK_NOTEBOOK (widget);
+
+  update_tab_state (notebook);
+  update_arrow_state (notebook);
+
   if (!gtk_widget_is_sensitive (widget))
-    stop_scrolling (GTK_NOTEBOOK (widget));
+    stop_scrolling (notebook);
 }
 
 static gboolean
@@ -3602,11 +3707,85 @@ gtk_notebook_focus_out (GtkWidget     *widget,
 }
 
 static void
+update_arrow_nodes (GtkNotebook *notebook)
+{
+  GtkNotebookPrivate *priv = notebook->priv;
+  gboolean arrow[4];
+  gint i;
+
+  arrow[0] = priv->has_before_previous;
+  arrow[1] = priv->has_before_next;
+  arrow[2] = priv->has_after_previous;
+  arrow[3] = priv->has_after_next;
+
+  for (i = 0; i < 4; i++)
+    {
+      if (priv->scrollable && arrow[i])
+        {
+          if (priv->arrow_node[i] == NULL)
+            {
+              priv->arrow_node[i] = gtk_css_node_new ();
+              gtk_css_node_set_name (priv->arrow_node[i], I_("arrow"));
+              if (i == ARROW_LEFT_BEFORE || i == ARROW_LEFT_AFTER)
+                gtk_css_node_add_class (priv->arrow_node[i], g_quark_from_static_string ("down"));
+              else
+                gtk_css_node_add_class (priv->arrow_node[i], g_quark_from_static_string ("up"));
+              gtk_css_node_set_state (priv->arrow_node[i], gtk_css_node_get_state (priv->tabs_node));
+              g_signal_connect_object (priv->arrow_node[i], "style-changed", G_CALLBACK 
(node_style_changed_cb), notebook, 0);
+              switch (i)
+                {
+                case 0:
+                  if (priv->arrow_node[1])
+                    gtk_css_node_insert_before (priv->tabs_node, priv->arrow_node[0], priv->arrow_node[1]);
+                  else
+                    gtk_css_node_insert_before (priv->tabs_node, priv->arrow_node[0], 
gtk_css_node_get_first_child (priv->tabs_node));
+                  break;
+
+                case 1:
+                  if (priv->arrow_node[0])
+                    gtk_css_node_insert_after (priv->tabs_node, priv->arrow_node[1], priv->arrow_node[0]);
+                  else
+                    gtk_css_node_insert_before (priv->tabs_node, priv->arrow_node[1], 
gtk_css_node_get_first_child (priv->tabs_node));
+                  break;
+
+                case 2:
+                  if (priv->arrow_node[3])
+                    gtk_css_node_insert_before (priv->tabs_node, priv->arrow_node[2], priv->arrow_node[3]);
+                  else
+                    gtk_css_node_insert_after (priv->tabs_node, priv->arrow_node[2], 
gtk_css_node_get_last_child (priv->tabs_node));
+                  break;
+
+                case 3:
+                  if (priv->arrow_node[2])
+                    gtk_css_node_insert_after (priv->tabs_node, priv->arrow_node[3], priv->arrow_node[2]);
+                  else
+                    gtk_css_node_insert_after (priv->tabs_node, priv->arrow_node[3], 
gtk_css_node_get_last_child (priv->tabs_node));
+                  break;
+
+                default:
+                  g_assert_not_reached ();
+                  break;
+                }
+
+              g_object_unref (priv->arrow_node[i]);
+           }
+        }
+      else
+        {
+          if (priv->arrow_node[i])
+            {
+              gtk_css_node_set_parent (priv->arrow_node[i], NULL);
+              priv->arrow_node[i] = NULL;
+            }
+        }
+    }
+}
+
+static void
 gtk_notebook_style_updated (GtkWidget *widget)
 {
   GtkNotebook *notebook = GTK_NOTEBOOK (widget);
   GtkNotebookPrivate *priv = notebook->priv;
-
   gboolean has_before_previous;
   gboolean has_before_next;
   gboolean has_after_previous;
@@ -3624,6 +3803,9 @@ gtk_notebook_style_updated (GtkWidget *widget)
   priv->has_after_previous = has_after_previous;
   priv->has_after_next = has_after_next;
 
+  update_arrow_nodes (notebook);
+  update_arrow_state (notebook);
+
   GTK_WIDGET_CLASS (gtk_notebook_parent_class)->style_updated (widget);
 }
 
@@ -3818,7 +4000,7 @@ gtk_notebook_drag_motion (GtkWidget      *widget,
   arrow = gtk_notebook_get_arrow (notebook,
                                   x + allocation.x,
                                   y + allocation.y);
-  if (arrow)
+  if (arrow != ARROW_NONE)
     {
       priv->click_child = arrow;
       gtk_notebook_set_scroll_timer (notebook);
@@ -4653,6 +4835,7 @@ gtk_notebook_real_insert_page (GtkNotebook *notebook,
   GtkNotebookPage *page;
   gint nchildren;
   GList *list;
+  GtkCssNode *sibling;
 
   gtk_widget_freeze_child_notify (child);
 
@@ -4668,9 +4851,16 @@ gtk_notebook_real_insert_page (GtkNotebook *notebook,
   page->cssnode = gtk_css_node_new ();
   gtk_css_node_set_name (page->cssnode, I_("tab"));
   gtk_css_node_set_state (page->cssnode, gtk_css_node_get_state (priv->tabs_node));
-  gtk_css_node_insert_after (priv->tabs_node,
-                             page->cssnode,
-                             position > 0 ? GTK_NOTEBOOK_PAGE (g_list_nth (priv->children, position - 
1))->cssnode : NULL);
+  g_signal_connect_object (page->cssnode, "style-changed", G_CALLBACK (node_style_changed_cb), notebook, 0);
+
+  if (position > 0)
+    sibling = GTK_NOTEBOOK_PAGE (g_list_nth (priv->children, position - 1))->cssnode;
+  else if (priv->arrow_node[ARROW_RIGHT_BEFORE])
+    sibling = priv->arrow_node[ARROW_RIGHT_BEFORE];
+  else
+    sibling = priv->arrow_node[ARROW_LEFT_BEFORE];
+
+  gtk_css_node_insert_after (priv->tabs_node, page->cssnode, sibling);
 
   if (!tab_label)
     page->default_tab = TRUE;
@@ -4900,6 +5090,8 @@ gtk_notebook_redraw_arrows (GtkNotebook *notebook)
 {
   GtkNotebookPrivate *priv = notebook->priv;
 
+  update_arrow_state (notebook);
+
   if (gtk_widget_get_mapped (GTK_WIDGET (notebook)) &&
       gtk_notebook_show_arrows (notebook))
     {
@@ -5543,10 +5735,8 @@ gtk_notebook_draw_arrow (GtkNotebook      *notebook,
 {
   GtkNotebookPrivate *priv = notebook->priv;
   GtkStyleContext *context;
-  GtkStateFlags state = 0;
   GtkWidget *widget;
   GdkRectangle arrow_rect;
-  gboolean is_rtl, left;
   gint scroll_arrow_hlength;
   gint scroll_arrow_vlength;
   gint arrow_size;
@@ -5554,33 +5744,14 @@ gtk_notebook_draw_arrow (GtkNotebook      *notebook,
 
   widget = GTK_WIDGET (notebook);
   context = gtk_widget_get_style_context (widget);
-  state = gtk_widget_get_state_flags (widget);
 
   gtk_notebook_get_arrow_rect (notebook, &arrow_rect, nbarrow);
 
-  is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
-  left = (ARROW_IS_LEFT (nbarrow) && !is_rtl) ||
-         (!ARROW_IS_LEFT (nbarrow) && is_rtl);
-
   gtk_widget_style_get (widget,
                         "scroll-arrow-hlength", &scroll_arrow_hlength,
                         "scroll-arrow-vlength", &scroll_arrow_vlength,
                         NULL);
 
-  if (priv->focus_tab &&
-      !gtk_notebook_search_page (notebook, priv->focus_tab,
-                                 left ? STEP_PREV : STEP_NEXT, TRUE))
-    {
-      state |= GTK_STATE_FLAG_INSENSITIVE;
-    }
-  else if (priv->in_child == nbarrow)
-    {
-      state |= GTK_STATE_FLAG_PRELIGHT;
-
-      if (priv->click_child == nbarrow)
-        state |= GTK_STATE_FLAG_ACTIVE;
-    }
-
   if (priv->tab_pos == GTK_POS_LEFT ||
       priv->tab_pos == GTK_POS_RIGHT)
     {
@@ -5593,8 +5764,7 @@ gtk_notebook_draw_arrow (GtkNotebook      *notebook,
       arrow_size = scroll_arrow_hlength;
     }
 
-  gtk_style_context_save (context);
-  gtk_style_context_set_state (context, state);
+  gtk_style_context_save_to_node (context, priv->arrow_node[nbarrow]);
 
   gtk_render_arrow (context, cr, angle,
                     arrow_rect.x, arrow_rect.y,
@@ -7597,15 +7767,18 @@ gtk_notebook_set_scrollable (GtkNotebook *notebook,
 
   scrollable = (scrollable != FALSE);
 
-  if (scrollable != priv->scrollable)
-    {
-      priv->scrollable = scrollable;
+  if (priv->scrollable == scrollable)
+    return;
 
-      if (gtk_widget_get_visible (GTK_WIDGET (notebook)))
-        gtk_widget_queue_resize (GTK_WIDGET (notebook));
+  priv->scrollable = scrollable;
 
-      g_object_notify_by_pspec (G_OBJECT (notebook), properties[PROP_SCROLLABLE]);
-    }
+  update_arrow_nodes (notebook);
+  update_arrow_state (notebook);
+
+  if (gtk_widget_get_visible (GTK_WIDGET (notebook)))
+    gtk_widget_queue_resize (GTK_WIDGET (notebook));
+
+  g_object_notify_by_pspec (G_OBJECT (notebook), properties[PROP_SCROLLABLE]);
 }
 
 /**
@@ -8055,6 +8228,7 @@ gtk_notebook_child_reordered (GtkNotebook     *notebook,
 {
   GtkNotebookPrivate *priv = notebook->priv;
   GList *list;
+  GtkCssNode *sibling;
 
   list = g_list_find (priv->children, page);
 
@@ -8068,9 +8242,14 @@ gtk_notebook_child_reordered (GtkNotebook     *notebook,
       gtk_notebook_menu_item_create (notebook, list);
     }
 
-  gtk_css_node_insert_after (priv->tabs_node,
-                             page->cssnode,
-                             list->prev ? GTK_NOTEBOOK_PAGE (list->prev)->cssnode : NULL);
+  if (list->prev)
+    sibling = GTK_NOTEBOOK_PAGE (list->prev)->cssnode;
+  else if (priv->arrow_node[ARROW_RIGHT_BEFORE])
+    sibling = priv->arrow_node[ARROW_RIGHT_BEFORE];
+  else
+    sibling = priv->arrow_node[ARROW_LEFT_BEFORE];
+
+  gtk_css_node_insert_after (priv->tabs_node, page->cssnode, sibling);
   gtk_notebook_update_labels (notebook);
 }
 
diff --git a/gtk/theme/Adwaita/_common.scss b/gtk/theme/Adwaita/_common.scss
index 6b5ce55..ffb5d31 100644
--- a/gtk/theme/Adwaita/_common.scss
+++ b/gtk/theme/Adwaita/_common.scss
@@ -1545,10 +1545,9 @@ popover {
   &.osd { @extend %osd; }
 }
 
-/*****************
- * Notebooks and *
- * Tabs          *
- *****************/
+/*************
+ * Notebooks *
+ *************/
 
 notebook {
   -GtkNotebook-initial-gap: 10;
@@ -1753,20 +1752,26 @@ notebook {
       }
     }
   }
-  &.arrow {
-    color: $insensitive_fg_color;
-    &:hover { color: mix($fg_color, $insensitive_fg_color, 50%); }
-    &:active { color: $fg_color; }
-    &:insensitive {
-      color: transparentize($insensitive_fg_color,0.7);
-    }
-    &:backdrop {
-      color: transparentize($backdrop_fg_color,0.6);
+}
+
+notebook header tabs arrow.arrow {
+      -gtk-icon-source: -gtk-icontheme('pan-end-symbolic');
+      color: red; //$insensitive_fg_color;
+      &:hover {
+        color: mix($fg_color, $insensitive_fg_color, 50%);
+      }
+      &:active {
+        color: green; //$fg_color;
+      }
       &:insensitive {
-        color: $backdrop_insensitive_color;
+        color: blue; //transparentize($insensitive_fg_color,0.7);
+      }
+      &:backdrop {
+        color: brown; //transparentize($backdrop_fg_color,0.6);
+        &:insensitive {
+          color: $backdrop_insensitive_color;
+        }
       }
-    }
-  }
 }
 
 /**************


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