diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /widget/android/nsNativeThemeAndroid.cpp | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | widget/android/nsNativeThemeAndroid.cpp | 926 |
1 files changed, 926 insertions, 0 deletions
diff --git a/widget/android/nsNativeThemeAndroid.cpp b/widget/android/nsNativeThemeAndroid.cpp new file mode 100644 index 0000000000..3f42a38374 --- /dev/null +++ b/widget/android/nsNativeThemeAndroid.cpp @@ -0,0 +1,926 @@ +/* -*- 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 "nsNativeThemeAndroid.h" + +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/ClearOnShutdown.h" +#include "nsCSSRendering.h" +#include "nsDateTimeControlFrame.h" +#include "nsDeviceContext.h" +#include "nsLayoutUtils.h" +#include "PathHelpers.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::widget; + +namespace mozilla { +namespace widget { + +static const sRGBColor sBackgroundColor(sRGBColor(1.0f, 1.0f, 1.0f)); +static const sRGBColor sBackgroundActiveColor(sRGBColor(0.88f, 0.88f, 0.9f)); +static const sRGBColor sBackgroundActiveColorDisabled(sRGBColor(0.88f, 0.88f, + 0.9f, 0.4f)); +static const sRGBColor sBorderColor(sRGBColor(0.62f, 0.62f, 0.68f)); +static const sRGBColor sBorderColorDisabled(sRGBColor(0.44f, 0.44f, 0.44f, + 0.4f)); +static const sRGBColor sBorderHoverColor(sRGBColor(0.5f, 0.5f, 0.56f)); +static const sRGBColor sBorderHoverColorDisabled(sRGBColor(0.5f, 0.5f, 0.56f, + 0.4f)); +static const sRGBColor sBorderFocusColor(sRGBColor(0.04f, 0.52f, 1.0f)); +static const sRGBColor sCheckBackgroundColor(sRGBColor(0.18f, 0.39f, 0.89f)); +static const sRGBColor sCheckBackgroundColorDisabled(sRGBColor(0.18f, 0.39f, + 0.89f, 0.4f)); +static const sRGBColor sCheckBackgroundHoverColor(sRGBColor(0.02f, 0.24f, + 0.58f)); +static const sRGBColor sCheckBackgroundHoverColorDisabled( + sRGBColor(0.02f, 0.24f, 0.58f, 0.4f)); +static const sRGBColor sCheckBackgroundActiveColor(sRGBColor(0.03f, 0.19f, + 0.45f)); +static const sRGBColor sCheckBackgroundActiveColorDisabled( + sRGBColor(0.03f, 0.19f, 0.45f, 0.4f)); +static const sRGBColor sDisabledColor(sRGBColor(0.89f, 0.89f, 0.89f)); +static const sRGBColor sActiveColor(sRGBColor(0.47f, 0.47f, 0.48f)); +static const sRGBColor sInputHoverColor(sRGBColor(0.05f, 0.05f, 0.05f, 0.5f)); +static const sRGBColor sRangeInputBackgroundColor(sRGBColor(0.89f, 0.89f, + 0.89f)); +static const sRGBColor sButtonColor(sRGBColor(0.98f, 0.98f, 0.98f)); +static const sRGBColor sButtonHoverColor(sRGBColor(0.94f, 0.94f, 0.96f)); +static const sRGBColor sButtonActiveColor(sRGBColor(0.88f, 0.88f, 0.90f)); +static const sRGBColor sWhiteColor(sRGBColor(1.0f, 1.0f, 1.0f, 0.0f)); + +static const CSSIntCoord kMinimumAndroidWidgetSize = 17; + +} // namespace widget +} // namespace mozilla + +NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeAndroid, nsNativeTheme, nsITheme) + +static uint32_t GetDPIRatio(nsIFrame* aFrame) { + return AppUnitsPerCSSPixel() / aFrame->PresContext() + ->DeviceContext() + ->AppUnitsPerDevPixelAtUnitFullZoom(); +} + +static bool IsDateTimeResetButton(nsIFrame* aFrame) { + nsIFrame* parent = aFrame->GetParent(); + if (parent && (parent = parent->GetParent()) && + (parent = parent->GetParent())) { + nsDateTimeControlFrame* dateTimeFrame = do_QueryFrame(parent); + if (dateTimeFrame) { + return true; + } + } + return false; +} + +static bool IsDateTimeTextField(nsIFrame* aFrame) { + nsDateTimeControlFrame* dateTimeFrame = do_QueryFrame(aFrame); + return dateTimeFrame; +} + +static void ComputeCheckColors(const EventStates& aState, + sRGBColor& aBackgroundColor, + sRGBColor& aBorderColor) { + bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); + bool isPressed = !isDisabled && aState.HasAllStates(NS_EVENT_STATE_HOVER | + NS_EVENT_STATE_ACTIVE); + bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER); + bool isFocused = aState.HasState(NS_EVENT_STATE_FOCUS); + bool isChecked = aState.HasState(NS_EVENT_STATE_CHECKED); + + sRGBColor fillColor = sBackgroundColor; + sRGBColor borderColor = sBorderColor; + if (isDisabled) { + if (isChecked) { + fillColor = borderColor = sCheckBackgroundColorDisabled; + } else { + fillColor = sBackgroundColor; + borderColor = sBorderColorDisabled; + } + } else { + if (isChecked) { + if (isPressed) { + fillColor = borderColor = sCheckBackgroundActiveColor; + } else if (isHovered) { + fillColor = borderColor = sCheckBackgroundHoverColor; + } else { + fillColor = borderColor = sCheckBackgroundColor; + } + } else if (isPressed) { + fillColor = sBackgroundActiveColor; + borderColor = sBorderHoverColor; + } else if (isFocused) { + fillColor = sBackgroundActiveColor; + borderColor = sBorderFocusColor; + } else if (isHovered) { + fillColor = sBackgroundColor; + borderColor = sBorderHoverColor; + } else { + fillColor = sBackgroundColor; + borderColor = sBorderColor; + } + } + + aBackgroundColor = fillColor; + aBorderColor = borderColor; +} + +// Checkbox and radio need to preserve aspect-ratio for compat. +static Rect FixAspectRatio(const Rect& aRect) { + Rect rect(aRect); + if (rect.width == rect.height) { + return rect; + } + + if (rect.width > rect.height) { + auto diff = rect.width - rect.height; + rect.width = rect.height; + rect.x += diff / 2; + } else { + auto diff = rect.height - rect.width; + rect.height = rect.width; + rect.y += diff / 2; + } + + return rect; +} + +// This pushes and pops a clip rect to the draw target. +// +// This is done to reduce fuzz in places where we may have antialiasing, because +// skia is not clip-invariant: given different clips, it does not guarantee the +// same result, even if the painted content doesn't intersect the clips. +// +// This is a bit sad, overall, but... +struct MOZ_RAII AutoClipRect { + AutoClipRect(DrawTarget& aDt, const Rect& aRect) : mDt(aDt) { + mDt.PushClipRect(aRect); + } + + ~AutoClipRect() { mDt.PopClip(); } + + private: + DrawTarget& mDt; +}; + +static void PaintRoundedRectWithBorder(DrawTarget* aDrawTarget, + const Rect& aRect, + const sRGBColor& aBackgroundColor, + const sRGBColor& aBorderColor, + CSSCoord aBorderWidth, CSSCoord aRadius, + uint32_t aDpi) { + const LayoutDeviceCoord borderWidth(aBorderWidth * aDpi); + const LayoutDeviceCoord radius(aRadius * aDpi); + + Rect rect(aRect); + // Deflate the rect by half the border width, so that the middle of the stroke + // fills exactly the area we want to fill and not more. + rect.Deflate(borderWidth * 0.5f); + + RectCornerRadii radii(radius, radius, radius, radius); + RefPtr<Path> roundedRect = MakePathForRoundedRect(*aDrawTarget, rect, radii); + + aDrawTarget->Fill(roundedRect, ColorPattern(ToDeviceColor(aBackgroundColor))); + aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(aBorderColor)), + StrokeOptions(borderWidth)); +} + +static void PaintCheckboxControl(DrawTarget* aDrawTarget, const Rect& aRect, + const EventStates& aState, uint32_t aDpi) { + const CSSCoord kBorderWidth = 2.0f; + const CSSCoord kRadius = 4.0f; + + sRGBColor backgroundColor; + sRGBColor borderColor; + ComputeCheckColors(aState, backgroundColor, borderColor); + PaintRoundedRectWithBorder(aDrawTarget, aRect, backgroundColor, borderColor, + kBorderWidth, kRadius, aDpi); +} + +static void PaintCheckMark(DrawTarget* aDrawTarget, const Rect& aRect, + const EventStates& aState, uint32_t aDpi) { + // Points come from the coordinates on a 7X7 unit box centered at 0,0 + const float checkPolygonX[] = {-2.5, -0.7, 2.5}; + const float checkPolygonY[] = {-0.3, 1.7, -1.5}; + const int32_t checkNumPoints = sizeof(checkPolygonX) / sizeof(float); + const int32_t checkSize = 8; + + auto center = aRect.Center(); + + // Scale the checkmark based on the smallest dimension + nscoord paintScale = std::min(aRect.width, aRect.height) / checkSize; + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + Point p = center + + Point(checkPolygonX[0] * paintScale, checkPolygonY[0] * paintScale); + builder->MoveTo(p); + for (int32_t polyIndex = 1; polyIndex < checkNumPoints; polyIndex++) { + p = center + Point(checkPolygonX[polyIndex] * paintScale, + checkPolygonY[polyIndex] * paintScale); + builder->LineTo(p); + } + RefPtr<Path> path = builder->Finish(); + aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(sBackgroundColor)), + StrokeOptions(2.0f * aDpi)); +} + +static void PaintIndeterminateMark(DrawTarget* aDrawTarget, const Rect& aRect, + const EventStates& aState) { + bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); + + Rect rect(aRect); + rect.y += (rect.height - rect.height / 4) / 2; + rect.height /= 4; + + aDrawTarget->FillRect( + rect, ColorPattern( + ToDeviceColor(isDisabled ? sDisabledColor : sBackgroundColor))); +} + +static void PaintStrokedEllipse(DrawTarget* aDrawTarget, const Rect& aRect, + const sRGBColor& aBackgroundColor, + const sRGBColor& aBorderColor, + const CSSCoord aBorderWidth, uint32_t aDpi) { + const LayoutDeviceCoord borderWidth(aBorderWidth * aDpi); + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + + // Deflate for the same reason as PaintRoundedRectWithBorder. Note that the + // size is the diameter, so we just shrink by the border width once. + Size size(aRect.Size() - Size(borderWidth, borderWidth)); + AppendEllipseToPath(builder, aRect.Center(), size); + RefPtr<Path> ellipse = builder->Finish(); + + aDrawTarget->Fill(ellipse, ColorPattern(ToDeviceColor(aBackgroundColor))); + aDrawTarget->Stroke(ellipse, ColorPattern(ToDeviceColor(aBorderColor)), + StrokeOptions(borderWidth)); +} + +static void PaintRadioControl(DrawTarget* aDrawTarget, const Rect& aRect, + const EventStates& aState, uint32_t aDpi) { + const CSSCoord kBorderWidth = 2.0f; + + sRGBColor backgroundColor; + sRGBColor borderColor; + ComputeCheckColors(aState, backgroundColor, borderColor); + + PaintStrokedEllipse(aDrawTarget, aRect, backgroundColor, borderColor, + kBorderWidth, aDpi); +} + +static void PaintCheckedRadioButton(DrawTarget* aDrawTarget, const Rect& aRect, + uint32_t aDpi) { + Rect rect(aRect); + rect.x += 4.5f * aDpi; + rect.width -= 9.0f * aDpi; + rect.y += 4.5f * aDpi; + rect.height -= 9.0f * aDpi; + + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + AppendEllipseToPath(builder, rect.Center(), rect.Size()); + RefPtr<Path> ellipse = builder->Finish(); + aDrawTarget->Fill(ellipse, ColorPattern(ToDeviceColor(sBackgroundColor))); +} + +static sRGBColor ComputeBorderColor(const EventStates& aState) { + bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); + bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER); + bool isFocused = aState.HasState(NS_EVENT_STATE_FOCUS); + if (isFocused) { + return sBorderFocusColor; + } + if (isHovered) { + return sBorderHoverColor; + } + return sBorderColor; +} + +static void PaintTextField(DrawTarget* aDrawTarget, const Rect& aRect, + const EventStates& aState, uint32_t aDpi) { + bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); + const sRGBColor& backgroundColor = + isDisabled ? sDisabledColor : sBackgroundColor; + const sRGBColor borderColor = ComputeBorderColor(aState); + + const CSSCoord kRadius = 4.0f; + + PaintRoundedRectWithBorder(aDrawTarget, aRect, backgroundColor, borderColor, + kTextFieldBorderWidth, kRadius, aDpi); +} + +std::pair<sRGBColor, sRGBColor> ComputeButtonColors( + const EventStates& aState, bool aIsDatetimeResetButton = false) { + bool isActive = + aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE); + bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); + bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER); + + const sRGBColor& backgroundColor = [&] { + if (isDisabled) { + return sDisabledColor; + } + if (aIsDatetimeResetButton) { + return sWhiteColor; + } + if (isActive) { + return sButtonActiveColor; + } + if (isHovered) { + return sButtonHoverColor; + } + return sButtonColor; + }(); + + const sRGBColor borderColor = ComputeBorderColor(aState); + + return std::make_pair(backgroundColor, borderColor); +} + +static void PaintMenulist(DrawTarget* aDrawTarget, const Rect& aRect, + const EventStates& aState, uint32_t aDpi) { + const CSSCoord kRadius = 4.0f; + + sRGBColor backgroundColor, borderColor; + std::tie(backgroundColor, borderColor) = ComputeButtonColors(aState); + + PaintRoundedRectWithBorder(aDrawTarget, aRect, backgroundColor, borderColor, + kMenulistBorderWidth, kRadius, aDpi); +} + +static void PaintArrow(DrawTarget* aDrawTarget, const Rect& aRect, + const int32_t aArrowPolygonX[], + const int32_t aArrowPolygonY[], + const int32_t aArrowNumPoints, const int32_t aArrowSize, + const sRGBColor aFillColor, uint32_t aDpi) { + nscoord paintScale = std::min(aRect.width, aRect.height) / aArrowSize; + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + Point p = aRect.Center() + Point(aArrowPolygonX[0] * paintScale, + aArrowPolygonY[0] * paintScale); + + builder->MoveTo(p); + for (int32_t polyIndex = 1; polyIndex < aArrowNumPoints; polyIndex++) { + p = aRect.Center() + Point(aArrowPolygonX[polyIndex] * paintScale, + aArrowPolygonY[polyIndex] * paintScale); + builder->LineTo(p); + } + RefPtr<Path> path = builder->Finish(); + + aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(aFillColor)), + StrokeOptions(2.0f * aDpi)); +} + +static void PaintMenulistArrowButton(nsIFrame* aFrame, DrawTarget* aDrawTarget, + const Rect& aRect, + const EventStates& aState, uint32_t aDpi) { + bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); + bool isPressed = !isDisabled && aState.HasAllStates(NS_EVENT_STATE_HOVER | + NS_EVENT_STATE_ACTIVE); + bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER); + bool isHTML = nsNativeTheme::IsHTMLContent(aFrame); + + if (!isHTML && nsNativeTheme::CheckBooleanAttr(aFrame, nsGkAtoms::open)) { + isHovered = false; + } + + const int32_t arrowSize = 8; + int32_t arrowPolygonX[] = {-4, -2, 0}; + int32_t arrowPolygonY[] = {-1, 1, -1}; + const int32_t arrowNumPoints = ArrayLength(arrowPolygonX); + + PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints, + arrowSize, + isPressed ? sActiveColor + : isHovered ? sBorderHoverColor + : sBorderColor, + aDpi); +} + +static void PaintSpinnerButton(DrawTarget* aDrawTarget, const Rect& aRect, + const EventStates& aState, + StyleAppearance aAppearance, uint32_t aDpi) { + bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); + bool isPressed = !isDisabled && aState.HasAllStates(NS_EVENT_STATE_HOVER | + NS_EVENT_STATE_ACTIVE); + bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER); + + const int32_t arrowSize = 8; + int32_t arrowPolygonX[] = {0, 2, 4}; + int32_t arrowPolygonY[] = {-3, -1, -3}; + const int32_t arrowNumPoints = ArrayLength(arrowPolygonX); + + if (aAppearance == StyleAppearance::SpinnerUpbutton) { + for (int32_t i = 0; i < arrowNumPoints; i++) { + arrowPolygonY[i] *= -1; + } + } + + PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints, + arrowSize, + isPressed ? sActiveColor + : isHovered ? sBorderHoverColor + : sBorderColor, + aDpi); +} + +static void PaintRangeInputBackground(DrawTarget* aDrawTarget, + const Rect& aRect, + const EventStates& aState, uint32_t aDpi, + bool aHorizontal) { + Rect rect(aRect); + const LayoutDeviceCoord kVerticalSize = + kMinimumAndroidWidgetSize * 0.25f * aDpi; + + if (aHorizontal) { + rect.y += (rect.height - kVerticalSize) / 2; + rect.height = kVerticalSize; + } else { + rect.x += (rect.width - kVerticalSize) / 2; + rect.width = kVerticalSize; + } + + aDrawTarget->FillRect( + rect, ColorPattern(ToDeviceColor(sRangeInputBackgroundColor))); + aDrawTarget->StrokeRect(rect, + ColorPattern(ToDeviceColor(sButtonActiveColor))); +} + +static void PaintScrollbarthumbHorizontal(DrawTarget* aDrawTarget, + const Rect& aRect, + const EventStates& aState) { + sRGBColor thumbColor = sScrollbarThumbColor; + if (aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) { + thumbColor = sScrollbarThumbColorActive; + } else if (aState.HasState(NS_EVENT_STATE_HOVER)) { + thumbColor = sScrollbarThumbColorHover; + } + aDrawTarget->FillRect(aRect, ColorPattern(ToDeviceColor(thumbColor))); +} + +static void PaintScrollbarthumbVertical(DrawTarget* aDrawTarget, + const Rect& aRect, + const EventStates& aState) { + sRGBColor thumbColor = sScrollbarThumbColor; + if (aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) { + thumbColor = sScrollbarThumbColorActive; + } else if (aState.HasState(NS_EVENT_STATE_HOVER)) { + thumbColor = sScrollbarThumbColorHover; + } + aDrawTarget->FillRect(aRect, ColorPattern(ToDeviceColor(thumbColor))); +} + +static void PaintScrollbarHorizontal(DrawTarget* aDrawTarget, + const Rect& aRect) { + aDrawTarget->FillRect(aRect, ColorPattern(ToDeviceColor(sScrollbarColor))); + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + builder->MoveTo(Point(aRect.x, aRect.y)); + builder->LineTo(Point(aRect.x + aRect.width, aRect.y)); + RefPtr<Path> path = builder->Finish(); + aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(sScrollbarBorderColor))); +} + +static void PaintScrollbarVerticalAndCorner(DrawTarget* aDrawTarget, + const Rect& aRect, uint32_t aDpi) { + aDrawTarget->FillRect(aRect, ColorPattern(ToDeviceColor(sScrollbarColor))); + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + builder->MoveTo(Point(aRect.x, aRect.y)); + builder->LineTo(Point(aRect.x, aRect.y + aRect.height)); + RefPtr<Path> path = builder->Finish(); + aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(sScrollbarBorderColor)), + StrokeOptions(1.0f * aDpi)); +} + +static void PaintScrollbarbutton(DrawTarget* aDrawTarget, + StyleAppearance aAppearance, const Rect& aRect, + const EventStates& aState, uint32_t aDpi) { + bool isActive = + aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE); + bool isHovered = aState.HasState(NS_EVENT_STATE_HOVER); + + aDrawTarget->FillRect( + aRect, ColorPattern(ToDeviceColor(isActive ? sScrollbarButtonActiveColor + : isHovered ? sScrollbarButtonHoverColor + : sScrollbarColor))); + + // Start with Up arrow. + int32_t arrowPolygonX[] = {3, 0, -3}; + int32_t arrowPolygonY[] = {2, -1, 2}; + const int32_t arrowNumPoints = ArrayLength(arrowPolygonX); + const int32_t arrowSize = 14; + + switch (aAppearance) { + case StyleAppearance::ScrollbarbuttonUp: + break; + case StyleAppearance::ScrollbarbuttonDown: + for (int32_t i = 0; i < arrowNumPoints; i++) { + arrowPolygonY[i] *= -1; + } + break; + case StyleAppearance::ScrollbarbuttonLeft: + for (int32_t i = 0; i < arrowNumPoints; i++) { + int32_t temp = arrowPolygonX[i]; + arrowPolygonX[i] = arrowPolygonY[i]; + arrowPolygonY[i] = temp; + } + break; + case StyleAppearance::ScrollbarbuttonRight: + for (int32_t i = 0; i < arrowNumPoints; i++) { + int32_t temp = arrowPolygonX[i]; + arrowPolygonX[i] = arrowPolygonY[i] * -1; + arrowPolygonY[i] = temp; + } + break; + default: + return; + } + + PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints, + arrowSize, + isActive ? sScrollbarArrowColorActive + : isHovered ? sScrollbarArrowColorHover + : sScrollbarArrowColor, + aDpi); + + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(); + builder->MoveTo(Point(aRect.x, aRect.y)); + if (aAppearance == StyleAppearance::ScrollbarbuttonUp || + aAppearance == StyleAppearance::ScrollbarbuttonDown) { + builder->LineTo(Point(aRect.x, aRect.y + aRect.height)); + } else { + builder->LineTo(Point(aRect.x + aRect.width, aRect.y)); + } + + RefPtr<Path> path = builder->Finish(); + aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(sScrollbarBorderColor)), + StrokeOptions(1.0f * aDpi)); +} + +static void PaintButton(nsIFrame* aFrame, DrawTarget* aDrawTarget, + const Rect& aRect, const EventStates& aState, + uint32_t aDpi) { + const CSSCoord kRadius = 4.0f; + + // FIXME: The DateTimeResetButton bit feels like a bit of a hack. + sRGBColor backgroundColor, borderColor; + std::tie(backgroundColor, borderColor) = + ComputeButtonColors(aState, IsDateTimeResetButton(aFrame)); + + PaintRoundedRectWithBorder(aDrawTarget, aRect, backgroundColor, borderColor, + kButtonBorderWidth, kRadius, aDpi); +} + +static void PaintRangeThumb(DrawTarget* aDrawTarget, const Rect& aRect, + const EventStates& aState, uint32_t aDpi) { + const CSSCoord kBorderWidth = 2.0f; + + sRGBColor backgroundColor, borderColor; + std::tie(backgroundColor, borderColor) = ComputeButtonColors(aState); + + PaintStrokedEllipse(aDrawTarget, aRect, backgroundColor, borderColor, + kBorderWidth, aDpi); +} + +NS_IMETHODIMP +nsNativeThemeAndroid::DrawWidgetBackground(gfxContext* aContext, + nsIFrame* aFrame, + StyleAppearance aAppearance, + const nsRect& aRect, + const nsRect& /* aDirtyRect */) { + DrawTarget* dt = aContext->GetDrawTarget(); + const nscoord twipsPerPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); + EventStates eventState = GetContentState(aFrame, aAppearance); + + Rect devPxRect = NSRectToSnappedRect(aRect, twipsPerPixel, *dt); + AutoClipRect clip(*dt, devPxRect); + + if (aAppearance == StyleAppearance::MozMenulistArrowButton) { + bool isHTML = IsHTMLContent(aFrame); + nsIFrame* parentFrame = aFrame->GetParent(); + bool isMenulist = !isHTML && parentFrame->IsMenuFrame(); + // HTML select and XUL menulist dropdown buttons get state from the + // parent. + if (isHTML || isMenulist) { + aFrame = parentFrame; + eventState = GetContentState(parentFrame, aAppearance); + } + } + + uint32_t dpi = GetDPIRatio(aFrame); + + switch (aAppearance) { + case StyleAppearance::Radio: { + auto rect = FixAspectRatio(devPxRect); + PaintRadioControl(dt, rect, eventState, dpi); + if (IsSelected(aFrame)) { + PaintCheckedRadioButton(dt, rect, dpi); + } + break; + } + case StyleAppearance::Checkbox: { + auto rect = FixAspectRatio(devPxRect); + PaintCheckboxControl(dt, rect, eventState, dpi); + if (IsChecked(aFrame)) { + PaintCheckMark(dt, rect, eventState, dpi); + } + if (GetIndeterminate(aFrame)) { + PaintIndeterminateMark(dt, rect, eventState); + } + break; + } + case StyleAppearance::Textarea: + case StyleAppearance::Textfield: + case StyleAppearance::NumberInput: + PaintTextField(dt, devPxRect, eventState, dpi); + break; + case StyleAppearance::Listbox: + case StyleAppearance::Menulist: + case StyleAppearance::MenulistButton: + PaintMenulist(dt, devPxRect, eventState, dpi); + break; + case StyleAppearance::MozMenulistArrowButton: + PaintMenulistArrowButton(aFrame, dt, devPxRect, eventState, dpi); + break; + case StyleAppearance::SpinnerUpbutton: + case StyleAppearance::SpinnerDownbutton: + PaintSpinnerButton(dt, devPxRect, eventState, aAppearance, dpi); + break; + case StyleAppearance::Range: + PaintRangeInputBackground(dt, devPxRect, eventState, dpi, + IsRangeHorizontal(aFrame)); + break; + case StyleAppearance::RangeThumb: + // TODO(emilio): Do we want to enforce it being a circle using + // FixAspectRatio here? For now let authors tweak, it's a custom pseudo so + // it doesn't probably have much compat impact if at all. + PaintRangeThumb(dt, devPxRect, eventState, dpi); + break; + case StyleAppearance::ScrollbarthumbHorizontal: + PaintScrollbarthumbHorizontal(dt, devPxRect, eventState); + break; + case StyleAppearance::ScrollbarthumbVertical: + PaintScrollbarthumbVertical(dt, devPxRect, eventState); + break; + case StyleAppearance::ScrollbarHorizontal: + PaintScrollbarHorizontal(dt, devPxRect); + break; + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::Scrollcorner: + PaintScrollbarVerticalAndCorner(dt, devPxRect, dpi); + break; + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: + PaintScrollbarbutton(dt, aAppearance, devPxRect, eventState, dpi); + break; + case StyleAppearance::Button: + PaintButton(aFrame, dt, devPxRect, eventState, dpi); + break; + default: + MOZ_ASSERT_UNREACHABLE( + "Should not get here with a widget type we don't support."); + return NS_ERROR_NOT_IMPLEMENTED; + } + + return NS_OK; +} + +/*bool +nsNativeThemeAndroid::CreateWebRenderCommandsForWidget(mozilla::wr::DisplayListBuilder& +aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources, const +mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* +aManager, nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect) { +}*/ + +LayoutDeviceIntMargin nsNativeThemeAndroid::GetWidgetBorder( + nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) { + uint32_t dpi = GetDPIRatio(aFrame); + switch (aAppearance) { + case StyleAppearance::Textfield: + case StyleAppearance::Textarea: + case StyleAppearance::NumberInput: { + const LayoutDeviceIntCoord w = kTextFieldBorderWidth * dpi; + return LayoutDeviceIntMargin(w, w, w, w); + } + case StyleAppearance::Listbox: + case StyleAppearance::Menulist: + case StyleAppearance::MenulistButton: { + const LayoutDeviceIntCoord w = kMenulistBorderWidth * dpi; + return LayoutDeviceIntMargin(w, w, w, w); + } + case StyleAppearance::Button: { + const LayoutDeviceIntCoord w = kButtonBorderWidth * dpi; + return LayoutDeviceIntMargin(w, w, w, w); + } + default: + return LayoutDeviceIntMargin(); + } +} + +bool nsNativeThemeAndroid::GetWidgetPadding(nsDeviceContext* aContext, + nsIFrame* aFrame, + StyleAppearance aAppearance, + LayoutDeviceIntMargin* aResult) { + uint32_t dpiRatio = GetDPIRatio(aFrame); + switch (aAppearance) { + // Radios and checkboxes return a fixed size in GetMinimumWidgetSize + // and have a meaningful baseline, so they can't have + // author-specified padding. + case StyleAppearance::Radio: + case StyleAppearance::Checkbox: + aResult->SizeTo(0, 0, 0, 0); + return true; + case StyleAppearance::MozMenulistArrowButton: + aResult->SizeTo(1 * dpiRatio, 4 * dpiRatio, 1 * dpiRatio, 4 * dpiRatio); + return true; + default: + break; + } + + // Respect author padding. + // + // TODO(emilio): Consider just unconditionally returning false, so that the + // default size of all elements matches other platforms and the UA stylesheet. + if (aFrame->PresContext()->HasAuthorSpecifiedRules( + aFrame, NS_AUTHOR_SPECIFIED_PADDING)) { + return false; + } + + uint32_t dpi = GetDPIRatio(aFrame); + switch (aAppearance) { + case StyleAppearance::Textarea: + case StyleAppearance::Listbox: + case StyleAppearance::Menulist: + case StyleAppearance::MenulistButton: + case StyleAppearance::NumberInput: + aResult->SizeTo(6 * dpi, 7 * dpi, 6 * dpi, 7 * dpi); + return true; + case StyleAppearance::Button: + aResult->SizeTo(6 * dpi, 21 * dpi, 6 * dpi, 21 * dpi); + return true; + case StyleAppearance::Textfield: + if (IsDateTimeTextField(aFrame)) { + aResult->SizeTo(7 * dpi, 7 * dpi, 5 * dpi, 7 * dpi); + return true; + } + aResult->SizeTo(6 * dpi, 7 * dpi, 6 * dpi, 7 * dpi); + return true; + default: + return false; + } +} + +bool nsNativeThemeAndroid::GetWidgetOverflow(nsDeviceContext* aContext, + nsIFrame* aFrame, + StyleAppearance aAppearance, + nsRect* aOverflowRect) { + // TODO(bug 1620360): This should return non-zero for + // StyleAppearance::FocusOutline, if we implement outline-style: auto. + return false; +} + +NS_IMETHODIMP +nsNativeThemeAndroid::GetMinimumWidgetSize(nsPresContext* aPresContext, + nsIFrame* aFrame, + StyleAppearance aAppearance, + LayoutDeviceIntSize* aResult, + bool* aIsOverridable) { + uint32_t dpiRatio = GetDPIRatio(aFrame); + aResult->width = aResult->height = + static_cast<uint32_t>(kMinimumAndroidWidgetSize) * dpiRatio; + *aIsOverridable = true; + if (aAppearance == StyleAppearance::MozMenulistArrowButton) { + aResult->width = + static_cast<uint32_t>(kMinimumDropdownArrowButtonWidth) * dpiRatio; + } + return NS_OK; +} + +nsITheme::Transparency nsNativeThemeAndroid::GetWidgetTransparency( + nsIFrame* aFrame, StyleAppearance aAppearance) { + return eUnknownTransparency; +} + +NS_IMETHODIMP +nsNativeThemeAndroid::WidgetStateChanged(nsIFrame* aFrame, + StyleAppearance aAppearance, + nsAtom* aAttribute, + bool* aShouldRepaint, + const nsAttrValue* aOldValue) { + if (!aAttribute) { + // Hover/focus/active changed. Always repaint. + *aShouldRepaint = true; + } else { + // Check the attribute to see if it's relevant. + // disabled, checked, dlgtype, default, etc. + *aShouldRepaint = false; + if ((aAttribute == nsGkAtoms::disabled) || + (aAttribute == nsGkAtoms::checked) || + (aAttribute == nsGkAtoms::selected) || + (aAttribute == nsGkAtoms::visuallyselected) || + (aAttribute == nsGkAtoms::menuactive) || + (aAttribute == nsGkAtoms::sortDirection) || + (aAttribute == nsGkAtoms::focused) || + (aAttribute == nsGkAtoms::_default) || + (aAttribute == nsGkAtoms::open) || (aAttribute == nsGkAtoms::hover)) { + *aShouldRepaint = true; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsNativeThemeAndroid::ThemeChanged() { return NS_OK; } + +bool nsNativeThemeAndroid::WidgetAppearanceDependsOnWindowFocus( + StyleAppearance) { + return false; +} + +nsITheme::ThemeGeometryType nsNativeThemeAndroid::ThemeGeometryTypeForWidget( + nsIFrame* aFrame, StyleAppearance aAppearance) { + return eThemeGeometryTypeUnknown; +} + +bool nsNativeThemeAndroid::ThemeSupportsWidget(nsPresContext* aPresContext, + nsIFrame* aFrame, + StyleAppearance aAppearance) { + if (IsWidgetScrollbarPart(aAppearance)) { + const auto* style = nsLayoutUtils::StyleForScrollbar(aFrame); + // We don't currently handle custom scrollbars on + // nsNativeThemeAndroid. We could, potentially. + if (style->StyleUI()->HasCustomScrollbars() || + style->StyleUIReset()->mScrollbarWidth == StyleScrollbarWidth::Thin) { + return false; + } + } + + switch (aAppearance) { + case StyleAppearance::Radio: + case StyleAppearance::Checkbox: + case StyleAppearance::Textarea: + case StyleAppearance::Textfield: + case StyleAppearance::Range: + case StyleAppearance::RangeThumb: + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: + case StyleAppearance::ScrollbarthumbHorizontal: + case StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::ScrollbarNonDisappearing: + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::Scrollcorner: + case StyleAppearance::Button: + case StyleAppearance::Listbox: + case StyleAppearance::Menulist: + case StyleAppearance::MenulistButton: + case StyleAppearance::NumberInput: + case StyleAppearance::MozMenulistArrowButton: + case StyleAppearance::SpinnerUpbutton: + case StyleAppearance::SpinnerDownbutton: + return !IsWidgetStyled(aPresContext, aFrame, aAppearance); + default: + return false; + } +} + +bool nsNativeThemeAndroid::WidgetIsContainer(StyleAppearance aAppearance) { + switch (aAppearance) { + case StyleAppearance::MozMenulistArrowButton: + case StyleAppearance::Radio: + case StyleAppearance::Checkbox: + return false; + default: + return true; + } +} + +bool nsNativeThemeAndroid::ThemeDrawsFocusForWidget( + StyleAppearance aAppearance) { + switch (aAppearance) { + case StyleAppearance::Range: + // TODO(emilio): Checkbox / Radio don't have focus indicators when checked. + // If they did, we could just return true here unconditionally. + case StyleAppearance::Checkbox: + case StyleAppearance::Radio: + return false; + default: + return true; + } +} + +bool nsNativeThemeAndroid::ThemeNeedsComboboxDropmarker() { return true; } + +already_AddRefed<nsITheme> do_GetNativeThemeDoNotUseDirectly() { + static StaticRefPtr<nsITheme> gInstance; + if (MOZ_UNLIKELY(!gInstance)) { + gInstance = new nsNativeThemeAndroid(); + ClearOnShutdown(&gInstance); + } + return do_AddRef(gInstance); +} |