Bug 1464722 part 4 - Render custom scrollbars on cocoa widget. r?spohl draft
authorXidorn Quan <me@upsuper.org>
Fri, 22 Jun 2018 14:17:22 +1000
changeset 810029 e935bc0f51a1e94d22ce8a34359e7e39f31e2be9
parent 810028 caa6062380454fcbd9cf18f2993fa789155d4912
child 810030 7f14abc673f5e1144b1ad2e7e1bb63d3f6e2ea3f
push id113864
push userxquan@mozilla.com
push dateMon, 25 Jun 2018 00:30:18 +0000
reviewersspohl
bugs1464722
milestone62.0a1
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);