Bug 1464722 part 4 - Render custom scrollbars on cocoa widget. r=spohl
authorXidorn Quan <me@upsuper.org>
Fri, 22 Jun 2018 14:17:22 +1000
changeset 423985 664fc5136a49ef5f0178073ffd1f00bc95663472
parent 423984 cea8532fd3f4741e944ec511151a9b3fab4056bc
child 423986 a8b609fb90ade459a1a79fc495045401230e8169
push id34197
push usercsabou@mozilla.com
push dateThu, 28 Jun 2018 09:44:02 +0000
treeherdermozilla-central@db455160668d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersspohl
bugs1464722
milestone63.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 1464722 part 4 - Render custom scrollbars on cocoa widget. r=spohl MozReview-Commit-ID: ITzdevItp1d
widget/cocoa/nsNativeThemeCocoa.h
widget/cocoa/nsNativeThemeCocoa.mm
--- a/widget/cocoa/nsNativeThemeCocoa.h
+++ b/widget/cocoa/nsNativeThemeCocoa.h
@@ -227,24 +227,29 @@ public:
   struct ScrollbarParams {
     ScrollbarParams()
       : overlay(false)
       , rolledOver(false)
       , small(false)
       , horizontal(false)
       , rtl(false)
       , onDarkBackground(false)
+      , custom(false)
     {}
 
     bool overlay : 1;
     bool rolledOver : 1;
     bool small : 1;
     bool horizontal : 1;
     bool rtl : 1;
     bool onDarkBackground : 1;
+    bool custom : 1;
+    // Two colors only used when custom is true.
+    nscolor trackColor = NS_RGBA(0, 0, 0, 0);
+    nscolor faceColor = NS_RGBA(0, 0, 0, 0);
   };
 
   enum Widget : uint8_t {
     eColorFill,                    // mozilla::gfx::Color
     eSheetBackground,
     eDialogBackground,
     eMenuBackground,               // MenuBackgroundParams
     eMenuIcon,                     // MenuIconParams
--- a/widget/cocoa/nsNativeThemeCocoa.mm
+++ b/widget/cocoa/nsNativeThemeCocoa.mm
@@ -26,16 +26,17 @@
 #include "nsPresContext.h"
 #include "nsGkAtoms.h"
 #include "nsCocoaFeatures.h"
 #include "nsCocoaWindow.h"
 #include "nsNativeThemeColors.h"
 #include "nsIScrollableFrame.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/Range.h"
+#include "mozilla/RelativeLuminanceUtils.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLMeterElement.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "nsLookAndFeel.h"
 #include "VibrancyManager.h"
 
 #include "gfxContext.h"
 #include "gfxQuartzSurface.h"
@@ -1356,16 +1357,36 @@ nsNativeThemeCocoa::ComputeMenuItemParam
 }
 
 static void
 SetCGContextFillColor(CGContextRef cgContext, const Color& aColor)
 {
   CGContextSetRGBFillColor(cgContext, aColor.r, aColor.g, aColor.b, aColor.a);
 }
 
+static void
+SetCGContextFillColor(CGContextRef cgContext, nscolor aColor)
+{
+  CGContextSetRGBFillColor(cgContext,
+                           NS_GET_R(aColor) / 255.0f,
+                           NS_GET_G(aColor) / 255.0f,
+                           NS_GET_B(aColor) / 255.0f,
+                           NS_GET_A(aColor) / 255.0f);
+}
+
+static void
+SetCGContextStrokeColor(CGContextRef cgContext, nscolor aColor)
+{
+  CGContextSetRGBStrokeColor(cgContext,
+                             NS_GET_R(aColor) / 255.0f,
+                             NS_GET_G(aColor) / 255.0f,
+                             NS_GET_B(aColor) / 255.0f,
+                             NS_GET_A(aColor) / 255.0f);
+}
+
 void
 nsNativeThemeCocoa::DrawMenuItem(CGContextRef cgContext,
                                  const CGRect& inBoxRect,
                                  const MenuItemParams& aParams)
 {
   if (aParams.vibrancyColor) {
     SetCGContextFillColor(cgContext, *aParams.vibrancyColor);
     CGContextFillRect(cgContext, inBoxRect);
@@ -2689,38 +2710,95 @@ nsNativeThemeCocoa::DrawResizer(CGContex
   drawInfo.size = kHIThemeGrowBoxSizeNormal;
 
   RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo,
                                   aIsRTL);
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
+static nscolor
+GetAutoScrollbarTrackColor(ComputedStyle* aStyle)
+{
+  // Use the default scrollbar color. XXX Can we get it from the system?
+  return NS_RGB(0xFA, 0xFA, 0xFA);
+}
+
+static nscolor
+GetAutoScrollbarFaceColor(ComputedStyle* aStyle)
+{
+  // Use the default scrollbar color. We may want to derive from track
+  // color at some point.
+  return NS_RGB(0xC1, 0xC1, 0xC1);
+}
+
 nsNativeThemeCocoa::ScrollbarParams
 nsNativeThemeCocoa::ComputeScrollbarParams(nsIFrame* aFrame, bool aIsHorizontal)
 {
   ScrollbarParams params;
   params.overlay = nsLookAndFeel::UseOverlayScrollbars();
   params.rolledOver = IsParentScrollbarRolledOver(aFrame);
   nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
   params.small =
     (scrollbarFrame &&
      scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
   params.rtl = IsFrameRTL(aFrame);
   params.horizontal = aIsHorizontal;
   params.onDarkBackground = IsDarkBackground(aFrame);
+  // Don't use custom scrollbars for overlay scrollbars since they are
+  // generally good enough for use cases of custom scrollbars.
+  if (!params.overlay &&
+      aFrame->StyleUserInterface()->HasCustomScrollbars()) {
+    ComputedStyle* cs = aFrame->Style();
+    params.custom = true;
+    params.trackColor = GetScrollbarTrackColor(cs, &GetAutoScrollbarTrackColor);
+    params.faceColor = GetScrollbarFaceColor(cs, &GetAutoScrollbarFaceColor);
+  }
   return params;
 }
 
 void
 nsNativeThemeCocoa::DrawScrollbarThumb(CGContextRef cgContext,
                                        const CGRect& inBoxRect,
                                        ScrollbarParams aParams)
 {
   CGRect drawRect = inBoxRect;
+  if (aParams.custom) {
+    const CGFloat kWidthRatio = 8.0f / 15.0f;
+    const CGFloat kLengthReductionRatio = 2.0f / 15.0f;
+    const CGFloat kOrthogonalDirOffset = 4.0f / 15.0f;
+    const CGFloat kParallelDirOffset = 1.0f / 15.0f;
+    CGFloat baseSize, cornerWidth;
+    CGRect thumbRect = inBoxRect;
+    if (aParams.horizontal) {
+      baseSize = inBoxRect.size.height;
+      thumbRect.size.height *= kWidthRatio;
+      thumbRect.size.width -= baseSize * kLengthReductionRatio;
+      thumbRect.origin.y += baseSize * kOrthogonalDirOffset;
+      thumbRect.origin.x += baseSize * kParallelDirOffset;
+      cornerWidth = thumbRect.size.height / 2.0f;
+    } else {
+      baseSize = inBoxRect.size.width;
+      thumbRect.size.width *= kWidthRatio;
+      thumbRect.size.height -= baseSize * kLengthReductionRatio;
+      thumbRect.origin.x += baseSize * kOrthogonalDirOffset;
+      thumbRect.origin.y += baseSize * kParallelDirOffset;
+      cornerWidth = thumbRect.size.width / 2.0f;
+    }
+    CGPathRef path = CGPathCreateWithRoundedRect(thumbRect,
+                                                 cornerWidth,
+                                                 cornerWidth,
+                                                 nullptr);
+    CGContextAddPath(cgContext, path);
+    CGPathRelease(path);
+    SetCGContextFillColor(cgContext, aParams.faceColor);
+    CGContextFillPath(cgContext);
+    return;
+  }
+
   if (aParams.overlay && !aParams.rolledOver) {
     if (aParams.horizontal) {
       drawRect.origin.y += 4;
       drawRect.size.height -= 4;
     } else {
       if (!aParams.rtl) {
         drawRect.origin.x += 4;
       }
@@ -2751,27 +2829,89 @@ nsNativeThemeCocoa::DrawScrollbarTrack(C
                                        const CGRect& inBoxRect,
                                        ScrollbarParams aParams)
 {
   if (aParams.overlay && !aParams.rolledOver) {
     // Non-hovered overlay scrollbars don't have a track. Draw nothing.
     return;
   }
 
-  RenderWithCoreUI(inBoxRect, cgContext,
-          [NSDictionary dictionaryWithObjectsAndKeys:
-            (aParams.overlay ? @"kCUIWidgetOverlayScrollBar" : @"scrollbar"), @"widget",
-            (aParams.small ? @"small" : @"regular"), @"size",
-            (aParams.horizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"), @"kCUIOrientationKey",
-            (aParams.onDarkBackground ? @"kCUIVariantWhite" : @""), @"kCUIVariantKey",
-            [NSNumber numberWithBool:YES], @"noindicator",
-            [NSNumber numberWithBool:YES], @"kCUIThumbProportionKey",
-            [NSNumber numberWithBool:YES], @"is.flipped",
-            nil],
-          true);
+  if (!aParams.custom) {
+    RenderWithCoreUI(inBoxRect, cgContext,
+      [NSDictionary dictionaryWithObjectsAndKeys:
+        (aParams.overlay ? @"kCUIWidgetOverlayScrollBar" : @"scrollbar"), @"widget",
+        (aParams.small ? @"small" : @"regular"), @"size",
+        (aParams.horizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"), @"kCUIOrientationKey",
+        (aParams.onDarkBackground ? @"kCUIVariantWhite" : @""), @"kCUIVariantKey",
+        [NSNumber numberWithBool:YES], @"noindicator",
+        [NSNumber numberWithBool:YES], @"kCUIThumbProportionKey",
+        [NSNumber numberWithBool:YES], @"is.flipped",
+        nil],
+      true);
+    return;
+  }
+
+  nscolor color = aParams.trackColor;
+  // Paint the background color
+  SetCGContextFillColor(cgContext, color);
+  CGContextFillRect(cgContext, inBoxRect);
+  // Paint decorations
+  float luminance = RelativeLuminanceUtils::Compute(color);
+  nscolor innerColor, shadowColor, outerColor;
+  if (luminance >= 0.5) {
+    innerColor = RelativeLuminanceUtils::Adjust(color, luminance * 0.836);
+    shadowColor = RelativeLuminanceUtils::Adjust(color, luminance * 0.982);
+    outerColor = RelativeLuminanceUtils::Adjust(color, luminance * 0.886);
+  } else {
+    innerColor = RelativeLuminanceUtils::Adjust(color, luminance * 1.196);
+    shadowColor = RelativeLuminanceUtils::Adjust(color, luminance * 1.018);
+    outerColor = RelativeLuminanceUtils::Adjust(color, luminance * 1.129);
+  }
+  CGPoint innerPoints[2];
+  CGPoint shadowPoints[2];
+  CGPoint outerPoints[2];
+  if (aParams.horizontal) {
+    innerPoints[0].x = inBoxRect.origin.x;
+    innerPoints[0].y = inBoxRect.origin.y + 0.5f;
+    innerPoints[1].x = innerPoints[0].x + inBoxRect.size.width;
+    innerPoints[1].y = innerPoints[0].y;
+    shadowPoints[0].x = innerPoints[0].x;
+    shadowPoints[0].y = innerPoints[0].y + 1.0f;
+    shadowPoints[1].x = innerPoints[1].x;
+    shadowPoints[1].y = shadowPoints[0].y;
+    outerPoints[0].x = innerPoints[0].x;
+    outerPoints[0].y = innerPoints[0].y + inBoxRect.size.height - 1;
+    outerPoints[1].x = innerPoints[1].x;
+    outerPoints[1].y = outerPoints[0].y;
+  } else {
+    if (aParams.rtl) {
+      innerPoints[0].x = inBoxRect.origin.x + inBoxRect.size.width - 0.5f;
+      shadowPoints[0].x = innerPoints[0].x - 1.0f;
+      outerPoints[0].x = inBoxRect.origin.x + 0.5f;
+    } else {
+      innerPoints[0].x = inBoxRect.origin.x + 0.5f;
+      shadowPoints[0].x = innerPoints[0].x + 1.0f;
+      outerPoints[0].x = inBoxRect.origin.x + inBoxRect.size.width - 0.5f;
+    }
+    innerPoints[0].y = inBoxRect.origin.y;
+    innerPoints[1].x = innerPoints[0].x;
+    innerPoints[1].y = innerPoints[0].y + inBoxRect.size.height;
+    shadowPoints[0].y = innerPoints[0].y;
+    shadowPoints[1].x = shadowPoints[0].x;
+    shadowPoints[1].y = innerPoints[1].y;
+    outerPoints[0].y = innerPoints[0].y;
+    outerPoints[1].x = outerPoints[0].x;
+    outerPoints[1].y = innerPoints[1].y;
+  }
+  SetCGContextStrokeColor(cgContext, innerColor);
+  CGContextStrokeLineSegments(cgContext, innerPoints, 2);
+  SetCGContextStrokeColor(cgContext, shadowColor);
+  CGContextStrokeLineSegments(cgContext, shadowPoints, 2);
+  SetCGContextStrokeColor(cgContext, outerColor);
+  CGContextStrokeLineSegments(cgContext, outerPoints, 2);
 }
 
 static const Color kTooltipBackgroundColor(0.996, 1.000, 0.792, 0.950);
 static const Color kMultilineTextFieldTopBorderColor(0.4510, 0.4510, 0.4510, 1.0);
 static const Color kMultilineTextFieldSidesAndBottomBorderColor(0.6, 0.6, 0.6, 1.0);
 static const Color kListboxTopBorderColor(0.557, 0.557, 0.557, 1.0);
 static const Color kListBoxSidesAndBottomBorderColor(0.745, 0.745, 0.745, 1.0);