blob: fe7b03d4708c5c23e0ecf6d4c4805112e0ec064d [file] [log] [blame]
Yaroslav Shalivskyy80ae54e2022-09-12 20:54:171// Copyright 2022 The Chromium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ui/native_theme/native_theme_fluent.h"
6
Gastón Rodríguez56f41c42024-06-26 20:41:047#include <memory>
8
Gastón Rodríguez7db797c2024-04-01 11:11:539#include "cc/paint/paint_flags.h"
10#include "cc/paint/paint_op.h"
11#include "cc/paint/paint_record.h"
12#include "cc/paint/record_paint_canvas.h"
Kevin Lubick24166022023-11-01 17:14:0413#include "skia/ext/font_utils.h"
Yaroslav Shalivskyy80ae54e2022-09-12 20:54:1714#include "testing/gtest/include/gtest/gtest.h"
Gastón Rodríguez56f41c42024-06-26 20:41:0415#include "third_party/skia/include/core/SkColor.h"
Yaroslav Shalivskyyaad76592022-10-31 19:01:2916#include "third_party/skia/include/core/SkTypeface.h"
Gastón Rodríguez56f41c42024-06-26 20:41:0417#include "ui/color/color_id.h"
Gastón Rodríguez7db797c2024-04-01 11:11:5318#include "ui/color/color_provider.h"
Gastón Rodríguez56f41c42024-06-26 20:41:0419#include "ui/color/color_provider_utils.h"
Yaroslav Shalivskyyaad76592022-10-31 19:01:2920#include "ui/gfx/geometry/rect_conversions.h"
21#include "ui/gfx/geometry/rect_f.h"
Gastón Rodríguez56f41c42024-06-26 20:41:0422#include "ui/native_theme/native_theme.h"
Yaroslav Shalivskyy80ae54e2022-09-12 20:54:1723#include "ui/native_theme/native_theme_constants_fluent.h"
24
25namespace ui {
26
27class NativeThemeFluentTest : public ::testing::Test,
28 public ::testing::WithParamInterface<float> {
29 protected:
Yaroslav Shalivskyyaad76592022-10-31 19:01:2930 void VerifyArrowRectCommonDimensions(const gfx::RectF& arrow_rect) const {
31 EXPECT_FALSE(arrow_rect.IsEmpty());
32 EXPECT_EQ(arrow_rect.width(), arrow_rect.height());
33 EXPECT_EQ(arrow_rect.width(), std::floor(arrow_rect.width()));
Yaroslav Shalivskyy80ae54e2022-09-12 20:54:1734 }
35
Yaroslav Shalivskyyaad76592022-10-31 19:01:2936 void VerifyArrowRectIsCentered(const gfx::RectF& button_rect,
37 const gfx::RectF& arrow_rect,
38 NativeTheme::Part part) const {
39 if (part == NativeTheme::kScrollbarUpArrow ||
40 part == NativeTheme::kScrollbarDownArrow) {
41 EXPECT_EQ(button_rect.CenterPoint().x(), arrow_rect.CenterPoint().x());
42 // Due to the offset the arrow rect is shifted from the center.
43 // See NativeThemeFluent::OffsetArrowRect() for more details. Same below.
44 EXPECT_NEAR(button_rect.CenterPoint().y(), arrow_rect.CenterPoint().y(),
45 ScaleFromDIP() * 2);
46 } else {
47 EXPECT_EQ(button_rect.CenterPoint().y(), arrow_rect.CenterPoint().y());
48 EXPECT_NEAR(button_rect.CenterPoint().x(), arrow_rect.CenterPoint().x(),
49 ScaleFromDIP() * 2);
50 }
Yaroslav Shalivskyy80ae54e2022-09-12 20:54:1751 }
52
Yaroslav Shalivskyyaad76592022-10-31 19:01:2953 void VerifyArrowRectIsIntRect(const gfx::RectF& arrow_rect) const {
Peter Kasting38d41962024-12-23 00:00:5154 if (theme_.ArrowIconsAvailable()) {
Yaroslav Shalivskyyaad76592022-10-31 19:01:2955 return;
Peter Kasting38d41962024-12-23 00:00:5156 }
Yaroslav Shalivskyy80ae54e2022-09-12 20:54:1757
Yaroslav Shalivskyyaad76592022-10-31 19:01:2958 // Verify that an arrow rect with triangular arrows is an integer rect.
59 EXPECT_TRUE(IsNearestRectWithinDistance(arrow_rect, 0.01f));
Yaroslav Shalivskyy80ae54e2022-09-12 20:54:1760 }
61
Yaroslav Shalivskyyaad76592022-10-31 19:01:2962 void VerifyArrowRectLengthRatio(const gfx::RectF& button_rect,
63 const gfx::RectF& arrow_rect,
64 NativeTheme::State state) const {
65 const int smaller_button_side =
66 std::min(button_rect.width(), button_rect.height());
67 if (state == NativeTheme::kNormal) {
68 // Default state arrows are slightly bigger than the half of the button's
69 // smaller side (track thickness).
70 EXPECT_GT(arrow_rect.width(), smaller_button_side / 2.0f);
71 EXPECT_LT(arrow_rect.width(), smaller_button_side);
72 } else {
73 EXPECT_GT(arrow_rect.width(), smaller_button_side / 3.0f);
74 EXPECT_LT(arrow_rect.width(), smaller_button_side / 1.5f);
75 }
Yaroslav Shalivskyy80ae54e2022-09-12 20:54:1776 }
77
Yaroslav Shalivskyyaad76592022-10-31 19:01:2978 void VerifyArrowRect() const {
79 for (auto const& part :
80 {NativeTheme::kScrollbarUpArrow, NativeTheme::kScrollbarLeftArrow}) {
81 const gfx::RectF button_rect(ButtonRect(part));
82 for (auto const& state : {NativeTheme::kNormal, NativeTheme::kPressed}) {
83 const gfx::RectF arrow_rect =
84 theme_.GetArrowRect(ToNearestRect(button_rect), part, state);
85 VerifyArrowRectCommonDimensions(arrow_rect);
86 VerifyArrowRectIsIntRect(arrow_rect);
87 VerifyArrowRectIsCentered(button_rect, arrow_rect, part);
88 VerifyArrowRectLengthRatio(button_rect, arrow_rect, state);
89 }
90 }
91 }
92
93 gfx::RectF ButtonRect(NativeTheme::Part part) const {
94 const int button_length =
95 base::ClampFloor(kFluentScrollbarButtonSideLength * ScaleFromDIP());
96 const int track_thickness =
97 base::ClampFloor(kFluentScrollbarThickness * ScaleFromDIP());
98
99 if (part == NativeTheme::kScrollbarUpArrow ||
Peter Kasting38d41962024-12-23 00:00:51100 part == NativeTheme::kScrollbarDownArrow) {
Yaroslav Shalivskyyaad76592022-10-31 19:01:29101 return gfx::RectF(0, 0, track_thickness, button_length);
Peter Kasting38d41962024-12-23 00:00:51102 }
Yaroslav Shalivskyyaad76592022-10-31 19:01:29103
104 return gfx::RectF(0, 0, button_length, track_thickness);
Yaroslav Shalivskyy80ae54e2022-09-12 20:54:17105 }
106
107 float ScaleFromDIP() const { return GetParam(); }
Yaroslav Shalivskyyaad76592022-10-31 19:01:29108
109 // Mocks the availability of the font for drawing arrow icons.
110 void SetArrowIconsAvailable(bool enabled) {
111 if (enabled) {
Kevin Lubick24166022023-11-01 17:14:04112 theme_.typeface_ = skia::DefaultTypeface();
Yaroslav Shalivskyyaad76592022-10-31 19:01:29113 EXPECT_TRUE(theme_.ArrowIconsAvailable());
114 } else {
115 theme_.typeface_ = nullptr;
116 EXPECT_FALSE(theme_.ArrowIconsAvailable());
117 }
118 }
119
120 NativeThemeFluent theme_{false};
Yaroslav Shalivskyy80ae54e2022-09-12 20:54:17121};
122
Yaroslav Shalivskyyaad76592022-10-31 19:01:29123// Verify the dimensions of an arrow rect with triangular arrows for a given
124// button rect depending on the arrow direction and state.
125TEST_P(NativeThemeFluentTest, VerifyArrowRectWithTriangularArrows) {
126 SetArrowIconsAvailable(false);
127 VerifyArrowRect();
Yaroslav Shalivskyy80ae54e2022-09-12 20:54:17128}
129
Yaroslav Shalivskyyaad76592022-10-31 19:01:29130// Verify the dimensions of an arrow rect with arrow icons for a given
131// button rect depending on the arrow direction and state.
132TEST_P(NativeThemeFluentTest, VerifyArrowRectWithArrowIcons) {
133 SetArrowIconsAvailable(true);
134 VerifyArrowRect();
Yaroslav Shalivskyy80ae54e2022-09-12 20:54:17135}
136
Gastón Rodríguez7db797c2024-04-01 11:11:53137// Verify that the thumb paint function draws a round rectangle.
138// Generally, NativeThemeFluent::Paint* functions are covered by
139// Blink's web tests; but in web tests we render the thumbs as squares
140// instead of pill-shaped. This test ensures we don't lose coverage on the
141// PaintOp called to draw the thumb.
142TEST_F(NativeThemeFluentTest, PaintThumbRoundedCorners) {
143 cc::RecordPaintCanvas canvas;
144 ColorProvider color_provider;
Gastón Rodríguez7db797c2024-04-01 11:11:53145 constexpr gfx::Rect kRect(15, 100);
146 // `is_web_test` is `false` by default.
147 const NativeTheme::ScrollbarThumbExtraParams extra_params;
148 theme_.PaintScrollbarThumb(
149 &canvas, &color_provider,
150 /*part=*/NativeTheme::kScrollbarVerticalThumb,
151 /*state=*/NativeTheme::kNormal, kRect, extra_params,
152 /*color_scheme=*/NativeTheme::ColorScheme::kDefault);
153 EXPECT_EQ(canvas.TotalOpCount(), 1u);
154 EXPECT_EQ(canvas.ReleaseAsRecord().GetFirstOp().GetType(),
155 cc::PaintOpType::kDrawRRect);
156}
157
Gastón Rodríguez56f41c42024-06-26 20:41:04158// Verify that GetThumbColor returns the correct color given the scrollbar state
159// and extra params.
160TEST_F(NativeThemeFluentTest, GetThumbColor) {
161 const std::unique_ptr<ui::ColorProvider> color_provider =
162 CreateDefaultColorProviderForBlink(/*dark_mode=*/false);
163 NativeTheme::ScrollbarThumbExtraParams extra_params;
164 const auto to_skcolor = [&](auto id) {
165 return SkColor4f::FromColor(color_provider->GetColor(id));
166 };
167 const auto scrollbar_color = [&](auto state) {
168 return theme_.GetScrollbarThumbColor(*color_provider, state, extra_params);
169 };
170
171 const SkColor4f normal_thumb_color =
172 to_skcolor(kColorWebNativeControlScrollbarThumb);
173 const SkColor4f hovered_thumb_color =
174 to_skcolor(kColorWebNativeControlScrollbarThumbHovered);
175 const SkColor4f pressed_thumb_color =
176 to_skcolor(kColorWebNativeControlScrollbarThumbPressed);
177 const SkColor4f minimal_thumb_color =
178 to_skcolor(kColorWebNativeControlScrollbarThumbOverlayMinimalMode);
179 static constexpr SkColor css_color = SK_ColorRED;
180
181 // When there are no extra params set, the colors should be the ones that
182 // correspond to the ColorId.
183 EXPECT_EQ(normal_thumb_color, scrollbar_color(NativeTheme::kNormal));
184 EXPECT_EQ(hovered_thumb_color, scrollbar_color(NativeTheme::kHovered));
185 EXPECT_EQ(pressed_thumb_color, scrollbar_color(NativeTheme::kPressed));
186
187 // When the thumb is being painted in minimal mode, the normal state should
188 // return the minimal mode's transparent color while the other states remain
189 // unaffected.
190 extra_params.is_thumb_minimal_mode = true;
191 EXPECT_EQ(minimal_thumb_color, scrollbar_color(NativeTheme::kNormal));
192 EXPECT_EQ(hovered_thumb_color, scrollbar_color(NativeTheme::kHovered));
193 EXPECT_EQ(pressed_thumb_color, scrollbar_color(NativeTheme::kPressed));
194
Gastón Rodríguez5ca1f812025-01-22 18:13:43195 // When there is a css color set in the extra params, we modify the color
196 // when it is hovered or pressed to signal the change in state.
Gastón Rodríguez56f41c42024-06-26 20:41:04197 extra_params.thumb_color = css_color;
198 EXPECT_EQ(to_skcolor(css_color), scrollbar_color(NativeTheme::kNormal));
Gastón Rodríguez5ca1f812025-01-22 18:13:43199 EXPECT_NE(to_skcolor(css_color), scrollbar_color(NativeTheme::kHovered));
200 EXPECT_NE(to_skcolor(css_color), scrollbar_color(NativeTheme::kPressed));
Gastón Rodríguez56f41c42024-06-26 20:41:04201}
202
Yaroslav Shalivskyy80ae54e2022-09-12 20:54:17203INSTANTIATE_TEST_SUITE_P(All,
204 NativeThemeFluentTest,
205 ::testing::Values(1.f, 1.25f, 1.5f, 1.75f, 2.f));
206
207} // namespace ui