Bug 1283299 - Part 6: Implement titlebar and titlebar button drawing using GtkHeaderBar. r?karlt draft
authorAndrew Comminos <andrew@comminos.com>
Tue, 05 Jul 2016 14:12:42 -0400
changeset 384223 74b7a833bc8e98b22313405e6fce9ee38c4f022e
parent 384222 813dfd170698cbcf9beba959e010196fe939702f
child 384224 1da2d6f52f42e55adf029a354e0c7371b4a770cf
push id22212
push userbmo:andrew@comminos.com
push dateTue, 05 Jul 2016 20:52:16 +0000
reviewerskarlt
bugs1283299
milestone50.0a1
Bug 1283299 - Part 6: Implement titlebar and titlebar button drawing using GtkHeaderBar. r?karlt MozReview-Commit-ID: Ieo876WN4FW
widget/gtk/WidgetStyleCache.cpp
widget/gtk/WidgetStyleCache.h
widget/gtk/gtk3drawing.cpp
widget/gtk/gtkdrawing.h
widget/gtk/nsNativeThemeGTK.cpp
--- a/widget/gtk/WidgetStyleCache.cpp
+++ b/widget/gtk/WidgetStyleCache.cpp
@@ -123,16 +123,50 @@ CreateTooltipWidget()
              "CreateTooltipWidget should be used for Gtk < 3.20 only.");
   GtkWidget* widget = CreateWindowWidget(MOZ_GTK_WINDOW);
   GtkStyleContext* style = gtk_widget_get_style_context(widget);
   gtk_style_context_add_class(style, GTK_STYLE_CLASS_TOOLTIP);
   return widget;
 }
 
 static GtkWidget*
+CreateHeaderBar()
+{
+  MOZ_ASSERT(gtk_check_version(3, 10, 0) == nullptr,
+             "GtkHeaderBar is only available on GTK 3.10+.");
+
+  static auto sGtkHeaderBarNewPtr = (GtkWidget* (*)())
+    dlsym(RTLD_DEFAULT, "gtk_header_bar_new");
+  static const char* MOZ_GTK_STYLE_CLASS_TITLEBAR = "titlebar";
+
+  GtkWidget* widget = sGtkHeaderBarNewPtr();
+  AddToWindowContainer(widget);
+
+  GtkStyleContext* style = gtk_widget_get_style_context(widget);
+  gtk_style_context_add_class(style, MOZ_GTK_STYLE_CLASS_TITLEBAR);
+
+  return widget;
+}
+
+static GtkWidget*
+CreateHeaderBarButton()
+{
+  static const char* MOZ_GTK_STYLE_CLASS_TITLEBUTTON = "titlebutton";
+
+  GtkWidget* widget = gtk_button_new();
+  gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_HEADER_BAR)), widget);
+
+  GtkStyleContext* style = gtk_widget_get_style_context(widget);
+  gtk_style_context_add_class(style, MOZ_GTK_STYLE_CLASS_TITLEBUTTON);
+
+  return widget;
+
+}
+
+static GtkWidget*
 CreateWidget(WidgetNodeType aWidgetType)
 {
   switch (aWidgetType) {
     case MOZ_GTK_WINDOW:
     case MOZ_GTK_WINDOW_CSD:
     case MOZ_GTK_WINDOW_SOLID_CSD:
       return CreateWindowWidget(aWidgetType);
     case MOZ_GTK_WINDOW_CONTAINER:
@@ -152,16 +186,20 @@ CreateWidget(WidgetNodeType aWidgetType)
     case MOZ_GTK_MENUBAR:
       return CreateMenuBarWidget();
     case MOZ_GTK_MENUPOPUP:
       return CreateMenuPopupWidget();
     case MOZ_GTK_MENUBARITEM:
       return CreateMenuItemWidget(MOZ_GTK_MENUBAR);
     case MOZ_GTK_MENUITEM:
       return CreateMenuItemWidget(MOZ_GTK_MENUPOPUP);
+    case MOZ_GTK_HEADER_BAR:
+      return CreateHeaderBar();
+    case MOZ_GTK_HEADER_BAR_BUTTON:
+      return CreateHeaderBarButton();
     default:
       /* Not implemented */
       return nullptr;
   }
 }
 
 GtkWidget*
 GetWidget(WidgetNodeType aWidgetType)
@@ -381,16 +419,22 @@ ResetWidgetCache(void)
   mozilla::PodArrayZero(sWidgetStorage);
 }
 
 GtkStyleContext*
 ClaimStyleContext(WidgetNodeType aNodeType, GtkTextDirection aDirection,
                   GtkStateFlags aStateFlags, StyleFlags aFlags)
 {
   MOZ_ASSERT(!sStyleContextNeedsRestore);
+
+  GtkWidget* window = GetWidget(MOZ_GTK_WINDOW);
+  GtkStyleContext* windowStyle = gtk_widget_get_style_context(window);
+  if (aFlags & MOZ_WINDOW_MAXIMIZED)
+    gtk_style_context_add_class(windowStyle, "maximized");
+
   GtkStyleContext* style;
   if (gtk_check_version(3, 20, 0) != nullptr) {
     style = GetWidgetStyleInternal(aNodeType);
   } else {
     style = GetCssNodeStyleInternal(aNodeType);
   }
 #ifdef DEBUG
   MOZ_ASSERT(!sCurrentStyleContext);
@@ -425,9 +469,13 @@ ReleaseStyleContext(GtkStyleContext* aSt
   if (sStyleContextNeedsRestore) {
     gtk_style_context_restore(aStyleContext);
   }
   sStyleContextNeedsRestore = false;
 #ifdef DEBUG
   MOZ_ASSERT(sCurrentStyleContext == aStyleContext);
   sCurrentStyleContext = nullptr;
 #endif
+
+  GtkWidget* window = GetWidget(MOZ_GTK_WINDOW);
+  GtkStyleContext* style = gtk_widget_get_style_context(window);
+  gtk_style_context_remove_class(style, "maximized");
 }
--- a/widget/gtk/WidgetStyleCache.h
+++ b/widget/gtk/WidgetStyleCache.h
@@ -10,17 +10,18 @@
 
 #include <gtk/gtk.h>
 #include "gtkdrawing.h"
 
 
 typedef unsigned StyleFlags;
 enum : StyleFlags {
   NO_STYLE_FLAGS,
-  WHATEVER_MIGHT_BE_NEEDED = 1U << 0,
+  /* Sets the "maximized" style class on the window. */
+  MOZ_WINDOW_MAXIMIZED = 1U << 0,
 };
 
 GtkWidget*
 GetWidget(WidgetNodeType aNodeType);
 
 /*
  * Return a new style context based on aWidget, as a child of aParentStyle.
  * If aWidget still has a floating reference, then it is sunk and released.
--- a/widget/gtk/gtk3drawing.cpp
+++ b/widget/gtk/gtk3drawing.cpp
@@ -854,16 +854,34 @@ moz_gtk_button_paint(cairo_t *cr, GdkRec
         height -= (border.top + border.bottom);
         gtk_render_focus(style, cr, x, y, width, height);
     }
     gtk_style_context_restore(style);
     return MOZ_GTK_SUCCESS;
 }
 
 static gint
+moz_gtk_header_bar_button_paint(cairo_t *cr, GdkRectangle* rect,
+                                GtkWidgetState* state,
+                                GtkReliefStyle relief, GtkWidget* widget,
+                                GtkTextDirection direction)
+{
+  GtkBorder margin;
+  GtkStyleContext* style = gtk_widget_get_style_context(widget);
+  gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &margin);
+
+  rect->x += margin.left;
+  rect->y += margin.top;
+  rect->width -= margin.left + margin.right;
+  rect->height -= margin.top + margin.bottom;
+
+  return moz_gtk_button_paint(cr, rect, state, relief, widget, direction);
+}
+
+static gint
 moz_gtk_toggle_paint(cairo_t *cr, GdkRectangle* rect,
                      GtkWidgetState* state,
                      gboolean selected, gboolean inconsistent,
                      gboolean isradio, GtkTextDirection direction)
 {
     GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
     gint indicator_size, indicator_spacing;
     gint x, y, width, height;
@@ -2579,16 +2597,39 @@ moz_gtk_info_bar_paint(cairo_t *cr, GdkR
                           rect->height);
     gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
 
     gtk_style_context_restore(style);
 
     return MOZ_GTK_SUCCESS;
 }
 
+static gint
+moz_gtk_header_bar_paint(cairo_t *cr, GdkRectangle* rect, GtkWidgetState* state,
+                         GtkCSDFlags flags)
+{
+    GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+    GtkStyleContext *style;
+    StyleFlags style_flags = 0;
+
+    if (flags & MOZ_GTK_CSD_MAXIMIZED)
+        style_flags |= MOZ_WINDOW_MAXIMIZED;
+
+    style = ClaimStyleContext(MOZ_GTK_HEADER_BAR, GTK_TEXT_DIR_LTR,
+                              state_flags, style_flags);
+
+    gtk_render_background(style, cr, rect->x, rect->y, rect->width,
+                          rect->height);
+    gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+
+    ReleaseStyleContext(style);
+
+    return MOZ_GTK_SUCCESS;
+}
+
 static void
 moz_gtk_add_style_border(GtkStyleContext* style,
                          gint* left, gint* top, gint* right, gint* bottom)
 {
     GtkBorder border;
 
     gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border);
 
@@ -2823,16 +2864,33 @@ moz_gtk_get_widget_border(WidgetNodeType
     case MOZ_GTK_TOOLTIP:
         {
             style = ClaimStyleContext(MOZ_GTK_TOOLTIP);
             moz_gtk_add_style_border(style, left, top, right, bottom);
             moz_gtk_add_style_padding(style, left, top, right, bottom);
             ReleaseStyleContext(style);
             return MOZ_GTK_SUCCESS;
         }
+    case MOZ_GTK_HEADER_BAR:
+        {
+            style = ClaimStyleContext(MOZ_GTK_HEADER_BAR);
+            moz_gtk_add_style_border(style, left, top, right, bottom);
+            moz_gtk_add_style_padding(style, left, top, right, bottom);
+            ReleaseStyleContext(style);
+            return MOZ_GTK_SUCCESS;
+        }
+    case MOZ_GTK_HEADER_BAR_BUTTON:
+        {
+            style = ClaimStyleContext(MOZ_GTK_HEADER_BAR_BUTTON);
+            moz_gtk_add_style_border(style, left, top, right, bottom);
+            moz_gtk_add_style_padding(style, left, top, right, bottom);
+            moz_gtk_add_style_margin(style, left, top, right, bottom);
+            ReleaseStyleContext(style);
+            return MOZ_GTK_SUCCESS;
+        }
     case MOZ_GTK_WINDOW_DECORATION:
         {
             moz_gtk_get_window_decoration_extents(top, right, bottom, left);
 
             style = ClaimStyleContext(MOZ_GTK_WINDOW_DECORATION);
             moz_gtk_add_style_border(style, left, top, right, bottom);
             moz_gtk_add_style_padding(style, left, top, right, bottom);
             ReleaseStyleContext(style);
@@ -3181,16 +3239,22 @@ moz_gtk_widget_paint(WidgetNodeType widg
                                         (GtkReliefStyle) flags,
                                         gToggleButtonWidget, direction);
         }
         ensure_button_widget();
         return moz_gtk_button_paint(cr, rect, state,
                                     (GtkReliefStyle) flags, gButtonWidget,
                                     direction);
         break;
+    case MOZ_GTK_HEADER_BAR_BUTTON:
+        return moz_gtk_header_bar_button_paint(cr, rect, state,
+                                               (GtkReliefStyle) flags,
+                                               GetWidget(MOZ_GTK_HEADER_BAR_BUTTON),
+                                               direction);
+        break;
     case MOZ_GTK_CHECKBUTTON:
     case MOZ_GTK_RADIOBUTTON:
         return moz_gtk_toggle_paint(cr, rect, state,
                                     !!(flags & MOZ_GTK_WIDGET_CHECKED),
                                     !!(flags & MOZ_GTK_WIDGET_INCONSISTENT),
                                     (widget == MOZ_GTK_RADIOBUTTON),
                                     direction);
         break;
@@ -3360,16 +3424,20 @@ moz_gtk_widget_paint(WidgetNodeType widg
         return moz_gtk_hpaned_paint(cr, rect, state);
         break;
     case MOZ_GTK_WINDOW:
         return moz_gtk_window_paint(cr, rect, direction);
         break;
     case MOZ_GTK_INFO_BAR:
         return moz_gtk_info_bar_paint(cr, rect, state);
         break;
+    case MOZ_GTK_HEADER_BAR:
+        return moz_gtk_header_bar_paint(cr, rect, state,
+                                        (GtkCSDFlags) flags);
+        break;
     case MOZ_GTK_WINDOW_DECORATION:
         return moz_gtk_window_decoration_paint(cr, rect, direction);
         break;
     case MOZ_GTK_WINDOW_DECORATION_SOLID:
         return moz_gtk_window_decoration_solid_paint(cr, rect, direction);
         break;
     default:
         g_warning("Unknown widget type: %d", widget);
--- a/widget/gtk/gtkdrawing.h
+++ b/widget/gtk/gtkdrawing.h
@@ -64,16 +64,21 @@ typedef enum {
   /* bottom tabs */
   MOZ_GTK_TAB_BOTTOM          = 1 << 8,
   /* the first tab in the group */
   MOZ_GTK_TAB_FIRST           = 1 << 9,
   /* the selected tab */
   MOZ_GTK_TAB_SELECTED        = 1 << 10
 } GtkTabFlags;
 
+// Flags used when drawing window decorations.
+typedef enum {
+  MOZ_GTK_CSD_MAXIMIZED = 1 << 1
+} GtkCSDFlags;
+
 /* function type for moz_gtk_enable_style_props */
 typedef gint (*style_prop_t)(GtkStyle*, const gchar*, gint);
 
 /*** result/error codes ***/
 #define MOZ_GTK_SUCCESS 0
 #define MOZ_GTK_UNKNOWN_WIDGET -1
 #define MOZ_GTK_UNSAFE_THEME -2
 
@@ -207,16 +212,20 @@ typedef enum {
   /* Window with the 'csd' style class. */
   MOZ_GTK_WINDOW_SOLID_CSD,
   /* Client-side window decoration node. Available on GTK 3.20+. */
   MOZ_GTK_WINDOW_DECORATION,
   /* Solid client-side window decoration node. Available on GTK 3.20+. */
   MOZ_GTK_WINDOW_DECORATION_SOLID,
   /* Paints a GtkInfoBar, for notifications. */
   MOZ_GTK_INFO_BAR,
+  /* Paints a GtkHeaderBar */
+  MOZ_GTK_HEADER_BAR,
+  /* Paints a GtkHeaderBar title button */
+  MOZ_GTK_HEADER_BAR_BUTTON,
 
   MOZ_GTK_WIDGET_NODE_COUNT
 } WidgetNodeType;
 
 /*** General library functions ***/
 /**
  * Initializes the drawing library.  You must call this function
  * prior to using any other functionality.
--- a/widget/gtk/nsNativeThemeGTK.cpp
+++ b/widget/gtk/nsNativeThemeGTK.cpp
@@ -701,16 +701,30 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u
     break;
   case NS_THEME_WINDOW:
   case NS_THEME_DIALOG:
     aGtkWidgetType = MOZ_GTK_WINDOW;
     break;
   case NS_THEME_GTK_INFO_BAR:
     aGtkWidgetType = MOZ_GTK_INFO_BAR;
     break;
+  case NS_THEME_WINDOW_TITLEBAR:
+  case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+    aGtkWidgetType = MOZ_GTK_HEADER_BAR;
+    if (aWidgetFlags) {
+      *aWidgetFlags = aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED ?
+                      MOZ_GTK_CSD_MAXIMIZED : 0;
+    }
+    break;
+  case NS_THEME_WINDOW_BUTTON_CLOSE:
+  case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+  case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+  case NS_THEME_WINDOW_BUTTON_RESTORE:
+    aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON;
+    break;
   case NS_THEME_GTK_WINDOW_DECORATION:
   {
     nsWindow* window = static_cast<nsWindow*>(aFrame->GetNearestWidget());
     if (window && !window->UseSolidCSD()) {
       aGtkWidgetType = MOZ_GTK_WINDOW_DECORATION;
     } else {
       aGtkWidgetType = MOZ_GTK_WINDOW_DECORATION_SOLID;
     }
@@ -1605,16 +1619,20 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n
   case NS_THEME_CHECKBOX_CONTAINER:
   case NS_THEME_RADIO_CONTAINER:
   case NS_THEME_CHECKBOX_LABEL:
   case NS_THEME_RADIO_LABEL:
   case NS_THEME_BUTTON:
   case NS_THEME_MENULIST:
   case NS_THEME_TOOLBARBUTTON:
   case NS_THEME_TREEHEADERCELL:
+  case NS_THEME_WINDOW_BUTTON_CLOSE:
+  case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+  case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+  case NS_THEME_WINDOW_BUTTON_RESTORE:
     {
       if (aWidgetType == NS_THEME_MENULIST) {
         // Include the arrow size.
         moz_gtk_get_arrow_size(MOZ_GTK_DROPDOWN,
                                &aResult->width, &aResult->height);
       }
       // else the minimum size is missing consideration of container
       // descendants; the value returned here will not be helpful, but the
@@ -1872,16 +1890,27 @@ nsNativeThemeGTK::ThemeSupportsWidget(ns
   case NS_THEME_WINDOW:
   case NS_THEME_DIALOG:
 #if (MOZ_WIDGET_GTK == 3)
   case NS_THEME_GTK_INFO_BAR:
   case NS_THEME_GTK_WINDOW_DECORATION:
 #endif
     return !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
 
+  case NS_THEME_WINDOW_BUTTON_CLOSE:
+  case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+  case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+  case NS_THEME_WINDOW_BUTTON_RESTORE:
+  case NS_THEME_WINDOW_TITLEBAR:
+  case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+    // GtkHeaderBar is available on GTK 3.10+, which is used for styling
+    // title bars and title buttons.
+    return gtk_check_version(3, 10, 0) == nullptr &&
+           !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
+
   case NS_THEME_MENULIST_BUTTON:
     if (aFrame && aFrame->GetWritingMode().IsVertical()) {
       return false;
     }
     // "Native" dropdown buttons cause padding and margin problems, but only
     // in HTML so allow them in XUL.
     return (!aFrame || IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) &&
            !IsWidgetStyled(aPresContext, aFrame, aWidgetType);