Bug 1692971 [wpt PR 27638] - WebKit export of https://bugs.webkit.org/show_bug.cgi...
[gecko.git] / widget / nsNativeBasicTheme.cpp
blob1940a4995cadc88c581dea4d174d391a59da4b4d
1 /* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsNativeBasicTheme.h"
8 #include "gfxBlur.h"
9 #include "mozilla/MathAlgorithms.h"
10 #include "mozilla/dom/Document.h"
11 #include "mozilla/gfx/Rect.h"
12 #include "mozilla/gfx/Types.h"
13 #include "mozilla/gfx/Filters.h"
14 #include "mozilla/RelativeLuminanceUtils.h"
15 #include "mozilla/StaticPrefs_widget.h"
16 #include "nsCSSColorUtils.h"
17 #include "nsCSSRendering.h"
18 #include "nsLayoutUtils.h"
19 #include "PathHelpers.h"
21 #include "nsDeviceContext.h"
23 #include "nsColorControlFrame.h"
24 #include "nsDateTimeControlFrame.h"
25 #include "nsMeterFrame.h"
26 #include "nsProgressFrame.h"
27 #include "nsRangeFrame.h"
28 #include "mozilla/dom/HTMLMeterElement.h"
29 #include "mozilla/dom/HTMLProgressElement.h"
31 using namespace mozilla;
32 using namespace mozilla::widget;
33 using namespace mozilla::gfx;
35 NS_IMPL_ISUPPORTS_INHERITED(nsNativeBasicTheme, nsNativeTheme, nsITheme)
37 namespace {
39 // This pushes and pops a clip rect to the draw target.
41 // This is done to reduce fuzz in places where we may have antialiasing,
42 // because skia is not clip-invariant: given different clips, it does not
43 // guarantee the same result, even if the painted content doesn't intersect
44 // the clips.
46 // This is a bit sad, overall, but...
47 struct MOZ_RAII AutoClipRect {
48 AutoClipRect(DrawTarget& aDt, const LayoutDeviceRect& aRect) : mDt(aDt) {
49 mDt.PushClipRect(aRect.ToUnknownRect());
52 ~AutoClipRect() { mDt.PopClip(); }
54 private:
55 DrawTarget& mDt;
58 static LayoutDeviceIntCoord SnapBorderWidth(
59 CSSCoord aCssWidth, nsNativeBasicTheme::DPIRatio aDpiRatio) {
60 if (aCssWidth == 0.0f) {
61 return 0;
63 return std::max(LayoutDeviceIntCoord(1), (aCssWidth * aDpiRatio).Truncated());
66 [[nodiscard]] static float ScaleLuminanceBy(float aLuminance, float aFactor) {
67 return aLuminance >= 0.18f ? aLuminance * aFactor : aLuminance / aFactor;
70 static nscolor ThemedAccentColor(bool aBackground) {
71 MOZ_ASSERT(StaticPrefs::widget_non_native_use_theme_accent());
72 nscolor color = LookAndFeel::GetColor(
73 aBackground ? LookAndFeel::ColorID::MozAccentColor
74 : LookAndFeel::ColorID::MozAccentColorForeground);
75 if (NS_GET_A(color) != 0xff) {
76 // Blend with white, ensuring the color is opaque to avoid surprises if we
77 // overdraw.
78 color = NS_ComposeColors(NS_RGB(0xff, 0xff, 0xff), color);
80 return color;
83 } // namespace
85 sRGBColor nsNativeBasicTheme::sAccentColor = sRGBColor::OpaqueWhite();
86 sRGBColor nsNativeBasicTheme::sAccentColorForeground = sRGBColor::OpaqueWhite();
87 sRGBColor nsNativeBasicTheme::sAccentColorLight = sRGBColor::OpaqueWhite();
88 sRGBColor nsNativeBasicTheme::sAccentColorDark = sRGBColor::OpaqueWhite();
89 sRGBColor nsNativeBasicTheme::sAccentColorDarker = sRGBColor::OpaqueWhite();
91 void nsNativeBasicTheme::Init() {
92 Preferences::RegisterCallbackAndCall(PrefChangedCallback,
93 "widget.non-native.use-theme-accent");
96 void nsNativeBasicTheme::Shutdown() {
97 Preferences::UnregisterCallback(PrefChangedCallback,
98 "widget.non-native.use-theme-accent");
101 void nsNativeBasicTheme::LookAndFeelChanged() { RecomputeAccentColors(); }
103 void nsNativeBasicTheme::RecomputeAccentColors() {
104 MOZ_RELEASE_ASSERT(NS_IsMainThread());
106 if (!StaticPrefs::widget_non_native_use_theme_accent()) {
107 sAccentColorForeground = sColorWhite;
108 sAccentColor =
109 sRGBColor::UnusualFromARGB(0xff0060df); // Luminance: 13.69346%
110 sAccentColorLight =
111 sRGBColor::UnusualFromARGB(0x4d008deb); // Luminance: 25.04791%
112 sAccentColorDark =
113 sRGBColor::UnusualFromARGB(0xff0250bb); // Luminance: 9.33808%
114 sAccentColorDarker =
115 sRGBColor::UnusualFromARGB(0xff054096); // Luminance: 5.90106%
116 return;
119 sAccentColorForeground = sRGBColor::FromABGR(ThemedAccentColor(false));
120 const nscolor accent = ThemedAccentColor(true);
121 const float luminance = RelativeLuminanceUtils::Compute(accent);
123 constexpr float kLightLuminanceScale = 25.048f / 13.693f;
124 constexpr float kDarkLuminanceScale = 9.338f / 13.693f;
125 constexpr float kDarkerLuminanceScale = 5.901f / 13.693f;
127 const float lightLuminanceAdjust =
128 ScaleLuminanceBy(luminance, kLightLuminanceScale);
129 const float darkLuminanceAdjust =
130 ScaleLuminanceBy(luminance, kDarkLuminanceScale);
131 const float darkerLuminanceAdjust =
132 ScaleLuminanceBy(luminance, kDarkerLuminanceScale);
134 sAccentColor = sRGBColor::FromABGR(accent);
137 nscolor lightColor =
138 RelativeLuminanceUtils::Adjust(accent, lightLuminanceAdjust);
139 lightColor = NS_RGBA(NS_GET_R(lightColor), NS_GET_G(lightColor),
140 NS_GET_B(lightColor), 0x4d);
141 sAccentColorLight = sRGBColor::FromABGR(lightColor);
144 sAccentColorDark = sRGBColor::FromABGR(
145 RelativeLuminanceUtils::Adjust(accent, darkLuminanceAdjust));
146 sAccentColorDarker = sRGBColor::FromABGR(
147 RelativeLuminanceUtils::Adjust(accent, darkerLuminanceAdjust));
150 static bool IsScrollbarWidthThin(nsIFrame* aFrame) {
151 ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
152 auto scrollbarWidth = style->StyleUIReset()->mScrollbarWidth;
153 return scrollbarWidth == StyleScrollbarWidth::Thin;
156 /* static */
157 auto nsNativeBasicTheme::GetDPIRatioForScrollbarPart(nsPresContext* aPc)
158 -> DPIRatio {
159 return DPIRatio(float(AppUnitsPerCSSPixel()) /
160 aPc->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom());
163 /* static */
164 auto nsNativeBasicTheme::GetDPIRatio(nsPresContext* aPc,
165 StyleAppearance aAppearance) -> DPIRatio {
166 // Widgets react to zoom, except scrollbars.
167 if (IsWidgetScrollbarPart(aAppearance)) {
168 return GetDPIRatioForScrollbarPart(aPc);
170 return DPIRatio(float(AppUnitsPerCSSPixel()) / aPc->AppUnitsPerDevPixel());
173 /* static */
174 auto nsNativeBasicTheme::GetDPIRatio(nsIFrame* aFrame,
175 StyleAppearance aAppearance) -> DPIRatio {
176 return GetDPIRatio(aFrame->PresContext(), aAppearance);
179 /* static */
180 bool nsNativeBasicTheme::IsDateTimeResetButton(nsIFrame* aFrame) {
181 if (!aFrame) {
182 return false;
185 nsIFrame* parent = aFrame->GetParent();
186 if (parent && (parent = parent->GetParent()) &&
187 (parent = parent->GetParent())) {
188 nsDateTimeControlFrame* dateTimeFrame = do_QueryFrame(parent);
189 if (dateTimeFrame) {
190 return true;
193 return false;
196 /* static */
197 bool nsNativeBasicTheme::IsColorPickerButton(nsIFrame* aFrame) {
198 nsColorControlFrame* colorPickerButton = do_QueryFrame(aFrame);
199 return colorPickerButton;
202 /* static */
203 LayoutDeviceRect nsNativeBasicTheme::FixAspectRatio(
204 const LayoutDeviceRect& aRect) {
205 // Checkbox and radio need to preserve aspect-ratio for compat.
206 LayoutDeviceRect rect(aRect);
207 if (rect.width == rect.height) {
208 return rect;
211 if (rect.width > rect.height) {
212 auto diff = rect.width - rect.height;
213 rect.width = rect.height;
214 rect.x += diff / 2;
215 } else {
216 auto diff = rect.height - rect.width;
217 rect.height = rect.width;
218 rect.y += diff / 2;
221 return rect;
224 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeCheckboxColors(
225 const EventStates& aState, StyleAppearance aAppearance) {
226 MOZ_ASSERT(aAppearance == StyleAppearance::Checkbox ||
227 aAppearance == StyleAppearance::Radio);
229 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
230 bool isPressed = !isDisabled && aState.HasAllStates(NS_EVENT_STATE_HOVER |
231 NS_EVENT_STATE_ACTIVE);
232 bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
233 bool isChecked = aState.HasState(NS_EVENT_STATE_CHECKED);
234 bool isIndeterminate = aAppearance == StyleAppearance::Checkbox &&
235 aState.HasState(NS_EVENT_STATE_INDETERMINATE);
237 sRGBColor backgroundColor = sColorWhite;
238 sRGBColor borderColor = sColorGrey40;
239 if (isDisabled) {
240 if (isChecked || isIndeterminate) {
241 backgroundColor = borderColor = sColorGrey40Alpha50;
242 } else {
243 backgroundColor = sColorWhiteAlpha50;
244 borderColor = sColorGrey40Alpha50;
246 } else {
247 if (isChecked || isIndeterminate) {
248 const auto& color = isPressed ? sAccentColorDarker
249 : isHovered ? sAccentColorDark
250 : sAccentColor;
251 backgroundColor = borderColor = color;
252 } else if (isPressed) {
253 backgroundColor = sColorGrey20;
254 borderColor = sColorGrey60;
255 } else if (isHovered) {
256 backgroundColor = sColorWhite;
257 borderColor = sColorGrey50;
258 } else {
259 backgroundColor = sColorWhite;
260 borderColor = sColorGrey40;
264 return std::make_pair(backgroundColor, borderColor);
267 sRGBColor nsNativeBasicTheme::ComputeCheckmarkColor(const EventStates& aState) {
268 if (aState.HasState(NS_EVENT_STATE_DISABLED)) {
269 return sColorWhiteAlpha50;
271 return sAccentColorForeground;
274 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeRadioCheckmarkColors(
275 const EventStates& aState) {
276 auto [unusedColor, checkColor] =
277 ComputeCheckboxColors(aState, StyleAppearance::Radio);
278 Unused << unusedColor;
279 return std::make_pair(ComputeCheckmarkColor(aState), checkColor);
282 sRGBColor nsNativeBasicTheme::ComputeBorderColor(const EventStates& aState) {
283 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
284 bool isActive =
285 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
286 bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
287 bool isFocused = aState.HasState(NS_EVENT_STATE_FOCUSRING);
288 if (isDisabled) {
289 return sColorGrey40Alpha50;
291 if (isFocused) {
292 return sAccentColor;
294 if (isActive) {
295 return sColorGrey60;
297 if (isHovered) {
298 return sColorGrey50;
300 return sColorGrey40;
303 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeButtonColors(
304 const EventStates& aState, nsIFrame* aFrame) {
305 bool isActive =
306 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
307 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
308 bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
310 const sRGBColor& backgroundColor = [&] {
311 if (isDisabled) {
312 return sColorGrey10Alpha50;
314 if (IsDateTimeResetButton(aFrame)) {
315 return sColorWhite;
317 if (isActive) {
318 return sColorGrey30;
320 if (isHovered) {
321 return sColorGrey20;
323 return sColorGrey10;
324 }();
326 const sRGBColor borderColor = ComputeBorderColor(aState);
327 return std::make_pair(backgroundColor, borderColor);
330 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeTextfieldColors(
331 const EventStates& aState) {
332 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
333 const sRGBColor& backgroundColor =
334 isDisabled ? sColorWhiteAlpha50 : sColorWhite;
335 const sRGBColor borderColor = ComputeBorderColor(aState);
337 return std::make_pair(backgroundColor, borderColor);
340 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeRangeProgressColors(
341 const EventStates& aState) {
342 bool isActive =
343 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
344 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
345 bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
347 if (isDisabled) {
348 return std::make_pair(sColorGrey40Alpha50, sColorGrey40Alpha50);
350 if (isActive || isHovered) {
351 return std::make_pair(sAccentColorDark, sAccentColorDarker);
353 return std::make_pair(sAccentColor, sAccentColorDark);
356 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeRangeTrackColors(
357 const EventStates& aState) {
358 bool isActive =
359 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
360 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
361 bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
363 if (isDisabled) {
364 return std::make_pair(sColorGrey10Alpha50, sColorGrey40Alpha50);
366 if (isActive || isHovered) {
367 return std::make_pair(sColorGrey20, sColorGrey50);
369 return std::make_pair(sColorGrey10, sColorGrey40);
372 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeRangeThumbColors(
373 const EventStates& aState) {
374 bool isActive =
375 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
376 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
377 bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
379 const sRGBColor& backgroundColor = [&] {
380 if (isDisabled) {
381 return sColorGrey50Alpha50;
383 if (isActive) {
384 return sAccentColor;
386 if (isHovered) {
387 return sColorGrey60;
389 return sColorGrey50;
390 }();
392 const sRGBColor borderColor = sColorWhite;
394 return std::make_pair(backgroundColor, borderColor);
397 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeProgressColors() {
398 return std::make_pair(sAccentColor, sAccentColorDark);
401 std::pair<sRGBColor, sRGBColor>
402 nsNativeBasicTheme::ComputeProgressTrackColors() {
403 return std::make_pair(sColorGrey10, sColorGrey40);
406 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeMeterchunkColors(
407 const EventStates& aMeterState) {
408 sRGBColor borderColor = sColorMeterGreen20;
409 sRGBColor chunkColor = sColorMeterGreen10;
411 if (aMeterState.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
412 borderColor = sColorMeterYellow20;
413 chunkColor = sColorMeterYellow10;
414 } else if (aMeterState.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
415 borderColor = sColorMeterRed20;
416 chunkColor = sColorMeterRed10;
419 return std::make_pair(chunkColor, borderColor);
422 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeMeterTrackColors() {
423 return std::make_pair(sColorGrey10, sColorGrey40);
426 sRGBColor nsNativeBasicTheme::ComputeMenulistArrowButtonColor(
427 const EventStates& aState) {
428 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
429 return isDisabled ? sColorGrey60Alpha50 : sColorGrey60;
432 std::array<sRGBColor, 3> nsNativeBasicTheme::ComputeFocusRectColors() {
433 return {sAccentColor, sColorWhiteAlpha80, sAccentColorLight};
436 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeScrollbarColors(
437 nsIFrame* aFrame, const ComputedStyle& aStyle,
438 const EventStates& aDocumentState) {
439 const nsStyleUI* ui = aStyle.StyleUI();
440 nscolor color;
441 if (ui->mScrollbarColor.IsColors()) {
442 color = ui->mScrollbarColor.AsColors().track.CalcColor(aStyle);
443 } else if (aDocumentState.HasAllStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
444 color = LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarInactive,
445 sScrollbarColor.ToABGR());
446 } else {
447 color = LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbar,
448 sScrollbarColor.ToABGR());
450 return std::make_pair(gfx::sRGBColor::FromABGR(color), sScrollbarBorderColor);
453 nscolor nsNativeBasicTheme::AdjustUnthemedScrollbarThumbColor(
454 nscolor aFaceColor, EventStates aStates) {
455 // In Windows 10, scrollbar thumb has the following colors:
457 // State | Color | Luminance
458 // -------+----------+----------
459 // Normal | Gray 205 | 61.0%
460 // Hover | Gray 166 | 38.1%
461 // Active | Gray 96 | 11.7%
463 // This function is written based on the ratios between the values.
464 bool isActive = aStates.HasState(NS_EVENT_STATE_ACTIVE);
465 bool isHover = aStates.HasState(NS_EVENT_STATE_HOVER);
466 if (!isActive && !isHover) {
467 return aFaceColor;
469 float luminance = RelativeLuminanceUtils::Compute(aFaceColor);
470 if (isActive) {
471 // 11.7 / 61.0
472 luminance = ScaleLuminanceBy(luminance, 0.192f);
473 } else {
474 // 38.1 / 61.0
475 luminance = ScaleLuminanceBy(luminance, 0.625f);
477 return RelativeLuminanceUtils::Adjust(aFaceColor, luminance);
480 sRGBColor nsNativeBasicTheme::ComputeScrollbarThumbColor(
481 nsIFrame* aFrame, const ComputedStyle& aStyle,
482 const EventStates& aElementState, const EventStates& aDocumentState) {
483 const nsStyleUI* ui = aStyle.StyleUI();
484 nscolor color;
485 if (ui->mScrollbarColor.IsColors()) {
486 color = AdjustUnthemedScrollbarThumbColor(
487 ui->mScrollbarColor.AsColors().thumb.CalcColor(aStyle), aElementState);
488 } else if (aDocumentState.HasAllStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
489 color = LookAndFeel::GetColor(
490 LookAndFeel::ColorID::ThemedScrollbarThumbInactive,
491 sScrollbarThumbColor.ToABGR());
492 } else if (aElementState.HasAllStates(NS_EVENT_STATE_ACTIVE)) {
493 color =
494 LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarThumbActive,
495 sScrollbarThumbColorActive.ToABGR());
496 } else if (aElementState.HasAllStates(NS_EVENT_STATE_HOVER)) {
497 color =
498 LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarThumbHover,
499 sScrollbarThumbColorHover.ToABGR());
500 } else {
501 color = LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarThumb,
502 sScrollbarThumbColor.ToABGR());
504 return gfx::sRGBColor::FromABGR(color);
507 std::array<sRGBColor, 3> nsNativeBasicTheme::ComputeScrollbarButtonColors(
508 nsIFrame* aFrame, StyleAppearance aAppearance, const ComputedStyle& aStyle,
509 const EventStates& aElementState, const EventStates& aDocumentState) {
510 bool isActive = aElementState.HasState(NS_EVENT_STATE_ACTIVE);
511 bool isHovered = aElementState.HasState(NS_EVENT_STATE_HOVER);
513 bool hasCustomColor = aStyle.StyleUI()->mScrollbarColor.IsColors();
514 sRGBColor buttonColor;
515 if (hasCustomColor) {
516 // When scrollbar-color is in use, use the thumb color for the button.
517 buttonColor = ComputeScrollbarThumbColor(aFrame, aStyle, aElementState,
518 aDocumentState);
519 } else if (isActive) {
520 buttonColor = sScrollbarButtonActiveColor;
521 } else if (!hasCustomColor && isHovered) {
522 buttonColor = sScrollbarButtonHoverColor;
523 } else {
524 buttonColor = sScrollbarColor;
527 sRGBColor arrowColor;
528 if (hasCustomColor) {
529 // When scrollbar-color is in use, derive the arrow color from the button
530 // color.
531 nscolor bg = buttonColor.ToABGR();
532 bool darken = NS_GetLuminosity(bg) >= NS_MAX_LUMINOSITY / 2;
533 if (isActive) {
534 float c = darken ? 0.0f : 1.0f;
535 arrowColor = sRGBColor(c, c, c);
536 } else {
537 uint8_t c = darken ? 0 : 255;
538 arrowColor =
539 sRGBColor::FromABGR(NS_ComposeColors(bg, NS_RGBA(c, c, c, 160)));
541 } else if (isActive) {
542 arrowColor = sScrollbarArrowColorActive;
543 } else if (isHovered) {
544 arrowColor = sScrollbarArrowColorHover;
545 } else {
546 arrowColor = sScrollbarArrowColor;
549 return {buttonColor, arrowColor, sScrollbarBorderColor};
552 static already_AddRefed<Path> GetFocusStrokePath(
553 DrawTarget* aDrawTarget, LayoutDeviceRect& aFocusRect,
554 LayoutDeviceCoord aOffset, const LayoutDeviceCoord aRadius,
555 LayoutDeviceCoord aFocusWidth) {
556 RectCornerRadii radii(aRadius, aRadius, aRadius, aRadius);
557 aFocusRect.Inflate(aOffset);
559 LayoutDeviceRect focusRect(aFocusRect);
560 // Deflate the rect by half the border width, so that the middle of the
561 // stroke fills exactly the area we want to fill and not more.
562 focusRect.Deflate(aFocusWidth * 0.5f);
564 return MakePathForRoundedRect(*aDrawTarget, focusRect.ToUnknownRect(), radii);
567 static const CSSCoord kInnerFocusOutlineWidth = 2.0f;
569 void nsNativeBasicTheme::PaintRoundedFocusRect(DrawTarget* aDrawTarget,
570 const LayoutDeviceRect& aRect,
571 DPIRatio aDpiRatio,
572 CSSCoord aRadius,
573 CSSCoord aOffset) {
574 // NOTE(emilio): If the widths or offsets here change, make sure to tweak
575 // the GetWidgetOverflow path for FocusOutline.
576 auto [innerColor, middleColor, outerColor] = ComputeFocusRectColors();
578 LayoutDeviceRect focusRect(aRect);
580 // The focus rect is painted outside of the border area (aRect), see:
582 // data:text/html,<div style="border: 1px solid; outline: 2px solid
583 // red">Foobar</div>
585 // But some controls might provide a negative offset to cover the border, if
586 // necessary.
587 LayoutDeviceCoord offset = aOffset * aDpiRatio;
588 LayoutDeviceCoord strokeWidth = kInnerFocusOutlineWidth * aDpiRatio;
589 focusRect.Inflate(strokeWidth);
591 LayoutDeviceCoord strokeRadius = aRadius * aDpiRatio;
592 RefPtr<Path> roundedRect = GetFocusStrokePath(aDrawTarget, focusRect, offset,
593 strokeRadius, strokeWidth);
594 aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(innerColor)),
595 StrokeOptions(strokeWidth));
597 offset = CSSCoord(1.0f) * aDpiRatio;
598 strokeRadius += offset;
599 strokeWidth = CSSCoord(1.0f) * aDpiRatio;
600 roundedRect = GetFocusStrokePath(aDrawTarget, focusRect, offset, strokeRadius,
601 strokeWidth);
602 aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(middleColor)),
603 StrokeOptions(strokeWidth));
605 offset = CSSCoord(2.0f) * aDpiRatio;
606 strokeRadius += offset;
607 strokeWidth = CSSCoord(2.0f) * aDpiRatio;
608 roundedRect = GetFocusStrokePath(aDrawTarget, focusRect, offset, strokeRadius,
609 strokeWidth);
610 aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(outerColor)),
611 StrokeOptions(strokeWidth));
614 void nsNativeBasicTheme::PaintRoundedRectWithRadius(
615 DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
616 const sRGBColor& aBackgroundColor, const sRGBColor& aBorderColor,
617 CSSCoord aBorderWidth, CSSCoord aRadius, DPIRatio aDpiRatio) {
618 const LayoutDeviceCoord borderWidth(SnapBorderWidth(aBorderWidth, aDpiRatio));
620 LayoutDeviceRect rect(aRect);
621 // Deflate the rect by half the border width, so that the middle of the
622 // stroke fills exactly the area we want to fill and not more.
623 rect.Deflate(borderWidth * 0.5f);
625 LayoutDeviceCoord radius(aRadius * aDpiRatio);
626 // Fix up the radius if it's too large with the rect we're going to paint.
628 LayoutDeviceCoord min = std::min(rect.width, rect.height);
629 if (radius * 2.0f > min) {
630 radius = min * 0.5f;
634 RectCornerRadii radii(radius, radius, radius, radius);
635 RefPtr<Path> roundedRect =
636 MakePathForRoundedRect(*aDrawTarget, rect.ToUnknownRect(), radii);
638 aDrawTarget->Fill(roundedRect, ColorPattern(ToDeviceColor(aBackgroundColor)));
639 aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(aBorderColor)),
640 StrokeOptions(borderWidth));
643 void nsNativeBasicTheme::PaintCheckboxControl(DrawTarget* aDrawTarget,
644 const LayoutDeviceRect& aRect,
645 const EventStates& aState,
646 DPIRatio aDpiRatio) {
647 const CSSCoord radius = 2.0f;
648 auto [backgroundColor, borderColor] =
649 ComputeCheckboxColors(aState, StyleAppearance::Checkbox);
650 PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
651 kCheckboxRadioBorderWidth, radius, aDpiRatio);
653 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
654 PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, 5.0f, 1.0f);
659 // Returns the right scale for points in a 14x14 unit box centered at 0x0 to
660 // fill aRect in the smaller dimension.
661 static float ScaleToFillRect(const LayoutDeviceRect& aRect) {
662 static constexpr float kPathPointsScale = 14.0f;
663 return std::min(aRect.width, aRect.height) / kPathPointsScale;
666 void nsNativeBasicTheme::PaintCheckMark(DrawTarget* aDrawTarget,
667 const LayoutDeviceRect& aRect,
668 const EventStates& aState) {
669 // Points come from the coordinates on a 14X14 unit box centered at 0,0
670 const float checkPolygonX[] = {-4.5f, -1.5f, -0.5f, 5.0f, 4.75f,
671 3.5f, -0.5f, -1.5f, -3.5f};
672 const float checkPolygonY[] = {0.5f, 4.0f, 4.0f, -2.5f, -4.0f,
673 -4.0f, 1.0f, 1.25f, -1.0f};
674 const int32_t checkNumPoints = sizeof(checkPolygonX) / sizeof(float);
675 const float scale = ScaleToFillRect(aRect);
676 auto center = aRect.Center().ToUnknownPoint();
678 RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
679 Point p = center + Point(checkPolygonX[0] * scale, checkPolygonY[0] * scale);
680 builder->MoveTo(p);
681 for (int32_t i = 1; i < checkNumPoints; i++) {
682 p = center + Point(checkPolygonX[i] * scale, checkPolygonY[i] * scale);
683 builder->LineTo(p);
685 RefPtr<Path> path = builder->Finish();
687 sRGBColor fillColor = ComputeCheckmarkColor(aState);
688 aDrawTarget->Fill(path, ColorPattern(ToDeviceColor(fillColor)));
691 void nsNativeBasicTheme::PaintIndeterminateMark(DrawTarget* aDrawTarget,
692 const LayoutDeviceRect& aRect,
693 const EventStates& aState) {
694 const CSSCoord borderWidth = 2.0f;
695 const float scale = ScaleToFillRect(aRect);
697 Rect rect = aRect.ToUnknownRect();
698 rect.y += (rect.height / 2) - (borderWidth * scale / 2);
699 rect.height = borderWidth * scale;
700 rect.x += (borderWidth * scale) + (borderWidth * scale / 8);
701 rect.width -= ((borderWidth * scale) + (borderWidth * scale / 8)) * 2;
703 sRGBColor fillColor = ComputeCheckmarkColor(aState);
704 aDrawTarget->FillRect(rect, ColorPattern(ToDeviceColor(fillColor)));
707 void nsNativeBasicTheme::PaintStrokedEllipse(DrawTarget* aDrawTarget,
708 const LayoutDeviceRect& aRect,
709 const sRGBColor& aBackgroundColor,
710 const sRGBColor& aBorderColor,
711 const CSSCoord aBorderWidth,
712 DPIRatio aDpiRatio) {
713 const LayoutDeviceCoord borderWidth(aBorderWidth * aDpiRatio);
714 RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
716 // Deflate for the same reason as PaintRoundedRectWithRadius. Note that the
717 // size is the diameter, so we just shrink by the border width once.
718 auto size = aRect.Size() - LayoutDeviceSize(borderWidth, borderWidth);
719 AppendEllipseToPath(builder, aRect.Center().ToUnknownPoint(),
720 size.ToUnknownSize());
721 RefPtr<Path> ellipse = builder->Finish();
723 aDrawTarget->Fill(ellipse, ColorPattern(ToDeviceColor(aBackgroundColor)));
724 aDrawTarget->Stroke(ellipse, ColorPattern(ToDeviceColor(aBorderColor)),
725 StrokeOptions(borderWidth));
728 void nsNativeBasicTheme::PaintEllipseShadow(DrawTarget* aDrawTarget,
729 const LayoutDeviceRect& aRect,
730 float aShadowAlpha,
731 const CSSPoint& aShadowOffset,
732 CSSCoord aShadowBlurStdDev,
733 DPIRatio aDpiRatio) {
734 Float stdDev = aShadowBlurStdDev * aDpiRatio;
735 Point offset = (aShadowOffset * aDpiRatio).ToUnknownPoint();
737 RefPtr<FilterNode> blurFilter =
738 aDrawTarget->CreateFilter(FilterType::GAUSSIAN_BLUR);
739 if (!blurFilter) {
740 return;
743 blurFilter->SetAttribute(ATT_GAUSSIAN_BLUR_STD_DEVIATION, stdDev);
745 IntSize inflation =
746 gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(stdDev, stdDev));
747 Rect inflatedRect = aRect.ToUnknownRect();
748 inflatedRect.Inflate(inflation.width, inflation.height);
749 Rect sourceRectInFilterSpace =
750 inflatedRect - aRect.TopLeft().ToUnknownPoint();
751 Point destinationPointOfSourceRect = inflatedRect.TopLeft() + offset;
753 IntSize dtSize = RoundedToInt(aRect.Size().ToUnknownSize());
754 RefPtr<DrawTarget> ellipseDT = aDrawTarget->CreateSimilarDrawTargetForFilter(
755 dtSize, SurfaceFormat::A8, blurFilter, blurFilter,
756 sourceRectInFilterSpace, destinationPointOfSourceRect);
757 if (!ellipseDT) {
758 return;
761 RefPtr<Path> ellipse = MakePathForEllipse(
762 *ellipseDT, (aRect - aRect.TopLeft()).Center().ToUnknownPoint(),
763 aRect.Size().ToUnknownSize());
764 ellipseDT->Fill(ellipse,
765 ColorPattern(DeviceColor(0.0f, 0.0f, 0.0f, aShadowAlpha)));
766 RefPtr<SourceSurface> ellipseSurface = ellipseDT->Snapshot();
768 blurFilter->SetInput(IN_GAUSSIAN_BLUR_IN, ellipseSurface);
769 aDrawTarget->DrawFilter(blurFilter, sourceRectInFilterSpace,
770 destinationPointOfSourceRect);
773 void nsNativeBasicTheme::PaintRadioControl(DrawTarget* aDrawTarget,
774 const LayoutDeviceRect& aRect,
775 const EventStates& aState,
776 DPIRatio aDpiRatio) {
777 const CSSCoord borderWidth = 2.0f;
778 auto [backgroundColor, borderColor] =
779 ComputeCheckboxColors(aState, StyleAppearance::Radio);
781 PaintStrokedEllipse(aDrawTarget, aRect, backgroundColor, borderColor,
782 borderWidth, aDpiRatio);
784 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
785 PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, 5.0f, 1.0f);
789 void nsNativeBasicTheme::PaintRadioCheckmark(DrawTarget* aDrawTarget,
790 const LayoutDeviceRect& aRect,
791 const EventStates& aState,
792 DPIRatio aDpiRatio) {
793 const CSSCoord borderWidth = 2.0f;
794 const float scale = ScaleToFillRect(aRect);
795 auto [backgroundColor, checkColor] = ComputeRadioCheckmarkColors(aState);
797 LayoutDeviceRect rect(aRect);
798 rect.Deflate(borderWidth * scale);
800 PaintStrokedEllipse(aDrawTarget, rect, checkColor, backgroundColor,
801 borderWidth, aDpiRatio);
804 void nsNativeBasicTheme::PaintTextField(DrawTarget* aDrawTarget,
805 const LayoutDeviceRect& aRect,
806 const EventStates& aState,
807 DPIRatio aDpiRatio) {
808 auto [backgroundColor, borderColor] = ComputeTextfieldColors(aState);
810 const CSSCoord radius = 2.0f;
812 PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
813 kTextFieldBorderWidth, radius, aDpiRatio);
815 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
816 PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius,
817 -kTextFieldBorderWidth);
821 void nsNativeBasicTheme::PaintListbox(DrawTarget* aDrawTarget,
822 const LayoutDeviceRect& aRect,
823 const EventStates& aState,
824 DPIRatio aDpiRatio) {
825 const CSSCoord radius = 2.0f;
826 auto [backgroundColor, borderColor] = ComputeTextfieldColors(aState);
828 PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
829 kMenulistBorderWidth, radius, aDpiRatio);
831 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
832 PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius,
833 -kMenulistBorderWidth);
837 void nsNativeBasicTheme::PaintMenulist(DrawTarget* aDrawTarget,
838 const LayoutDeviceRect& aRect,
839 const EventStates& aState,
840 DPIRatio aDpiRatio) {
841 const CSSCoord radius = 4.0f;
842 auto [backgroundColor, borderColor] = ComputeButtonColors(aState);
844 PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
845 kMenulistBorderWidth, radius, aDpiRatio);
847 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
848 PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius,
849 -kMenulistBorderWidth);
853 void nsNativeBasicTheme::PaintArrow(DrawTarget* aDrawTarget,
854 const LayoutDeviceRect& aRect,
855 const float aArrowPolygonX[],
856 const float aArrowPolygonY[],
857 const int32_t aArrowNumPoints,
858 const sRGBColor aFillColor) {
859 const float scale = ScaleToFillRect(aRect);
861 auto center = aRect.Center().ToUnknownPoint();
863 RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
864 Point p =
865 center + Point(aArrowPolygonX[0] * scale, aArrowPolygonY[0] * scale);
866 builder->MoveTo(p);
867 for (int32_t i = 1; i < aArrowNumPoints; i++) {
868 p = center + Point(aArrowPolygonX[i] * scale, aArrowPolygonY[i] * scale);
869 builder->LineTo(p);
871 RefPtr<Path> path = builder->Finish();
873 aDrawTarget->Fill(path, ColorPattern(ToDeviceColor(aFillColor)));
876 void nsNativeBasicTheme::PaintMenulistArrowButton(nsIFrame* aFrame,
877 DrawTarget* aDrawTarget,
878 const LayoutDeviceRect& aRect,
879 const EventStates& aState) {
880 const float arrowPolygonX[] = {-3.5f, -0.5f, 0.5f, 3.5f, 3.5f,
881 3.0f, 0.5f, -0.5f, -3.0f, -3.5f};
882 const float arrowPolygonY[] = {-0.5f, 2.5f, 2.5f, -0.5f, -2.0f,
883 -2.0f, 1.0f, 1.0f, -2.0f, -2.0f};
884 const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
885 sRGBColor arrowColor = ComputeMenulistArrowButtonColor(aState);
886 PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints,
887 arrowColor);
890 void nsNativeBasicTheme::PaintSpinnerButton(nsIFrame* aFrame,
891 DrawTarget* aDrawTarget,
892 const LayoutDeviceRect& aRect,
893 const EventStates& aState,
894 StyleAppearance aAppearance,
895 DPIRatio aDpiRatio) {
896 auto [backgroundColor, borderColor] = ComputeButtonColors(aState);
898 aDrawTarget->FillRect(aRect.ToUnknownRect(),
899 ColorPattern(ToDeviceColor(backgroundColor)));
901 const float arrowPolygonX[] = {-5.25f, -0.75f, 0.75f, 5.25f, 5.25f,
902 4.5f, 0.75f, -0.75f, -4.5f, -5.25f};
903 const float arrowPolygonY[] = {-1.875f, 2.625f, 2.625f, -1.875f, -4.125f,
904 -4.125f, 0.375f, 0.375f, -4.125f, -4.125f};
905 const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
906 const float scaleX = ScaleToFillRect(aRect);
907 const float scaleY =
908 aAppearance == StyleAppearance::SpinnerDownbutton ? scaleX : -scaleX;
910 RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
911 auto center = aRect.Center().ToUnknownPoint();
912 Point p =
913 center + Point(arrowPolygonX[0] * scaleX, arrowPolygonY[0] * scaleY);
914 builder->MoveTo(p);
915 for (int32_t i = 1; i < arrowNumPoints; i++) {
916 p = center + Point(arrowPolygonX[i] * scaleX, arrowPolygonY[i] * scaleY);
917 builder->LineTo(p);
919 RefPtr<Path> path = builder->Finish();
920 aDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
923 void nsNativeBasicTheme::PaintRange(nsIFrame* aFrame, DrawTarget* aDrawTarget,
924 const LayoutDeviceRect& aRect,
925 const EventStates& aState,
926 DPIRatio aDpiRatio, bool aHorizontal) {
927 nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
928 if (!rangeFrame) {
929 return;
932 double progress = rangeFrame->GetValueAsFractionOfRange();
933 auto rect = aRect;
934 LayoutDeviceRect thumbRect(0, 0, kMinimumRangeThumbSize * aDpiRatio,
935 kMinimumRangeThumbSize * aDpiRatio);
936 Rect overflowRect = aRect.ToUnknownRect();
937 overflowRect.Inflate(CSSCoord(6.0f) * aDpiRatio); // See GetWidgetOverflow
938 Rect progressClipRect(overflowRect);
939 Rect trackClipRect(overflowRect);
940 const LayoutDeviceCoord verticalSize = kRangeHeight * aDpiRatio;
941 if (aHorizontal) {
942 rect.height = verticalSize;
943 rect.y = aRect.y + (aRect.height - rect.height) / 2;
944 thumbRect.y = aRect.y + (aRect.height - thumbRect.height) / 2;
946 if (IsFrameRTL(aFrame)) {
947 thumbRect.x =
948 aRect.x + (aRect.width - thumbRect.width) * (1.0 - progress);
949 float midPoint = thumbRect.Center().X();
950 trackClipRect.SetBoxX(overflowRect.X(), midPoint);
951 progressClipRect.SetBoxX(midPoint, overflowRect.XMost());
952 } else {
953 thumbRect.x = aRect.x + (aRect.width - thumbRect.width) * progress;
954 float midPoint = thumbRect.Center().X();
955 progressClipRect.SetBoxX(overflowRect.X(), midPoint);
956 trackClipRect.SetBoxX(midPoint, overflowRect.XMost());
958 } else {
959 rect.width = verticalSize;
960 rect.x = aRect.x + (aRect.width - rect.width) / 2;
961 thumbRect.x = aRect.x + (aRect.width - thumbRect.width) / 2;
963 thumbRect.y = aRect.y + (aRect.height - thumbRect.height) * progress;
964 float midPoint = thumbRect.Center().Y();
965 trackClipRect.SetBoxY(overflowRect.Y(), midPoint);
966 progressClipRect.SetBoxY(midPoint, overflowRect.YMost());
969 const CSSCoord borderWidth = 1.0f;
970 const CSSCoord radius = 2.0f;
972 auto [progressColor, progressBorderColor] =
973 ComputeRangeProgressColors(aState);
974 auto [trackColor, trackBorderColor] = ComputeRangeTrackColors(aState);
976 // Make a path that clips out the range thumb.
977 RefPtr<PathBuilder> builder =
978 aDrawTarget->CreatePathBuilder(FillRule::FILL_EVEN_ODD);
979 AppendRectToPath(builder, overflowRect);
980 AppendEllipseToPath(builder, thumbRect.Center().ToUnknownPoint(),
981 thumbRect.Size().ToUnknownSize());
982 RefPtr<Path> path = builder->Finish();
984 // Draw the progress and track pieces with the thumb clipped out, so that
985 // they're not visible behind the thumb even if the thumb is partially
986 // transparent (which is the case in the disabled state).
987 aDrawTarget->PushClip(path);
989 aDrawTarget->PushClipRect(progressClipRect);
990 PaintRoundedRectWithRadius(aDrawTarget, rect, progressColor,
991 progressBorderColor, borderWidth, radius,
992 aDpiRatio);
993 aDrawTarget->PopClip();
995 aDrawTarget->PushClipRect(trackClipRect);
996 PaintRoundedRectWithRadius(aDrawTarget, rect, trackColor, trackBorderColor,
997 borderWidth, radius, aDpiRatio);
998 aDrawTarget->PopClip();
1000 if (!aState.HasState(NS_EVENT_STATE_DISABLED)) {
1001 // Thumb shadow
1002 PaintEllipseShadow(aDrawTarget, thumbRect, 0.3f, CSSPoint(0.0f, 2.0f),
1003 2.0f, aDpiRatio);
1006 aDrawTarget->PopClip();
1008 // Draw the thumb on top.
1009 const CSSCoord thumbBorderWidth = 2.0f;
1010 auto [thumbColor, thumbBorderColor] = ComputeRangeThumbColors(aState);
1012 PaintStrokedEllipse(aDrawTarget, thumbRect, thumbColor, thumbBorderColor,
1013 thumbBorderWidth, aDpiRatio);
1015 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
1016 PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius, 1.0f);
1020 // TODO: Indeterminate state.
1021 void nsNativeBasicTheme::PaintProgress(
1022 nsIFrame* aFrame, DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
1023 const EventStates& aState, DPIRatio aDpiRatio, bool aIsMeter, bool aBar) {
1024 auto [backgroundColor, borderColor] = [&] {
1025 if (aIsMeter) {
1026 return aBar ? ComputeMeterTrackColors() : ComputeMeterchunkColors(aState);
1028 return aBar ? ComputeProgressTrackColors() : ComputeProgressColors();
1029 }();
1031 const CSSCoord borderWidth = 1.0f;
1032 const CSSCoord radius = aIsMeter ? 5.0f : 2.0f;
1034 LayoutDeviceRect rect(aRect);
1035 const LayoutDeviceCoord thickness =
1036 (aIsMeter ? kMeterHeight : kProgressbarHeight) * aDpiRatio;
1038 const bool isHorizontal = !nsNativeTheme::IsVerticalProgress(aFrame);
1039 if (isHorizontal) {
1040 // Center it vertically.
1041 rect.y += (rect.height - thickness) / 2;
1042 rect.height = thickness;
1043 } else {
1044 // Center it horizontally.
1045 rect.x += (rect.width - thickness) / 2;
1046 rect.width = thickness;
1049 // This is the progress chunk, clip it to the right amount.
1050 if (!aBar) {
1051 double position = [&] {
1052 if (aIsMeter) {
1053 auto* meter = dom::HTMLMeterElement::FromNode(aFrame->GetContent());
1054 if (!meter) {
1055 return 0.0;
1057 return meter->Value() / meter->Max();
1059 auto* progress = dom::HTMLProgressElement::FromNode(aFrame->GetContent());
1060 if (!progress) {
1061 return 0.0;
1063 return progress->Value() / progress->Max();
1064 }();
1065 LayoutDeviceRect clipRect = rect;
1066 if (isHorizontal) {
1067 double clipWidth = rect.width * position;
1068 clipRect.width = clipWidth;
1069 if (IsFrameRTL(aFrame)) {
1070 clipRect.x += rect.width - clipWidth;
1072 } else {
1073 double clipHeight = rect.height * position;
1074 clipRect.height = clipHeight;
1075 clipRect.y += rect.height - clipHeight;
1077 aDrawTarget->PushClipRect(clipRect.ToUnknownRect());
1080 PaintRoundedRectWithRadius(aDrawTarget, rect, backgroundColor, borderColor,
1081 borderWidth, radius, aDpiRatio);
1083 if (!aBar) {
1084 aDrawTarget->PopClip();
1088 void nsNativeBasicTheme::PaintButton(nsIFrame* aFrame, DrawTarget* aDrawTarget,
1089 const LayoutDeviceRect& aRect,
1090 const EventStates& aState,
1091 DPIRatio aDpiRatio) {
1092 const CSSCoord radius = 4.0f;
1093 auto [backgroundColor, borderColor] = ComputeButtonColors(aState, aFrame);
1095 PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
1096 kButtonBorderWidth, radius, aDpiRatio);
1098 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
1099 PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius,
1100 -kButtonBorderWidth);
1104 void nsNativeBasicTheme::PaintScrollbarThumb(DrawTarget* aDrawTarget,
1105 const LayoutDeviceRect& aRect,
1106 bool aHorizontal, nsIFrame* aFrame,
1107 const ComputedStyle& aStyle,
1108 const EventStates& aElementState,
1109 const EventStates& aDocumentState,
1110 DPIRatio aDpiRatio) {
1111 sRGBColor thumbColor =
1112 ComputeScrollbarThumbColor(aFrame, aStyle, aElementState, aDocumentState);
1114 aDrawTarget->FillRect(aRect.ToUnknownRect(),
1115 ColorPattern(ToDeviceColor(thumbColor)));
1118 void nsNativeBasicTheme::PaintScrollbarTrack(DrawTarget* aDrawTarget,
1119 const LayoutDeviceRect& aRect,
1120 bool aHorizontal, nsIFrame* aFrame,
1121 const ComputedStyle& aStyle,
1122 const EventStates& aDocumentState,
1123 DPIRatio aDpiRatio) {
1124 // Draw nothing by default. Subclasses can override this.
1127 void nsNativeBasicTheme::PaintScrollbar(DrawTarget* aDrawTarget,
1128 const LayoutDeviceRect& aRect,
1129 bool aHorizontal, nsIFrame* aFrame,
1130 const ComputedStyle& aStyle,
1131 const EventStates& aDocumentState,
1132 DPIRatio aDpiRatio) {
1133 auto [scrollbarColor, borderColor] =
1134 ComputeScrollbarColors(aFrame, aStyle, aDocumentState);
1135 aDrawTarget->FillRect(aRect.ToUnknownRect(),
1136 ColorPattern(ToDeviceColor(scrollbarColor)));
1137 // FIXME(heycam): We should probably derive the border color when custom
1138 // scrollbar colors are in use too. But for now, just skip painting it,
1139 // to avoid ugliness.
1140 if (aStyle.StyleUI()->mScrollbarColor.IsAuto()) {
1141 RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
1142 LayoutDeviceRect strokeRect(aRect);
1143 strokeRect.Deflate(CSSCoord(0.5f) * aDpiRatio);
1144 builder->MoveTo(strokeRect.TopLeft().ToUnknownPoint());
1145 builder->LineTo(
1146 (aHorizontal ? strokeRect.TopRight() : strokeRect.BottomLeft())
1147 .ToUnknownPoint());
1148 RefPtr<Path> path = builder->Finish();
1149 aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(borderColor)),
1150 StrokeOptions(CSSCoord(1.0f) * aDpiRatio));
1154 void nsNativeBasicTheme::PaintScrollCorner(DrawTarget* aDrawTarget,
1155 const LayoutDeviceRect& aRect,
1156 nsIFrame* aFrame,
1157 const ComputedStyle& aStyle,
1158 const EventStates& aDocumentState,
1159 DPIRatio aDpiRatio) {
1160 auto [scrollbarColor, borderColor] =
1161 ComputeScrollbarColors(aFrame, aStyle, aDocumentState);
1162 Unused << borderColor;
1163 aDrawTarget->FillRect(aRect.ToUnknownRect(),
1164 ColorPattern(ToDeviceColor(scrollbarColor)));
1167 void nsNativeBasicTheme::PaintScrollbarButton(
1168 DrawTarget* aDrawTarget, StyleAppearance aAppearance,
1169 const LayoutDeviceRect& aRect, nsIFrame* aFrame,
1170 const ComputedStyle& aStyle, const EventStates& aElementState,
1171 const EventStates& aDocumentState, DPIRatio aDpiRatio) {
1172 bool hasCustomColor = aStyle.StyleUI()->mScrollbarColor.IsColors();
1173 auto [buttonColor, arrowColor, borderColor] = ComputeScrollbarButtonColors(
1174 aFrame, aAppearance, aStyle, aElementState, aDocumentState);
1175 aDrawTarget->FillRect(aRect.ToUnknownRect(),
1176 ColorPattern(ToDeviceColor(buttonColor)));
1178 // Start with Up arrow.
1179 float arrowPolygonX[] = {-3.0f, 0.0f, 3.0f, 3.0f, 0.0f, -3.0f};
1180 float arrowPolygonY[] = {0.4f, -3.1f, 0.4f, 2.7f, -0.8f, 2.7f};
1182 const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
1183 switch (aAppearance) {
1184 case StyleAppearance::ScrollbarbuttonUp:
1185 break;
1186 case StyleAppearance::ScrollbarbuttonDown:
1187 for (int32_t i = 0; i < arrowNumPoints; i++) {
1188 arrowPolygonY[i] *= -1;
1190 break;
1191 case StyleAppearance::ScrollbarbuttonLeft:
1192 for (int32_t i = 0; i < arrowNumPoints; i++) {
1193 int32_t temp = arrowPolygonX[i];
1194 arrowPolygonX[i] = arrowPolygonY[i];
1195 arrowPolygonY[i] = temp;
1197 break;
1198 case StyleAppearance::ScrollbarbuttonRight:
1199 for (int32_t i = 0; i < arrowNumPoints; i++) {
1200 int32_t temp = arrowPolygonX[i];
1201 arrowPolygonX[i] = arrowPolygonY[i] * -1;
1202 arrowPolygonY[i] = temp;
1204 break;
1205 default:
1206 return;
1208 PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints,
1209 arrowColor);
1211 // FIXME(heycam): We should probably derive the border color when custom
1212 // scrollbar colors are in use too. But for now, just skip painting it,
1213 // to avoid ugliness.
1214 if (!hasCustomColor) {
1215 RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
1216 builder->MoveTo(Point(aRect.x, aRect.y));
1217 if (aAppearance == StyleAppearance::ScrollbarbuttonUp ||
1218 aAppearance == StyleAppearance::ScrollbarbuttonDown) {
1219 builder->LineTo(Point(aRect.x, aRect.y + aRect.height));
1220 } else {
1221 builder->LineTo(Point(aRect.x + aRect.width, aRect.y));
1224 RefPtr<Path> path = builder->Finish();
1225 aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(borderColor)),
1226 StrokeOptions(CSSCoord(1.0f) * aDpiRatio));
1230 NS_IMETHODIMP
1231 nsNativeBasicTheme::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
1232 StyleAppearance aAppearance,
1233 const nsRect& aRect,
1234 const nsRect& /* aDirtyRect */) {
1235 DrawTarget* dt = aContext->GetDrawTarget();
1236 const nscoord twipsPerPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
1237 EventStates eventState = GetContentState(aFrame, aAppearance);
1238 EventStates docState = aFrame->PresContext()->Document()->GetDocumentState();
1239 auto devPxRect = LayoutDeviceRect::FromUnknownRect(
1240 NSRectToSnappedRect(aRect, twipsPerPixel, *dt));
1242 if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
1243 bool isHTML = IsHTMLContent(aFrame);
1244 nsIFrame* parentFrame = aFrame->GetParent();
1245 bool isMenulist = !isHTML && parentFrame->IsMenuFrame();
1246 // HTML select and XUL menulist dropdown buttons get state from the
1247 // parent.
1248 if (isHTML || isMenulist) {
1249 aFrame = parentFrame;
1250 eventState = GetContentState(parentFrame, aAppearance);
1254 // Hack to avoid skia fuzziness: Add a dummy clip if the widget doesn't
1255 // overflow devPxRect.
1256 Maybe<AutoClipRect> maybeClipRect;
1257 if (aAppearance != StyleAppearance::FocusOutline &&
1258 aAppearance != StyleAppearance::Range &&
1259 !eventState.HasState(NS_EVENT_STATE_FOCUSRING)) {
1260 maybeClipRect.emplace(*dt, devPxRect);
1263 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1265 switch (aAppearance) {
1266 case StyleAppearance::Radio: {
1267 auto rect = FixAspectRatio(devPxRect);
1268 PaintRadioControl(dt, rect, eventState, dpiRatio);
1269 if (IsSelected(aFrame)) {
1270 PaintRadioCheckmark(dt, rect, eventState, dpiRatio);
1272 break;
1274 case StyleAppearance::Checkbox: {
1275 auto rect = FixAspectRatio(devPxRect);
1276 PaintCheckboxControl(dt, rect, eventState, dpiRatio);
1277 if (GetIndeterminate(aFrame)) {
1278 PaintIndeterminateMark(dt, rect, eventState);
1279 } else if (IsChecked(aFrame)) {
1280 PaintCheckMark(dt, rect, eventState);
1282 break;
1284 case StyleAppearance::Textarea:
1285 case StyleAppearance::Textfield:
1286 case StyleAppearance::NumberInput:
1287 PaintTextField(dt, devPxRect, eventState, dpiRatio);
1288 break;
1289 case StyleAppearance::Listbox:
1290 PaintListbox(dt, devPxRect, eventState, dpiRatio);
1291 break;
1292 case StyleAppearance::MenulistButton:
1293 case StyleAppearance::Menulist:
1294 PaintMenulist(dt, devPxRect, eventState, dpiRatio);
1295 break;
1296 case StyleAppearance::MozMenulistArrowButton:
1297 PaintMenulistArrowButton(aFrame, dt, devPxRect, eventState);
1298 break;
1299 case StyleAppearance::SpinnerUpbutton:
1300 case StyleAppearance::SpinnerDownbutton:
1301 PaintSpinnerButton(aFrame, dt, devPxRect, eventState, aAppearance,
1302 dpiRatio);
1303 break;
1304 case StyleAppearance::Range:
1305 PaintRange(aFrame, dt, devPxRect, eventState, dpiRatio,
1306 IsRangeHorizontal(aFrame));
1307 break;
1308 case StyleAppearance::RangeThumb:
1309 // Painted as part of StyleAppearance::Range.
1310 break;
1311 case StyleAppearance::ProgressBar:
1312 PaintProgress(aFrame, dt, devPxRect, eventState, dpiRatio,
1313 /* aMeter = */ false, /* aBar = */ true);
1314 break;
1315 case StyleAppearance::Progresschunk:
1316 if (nsProgressFrame* f = do_QueryFrame(aFrame->GetParent())) {
1317 PaintProgress(f, dt, devPxRect, f->GetContent()->AsElement()->State(),
1318 dpiRatio, /* aMeter = */ false, /* aBar = */ false);
1320 break;
1321 case StyleAppearance::Meter:
1322 PaintProgress(aFrame, dt, devPxRect, eventState, dpiRatio,
1323 /* aMeter = */ true, /* aBar = */ true);
1324 break;
1325 case StyleAppearance::Meterchunk:
1326 if (nsMeterFrame* f = do_QueryFrame(aFrame->GetParent())) {
1327 PaintProgress(f, dt, devPxRect, f->GetContent()->AsElement()->State(),
1328 dpiRatio, /* aMeter = */ true, /* aBar = */ false);
1330 break;
1331 case StyleAppearance::ScrollbarthumbHorizontal:
1332 case StyleAppearance::ScrollbarthumbVertical: {
1333 bool isHorizontal =
1334 aAppearance == StyleAppearance::ScrollbarthumbHorizontal;
1335 PaintScrollbarThumb(dt, devPxRect, isHorizontal, aFrame,
1336 *nsLayoutUtils::StyleForScrollbar(aFrame), eventState,
1337 docState, dpiRatio);
1338 break;
1340 case StyleAppearance::ScrollbartrackHorizontal:
1341 case StyleAppearance::ScrollbartrackVertical: {
1342 bool isHorizontal =
1343 aAppearance == StyleAppearance::ScrollbartrackHorizontal;
1344 PaintScrollbarTrack(dt, devPxRect, isHorizontal, aFrame,
1345 *nsLayoutUtils::StyleForScrollbar(aFrame), docState,
1346 dpiRatio);
1347 break;
1349 case StyleAppearance::ScrollbarHorizontal:
1350 case StyleAppearance::ScrollbarVertical: {
1351 bool isHorizontal = aAppearance == StyleAppearance::ScrollbarHorizontal;
1352 PaintScrollbar(dt, devPxRect, isHorizontal, aFrame,
1353 *nsLayoutUtils::StyleForScrollbar(aFrame), docState,
1354 dpiRatio);
1355 break;
1357 case StyleAppearance::Scrollcorner:
1358 PaintScrollCorner(dt, devPxRect, aFrame,
1359 *nsLayoutUtils::StyleForScrollbar(aFrame), docState,
1360 dpiRatio);
1361 break;
1362 case StyleAppearance::ScrollbarbuttonUp:
1363 case StyleAppearance::ScrollbarbuttonDown:
1364 case StyleAppearance::ScrollbarbuttonLeft:
1365 case StyleAppearance::ScrollbarbuttonRight:
1366 // For scrollbar-width:thin, we don't display the buttons.
1367 if (!IsScrollbarWidthThin(aFrame)) {
1368 PaintScrollbarButton(dt, aAppearance, devPxRect, aFrame,
1369 *nsLayoutUtils::StyleForScrollbar(aFrame),
1370 eventState, docState, dpiRatio);
1372 break;
1373 case StyleAppearance::Button:
1374 PaintButton(aFrame, dt, devPxRect, eventState, dpiRatio);
1375 break;
1376 case StyleAppearance::FocusOutline:
1377 PaintAutoStyleOutline(aFrame, dt, devPxRect, dpiRatio);
1378 break;
1379 default:
1380 // Various appearance values are used for XUL elements. Normally these
1381 // will not be available in content documents (and thus in the content
1382 // processes where the native basic theme can be used), but tests are
1383 // run with the remote XUL pref enabled and so we can get in here. So
1384 // we just return an error rather than assert.
1385 return NS_ERROR_NOT_IMPLEMENTED;
1388 return NS_OK;
1391 void nsNativeBasicTheme::PaintAutoStyleOutline(nsIFrame* aFrame,
1392 DrawTarget* aDt,
1393 const LayoutDeviceRect& aRect,
1394 DPIRatio aDpiRatio) {
1395 auto [innerColor, middleColor, outerColor] = ComputeFocusRectColors();
1396 Unused << middleColor;
1397 Unused << outerColor;
1399 const LayoutDeviceCoord width = kInnerFocusOutlineWidth * aDpiRatio;
1400 const LayoutDeviceCoord halfWidth = width * 0.5f;
1402 LayoutDeviceRect rect(aRect);
1403 // This is equivalent to Inflate(width), to paint the outline outside of
1404 // aRect, then Deflate(width * 0.5), to stroke at the right place.
1405 rect.Inflate(halfWidth);
1407 nscoord cssRadii[8];
1408 if (!aFrame->GetBorderRadii(cssRadii)) {
1409 return aDt->StrokeRect(rect.ToUnknownRect(),
1410 ColorPattern(ToDeviceColor(innerColor)),
1411 StrokeOptions(width));
1414 nsPresContext* pc = aFrame->PresContext();
1415 const nscoord offset = aFrame->StyleOutline()->mOutlineOffset.ToAppUnits();
1416 const Float devPixelOffset = pc->AppUnitsToFloatDevPixels(offset);
1418 RectCornerRadii innerRadii;
1419 nsCSSRendering::ComputePixelRadii(cssRadii, pc->AppUnitsPerDevPixel(),
1420 &innerRadii);
1422 RectCornerRadii outerRadii;
1423 const Float widths[4] = {
1424 halfWidth + devPixelOffset, halfWidth + devPixelOffset,
1425 halfWidth + devPixelOffset, halfWidth + devPixelOffset};
1426 nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outerRadii);
1427 RefPtr<Path> path =
1428 MakePathForRoundedRect(*aDt, rect.ToUnknownRect(), outerRadii);
1429 aDt->Stroke(path, ColorPattern(ToDeviceColor(innerColor)),
1430 StrokeOptions(width));
1433 /*bool
1434 nsNativeBasicTheme::CreateWebRenderCommandsForWidget(mozilla::wr::DisplayListBuilder&
1435 aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources, const
1436 mozilla::layers::StackingContextHelper& aSc,
1437 mozilla::layers::RenderRootStateManager*
1438 aManager, nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect)
1442 LayoutDeviceIntMargin nsNativeBasicTheme::GetWidgetBorder(
1443 nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
1444 switch (aAppearance) {
1445 case StyleAppearance::Textfield:
1446 case StyleAppearance::Textarea:
1447 case StyleAppearance::NumberInput:
1448 case StyleAppearance::Listbox:
1449 case StyleAppearance::Menulist:
1450 case StyleAppearance::MenulistButton:
1451 case StyleAppearance::Button:
1452 // Return the border size from the UA sheet, even though what we paint
1453 // doesn't actually match that. We know this is the UA sheet border
1454 // because we disable native theming when different border widths are
1455 // specified by authors, see nsNativeBasicTheme::IsWidgetStyled.
1457 // The Rounded() bit is technically redundant, but needed to appease the
1458 // type system, we should always end up with full device pixels due to
1459 // round_border_to_device_pixels at style time.
1460 return LayoutDeviceIntMargin::FromAppUnits(
1461 aFrame->StyleBorder()->GetComputedBorder(),
1462 aFrame->PresContext()->AppUnitsPerDevPixel())
1463 .Rounded();
1464 case StyleAppearance::Checkbox:
1465 case StyleAppearance::Radio: {
1466 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1467 LayoutDeviceIntCoord w =
1468 SnapBorderWidth(kCheckboxRadioBorderWidth, dpiRatio);
1469 return LayoutDeviceIntMargin(w, w, w, w);
1471 default:
1472 return LayoutDeviceIntMargin();
1476 bool nsNativeBasicTheme::GetWidgetPadding(nsDeviceContext* aContext,
1477 nsIFrame* aFrame,
1478 StyleAppearance aAppearance,
1479 LayoutDeviceIntMargin* aResult) {
1480 switch (aAppearance) {
1481 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
1482 // and have a meaningful baseline, so they can't have
1483 // author-specified padding.
1484 case StyleAppearance::Radio:
1485 case StyleAppearance::Checkbox:
1486 aResult->SizeTo(0, 0, 0, 0);
1487 return true;
1488 default:
1489 break;
1491 return false;
1494 static int GetScrollbarButtonCount() {
1495 int32_t buttons = LookAndFeel::GetInt(LookAndFeel::IntID::ScrollArrowStyle);
1496 return CountPopulation32(static_cast<uint32_t>(buttons));
1499 bool nsNativeBasicTheme::GetWidgetOverflow(nsDeviceContext* aContext,
1500 nsIFrame* aFrame,
1501 StyleAppearance aAppearance,
1502 nsRect* aOverflowRect) {
1503 nsIntMargin overflow;
1504 switch (aAppearance) {
1505 case StyleAppearance::FocusOutline:
1506 // 2px * one segment
1507 overflow.SizeTo(2, 2, 2, 2);
1508 break;
1509 case StyleAppearance::Radio:
1510 case StyleAppearance::Checkbox:
1511 case StyleAppearance::Range:
1512 overflow.SizeTo(6, 6, 6, 6);
1513 break;
1514 case StyleAppearance::Textarea:
1515 case StyleAppearance::Textfield:
1516 case StyleAppearance::NumberInput:
1517 case StyleAppearance::Listbox:
1518 case StyleAppearance::MenulistButton:
1519 case StyleAppearance::Menulist:
1520 case StyleAppearance::Button:
1521 // 2px for each segment, plus 1px separation, but we paint 1px inside
1522 // the border area so 4px overflow.
1523 overflow.SizeTo(4, 4, 4, 4);
1524 break;
1525 default:
1526 return false;
1529 // TODO: This should convert from device pixels to app units, not from CSS
1530 // pixels. And it should take the dpi ratio into account.
1531 // Using CSS pixels can cause the overflow to be too small if the page is
1532 // zoomed out.
1533 aOverflowRect->Inflate(nsMargin(CSSPixel::ToAppUnits(overflow.top),
1534 CSSPixel::ToAppUnits(overflow.right),
1535 CSSPixel::ToAppUnits(overflow.bottom),
1536 CSSPixel::ToAppUnits(overflow.left)));
1538 return true;
1541 auto nsNativeBasicTheme::GetScrollbarSizes(nsPresContext* aPresContext,
1542 StyleScrollbarWidth aWidth, Overlay)
1543 -> ScrollbarSizes {
1544 CSSCoord size = aWidth == StyleScrollbarWidth::Thin
1545 ? kMinimumThinScrollbarSize
1546 : kMinimumScrollbarSize;
1547 LayoutDeviceIntCoord s =
1548 (size * GetDPIRatioForScrollbarPart(aPresContext)).Rounded();
1549 return {s, s};
1552 nscoord nsNativeBasicTheme::GetCheckboxRadioPrefSize() {
1553 return CSSPixel::ToAppUnits(10);
1556 NS_IMETHODIMP
1557 nsNativeBasicTheme::GetMinimumWidgetSize(nsPresContext* aPresContext,
1558 nsIFrame* aFrame,
1559 StyleAppearance aAppearance,
1560 LayoutDeviceIntSize* aResult,
1561 bool* aIsOverridable) {
1562 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1564 aResult->width = aResult->height = 0;
1565 *aIsOverridable = true;
1567 switch (aAppearance) {
1568 case StyleAppearance::Button:
1569 if (IsColorPickerButton(aFrame)) {
1570 aResult->height = (kMinimumColorPickerHeight * dpiRatio).Rounded();
1572 break;
1573 case StyleAppearance::RangeThumb:
1574 aResult->SizeTo((kMinimumRangeThumbSize * dpiRatio).Rounded(),
1575 (kMinimumRangeThumbSize * dpiRatio).Rounded());
1576 break;
1577 case StyleAppearance::MozMenulistArrowButton:
1578 aResult->width = (kMinimumDropdownArrowButtonWidth * dpiRatio).Rounded();
1579 break;
1580 case StyleAppearance::SpinnerUpbutton:
1581 case StyleAppearance::SpinnerDownbutton:
1582 aResult->width = (kMinimumSpinnerButtonWidth * dpiRatio).Rounded();
1583 aResult->height = (kMinimumSpinnerButtonHeight * dpiRatio).Rounded();
1584 break;
1585 case StyleAppearance::ScrollbarbuttonUp:
1586 case StyleAppearance::ScrollbarbuttonDown:
1587 case StyleAppearance::ScrollbarbuttonLeft:
1588 case StyleAppearance::ScrollbarbuttonRight:
1589 // For scrollbar-width:thin, we don't display the buttons.
1590 if (IsScrollbarWidthThin(aFrame)) {
1591 aResult->SizeTo(0, 0);
1592 break;
1594 [[fallthrough]];
1595 case StyleAppearance::ScrollbarthumbVertical:
1596 case StyleAppearance::ScrollbarthumbHorizontal:
1597 case StyleAppearance::ScrollbarVertical:
1598 case StyleAppearance::ScrollbarHorizontal:
1599 case StyleAppearance::ScrollbartrackHorizontal:
1600 case StyleAppearance::ScrollbartrackVertical:
1601 case StyleAppearance::Scrollcorner: {
1602 auto* style = nsLayoutUtils::StyleForScrollbar(aFrame);
1603 auto width = style->StyleUIReset()->mScrollbarWidth;
1604 auto sizes = GetScrollbarSizes(aPresContext, width, Overlay::No);
1605 MOZ_ASSERT(sizes.mHorizontal == sizes.mVertical);
1606 aResult->SizeTo(sizes.mHorizontal, sizes.mHorizontal);
1607 if (width != StyleScrollbarWidth::Thin) {
1608 // If the scrollbar has any buttons, then we increase the minimum
1609 // size so that they fit too.
1611 // FIXME(heycam): We should probably ensure that the thumb disappears
1612 // if a scrollbar is big enough to fit the buttons but not the thumb,
1613 // which is what the Windows native theme does. If we do that, then
1614 // the minimum size here needs to be reduced accordingly.
1615 switch (aAppearance) {
1616 case StyleAppearance::ScrollbarHorizontal:
1617 aResult->width *= GetScrollbarButtonCount() + 1;
1618 break;
1619 case StyleAppearance::ScrollbarVertical:
1620 aResult->height *= GetScrollbarButtonCount() + 1;
1621 break;
1622 default:
1623 break;
1626 break;
1628 default:
1629 break;
1632 return NS_OK;
1635 nsITheme::Transparency nsNativeBasicTheme::GetWidgetTransparency(
1636 nsIFrame* aFrame, StyleAppearance aAppearance) {
1637 return eUnknownTransparency;
1640 NS_IMETHODIMP
1641 nsNativeBasicTheme::WidgetStateChanged(nsIFrame* aFrame,
1642 StyleAppearance aAppearance,
1643 nsAtom* aAttribute, bool* aShouldRepaint,
1644 const nsAttrValue* aOldValue) {
1645 if (!aAttribute) {
1646 // Hover/focus/active changed. Always repaint.
1647 *aShouldRepaint = true;
1648 } else {
1649 // Check the attribute to see if it's relevant.
1650 // disabled, checked, dlgtype, default, etc.
1651 *aShouldRepaint = false;
1652 if ((aAttribute == nsGkAtoms::disabled) ||
1653 (aAttribute == nsGkAtoms::checked) ||
1654 (aAttribute == nsGkAtoms::selected) ||
1655 (aAttribute == nsGkAtoms::visuallyselected) ||
1656 (aAttribute == nsGkAtoms::menuactive) ||
1657 (aAttribute == nsGkAtoms::sortDirection) ||
1658 (aAttribute == nsGkAtoms::focused) ||
1659 (aAttribute == nsGkAtoms::_default) ||
1660 (aAttribute == nsGkAtoms::open) || (aAttribute == nsGkAtoms::hover)) {
1661 *aShouldRepaint = true;
1665 return NS_OK;
1668 NS_IMETHODIMP
1669 nsNativeBasicTheme::ThemeChanged() { return NS_OK; }
1671 bool nsNativeBasicTheme::WidgetAppearanceDependsOnWindowFocus(
1672 StyleAppearance aAppearance) {
1673 return IsWidgetScrollbarPart(aAppearance);
1676 nsITheme::ThemeGeometryType nsNativeBasicTheme::ThemeGeometryTypeForWidget(
1677 nsIFrame* aFrame, StyleAppearance aAppearance) {
1678 return eThemeGeometryTypeUnknown;
1681 bool nsNativeBasicTheme::ThemeSupportsWidget(nsPresContext* aPresContext,
1682 nsIFrame* aFrame,
1683 StyleAppearance aAppearance) {
1684 switch (aAppearance) {
1685 case StyleAppearance::Radio:
1686 case StyleAppearance::Checkbox:
1687 case StyleAppearance::FocusOutline:
1688 case StyleAppearance::Textarea:
1689 case StyleAppearance::Textfield:
1690 case StyleAppearance::Range:
1691 case StyleAppearance::RangeThumb:
1692 case StyleAppearance::ProgressBar:
1693 case StyleAppearance::Progresschunk:
1694 case StyleAppearance::Meter:
1695 case StyleAppearance::Meterchunk:
1696 case StyleAppearance::ScrollbarbuttonUp:
1697 case StyleAppearance::ScrollbarbuttonDown:
1698 case StyleAppearance::ScrollbarbuttonLeft:
1699 case StyleAppearance::ScrollbarbuttonRight:
1700 case StyleAppearance::ScrollbarthumbHorizontal:
1701 case StyleAppearance::ScrollbarthumbVertical:
1702 case StyleAppearance::ScrollbartrackHorizontal:
1703 case StyleAppearance::ScrollbartrackVertical:
1704 case StyleAppearance::ScrollbarHorizontal:
1705 case StyleAppearance::ScrollbarVertical:
1706 case StyleAppearance::Scrollcorner:
1707 case StyleAppearance::Button:
1708 case StyleAppearance::Listbox:
1709 case StyleAppearance::Menulist:
1710 case StyleAppearance::MenulistButton:
1711 case StyleAppearance::NumberInput:
1712 case StyleAppearance::MozMenulistArrowButton:
1713 case StyleAppearance::SpinnerUpbutton:
1714 case StyleAppearance::SpinnerDownbutton:
1715 return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
1716 default:
1717 return false;
1721 bool nsNativeBasicTheme::WidgetIsContainer(StyleAppearance aAppearance) {
1722 switch (aAppearance) {
1723 case StyleAppearance::MozMenulistArrowButton:
1724 case StyleAppearance::Radio:
1725 case StyleAppearance::Checkbox:
1726 return false;
1727 default:
1728 return true;
1732 bool nsNativeBasicTheme::ThemeDrawsFocusForWidget(StyleAppearance aAppearance) {
1733 return true;
1736 bool nsNativeBasicTheme::ThemeNeedsComboboxDropmarker() { return true; }