gecko-dev/widget/ScrollbarDrawingWin11.cpp

323 строки
12 KiB
C++

/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "ScrollbarDrawingWin11.h"
#include "mozilla/gfx/Helpers.h"
#include "mozilla/Maybe.h"
#include "mozilla/StaticPrefs_widget.h"
#include "nsLayoutUtils.h"
#include "Theme.h"
#include "nsNativeTheme.h"
using mozilla::gfx::sRGBColor;
namespace mozilla::widget {
LayoutDeviceIntSize ScrollbarDrawingWin11::GetMinimumWidgetSize(
nsPresContext* aPresContext, StyleAppearance aAppearance,
nsIFrame* aFrame) {
MOZ_ASSERT(nsNativeTheme::IsWidgetScrollbarPart(aAppearance));
if (!UseOverlayStyle(aPresContext)) {
return ScrollbarDrawingWin::GetMinimumWidgetSize(aPresContext, aAppearance,
aFrame);
}
constexpr float kArrowRatio = 14.0f / kDefaultWinScrollbarSize;
switch (aAppearance) {
case StyleAppearance::ScrollbarbuttonUp:
case StyleAppearance::ScrollbarbuttonDown: {
if (IsScrollbarWidthThin(aFrame)) {
return {};
}
const LayoutDeviceIntCoord size =
GetScrollbarSizes(aPresContext, aFrame).mVertical;
return LayoutDeviceIntSize{
size, (kArrowRatio * LayoutDeviceCoord(size)).Rounded()};
}
case StyleAppearance::ScrollbarbuttonLeft:
case StyleAppearance::ScrollbarbuttonRight: {
if (IsScrollbarWidthThin(aFrame)) {
return {};
}
const LayoutDeviceIntCoord size =
GetScrollbarSizes(aPresContext, aFrame).mHorizontal;
return LayoutDeviceIntSize{
(kArrowRatio * LayoutDeviceCoord(size)).Rounded(), size};
}
default:
return ScrollbarDrawingWin::GetMinimumWidgetSize(aPresContext,
aAppearance, aFrame);
}
}
sRGBColor ScrollbarDrawingWin11::ComputeScrollbarTrackColor(
nsIFrame* aFrame, const ComputedStyle& aStyle,
const EventStates& aDocumentState, const Colors& aColors) {
if (aColors.HighContrast()) {
return ScrollbarDrawingWin::ComputeScrollbarTrackColor(
aFrame, aStyle, aDocumentState, aColors);
}
const nsStyleUI* ui = aStyle.StyleUI();
if (ui->mScrollbarColor.IsColors()) {
return sRGBColor::FromABGR(
ui->mScrollbarColor.AsColors().track.CalcColor(aStyle));
}
return aColors.IsDark() ? sRGBColor::FromU8(23, 23, 23, 255)
: sRGBColor::FromU8(240, 240, 240, 255);
}
sRGBColor ScrollbarDrawingWin11::ComputeScrollbarThumbColor(
nsIFrame* aFrame, const ComputedStyle& aStyle,
const EventStates& aElementState, const EventStates& aDocumentState,
const Colors& aColors) {
if (aColors.HighContrast()) {
return ScrollbarDrawingWin::ComputeScrollbarThumbColor(
aFrame, aStyle, aElementState, aDocumentState, aColors);
}
const nscolor baseColor = [&] {
const nsStyleUI* ui = aStyle.StyleUI();
if (ui->mScrollbarColor.IsColors()) {
return ui->mScrollbarColor.AsColors().thumb.CalcColor(aStyle);
}
return aColors.IsDark() ? NS_RGBA(149, 149, 149, 255)
: NS_RGBA(133, 133, 133, 255);
}();
EventStates state = aElementState;
if (!IsScrollbarWidthThin(aStyle)) {
// non-thin scrollbars get hover feedback by changing thumb shape, so we
// only provide active feedback (and we use the hover state for that as it's
// more subtle).
state &= ~NS_EVENT_STATE_HOVER;
if (state.HasState(NS_EVENT_STATE_ACTIVE)) {
state &= ~NS_EVENT_STATE_ACTIVE;
state |= NS_EVENT_STATE_HOVER;
}
}
return sRGBColor::FromABGR(
ThemeColors::AdjustUnthemedScrollbarThumbColor(baseColor, state));
}
std::pair<sRGBColor, sRGBColor>
ScrollbarDrawingWin11::ComputeScrollbarButtonColors(
nsIFrame* aFrame, StyleAppearance aAppearance, const ComputedStyle& aStyle,
const EventStates& aElementState, const EventStates& aDocumentState,
const Colors& aColors) {
if (aColors.HighContrast()) {
return ScrollbarDrawingWin::ComputeScrollbarButtonColors(
aFrame, aAppearance, aStyle, aElementState, aDocumentState, aColors);
}
auto buttonColor =
ComputeScrollbarTrackColor(aFrame, aStyle, aDocumentState, aColors);
sRGBColor arrowColor = ComputeScrollbarThumbColor(
aFrame, aStyle, aElementState, aDocumentState, aColors);
return {buttonColor, arrowColor};
}
bool ScrollbarDrawingWin11::PaintScrollbarButton(
DrawTarget& aDrawTarget, StyleAppearance aAppearance,
const LayoutDeviceRect& aRect, nsIFrame* aFrame,
const ComputedStyle& aStyle, const EventStates& aElementState,
const EventStates& aDocumentState, const Colors& aColors,
const DPIRatio& aDpiRatio) {
if (!ScrollbarDrawing::IsParentScrollbarHoveredOrActive(aFrame)) {
return true;
}
const bool overlay = UseOverlayStyle(aFrame->PresContext());
auto [buttonColor, arrowColor] = ComputeScrollbarButtonColors(
aFrame, aAppearance, aStyle, aElementState, aDocumentState, aColors);
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};
float arrowPolygonXActive[] = {-4.0f, 4.0f, 4.0f, -0.25f,
-0.25f, -4.0f, -4.0f};
float arrowPolygonXHover[] = {-5.0f, 5.0f, 5.0f, 0.75f, -0.75f, -5.0f, -5.0f};
float arrowPolygonY[] = {2.5f, 2.5f, 1.0f, -4.0f, -4.0f, 1.0f, 2.5f};
float arrowPolygonYActive[] = {2.0f, 2.0f, 0.5f, -3.5f, -3.5f, 0.5f, 2.0f};
float arrowPolygonYHover[] = {3.0f, 3.0f, 1.5f, -4.5f, -4.5f, 1.5f, 3.0f};
float* arrowX = arrowPolygonX;
float* arrowY = arrowPolygonY;
const bool horizontal =
aAppearance == StyleAppearance::ScrollbarbuttonRight ||
aAppearance == StyleAppearance::ScrollbarbuttonLeft;
const float offset = [&] {
if (!overlay) {
return 0.0f; // Always center it in the rect.
}
// Compensate for the displacement we do of the thumb position by displacing
// the arrow as well, see comment in DoPaintScrollbarThumb.
if (horizontal) {
return -0.5f;
}
return aFrame->GetWritingMode().IsPhysicalLTR() ? 0.5f : -0.5f;
}();
const float kPolygonSize = kDefaultWinScrollbarSize;
const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
if (aElementState.HasState(NS_EVENT_STATE_ACTIVE)) {
arrowX = arrowPolygonXActive;
arrowY = arrowPolygonYActive;
} else if (aElementState.HasState(NS_EVENT_STATE_HOVER)) {
arrowX = arrowPolygonXHover;
arrowY = arrowPolygonYHover;
}
switch (aAppearance) {
case StyleAppearance::ScrollbarbuttonDown:
case StyleAppearance::ScrollbarbuttonRight:
for (int32_t i = 0; i < arrowNumPoints; i++) {
arrowY[i] *= -1;
}
[[fallthrough]];
case StyleAppearance::ScrollbarbuttonUp:
case StyleAppearance::ScrollbarbuttonLeft:
if (offset != 0.0f) {
for (int32_t i = 0; i < arrowNumPoints; i++) {
arrowX[i] += offset;
}
}
break;
default:
return false;
}
if (horizontal) {
std::swap(arrowX, arrowY);
}
LayoutDeviceRect arrowRect(aRect);
if (!overlay) {
auto margin = CSSCoord(2) * aDpiRatio;
arrowRect.Deflate(margin, margin);
}
ThemeDrawing::PaintArrow(aDrawTarget, arrowRect, arrowX, arrowY, kPolygonSize,
arrowNumPoints, arrowColor);
return true;
}
bool ScrollbarDrawingWin11::UseOverlayStyle(nsPresContext* aPresContext) {
return StaticPrefs::
widget_non_native_theme_win11_scrollbar_force_overlay_style() ||
aPresContext->UseOverlayScrollbars();
}
template <typename PaintBackendData>
bool ScrollbarDrawingWin11::DoPaintScrollbarThumb(
PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
bool aHorizontal, nsIFrame* aFrame, const ComputedStyle& aStyle,
const EventStates& aElementState, const EventStates& aDocumentState,
const Colors& aColors, const DPIRatio& aDpiRatio) {
sRGBColor thumbColor = ComputeScrollbarThumbColor(
aFrame, aStyle, aElementState, aDocumentState, aColors);
LayoutDeviceRect thumbRect(aRect);
const bool overlay = UseOverlayStyle(aFrame->PresContext());
const bool hovered =
ScrollbarDrawing::IsParentScrollbarHoveredOrActive(aFrame) ||
IsScrollbarWidthThin(aStyle);
if (!overlay) {
constexpr float kHoveredThumbRatio =
(1.0f - (11.0f / kDefaultWinScrollbarSize)) / 2.0f;
constexpr float kUnhoveredThumbRatio =
(1.0f - (9.0f / kDefaultWinScrollbarSize)) / 2.0f;
const float ratio = hovered ? kHoveredThumbRatio : kUnhoveredThumbRatio;
if (aHorizontal) {
thumbRect.Deflate(0, thumbRect.height * ratio);
} else {
thumbRect.Deflate(thumbRect.width * ratio, 0);
}
auto radius = CSSCoord(hovered ? 2 : 0);
ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, thumbRect, thumbColor,
sRGBColor(), 0, radius, aDpiRatio);
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;
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;
}
thumbRect.width *= kHoverThumbSize;
}
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;
}
bool ScrollbarDrawingWin11::PaintScrollbarThumb(
DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect, bool aHorizontal,
nsIFrame* aFrame, const ComputedStyle& aStyle,
const EventStates& aElementState, const EventStates& aDocumentState,
const Colors& aColors, const DPIRatio& aDpiRatio) {
return DoPaintScrollbarThumb(aDrawTarget, aRect, aHorizontal, aFrame, aStyle,
aElementState, aDocumentState, aColors,
aDpiRatio);
}
bool ScrollbarDrawingWin11::PaintScrollbarThumb(
WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
bool aHorizontal, nsIFrame* aFrame, const ComputedStyle& aStyle,
const EventStates& aElementState, const EventStates& aDocumentState,
const Colors& aColors, const DPIRatio& aDpiRatio) {
return DoPaintScrollbarThumb(aWrData, aRect, aHorizontal, aFrame, aStyle,
aElementState, aDocumentState, aColors,
aDpiRatio);
}
} // namespace mozilla::widget