summaryrefslogtreecommitdiffstats
path: root/widget/ScrollbarDrawingMac.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/ScrollbarDrawingMac.cpp')
-rw-r--r--widget/ScrollbarDrawingMac.cpp327
1 files changed, 327 insertions, 0 deletions
diff --git a/widget/ScrollbarDrawingMac.cpp b/widget/ScrollbarDrawingMac.cpp
new file mode 100644
index 0000000000..dcc352ccb6
--- /dev/null
+++ b/widget/ScrollbarDrawingMac.cpp
@@ -0,0 +1,327 @@
+/* -*- 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/gfx/PathHelpers.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);
+}
+
+LayoutDeviceIntSize ScrollbarDrawingMac::GetMinimumWidgetSize(
+ StyleAppearance aAppearance, nsIFrame* aFrame, float aDpiRatio) {
+ auto fn = [](StyleAppearance aAppearance, nsIFrame* aFrame) -> IntSize {
+ 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);
+ bool isSmall =
+ style->StyleUIReset()->mScrollbarWidth == StyleScrollbarWidth::Thin;
+ if (nsLookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) !=
+ 0) {
+ if (isSmall) {
+ return IntSize{14, 14};
+ }
+ return IntSize{16, 16};
+ }
+ if (isSmall) {
+ return IntSize{11, 11};
+ }
+ return IntSize{15, 15};
+ }
+ case StyleAppearance::MozMenulistArrowButton:
+ case StyleAppearance::ScrollbarNonDisappearing:
+ return IntSize{15, 15};
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ return IntSize{15, 16};
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ return IntSize{16, 15};
+ default:
+ return IntSize{};
+ }
+ };
+
+ IntSize minSize = fn(aAppearance, aFrame);
+ 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 = 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;
+}
+
+void ScrollbarDrawingMac::DrawScrollbarThumb(DrawTarget& aDT, const Rect& aRect,
+ const ScrollbarParams& aParams) {
+ // 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;
+ }
+ }
+
+ // Compute the thumb rect.
+ float outerSpacing = (aParams.overlay || aParams.small) ? 1.0f : 2.0f;
+ Rect thumbRect = aRect;
+ thumbRect.Deflate(1.0f);
+ 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 {
+ faceColor = aParams.rolledOver ? NS_RGBA(125, 125, 125, 255)
+ : NS_RGBA(194, 194, 194, 255);
+ }
+ }
+
+ // Fill the thumb shape with the color.
+ float cornerRadius =
+ (aParams.horizontal ? thumbRect.Height() : thumbRect.Width()) / 2.0f;
+ aDT.FillRoundedRect(RoundedRect(thumbRect, RectCornerRadii(cornerRadius)),
+ ColorPattern(ToDeviceColor(faceColor)));
+
+ // Overlay scrollbars have an additional stroke around the fill.
+ if (aParams.overlay) {
+ float strokeOutset = aParams.onDarkBackground ? 0.3f : 0.5f;
+ float strokeWidth = aParams.onDarkBackground ? 0.6f : 0.8f;
+ nscolor strokeColor = aParams.onDarkBackground ? NS_RGBA(0, 0, 0, 48)
+ : NS_RGBA(255, 255, 255, 48);
+ Rect thumbStrokeRect = thumbRect;
+ thumbStrokeRect.Inflate(strokeOutset);
+ float strokeRadius = (aParams.horizontal ? thumbStrokeRect.Height()
+ : thumbStrokeRect.Width()) /
+ 2.0f;
+
+ RefPtr<Path> path = MakePathForRoundedRect(aDT, thumbStrokeRect,
+ RectCornerRadii(strokeRadius));
+ aDT.Stroke(path, ColorPattern(ToDeviceColor(strokeColor)),
+ StrokeOptions(strokeWidth));
+ }
+}
+
+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;
+}
+
+void ScrollbarDrawingMac::DrawScrollbarTrack(DrawTarget& aDT, const Rect& aRect,
+ const ScrollbarParams& aParams) {
+ if (aParams.overlay && !aParams.rolledOver) {
+ // Non-hovered overlay scrollbars don't have a track. Draw nothing.
+ return;
+ }
+
+ 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 = 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},
+ {colors.mShadowColor, 1.0f},
+ {trackColor, thickness - 3.0f},
+ {colors.mOuterColor, 1.0f},
+ };
+
+ // 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.
+ 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);
+ }
+ }
+ aDT.FillRect(segmentRect, ColorPattern(ToDeviceColor(segment.color)));
+ accumulatedThickness = endThickness;
+ }
+}
+
+void ScrollbarDrawingMac::DrawScrollCorner(DrawTarget& aDT, const Rect& aRect,
+ const ScrollbarParams& aParams) {
+ if (aParams.overlay && !aParams.rolledOver) {
+ // Non-hovered overlay scrollbars don't have a corner. Draw nothing.
+ return;
+ }
+
+ // 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 =
+ aParams.custom ? aParams.trackColor : NS_RGBA(250, 250, 250, 255);
+ ScrollbarTrackDecorationColors colors =
+ ComputeScrollbarTrackDecorationColors(trackColor);
+ struct {
+ nscolor color;
+ Rect relativeRect;
+ } pieces[] = {
+ {colors.mInnerColor, {0.0f, 0.0f, 1.0f, 1.0f}},
+ {colors.mShadowColor, {1.0f, 0.0f, 1.0f, 1.0f}},
+ {colors.mShadowColor, {0.0f, 1.0f, 2.0f, 1.0f}},
+ {trackColor, {2.0f, 0.0f, width - 3.0f, 2.0f}},
+ {trackColor, {0.0f, 2.0f, width - 1.0f, height - 3.0f}},
+ {colors.mOuterColor, {width - 1.0f, 0.0f, 1.0f, height - 1.0f}},
+ {colors.mOuterColor, {0.0f, height - 1.0f, width, 1.0f}},
+ };
+
+ for (const auto& piece : pieces) {
+ Rect pieceRect = piece.relativeRect + aRect.TopLeft();
+ if (aParams.rtl) {
+ pieceRect.x = aRect.XMost() - piece.relativeRect.XMost();
+ }
+ aDT.FillRect(pieceRect, ColorPattern(ToDeviceColor(piece.color)));
+ }
+}
+
+} // namespace widget
+} // namespace mozilla