Bug 1433092 - Add spacing around titlebar buttons, r?jhorak draft
authorMartin Stransky <stransky@redhat.com>
Fri, 23 Feb 2018 10:37:07 +0100
changeset 760352 657bff48e845f8d642916518390744631196a909
parent 758791 f7c5598e45c323547dc6d030bf8442850c15813b
child 760354 74ffe22af1ed8309d5eba941230e0ad4ab6114b9
child 760425 a7dc7d4f834e66f772936a65eba8acb2ff443398
push id100604
push userstransky@redhat.com
push dateTue, 27 Feb 2018 11:12:23 +0000
reviewersjhorak
bugs1433092
milestone60.0a1
Bug 1433092 - Add spacing around titlebar buttons, r?jhorak GtkHeaderBar has property "spacing" which defines space between buttons at titlebar. Get this property and apply as margin to titlebar buttons when there's more than one. Also cache this value for furter use at titlebar metrics cache. MozReview-Commit-ID: J7qAIWEnK4Y
widget/gtk/gtk3drawing.cpp
widget/gtk/gtkdrawing.h
widget/gtk/nsLookAndFeel.cpp
--- a/widget/gtk/gtk3drawing.cpp
+++ b/widget/gtk/gtk3drawing.cpp
@@ -43,26 +43,29 @@ moz_gtk_get_tab_thickness(GtkStyleContex
 static gint
 moz_gtk_menu_item_paint(WidgetNodeType widget, cairo_t *cr, GdkRectangle* rect,
                         GtkWidgetState* state, GtkTextDirection direction);
 
 static GtkBorder
 GetMarginBorderPadding(GtkStyleContext* aStyle);
 
 static void
+Inset(GdkRectangle* rect, const GtkBorder& aBorder);
+
+static void
 InsetByMargin(GdkRectangle* rect, GtkStyleContext* style);
 
 static void
 moz_gtk_add_style_margin(GtkStyleContext* style,
                          gint* left, gint* top, gint* right, gint* bottom)
 {
     GtkBorder margin;
 
-    gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &margin);
-
+    gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
+                                 &margin);
     *left += margin.left;
     *right += margin.right;
     *top += margin.top;
     *bottom += margin.bottom;
 }
 
 static void
 moz_gtk_add_style_border(GtkStyleContext* style,
@@ -304,76 +307,202 @@ moz_gtk_splitter_get_metrics(gint orient
     } else {
         style = GetStyleContext(MOZ_GTK_SPLITTER_VERTICAL);
     }
     gtk_style_context_get_style(style, "handle_size", size, NULL);
     return MOZ_GTK_SUCCESS;
 }
 
 static void
-InitToolbarButtonMetrics(ToolbarButtonGTKMetrics *aButtonMetrics,
-                         WidgetNodeType aWidgetType)
+CalculateToolbarButtonMetrics(WidgetNodeType aWidgetType,
+                              ToolbarButtonGTKMetrics* aMetrics)
 {
-    GtkStyleContext* style = GetStyleContext(aWidgetType);
-
     gint iconWidth, iconHeight;
     if (!gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &iconWidth, &iconHeight)) {
         NS_WARNING("Failed to get Gtk+ icon size for titlebar button!");
 
         // Use some reasonable fallback size
         iconWidth = 16;
         iconHeight = 16;
     }
 
+    GtkStyleContext* style = GetStyleContext(aWidgetType);
     gint width = 0, height = 0;
     if (gtk_check_version(3, 20, 0) == nullptr) {
         gtk_style_context_get(style,  gtk_style_context_get_state(style),
                               "min-width", &width,
                               "min-height", &height, NULL);
     }
 
     // Cover cases when min-width/min-height is not set, it's invalid
     // or we're running on Gtk+ < 3.20.
     if (width < iconWidth)
         width = iconWidth;
     if (height < iconHeight)
         height = iconHeight;
 
     gint left = 0, top = 0, right = 0, bottom = 0;
-    moz_gtk_add_margin_border_padding(style, &left, &top, &right, &bottom);
-
+    moz_gtk_add_border_padding(style, &left, &top, &right, &bottom);
+
+    // Button size is calculated as min-width/height + border/padding.
     width += left + right;
     height += top + bottom;
-    aButtonMetrics->minSizeWithBorderMargin.width = width;
-    aButtonMetrics->minSizeWithBorderMargin.height = height;
-
-    // Get border size from gap between icon and button sizes.
-    // Buton size is calculated as min-width/height + border/padding.
-    aButtonMetrics->iconXPosition = (width - iconWidth) / 2;
-    aButtonMetrics->iconYPosition = (height - iconHeight) / 2;
+
+    // Place icon at button center.
+    aMetrics->iconXPosition = (width - iconWidth) / 2;
+    aMetrics->iconYPosition = (height - iconHeight) / 2;
+
+    aMetrics->minSizeWithBorderMargin.width = width;
+    aMetrics->minSizeWithBorderMargin.height = height;
+}
+
+// We support LTR layout only here for now.
+static void
+CalculateToolbarButtonSpacing(WidgetNodeType aWidgetType,
+                              ToolbarButtonGTKMetrics* aMetrics)
+{
+    GtkStyleContext* style = GetStyleContext(aWidgetType);
+    gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
+                                 &aMetrics->buttonMargin);
+
+    // Get titlebar spacing, a default one is 6 pixels (gtk/gtkheaderbar.c)
+    gint buttonSpacing = 6;
+    g_object_get(GetWidget(MOZ_GTK_HEADER_BAR),
+                 "spacing", &buttonSpacing, nullptr);
+
+    // We apply spacing as a margin equaly to both adjacent buttons.
+    buttonSpacing /= 2;
+
+    if (!aMetrics->firstButton) {
+      aMetrics->buttonMargin.left += buttonSpacing;
+    }
+    if (!aMetrics->lastButton) {
+      aMetrics->buttonMargin.right += buttonSpacing;
+    }
+
+    aMetrics->iconXPosition += aMetrics->buttonMargin.left;
+    aMetrics->iconYPosition += aMetrics->buttonMargin.top;
+
+    aMetrics->minSizeWithBorderMargin.width +=
+        aMetrics->buttonMargin.right + aMetrics->buttonMargin.left;
+    aMetrics->minSizeWithBorderMargin.height +=
+        aMetrics->buttonMargin.top + aMetrics->buttonMargin.bottom;
+}
+
+static int
+GetGtkHeaderBarButtonLayout(WidgetNodeType* aButtonLayout, int aMaxButtonNums)
+{
+  NS_ASSERTION(aMaxButtonNums >= TOOLBAR_BUTTONS,
+               "Requested number of buttons is higher than storage capacity!");
+
+  static auto sGtkHeaderBarGetDecorationLayoutPtr =
+    (const gchar* (*)(GtkWidget*))
+    dlsym(RTLD_DEFAULT, "gtk_header_bar_get_decoration_layout");
+
+  const gchar* decorationLayout = nullptr;
+  if (sGtkHeaderBarGetDecorationLayoutPtr) {
+      GtkWidget* headerBar = GetWidget(MOZ_GTK_HEADER_BAR);
+      decorationLayout = sGtkHeaderBarGetDecorationLayoutPtr(headerBar);
+      if (!decorationLayout) {
+          GtkSettings *settings = gtk_settings_get_for_screen(
+              gdk_screen_get_default());
+          g_object_get(settings, "gtk-decoration-layout",
+                       &decorationLayout,
+                       nullptr);
+      }
+  }
+
+  // Use a default layout
+  if (!decorationLayout) {
+      decorationLayout = "minimize,maximize,close";
+  }
+
+  // We support only default button order now:
+  // minimize/maximize/close
+  int activeButtonNums = 0;
+  if (strstr(decorationLayout, "minimize") != nullptr) {
+      aButtonLayout[activeButtonNums++] = MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE;
+  }
+  if (strstr(decorationLayout, "maximize") != nullptr) {
+      aButtonLayout[activeButtonNums++] = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE;
+  }
+  if (strstr(decorationLayout, "close") != nullptr) {
+      aButtonLayout[activeButtonNums++] = MOZ_GTK_HEADER_BAR_BUTTON_CLOSE;
+  }
+
+  return activeButtonNums;
+}
+
+static void
+EnsureToolbarMetrics(void)
+{
+  if (!sToolbarMetrics.initialized) {
+      // Make sure we have clean cache after theme reset, etc.
+      memset(&sToolbarMetrics, 0, sizeof(sToolbarMetrics));
+
+      // We're running on old Gtk+ version. Leave the cache empty
+      // which means all buttons are disabled.
+      if (gtk_check_version(3, 10, 0) != nullptr) {
+          sToolbarMetrics.initialized = true;
+          return;
+      }
+
+      // Calculate titlebar button visibility and positions.
+      WidgetNodeType aButtonLayout[TOOLBAR_BUTTONS];
+      int activeButtonNums =
+          GetGtkHeaderBarButtonLayout(aButtonLayout, TOOLBAR_BUTTONS);
+
+      for (int i = 0; i < activeButtonNums; i++) {
+          int buttonIndex = (aButtonLayout[i] - MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
+          ToolbarButtonGTKMetrics* metrics = sToolbarMetrics.button + buttonIndex;
+          metrics->visible = true;
+          // Mark first button
+          if (!i) {
+              metrics->firstButton = true;
+          }
+          // Mark last button.
+          if (i == (activeButtonNums-1)) {
+              metrics->lastButton = true;
+          }
+
+          CalculateToolbarButtonMetrics(aButtonLayout[i], metrics);
+          CalculateToolbarButtonSpacing(aButtonLayout[i], metrics);
+      }
+
+      sToolbarMetrics.initialized = true;
+  }
 }
 
 const ToolbarButtonGTKMetrics*
 GetToolbarButtonMetrics(WidgetNodeType aWidgetType)
 {
-    if (!sToolbarMetrics.initialized) {
-        for (int i = 0; i < TOOLBAR_BUTTONS; i++) {
-            InitToolbarButtonMetrics(sToolbarMetrics.button + i,
-                WidgetNodeType(int(MOZ_GTK_HEADER_BAR_BUTTON_CLOSE) + i));
-        }
-        sToolbarMetrics.initialized = true;
-    }
+    EnsureToolbarMetrics();
 
     int buttonIndex = (aWidgetType - MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
     NS_ASSERTION(buttonIndex >= 0 &&
                  buttonIndex <= TOOLBAR_BUTTONS,
                  "GetToolbarButtonMetrics(): Wrong titlebar button!");
     return sToolbarMetrics.button + buttonIndex;
 }
 
+bool
+IsToolbarButtonEnabled(WidgetNodeType aWidgetType)
+{
+    WidgetNodeType aButtonLayout[TOOLBAR_BUTTONS];
+    int activeButtonNums =
+        GetGtkHeaderBarButtonLayout(aButtonLayout, TOOLBAR_BUTTONS);
+
+    for (int i = 0; i < activeButtonNums; i++) {
+        if (aButtonLayout[i] == aWidgetType) {
+            return true;
+        }
+    }
+    return false;
+}
+
 static gint
 moz_gtk_window_paint(cairo_t *cr, GdkRectangle* rect,
                      GtkTextDirection direction)
 {
     GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW, direction);
 
     gtk_style_context_save(style);
     gtk_style_context_add_class(style, GTK_STYLE_CLASS_BACKGROUND);
@@ -439,18 +568,23 @@ moz_gtk_button_paint(cairo_t *cr, GdkRec
 
 static gint
 moz_gtk_header_bar_button_paint(cairo_t *cr, GdkRectangle* rect,
                                 GtkWidgetState* state,
                                 GtkReliefStyle relief,
                                 WidgetNodeType aWidgetType,
                                 GtkTextDirection direction)
 {
+    // We need to inset our calculated margin because it also
+    // contains titlebar button spacing.
+    const ToolbarButtonGTKMetrics* metrics =
+        GetToolbarButtonMetrics(aWidgetType);
+    Inset(rect, metrics->buttonMargin);
+
     GtkWidget *widget = GetWidget(aWidgetType);
-    InsetByMargin(rect, gtk_widget_get_style_context(widget));
     moz_gtk_button_paint(cr, rect, state, relief, widget, direction);
 
     GtkWidget* iconWidget = gtk_bin_get_child(GTK_BIN(widget));
     cairo_surface_t *surface = GetWidgetIconSurface(iconWidget, state->scale);
 
     if (surface) {
         GtkStyleContext* style = gtk_widget_get_style_context(iconWidget);
         GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
@@ -641,17 +775,17 @@ static MozGtkSize
 GetMinMarginBox(WidgetNodeType aNodeType)
 {
     gint width, height;
     moz_gtk_get_widget_min_size(aNodeType, &width, &height);
     return {width, height};
 }
 
 static void
-Inset(GdkRectangle* rect, GtkBorder& aBorder)
+Inset(GdkRectangle* rect, const GtkBorder& aBorder)
 {
     MOZ_ASSERT(rect);
     rect->x += aBorder.left;
     rect->y += aBorder.top;
     rect->width -= aBorder.left + aBorder.right;
     rect->height -= aBorder.top + aBorder.bottom;
 }
 
--- a/widget/gtk/gtkdrawing.h
+++ b/widget/gtk/gtkdrawing.h
@@ -82,18 +82,22 @@ typedef struct {
 typedef struct {
   bool initialized;
   MozGtkSize minSizeWithBorder;
   GtkBorder borderAndPadding;
 } ToggleGTKMetrics;
 
 typedef struct {
   MozGtkSize minSizeWithBorderMargin;
+  GtkBorder  buttonMargin;
   gint iconXPosition;
   gint iconYPosition;
+  bool visible;
+  bool firstButton;
+  bool lastButton;
 } ToolbarButtonGTKMetrics;
 
 #define TOOLBAR_BUTTONS 4
 typedef struct {
   bool initialized;
   ToolbarButtonGTKMetrics button[TOOLBAR_BUTTONS];
 } ToolbarGTKMetrics;
 
@@ -571,9 +575,14 @@ moz_gtk_get_tab_thickness(WidgetNodeType
 
 
 /**
  * Get ToolbarButtonGTKMetrics for recent theme.
  */
 const ToolbarButtonGTKMetrics*
 GetToolbarButtonMetrics(WidgetNodeType aWidgetType);
 
+/* Get toolbar button state.
+*/
+bool
+IsToolbarButtonEnabled(WidgetNodeType aWidgetType);
+
 #endif
--- a/widget/gtk/nsLookAndFeel.cpp
+++ b/widget/gtk/nsLookAndFeel.cpp
@@ -21,18 +21,16 @@
 #include "ScreenHelperGTK.h"
 
 #include "gtkdrawing.h"
 #include "nsStyleConsts.h"
 #include "gfxFontConstants.h"
 #include "WidgetUtils.h"
 #include "nsWindow.h"
 
-#include <dlfcn.h>
-
 #include "mozilla/gfx/2D.h"
 
 #include <cairo-gobject.h>
 #include "WidgetStyleCache.h"
 #include "prenv.h"
 
 using mozilla::LookAndFeel;
 
@@ -1084,45 +1082,22 @@ nsLookAndFeel::EnsureInit()
     g_object_unref(labelWidget);
 
     // Require GTK 3.10 for GtkHeaderBar support and compatible window manager.
     mCSDAvailable = (gtk_check_version(3, 10, 0) == nullptr &&
         nsWindow::GetCSDSupportLevel() != nsWindow::CSD_SUPPORT_NONE);
 
     // We need to initialize whole CSD config explicitly because it's queried
     // as -moz-gtk* media features.
-    mCSDCloseButton = true;
-    mCSDMaximizeButton = false;
-    mCSDMinimizeButton = false;
-
-    if (mCSDAvailable) {
-        static auto sGtkHeaderBarGetDecorationLayoutPtr =
-          (const gchar* (*)(GtkWidget*))
-          dlsym(RTLD_DEFAULT, "gtk_header_bar_get_decoration_layout");
-
-        if (sGtkHeaderBarGetDecorationLayoutPtr) {
-            GtkWidget* headerBar = GetWidget(MOZ_GTK_HEADER_BAR);
-            const gchar* decorationLayout =
-                sGtkHeaderBarGetDecorationLayoutPtr(headerBar);
-            if (!decorationLayout) {
-                g_object_get(settings, "gtk-decoration-layout",
-                             &decorationLayout,
-                             nullptr);
-            }
-
-            if (decorationLayout) {
-                mCSDCloseButton =
-                    (strstr(decorationLayout, "close") != nullptr);
-                mCSDMaximizeButton =
-                    (strstr(decorationLayout, "maximize") != nullptr);
-                mCSDMinimizeButton =
-                    (strstr(decorationLayout, "minimize") != nullptr);
-            }
-        }
-    }
+    mCSDCloseButton =
+        IsToolbarButtonEnabled(MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
+    mCSDMinimizeButton =
+        IsToolbarButtonEnabled(MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE);
+    mCSDMaximizeButton =
+        IsToolbarButtonEnabled(MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE);
 }
 
 // virtual
 char16_t
 nsLookAndFeel::GetPasswordCharacterImpl()
 {
     EnsureInit();
     return mInvisibleCharacter;