forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			364 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			364 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; -*- */
 | |
| /* vim: set sw=2 ts=8 et tw=80 : */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "ScrollbarDrawingMac.h"
 | |
| #include "mozilla/gfx/Helpers.h"
 | |
| #include "mozilla/RelativeLuminanceUtils.h"
 | |
| #include "nsLayoutUtils.h"
 | |
| #include "nsIFrame.h"
 | |
| #include "nsLookAndFeel.h"
 | |
| #include "nsContainerFrame.h"
 | |
| #include "nsNativeTheme.h"
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| using namespace gfx;
 | |
| 
 | |
| namespace widget {
 | |
| 
 | |
| static nsIFrame* GetParentScrollbarFrame(nsIFrame* aFrame) {
 | |
|   // Walk our parents to find a scrollbar frame
 | |
|   nsIFrame* scrollbarFrame = aFrame;
 | |
|   do {
 | |
|     if (scrollbarFrame->IsScrollbarFrame()) {
 | |
|       break;
 | |
|     }
 | |
|   } while ((scrollbarFrame = scrollbarFrame->GetParent()));
 | |
| 
 | |
|   // We return null if we can't find a parent scrollbar frame
 | |
|   return scrollbarFrame;
 | |
| }
 | |
| 
 | |
| static bool IsParentScrollbarRolledOver(nsIFrame* aFrame) {
 | |
|   nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
 | |
|   return nsLookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) != 0
 | |
|              ? nsNativeTheme::CheckBooleanAttr(scrollbarFrame, nsGkAtoms::hover)
 | |
|              : nsNativeTheme::GetContentState(scrollbarFrame,
 | |
|                                               StyleAppearance::None)
 | |
|                    .HasState(NS_EVENT_STATE_HOVER);
 | |
| }
 | |
| 
 | |
| CSSIntCoord ScrollbarDrawingMac::GetScrollbarSize(StyleScrollbarWidth aWidth,
 | |
|                                                   bool aOverlay) {
 | |
|   bool isSmall = aWidth == StyleScrollbarWidth::Thin;
 | |
|   if (aOverlay) {
 | |
|     return isSmall ? 14 : 16;
 | |
|   }
 | |
|   return isSmall ? 11 : 15;
 | |
| }
 | |
| 
 | |
| LayoutDeviceIntCoord ScrollbarDrawingMac::GetScrollbarSize(
 | |
|     StyleScrollbarWidth aWidth, bool aOverlay, float aDpiRatio) {
 | |
|   CSSIntCoord size = GetScrollbarSize(aWidth, aOverlay);
 | |
|   if (aDpiRatio >= 2.0f) {
 | |
|     return int32_t(size) * 2;
 | |
|   }
 | |
|   return int32_t(size);
 | |
| }
 | |
| 
 | |
| LayoutDeviceIntSize ScrollbarDrawingMac::GetMinimumWidgetSize(
 | |
|     StyleAppearance aAppearance, nsIFrame* aFrame, float aDpiRatio) {
 | |
|   auto minSize = [&] {
 | |
|     switch (aAppearance) {
 | |
|       case StyleAppearance::ScrollbarthumbHorizontal:
 | |
|         return IntSize{26, 0};
 | |
|       case StyleAppearance::ScrollbarthumbVertical:
 | |
|         return IntSize{0, 26};
 | |
|       case StyleAppearance::ScrollbarVertical:
 | |
|       case StyleAppearance::ScrollbarHorizontal:
 | |
|       case StyleAppearance::ScrollbartrackVertical:
 | |
|       case StyleAppearance::ScrollbartrackHorizontal: {
 | |
|         ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
 | |
|         auto scrollbarWidth = style->StyleUIReset()->mScrollbarWidth;
 | |
|         auto size = GetScrollbarSize(
 | |
|             scrollbarWidth,
 | |
|             LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars));
 | |
|         return IntSize{size, size};
 | |
|       }
 | |
|       case StyleAppearance::MozMenulistArrowButton: {
 | |
|         auto size =
 | |
|             GetScrollbarSize(StyleScrollbarWidth::Auto, /* aOverlay = */ false);
 | |
|         return IntSize{size, size};
 | |
|       }
 | |
|       case StyleAppearance::ScrollbarbuttonUp:
 | |
|       case StyleAppearance::ScrollbarbuttonDown:
 | |
|         return IntSize{15, 16};
 | |
|       case StyleAppearance::ScrollbarbuttonLeft:
 | |
|       case StyleAppearance::ScrollbarbuttonRight:
 | |
|         return IntSize{16, 15};
 | |
|       default:
 | |
|         return IntSize{};
 | |
|     }
 | |
|   }();
 | |
| 
 | |
|   if (aDpiRatio >= 2.0f) {
 | |
|     return LayoutDeviceIntSize{minSize.width * 2, minSize.height * 2};
 | |
|   }
 | |
|   return LayoutDeviceIntSize{minSize.width, minSize.height};
 | |
| }
 | |
| 
 | |
| ScrollbarParams ScrollbarDrawingMac::ComputeScrollbarParams(
 | |
|     nsIFrame* aFrame, const ComputedStyle& aStyle, bool aIsHorizontal) {
 | |
|   ScrollbarParams params;
 | |
|   params.overlay =
 | |
|       nsLookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) != 0;
 | |
|   params.rolledOver = IsParentScrollbarRolledOver(aFrame);
 | |
|   params.small =
 | |
|       aStyle.StyleUIReset()->mScrollbarWidth == StyleScrollbarWidth::Thin;
 | |
|   params.rtl = nsNativeTheme::IsFrameRTL(aFrame);
 | |
|   params.horizontal = aIsHorizontal;
 | |
|   params.onDarkBackground = !StaticPrefs::widget_disable_dark_scrollbar() &&
 | |
|                             nsNativeTheme::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) {
 | |
|     const nsStyleUI* ui = aStyle.StyleUI();
 | |
|     if (ui->HasCustomScrollbars()) {
 | |
|       const auto& colors = ui->mScrollbarColor.AsColors();
 | |
|       params.custom = true;
 | |
|       params.trackColor = colors.track.CalcColor(aStyle);
 | |
|       params.faceColor = colors.thumb.CalcColor(aStyle);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return params;
 | |
| }
 | |
| 
 | |
| auto ScrollbarDrawingMac::GetThumbRect(const Rect& aRect,
 | |
|                                        const ScrollbarParams& aParams,
 | |
|                                        float aScale) -> ThumbRect {
 | |
|   // This matches the sizing checks in GetMinimumWidgetSize etc.
 | |
|   aScale = aScale >= 2.0f ? 2.0f : 1.0f;
 | |
| 
 | |
|   // Compute the thumb thickness. This varies based on aParams.small,
 | |
|   // aParams.overlay and aParams.rolledOver. non-overlay: 6 / 8, overlay
 | |
|   // non-hovered: 5 / 7, overlay hovered: 9 / 11
 | |
|   float thickness = aParams.small ? 6.0f : 8.0f;
 | |
|   if (aParams.overlay) {
 | |
|     thickness -= 1.0f;
 | |
|     if (aParams.rolledOver) {
 | |
|       thickness += 4.0f;
 | |
|     }
 | |
|   }
 | |
|   thickness *= aScale;
 | |
| 
 | |
|   // Compute the thumb rect.
 | |
|   const float outerSpacing =
 | |
|       ((aParams.overlay || aParams.small) ? 1.0f : 2.0f) * aScale;
 | |
|   Rect thumbRect = aRect;
 | |
|   thumbRect.Deflate(1.0f * aScale);
 | |
|   if (aParams.horizontal) {
 | |
|     float bottomEdge = thumbRect.YMost() - outerSpacing;
 | |
|     thumbRect.SetBoxY(bottomEdge - thickness, bottomEdge);
 | |
|   } else {
 | |
|     if (aParams.rtl) {
 | |
|       float leftEdge = thumbRect.X() + outerSpacing;
 | |
|       thumbRect.SetBoxX(leftEdge, leftEdge + thickness);
 | |
|     } else {
 | |
|       float rightEdge = thumbRect.XMost() - outerSpacing;
 | |
|       thumbRect.SetBoxX(rightEdge - thickness, rightEdge);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Compute the thumb fill color.
 | |
|   nscolor faceColor;
 | |
|   if (aParams.custom) {
 | |
|     faceColor = aParams.faceColor;
 | |
|   } else {
 | |
|     if (aParams.overlay) {
 | |
|       faceColor = aParams.onDarkBackground ? NS_RGBA(255, 255, 255, 128)
 | |
|                                            : NS_RGBA(0, 0, 0, 128);
 | |
|     } else if (aParams.onDarkBackground) {
 | |
|       faceColor = aParams.rolledOver ? NS_RGBA(158, 158, 158, 255)
 | |
|                                      : NS_RGBA(117, 117, 117, 255);
 | |
|     } else {
 | |
|       faceColor = aParams.rolledOver ? NS_RGBA(125, 125, 125, 255)
 | |
|                                      : NS_RGBA(194, 194, 194, 255);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nscolor strokeColor = 0;
 | |
|   float strokeOutset = 0.0f;
 | |
|   float strokeWidth = 0.0f;
 | |
| 
 | |
|   // Overlay scrollbars have an additional stroke around the fill.
 | |
|   if (aParams.overlay) {
 | |
|     strokeOutset = (aParams.onDarkBackground ? 0.3f : 0.5f) * aScale;
 | |
|     strokeWidth = (aParams.onDarkBackground ? 0.6f : 0.8f) * aScale;
 | |
| 
 | |
|     strokeColor = aParams.onDarkBackground ? NS_RGBA(0, 0, 0, 48)
 | |
|                                            : NS_RGBA(255, 255, 255, 48);
 | |
|   }
 | |
| 
 | |
|   return {thumbRect, faceColor, strokeColor, strokeWidth, strokeOutset};
 | |
| }
 | |
| 
 | |
| struct ScrollbarTrackDecorationColors {
 | |
|   nscolor mInnerColor = 0;
 | |
|   nscolor mShadowColor = 0;
 | |
|   nscolor mOuterColor = 0;
 | |
| };
 | |
| 
 | |
| static ScrollbarTrackDecorationColors ComputeScrollbarTrackDecorationColors(
 | |
|     nscolor aTrackColor) {
 | |
|   ScrollbarTrackDecorationColors result;
 | |
|   float luminance = RelativeLuminanceUtils::Compute(aTrackColor);
 | |
|   if (luminance >= 0.5f) {
 | |
|     result.mInnerColor =
 | |
|         RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.836f);
 | |
|     result.mShadowColor =
 | |
|         RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.982f);
 | |
|     result.mOuterColor =
 | |
|         RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.886f);
 | |
|   } else {
 | |
|     result.mInnerColor =
 | |
|         RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.196f);
 | |
|     result.mShadowColor =
 | |
|         RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.018f);
 | |
|     result.mOuterColor =
 | |
|         RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.129f);
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| bool ScrollbarDrawingMac::GetScrollbarTrackRects(const Rect& aRect,
 | |
|                                                  const ScrollbarParams& aParams,
 | |
|                                                  float aScale,
 | |
|                                                  ScrollbarTrackRects& aRects) {
 | |
|   if (aParams.overlay && !aParams.rolledOver) {
 | |
|     // Non-hovered overlay scrollbars don't have a track. Draw nothing.
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // This matches the sizing checks in GetMinimumWidgetSize etc.
 | |
|   aScale = aScale >= 2.0f ? 2.0f : 1.0f;
 | |
| 
 | |
|   nscolor trackColor;
 | |
|   if (aParams.custom) {
 | |
|     trackColor = aParams.trackColor;
 | |
|   } else {
 | |
|     if (aParams.overlay) {
 | |
|       trackColor = aParams.onDarkBackground ? NS_RGBA(201, 201, 201, 38)
 | |
|                                             : NS_RGBA(250, 250, 250, 191);
 | |
|     } else {
 | |
|       trackColor = aParams.onDarkBackground ? NS_RGBA(46, 46, 46, 255)
 | |
|                                             : NS_RGBA(250, 250, 250, 255);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   float thickness = aParams.horizontal ? aRect.height : aRect.width;
 | |
| 
 | |
|   // The scrollbar track is drawn as multiple non-overlapping segments, which
 | |
|   // make up lines of different widths and with slightly different shading.
 | |
|   ScrollbarTrackDecorationColors colors =
 | |
|       ComputeScrollbarTrackDecorationColors(trackColor);
 | |
|   struct {
 | |
|     nscolor color;
 | |
|     float thickness;
 | |
|   } segments[] = {
 | |
|       {colors.mInnerColor, 1.0f * aScale},
 | |
|       {colors.mShadowColor, 1.0f * aScale},
 | |
|       {trackColor, thickness - 3.0f * aScale},
 | |
|       {colors.mOuterColor, 1.0f * aScale},
 | |
|   };
 | |
| 
 | |
|   // Iterate over the segments "from inside to outside" and fill each segment.
 | |
|   // For horizontal scrollbars, iterate top to bottom.
 | |
|   // For vertical scrollbars, iterate left to right or right to left based on
 | |
|   // aParams.rtl.
 | |
|   auto current = aRects.begin();
 | |
|   float accumulatedThickness = 0.0f;
 | |
|   for (const auto& segment : segments) {
 | |
|     Rect segmentRect = aRect;
 | |
|     float startThickness = accumulatedThickness;
 | |
|     float endThickness = startThickness + segment.thickness;
 | |
|     if (aParams.horizontal) {
 | |
|       segmentRect.SetBoxY(aRect.Y() + startThickness, aRect.Y() + endThickness);
 | |
|     } else {
 | |
|       if (aParams.rtl) {
 | |
|         segmentRect.SetBoxX(aRect.XMost() - endThickness,
 | |
|                             aRect.XMost() - startThickness);
 | |
|       } else {
 | |
|         segmentRect.SetBoxX(aRect.X() + startThickness,
 | |
|                             aRect.X() + endThickness);
 | |
|       }
 | |
|     }
 | |
|     accumulatedThickness = endThickness;
 | |
|     *current++ = {segmentRect, segment.color};
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool ScrollbarDrawingMac::GetScrollCornerRects(const Rect& aRect,
 | |
|                                                const ScrollbarParams& aParams,
 | |
|                                                float aScale,
 | |
|                                                ScrollCornerRects& aRects) {
 | |
|   if (aParams.overlay && !aParams.rolledOver) {
 | |
|     // Non-hovered overlay scrollbars don't have a corner. Draw nothing.
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // This matches the sizing checks in GetMinimumWidgetSize etc.
 | |
|   aScale = aScale >= 2.0f ? 2.0f : 1.0f;
 | |
| 
 | |
|   // Draw the following scroll corner.
 | |
|   //
 | |
|   //        Output:                      Rectangles:
 | |
|   // +---+---+----------+---+     +---+---+----------+---+
 | |
|   // | I | S | T ...  T | O |     | I | S | T ...  T | O |
 | |
|   // +---+   |          |   |     +---+---+          |   |
 | |
|   // | S   S | T ...  T |   |     | S   S | T ...  T | . |
 | |
|   // +-------+          | . |     +-------+----------+ . |
 | |
|   // | T      ...     T | . |     | T      ...     T | . |
 | |
|   // | .              . | . |     | .              . |   |
 | |
|   // | T      ...     T |   |     | T      ...     T | O |
 | |
|   // +------------------+   |     +------------------+---+
 | |
|   // | O       ...        O |     | O       ...        O |
 | |
|   // +----------------------+     +----------------------+
 | |
| 
 | |
|   float width = aRect.width;
 | |
|   float height = aRect.height;
 | |
|   nscolor trackColor;
 | |
|   if (aParams.custom) {
 | |
|     trackColor = aParams.trackColor;
 | |
|   } else {
 | |
|     trackColor = aParams.onDarkBackground ? NS_RGBA(46, 46, 46, 255)
 | |
|                                           : NS_RGBA(250, 250, 250, 255);
 | |
|   }
 | |
|   ScrollbarTrackDecorationColors colors =
 | |
|       ComputeScrollbarTrackDecorationColors(trackColor);
 | |
|   struct {
 | |
|     nscolor color;
 | |
|     Rect relativeRect;
 | |
|   } pieces[] = {
 | |
|       {colors.mInnerColor, {0.0f, 0.0f, 1.0f * aScale, 1.0f * aScale}},
 | |
|       {colors.mShadowColor,
 | |
|        {1.0f * aScale, 0.0f, 1.0f * aScale, 1.0f * aScale}},
 | |
|       {colors.mShadowColor,
 | |
|        {0.0f, 1.0f * aScale, 2.0f * aScale, 1.0f * aScale}},
 | |
|       {trackColor, {2.0f * aScale, 0.0f, width - 3.0f * aScale, 2.0f * aScale}},
 | |
|       {trackColor,
 | |
|        {0.0f, 2.0f * aScale, width - 1.0f * aScale, height - 3.0f * aScale}},
 | |
|       {colors.mOuterColor,
 | |
|        {width - 1.0f * aScale, 0.0f, 1.0f * aScale, height - 1.0f * aScale}},
 | |
|       {colors.mOuterColor,
 | |
|        {0.0f, height - 1.0f * aScale, width, 1.0f * aScale}},
 | |
|   };
 | |
| 
 | |
|   auto current = aRects.begin();
 | |
|   for (const auto& piece : pieces) {
 | |
|     Rect pieceRect = piece.relativeRect + aRect.TopLeft();
 | |
|     if (aParams.rtl) {
 | |
|       pieceRect.x = aRect.XMost() - piece.relativeRect.XMost();
 | |
|     }
 | |
|     *current++ = {pieceRect, piece.color};
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| }  // namespace widget
 | |
| }  // namespace mozilla
 | 
