Bug 1688354 [wpt PR 27298] - Treat 'rem' as an absolute unit for font size, a=testonly
[gecko.git] / widget / nsNativeBasicTheme.cpp
blob4752fe303c9cbb76dd1e8280112194351ab7a6e4
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 "nsCSSColorUtils.h"
15 #include "nsCSSRendering.h"
16 #include "nsLayoutUtils.h"
17 #include "PathHelpers.h"
19 using namespace mozilla;
20 using namespace mozilla::widget;
21 using namespace mozilla::gfx;
23 NS_IMPL_ISUPPORTS_INHERITED(nsNativeBasicTheme, nsNativeTheme, nsITheme)
25 namespace {
27 // This pushes and pops a clip rect to the draw target.
29 // This is done to reduce fuzz in places where we may have antialiasing,
30 // because skia is not clip-invariant: given different clips, it does not
31 // guarantee the same result, even if the painted content doesn't intersect
32 // the clips.
34 // This is a bit sad, overall, but...
35 struct MOZ_RAII AutoClipRect {
36 AutoClipRect(DrawTarget& aDt, const LayoutDeviceRect& aRect) : mDt(aDt) {
37 mDt.PushClipRect(aRect.ToUnknownRect());
40 ~AutoClipRect() { mDt.PopClip(); }
42 private:
43 DrawTarget& mDt;
46 static LayoutDeviceIntCoord SnapBorderWidth(
47 CSSCoord aCssWidth, nsNativeBasicTheme::DPIRatio aDpiRatio) {
48 if (aCssWidth == 0.0f) {
49 return 0;
51 return std::max(LayoutDeviceIntCoord(1), (aCssWidth * aDpiRatio).Truncated());
54 } // namespace
56 static bool IsScrollbarWidthThin(nsIFrame* aFrame) {
57 ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
58 auto scrollbarWidth = style->StyleUIReset()->mScrollbarWidth;
59 return scrollbarWidth == StyleScrollbarWidth::Thin;
62 /* static */
63 auto nsNativeBasicTheme::GetDPIRatioForScrollbarPart(nsPresContext* aPc)
64 -> DPIRatio {
65 return DPIRatio(float(AppUnitsPerCSSPixel()) /
66 aPc->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom());
69 /* static */
70 auto nsNativeBasicTheme::GetDPIRatio(nsPresContext* aPc,
71 StyleAppearance aAppearance) -> DPIRatio {
72 // Widgets react to zoom, except scrollbars.
73 if (IsWidgetScrollbarPart(aAppearance)) {
74 return GetDPIRatioForScrollbarPart(aPc);
76 return DPIRatio(float(AppUnitsPerCSSPixel()) / aPc->AppUnitsPerDevPixel());
79 /* static */
80 auto nsNativeBasicTheme::GetDPIRatio(nsIFrame* aFrame,
81 StyleAppearance aAppearance) -> DPIRatio {
82 return GetDPIRatio(aFrame->PresContext(), aAppearance);
85 /* static */
86 bool nsNativeBasicTheme::IsDateTimeResetButton(nsIFrame* aFrame) {
87 if (!aFrame) {
88 return false;
91 nsIFrame* parent = aFrame->GetParent();
92 if (parent && (parent = parent->GetParent()) &&
93 (parent = parent->GetParent())) {
94 nsDateTimeControlFrame* dateTimeFrame = do_QueryFrame(parent);
95 if (dateTimeFrame) {
96 return true;
99 return false;
102 /* static */
103 bool nsNativeBasicTheme::IsColorPickerButton(nsIFrame* aFrame) {
104 nsColorControlFrame* colorPickerButton = do_QueryFrame(aFrame);
105 return colorPickerButton;
108 /* static */
109 LayoutDeviceRect nsNativeBasicTheme::FixAspectRatio(
110 const LayoutDeviceRect& aRect) {
111 // Checkbox and radio need to preserve aspect-ratio for compat.
112 LayoutDeviceRect rect(aRect);
113 if (rect.width == rect.height) {
114 return rect;
117 if (rect.width > rect.height) {
118 auto diff = rect.width - rect.height;
119 rect.width = rect.height;
120 rect.x += diff / 2;
121 } else {
122 auto diff = rect.height - rect.width;
123 rect.height = rect.width;
124 rect.y += diff / 2;
127 return rect;
130 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeCheckboxColors(
131 const EventStates& aState, StyleAppearance aAppearance) {
132 MOZ_ASSERT(aAppearance == StyleAppearance::Checkbox ||
133 aAppearance == StyleAppearance::Radio);
135 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
136 bool isPressed = !isDisabled && aState.HasAllStates(NS_EVENT_STATE_HOVER |
137 NS_EVENT_STATE_ACTIVE);
138 bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
139 bool isChecked = aState.HasState(NS_EVENT_STATE_CHECKED);
140 bool isIndeterminate = aAppearance == StyleAppearance::Checkbox &&
141 aState.HasState(NS_EVENT_STATE_INDETERMINATE);
143 sRGBColor backgroundColor = sColorWhite;
144 sRGBColor borderColor = sColorGrey40;
145 if (isDisabled) {
146 if (isChecked || isIndeterminate) {
147 backgroundColor = borderColor = sColorGrey40Alpha50;
148 } else {
149 backgroundColor = sColorWhiteAlpha50;
150 borderColor = sColorGrey40Alpha50;
152 } else {
153 if (isChecked || isIndeterminate) {
154 if (isPressed) {
155 backgroundColor = borderColor = sColorAccentDarker;
156 } else if (isHovered) {
157 backgroundColor = borderColor = sColorAccentDark;
158 } else {
159 backgroundColor = borderColor = sColorAccent;
161 } else if (isPressed) {
162 backgroundColor = sColorGrey20;
163 borderColor = sColorGrey60;
164 } else if (isHovered) {
165 backgroundColor = sColorWhite;
166 borderColor = sColorGrey50;
167 } else {
168 backgroundColor = sColorWhite;
169 borderColor = sColorGrey40;
173 return std::make_pair(backgroundColor, borderColor);
176 sRGBColor nsNativeBasicTheme::ComputeCheckmarkColor(const EventStates& aState) {
177 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
178 return isDisabled ? sColorWhiteAlpha50 : sColorWhite;
181 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeRadioCheckmarkColors(
182 const EventStates& aState) {
183 auto [unusedColor, checkColor] =
184 ComputeCheckboxColors(aState, StyleAppearance::Radio);
185 Unused << unusedColor;
187 return std::make_pair(sColorWhite, checkColor);
190 sRGBColor nsNativeBasicTheme::ComputeBorderColor(const EventStates& aState) {
191 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
192 bool isActive =
193 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
194 bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
195 bool isFocused = aState.HasState(NS_EVENT_STATE_FOCUSRING);
196 if (isDisabled) {
197 return sColorGrey40Alpha50;
199 if (isFocused) {
200 return sColorAccent;
202 if (isActive) {
203 return sColorGrey60;
205 if (isHovered) {
206 return sColorGrey50;
208 return sColorGrey40;
211 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeButtonColors(
212 const EventStates& aState, nsIFrame* aFrame) {
213 bool isActive =
214 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
215 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
216 bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
218 const sRGBColor& backgroundColor = [&] {
219 if (isDisabled) {
220 return sColorGrey10Alpha50;
222 if (IsDateTimeResetButton(aFrame)) {
223 return sColorWhite;
225 if (isActive) {
226 return sColorGrey30;
228 if (isHovered) {
229 return sColorGrey20;
231 return sColorGrey10;
232 }();
234 const sRGBColor borderColor = ComputeBorderColor(aState);
235 return std::make_pair(backgroundColor, borderColor);
238 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeTextfieldColors(
239 const EventStates& aState) {
240 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
241 const sRGBColor& backgroundColor =
242 isDisabled ? sColorWhiteAlpha50 : sColorWhite;
243 const sRGBColor borderColor = ComputeBorderColor(aState);
245 return std::make_pair(backgroundColor, borderColor);
248 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeRangeProgressColors(
249 const EventStates& aState) {
250 bool isActive =
251 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
252 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
253 bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
255 if (isDisabled) {
256 return std::make_pair(sColorGrey40Alpha50, sColorGrey40Alpha50);
258 if (isActive || isHovered) {
259 return std::make_pair(sColorAccentDark, sColorAccentDarker);
261 return std::make_pair(sColorAccent, sColorAccentDark);
264 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeRangeTrackColors(
265 const EventStates& aState) {
266 bool isActive =
267 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
268 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
269 bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
271 if (isDisabled) {
272 return std::make_pair(sColorGrey10Alpha50, sColorGrey40Alpha50);
274 if (isActive || isHovered) {
275 return std::make_pair(sColorGrey20, sColorGrey50);
277 return std::make_pair(sColorGrey10, sColorGrey40);
280 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeRangeThumbColors(
281 const EventStates& aState) {
282 bool isActive =
283 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
284 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
285 bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
287 const sRGBColor& backgroundColor = [&] {
288 if (isDisabled) {
289 return sColorGrey50Alpha50;
291 if (isActive) {
292 return sColorAccent;
294 if (isHovered) {
295 return sColorGrey60;
297 return sColorGrey50;
298 }();
300 const sRGBColor borderColor = sColorWhite;
302 return std::make_pair(backgroundColor, borderColor);
305 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeProgressColors() {
306 return std::make_pair(sColorAccent, sColorAccentDark);
309 std::pair<sRGBColor, sRGBColor>
310 nsNativeBasicTheme::ComputeProgressTrackColors() {
311 return std::make_pair(sColorGrey10, sColorGrey40);
314 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeMeterchunkColors(
315 const EventStates& aMeterState) {
316 sRGBColor borderColor = sColorMeterGreen20;
317 sRGBColor chunkColor = sColorMeterGreen10;
319 if (aMeterState.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
320 borderColor = sColorMeterYellow20;
321 chunkColor = sColorMeterYellow10;
322 } else if (aMeterState.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
323 borderColor = sColorMeterRed20;
324 chunkColor = sColorMeterRed10;
327 return std::make_pair(chunkColor, borderColor);
330 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeMeterTrackColors() {
331 return std::make_pair(sColorGrey10, sColorGrey40);
334 sRGBColor nsNativeBasicTheme::ComputeMenulistArrowButtonColor(
335 const EventStates& aState) {
336 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
337 return isDisabled ? sColorGrey60Alpha50 : sColorGrey60;
340 std::array<sRGBColor, 3> nsNativeBasicTheme::ComputeFocusRectColors() {
341 return {sColorAccent, sColorWhiteAlpha80, sColorAccentLight};
344 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeScrollbarColors(
345 nsIFrame* aFrame, const ComputedStyle& aStyle,
346 const EventStates& aDocumentState, bool aIsRoot) {
347 const nsStyleUI* ui = aStyle.StyleUI();
348 nscolor color;
349 if (ui->mScrollbarColor.IsColors()) {
350 color = ui->mScrollbarColor.AsColors().track.CalcColor(aStyle);
351 } else if (aDocumentState.HasAllStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
352 color = LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarInactive,
353 sScrollbarColor.ToABGR());
354 } else {
355 color = LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbar,
356 sScrollbarColor.ToABGR());
358 if (aIsRoot) {
359 // Root scrollbars must be opaque.
360 nscolor bg = LookAndFeel::GetColor(LookAndFeel::ColorID::WindowBackground,
361 NS_RGB(0xff, 0xff, 0xff));
362 color = NS_ComposeColors(bg, color);
364 return std::make_pair(gfx::sRGBColor::FromABGR(color), sScrollbarBorderColor);
367 sRGBColor nsNativeBasicTheme::ComputeScrollbarThumbColor(
368 nsIFrame* aFrame, const ComputedStyle& aStyle,
369 const EventStates& aElementState, const EventStates& aDocumentState) {
370 const nsStyleUI* ui = aStyle.StyleUI();
371 nscolor color;
372 if (ui->mScrollbarColor.IsColors()) {
373 color = ui->mScrollbarColor.AsColors().thumb.CalcColor(aStyle);
374 } else if (aDocumentState.HasAllStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
375 color = LookAndFeel::GetColor(
376 LookAndFeel::ColorID::ThemedScrollbarThumbInactive,
377 sScrollbarThumbColor.ToABGR());
378 } else if (aElementState.HasAllStates(NS_EVENT_STATE_ACTIVE)) {
379 color =
380 LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarThumbActive,
381 sScrollbarThumbColorActive.ToABGR());
382 } else if (aElementState.HasAllStates(NS_EVENT_STATE_HOVER)) {
383 color =
384 LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarThumbHover,
385 sScrollbarThumbColorHover.ToABGR());
386 } else {
387 color = LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarThumb,
388 sScrollbarThumbColor.ToABGR());
390 return gfx::sRGBColor::FromABGR(color);
393 std::array<sRGBColor, 3> nsNativeBasicTheme::ComputeScrollbarButtonColors(
394 nsIFrame* aFrame, StyleAppearance aAppearance, const ComputedStyle& aStyle,
395 const EventStates& aElementState, const EventStates& aDocumentState) {
396 bool isActive = aElementState.HasState(NS_EVENT_STATE_ACTIVE);
397 bool isHovered = aElementState.HasState(NS_EVENT_STATE_HOVER);
399 bool hasCustomColor = aStyle.StyleUI()->mScrollbarColor.IsColors();
400 sRGBColor buttonColor;
401 if (hasCustomColor) {
402 // When scrollbar-color is in use, use the thumb color for the button.
403 buttonColor = ComputeScrollbarThumbColor(aFrame, aStyle, aElementState,
404 aDocumentState);
405 } else if (isActive) {
406 buttonColor = sScrollbarButtonActiveColor;
407 } else if (!hasCustomColor && isHovered) {
408 buttonColor = sScrollbarButtonHoverColor;
409 } else {
410 buttonColor = sScrollbarColor;
413 sRGBColor arrowColor;
414 if (hasCustomColor) {
415 // When scrollbar-color is in use, derive the arrow color from the button
416 // color.
417 nscolor bg = buttonColor.ToABGR();
418 bool darken = NS_GetLuminosity(bg) >= NS_MAX_LUMINOSITY / 2;
419 if (isActive) {
420 float c = darken ? 0.0f : 1.0f;
421 arrowColor = sRGBColor(c, c, c);
422 } else {
423 uint8_t c = darken ? 0 : 255;
424 arrowColor =
425 sRGBColor::FromABGR(NS_ComposeColors(bg, NS_RGBA(c, c, c, 160)));
427 } else if (isActive) {
428 arrowColor = sScrollbarArrowColorActive;
429 } else if (isHovered) {
430 arrowColor = sScrollbarArrowColorHover;
431 } else {
432 arrowColor = sScrollbarArrowColor;
435 return {buttonColor, arrowColor, sScrollbarBorderColor};
438 static already_AddRefed<Path> GetFocusStrokePath(
439 DrawTarget* aDrawTarget, LayoutDeviceRect& aFocusRect,
440 LayoutDeviceCoord aOffset, const LayoutDeviceCoord aRadius,
441 LayoutDeviceCoord aFocusWidth) {
442 RectCornerRadii radii(aRadius, aRadius, aRadius, aRadius);
443 aFocusRect.Inflate(aOffset);
445 LayoutDeviceRect focusRect(aFocusRect);
446 // Deflate the rect by half the border width, so that the middle of the
447 // stroke fills exactly the area we want to fill and not more.
448 focusRect.Deflate(aFocusWidth * 0.5f);
450 return MakePathForRoundedRect(*aDrawTarget, focusRect.ToUnknownRect(), radii);
453 void nsNativeBasicTheme::PaintRoundedFocusRect(DrawTarget* aDrawTarget,
454 const LayoutDeviceRect& aRect,
455 DPIRatio aDpiRatio,
456 CSSCoord aRadius,
457 CSSCoord aOffset) {
458 // NOTE(emilio): If the widths or offsets here change, make sure to tweak
459 // the GetWidgetOverflow path for FocusOutline.
460 auto [innerColor, middleColor, outerColor] = ComputeFocusRectColors();
462 LayoutDeviceRect focusRect(aRect);
464 // The focus rect is painted outside of the border area (aRect), see:
466 // data:text/html,<div style="border: 1px solid; outline: 2px solid
467 // red">Foobar</div>
469 // But some controls might provide a negative offset to cover the border, if
470 // necessary.
471 LayoutDeviceCoord offset = aOffset * aDpiRatio;
472 LayoutDeviceCoord strokeWidth = CSSCoord(2.0f) * aDpiRatio;
473 focusRect.Inflate(strokeWidth);
475 LayoutDeviceCoord strokeRadius = aRadius * aDpiRatio;
476 RefPtr<Path> roundedRect = GetFocusStrokePath(aDrawTarget, focusRect, offset,
477 strokeRadius, strokeWidth);
478 aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(innerColor)),
479 StrokeOptions(strokeWidth));
481 offset = CSSCoord(1.0f) * aDpiRatio;
482 strokeRadius += offset;
483 strokeWidth = CSSCoord(1.0f) * aDpiRatio;
484 roundedRect = GetFocusStrokePath(aDrawTarget, focusRect, offset, strokeRadius,
485 strokeWidth);
486 aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(middleColor)),
487 StrokeOptions(strokeWidth));
489 offset = CSSCoord(2.0f) * aDpiRatio;
490 strokeRadius += offset;
491 strokeWidth = CSSCoord(2.0f) * aDpiRatio;
492 roundedRect = GetFocusStrokePath(aDrawTarget, focusRect, offset, strokeRadius,
493 strokeWidth);
494 aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(outerColor)),
495 StrokeOptions(strokeWidth));
498 void nsNativeBasicTheme::PaintRoundedRectWithRadius(
499 DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
500 const sRGBColor& aBackgroundColor, const sRGBColor& aBorderColor,
501 CSSCoord aBorderWidth, CSSCoord aRadius, DPIRatio aDpiRatio) {
502 const LayoutDeviceCoord borderWidth(SnapBorderWidth(aBorderWidth, aDpiRatio));
504 LayoutDeviceRect rect(aRect);
505 // Deflate the rect by half the border width, so that the middle of the
506 // stroke fills exactly the area we want to fill and not more.
507 rect.Deflate(borderWidth * 0.5f);
509 LayoutDeviceCoord radius(aRadius * aDpiRatio);
510 // Fix up the radius if it's too large with the rect we're going to paint.
512 LayoutDeviceCoord min = std::min(rect.width, rect.height);
513 if (radius * 2.0f > min) {
514 radius = min * 0.5f;
518 RectCornerRadii radii(radius, radius, radius, radius);
519 RefPtr<Path> roundedRect =
520 MakePathForRoundedRect(*aDrawTarget, rect.ToUnknownRect(), radii);
522 aDrawTarget->Fill(roundedRect, ColorPattern(ToDeviceColor(aBackgroundColor)));
523 aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(aBorderColor)),
524 StrokeOptions(borderWidth));
527 void nsNativeBasicTheme::PaintCheckboxControl(DrawTarget* aDrawTarget,
528 const LayoutDeviceRect& aRect,
529 const EventStates& aState,
530 DPIRatio aDpiRatio) {
531 const CSSCoord radius = 2.0f;
532 auto [backgroundColor, borderColor] =
533 ComputeCheckboxColors(aState, StyleAppearance::Checkbox);
534 PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
535 kCheckboxRadioBorderWidth, radius, aDpiRatio);
537 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
538 PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, 5.0f, 1.0f);
542 // Returns the right scale to cover aRect in the smaller dimension.
543 static float ScaleToWidgetRect(const LayoutDeviceRect& aRect) {
544 return std::min(aRect.width, aRect.height) / kMinimumWidgetSize;
547 void nsNativeBasicTheme::PaintCheckMark(DrawTarget* aDrawTarget,
548 const LayoutDeviceRect& aRect,
549 const EventStates& aState) {
550 // Points come from the coordinates on a 14X14 unit box centered at 0,0
551 const float checkPolygonX[] = {-4.5f, -1.5f, -0.5f, 5.0f, 4.75f,
552 3.5f, -0.5f, -1.5f, -3.5f};
553 const float checkPolygonY[] = {0.5f, 4.0f, 4.0f, -2.5f, -4.0f,
554 -4.0f, 1.0f, 1.25f, -1.0f};
555 const int32_t checkNumPoints = sizeof(checkPolygonX) / sizeof(float);
556 const float scale = ScaleToWidgetRect(aRect);
557 auto center = aRect.Center().ToUnknownPoint();
559 RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
560 Point p = center + Point(checkPolygonX[0] * scale, checkPolygonY[0] * scale);
561 builder->MoveTo(p);
562 for (int32_t i = 1; i < checkNumPoints; i++) {
563 p = center + Point(checkPolygonX[i] * scale, checkPolygonY[i] * scale);
564 builder->LineTo(p);
566 RefPtr<Path> path = builder->Finish();
568 sRGBColor fillColor = ComputeCheckmarkColor(aState);
569 aDrawTarget->Fill(path, ColorPattern(ToDeviceColor(fillColor)));
572 void nsNativeBasicTheme::PaintIndeterminateMark(DrawTarget* aDrawTarget,
573 const LayoutDeviceRect& aRect,
574 const EventStates& aState) {
575 const CSSCoord borderWidth = 2.0f;
576 const float scale = ScaleToWidgetRect(aRect);
578 Rect rect = aRect.ToUnknownRect();
579 rect.y += (rect.height / 2) - (borderWidth * scale / 2);
580 rect.height = borderWidth * scale;
581 rect.x += (borderWidth * scale) + (borderWidth * scale / 8);
582 rect.width -= ((borderWidth * scale) + (borderWidth * scale / 8)) * 2;
584 sRGBColor fillColor = ComputeCheckmarkColor(aState);
585 aDrawTarget->FillRect(rect, ColorPattern(ToDeviceColor(fillColor)));
588 void nsNativeBasicTheme::PaintStrokedEllipse(DrawTarget* aDrawTarget,
589 const LayoutDeviceRect& aRect,
590 const sRGBColor& aBackgroundColor,
591 const sRGBColor& aBorderColor,
592 const CSSCoord aBorderWidth,
593 DPIRatio aDpiRatio) {
594 const LayoutDeviceCoord borderWidth(aBorderWidth * aDpiRatio);
595 RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
597 // Deflate for the same reason as PaintRoundedRectWithRadius. Note that the
598 // size is the diameter, so we just shrink by the border width once.
599 auto size = aRect.Size() - LayoutDeviceSize(borderWidth, borderWidth);
600 AppendEllipseToPath(builder, aRect.Center().ToUnknownPoint(),
601 size.ToUnknownSize());
602 RefPtr<Path> ellipse = builder->Finish();
604 aDrawTarget->Fill(ellipse, ColorPattern(ToDeviceColor(aBackgroundColor)));
605 aDrawTarget->Stroke(ellipse, ColorPattern(ToDeviceColor(aBorderColor)),
606 StrokeOptions(borderWidth));
609 void nsNativeBasicTheme::PaintEllipseShadow(DrawTarget* aDrawTarget,
610 const LayoutDeviceRect& aRect,
611 float aShadowAlpha,
612 const CSSPoint& aShadowOffset,
613 CSSCoord aShadowBlurStdDev,
614 DPIRatio aDpiRatio) {
615 Float stdDev = aShadowBlurStdDev * aDpiRatio;
616 Point offset = (aShadowOffset * aDpiRatio).ToUnknownPoint();
618 RefPtr<FilterNode> blurFilter =
619 aDrawTarget->CreateFilter(FilterType::GAUSSIAN_BLUR);
620 if (!blurFilter) {
621 return;
624 blurFilter->SetAttribute(ATT_GAUSSIAN_BLUR_STD_DEVIATION, stdDev);
626 IntSize inflation =
627 gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(stdDev, stdDev));
628 Rect inflatedRect = aRect.ToUnknownRect();
629 inflatedRect.Inflate(inflation.width, inflation.height);
630 Rect sourceRectInFilterSpace =
631 inflatedRect - aRect.TopLeft().ToUnknownPoint();
632 Point destinationPointOfSourceRect = inflatedRect.TopLeft() + offset;
634 IntSize dtSize = RoundedToInt(aRect.Size().ToUnknownSize());
635 RefPtr<DrawTarget> ellipseDT = aDrawTarget->CreateSimilarDrawTargetForFilter(
636 dtSize, SurfaceFormat::A8, blurFilter, blurFilter,
637 sourceRectInFilterSpace, destinationPointOfSourceRect);
638 if (!ellipseDT) {
639 return;
642 RefPtr<Path> ellipse = MakePathForEllipse(
643 *ellipseDT, (aRect - aRect.TopLeft()).Center().ToUnknownPoint(),
644 aRect.Size().ToUnknownSize());
645 ellipseDT->Fill(ellipse,
646 ColorPattern(DeviceColor(0.0f, 0.0f, 0.0f, aShadowAlpha)));
647 RefPtr<SourceSurface> ellipseSurface = ellipseDT->Snapshot();
649 blurFilter->SetInput(IN_GAUSSIAN_BLUR_IN, ellipseSurface);
650 aDrawTarget->DrawFilter(blurFilter, sourceRectInFilterSpace,
651 destinationPointOfSourceRect);
654 void nsNativeBasicTheme::PaintRadioControl(DrawTarget* aDrawTarget,
655 const LayoutDeviceRect& aRect,
656 const EventStates& aState,
657 DPIRatio aDpiRatio) {
658 const CSSCoord borderWidth = 2.0f;
659 auto [backgroundColor, borderColor] =
660 ComputeCheckboxColors(aState, StyleAppearance::Radio);
662 PaintStrokedEllipse(aDrawTarget, aRect, backgroundColor, borderColor,
663 borderWidth, aDpiRatio);
665 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
666 PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, 5.0f, 1.0f);
670 void nsNativeBasicTheme::PaintRadioCheckmark(DrawTarget* aDrawTarget,
671 const LayoutDeviceRect& aRect,
672 const EventStates& aState,
673 DPIRatio aDpiRatio) {
674 const CSSCoord borderWidth = 2.0f;
675 const float scale = ScaleToWidgetRect(aRect);
676 auto [backgroundColor, checkColor] = ComputeRadioCheckmarkColors(aState);
678 LayoutDeviceRect rect(aRect);
679 rect.Deflate(borderWidth * scale);
681 PaintStrokedEllipse(aDrawTarget, rect, checkColor, backgroundColor,
682 borderWidth, aDpiRatio);
685 void nsNativeBasicTheme::PaintTextField(DrawTarget* aDrawTarget,
686 const LayoutDeviceRect& aRect,
687 const EventStates& aState,
688 DPIRatio aDpiRatio) {
689 auto [backgroundColor, borderColor] = ComputeTextfieldColors(aState);
691 const CSSCoord radius = 2.0f;
693 PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
694 kTextFieldBorderWidth, radius, aDpiRatio);
696 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
697 PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius,
698 -kTextFieldBorderWidth);
702 void nsNativeBasicTheme::PaintListbox(DrawTarget* aDrawTarget,
703 const LayoutDeviceRect& aRect,
704 const EventStates& aState,
705 DPIRatio aDpiRatio) {
706 const CSSCoord radius = 2.0f;
707 auto [backgroundColor, borderColor] = ComputeTextfieldColors(aState);
709 PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
710 kMenulistBorderWidth, radius, aDpiRatio);
712 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
713 PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius,
714 -kMenulistBorderWidth);
718 void nsNativeBasicTheme::PaintMenulist(DrawTarget* aDrawTarget,
719 const LayoutDeviceRect& aRect,
720 const EventStates& aState,
721 DPIRatio aDpiRatio) {
722 const CSSCoord radius = 4.0f;
723 auto [backgroundColor, borderColor] = ComputeButtonColors(aState);
725 PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
726 kMenulistBorderWidth, radius, aDpiRatio);
728 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
729 PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius,
730 -kMenulistBorderWidth);
734 void nsNativeBasicTheme::PaintArrow(DrawTarget* aDrawTarget,
735 const LayoutDeviceRect& aRect,
736 const float aArrowPolygonX[],
737 const float aArrowPolygonY[],
738 const int32_t aArrowNumPoints,
739 const sRGBColor aFillColor) {
740 const float scale = ScaleToWidgetRect(aRect);
742 auto center = aRect.Center().ToUnknownPoint();
744 RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
745 Point p =
746 center + Point(aArrowPolygonX[0] * scale, aArrowPolygonY[0] * scale);
747 builder->MoveTo(p);
748 for (int32_t i = 1; i < aArrowNumPoints; i++) {
749 p = center + Point(aArrowPolygonX[i] * scale, aArrowPolygonY[i] * scale);
750 builder->LineTo(p);
752 RefPtr<Path> path = builder->Finish();
754 aDrawTarget->Fill(path, ColorPattern(ToDeviceColor(aFillColor)));
757 void nsNativeBasicTheme::PaintMenulistArrowButton(nsIFrame* aFrame,
758 DrawTarget* aDrawTarget,
759 const LayoutDeviceRect& aRect,
760 const EventStates& aState) {
761 const float arrowPolygonX[] = {-3.5f, -0.5f, 0.5f, 3.5f, 3.5f,
762 3.0f, 0.5f, -0.5f, -3.0f, -3.5f};
763 const float arrowPolygonY[] = {-0.5f, 2.5f, 2.5f, -0.5f, -2.0f,
764 -2.0f, 1.0f, 1.0f, -2.0f, -2.0f};
765 const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
766 sRGBColor arrowColor = ComputeMenulistArrowButtonColor(aState);
767 PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints,
768 arrowColor);
771 void nsNativeBasicTheme::PaintSpinnerButton(nsIFrame* aFrame,
772 DrawTarget* aDrawTarget,
773 const LayoutDeviceRect& aRect,
774 const EventStates& aState,
775 StyleAppearance aAppearance,
776 DPIRatio aDpiRatio) {
777 auto [backgroundColor, borderColor] = ComputeButtonColors(aState);
779 aDrawTarget->FillRect(aRect.ToUnknownRect(),
780 ColorPattern(ToDeviceColor(backgroundColor)));
782 const float arrowPolygonX[] = {-5.25f, -0.75f, 0.75f, 5.25f, 5.25f,
783 4.5f, 0.75f, -0.75f, -4.5f, -5.25f};
784 const float arrowPolygonY[] = {-1.875f, 2.625f, 2.625f, -1.875f, -4.125f,
785 -4.125f, 0.375f, 0.375f, -4.125f, -4.125f};
786 const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
787 const float scaleX = ScaleToWidgetRect(aRect);
788 const float scaleY =
789 aAppearance == StyleAppearance::SpinnerDownbutton ? scaleX : -scaleX;
791 RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
792 auto center = aRect.Center().ToUnknownPoint();
793 Point p =
794 center + Point(arrowPolygonX[0] * scaleX, arrowPolygonY[0] * scaleY);
795 builder->MoveTo(p);
796 for (int32_t i = 1; i < arrowNumPoints; i++) {
797 p = center + Point(arrowPolygonX[i] * scaleX, arrowPolygonY[i] * scaleY);
798 builder->LineTo(p);
800 RefPtr<Path> path = builder->Finish();
801 aDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
804 void nsNativeBasicTheme::PaintRange(nsIFrame* aFrame, DrawTarget* aDrawTarget,
805 const LayoutDeviceRect& aRect,
806 const EventStates& aState,
807 DPIRatio aDpiRatio, bool aHorizontal) {
808 nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
809 if (!rangeFrame) {
810 return;
813 double progress = rangeFrame->GetValueAsFractionOfRange();
814 auto rect = aRect;
815 LayoutDeviceRect thumbRect(0, 0, kMinimumRangeThumbSize * aDpiRatio,
816 kMinimumRangeThumbSize * aDpiRatio);
817 Rect overflowRect = aRect.ToUnknownRect();
818 overflowRect.Inflate(CSSCoord(6.0f) * aDpiRatio); // See GetWidgetOverflow
819 Rect progressClipRect(overflowRect);
820 Rect trackClipRect(overflowRect);
821 const LayoutDeviceCoord verticalSize = kRangeHeight * aDpiRatio;
822 if (aHorizontal) {
823 rect.height = verticalSize;
824 rect.y = aRect.y + (aRect.height - rect.height) / 2;
825 thumbRect.y = aRect.y + (aRect.height - thumbRect.height) / 2;
827 if (IsFrameRTL(aFrame)) {
828 thumbRect.x =
829 aRect.x + (aRect.width - thumbRect.width) * (1.0 - progress);
830 float midPoint = thumbRect.Center().X();
831 trackClipRect.SetBoxX(overflowRect.X(), midPoint);
832 progressClipRect.SetBoxX(midPoint, overflowRect.XMost());
833 } else {
834 thumbRect.x = aRect.x + (aRect.width - thumbRect.width) * progress;
835 float midPoint = thumbRect.Center().X();
836 progressClipRect.SetBoxX(overflowRect.X(), midPoint);
837 trackClipRect.SetBoxX(midPoint, overflowRect.XMost());
839 } else {
840 rect.width = verticalSize;
841 rect.x = aRect.x + (aRect.width - rect.width) / 2;
842 thumbRect.x = aRect.x + (aRect.width - thumbRect.width) / 2;
844 thumbRect.y = aRect.y + (aRect.height - thumbRect.height) * progress;
845 float midPoint = thumbRect.Center().Y();
846 trackClipRect.SetBoxY(overflowRect.Y(), midPoint);
847 progressClipRect.SetBoxY(midPoint, overflowRect.YMost());
850 const CSSCoord borderWidth = 1.0f;
851 const CSSCoord radius = 2.0f;
853 auto [progressColor, progressBorderColor] =
854 ComputeRangeProgressColors(aState);
855 auto [trackColor, trackBorderColor] = ComputeRangeTrackColors(aState);
857 // Make a path that clips out the range thumb.
858 RefPtr<PathBuilder> builder =
859 aDrawTarget->CreatePathBuilder(FillRule::FILL_EVEN_ODD);
860 AppendRectToPath(builder, overflowRect);
861 AppendEllipseToPath(builder, thumbRect.Center().ToUnknownPoint(),
862 thumbRect.Size().ToUnknownSize());
863 RefPtr<Path> path = builder->Finish();
865 // Draw the progress and track pieces with the thumb clipped out, so that
866 // they're not visible behind the thumb even if the thumb is partially
867 // transparent (which is the case in the disabled state).
868 aDrawTarget->PushClip(path);
870 aDrawTarget->PushClipRect(progressClipRect);
871 PaintRoundedRectWithRadius(aDrawTarget, rect, progressColor,
872 progressBorderColor, borderWidth, radius,
873 aDpiRatio);
874 aDrawTarget->PopClip();
876 aDrawTarget->PushClipRect(trackClipRect);
877 PaintRoundedRectWithRadius(aDrawTarget, rect, trackColor, trackBorderColor,
878 borderWidth, radius, aDpiRatio);
879 aDrawTarget->PopClip();
881 if (!aState.HasState(NS_EVENT_STATE_DISABLED)) {
882 // Thumb shadow
883 PaintEllipseShadow(aDrawTarget, thumbRect, 0.3f, CSSPoint(0.0f, 2.0f),
884 2.0f, aDpiRatio);
887 aDrawTarget->PopClip();
889 // Draw the thumb on top.
890 const CSSCoord thumbBorderWidth = 2.0f;
891 auto [thumbColor, thumbBorderColor] = ComputeRangeThumbColors(aState);
893 PaintStrokedEllipse(aDrawTarget, thumbRect, thumbColor, thumbBorderColor,
894 thumbBorderWidth, aDpiRatio);
896 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
897 PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius, 1.0f);
901 // TODO: Vertical.
902 // TODO: Indeterminate state.
903 void nsNativeBasicTheme::PaintProgress(
904 nsIFrame* aFrame, DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
905 const EventStates& aState, DPIRatio aDpiRatio, bool aIsMeter, bool aBar) {
906 auto [backgroundColor, borderColor] = [&] {
907 if (aIsMeter) {
908 return aBar ? ComputeMeterTrackColors() : ComputeMeterchunkColors(aState);
910 return aBar ? ComputeProgressTrackColors() : ComputeProgressColors();
911 }();
913 const CSSCoord borderWidth = 1.0f;
914 const CSSCoord radius = aIsMeter ? 5.0f : 2.0f;
916 // Center it vertically.
917 LayoutDeviceRect rect(aRect);
918 const LayoutDeviceCoord height =
919 (aIsMeter ? kMeterHeight : kProgressbarHeight) * aDpiRatio;
920 rect.y += (rect.height - height) / 2;
921 rect.height = height;
923 // This is the progress chunk, clip it to the right amount.
924 if (!aBar) {
925 double position = [&] {
926 if (aIsMeter) {
927 auto* meter = dom::HTMLMeterElement::FromNode(aFrame->GetContent());
928 if (!meter) {
929 return 0.0;
931 return meter->Value() / meter->Max();
933 auto* progress = dom::HTMLProgressElement::FromNode(aFrame->GetContent());
934 if (!progress) {
935 return 0.0;
937 return progress->Value() / progress->Max();
938 }();
939 LayoutDeviceRect clipRect = rect;
940 double clipWidth = rect.width * position;
941 clipRect.width = clipWidth;
942 if (IsFrameRTL(aFrame)) {
943 clipRect.x += rect.width - clipWidth;
945 aDrawTarget->PushClipRect(clipRect.ToUnknownRect());
948 PaintRoundedRectWithRadius(aDrawTarget, rect, backgroundColor, borderColor,
949 borderWidth, radius, aDpiRatio);
951 if (!aBar) {
952 aDrawTarget->PopClip();
956 void nsNativeBasicTheme::PaintButton(nsIFrame* aFrame, DrawTarget* aDrawTarget,
957 const LayoutDeviceRect& aRect,
958 const EventStates& aState,
959 DPIRatio aDpiRatio) {
960 const CSSCoord radius = 4.0f;
961 auto [backgroundColor, borderColor] = ComputeButtonColors(aState, aFrame);
963 PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
964 kButtonBorderWidth, radius, aDpiRatio);
966 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
967 PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius,
968 -kButtonBorderWidth);
972 void nsNativeBasicTheme::PaintScrollbarThumb(DrawTarget* aDrawTarget,
973 const LayoutDeviceRect& aRect,
974 bool aHorizontal, nsIFrame* aFrame,
975 const ComputedStyle& aStyle,
976 const EventStates& aElementState,
977 const EventStates& aDocumentState,
978 DPIRatio aDpiRatio) {
979 sRGBColor thumbColor =
980 ComputeScrollbarThumbColor(aFrame, aStyle, aElementState, aDocumentState);
982 aDrawTarget->FillRect(aRect.ToUnknownRect(),
983 ColorPattern(ToDeviceColor(thumbColor)));
986 void nsNativeBasicTheme::PaintScrollbarTrack(DrawTarget* aDrawTarget,
987 const LayoutDeviceRect& aRect,
988 bool aHorizontal, nsIFrame* aFrame,
989 const ComputedStyle& aStyle,
990 const EventStates& aDocumentState,
991 DPIRatio aDpiRatio, bool aIsRoot) {
992 // Draw nothing by default. Subclasses can override this.
995 void nsNativeBasicTheme::PaintScrollbar(DrawTarget* aDrawTarget,
996 const LayoutDeviceRect& aRect,
997 bool aHorizontal, nsIFrame* aFrame,
998 const ComputedStyle& aStyle,
999 const EventStates& aDocumentState,
1000 DPIRatio aDpiRatio, bool aIsRoot) {
1001 auto [scrollbarColor, borderColor] =
1002 ComputeScrollbarColors(aFrame, aStyle, aDocumentState, aIsRoot);
1003 aDrawTarget->FillRect(aRect.ToUnknownRect(),
1004 ColorPattern(ToDeviceColor(scrollbarColor)));
1005 // FIXME(heycam): We should probably derive the border color when custom
1006 // scrollbar colors are in use too. But for now, just skip painting it,
1007 // to avoid ugliness.
1008 if (aStyle.StyleUI()->mScrollbarColor.IsAuto()) {
1009 RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
1010 LayoutDeviceRect strokeRect(aRect);
1011 strokeRect.Deflate(CSSCoord(0.5f) * aDpiRatio);
1012 builder->MoveTo(strokeRect.TopLeft().ToUnknownPoint());
1013 builder->LineTo(
1014 (aHorizontal ? strokeRect.TopRight() : strokeRect.BottomLeft())
1015 .ToUnknownPoint());
1016 RefPtr<Path> path = builder->Finish();
1017 aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(borderColor)),
1018 StrokeOptions(CSSCoord(1.0f) * aDpiRatio));
1022 void nsNativeBasicTheme::PaintScrollCorner(DrawTarget* aDrawTarget,
1023 const LayoutDeviceRect& aRect,
1024 nsIFrame* aFrame,
1025 const ComputedStyle& aStyle,
1026 const EventStates& aDocumentState,
1027 DPIRatio aDpiRatio, bool aIsRoot) {
1028 auto [scrollbarColor, borderColor] =
1029 ComputeScrollbarColors(aFrame, aStyle, aDocumentState, aIsRoot);
1030 Unused << borderColor;
1031 aDrawTarget->FillRect(aRect.ToUnknownRect(),
1032 ColorPattern(ToDeviceColor(scrollbarColor)));
1035 void nsNativeBasicTheme::PaintScrollbarButton(
1036 DrawTarget* aDrawTarget, StyleAppearance aAppearance,
1037 const LayoutDeviceRect& aRect, nsIFrame* aFrame,
1038 const ComputedStyle& aStyle, const EventStates& aElementState,
1039 const EventStates& aDocumentState, DPIRatio aDpiRatio) {
1040 bool hasCustomColor = aStyle.StyleUI()->mScrollbarColor.IsColors();
1041 auto [buttonColor, arrowColor, borderColor] = ComputeScrollbarButtonColors(
1042 aFrame, aAppearance, aStyle, aElementState, aDocumentState);
1043 aDrawTarget->FillRect(aRect.ToUnknownRect(),
1044 ColorPattern(ToDeviceColor(buttonColor)));
1046 // Start with Up arrow.
1047 float arrowPolygonX[] = {-3.0f, 0.0f, 3.0f, 3.0f, 0.0f, -3.0f};
1048 float arrowPolygonY[] = {0.4f, -3.1f, 0.4f, 2.7f, -0.8f, 2.7f};
1050 const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
1051 switch (aAppearance) {
1052 case StyleAppearance::ScrollbarbuttonUp:
1053 break;
1054 case StyleAppearance::ScrollbarbuttonDown:
1055 for (int32_t i = 0; i < arrowNumPoints; i++) {
1056 arrowPolygonY[i] *= -1;
1058 break;
1059 case StyleAppearance::ScrollbarbuttonLeft:
1060 for (int32_t i = 0; i < arrowNumPoints; i++) {
1061 int32_t temp = arrowPolygonX[i];
1062 arrowPolygonX[i] = arrowPolygonY[i];
1063 arrowPolygonY[i] = temp;
1065 break;
1066 case StyleAppearance::ScrollbarbuttonRight:
1067 for (int32_t i = 0; i < arrowNumPoints; i++) {
1068 int32_t temp = arrowPolygonX[i];
1069 arrowPolygonX[i] = arrowPolygonY[i] * -1;
1070 arrowPolygonY[i] = temp;
1072 break;
1073 default:
1074 return;
1076 PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints,
1077 arrowColor);
1079 // FIXME(heycam): We should probably derive the border color when custom
1080 // scrollbar colors are in use too. But for now, just skip painting it,
1081 // to avoid ugliness.
1082 if (!hasCustomColor) {
1083 RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
1084 builder->MoveTo(Point(aRect.x, aRect.y));
1085 if (aAppearance == StyleAppearance::ScrollbarbuttonUp ||
1086 aAppearance == StyleAppearance::ScrollbarbuttonDown) {
1087 builder->LineTo(Point(aRect.x, aRect.y + aRect.height));
1088 } else {
1089 builder->LineTo(Point(aRect.x + aRect.width, aRect.y));
1092 RefPtr<Path> path = builder->Finish();
1093 aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(borderColor)),
1094 StrokeOptions(CSSCoord(1.0f) * aDpiRatio));
1098 // Checks whether the frame is for a root <scrollbar> or <scrollcorner>, which
1099 // influences some platforms' scrollbar rendering.
1100 bool nsNativeBasicTheme::IsRootScrollbar(nsIFrame* aFrame) {
1101 return CheckBooleanAttr(aFrame, nsGkAtoms::root_) &&
1102 aFrame->PresContext()->IsRootContentDocument() &&
1103 aFrame->GetContent() &&
1104 aFrame->GetContent()->IsInNamespace(kNameSpaceID_XUL);
1107 NS_IMETHODIMP
1108 nsNativeBasicTheme::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
1109 StyleAppearance aAppearance,
1110 const nsRect& aRect,
1111 const nsRect& /* aDirtyRect */) {
1112 DrawTarget* dt = aContext->GetDrawTarget();
1113 const nscoord twipsPerPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
1114 EventStates eventState = GetContentState(aFrame, aAppearance);
1115 EventStates docState = aFrame->GetContent()->OwnerDoc()->GetDocumentState();
1116 auto devPxRect = LayoutDeviceRect::FromUnknownRect(
1117 NSRectToSnappedRect(aRect, twipsPerPixel, *dt));
1119 if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
1120 bool isHTML = IsHTMLContent(aFrame);
1121 nsIFrame* parentFrame = aFrame->GetParent();
1122 bool isMenulist = !isHTML && parentFrame->IsMenuFrame();
1123 // HTML select and XUL menulist dropdown buttons get state from the
1124 // parent.
1125 if (isHTML || isMenulist) {
1126 aFrame = parentFrame;
1127 eventState = GetContentState(parentFrame, aAppearance);
1131 // Hack to avoid skia fuzziness: Add a dummy clip if the widget doesn't
1132 // overflow devPxRect.
1133 Maybe<AutoClipRect> maybeClipRect;
1134 if (aAppearance != StyleAppearance::FocusOutline &&
1135 aAppearance != StyleAppearance::Range &&
1136 !eventState.HasState(NS_EVENT_STATE_FOCUSRING)) {
1137 maybeClipRect.emplace(*dt, devPxRect);
1140 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1142 switch (aAppearance) {
1143 case StyleAppearance::Radio: {
1144 auto rect = FixAspectRatio(devPxRect);
1145 PaintRadioControl(dt, rect, eventState, dpiRatio);
1146 if (IsSelected(aFrame)) {
1147 PaintRadioCheckmark(dt, rect, eventState, dpiRatio);
1149 break;
1151 case StyleAppearance::Checkbox: {
1152 auto rect = FixAspectRatio(devPxRect);
1153 PaintCheckboxControl(dt, rect, eventState, dpiRatio);
1154 if (GetIndeterminate(aFrame)) {
1155 PaintIndeterminateMark(dt, rect, eventState);
1156 } else if (IsChecked(aFrame)) {
1157 PaintCheckMark(dt, rect, eventState);
1159 break;
1161 case StyleAppearance::Textarea:
1162 case StyleAppearance::Textfield:
1163 case StyleAppearance::NumberInput:
1164 PaintTextField(dt, devPxRect, eventState, dpiRatio);
1165 break;
1166 case StyleAppearance::Listbox:
1167 PaintListbox(dt, devPxRect, eventState, dpiRatio);
1168 break;
1169 case StyleAppearance::MenulistButton:
1170 case StyleAppearance::Menulist:
1171 PaintMenulist(dt, devPxRect, eventState, dpiRatio);
1172 break;
1173 case StyleAppearance::MozMenulistArrowButton:
1174 PaintMenulistArrowButton(aFrame, dt, devPxRect, eventState);
1175 break;
1176 case StyleAppearance::SpinnerUpbutton:
1177 case StyleAppearance::SpinnerDownbutton:
1178 PaintSpinnerButton(aFrame, dt, devPxRect, eventState, aAppearance,
1179 dpiRatio);
1180 break;
1181 case StyleAppearance::Range:
1182 PaintRange(aFrame, dt, devPxRect, eventState, dpiRatio,
1183 IsRangeHorizontal(aFrame));
1184 break;
1185 case StyleAppearance::RangeThumb:
1186 // Painted as part of StyleAppearance::Range.
1187 break;
1188 case StyleAppearance::ProgressBar:
1189 PaintProgress(aFrame, dt, devPxRect, eventState, dpiRatio,
1190 /* aMeter = */ false, /* aBar = */ true);
1191 break;
1192 case StyleAppearance::Progresschunk:
1193 if (nsProgressFrame* f = do_QueryFrame(aFrame->GetParent())) {
1194 PaintProgress(f, dt, devPxRect, f->GetContent()->AsElement()->State(),
1195 dpiRatio, /* aMeter = */ false, /* aBar = */ false);
1197 break;
1198 case StyleAppearance::Meter:
1199 PaintProgress(aFrame, dt, devPxRect, eventState, dpiRatio,
1200 /* aMeter = */ true, /* aBar = */ true);
1201 break;
1202 case StyleAppearance::Meterchunk:
1203 if (nsMeterFrame* f = do_QueryFrame(aFrame->GetParent())) {
1204 PaintProgress(f, dt, devPxRect, f->GetContent()->AsElement()->State(),
1205 dpiRatio, /* aMeter = */ true, /* aBar = */ false);
1207 break;
1208 case StyleAppearance::ScrollbarthumbHorizontal:
1209 case StyleAppearance::ScrollbarthumbVertical: {
1210 bool isHorizontal =
1211 aAppearance == StyleAppearance::ScrollbarthumbHorizontal;
1212 PaintScrollbarThumb(dt, devPxRect, isHorizontal, aFrame,
1213 *nsLayoutUtils::StyleForScrollbar(aFrame), eventState,
1214 docState, dpiRatio);
1215 break;
1217 case StyleAppearance::ScrollbartrackHorizontal:
1218 case StyleAppearance::ScrollbartrackVertical: {
1219 bool isHorizontal =
1220 aAppearance == StyleAppearance::ScrollbartrackHorizontal;
1221 PaintScrollbarTrack(dt, devPxRect, isHorizontal, aFrame,
1222 *nsLayoutUtils::StyleForScrollbar(aFrame), docState,
1223 dpiRatio, IsRootScrollbar(aFrame));
1224 break;
1226 case StyleAppearance::ScrollbarHorizontal:
1227 case StyleAppearance::ScrollbarVertical: {
1228 bool isHorizontal = aAppearance == StyleAppearance::ScrollbarHorizontal;
1229 PaintScrollbar(dt, devPxRect, isHorizontal, aFrame,
1230 *nsLayoutUtils::StyleForScrollbar(aFrame), docState,
1231 dpiRatio, IsRootScrollbar(aFrame));
1232 break;
1234 case StyleAppearance::Scrollcorner:
1235 PaintScrollCorner(dt, devPxRect, aFrame,
1236 *nsLayoutUtils::StyleForScrollbar(aFrame), docState,
1237 dpiRatio, IsRootScrollbar(aFrame));
1238 break;
1239 case StyleAppearance::ScrollbarbuttonUp:
1240 case StyleAppearance::ScrollbarbuttonDown:
1241 case StyleAppearance::ScrollbarbuttonLeft:
1242 case StyleAppearance::ScrollbarbuttonRight:
1243 // For scrollbar-width:thin, we don't display the buttons.
1244 if (!IsScrollbarWidthThin(aFrame)) {
1245 PaintScrollbarButton(dt, aAppearance, devPxRect, aFrame,
1246 *nsLayoutUtils::StyleForScrollbar(aFrame),
1247 eventState, docState, dpiRatio);
1249 break;
1250 case StyleAppearance::Button:
1251 PaintButton(aFrame, dt, devPxRect, eventState, dpiRatio);
1252 break;
1253 case StyleAppearance::FocusOutline:
1254 // TODO(emilio): Consider supporting outline-radius / outline-offset?
1255 PaintRoundedFocusRect(dt, devPxRect, dpiRatio, 0.0f, 0.0f);
1256 break;
1257 default:
1258 // Various appearance values are used for XUL elements. Normally these
1259 // will not be available in content documents (and thus in the content
1260 // processes where the native basic theme can be used), but tests are
1261 // run with the remote XUL pref enabled and so we can get in here. So
1262 // we just return an error rather than assert.
1263 return NS_ERROR_NOT_IMPLEMENTED;
1266 return NS_OK;
1269 /*bool
1270 nsNativeBasicTheme::CreateWebRenderCommandsForWidget(mozilla::wr::DisplayListBuilder&
1271 aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources, const
1272 mozilla::layers::StackingContextHelper& aSc,
1273 mozilla::layers::RenderRootStateManager*
1274 aManager, nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect)
1278 LayoutDeviceIntMargin nsNativeBasicTheme::GetWidgetBorder(
1279 nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
1280 switch (aAppearance) {
1281 case StyleAppearance::Textfield:
1282 case StyleAppearance::Textarea:
1283 case StyleAppearance::NumberInput:
1284 case StyleAppearance::Listbox:
1285 case StyleAppearance::Menulist:
1286 case StyleAppearance::MenulistButton:
1287 case StyleAppearance::Button:
1288 // Return the border size from the UA sheet, even though what we paint
1289 // doesn't actually match that. We know this is the UA sheet border
1290 // because we disable native theming when different border widths are
1291 // specified by authors, see nsNativeBasicTheme::IsWidgetStyled.
1293 // The Rounded() bit is technically redundant, but needed to appease the
1294 // type system, we should always end up with full device pixels due to
1295 // round_border_to_device_pixels at style time.
1296 return LayoutDeviceIntMargin::FromAppUnits(
1297 aFrame->StyleBorder()->GetComputedBorder(),
1298 aFrame->PresContext()->AppUnitsPerDevPixel())
1299 .Rounded();
1300 case StyleAppearance::Checkbox:
1301 case StyleAppearance::Radio: {
1302 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1303 LayoutDeviceIntCoord w =
1304 SnapBorderWidth(kCheckboxRadioBorderWidth, dpiRatio);
1305 return LayoutDeviceIntMargin(w, w, w, w);
1307 default:
1308 return LayoutDeviceIntMargin();
1312 bool nsNativeBasicTheme::GetWidgetPadding(nsDeviceContext* aContext,
1313 nsIFrame* aFrame,
1314 StyleAppearance aAppearance,
1315 LayoutDeviceIntMargin* aResult) {
1316 switch (aAppearance) {
1317 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
1318 // and have a meaningful baseline, so they can't have
1319 // author-specified padding.
1320 case StyleAppearance::Radio:
1321 case StyleAppearance::Checkbox:
1322 aResult->SizeTo(0, 0, 0, 0);
1323 return true;
1324 default:
1325 break;
1327 return false;
1330 static int GetScrollbarButtonCount() {
1331 int32_t buttons = LookAndFeel::GetInt(LookAndFeel::IntID::ScrollArrowStyle);
1332 return CountPopulation32(static_cast<uint32_t>(buttons));
1335 bool nsNativeBasicTheme::GetWidgetOverflow(nsDeviceContext* aContext,
1336 nsIFrame* aFrame,
1337 StyleAppearance aAppearance,
1338 nsRect* aOverflowRect) {
1339 nsIntMargin overflow;
1340 switch (aAppearance) {
1341 case StyleAppearance::FocusOutline:
1342 // 2px * each of the segments + 1 px for the separation between them.
1343 overflow.SizeTo(5, 5, 5, 5);
1344 break;
1345 case StyleAppearance::Radio:
1346 case StyleAppearance::Checkbox:
1347 case StyleAppearance::Range:
1348 overflow.SizeTo(6, 6, 6, 6);
1349 break;
1350 case StyleAppearance::Textarea:
1351 case StyleAppearance::Textfield:
1352 case StyleAppearance::NumberInput:
1353 case StyleAppearance::Listbox:
1354 case StyleAppearance::MenulistButton:
1355 case StyleAppearance::Menulist:
1356 case StyleAppearance::Button:
1357 // 2px for each segment, plus 1px separation, but we paint 1px inside
1358 // the border area so 4px overflow.
1359 overflow.SizeTo(4, 4, 4, 4);
1360 break;
1361 default:
1362 return false;
1365 // TODO: This should convert from device pixels to app units, not from CSS
1366 // pixels. And it should take the dpi ratio into account.
1367 // Using CSS pixels can cause the overflow to be too small if the page is
1368 // zoomed out.
1369 aOverflowRect->Inflate(nsMargin(CSSPixel::ToAppUnits(overflow.top),
1370 CSSPixel::ToAppUnits(overflow.right),
1371 CSSPixel::ToAppUnits(overflow.bottom),
1372 CSSPixel::ToAppUnits(overflow.left)));
1374 return true;
1377 auto nsNativeBasicTheme::GetScrollbarSizes(nsPresContext* aPresContext,
1378 StyleScrollbarWidth aWidth, Overlay)
1379 -> ScrollbarSizes {
1380 CSSCoord size = aWidth == StyleScrollbarWidth::Thin
1381 ? kMinimumThinScrollbarSize
1382 : kMinimumScrollbarSize;
1383 LayoutDeviceIntCoord s =
1384 (size * GetDPIRatioForScrollbarPart(aPresContext)).Rounded();
1385 return {s, s};
1388 NS_IMETHODIMP
1389 nsNativeBasicTheme::GetMinimumWidgetSize(nsPresContext* aPresContext,
1390 nsIFrame* aFrame,
1391 StyleAppearance aAppearance,
1392 LayoutDeviceIntSize* aResult,
1393 bool* aIsOverridable) {
1394 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1396 aResult->width = aResult->height = (kMinimumWidgetSize * dpiRatio).Rounded();
1398 switch (aAppearance) {
1399 case StyleAppearance::Button:
1400 if (IsColorPickerButton(aFrame)) {
1401 aResult->height = (kMinimumColorPickerHeight * dpiRatio).Rounded();
1403 break;
1404 case StyleAppearance::RangeThumb:
1405 aResult->SizeTo((kMinimumRangeThumbSize * dpiRatio).Rounded(),
1406 (kMinimumRangeThumbSize * dpiRatio).Rounded());
1407 break;
1408 case StyleAppearance::MozMenulistArrowButton:
1409 aResult->width = (kMinimumDropdownArrowButtonWidth * dpiRatio).Rounded();
1410 break;
1411 case StyleAppearance::SpinnerUpbutton:
1412 case StyleAppearance::SpinnerDownbutton:
1413 aResult->width = (kMinimumSpinnerButtonWidth * dpiRatio).Rounded();
1414 aResult->height = (kMinimumSpinnerButtonHeight * dpiRatio).Rounded();
1415 break;
1416 case StyleAppearance::ScrollbarbuttonUp:
1417 case StyleAppearance::ScrollbarbuttonDown:
1418 case StyleAppearance::ScrollbarbuttonLeft:
1419 case StyleAppearance::ScrollbarbuttonRight:
1420 // For scrollbar-width:thin, we don't display the buttons.
1421 if (IsScrollbarWidthThin(aFrame)) {
1422 aResult->SizeTo(0, 0);
1423 break;
1425 [[fallthrough]];
1426 case StyleAppearance::ScrollbarthumbVertical:
1427 case StyleAppearance::ScrollbarthumbHorizontal:
1428 case StyleAppearance::ScrollbarVertical:
1429 case StyleAppearance::ScrollbarHorizontal:
1430 case StyleAppearance::ScrollbartrackHorizontal:
1431 case StyleAppearance::ScrollbartrackVertical:
1432 case StyleAppearance::Scrollcorner: {
1433 auto* style = nsLayoutUtils::StyleForScrollbar(aFrame);
1434 auto width = style->StyleUIReset()->mScrollbarWidth;
1435 auto sizes = GetScrollbarSizes(aPresContext, width, Overlay::No);
1436 MOZ_ASSERT(sizes.mHorizontal == sizes.mVertical);
1437 aResult->SizeTo(sizes.mHorizontal, sizes.mHorizontal);
1438 if (width != StyleScrollbarWidth::Thin) {
1439 // If the scrollbar has any buttons, then we increase the minimum
1440 // size so that they fit too.
1442 // FIXME(heycam): We should probably ensure that the thumb disappears
1443 // if a scrollbar is big enough to fit the buttons but not the thumb,
1444 // which is what the Windows native theme does. If we do that, then
1445 // the minimum size here needs to be reduced accordingly.
1446 switch (aAppearance) {
1447 case StyleAppearance::ScrollbarHorizontal:
1448 aResult->width *= GetScrollbarButtonCount() + 1;
1449 break;
1450 case StyleAppearance::ScrollbarVertical:
1451 aResult->height *= GetScrollbarButtonCount() + 1;
1452 break;
1453 default:
1454 break;
1457 break;
1459 default:
1460 break;
1463 *aIsOverridable = true;
1464 return NS_OK;
1467 nsITheme::Transparency nsNativeBasicTheme::GetWidgetTransparency(
1468 nsIFrame* aFrame, StyleAppearance aAppearance) {
1469 return eUnknownTransparency;
1472 NS_IMETHODIMP
1473 nsNativeBasicTheme::WidgetStateChanged(nsIFrame* aFrame,
1474 StyleAppearance aAppearance,
1475 nsAtom* aAttribute, bool* aShouldRepaint,
1476 const nsAttrValue* aOldValue) {
1477 if (!aAttribute) {
1478 // Hover/focus/active changed. Always repaint.
1479 *aShouldRepaint = true;
1480 } else {
1481 // Check the attribute to see if it's relevant.
1482 // disabled, checked, dlgtype, default, etc.
1483 *aShouldRepaint = false;
1484 if ((aAttribute == nsGkAtoms::disabled) ||
1485 (aAttribute == nsGkAtoms::checked) ||
1486 (aAttribute == nsGkAtoms::selected) ||
1487 (aAttribute == nsGkAtoms::visuallyselected) ||
1488 (aAttribute == nsGkAtoms::menuactive) ||
1489 (aAttribute == nsGkAtoms::sortDirection) ||
1490 (aAttribute == nsGkAtoms::focused) ||
1491 (aAttribute == nsGkAtoms::_default) ||
1492 (aAttribute == nsGkAtoms::open) || (aAttribute == nsGkAtoms::hover)) {
1493 *aShouldRepaint = true;
1497 return NS_OK;
1500 NS_IMETHODIMP
1501 nsNativeBasicTheme::ThemeChanged() { return NS_OK; }
1503 bool nsNativeBasicTheme::WidgetAppearanceDependsOnWindowFocus(
1504 StyleAppearance aAppearance) {
1505 return IsWidgetScrollbarPart(aAppearance);
1508 nsITheme::ThemeGeometryType nsNativeBasicTheme::ThemeGeometryTypeForWidget(
1509 nsIFrame* aFrame, StyleAppearance aAppearance) {
1510 return eThemeGeometryTypeUnknown;
1513 bool nsNativeBasicTheme::ThemeSupportsWidget(nsPresContext* aPresContext,
1514 nsIFrame* aFrame,
1515 StyleAppearance aAppearance) {
1516 switch (aAppearance) {
1517 case StyleAppearance::Radio:
1518 case StyleAppearance::Checkbox:
1519 case StyleAppearance::FocusOutline:
1520 case StyleAppearance::Textarea:
1521 case StyleAppearance::Textfield:
1522 case StyleAppearance::Range:
1523 case StyleAppearance::RangeThumb:
1524 case StyleAppearance::ProgressBar:
1525 case StyleAppearance::Progresschunk:
1526 case StyleAppearance::Meter:
1527 case StyleAppearance::Meterchunk:
1528 case StyleAppearance::ScrollbarbuttonUp:
1529 case StyleAppearance::ScrollbarbuttonDown:
1530 case StyleAppearance::ScrollbarbuttonLeft:
1531 case StyleAppearance::ScrollbarbuttonRight:
1532 case StyleAppearance::ScrollbarthumbHorizontal:
1533 case StyleAppearance::ScrollbarthumbVertical:
1534 case StyleAppearance::ScrollbartrackHorizontal:
1535 case StyleAppearance::ScrollbartrackVertical:
1536 case StyleAppearance::ScrollbarHorizontal:
1537 case StyleAppearance::ScrollbarVertical:
1538 case StyleAppearance::Scrollcorner:
1539 case StyleAppearance::Button:
1540 case StyleAppearance::Listbox:
1541 case StyleAppearance::Menulist:
1542 case StyleAppearance::Menuitem:
1543 case StyleAppearance::Menuitemtext:
1544 case StyleAppearance::MenulistText:
1545 case StyleAppearance::MenulistButton:
1546 case StyleAppearance::NumberInput:
1547 case StyleAppearance::MozMenulistArrowButton:
1548 case StyleAppearance::SpinnerUpbutton:
1549 case StyleAppearance::SpinnerDownbutton:
1550 return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
1551 default:
1552 return false;
1556 bool nsNativeBasicTheme::WidgetIsContainer(StyleAppearance aAppearance) {
1557 switch (aAppearance) {
1558 case StyleAppearance::MozMenulistArrowButton:
1559 case StyleAppearance::Radio:
1560 case StyleAppearance::Checkbox:
1561 return false;
1562 default:
1563 return true;
1567 bool nsNativeBasicTheme::ThemeDrawsFocusForWidget(StyleAppearance aAppearance) {
1568 return true;
1571 bool nsNativeBasicTheme::ThemeNeedsComboboxDropmarker() { return true; }