Bug 1144745 - Scale GTK widgets properly on HiDPI r=karlt
authorAndrew Comminos <andrew@morlunk.com>
Sun, 29 Mar 2015 18:06:00 +0200
changeset 238854 1381d4b4a461dfaef039be1dc91a070dd6c42e47
parent 238853 d286935cb11b55ee80a35aef3bd5137761954cc3
child 238855 c76e6b6d4591b51fc4c5e6a81a165518380bd7c9
push id28574
push userkwierso@gmail.com
push dateTue, 14 Apr 2015 00:13:27 +0000
treeherdermozilla-central@7f343964210b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskarlt
bugs1144745
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1144745 - Scale GTK widgets properly on HiDPI r=karlt
widget/gtk/nsNativeThemeGTK.cpp
widget/gtk/nsNativeThemeGTK.h
--- a/widget/gtk/nsNativeThemeGTK.cpp
+++ b/widget/gtk/nsNativeThemeGTK.cpp
@@ -27,16 +27,17 @@
 
 #include <gdk/gdkprivate.h>
 #include <gtk/gtk.h>
 
 #include "gfxContext.h"
 #include "gfxPlatformGtk.h"
 #include "gfxGdkNativeRenderer.h"
 #include <algorithm>
+#include <dlfcn.h>
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
 NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeGTK, nsNativeTheme, nsITheme,
                                                              nsIObserver)
 
 static int gLastGdkError;
@@ -83,16 +84,34 @@ nsNativeThemeGTK::RefreshWidgetWindow(ns
 
   nsViewManager* vm = shell->GetViewManager();
   if (!vm)
     return;
  
   vm->InvalidateAllViews();
 }
 
+gint
+nsNativeThemeGTK::GdkScaleFactor()
+{
+#if (MOZ_WIDGET_GTK >= 3)
+  // Since GDK 3.10
+  static auto sGdkScreenGetMonitorScaleFactorPtr = (gint (*)(GdkScreen*, gint))
+      dlsym(RTLD_DEFAULT, "gdk_screen_get_monitor_scale_factor");
+  if (sGdkScreenGetMonitorScaleFactorPtr) {
+      // FIXME: In the future, we'll want to fix this for GTK on Wayland which
+      // supports a variable scale factor per display.
+      GdkScreen *screen = gdk_screen_get_default();
+      return sGdkScreenGetMonitorScaleFactorPtr(screen, 0);
+  }
+#endif
+    return 1;
+}
+
+
 static bool IsFrameContentNodeInNamespace(nsIFrame *aFrame, uint32_t aNamespace)
 {
   nsIContent *content = aFrame ? aFrame->GetContent() : nullptr;
   if (!content)
     return false;
   return content->IsInNamespace(aNamespace);
 }
 
@@ -706,20 +725,20 @@ nsNativeThemeGTK::GetExtraSizeForWidget(
 {
   *aExtra = nsIntMargin(0,0,0,0);
   // Allow an extra one pixel above and below the thumb for certain
   // GTK2 themes (Ximian Industrial, Bluecurve, Misty, at least);
   // We modify the frame's overflow area.  See bug 297508.
   switch (aWidgetType) {
   case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
     aExtra->top = aExtra->bottom = 1;
-    return true;
+    break;
   case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
     aExtra->left = aExtra->right = 1;
-    return true;
+    break;
 
   // Include the indicator spacing (the padding around the control).
   case NS_THEME_CHECKBOX:
   case NS_THEME_RADIO:
     {
       gint indicator_size, indicator_spacing;
 
       if (aWidgetType == NS_THEME_CHECKBOX) {
@@ -727,38 +746,38 @@ nsNativeThemeGTK::GetExtraSizeForWidget(
       } else {
         moz_gtk_radio_get_metrics(&indicator_size, &indicator_spacing);
       }
 
       aExtra->top = indicator_spacing;
       aExtra->right = indicator_spacing;
       aExtra->bottom = indicator_spacing;
       aExtra->left = indicator_spacing;
-      return true;
+      break;
     }
   case NS_THEME_BUTTON :
     {
       if (IsDefaultButton(aFrame)) {
         // Some themes draw a default indicator outside the widget,
         // include that in overflow
         gint top, left, bottom, right;
         moz_gtk_button_get_default_overflow(&top, &left, &bottom, &right);
         aExtra->top = top;
         aExtra->right = right;
         aExtra->bottom = bottom;
         aExtra->left = left;
-        return true;
+        break;
       }
     }
   case NS_THEME_FOCUS_OUTLINE:
     {
       moz_gtk_get_focus_outline_size(&aExtra->left, &aExtra->top);
       aExtra->right = aExtra->left;
       aExtra->bottom = aExtra->top;
-      return true;
+      break;
     }
   case NS_THEME_TAB :
     {
       if (!IsSelectedTab(aFrame))
         return false;
 
       gint gap_height = moz_gtk_get_tab_thickness();
 
@@ -770,16 +789,21 @@ nsNativeThemeGTK::GetExtraSizeForWidget(
         aExtra->top = extra;
       } else {
         aExtra->bottom = extra;
       }
     }
   default:
     return false;
   }
+  aExtra->top *= GdkScaleFactor();
+  aExtra->right *= GdkScaleFactor();
+  aExtra->bottom *= GdkScaleFactor();
+  aExtra->left *= GdkScaleFactor();
+  return true;
 }
 
 NS_IMETHODIMP
 nsNativeThemeGTK::DrawWidgetBackground(nsRenderingContext* aContext,
                                        nsIFrame* aFrame,
                                        uint8_t aWidgetType,
                                        const nsRect& aRect,
                                        const nsRect& aDirtyRect)
@@ -796,16 +820,17 @@ nsNativeThemeGTK::DrawWidgetBackground(n
                             &flags))
     return NS_OK;
 
   gfxContext* ctx = aContext->ThebesContext();
   nsPresContext *presContext = aFrame->PresContext();
 
   gfxRect rect = presContext->AppUnitsToGfxUnits(aRect);
   gfxRect dirtyRect = presContext->AppUnitsToGfxUnits(aDirtyRect);
+  gint scaleFactor = GdkScaleFactor();
 
   // Align to device pixels where sensible
   // to provide crisper and faster drawing.
   // Don't snap if it's a non-unit scale factor. We're going to have to take
   // slow paths then in any case.
   bool snapXY = ctx->UserToDevicePixelSnapped(rect);
   if (snapXY) {
     // Leave rect in device coords but make dirtyRect consistent.
@@ -833,26 +858,29 @@ nsNativeThemeGTK::DrawWidgetBackground(n
                         int32_t(dirtyRect.Width()),
                         int32_t(dirtyRect.Height()));
   if (widgetRect.IsEmpty()
       || !drawingRect.IntersectRect(overflowRect, drawingRect))
     return NS_OK;
 
   // gdk rectangles are wrt the drawing rect.
 
-  GdkRectangle gdk_rect = {-drawingRect.x, -drawingRect.y,
-                           widgetRect.width, widgetRect.height};
+  GdkRectangle gdk_rect = {-drawingRect.x/scaleFactor,
+                           -drawingRect.y/scaleFactor,
+                           widgetRect.width/scaleFactor,
+                           widgetRect.height/scaleFactor};
 
   // translate everything so (0,0) is the top left of the drawingRect
   gfxContextAutoSaveRestore autoSR(ctx);
   gfxMatrix tm;
   if (!snapXY) { // else rects are in device coords
     tm = ctx->CurrentMatrix();
   }
   tm.Translate(rect.TopLeft() + gfxPoint(drawingRect.x, drawingRect.y));
+  tm.Scale(scaleFactor, scaleFactor); // Draw in GDK coords
   ctx->SetMatrix(tm);
 
   NS_ASSERTION(!IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType),
                "Trying to render an unsafe widget!");
 
   bool safeState = IsWidgetStateSafe(mSafeWidgetStates, aWidgetType, &state);
   if (!safeState) {
     gLastGdkError = 0;
@@ -1031,16 +1059,21 @@ nsNativeThemeGTK::GetWidgetPadding(nsDev
         if (aWidgetType == NS_THEME_MENUITEM)
           moz_gtk_menuitem_get_horizontal_padding(&horizontal_padding);
         else
           moz_gtk_checkmenuitem_get_horizontal_padding(&horizontal_padding);
 
         aResult->left += horizontal_padding;
         aResult->right += horizontal_padding;
 
+        aResult->top *= GdkScaleFactor();
+        aResult->right *= GdkScaleFactor();
+        aResult->bottom *= GdkScaleFactor();
+        aResult->left *= GdkScaleFactor();
+
         return true;
       }
   }
 
   return false;
 }
 
 bool
@@ -1292,16 +1325,19 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n
       gint expander_size;
 
       moz_gtk_get_treeview_expander_size(&expander_size);
       aResult->width = aResult->height = expander_size;
       *aIsOverridable = false;
     }
     break;
   }
+
+  *aResult = *aResult * GdkScaleFactor();
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNativeThemeGTK::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType, 
                                      nsIAtom* aAttribute, bool* aShouldRepaint)
 {
   // Some widget types just never change state.
--- a/widget/gtk/nsNativeThemeGTK.h
+++ b/widget/gtk/nsNativeThemeGTK.h
@@ -76,15 +76,16 @@ private:
   gint GetTabMarginPixels(nsIFrame* aFrame);
   bool GetGtkWidgetAndState(uint8_t aWidgetType, nsIFrame* aFrame,
                               GtkThemeWidgetType& aGtkWidgetType,
                               GtkWidgetState* aState, gint* aWidgetFlags);
   bool GetExtraSizeForWidget(nsIFrame* aFrame, uint8_t aWidgetType,
                                nsIntMargin* aExtra);
 
   void RefreshWidgetWindow(nsIFrame* aFrame);
+  gint GdkScaleFactor();
 
   uint8_t mDisabledWidgetTypes[32];
   uint8_t mSafeWidgetStates[1024];    // 256 widgets * 32 bits per widget
   static const char* sDisabledEngines[];
 };
 
 #endif