/* -*- 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 "nsNativeBasicTheme.h" #include "gfxBlur.h" #include "mozilla/dom/Document.h" #include "mozilla/gfx/Rect.h" #include "mozilla/gfx/Types.h" #include "mozilla/gfx/Filters.h" #include "nsCSSColorUtils.h" #include "nsCSSRendering.h" #include "nsLayoutUtils.h" #include "PathHelpers.h" using namespace mozilla; using namespace mozilla::widget; using namespace mozilla::gfx; NS_IMPL_ISUPPORTS_INHERITED(nsNativeBasicTheme, nsNativeTheme, nsITheme) namespace { // 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 LayoutDeviceRect& aRect) : mDt(aDt) { mDt.PushClipRect(aRect.ToUnknownRect()); } ~AutoClipRect() { mDt.PopClip(); } private: DrawTarget& mDt; }; } // namespace static bool IsScrollbarWidthThin(nsIFrame* aFrame) { ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame); auto scrollbarWidth = style->StyleUIReset()->mScrollbarWidth; return scrollbarWidth == StyleScrollbarWidth::Thin; } /* static */ auto nsNativeBasicTheme::GetDPIRatio(nsIFrame* aFrame) -> DPIRatio { return DPIRatio(float(AppUnitsPerCSSPixel()) / aFrame->PresContext() ->DeviceContext() ->AppUnitsPerDevPixelAtUnitFullZoom()); } /* static */ bool nsNativeBasicTheme::IsDateTimeResetButton(nsIFrame* aFrame) { if (!aFrame) { return false; } 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 nsNativeBasicTheme::IsDateTimeTextField(nsIFrame* aFrame) { nsDateTimeControlFrame* dateTimeFrame = do_QueryFrame(aFrame); return dateTimeFrame; } /* static */ bool nsNativeBasicTheme::IsColorPickerButton(nsIFrame* aFrame) { nsColorControlFrame* colorPickerButton = do_QueryFrame(aFrame); return colorPickerButton; } /* static */ LayoutDeviceRect nsNativeBasicTheme::FixAspectRatio( const LayoutDeviceRect& aRect) { // Checkbox and radio need to preserve aspect-ratio for compat. LayoutDeviceRect 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; } std::pair nsNativeBasicTheme::ComputeCheckboxColors( const EventStates& aState, StyleAppearance aAppearance) { MOZ_ASSERT(aAppearance == StyleAppearance::Checkbox || aAppearance == StyleAppearance::Radio); 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 isChecked = aState.HasState(NS_EVENT_STATE_CHECKED); bool isIndeterminate = aAppearance == StyleAppearance::Checkbox && aState.HasState(NS_EVENT_STATE_INDETERMINATE); sRGBColor backgroundColor = sColorWhite; sRGBColor borderColor = sColorGrey40; if (isDisabled) { if (isChecked || isIndeterminate) { backgroundColor = borderColor = sColorGrey40Alpha50; } else { backgroundColor = sColorWhiteAlpha50; borderColor = sColorGrey40Alpha50; } } else { if (isChecked || isIndeterminate) { if (isPressed) { backgroundColor = borderColor = sColorAccentDarker; } else if (isHovered) { backgroundColor = borderColor = sColorAccentDark; } else { backgroundColor = borderColor = sColorAccent; } } else if (isPressed) { backgroundColor = sColorGrey20; borderColor = sColorGrey60; } else if (isHovered) { backgroundColor = sColorWhite; borderColor = sColorGrey50; } else { backgroundColor = sColorWhite; borderColor = sColorGrey40; } } return std::make_pair(backgroundColor, borderColor); } sRGBColor nsNativeBasicTheme::ComputeCheckmarkColor(const EventStates& aState) { bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); return isDisabled ? sColorWhiteAlpha50 : sColorWhite; } std::pair nsNativeBasicTheme::ComputeRadioCheckmarkColors( const EventStates& aState) { auto [unusedColor, checkColor] = ComputeCheckboxColors(aState, StyleAppearance::Radio); Unused << unusedColor; return std::make_pair(sColorWhite, checkColor); } sRGBColor nsNativeBasicTheme::ComputeBorderColor(const EventStates& aState) { bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); bool isActive = 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_FOCUSRING); if (isDisabled) { return sColorGrey40Alpha50; } if (isFocused) { return sColorAccent; } if (isActive) { return sColorGrey60; } if (isHovered) { return sColorGrey50; } return sColorGrey40; } std::pair nsNativeBasicTheme::ComputeButtonColors( const EventStates& aState, nsIFrame* aFrame) { 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 sColorGrey10Alpha50; } if (IsDateTimeResetButton(aFrame)) { return sColorWhite; } if (isActive) { return sColorGrey30; } if (isHovered) { return sColorGrey20; } return sColorGrey10; }(); const sRGBColor borderColor = ComputeBorderColor(aState); return std::make_pair(backgroundColor, borderColor); } std::pair nsNativeBasicTheme::ComputeTextfieldColors( const EventStates& aState) { bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); const sRGBColor& backgroundColor = isDisabled ? sColorWhiteAlpha50 : sColorWhite; const sRGBColor borderColor = ComputeBorderColor(aState); return std::make_pair(backgroundColor, borderColor); } std::pair nsNativeBasicTheme::ComputeRangeProgressColors( const EventStates& aState) { 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); if (isDisabled) { return std::make_pair(sColorGrey40Alpha50, sColorGrey40Alpha50); } if (isActive || isHovered) { return std::make_pair(sColorAccentDark, sColorAccentDarker); } return std::make_pair(sColorAccent, sColorAccentDark); } std::pair nsNativeBasicTheme::ComputeRangeTrackColors( const EventStates& aState) { 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); if (isDisabled) { return std::make_pair(sColorGrey10Alpha50, sColorGrey40Alpha50); } if (isActive || isHovered) { return std::make_pair(sColorGrey20, sColorGrey50); } return std::make_pair(sColorGrey10, sColorGrey40); } std::pair nsNativeBasicTheme::ComputeRangeThumbColors( const EventStates& aState) { 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 sColorGrey50Alpha50; } if (isActive) { return sColorAccent; } if (isHovered) { return sColorGrey60; } return sColorGrey50; }(); const sRGBColor borderColor = sColorWhite; return std::make_pair(backgroundColor, borderColor); } std::pair nsNativeBasicTheme::ComputeProgressColors() { return std::make_pair(sColorAccent, sColorAccentDark); } std::pair nsNativeBasicTheme::ComputeProgressTrackColors() { return std::make_pair(sColorGrey10, sColorGrey40); } std::pair nsNativeBasicTheme::ComputeMeterchunkColors( const EventStates& aMeterState) { sRGBColor borderColor = sColorMeterGreen20; sRGBColor chunkColor = sColorMeterGreen10; if (aMeterState.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) { borderColor = sColorMeterYellow20; chunkColor = sColorMeterYellow10; } else if (aMeterState.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) { borderColor = sColorMeterRed20; chunkColor = sColorMeterRed10; } return std::make_pair(chunkColor, borderColor); } std::pair nsNativeBasicTheme::ComputeMeterTrackColors() { return std::make_pair(sColorGrey10, sColorGrey40); } sRGBColor nsNativeBasicTheme::ComputeMenulistArrowButtonColor( const EventStates& aState) { bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); return isDisabled ? sColorGrey60Alpha50 : sColorGrey60; } std::array nsNativeBasicTheme::ComputeFocusRectColors() { return {sColorAccent, sColorWhiteAlpha80, sColorAccentLight}; } std::pair nsNativeBasicTheme::ComputeScrollbarColors( nsIFrame* aFrame, const ComputedStyle& aStyle, const EventStates& aDocumentState, bool aIsRoot) { const nsStyleUI* ui = aStyle.StyleUI(); nscolor color; if (ui->mScrollbarColor.IsColors()) { color = ui->mScrollbarColor.AsColors().track.CalcColor(aStyle); } else if (aDocumentState.HasAllStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) { color = LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarInactive, sScrollbarColor.ToABGR()); } else { color = LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbar, sScrollbarColor.ToABGR()); } if (aIsRoot) { // Root scrollbars must be opaque. nscolor bg = LookAndFeel::GetColor(LookAndFeel::ColorID::WindowBackground, NS_RGB(0xff, 0xff, 0xff)); color = NS_ComposeColors(bg, color); } return std::make_pair(gfx::sRGBColor::FromABGR(color), sScrollbarBorderColor); } sRGBColor nsNativeBasicTheme::ComputeScrollbarThumbColor( nsIFrame* aFrame, const ComputedStyle& aStyle, const EventStates& aElementState, const EventStates& aDocumentState) { const nsStyleUI* ui = aStyle.StyleUI(); nscolor color; if (ui->mScrollbarColor.IsColors()) { color = ui->mScrollbarColor.AsColors().thumb.CalcColor(aStyle); } else if (aDocumentState.HasAllStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) { color = LookAndFeel::GetColor( LookAndFeel::ColorID::ThemedScrollbarThumbInactive, sScrollbarThumbColor.ToABGR()); } else if (aElementState.HasAllStates(NS_EVENT_STATE_ACTIVE)) { color = LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarThumbActive, sScrollbarThumbColorActive.ToABGR()); } else if (aElementState.HasAllStates(NS_EVENT_STATE_HOVER)) { color = LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarThumbHover, sScrollbarThumbColorHover.ToABGR()); } else { color = LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarThumb, sScrollbarThumbColor.ToABGR()); } return gfx::sRGBColor::FromABGR(color); } std::array nsNativeBasicTheme::ComputeScrollbarButtonColors( nsIFrame* aFrame, StyleAppearance aAppearance, const ComputedStyle& aStyle, const EventStates& aElementState, const EventStates& aDocumentState) { bool isActive = aElementState.HasState(NS_EVENT_STATE_ACTIVE); bool isHovered = aElementState.HasState(NS_EVENT_STATE_HOVER); bool hasCustomColor = aStyle.StyleUI()->mScrollbarColor.IsColors(); sRGBColor buttonColor; if (hasCustomColor) { // When scrollbar-color is in use, use the thumb color for the button. buttonColor = ComputeScrollbarThumbColor(aFrame, aStyle, aElementState, aDocumentState); } else if (isActive) { buttonColor = sScrollbarButtonActiveColor; } else if (!hasCustomColor && isHovered) { buttonColor = sScrollbarButtonHoverColor; } else { buttonColor = sScrollbarColor; } sRGBColor arrowColor; if (hasCustomColor) { // When scrollbar-color is in use, derive the arrow color from the button // color. nscolor bg = buttonColor.ToABGR(); bool darken = NS_GetLuminosity(bg) >= NS_MAX_LUMINOSITY / 2; if (isActive) { float c = darken ? 0.0f : 1.0f; arrowColor = sRGBColor(c, c, c); } else { uint8_t c = darken ? 0 : 255; arrowColor = sRGBColor::FromABGR(NS_ComposeColors(bg, NS_RGBA(c, c, c, 160))); } } else if (isActive) { arrowColor = sScrollbarArrowColorActive; } else if (isHovered) { arrowColor = sScrollbarArrowColorHover; } else { arrowColor = sScrollbarArrowColor; } return {buttonColor, arrowColor, sScrollbarBorderColor}; } static already_AddRefed GetFocusStrokePath( DrawTarget* aDrawTarget, LayoutDeviceRect& aFocusRect, LayoutDeviceCoord aOffset, const LayoutDeviceCoord aRadius, LayoutDeviceCoord aFocusWidth) { RectCornerRadii radii(aRadius, aRadius, aRadius, aRadius); aFocusRect.Inflate(aOffset); LayoutDeviceRect focusRect(aFocusRect); // 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. focusRect.Deflate(aFocusWidth * 0.5f); return MakePathForRoundedRect(*aDrawTarget, focusRect.ToUnknownRect(), radii); } void nsNativeBasicTheme::PaintRoundedFocusRect(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, DPIRatio aDpiRatio, CSSCoord aRadius, CSSCoord aOffset) { // NOTE(emilio): If the widths or offsets here change, make sure to tweak the // GetWidgetOverflow path for FocusOutline. auto [innerColor, middleColor, outerColor] = ComputeFocusRectColors(); LayoutDeviceRect focusRect(aRect); // The focus rect is painted outside of the border area (aRect), see: // // data:text/html,
Foobar
// // But some controls might provide a negative offset to cover the border, if // necessary. LayoutDeviceCoord offset = aOffset * aDpiRatio; LayoutDeviceCoord strokeWidth = CSSCoord(2.0f) * aDpiRatio; focusRect.Inflate(strokeWidth); LayoutDeviceCoord strokeRadius = aRadius * aDpiRatio; RefPtr roundedRect = GetFocusStrokePath(aDrawTarget, focusRect, offset, strokeRadius, strokeWidth); aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(innerColor)), StrokeOptions(strokeWidth)); offset = CSSCoord(1.0f) * aDpiRatio; strokeRadius += offset; strokeWidth = CSSCoord(1.0f) * aDpiRatio; roundedRect = GetFocusStrokePath(aDrawTarget, focusRect, offset, strokeRadius, strokeWidth); aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(middleColor)), StrokeOptions(strokeWidth)); offset = CSSCoord(2.0f) * aDpiRatio; strokeRadius += offset; strokeWidth = CSSCoord(2.0f) * aDpiRatio; roundedRect = GetFocusStrokePath(aDrawTarget, focusRect, offset, strokeRadius, strokeWidth); aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(outerColor)), StrokeOptions(strokeWidth)); } void nsNativeBasicTheme::PaintRoundedRect(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const sRGBColor& aBackgroundColor, const sRGBColor& aBorderColor, CSSCoord aBorderWidth, RectCornerRadii aDpiAdjustedRadii, DPIRatio aDpiRatio) { const LayoutDeviceCoord borderWidth(aBorderWidth * aDpiRatio); LayoutDeviceRect 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); RefPtr roundedRect = MakePathForRoundedRect( *aDrawTarget, rect.ToUnknownRect(), aDpiAdjustedRadii); aDrawTarget->Fill(roundedRect, ColorPattern(ToDeviceColor(aBackgroundColor))); aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(aBorderColor)), StrokeOptions(borderWidth)); } void nsNativeBasicTheme::PaintRoundedRectWithRadius( DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const sRGBColor& aBackgroundColor, const sRGBColor& aBorderColor, CSSCoord aBorderWidth, CSSCoord aRadius, DPIRatio aDpiRatio) { const LayoutDeviceCoord radius(aRadius * aDpiRatio); RectCornerRadii radii(radius, radius, radius, radius); PaintRoundedRect(aDrawTarget, aRect, aBackgroundColor, aBorderColor, aBorderWidth, radii, aDpiRatio); } void nsNativeBasicTheme::PaintCheckboxControl(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const EventStates& aState, DPIRatio aDpiRatio) { const CSSCoord borderWidth = 2.0f; const CSSCoord radius = 2.0f; auto [backgroundColor, borderColor] = ComputeCheckboxColors(aState, StyleAppearance::Checkbox); PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor, borderWidth, radius, aDpiRatio); if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) { PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, 5.0f, 1.0f); } } // Returns the right scale to cover aRect in the smaller dimension. static float ScaleToWidgetRect(const LayoutDeviceRect& aRect) { return std::min(aRect.width, aRect.height) / kMinimumWidgetSize; } void nsNativeBasicTheme::PaintCheckMark(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const EventStates& aState) { // Points come from the coordinates on a 14X14 unit box centered at 0,0 const float checkPolygonX[] = {-4.5f, -1.5f, -0.5f, 5.0f, 4.75f, 3.5f, -0.5f, -1.5f, -3.5f}; const float checkPolygonY[] = {0.5f, 4.0f, 4.0f, -2.5f, -4.0f, -4.0f, 1.0f, 1.25f, -1.0f}; const int32_t checkNumPoints = sizeof(checkPolygonX) / sizeof(float); const float scale = ScaleToWidgetRect(aRect); auto center = aRect.Center().ToUnknownPoint(); RefPtr builder = aDrawTarget->CreatePathBuilder(); Point p = center + Point(checkPolygonX[0] * scale, checkPolygonY[0] * scale); builder->MoveTo(p); for (int32_t i = 1; i < checkNumPoints; i++) { p = center + Point(checkPolygonX[i] * scale, checkPolygonY[i] * scale); builder->LineTo(p); } RefPtr path = builder->Finish(); sRGBColor fillColor = ComputeCheckmarkColor(aState); aDrawTarget->Fill(path, ColorPattern(ToDeviceColor(fillColor))); } void nsNativeBasicTheme::PaintIndeterminateMark(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const EventStates& aState) { const CSSCoord borderWidth = 2.0f; const float scale = ScaleToWidgetRect(aRect); Rect rect = aRect.ToUnknownRect(); rect.y += (rect.height / 2) - (borderWidth * scale / 2); rect.height = borderWidth * scale; rect.x += (borderWidth * scale) + (borderWidth * scale / 8); rect.width -= ((borderWidth * scale) + (borderWidth * scale / 8)) * 2; sRGBColor fillColor = ComputeCheckmarkColor(aState); aDrawTarget->FillRect(rect, ColorPattern(ToDeviceColor(fillColor))); } void nsNativeBasicTheme::PaintStrokedEllipse(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const sRGBColor& aBackgroundColor, const sRGBColor& aBorderColor, const CSSCoord aBorderWidth, DPIRatio aDpiRatio) { const LayoutDeviceCoord borderWidth(aBorderWidth * aDpiRatio); RefPtr builder = aDrawTarget->CreatePathBuilder(); // Deflate for the same reason as PaintRoundedRectWithRadius. Note that the // size is the diameter, so we just shrink by the border width once. auto size = aRect.Size() - LayoutDeviceSize(borderWidth, borderWidth); AppendEllipseToPath(builder, aRect.Center().ToUnknownPoint(), size.ToUnknownSize()); RefPtr ellipse = builder->Finish(); aDrawTarget->Fill(ellipse, ColorPattern(ToDeviceColor(aBackgroundColor))); aDrawTarget->Stroke(ellipse, ColorPattern(ToDeviceColor(aBorderColor)), StrokeOptions(borderWidth)); } void nsNativeBasicTheme::PaintEllipseShadow(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, float aShadowAlpha, const CSSPoint& aShadowOffset, CSSCoord aShadowBlurStdDev, DPIRatio aDpiRatio) { Float stdDev = aShadowBlurStdDev * aDpiRatio; Point offset = (aShadowOffset * aDpiRatio).ToUnknownPoint(); RefPtr blurFilter = aDrawTarget->CreateFilter(FilterType::GAUSSIAN_BLUR); if (!blurFilter) { return; } blurFilter->SetAttribute(ATT_GAUSSIAN_BLUR_STD_DEVIATION, stdDev); IntSize inflation = gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(stdDev, stdDev)); Rect inflatedRect = aRect.ToUnknownRect(); inflatedRect.Inflate(inflation.width, inflation.height); Rect sourceRectInFilterSpace = inflatedRect - aRect.TopLeft().ToUnknownPoint(); Point destinationPointOfSourceRect = inflatedRect.TopLeft() + offset; IntSize dtSize = RoundedToInt(aRect.Size().ToUnknownSize()); RefPtr ellipseDT = aDrawTarget->CreateSimilarDrawTargetForFilter( dtSize, SurfaceFormat::A8, blurFilter, blurFilter, sourceRectInFilterSpace, destinationPointOfSourceRect); if (!ellipseDT) { return; } RefPtr ellipse = MakePathForEllipse( *ellipseDT, (aRect - aRect.TopLeft()).Center().ToUnknownPoint(), aRect.Size().ToUnknownSize()); ellipseDT->Fill(ellipse, ColorPattern(DeviceColor(0.0f, 0.0f, 0.0f, aShadowAlpha))); RefPtr ellipseSurface = ellipseDT->Snapshot(); blurFilter->SetInput(IN_GAUSSIAN_BLUR_IN, ellipseSurface); aDrawTarget->DrawFilter(blurFilter, sourceRectInFilterSpace, destinationPointOfSourceRect); } void nsNativeBasicTheme::PaintRadioControl(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const EventStates& aState, DPIRatio aDpiRatio) { const CSSCoord borderWidth = 2.0f; auto [backgroundColor, borderColor] = ComputeCheckboxColors(aState, StyleAppearance::Radio); PaintStrokedEllipse(aDrawTarget, aRect, backgroundColor, borderColor, borderWidth, aDpiRatio); if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) { PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, 5.0f, 1.0f); } } void nsNativeBasicTheme::PaintRadioCheckmark(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const EventStates& aState, DPIRatio aDpiRatio) { const CSSCoord borderWidth = 2.0f; const float scale = ScaleToWidgetRect(aRect); auto [backgroundColor, checkColor] = ComputeRadioCheckmarkColors(aState); LayoutDeviceRect rect(aRect); rect.Deflate(borderWidth * scale); PaintStrokedEllipse(aDrawTarget, rect, checkColor, backgroundColor, borderWidth, aDpiRatio); } void nsNativeBasicTheme::PaintTextField(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const EventStates& aState, DPIRatio aDpiRatio) { auto [backgroundColor, borderColor] = ComputeTextfieldColors(aState); const CSSCoord radius = 2.0f; PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor, kTextFieldBorderWidth, radius, aDpiRatio); if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) { PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius, -kTextFieldBorderWidth); } } void nsNativeBasicTheme::PaintListbox(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const EventStates& aState, DPIRatio aDpiRatio) { const CSSCoord radius = 2.0f; auto [backgroundColor, borderColor] = ComputeTextfieldColors(aState); PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor, kMenulistBorderWidth, radius, aDpiRatio); if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) { PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius, -kMenulistBorderWidth); } } void nsNativeBasicTheme::PaintMenulist(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const EventStates& aState, DPIRatio aDpiRatio) { const CSSCoord radius = 4.0f; auto [backgroundColor, borderColor] = ComputeButtonColors(aState); PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor, kMenulistBorderWidth, radius, aDpiRatio); if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) { PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius, -kMenulistBorderWidth); } } void nsNativeBasicTheme::PaintArrow(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const float aArrowPolygonX[], const float aArrowPolygonY[], const int32_t aArrowNumPoints, const sRGBColor aFillColor) { const float scale = ScaleToWidgetRect(aRect); auto center = aRect.Center().ToUnknownPoint(); RefPtr builder = aDrawTarget->CreatePathBuilder(); Point p = center + Point(aArrowPolygonX[0] * scale, aArrowPolygonY[0] * scale); builder->MoveTo(p); for (int32_t i = 1; i < aArrowNumPoints; i++) { p = center + Point(aArrowPolygonX[i] * scale, aArrowPolygonY[i] * scale); builder->LineTo(p); } RefPtr path = builder->Finish(); aDrawTarget->Fill(path, ColorPattern(ToDeviceColor(aFillColor))); } void nsNativeBasicTheme::PaintMenulistArrowButton(nsIFrame* aFrame, DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const EventStates& aState) { const float arrowPolygonX[] = {-3.5f, -0.5f, 0.5f, 3.5f, 3.5f, 3.0f, 0.5f, -0.5f, -3.0f, -3.5f}; const float arrowPolygonY[] = {-0.5f, 2.5f, 2.5f, -0.5f, -2.0f, -2.0f, 1.0f, 1.0f, -2.0f, -2.0f}; const int32_t arrowNumPoints = ArrayLength(arrowPolygonX); sRGBColor arrowColor = ComputeMenulistArrowButtonColor(aState); PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints, arrowColor); } void nsNativeBasicTheme::PaintSpinnerButton(nsIFrame* aFrame, DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const EventStates& aState, StyleAppearance aAppearance, DPIRatio aDpiRatio) { auto [backgroundColor, borderColor] = ComputeButtonColors(aState); RefPtr pathRect = MakePathForRect(*aDrawTarget, aRect.ToUnknownRect()); aDrawTarget->Fill(pathRect, ColorPattern(ToDeviceColor(backgroundColor))); RefPtr builder = aDrawTarget->CreatePathBuilder(); Point p; if (IsFrameRTL(aFrame)) { p = Point(aRect.x + aRect.width - 0.5f, aRect.y); } else { p = Point(aRect.x - 0.5f, aRect.y); } builder->MoveTo(p); p = Point(p.x, p.y + aRect.height); builder->LineTo(p); RefPtr path = builder->Finish(); aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(borderColor)), StrokeOptions(kSpinnerBorderWidth * aDpiRatio)); const float arrowPolygonX[] = {-5.25f, -0.75f, 0.75f, 5.25f, 5.25f, 4.5f, 0.75f, -0.75f, -4.5f, -5.25f}; const float arrowPolygonY[] = {-1.875f, 2.625f, 2.625f, -1.875f, -4.125f, -4.125f, 0.375f, 0.375f, -4.125f, -4.125f}; const int32_t arrowNumPoints = ArrayLength(arrowPolygonX); const float scaleX = ScaleToWidgetRect(aRect); const float scaleY = aAppearance == StyleAppearance::SpinnerDownbutton ? scaleX : -scaleX; builder = aDrawTarget->CreatePathBuilder(); auto center = aRect.Center().ToUnknownPoint(); p = center + Point(arrowPolygonX[0] * scaleX, arrowPolygonY[0] * scaleY); builder->MoveTo(p); for (int32_t i = 1; i < arrowNumPoints; i++) { p = center + Point(arrowPolygonX[i] * scaleX, arrowPolygonY[i] * scaleY); builder->LineTo(p); } path = builder->Finish(); aDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor))); } void nsNativeBasicTheme::PaintRange(nsIFrame* aFrame, DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const EventStates& aState, DPIRatio aDpiRatio, bool aHorizontal) { nsRangeFrame* rangeFrame = do_QueryFrame(aFrame); if (!rangeFrame) { return; } double progress = rangeFrame->GetValueAsFractionOfRange(); auto rect = aRect; LayoutDeviceRect thumbRect(0, 0, kMinimumRangeThumbSize * aDpiRatio, kMinimumRangeThumbSize * aDpiRatio); Rect overflowRect = aRect.ToUnknownRect(); overflowRect.Inflate(CSSCoord(6.0f) * aDpiRatio); // See GetWidgetOverflow Rect progressClipRect(overflowRect); Rect trackClipRect(overflowRect); const LayoutDeviceCoord verticalSize = kRangeHeight * aDpiRatio; if (aHorizontal) { rect.height = verticalSize; rect.y = aRect.y + (aRect.height - rect.height) / 2; thumbRect.y = aRect.y + (aRect.height - thumbRect.height) / 2; if (IsFrameRTL(aFrame)) { thumbRect.x = aRect.x + (aRect.width - thumbRect.width) * (1.0 - progress); float midPoint = thumbRect.Center().X(); trackClipRect.SetBoxX(overflowRect.X(), midPoint); progressClipRect.SetBoxX(midPoint, overflowRect.XMost()); } else { thumbRect.x = aRect.x + (aRect.width - thumbRect.width) * progress; float midPoint = thumbRect.Center().X(); progressClipRect.SetBoxX(overflowRect.X(), midPoint); trackClipRect.SetBoxX(midPoint, overflowRect.XMost()); } } else { rect.width = verticalSize; rect.x = aRect.x + (aRect.width - rect.width) / 2; thumbRect.x = aRect.x + (aRect.width - thumbRect.width) / 2; thumbRect.y = aRect.y + (aRect.height - thumbRect.height) * progress; float midPoint = thumbRect.Center().Y(); trackClipRect.SetBoxY(overflowRect.Y(), midPoint); progressClipRect.SetBoxY(midPoint, overflowRect.YMost()); } const CSSCoord borderWidth = 1.0f; const CSSCoord radius = 2.0f; auto [progressColor, progressBorderColor] = ComputeRangeProgressColors(aState); auto [trackColor, trackBorderColor] = ComputeRangeTrackColors(aState); // Make a path that clips out the range thumb. RefPtr builder = aDrawTarget->CreatePathBuilder(FillRule::FILL_EVEN_ODD); AppendRectToPath(builder, overflowRect); AppendEllipseToPath(builder, thumbRect.Center().ToUnknownPoint(), thumbRect.Size().ToUnknownSize()); RefPtr path = builder->Finish(); // Draw the progress and track pieces with the thumb clipped out, so that // they're not visible behind the thumb even if the thumb is partially // transparent (which is the case in the disabled state). aDrawTarget->PushClip(path); { aDrawTarget->PushClipRect(progressClipRect); PaintRoundedRectWithRadius(aDrawTarget, rect, progressColor, progressBorderColor, borderWidth, radius, aDpiRatio); aDrawTarget->PopClip(); aDrawTarget->PushClipRect(trackClipRect); PaintRoundedRectWithRadius(aDrawTarget, rect, trackColor, trackBorderColor, borderWidth, radius, aDpiRatio); aDrawTarget->PopClip(); if (!aState.HasState(NS_EVENT_STATE_DISABLED)) { // Thumb shadow PaintEllipseShadow(aDrawTarget, thumbRect, 0.3f, CSSPoint(0.0f, 2.0f), 2.0f, aDpiRatio); } } aDrawTarget->PopClip(); // Draw the thumb on top. const CSSCoord thumbBorderWidth = 2.0f; auto [thumbColor, thumbBorderColor] = ComputeRangeThumbColors(aState); PaintStrokedEllipse(aDrawTarget, thumbRect, thumbColor, thumbBorderColor, thumbBorderWidth, aDpiRatio); if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) { PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius, 1.0f); } } void nsNativeBasicTheme::PaintProgressBar(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const EventStates& aState, DPIRatio aDpiRatio) { const CSSCoord borderWidth = 1.0f; const CSSCoord radius = 2.0f; LayoutDeviceRect rect(aRect); const LayoutDeviceCoord height = kProgressbarHeight * aDpiRatio; rect.y += (rect.height - height) / 2; rect.height = height; auto [trackColor, trackBorderColor] = ComputeProgressTrackColors(); PaintRoundedRectWithRadius(aDrawTarget, rect, trackColor, trackBorderColor, borderWidth, radius, aDpiRatio); } void nsNativeBasicTheme::PaintProgresschunk(nsIFrame* aFrame, DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const EventStates& aState, DPIRatio aDpiRatio) { // TODO: vertical? // TODO: Address artifacts when position is between 0 and radius + border. // TODO: Handle indeterminate case. nsProgressFrame* progressFrame = do_QueryFrame(aFrame->GetParent()); if (!progressFrame) { return; } const CSSCoord borderWidth = 1.0f; const LayoutDeviceCoord radius = CSSCoord(2.0f) * aDpiRatio; LayoutDeviceCoord progressEndRadius = 0.0f; LayoutDeviceRect rect(aRect); const LayoutDeviceCoord height = kProgressbarHeight * aDpiRatio; rect.y += (rect.height - height) / 2; rect.height = height; double position = GetProgressValue(aFrame) / GetProgressMaxValue(aFrame); if (rect.width - (rect.width * position) < (borderWidth * aDpiRatio + radius)) { // Round corners when the progress chunk approaches the maximum value to // avoid artifacts. progressEndRadius = radius; } RectCornerRadii radii; if (IsFrameRTL(aFrame)) { radii = RectCornerRadii(progressEndRadius, radius, radius, progressEndRadius); } else { radii = RectCornerRadii(radius, progressEndRadius, progressEndRadius, radius); } auto [progressColor, progressBorderColor] = ComputeProgressColors(); PaintRoundedRect(aDrawTarget, rect, progressColor, progressBorderColor, borderWidth, radii, aDpiRatio); } void nsNativeBasicTheme::PaintMeter(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const EventStates& aState, DPIRatio aDpiRatio) { const CSSCoord borderWidth = 1.0f; const CSSCoord radius = 5.0f; LayoutDeviceRect rect(aRect); const LayoutDeviceCoord height = kMeterHeight * aDpiRatio; rect.y += (rect.height - height) / 2; rect.height = height; auto [backgroundColor, borderColor] = ComputeMeterTrackColors(); PaintRoundedRectWithRadius(aDrawTarget, rect, backgroundColor, borderColor, borderWidth, radius, aDpiRatio); } void nsNativeBasicTheme::PaintMeterchunk(nsIFrame* aFrame, DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, DPIRatio aDpiRatio) { // TODO: Address artifacts when position is between 0 and (radius + border). nsMeterFrame* meterFrame = do_QueryFrame(aFrame->GetParent()); if (!meterFrame) { return; } const CSSCoord borderWidth = 1.0f; const LayoutDeviceCoord radius = CSSCoord(5.0f) * aDpiRatio; LayoutDeviceCoord progressEndRadius = 0.0f; LayoutDeviceRect rect(aRect); const LayoutDeviceCoord height = kMeterHeight * aDpiRatio; rect.y += (rect.height - height) / 2; rect.height = height; auto* meter = static_cast(meterFrame->GetContent()); double value = meter->Value(); double max = meter->Max(); double position = value / max; if (rect.width - (rect.width * position) < (borderWidth * aDpiRatio + radius)) { // Round corners when the progress chunk approaches the maximum value to // avoid artifacts. progressEndRadius = radius; } RectCornerRadii radii; if (IsFrameRTL(aFrame)) { radii = RectCornerRadii(progressEndRadius, radius, radius, progressEndRadius); } else { radii = RectCornerRadii(radius, progressEndRadius, progressEndRadius, radius); } auto [chunkColor, borderColor] = ComputeMeterchunkColors(meter->State()); PaintRoundedRect(aDrawTarget, rect, chunkColor, borderColor, borderWidth, radii, aDpiRatio); } void nsNativeBasicTheme::PaintButton(nsIFrame* aFrame, DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, const EventStates& aState, DPIRatio aDpiRatio) { const CSSCoord radius = 4.0f; auto [backgroundColor, borderColor] = ComputeButtonColors(aState, aFrame); PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor, kButtonBorderWidth, radius, aDpiRatio); if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) { PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius, -kButtonBorderWidth); } } void nsNativeBasicTheme::PaintScrollbarThumb(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, bool aHorizontal, nsIFrame* aFrame, const ComputedStyle& aStyle, const EventStates& aElementState, const EventStates& aDocumentState, DPIRatio aDpiRatio) { sRGBColor thumbColor = ComputeScrollbarThumbColor(aFrame, aStyle, aElementState, aDocumentState); aDrawTarget->FillRect(aRect.ToUnknownRect(), ColorPattern(ToDeviceColor(thumbColor))); } void nsNativeBasicTheme::PaintScrollbarTrack(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, bool aHorizontal, nsIFrame* aFrame, const ComputedStyle& aStyle, const EventStates& aDocumentState, DPIRatio aDpiRatio, bool aIsRoot) { // Draw nothing by default. Subclasses can override this. } void nsNativeBasicTheme::PaintScrollbar(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, bool aHorizontal, nsIFrame* aFrame, const ComputedStyle& aStyle, const EventStates& aDocumentState, DPIRatio aDpiRatio, bool aIsRoot) { auto [scrollbarColor, borderColor] = ComputeScrollbarColors(aFrame, aStyle, aDocumentState, aIsRoot); aDrawTarget->FillRect(aRect.ToUnknownRect(), ColorPattern(ToDeviceColor(scrollbarColor))); // FIXME(heycam): We should probably derive the border color when custom // scrollbar colors are in use too. But for now, just skip painting it, // to avoid ugliness. if (aStyle.StyleUI()->mScrollbarColor.IsAuto()) { RefPtr builder = aDrawTarget->CreatePathBuilder(); LayoutDeviceRect strokeRect(aRect); strokeRect.Deflate(CSSCoord(0.5f) * aDpiRatio); builder->MoveTo(strokeRect.TopLeft().ToUnknownPoint()); builder->LineTo( (aHorizontal ? strokeRect.TopRight() : strokeRect.BottomLeft()) .ToUnknownPoint()); RefPtr path = builder->Finish(); aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(borderColor)), StrokeOptions(CSSCoord(1.0f) * aDpiRatio)); } } void nsNativeBasicTheme::PaintScrollCorner(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, nsIFrame* aFrame, const ComputedStyle& aStyle, const EventStates& aDocumentState, DPIRatio aDpiRatio, bool aIsRoot) { auto [scrollbarColor, borderColor] = ComputeScrollbarColors(aFrame, aStyle, aDocumentState, aIsRoot); Unused << borderColor; aDrawTarget->FillRect(aRect.ToUnknownRect(), ColorPattern(ToDeviceColor(scrollbarColor))); } void nsNativeBasicTheme::PaintScrollbarButton( DrawTarget* aDrawTarget, StyleAppearance aAppearance, const LayoutDeviceRect& aRect, nsIFrame* aFrame, const ComputedStyle& aStyle, const EventStates& aElementState, const EventStates& aDocumentState, DPIRatio aDpiRatio) { bool hasCustomColor = aStyle.StyleUI()->mScrollbarColor.IsColors(); auto [buttonColor, arrowColor, borderColor] = ComputeScrollbarButtonColors( aFrame, aAppearance, aStyle, aElementState, aDocumentState); aDrawTarget->FillRect(aRect.ToUnknownRect(), ColorPattern(ToDeviceColor(buttonColor))); // Start with Up arrow. float arrowPolygonX[] = {-3.0f, 0.0f, 3.0f, 3.0f, 0.0f, -3.0f}; float arrowPolygonY[] = {0.4f, -3.1f, 0.4f, 2.7f, -0.8f, 2.7f}; const int32_t arrowNumPoints = ArrayLength(arrowPolygonX); 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, arrowColor); // FIXME(heycam): We should probably derive the border color when custom // scrollbar colors are in use too. But for now, just skip painting it, // to avoid ugliness. if (!hasCustomColor) { RefPtr 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 = builder->Finish(); aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(borderColor)), StrokeOptions(CSSCoord(1.0f) * aDpiRatio)); } } // Checks whether the frame is for a root or , which // influences some platforms' scrollbar rendering. bool nsNativeBasicTheme::IsRootScrollbar(nsIFrame* aFrame) { return CheckBooleanAttr(aFrame, nsGkAtoms::root_) && aFrame->PresContext()->IsRootContentDocument() && aFrame->GetContent() && aFrame->GetContent()->IsInNamespace(kNameSpaceID_XUL); } NS_IMETHODIMP nsNativeBasicTheme::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect, const nsRect& /* aDirtyRect */) { DrawTarget* dt = aContext->GetDrawTarget(); // FIXME(emilio): Why does this use AppUnitsPerDevPixel() but GetDPIRatio() // uses AppUnitsPerDevPixelAtUnitFullZoom()? const nscoord twipsPerPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); EventStates eventState = GetContentState(aFrame, aAppearance); EventStates docState = aFrame->GetContent()->OwnerDoc()->GetDocumentState(); auto devPxRect = LayoutDeviceRect::FromUnknownRect( NSRectToSnappedRect(aRect, twipsPerPixel, *dt)); 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); } } // Hack to avoid skia fuzziness: Add a dummy clip if the widget doesn't // overflow devPxRect. Maybe maybeClipRect; if (aAppearance != StyleAppearance::FocusOutline && aAppearance != StyleAppearance::Range && !eventState.HasState(NS_EVENT_STATE_FOCUSRING)) { maybeClipRect.emplace(*dt, devPxRect); } DPIRatio dpiRatio = GetDPIRatio(aFrame); switch (aAppearance) { case StyleAppearance::Radio: { auto rect = FixAspectRatio(devPxRect); PaintRadioControl(dt, rect, eventState, dpiRatio); if (IsSelected(aFrame)) { PaintRadioCheckmark(dt, rect, eventState, dpiRatio); } break; } case StyleAppearance::Checkbox: { auto rect = FixAspectRatio(devPxRect); PaintCheckboxControl(dt, rect, eventState, dpiRatio); if (GetIndeterminate(aFrame)) { PaintIndeterminateMark(dt, rect, eventState); } else if (IsChecked(aFrame)) { PaintCheckMark(dt, rect, eventState); } break; } case StyleAppearance::Textarea: case StyleAppearance::Textfield: case StyleAppearance::NumberInput: PaintTextField(dt, devPxRect, eventState, dpiRatio); break; case StyleAppearance::Listbox: PaintListbox(dt, devPxRect, eventState, dpiRatio); break; case StyleAppearance::MenulistButton: case StyleAppearance::Menulist: PaintMenulist(dt, devPxRect, eventState, dpiRatio); break; case StyleAppearance::MozMenulistArrowButton: PaintMenulistArrowButton(aFrame, dt, devPxRect, eventState); break; case StyleAppearance::SpinnerUpbutton: case StyleAppearance::SpinnerDownbutton: PaintSpinnerButton(aFrame, dt, devPxRect, eventState, aAppearance, dpiRatio); break; case StyleAppearance::Range: PaintRange(aFrame, dt, devPxRect, eventState, dpiRatio, IsRangeHorizontal(aFrame)); break; case StyleAppearance::RangeThumb: // Painted as part of StyleAppearance::Range. break; case StyleAppearance::ProgressBar: PaintProgressBar(dt, devPxRect, eventState, dpiRatio); break; case StyleAppearance::Progresschunk: PaintProgresschunk(aFrame, dt, devPxRect, eventState, dpiRatio); break; case StyleAppearance::Meter: PaintMeter(dt, devPxRect, eventState, dpiRatio); break; case StyleAppearance::Meterchunk: PaintMeterchunk(aFrame, dt, devPxRect, dpiRatio); break; case StyleAppearance::ScrollbarthumbHorizontal: case StyleAppearance::ScrollbarthumbVertical: { bool isHorizontal = aAppearance == StyleAppearance::ScrollbarthumbHorizontal; PaintScrollbarThumb(dt, devPxRect, isHorizontal, aFrame, *nsLayoutUtils::StyleForScrollbar(aFrame), eventState, docState, dpiRatio); break; } case StyleAppearance::ScrollbartrackHorizontal: case StyleAppearance::ScrollbartrackVertical: { bool isHorizontal = aAppearance == StyleAppearance::ScrollbartrackHorizontal; PaintScrollbarTrack(dt, devPxRect, isHorizontal, aFrame, *nsLayoutUtils::StyleForScrollbar(aFrame), docState, dpiRatio, IsRootScrollbar(aFrame)); break; } case StyleAppearance::ScrollbarHorizontal: case StyleAppearance::ScrollbarVertical: { bool isHorizontal = aAppearance == StyleAppearance::ScrollbarHorizontal; PaintScrollbar(dt, devPxRect, isHorizontal, aFrame, *nsLayoutUtils::StyleForScrollbar(aFrame), docState, dpiRatio, IsRootScrollbar(aFrame)); break; } case StyleAppearance::Scrollcorner: PaintScrollCorner(dt, devPxRect, aFrame, *nsLayoutUtils::StyleForScrollbar(aFrame), docState, dpiRatio, IsRootScrollbar(aFrame)); break; case StyleAppearance::ScrollbarbuttonUp: case StyleAppearance::ScrollbarbuttonDown: case StyleAppearance::ScrollbarbuttonLeft: case StyleAppearance::ScrollbarbuttonRight: // For scrollbar-width:thin, we don't display the buttons. if (!IsScrollbarWidthThin(aFrame)) { PaintScrollbarButton(dt, aAppearance, devPxRect, aFrame, *nsLayoutUtils::StyleForScrollbar(aFrame), eventState, docState, dpiRatio); } break; case StyleAppearance::Button: PaintButton(aFrame, dt, devPxRect, eventState, dpiRatio); break; case StyleAppearance::FocusOutline: // TODO(emilio): Consider supporting outline-radius / outline-offset? PaintRoundedFocusRect(dt, devPxRect, dpiRatio, 0.0f, 0.0f); break; default: // Various appearance values are used for XUL elements. Normally these // will not be available in content documents (and thus in the content // processes where the native basic theme can be used), but tests are run // with the remote XUL pref enabled and so we can get in here. So we // just return an error rather than assert. return NS_ERROR_NOT_IMPLEMENTED; } return NS_OK; } /*bool nsNativeBasicTheme::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 nsNativeBasicTheme::GetWidgetBorder( nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) { DPIRatio dpiRatio = GetDPIRatio(aFrame); switch (aAppearance) { case StyleAppearance::Textfield: case StyleAppearance::Textarea: case StyleAppearance::NumberInput: { // FIXME: Do we want this margin not to be int-based? The native windows // theme rounds (see ScaleForDPI)... LayoutDeviceIntCoord w = (kTextFieldBorderWidth * dpiRatio).Rounded(); return LayoutDeviceIntMargin(w, w, w, w); } case StyleAppearance::Listbox: case StyleAppearance::Menulist: case StyleAppearance::MenulistButton: { LayoutDeviceIntCoord w = (kMenulistBorderWidth * dpiRatio).Rounded(); return LayoutDeviceIntMargin(w, w, w, w); } case StyleAppearance::Button: { LayoutDeviceIntCoord w = (kButtonBorderWidth * dpiRatio).Rounded(); return LayoutDeviceIntMargin(w, w, w, w); } case StyleAppearance::Checkbox: case StyleAppearance::Radio: { LayoutDeviceIntCoord w = (kCheckboxRadioBorderWidth * dpiRatio).Rounded(); return LayoutDeviceIntMargin(w, w, w, w); } default: return LayoutDeviceIntMargin(); } } bool nsNativeBasicTheme::GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance, LayoutDeviceIntMargin* aResult) { 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; default: break; } return false; } bool nsNativeBasicTheme::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance, nsRect* aOverflowRect) { nsIntMargin overflow; switch (aAppearance) { case StyleAppearance::FocusOutline: // 2px * each of the segments + 1 px for the separation between them. overflow.SizeTo(5, 5, 5, 5); break; case StyleAppearance::Radio: case StyleAppearance::Checkbox: case StyleAppearance::Range: overflow.SizeTo(6, 6, 6, 6); break; case StyleAppearance::Textarea: case StyleAppearance::Textfield: case StyleAppearance::NumberInput: case StyleAppearance::Listbox: case StyleAppearance::MenulistButton: case StyleAppearance::Menulist: case StyleAppearance::Button: // 2px for each segment, plus 1px separation, but we paint 1px inside the // border area so 4px overflow. overflow.SizeTo(4, 4, 4, 4); break; default: return false; } // TODO: This should convert from device pixels to app units, not from CSS // pixels. And it should take the dpi ratio into account. // Using CSS pixels can cause the overflow to be too small if the page is // zoomed out. aOverflowRect->Inflate(nsMargin(CSSPixel::ToAppUnits(overflow.top), CSSPixel::ToAppUnits(overflow.right), CSSPixel::ToAppUnits(overflow.bottom), CSSPixel::ToAppUnits(overflow.left))); return true; } NS_IMETHODIMP nsNativeBasicTheme::GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame, StyleAppearance aAppearance, LayoutDeviceIntSize* aResult, bool* aIsOverridable) { DPIRatio dpiRatio = GetDPIRatio(aFrame); aResult->width = aResult->height = (kMinimumWidgetSize * dpiRatio).Rounded(); switch (aAppearance) { case StyleAppearance::Button: if (IsColorPickerButton(aFrame)) { aResult->height = (kMinimumColorPickerHeight * dpiRatio).Rounded(); } break; case StyleAppearance::RangeThumb: aResult->SizeTo((kMinimumRangeThumbSize * dpiRatio).Rounded(), (kMinimumRangeThumbSize * dpiRatio).Rounded()); break; case StyleAppearance::MozMenulistArrowButton: aResult->width = (kMinimumDropdownArrowButtonWidth * dpiRatio).Rounded(); break; case StyleAppearance::SpinnerUpbutton: case StyleAppearance::SpinnerDownbutton: aResult->width = (kMinimumSpinnerButtonWidth * dpiRatio).Rounded(); aResult->height = (kMinimumSpinnerButtonHeight * dpiRatio).Rounded(); break; case StyleAppearance::ScrollbarbuttonUp: case StyleAppearance::ScrollbarbuttonDown: case StyleAppearance::ScrollbarbuttonLeft: case StyleAppearance::ScrollbarbuttonRight: // For scrollbar-width:thin, we don't display the buttons. if (IsScrollbarWidthThin(aFrame)) { aResult->SizeTo(0, 0); break; } [[fallthrough]]; case StyleAppearance::ScrollbarthumbVertical: case StyleAppearance::ScrollbarthumbHorizontal: case StyleAppearance::ScrollbarVertical: case StyleAppearance::ScrollbarHorizontal: case StyleAppearance::ScrollbartrackHorizontal: case StyleAppearance::ScrollbartrackVertical: case StyleAppearance::Scrollcorner: { if (IsScrollbarWidthThin(aFrame)) { aResult->SizeTo((kMinimumThinScrollbarSize * dpiRatio).Rounded(), (kMinimumThinScrollbarSize * dpiRatio).Rounded()); } else { aResult->SizeTo((kMinimumScrollbarSize * dpiRatio).Rounded(), (kMinimumScrollbarSize * dpiRatio).Rounded()); } break; } default: break; } *aIsOverridable = true; return NS_OK; } nsITheme::Transparency nsNativeBasicTheme::GetWidgetTransparency( nsIFrame* aFrame, StyleAppearance aAppearance) { return eUnknownTransparency; } NS_IMETHODIMP nsNativeBasicTheme::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 nsNativeBasicTheme::ThemeChanged() { return NS_OK; } bool nsNativeBasicTheme::WidgetAppearanceDependsOnWindowFocus( StyleAppearance aAppearance) { return IsWidgetScrollbarPart(aAppearance); } nsITheme::ThemeGeometryType nsNativeBasicTheme::ThemeGeometryTypeForWidget( nsIFrame* aFrame, StyleAppearance aAppearance) { return eThemeGeometryTypeUnknown; } bool nsNativeBasicTheme::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame, StyleAppearance aAppearance) { switch (aAppearance) { case StyleAppearance::Radio: case StyleAppearance::Checkbox: case StyleAppearance::FocusOutline: case StyleAppearance::Textarea: case StyleAppearance::Textfield: case StyleAppearance::Range: case StyleAppearance::RangeThumb: case StyleAppearance::ProgressBar: case StyleAppearance::Progresschunk: case StyleAppearance::Meter: case StyleAppearance::Meterchunk: case StyleAppearance::ScrollbarbuttonUp: case StyleAppearance::ScrollbarbuttonDown: case StyleAppearance::ScrollbarbuttonLeft: case StyleAppearance::ScrollbarbuttonRight: case StyleAppearance::ScrollbarthumbHorizontal: case StyleAppearance::ScrollbarthumbVertical: case StyleAppearance::ScrollbartrackHorizontal: case StyleAppearance::ScrollbartrackVertical: case StyleAppearance::ScrollbarHorizontal: case StyleAppearance::ScrollbarVertical: case StyleAppearance::Scrollcorner: case StyleAppearance::Button: case StyleAppearance::Listbox: case StyleAppearance::Menulist: case StyleAppearance::Menuitem: case StyleAppearance::Menuitemtext: case StyleAppearance::MenulistText: case StyleAppearance::MenulistButton: case StyleAppearance::NumberInput: case StyleAppearance::MozMenulistArrowButton: case StyleAppearance::SpinnerUpbutton: case StyleAppearance::SpinnerDownbutton: return !IsWidgetStyled(aPresContext, aFrame, aAppearance); default: return false; } } bool nsNativeBasicTheme::WidgetIsContainer(StyleAppearance aAppearance) { switch (aAppearance) { case StyleAppearance::MozMenulistArrowButton: case StyleAppearance::Radio: case StyleAppearance::Checkbox: return false; default: return true; } } bool nsNativeBasicTheme::ThemeDrawsFocusForWidget(StyleAppearance aAppearance) { return true; } bool nsNativeBasicTheme::ThemeNeedsComboboxDropmarker() { return true; }