diff --git a/widget/ScrollbarDrawing.cpp b/widget/ScrollbarDrawing.cpp index a082b58eb39d..e3fcb4c21d71 100644 --- a/widget/ScrollbarDrawing.cpp +++ b/widget/ScrollbarDrawing.cpp @@ -185,14 +185,22 @@ bool ScrollbarDrawing::DoPaintDefaultScrollbar( bool aHorizontal, nsIFrame* aFrame, const ComputedStyle& aStyle, const EventStates& aElementState, const EventStates& aDocumentState, const Colors& aColors, const DPIRatio& aDpiRatio) { - if (aFrame->PresContext()->UseOverlayScrollbars() && - !aElementState.HasAtLeastOneOfStates(NS_EVENT_STATE_HOVER | - NS_EVENT_STATE_ACTIVE)) { + const bool overlay = aFrame->PresContext()->UseOverlayScrollbars(); + if (overlay && !aElementState.HasAtLeastOneOfStates(NS_EVENT_STATE_HOVER | + NS_EVENT_STATE_ACTIVE)) { return true; } - auto scrollbarColor = + const auto color = ComputeScrollbarTrackColor(aFrame, aStyle, aDocumentState, aColors); - ThemeDrawing::FillRect(aPaintData, aRect, scrollbarColor); + if (overlay && mKind == Kind::Win11) { + LayoutDeviceCoord radius = + (aHorizontal ? aRect.height : aRect.width) / 2.0f; + ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, aRect, color, + sRGBColor(), 0, radius / aDpiRatio, + aDpiRatio); + } else { + ThemeDrawing::FillRect(aPaintData, aRect, color); + } return true; } diff --git a/widget/ScrollbarDrawing.h b/widget/ScrollbarDrawing.h index dc15249e4798..260c6d97ae5d 100644 --- a/widget/ScrollbarDrawing.h +++ b/widget/ScrollbarDrawing.h @@ -28,8 +28,16 @@ class ScrollbarDrawing { using Overlay = nsITheme::Overlay; using WebRenderBackendData = mozilla::widget::WebRenderBackendData; + enum class Kind : uint8_t { + Android, + Cocoa, + Gtk, + Win10, + Win11, + }; + public: - ScrollbarDrawing() = default; + explicit ScrollbarDrawing(Kind aKind) : mKind(aKind) {} virtual ~ScrollbarDrawing() = default; static DPIRatio GetDPIRatioForScrollbarPart(nsPresContext*); @@ -96,16 +104,16 @@ class ScrollbarDrawing { const EventStates& aElementState, const EventStates& aDocumentState, const Colors&, const DPIRatio&); - virtual bool PaintScrollbar(DrawTarget&, const LayoutDeviceRect&, - bool aHorizontal, nsIFrame*, const ComputedStyle&, - const EventStates& aElementState, - const EventStates& aDocumentState, const Colors&, - const DPIRatio&); - virtual bool PaintScrollbar(WebRenderBackendData&, const LayoutDeviceRect&, - bool aHorizontal, nsIFrame*, const ComputedStyle&, - const EventStates& aElementState, - const EventStates& aDocumentState, const Colors&, - const DPIRatio&); + bool PaintScrollbar(DrawTarget&, const LayoutDeviceRect&, bool aHorizontal, + nsIFrame*, const ComputedStyle&, + const EventStates& aElementState, + const EventStates& aDocumentState, const Colors&, + const DPIRatio&); + bool PaintScrollbar(WebRenderBackendData&, const LayoutDeviceRect&, + bool aHorizontal, nsIFrame*, const ComputedStyle&, + const EventStates& aElementState, + const EventStates& aDocumentState, const Colors&, + const DPIRatio&); virtual bool PaintScrollbarTrack(DrawTarget&, const LayoutDeviceRect&, bool aHorizontal, nsIFrame*, @@ -148,6 +156,9 @@ class ScrollbarDrawing { uint32_t GetVerticalScrollbarWidth() const { return mVerticalScrollbarWidth; } protected: + // For some kind of style differences a full virtual method is overkill, so we + // store the kind here so we can branch on it if necessary. + Kind mKind; uint32_t mHorizontalScrollbarHeight = 0; uint32_t mVerticalScrollbarWidth = 0; }; diff --git a/widget/ScrollbarDrawingAndroid.h b/widget/ScrollbarDrawingAndroid.h index 6be491d758f8..24453bba31d9 100644 --- a/widget/ScrollbarDrawingAndroid.h +++ b/widget/ScrollbarDrawingAndroid.h @@ -13,7 +13,7 @@ namespace mozilla::widget { class ScrollbarDrawingAndroid final : public ScrollbarDrawing { public: - ScrollbarDrawingAndroid() = default; + ScrollbarDrawingAndroid() : ScrollbarDrawing(Kind::Android) {} virtual ~ScrollbarDrawingAndroid() = default; LayoutDeviceIntSize GetMinimumWidgetSize(nsPresContext*, diff --git a/widget/ScrollbarDrawingCocoa.h b/widget/ScrollbarDrawingCocoa.h index d2ad89d52109..0053a59823ce 100644 --- a/widget/ScrollbarDrawingCocoa.h +++ b/widget/ScrollbarDrawingCocoa.h @@ -15,7 +15,7 @@ namespace mozilla::widget { class ScrollbarDrawingCocoa final : public ScrollbarDrawing { public: - ScrollbarDrawingCocoa() = default; + ScrollbarDrawingCocoa() : ScrollbarDrawing(Kind::Cocoa) {} virtual ~ScrollbarDrawingCocoa() = default; LayoutDeviceIntSize GetMinimumWidgetSize(nsPresContext*, diff --git a/widget/ScrollbarDrawingGTK.h b/widget/ScrollbarDrawingGTK.h index f295e97ef391..3d0b5ba5f0a2 100644 --- a/widget/ScrollbarDrawingGTK.h +++ b/widget/ScrollbarDrawingGTK.h @@ -14,7 +14,7 @@ namespace mozilla::widget { class ScrollbarDrawingGTK final : public ScrollbarDrawing { public: - ScrollbarDrawingGTK() = default; + ScrollbarDrawingGTK() : ScrollbarDrawing(Kind::Gtk) {} virtual ~ScrollbarDrawingGTK() = default; LayoutDeviceIntSize GetMinimumWidgetSize(nsPresContext*, diff --git a/widget/ScrollbarDrawingWin.h b/widget/ScrollbarDrawingWin.h index 9fd60f3cbae7..837e662a0c92 100644 --- a/widget/ScrollbarDrawingWin.h +++ b/widget/ScrollbarDrawingWin.h @@ -13,8 +13,12 @@ namespace mozilla::widget { class ScrollbarDrawingWin : public ScrollbarDrawing { + protected: + explicit ScrollbarDrawingWin(Kind aKind) : ScrollbarDrawing(aKind) {} + public: - ScrollbarDrawingWin() = default; + ScrollbarDrawingWin() : ScrollbarDrawingWin(Kind::Win10) {} + virtual ~ScrollbarDrawingWin() = default; LayoutDeviceIntSize GetMinimumWidgetSize(nsPresContext*, diff --git a/widget/ScrollbarDrawingWin11.cpp b/widget/ScrollbarDrawingWin11.cpp index 029a8d39abe7..c93af566dcdc 100644 --- a/widget/ScrollbarDrawingWin11.cpp +++ b/widget/ScrollbarDrawingWin11.cpp @@ -16,11 +16,57 @@ using mozilla::gfx::sRGBColor; namespace mozilla::widget { +// There are effectively three kinds of scrollbars in Windows 11: +// +// * Overlay scrollbars (the ones where the scrollbar disappears automatically +// and doesn't take space) +// * Non-overlay scrollbar with thin (overlay-like) thumb. +// * Non-overlay scrollbar with thick thumb. +// +// See bug 1755193 for some discussion on non-overlay scrollbar styles. +enum class Style { + Overlay, + ThinThumb, + ThickThumb, +}; + +static Style ScrollbarStyle(nsPresContext* aPresContext) { + if (aPresContext->UseOverlayScrollbars()) { + return Style::Overlay; + } + if (StaticPrefs:: + widget_non_native_theme_win11_scrollbar_force_overlay_style()) { + return Style::ThinThumb; + } + return Style::ThickThumb; +} + +static constexpr CSSIntCoord kDefaultWinOverlayScrollbarSize = CSSIntCoord(12); +static constexpr CSSIntCoord kDefaultWinOverlayThinScrollbarSize = + CSSIntCoord(10); + +auto ScrollbarDrawingWin11::GetScrollbarSizes(nsPresContext* aPresContext, + StyleScrollbarWidth aWidth, + Overlay aOverlay) + -> ScrollbarSizes { + if (aOverlay == Overlay::Yes) { + // TODO(emilio): Maybe make this configurable? Though this doesn't respect + // classic Windows registry settings, and cocoa overlay scrollbars also + // don't respect the override it seems, so this should be fine. + CSSCoord cssSize(aWidth == StyleScrollbarWidth::Thin + ? kDefaultWinOverlayThinScrollbarSize + : kDefaultWinOverlayScrollbarSize); + auto size = (cssSize * GetDPIRatioForScrollbarPart(aPresContext)).Rounded(); + return {size, size}; + } + return ScrollbarDrawingWin::GetScrollbarSizes(aPresContext, aWidth, aOverlay); +} + LayoutDeviceIntSize ScrollbarDrawingWin11::GetMinimumWidgetSize( nsPresContext* aPresContext, StyleAppearance aAppearance, nsIFrame* aFrame) { MOZ_ASSERT(nsNativeTheme::IsWidgetScrollbarPart(aAppearance)); - if (!UseOverlayStyle(aPresContext)) { + if (ScrollbarStyle(aPresContext) != Style::ThinThumb) { return ScrollbarDrawingWin::GetMinimumWidgetSize(aPresContext, aAppearance, aFrame); } @@ -32,7 +78,7 @@ LayoutDeviceIntSize ScrollbarDrawingWin11::GetMinimumWidgetSize( return {}; } const LayoutDeviceIntCoord size = - GetScrollbarSizes(aPresContext, aFrame).mVertical; + ScrollbarDrawing::GetScrollbarSizes(aPresContext, aFrame).mVertical; return LayoutDeviceIntSize{ size, (kArrowRatio * LayoutDeviceCoord(size)).Rounded()}; } @@ -42,7 +88,7 @@ LayoutDeviceIntSize ScrollbarDrawingWin11::GetMinimumWidgetSize( return {}; } const LayoutDeviceIntCoord size = - GetScrollbarSizes(aPresContext, aFrame).mHorizontal; + ScrollbarDrawing::GetScrollbarSizes(aPresContext, aFrame).mHorizontal; return LayoutDeviceIntSize{ (kArrowRatio * LayoutDeviceCoord(size)).Rounded(), size}; } @@ -108,12 +154,11 @@ ScrollbarDrawingWin11::ComputeScrollbarButtonColors( return ScrollbarDrawingWin::ComputeScrollbarButtonColors( aFrame, aAppearance, aStyle, aElementState, aDocumentState, aColors); } - - auto buttonColor = - ComputeScrollbarTrackColor(aFrame, aStyle, aDocumentState, aColors); + // The button always looks transparent (the track behind it is visible), so we + // can hardcode it. sRGBColor arrowColor = ComputeScrollbarThumbColor( aFrame, aStyle, aElementState, aDocumentState, aColors); - return {buttonColor, arrowColor}; + return {sRGBColor::White(0.0f), arrowColor}; } bool ScrollbarDrawingWin11::PaintScrollbarButton( @@ -126,11 +171,13 @@ bool ScrollbarDrawingWin11::PaintScrollbarButton( return true; } - const bool overlay = UseOverlayStyle(aFrame->PresContext()); + const auto style = ScrollbarStyle(aFrame->PresContext()); auto [buttonColor, arrowColor] = ComputeScrollbarButtonColors( aFrame, aAppearance, aStyle, aElementState, aDocumentState, aColors); - aDrawTarget.FillRect(aRect.ToUnknownRect(), - gfx::ColorPattern(ToDeviceColor(buttonColor))); + if (style != Style::Overlay) { + aDrawTarget.FillRect(aRect.ToUnknownRect(), + gfx::ColorPattern(ToDeviceColor(buttonColor))); + } // Start with Up arrow. float arrowPolygonX[] = {-4.5f, 4.5f, 4.5f, 0.5f, -0.5f, -4.5f, -4.5f}; @@ -146,8 +193,16 @@ bool ScrollbarDrawingWin11::PaintScrollbarButton( aAppearance == StyleAppearance::ScrollbarbuttonRight || aAppearance == StyleAppearance::ScrollbarbuttonLeft; - const float offset = [&] { - if (!overlay) { + const float verticalOffset = [&] { + if (style != Style::Overlay) { + return 0.0f; + } + // To compensate for the scrollbar track radius we shift stuff vertically a + // bit. This 1px is arbitrary, but enough for the triangle not to overflow. + return 1.0f; + }(); + const float horizontalOffset = [&] { + if (style != Style::ThinThumb) { return 0.0f; // Always center it in the rect. } // Compensate for the displacement we do of the thumb position by displacing @@ -157,7 +212,9 @@ bool ScrollbarDrawingWin11::PaintScrollbarButton( } return aFrame->GetWritingMode().IsPhysicalLTR() ? 0.5f : -0.5f; }(); - const float kPolygonSize = kDefaultWinScrollbarSize; + const float polygonSize = style == Style::Overlay + ? float(kDefaultWinOverlayScrollbarSize) + : float(kDefaultWinScrollbarSize); const int32_t arrowNumPoints = ArrayLength(arrowPolygonX); if (aElementState.HasState(NS_EVENT_STATE_ACTIVE)) { @@ -172,14 +229,15 @@ bool ScrollbarDrawingWin11::PaintScrollbarButton( case StyleAppearance::ScrollbarbuttonDown: case StyleAppearance::ScrollbarbuttonRight: for (int32_t i = 0; i < arrowNumPoints; i++) { + arrowY[i] += verticalOffset; arrowY[i] *= -1; } [[fallthrough]]; case StyleAppearance::ScrollbarbuttonUp: case StyleAppearance::ScrollbarbuttonLeft: - if (offset != 0.0f) { + if (horizontalOffset != 0.0f) { for (int32_t i = 0; i < arrowNumPoints; i++) { - arrowX[i] += offset; + arrowX[i] += horizontalOffset; } } break; @@ -192,22 +250,16 @@ bool ScrollbarDrawingWin11::PaintScrollbarButton( } LayoutDeviceRect arrowRect(aRect); - if (!overlay) { - auto margin = CSSCoord(2) * aDpiRatio; + if (style != Style::ThinThumb) { + auto margin = CSSCoord(style == Style::Overlay ? 1 : 2) * aDpiRatio; arrowRect.Deflate(margin, margin); } - ThemeDrawing::PaintArrow(aDrawTarget, arrowRect, arrowX, arrowY, kPolygonSize, + ThemeDrawing::PaintArrow(aDrawTarget, arrowRect, arrowX, arrowY, polygonSize, arrowNumPoints, arrowColor); return true; } -bool ScrollbarDrawingWin11::UseOverlayStyle(nsPresContext* aPresContext) { - return StaticPrefs:: - widget_non_native_theme_win11_scrollbar_force_overlay_style() || - aPresContext->UseOverlayScrollbars(); -} - template bool ScrollbarDrawingWin11::DoPaintScrollbarThumb( PaintBackendData& aPaintData, const LayoutDeviceRect& aRect, @@ -219,11 +271,11 @@ bool ScrollbarDrawingWin11::DoPaintScrollbarThumb( LayoutDeviceRect thumbRect(aRect); - const bool overlay = UseOverlayStyle(aFrame->PresContext()); + const auto style = ScrollbarStyle(aFrame->PresContext()); const bool hovered = ScrollbarDrawing::IsParentScrollbarHoveredOrActive(aFrame) || - IsScrollbarWidthThin(aStyle); - if (!overlay) { + (style != Style::Overlay && IsScrollbarWidthThin(aStyle)); + if (style == Style::ThickThumb) { constexpr float kHoveredThumbRatio = (1.0f - (11.0f / kDefaultWinScrollbarSize)) / 2.0f; constexpr float kUnhoveredThumbRatio = @@ -241,60 +293,65 @@ bool ScrollbarDrawingWin11::DoPaintScrollbarThumb( return true; } - if (hovered) { - constexpr float kHoverThumbSize = 6.0f / kDefaultWinScrollbarSize; - if (aHorizontal) { - // Scrollbar is 17px high by default. We make the thumb 6px tall and move - // it 5px towards the bottom, so the center (8.5 initially) is displaced - // by: - // (5px + 6px / 2) - 8.5px = -0.5px - constexpr float kShift = 5.0f / kDefaultWinScrollbarSize; + const float defaultTrackSize = style == Style::Overlay + ? float(kDefaultWinOverlayScrollbarSize) + : float(kDefaultWinScrollbarSize); + const float trackSize = aHorizontal ? thumbRect.height : thumbRect.width; + const float thumbSizeInPixels = hovered ? 6.0f : 2.0f; - thumbRect.y += thumbRect.height * kShift; - thumbRect.height *= kHoverThumbSize; - } else { - // Scrollbar is 17px wide by default. We make the thumb 6px wide and move - // it 5px or 6px towards the right (depending on writing-mode), so the - // center (8.5px initially) is displaced by: - // (6px + 6px / 2) - 8.5px = 0.5px for LTR - // (5px + 6px / 2) - 8.5px = -0.5px for RTL - constexpr float kLtrShift = 6.0f / kDefaultWinScrollbarSize; - constexpr float kRtlShift = 5.0f / kDefaultWinScrollbarSize; - - if (aFrame->GetWritingMode().IsPhysicalLTR()) { - thumbRect.x += thumbRect.width * kLtrShift; - } else { - thumbRect.x += thumbRect.width * kRtlShift; + // The thumb might be a bit off-center, depending on our scrollbar styles. + // + // Hovered shifts, if any, need to be accounted for in PaintScrollbarButton. + // For example, for the hovered horizontal thin scrollbar shift: + // + // Scrollbar is 17px high by default. We make the thumb 6px tall and move + // it 5px towards the bottom, so the center (8.5 initially) is displaced + // by: + // (5px + 6px / 2) - 8.5px = -0.5px + // + // Same calculations apply to other shifts. + const float shiftInPixels = [&] { + if (style == Style::Overlay) { + if (hovered) { + // Keep the center intact. + return (defaultTrackSize - thumbSizeInPixels) / 2.0f; } - thumbRect.width *= kHoverThumbSize; + // We want logical pixels from the thumb to the edge. For RTL and + // horizontal scrollbars that means shifting down the scrollbar size minus + // the thumb. + constexpr float kSpaceToEdge = 3.0f; + if (aHorizontal || aFrame->GetWritingMode().IsPhysicalLTR()) { + return defaultTrackSize - thumbSizeInPixels - kSpaceToEdge; + } + // For rtl is simpler. + return kSpaceToEdge; } + if (aHorizontal) { + return hovered ? 5.0f : 7.0f; + } + const bool ltr = aFrame->GetWritingMode().IsPhysicalLTR(); + return ltr ? (hovered ? 6.0f : 8.0f) : (hovered ? 5.0f : 7.0f); + }(); + + if (aHorizontal) { + thumbRect.y += shiftInPixels * trackSize / defaultTrackSize; + thumbRect.height *= thumbSizeInPixels / defaultTrackSize; + } else { + thumbRect.x += shiftInPixels * trackSize / defaultTrackSize; + thumbRect.width *= thumbSizeInPixels / defaultTrackSize; + } + + if (style == Style::Overlay || hovered) { LayoutDeviceCoord radius = (aHorizontal ? thumbRect.height : thumbRect.width) / 2.0f; MOZ_ASSERT(aRect.Contains(thumbRect)); - ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, thumbRect, thumbColor, sRGBColor(), 0, radius / aDpiRatio, aDpiRatio); return true; } - constexpr float kThumbSize = 2.0f / kDefaultWinScrollbarSize; - if (aHorizontal) { - constexpr float kShift = 7.0f / kDefaultWinScrollbarSize; - thumbRect.y += thumbRect.height * kShift; - thumbRect.height *= kThumbSize; - } else { - constexpr float kLtrShift = 8.0f / kDefaultWinScrollbarSize; - constexpr float kRtlShift = 7.0f / kDefaultWinScrollbarSize; - - if (aFrame->GetWritingMode().IsPhysicalLTR()) { - thumbRect.x += thumbRect.width * kLtrShift; - } else { - thumbRect.x += thumbRect.width * kRtlShift; - } - thumbRect.width *= kThumbSize; - } ThemeDrawing::FillRect(aPaintData, thumbRect, thumbColor); return true; } diff --git a/widget/ScrollbarDrawingWin11.h b/widget/ScrollbarDrawingWin11.h index 0da9a84423b5..22c87c335764 100644 --- a/widget/ScrollbarDrawingWin11.h +++ b/widget/ScrollbarDrawingWin11.h @@ -15,9 +15,12 @@ namespace mozilla::widget { class ScrollbarDrawingWin11 final : public ScrollbarDrawingWin { public: - ScrollbarDrawingWin11() = default; + ScrollbarDrawingWin11() : ScrollbarDrawingWin(Kind::Win11) {} virtual ~ScrollbarDrawingWin11() = default; + ScrollbarSizes GetScrollbarSizes(nsPresContext*, StyleScrollbarWidth, + Overlay) override; + LayoutDeviceIntSize GetMinimumWidgetSize(nsPresContext*, StyleAppearance aAppearance, nsIFrame* aFrame) override; @@ -30,8 +33,6 @@ class ScrollbarDrawingWin11 final : public ScrollbarDrawingWin { const EventStates& aDocumentState, const Colors&) override; - static bool UseOverlayStyle(nsPresContext*); - // Returned colors are button, arrow. std::pair ComputeScrollbarButtonColors( nsIFrame*, StyleAppearance, const ComputedStyle&,