1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
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_win.h"
6
7 #include <windows.h>
8 #include <stddef.h>
9 #include <uxtheme.h>
10 #include <vsstyle.h>
11 #include <vssym32.h>
12
13 #include "base/bind.h"
14 #include "base/command_line.h"
15 #include "base/logging.h"
16 #include "base/optional.h"
17 #include "base/stl_util.h"
18 #include "base/threading/sequenced_task_runner_handle.h"
19 #include "base/win/scoped_gdi_object.h"
20 #include "base/win/scoped_hdc.h"
21 #include "base/win/scoped_select_object.h"
22 #include "base/win/win_util.h"
23 #include "cc/paint/paint_canvas.h"
24 #include "cc/paint/paint_flags.h"
25 #include "skia/ext/platform_canvas.h"
26 #include "skia/ext/skia_utils_win.h"
27 #include "third_party/skia/include/core/SkCanvas.h"
28 #include "third_party/skia/include/core/SkColor.h"
29 #include "third_party/skia/include/core/SkColorPriv.h"
30 #include "third_party/skia/include/core/SkPath.h"
31 #include "third_party/skia/include/core/SkRefCnt.h"
32 #include "third_party/skia/include/core/SkShader.h"
33 #include "third_party/skia/include/core/SkSurface.h"
34 #include "ui/base/ui_base_features.h"
35 #include "ui/base/ui_base_switches.h"
36 #include "ui/display/win/screen_win.h"
37 #include "ui/gfx/color_palette.h"
38 #include "ui/gfx/color_utils.h"
39 #include "ui/gfx/gdi_util.h"
40 #include "ui/gfx/geometry/rect.h"
41 #include "ui/gfx/geometry/rect_conversions.h"
42 #include "ui/gfx/skia_util.h"
43 #include "ui/native_theme/common_theme.h"
44
45 // This was removed from Winvers.h but is still used.
46 #if !defined(COLOR_MENUHIGHLIGHT)
47 #define COLOR_MENUHIGHLIGHT 29
48 #endif
49
50 namespace {
51
52 // Windows system color IDs cached and updated by the native theme.
53 const int kSysColors[] = {
54 COLOR_BTNFACE, COLOR_BTNTEXT, COLOR_GRAYTEXT, COLOR_HIGHLIGHT,
55 COLOR_HIGHLIGHTTEXT, COLOR_HOTLIGHT, COLOR_MENUHIGHLIGHT, COLOR_SCROLLBAR,
56 COLOR_WINDOW, COLOR_WINDOWTEXT,
57 };
58
SetCheckerboardShader(SkPaint * paint,const RECT & align_rect)59 void SetCheckerboardShader(SkPaint* paint, const RECT& align_rect) {
60 // Create a 2x2 checkerboard pattern using the 3D face and highlight colors.
61 const SkColor face = color_utils::GetSysSkColor(COLOR_3DFACE);
62 const SkColor highlight = color_utils::GetSysSkColor(COLOR_3DHILIGHT);
63 SkColor buffer[] = { face, highlight, highlight, face };
64 // Confusing bit: we first create a temporary bitmap with our desired pattern,
65 // then copy it to another bitmap. The temporary bitmap doesn't take
66 // ownership of the pixel data, and so will point to garbage when this
67 // function returns. The copy will copy the pixel data into a place owned by
68 // the bitmap, which is in turn owned by the shader, etc., so it will live
69 // until we're done using it.
70 SkImageInfo info = SkImageInfo::MakeN32Premul(2, 2);
71 SkBitmap temp_bitmap;
72 temp_bitmap.installPixels(info, buffer, info.minRowBytes());
73 SkBitmap bitmap;
74 if (bitmap.tryAllocPixels(info))
75 temp_bitmap.readPixels(info, bitmap.getPixels(), bitmap.rowBytes(), 0, 0);
76
77 // Align the pattern with the upper corner of |align_rect|.
78 SkMatrix local_matrix;
79 local_matrix.setTranslate(SkIntToScalar(align_rect.left),
80 SkIntToScalar(align_rect.top));
81 paint->setShader(bitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat,
82 &local_matrix));
83 }
84
85 // <-a->
86 // [ ***** ]
87 // ____ | |
88 // <-a-> <------b----->
89 // a: object_width
90 // b: frame_width
91 // *: animating object
92 //
93 // - the animation goes from "[" to "]" repeatedly.
94 // - the animation offset is at first "|"
95 //
ComputeAnimationProgress(int frame_width,int object_width,int pixels_per_second,double animated_seconds)96 int ComputeAnimationProgress(int frame_width,
97 int object_width,
98 int pixels_per_second,
99 double animated_seconds) {
100 int animation_width = frame_width + object_width;
101 double interval = static_cast<double>(animation_width) / pixels_per_second;
102 double ratio = fmod(animated_seconds, interval) / interval;
103 return static_cast<int>(animation_width * ratio) - object_width;
104 }
105
106 // Custom scoped object for storing DC and a bitmap that was selected into it,
107 // and making sure that they are deleted in the right order.
108 class ScopedCreateDCWithBitmap {
109 public:
ScopedCreateDCWithBitmap(base::win::ScopedCreateDC::Handle hdc)110 explicit ScopedCreateDCWithBitmap(base::win::ScopedCreateDC::Handle hdc)
111 : dc_(hdc) {}
112
~ScopedCreateDCWithBitmap()113 ~ScopedCreateDCWithBitmap() {
114 // Delete DC before the bitmap, since objects should not be deleted while
115 // selected into a DC.
116 dc_.Close();
117 }
118
IsValid() const119 bool IsValid() const { return dc_.IsValid(); }
120
Get() const121 base::win::ScopedCreateDC::Handle Get() const { return dc_.Get(); }
122
123 // Selects |handle| to bitmap into DC. Returns false if handle is not valid.
SelectBitmap(base::win::ScopedBitmap::element_type handle)124 bool SelectBitmap(base::win::ScopedBitmap::element_type handle) {
125 bitmap_.reset(handle);
126 if (!bitmap_.is_valid())
127 return false;
128
129 SelectObject(dc_.Get(), bitmap_.get());
130 return true;
131 }
132
133 private:
134 base::win::ScopedCreateDC dc_;
135 base::win::ScopedBitmap bitmap_;
136
137 DISALLOW_COPY_AND_ASSIGN(ScopedCreateDCWithBitmap);
138 };
139
OpenThemeRegKey(REGSAM access)140 base::win::RegKey OpenThemeRegKey(REGSAM access) {
141 base::win::RegKey hkcu_themes_regkey;
142 hkcu_themes_regkey.Open(HKEY_CURRENT_USER,
143 L"Software\\Microsoft\\Windows\\CurrentVersion\\"
144 L"Themes\\Personalize",
145 access);
146 return hkcu_themes_regkey;
147 }
148
149 } // namespace
150
151 namespace ui {
152
SysColorToSystemThemeColor(int system_color)153 NativeTheme::SystemThemeColor SysColorToSystemThemeColor(int system_color) {
154 switch (system_color) {
155 case COLOR_BTNFACE:
156 return NativeTheme::SystemThemeColor::kButtonFace;
157 case COLOR_BTNTEXT:
158 return NativeTheme::SystemThemeColor::kButtonText;
159 case COLOR_GRAYTEXT:
160 return NativeTheme::SystemThemeColor::kGrayText;
161 case COLOR_HIGHLIGHT:
162 return NativeTheme::SystemThemeColor::kHighlight;
163 case COLOR_HIGHLIGHTTEXT:
164 return NativeTheme::SystemThemeColor::kHighlightText;
165 case COLOR_HOTLIGHT:
166 return NativeTheme::SystemThemeColor::kHotlight;
167 case COLOR_MENUHIGHLIGHT:
168 return NativeTheme::SystemThemeColor::kMenuHighlight;
169 case COLOR_SCROLLBAR:
170 return NativeTheme::SystemThemeColor::kScrollbar;
171 case COLOR_WINDOW:
172 return NativeTheme::SystemThemeColor::kWindow;
173 case COLOR_WINDOWTEXT:
174 return NativeTheme::SystemThemeColor::kWindowText;
175 default:
176 return NativeTheme::SystemThemeColor::kNotSupported;
177 }
178 }
179
GetInstanceForNativeUi()180 NativeTheme* NativeTheme::GetInstanceForNativeUi() {
181 static base::NoDestructor<NativeThemeWin> s_native_theme(true, false);
182 return s_native_theme.get();
183 }
184
GetInstanceForDarkUI()185 NativeTheme* NativeTheme::GetInstanceForDarkUI() {
186 static base::NoDestructor<NativeThemeWin> s_dark_native_theme(false, true);
187 return s_dark_native_theme.get();
188 }
189
190 // static
SystemDarkModeSupported()191 bool NativeTheme::SystemDarkModeSupported() {
192 static bool system_supports_dark_mode =
193 ([]() { return OpenThemeRegKey(KEY_READ).Valid(); })();
194 return system_supports_dark_mode;
195 }
196
197 // static
CloseHandles()198 void NativeThemeWin::CloseHandles() {
199 static_cast<NativeThemeWin*>(NativeTheme::GetInstanceForNativeUi())
200 ->CloseHandlesInternal();
201 }
202
GetPartSize(Part part,State state,const ExtraParams & extra) const203 gfx::Size NativeThemeWin::GetPartSize(Part part,
204 State state,
205 const ExtraParams& extra) const {
206 // The GetThemePartSize call below returns the default size without
207 // accounting for user customization (crbug/218291).
208 switch (part) {
209 case kScrollbarDownArrow:
210 case kScrollbarLeftArrow:
211 case kScrollbarRightArrow:
212 case kScrollbarUpArrow:
213 case kScrollbarHorizontalThumb:
214 case kScrollbarVerticalThumb:
215 case kScrollbarHorizontalTrack:
216 case kScrollbarVerticalTrack: {
217 int size = display::win::ScreenWin::GetSystemMetricsInDIP(SM_CXVSCROLL);
218 if (size == 0)
219 size = 17;
220 return gfx::Size(size, size);
221 }
222 default:
223 break;
224 }
225
226 int part_id = GetWindowsPart(part, state, extra);
227 int state_id = GetWindowsState(part, state, extra);
228
229 base::win::ScopedGetDC screen_dc(nullptr);
230 SIZE size;
231 HANDLE handle = GetThemeHandle(GetThemeName(part));
232 if (handle && SUCCEEDED(GetThemePartSize(handle, screen_dc, part_id, state_id,
233 nullptr, TS_TRUE, &size)))
234 return gfx::Size(size.cx, size.cy);
235
236 // TODO(rogerta): For now, we need to support radio buttons and checkboxes
237 // when theming is not enabled. Support for other parts can be added
238 // if/when needed.
239 return (part == kCheckbox || part == kRadio) ?
240 gfx::Size(13, 13) : gfx::Size();
241 }
242
Paint(cc::PaintCanvas * canvas,Part part,State state,const gfx::Rect & rect,const ExtraParams & extra,ColorScheme color_scheme) const243 void NativeThemeWin::Paint(cc::PaintCanvas* canvas,
244 Part part,
245 State state,
246 const gfx::Rect& rect,
247 const ExtraParams& extra,
248 ColorScheme color_scheme) const {
249 if (rect.IsEmpty())
250 return;
251
252 switch (part) {
253 case kMenuPopupGutter:
254 PaintMenuGutter(canvas, rect, color_scheme);
255 return;
256 case kMenuPopupSeparator:
257 PaintMenuSeparator(canvas, extra.menu_separator, color_scheme);
258 return;
259 case kMenuPopupBackground:
260 PaintMenuBackground(canvas, rect, color_scheme);
261 return;
262 case kMenuItemBackground:
263 CommonThemePaintMenuItemBackground(this, canvas, state, rect,
264 extra.menu_item, color_scheme);
265 return;
266 default:
267 PaintIndirect(canvas, part, state, rect, extra);
268 return;
269 }
270 }
271
NativeThemeWin(bool configure_web_instance,bool should_only_use_dark_colors)272 NativeThemeWin::NativeThemeWin(bool configure_web_instance,
273 bool should_only_use_dark_colors)
274 : NativeTheme(should_only_use_dark_colors), color_change_listener_(this) {
275 // If there's no sequenced task runner handle, we can't be called back for
276 // dark mode changes. This generally happens in tests. As a result, ignore
277 // dark mode in this case.
278 if (!should_only_use_dark_colors && !IsForcedDarkMode() &&
279 !IsForcedHighContrast() && base::SequencedTaskRunnerHandle::IsSet()) {
280 // Dark Mode currently targets UWP apps, which means Win32 apps need to use
281 // alternate, less reliable means of detecting the state. The following
282 // can break in future Windows versions.
283 hkcu_themes_regkey_ = OpenThemeRegKey(KEY_READ | KEY_NOTIFY);
284 if (hkcu_themes_regkey_.Valid()) {
285 UpdateDarkModeStatus();
286 RegisterThemeRegkeyObserver();
287 }
288 }
289 if (!IsForcedHighContrast()) {
290 set_high_contrast(IsUsingHighContrastThemeInternal());
291 }
292 // Initialize the cached system colors.
293 UpdateSystemColors();
294 set_preferred_color_scheme(CalculatePreferredColorScheme());
295
296 memset(theme_handles_, 0, sizeof(theme_handles_));
297
298 if (configure_web_instance)
299 ConfigureWebInstance();
300 }
301
ConfigureWebInstance()302 void NativeThemeWin::ConfigureWebInstance() {
303 if (!IsForcedDarkMode() && !IsForcedHighContrast() &&
304 base::SequencedTaskRunnerHandle::IsSet()) {
305 // Add the web native theme as an observer to stay in sync with dark mode,
306 // high contrast, and preferred color scheme changes.
307 color_scheme_observer_ =
308 std::make_unique<NativeTheme::ColorSchemeNativeThemeObserver>(
309 NativeTheme::GetInstanceForWeb());
310 AddObserver(color_scheme_observer_.get());
311 }
312
313 // Initialize the native theme web instance with the system color info.
314 NativeTheme* web_instance = NativeTheme::GetInstanceForWeb();
315 web_instance->set_use_dark_colors(ShouldUseDarkColors());
316 web_instance->set_high_contrast(UsesHighContrastColors());
317 web_instance->set_preferred_color_scheme(GetPreferredColorScheme());
318 web_instance->set_system_colors(GetSystemColors());
319 }
320
~NativeThemeWin()321 NativeThemeWin::~NativeThemeWin() {
322 // TODO(https://crbug.com/787692): Calling CloseHandles() here breaks
323 // certain tests and the reliability bots.
324 // CloseHandles();
325 }
326
IsUsingHighContrastThemeInternal() const327 bool NativeThemeWin::IsUsingHighContrastThemeInternal() const {
328 HIGHCONTRAST result;
329 result.cbSize = sizeof(HIGHCONTRAST);
330 return SystemParametersInfo(SPI_GETHIGHCONTRAST, result.cbSize, &result, 0) &&
331 (result.dwFlags & HCF_HIGHCONTRASTON) == HCF_HIGHCONTRASTON;
332 }
333
CloseHandlesInternal()334 void NativeThemeWin::CloseHandlesInternal() {
335 for (int i = 0; i < LAST; ++i) {
336 if (theme_handles_[i]) {
337 CloseThemeData(theme_handles_[i]);
338 theme_handles_[i] = nullptr;
339 }
340 }
341 }
342
OnSysColorChange()343 void NativeThemeWin::OnSysColorChange() {
344 UpdateSystemColors();
345 if (!IsForcedHighContrast())
346 set_high_contrast(IsUsingHighContrastThemeInternal());
347 set_preferred_color_scheme(CalculatePreferredColorScheme());
348 NotifyObservers();
349 }
350
UpdateSystemColors()351 void NativeThemeWin::UpdateSystemColors() {
352 for (int sys_color : kSysColors)
353 system_colors_[SysColorToSystemThemeColor(sys_color)] =
354 color_utils::GetSysSkColor(sys_color);
355 }
356
PaintMenuSeparator(cc::PaintCanvas * canvas,const MenuSeparatorExtraParams & params,ColorScheme color_scheme) const357 void NativeThemeWin::PaintMenuSeparator(cc::PaintCanvas* canvas,
358 const MenuSeparatorExtraParams& params,
359 ColorScheme color_scheme) const {
360 const gfx::RectF rect(*params.paint_rect);
361 gfx::PointF start = rect.CenterPoint();
362 gfx::PointF end = start;
363 if (params.type == ui::VERTICAL_SEPARATOR) {
364 start.set_y(rect.y());
365 end.set_y(rect.bottom());
366 } else {
367 start.set_x(rect.x());
368 end.set_x(rect.right());
369 }
370
371 cc::PaintFlags flags;
372 flags.setColor(
373 GetSystemColor(NativeTheme::kColorId_MenuSeparatorColor, color_scheme));
374 canvas->drawLine(start.x(), start.y(), end.x(), end.y(), flags);
375 }
376
PaintMenuGutter(cc::PaintCanvas * canvas,const gfx::Rect & rect,ColorScheme color_scheme) const377 void NativeThemeWin::PaintMenuGutter(cc::PaintCanvas* canvas,
378 const gfx::Rect& rect,
379 ColorScheme color_scheme) const {
380 cc::PaintFlags flags;
381 flags.setColor(
382 GetSystemColor(NativeTheme::kColorId_MenuSeparatorColor, color_scheme));
383 int position_x = rect.x() + rect.width() / 2;
384 canvas->drawLine(position_x, rect.y(), position_x, rect.bottom(), flags);
385 }
386
PaintMenuBackground(cc::PaintCanvas * canvas,const gfx::Rect & rect,ColorScheme color_scheme) const387 void NativeThemeWin::PaintMenuBackground(cc::PaintCanvas* canvas,
388 const gfx::Rect& rect,
389 ColorScheme color_scheme) const {
390 cc::PaintFlags flags;
391 flags.setColor(
392 GetSystemColor(NativeTheme::kColorId_MenuBackgroundColor, color_scheme));
393 canvas->drawRect(gfx::RectToSkRect(rect), flags);
394 }
395
PaintDirect(SkCanvas * destination_canvas,HDC hdc,Part part,State state,const gfx::Rect & rect,const ExtraParams & extra) const396 void NativeThemeWin::PaintDirect(SkCanvas* destination_canvas,
397 HDC hdc,
398 Part part,
399 State state,
400 const gfx::Rect& rect,
401 const ExtraParams& extra) const {
402 if (part == kScrollbarCorner) {
403 // Special-cased here since there is no theme name for kScrollbarCorner.
404 destination_canvas->drawColor(SK_ColorWHITE, SkBlendMode::kSrc);
405 return;
406 }
407
408 RECT rect_win = rect.ToRECT();
409 if (part == kTrackbarTrack) {
410 // Make the channel be 4 px thick in the center of the supplied rect. (4 px
411 // matches what XP does in various menus; GetThemePartSize() doesn't seem to
412 // return good values here.)
413 constexpr int kChannelThickness = 4;
414 if (extra.trackbar.vertical) {
415 rect_win.top += (rect_win.bottom - rect_win.top - kChannelThickness) / 2;
416 rect_win.bottom = rect_win.top + kChannelThickness;
417 } else {
418 rect_win.left += (rect_win.right - rect_win.left - kChannelThickness) / 2;
419 rect_win.right = rect_win.left + kChannelThickness;
420 }
421 }
422
423 // Most parts can be drawn simply when there is a theme handle.
424 const HANDLE handle = GetThemeHandle(GetThemeName(part));
425 const int part_id = GetWindowsPart(part, state, extra);
426 const int state_id = GetWindowsState(part, state, extra);
427 if (handle) {
428 switch (part) {
429 case kMenuPopupArrow:
430 // The right-pointing arrow can use the common code, but the
431 // left-pointing one needs custom code.
432 if (!extra.menu_arrow.pointing_right) {
433 PaintLeftMenuArrowThemed(hdc, handle, part_id, state_id, rect);
434 return;
435 }
436 FALLTHROUGH;
437 case kCheckbox:
438 case kInnerSpinButton:
439 case kMenuCheck:
440 case kMenuCheckBackground:
441 case kMenuList:
442 case kProgressBar:
443 case kPushButton:
444 case kRadio:
445 case kScrollbarHorizontalTrack:
446 case kScrollbarVerticalTrack:
447 case kTabPanelBackground:
448 case kTrackbarThumb:
449 case kTrackbarTrack:
450 case kWindowResizeGripper:
451 DrawThemeBackground(handle, hdc, part_id, state_id, &rect_win, nullptr);
452 if (part == kProgressBar)
453 break; // Further painting to do below.
454 return;
455 case kScrollbarDownArrow:
456 case kScrollbarHorizontalGripper:
457 case kScrollbarHorizontalThumb:
458 case kScrollbarLeftArrow:
459 case kScrollbarRightArrow:
460 case kScrollbarUpArrow:
461 case kScrollbarVerticalGripper:
462 case kScrollbarVerticalThumb:
463 PaintScaledTheme(handle, hdc, part_id, state_id, rect);
464 return;
465 case kTextField:
466 break; // Handled entirely below.
467 case kMenuItemBackground:
468 case kMenuPopupBackground:
469 case kMenuPopupGutter:
470 case kMenuPopupSeparator:
471 case kScrollbarCorner:
472 case kSliderTrack:
473 case kSliderThumb:
474 case kMaxPart:
475 NOTREACHED();
476 }
477 }
478
479 // Do any further painting the common code couldn't handle.
480 switch (part) {
481 case kCheckbox:
482 case kPushButton:
483 case kRadio:
484 PaintButtonClassic(hdc, part, state, &rect_win, extra.button);
485 return;
486 case kInnerSpinButton:
487 DrawFrameControl(hdc, &rect_win, DFC_SCROLL,
488 extra.inner_spin.classic_state);
489 return;
490 case kMenuCheck:
491 PaintFrameControl(
492 hdc, rect, DFC_MENU,
493 extra.menu_check.is_radio ? DFCS_MENUBULLET : DFCS_MENUCHECK,
494 extra.menu_check.is_selected, state);
495 return;
496 case kMenuList:
497 DrawFrameControl(hdc, &rect_win, DFC_SCROLL,
498 DFCS_SCROLLCOMBOBOX | extra.menu_list.classic_state);
499 return;
500 case kMenuPopupArrow:
501 // For some reason, Windows uses the name DFCS_MENUARROWRIGHT to indicate
502 // a left pointing arrow.
503 PaintFrameControl(hdc, rect, DFC_MENU,
504 extra.menu_arrow.pointing_right ? DFCS_MENUARROW
505 : DFCS_MENUARROWRIGHT,
506 extra.menu_arrow.is_selected, state);
507 return;
508 case kProgressBar: {
509 RECT value_rect = gfx::Rect(extra.progress_bar.value_rect_x,
510 extra.progress_bar.value_rect_y,
511 extra.progress_bar.value_rect_width,
512 extra.progress_bar.value_rect_height)
513 .ToRECT();
514 if (handle) {
515 PaintProgressBarOverlayThemed(hdc, handle, &rect_win, &value_rect,
516 extra.progress_bar);
517 } else {
518 FillRect(hdc, &rect_win, GetSysColorBrush(COLOR_BTNFACE));
519 FillRect(hdc, &value_rect, GetSysColorBrush(COLOR_BTNSHADOW));
520 DrawEdge(hdc, &rect_win, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
521 }
522 return;
523 }
524 case kScrollbarDownArrow:
525 case kScrollbarLeftArrow:
526 case kScrollbarRightArrow:
527 case kScrollbarUpArrow:
528 PaintScrollbarArrowClassic(hdc, part, state, &rect_win);
529 return;
530 case kScrollbarHorizontalThumb:
531 case kScrollbarVerticalThumb:
532 DrawEdge(hdc, &rect_win, EDGE_RAISED, BF_RECT | BF_MIDDLE);
533 return;
534 case kScrollbarHorizontalTrack:
535 case kScrollbarVerticalTrack:
536 PaintScrollbarTrackClassic(destination_canvas, hdc, &rect_win,
537 extra.scrollbar_track);
538 return;
539 case kTabPanelBackground:
540 // Classic just renders a flat color background.
541 FillRect(hdc, &rect_win, reinterpret_cast<HBRUSH>(COLOR_3DFACE + 1));
542 return;
543 case kTextField: {
544 // TODO(mpcomplete): can we detect if the color is specified by the user,
545 // and if not, just use the system color?
546 // CreateSolidBrush() accepts a RGB value but alpha must be 0.
547 base::win::ScopedGDIObject<HBRUSH> bg_brush(CreateSolidBrush(
548 skia::SkColorToCOLORREF(extra.text_field.background_color)));
549 if (handle) {
550 PaintTextFieldThemed(hdc, handle, bg_brush.get(), part_id, state_id,
551 &rect_win, extra.text_field);
552 } else {
553 PaintTextFieldClassic(hdc, bg_brush.get(), &rect_win, extra.text_field);
554 }
555 return;
556 }
557 case kTrackbarThumb:
558 if (extra.trackbar.vertical) {
559 DrawEdge(hdc, &rect_win, EDGE_RAISED, BF_RECT | BF_SOFT | BF_MIDDLE);
560 } else {
561 PaintHorizontalTrackbarThumbClassic(destination_canvas, hdc, rect_win,
562 extra.trackbar);
563 }
564 return;
565 case kTrackbarTrack:
566 DrawEdge(hdc, &rect_win, EDGE_SUNKEN, BF_RECT);
567 return;
568 case kWindowResizeGripper:
569 // Draw a windows classic scrollbar gripper.
570 DrawFrameControl(hdc, &rect_win, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
571 return;
572 case kMenuCheckBackground:
573 case kScrollbarHorizontalGripper:
574 case kScrollbarVerticalGripper:
575 return; // No further painting necessary.
576 case kMenuItemBackground:
577 case kMenuPopupBackground:
578 case kMenuPopupGutter:
579 case kMenuPopupSeparator:
580 case kScrollbarCorner:
581 case kSliderTrack:
582 case kSliderThumb:
583 case kMaxPart:
584 NOTREACHED();
585 }
586 }
587
GetSystemColor(ColorId color_id,ColorScheme color_scheme) const588 SkColor NativeThemeWin::GetSystemColor(ColorId color_id,
589 ColorScheme color_scheme) const {
590 if (color_scheme == ColorScheme::kDefault)
591 color_scheme = GetDefaultSystemColorScheme();
592
593 return (color_scheme == ColorScheme::kPlatformHighContrast)
594 ? GetPlatformHighContrastColor(color_id)
595 : NativeTheme::GetSystemColor(color_id, color_scheme);
596 }
597
GetPlatformHighContrastColor(ColorId color_id) const598 SkColor NativeThemeWin::GetPlatformHighContrastColor(ColorId color_id) const {
599 switch (color_id) {
600 // Window Background
601 case kColorId_WindowBackground:
602 case kColorId_DialogBackground:
603 case kColorId_BubbleBackground:
604 case kColorId_BubbleFooterBackground:
605 case kColorId_TreeBackground:
606 case kColorId_TableHeaderBackground:
607 case kColorId_TableBackground:
608 case kColorId_TooltipBackground:
609 case kColorId_ProminentButtonDisabledColor:
610 return system_colors_[SystemThemeColor::kWindow];
611
612 // Window Text
613 case kColorId_DefaultIconColor:
614 case kColorId_DialogForeground:
615 case kColorId_LabelEnabledColor:
616 case kColorId_LabelSecondaryColor:
617 case kColorId_TreeText:
618 case kColorId_TableText:
619 case kColorId_TableHeaderText:
620 case kColorId_TableGroupingIndicatorColor:
621 case kColorId_TableHeaderSeparator:
622 case kColorId_TooltipText:
623 case kColorId_ThrobberSpinningColor:
624 case kColorId_ThrobberLightColor:
625 case kColorId_AlertSeverityLow:
626 case kColorId_AlertSeverityMedium:
627 case kColorId_AlertSeverityHigh:
628 return system_colors_[SystemThemeColor::kWindowText];
629
630 // Hyperlinks
631 case kColorId_LinkEnabled:
632 case kColorId_LinkPressed:
633 case kColorId_HighlightedMenuItemForegroundColor:
634 return system_colors_[SystemThemeColor::kHotlight];
635
636 // Gray/Disabled Text
637 case kColorId_DisabledMenuItemForegroundColor:
638 case kColorId_LinkDisabled:
639 case kColorId_LabelDisabledColor:
640 case kColorId_ButtonDisabledColor:
641 case kColorId_ThrobberWaitingColor:
642 return system_colors_[SystemThemeColor::kGrayText];
643
644 // Button Background
645 case kColorId_MenuBackgroundColor:
646 case kColorId_HighlightedMenuItemBackgroundColor:
647 case kColorId_TextfieldDefaultBackground:
648 case kColorId_TextfieldReadOnlyBackground:
649 case kColorId_ButtonPressedShade:
650 return system_colors_[SystemThemeColor::kButtonFace];
651
652 // Button Text Foreground
653 case kColorId_EnabledMenuItemForegroundColor:
654 case kColorId_MenuItemMinorTextColor:
655 case kColorId_MenuBorderColor:
656 case kColorId_MenuSeparatorColor:
657 case kColorId_SeparatorColor:
658 case kColorId_TextfieldDefaultColor:
659 case kColorId_ButtonEnabledColor:
660 case kColorId_UnfocusedBorderColor:
661 case kColorId_TextfieldPlaceholderColor:
662 case kColorId_TextfieldReadOnlyColor:
663 case kColorId_FocusedBorderColor:
664 case kColorId_TabTitleColorActive:
665 case kColorId_TabTitleColorInactive:
666 case kColorId_TabBottomBorder:
667 return system_colors_[SystemThemeColor::kButtonText];
668
669 // Highlight/Selected Background
670 case kColorId_ProminentButtonColor:
671 case kColorId_ProminentButtonFocusedColor:
672 case kColorId_ButtonBorderColor:
673 case kColorId_FocusedMenuItemBackgroundColor:
674 case kColorId_LabelTextSelectionBackgroundFocused:
675 case kColorId_TextfieldSelectionBackgroundFocused:
676 case kColorId_TreeSelectionBackgroundFocused:
677 case kColorId_TreeSelectionBackgroundUnfocused:
678 case kColorId_TableSelectionBackgroundFocused:
679 case kColorId_TableSelectionBackgroundUnfocused:
680 return system_colors_[SystemThemeColor::kHighlight];
681
682 // Highlight/Selected Text Foreground
683 case kColorId_TextOnProminentButtonColor:
684 case kColorId_SelectedMenuItemForegroundColor:
685 case kColorId_TextfieldSelectionColor:
686 case kColorId_LabelTextSelectionColor:
687 case kColorId_TreeSelectedText:
688 case kColorId_TreeSelectedTextUnfocused:
689 case kColorId_TableSelectedText:
690 case kColorId_TableSelectedTextUnfocused:
691 return system_colors_[SystemThemeColor::kHighlightText];
692
693 default:
694 return gfx::kPlaceholderColor;
695 }
696 }
697
SupportsNinePatch(Part part) const698 bool NativeThemeWin::SupportsNinePatch(Part part) const {
699 // The only nine-patch resources currently supported (overlay scrollbar) are
700 // painted by NativeThemeAura on Windows.
701 return false;
702 }
703
GetNinePatchCanvasSize(Part part) const704 gfx::Size NativeThemeWin::GetNinePatchCanvasSize(Part part) const {
705 NOTREACHED() << "NativeThemeWin doesn't support nine-patch resources.";
706 return gfx::Size();
707 }
708
GetNinePatchAperture(Part part) const709 gfx::Rect NativeThemeWin::GetNinePatchAperture(Part part) const {
710 NOTREACHED() << "NativeThemeWin doesn't support nine-patch resources.";
711 return gfx::Rect();
712 }
713
ShouldUseDarkColors() const714 bool NativeThemeWin::ShouldUseDarkColors() const {
715 // Windows high contrast modes are entirely different themes,
716 // so let them take priority over dark mode.
717 // ...unless --force-dark-mode was specified in which case caveat emptor.
718 if (UsesHighContrastColors() && !IsForcedDarkMode())
719 return false;
720 return NativeTheme::ShouldUseDarkColors();
721 }
722
723 NativeTheme::PreferredColorScheme
CalculatePreferredColorScheme() const724 NativeThemeWin::CalculatePreferredColorScheme() const {
725 if (!UsesHighContrastColors())
726 return NativeTheme::CalculatePreferredColorScheme();
727
728 // The Windows SystemParametersInfo API will return the high contrast theme
729 // as a string. However, this string is language dependent. Instead, to
730 // account for non-English systems, sniff out the system colors to
731 // determine the high contrast color scheme.
732 SkColor fg_color = system_colors_[SystemThemeColor::kWindowText];
733 SkColor bg_color = system_colors_[SystemThemeColor::kWindow];
734 if (bg_color == SK_ColorWHITE && fg_color == SK_ColorBLACK)
735 return NativeTheme::PreferredColorScheme::kLight;
736 if (bg_color == SK_ColorBLACK && fg_color == SK_ColorWHITE)
737 return NativeTheme::PreferredColorScheme::kDark;
738 return NativeTheme::PreferredColorScheme::kNoPreference;
739 }
740
GetDefaultSystemColorScheme() const741 NativeTheme::ColorScheme NativeThemeWin::GetDefaultSystemColorScheme() const {
742 return UsesHighContrastColors() ? ColorScheme::kPlatformHighContrast
743 : NativeTheme::GetDefaultSystemColorScheme();
744 }
745
PaintIndirect(cc::PaintCanvas * destination_canvas,Part part,State state,const gfx::Rect & rect,const ExtraParams & extra) const746 void NativeThemeWin::PaintIndirect(cc::PaintCanvas* destination_canvas,
747 Part part,
748 State state,
749 const gfx::Rect& rect,
750 const ExtraParams& extra) const {
751 // TODO(asvitkine): This path is pretty inefficient - for each paint operation
752 // it creates a new offscreen bitmap Skia canvas. This can be sped up by doing
753 // it only once per part/state and keeping a cache of the resulting bitmaps.
754 //
755 // TODO(enne): This could also potentially be sped up for software raster
756 // by moving these draw ops into PaintRecord itself and then moving the
757 // PaintDirect code to be part of the raster for PaintRecord.
758
759 // If this process doesn't have access to GDI, we'd need to use shared memory
760 // segment instead but that is not supported right now.
761 if (!base::win::IsUser32AndGdi32Available())
762 return;
763
764 ScopedCreateDCWithBitmap offscreen_hdc(CreateCompatibleDC(nullptr));
765 if (!offscreen_hdc.IsValid())
766 return;
767
768 skia::InitializeDC(offscreen_hdc.Get());
769 HRGN clip = CreateRectRgn(0, 0, rect.width(), rect.height());
770 if ((SelectClipRgn(offscreen_hdc.Get(), clip) == ERROR) ||
771 !DeleteObject(clip)) {
772 return;
773 }
774
775 if (!offscreen_hdc.SelectBitmap(skia::CreateHBitmap(
776 rect.width(), rect.height(), false, nullptr, nullptr))) {
777 return;
778 }
779
780 // Will be NULL if lower-level Windows calls fail, or if the backing
781 // allocated is 0 pixels in size (which should never happen according to
782 // Windows documentation).
783 sk_sp<SkSurface> offscreen_surface =
784 skia::MapPlatformSurface(offscreen_hdc.Get());
785 if (!offscreen_surface)
786 return;
787
788 SkCanvas* offscreen_canvas = offscreen_surface->getCanvas();
789 DCHECK(offscreen_canvas);
790
791 // Some of the Windows theme drawing operations do not write correct alpha
792 // values for fully-opaque pixels; instead the pixels get alpha 0. This is
793 // especially a problem on Windows XP or when using the Classic theme.
794 //
795 // To work-around this, mark all pixels with a placeholder value, to detect
796 // which pixels get touched by the paint operation. After paint, set any
797 // pixels that have alpha 0 to opaque and placeholders to fully-transparent.
798 constexpr SkColor placeholder = SkColorSetARGB(1, 0, 0, 0);
799 offscreen_canvas->clear(placeholder);
800
801 // Offset destination rects to have origin (0,0).
802 gfx::Rect adjusted_rect(rect.size());
803 ExtraParams adjusted_extra(extra);
804 switch (part) {
805 case kProgressBar:
806 adjusted_extra.progress_bar.value_rect_x = 0;
807 adjusted_extra.progress_bar.value_rect_y = 0;
808 break;
809 case kScrollbarHorizontalTrack:
810 case kScrollbarVerticalTrack:
811 adjusted_extra.scrollbar_track.track_x = 0;
812 adjusted_extra.scrollbar_track.track_y = 0;
813 break;
814 default:
815 break;
816 }
817 // Draw the theme controls using existing HDC-drawing code.
818 PaintDirect(offscreen_canvas, offscreen_hdc.Get(), part, state,
819 adjusted_rect, adjusted_extra);
820
821 SkBitmap offscreen_bitmap = skia::MapPlatformBitmap(offscreen_hdc.Get());
822
823 // Post-process the pixels to fix up the alpha values (see big comment above).
824 const SkPMColor placeholder_value = SkPreMultiplyColor(placeholder);
825 const int pixel_count = rect.width() * rect.height();
826 SkPMColor* pixels = offscreen_bitmap.getAddr32(0, 0);
827 for (int i = 0; i < pixel_count; i++) {
828 if (pixels[i] == placeholder_value) {
829 // Pixel wasn't touched - make it fully transparent.
830 pixels[i] = SkPackARGB32(0, 0, 0, 0);
831 } else if (SkGetPackedA32(pixels[i]) == 0) {
832 // Pixel was touched but has incorrect alpha of 0, make it fully opaque.
833 pixels[i] = SkPackARGB32(0xFF,
834 SkGetPackedR32(pixels[i]),
835 SkGetPackedG32(pixels[i]),
836 SkGetPackedB32(pixels[i]));
837 }
838 }
839
840 destination_canvas->drawImage(
841 cc::PaintImage::CreateFromBitmap(std::move(offscreen_bitmap)), rect.x(),
842 rect.y());
843 }
844
PaintButtonClassic(HDC hdc,Part part,State state,RECT * rect,const ButtonExtraParams & extra) const845 void NativeThemeWin::PaintButtonClassic(HDC hdc,
846 Part part,
847 State state,
848 RECT* rect,
849 const ButtonExtraParams& extra) const {
850 int classic_state = extra.classic_state;
851 switch (part) {
852 case kCheckbox:
853 classic_state |= DFCS_BUTTONCHECK;
854 break;
855 case kPushButton:
856 classic_state |= DFCS_BUTTONRADIO;
857 break;
858 case kRadio:
859 classic_state |= DFCS_BUTTONPUSH;
860 break;
861 default:
862 NOTREACHED();
863 break;
864 }
865
866 if (state == kDisabled)
867 classic_state |= DFCS_INACTIVE;
868 else if (state == kPressed)
869 classic_state |= DFCS_PUSHED;
870
871 if (extra.checked)
872 classic_state |= DFCS_CHECKED;
873
874 if ((part == kPushButton) && ((state == kPressed) || extra.is_default)) {
875 // Pressed or defaulted buttons have a shadow replacing the outer 1 px.
876 HBRUSH brush = GetSysColorBrush(COLOR_3DDKSHADOW);
877 if (brush) {
878 FrameRect(hdc, rect, brush);
879 InflateRect(rect, -1, -1);
880 }
881 }
882
883 DrawFrameControl(hdc, rect, DFC_BUTTON, classic_state);
884
885 // Draw a focus rectangle (the dotted line box) on defaulted buttons.
886 if ((part == kPushButton) && extra.is_default) {
887 InflateRect(rect, -GetSystemMetrics(SM_CXEDGE),
888 -GetSystemMetrics(SM_CYEDGE));
889 DrawFocusRect(hdc, rect);
890 }
891
892 // Classic theme doesn't support indeterminate checkboxes. We draw a
893 // recangle inside a checkbox like IE10 does.
894 if ((part == kCheckbox) && extra.indeterminate) {
895 RECT inner_rect = *rect;
896 // "4 / 13" is same as IE10 in classic theme.
897 int padding = (inner_rect.right - inner_rect.left) * 4 / 13;
898 InflateRect(&inner_rect, -padding, -padding);
899 int color_index = (state == kDisabled) ? COLOR_GRAYTEXT : COLOR_WINDOWTEXT;
900 FillRect(hdc, &inner_rect, GetSysColorBrush(color_index));
901 }
902 }
903
PaintLeftMenuArrowThemed(HDC hdc,HANDLE handle,int part_id,int state_id,const gfx::Rect & rect) const904 void NativeThemeWin::PaintLeftMenuArrowThemed(HDC hdc,
905 HANDLE handle,
906 int part_id,
907 int state_id,
908 const gfx::Rect& rect) const {
909 // There is no way to tell the uxtheme API to draw a left pointing arrow; it
910 // doesn't have a flag equivalent to DFCS_MENUARROWRIGHT. But they are needed
911 // for RTL locales on Vista. So use a memory DC and mirror the region with
912 // GDI's StretchBlt.
913 base::win::ScopedCreateDC mem_dc(CreateCompatibleDC(hdc));
914 base::win::ScopedBitmap mem_bitmap(
915 CreateCompatibleBitmap(hdc, rect.width(), rect.height()));
916 base::win::ScopedSelectObject select_bitmap(mem_dc.Get(), mem_bitmap.get());
917 // Copy and horizontally mirror the background from hdc into mem_dc. Use a
918 // negative-width source rect, starting at the rightmost pixel.
919 StretchBlt(mem_dc.Get(), 0, 0, rect.width(), rect.height(), hdc,
920 rect.right() - 1, rect.y(), -rect.width(), rect.height(), SRCCOPY);
921 // Draw the arrow.
922 RECT theme_rect = {0, 0, rect.width(), rect.height()};
923 DrawThemeBackground(handle, mem_dc.Get(), part_id, state_id, &theme_rect,
924 nullptr);
925 // Copy and mirror the result back into mem_dc.
926 StretchBlt(hdc, rect.x(), rect.y(), rect.width(), rect.height(), mem_dc.Get(),
927 rect.width() - 1, 0, -rect.width(), rect.height(), SRCCOPY);
928 }
929
PaintScrollbarArrowClassic(HDC hdc,Part part,State state,RECT * rect) const930 void NativeThemeWin::PaintScrollbarArrowClassic(HDC hdc,
931 Part part,
932 State state,
933 RECT* rect) const {
934 int classic_state = DFCS_SCROLLDOWN;
935 switch (part) {
936 case kScrollbarDownArrow:
937 break;
938 case kScrollbarLeftArrow:
939 classic_state = DFCS_SCROLLLEFT;
940 break;
941 case kScrollbarRightArrow:
942 classic_state = DFCS_SCROLLRIGHT;
943 break;
944 case kScrollbarUpArrow:
945 classic_state = DFCS_SCROLLUP;
946 break;
947 default:
948 NOTREACHED();
949 break;
950 }
951 switch (state) {
952 case kDisabled:
953 classic_state |= DFCS_INACTIVE;
954 break;
955 case kHovered:
956 classic_state |= DFCS_HOT;
957 break;
958 case kNormal:
959 break;
960 case kPressed:
961 classic_state |= DFCS_PUSHED;
962 break;
963 case kNumStates:
964 NOTREACHED();
965 break;
966 }
967 DrawFrameControl(hdc, rect, DFC_SCROLL, classic_state);
968 }
969
PaintScrollbarTrackClassic(SkCanvas * canvas,HDC hdc,RECT * rect,const ScrollbarTrackExtraParams & extra) const970 void NativeThemeWin::PaintScrollbarTrackClassic(
971 SkCanvas* canvas,
972 HDC hdc,
973 RECT* rect,
974 const ScrollbarTrackExtraParams& extra) const {
975 if ((system_colors_[SystemThemeColor::kScrollbar] !=
976 system_colors_[SystemThemeColor::kButtonFace]) &&
977 (system_colors_[SystemThemeColor::kScrollbar] !=
978 system_colors_[SystemThemeColor::kWindow])) {
979 FillRect(hdc, rect, reinterpret_cast<HBRUSH>(COLOR_SCROLLBAR + 1));
980 } else {
981 SkPaint paint;
982 RECT align_rect = gfx::Rect(extra.track_x, extra.track_y, extra.track_width,
983 extra.track_height)
984 .ToRECT();
985 SetCheckerboardShader(&paint, align_rect);
986 canvas->drawIRect(skia::RECTToSkIRect(*rect), paint);
987 }
988 if (extra.classic_state & DFCS_PUSHED)
989 InvertRect(hdc, rect);
990 }
991
PaintHorizontalTrackbarThumbClassic(SkCanvas * canvas,HDC hdc,const RECT & rect,const TrackbarExtraParams & extra) const992 void NativeThemeWin::PaintHorizontalTrackbarThumbClassic(
993 SkCanvas* canvas,
994 HDC hdc,
995 const RECT& rect,
996 const TrackbarExtraParams& extra) const {
997 // Split rect into top and bottom pieces.
998 RECT top_section = rect;
999 RECT bottom_section = rect;
1000 top_section.bottom -= ((bottom_section.right - bottom_section.left) / 2);
1001 bottom_section.top = top_section.bottom;
1002 DrawEdge(hdc, &top_section, EDGE_RAISED,
1003 BF_LEFT | BF_TOP | BF_RIGHT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
1004
1005 // Split triangular piece into two diagonals.
1006 RECT& left_half = bottom_section;
1007 RECT right_half = bottom_section;
1008 right_half.left += ((bottom_section.right - bottom_section.left) / 2);
1009 left_half.right = right_half.left;
1010 DrawEdge(hdc, &left_half, EDGE_RAISED,
1011 BF_DIAGONAL_ENDTOPLEFT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
1012 DrawEdge(hdc, &right_half, EDGE_RAISED,
1013 BF_DIAGONAL_ENDBOTTOMLEFT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
1014
1015 // If the button is pressed, draw hatching.
1016 if (extra.classic_state & DFCS_PUSHED) {
1017 SkPaint paint;
1018 SetCheckerboardShader(&paint, rect);
1019
1020 // Fill all three pieces with the pattern.
1021 canvas->drawIRect(skia::RECTToSkIRect(top_section), paint);
1022
1023 SkScalar left_triangle_top = SkIntToScalar(left_half.top);
1024 SkScalar left_triangle_right = SkIntToScalar(left_half.right);
1025 SkPath left_triangle;
1026 left_triangle.moveTo(SkIntToScalar(left_half.left), left_triangle_top);
1027 left_triangle.lineTo(left_triangle_right, left_triangle_top);
1028 left_triangle.lineTo(left_triangle_right, SkIntToScalar(left_half.bottom));
1029 left_triangle.close();
1030 canvas->drawPath(left_triangle, paint);
1031
1032 SkScalar right_triangle_left = SkIntToScalar(right_half.left);
1033 SkScalar right_triangle_top = SkIntToScalar(right_half.top);
1034 SkPath right_triangle;
1035 right_triangle.moveTo(right_triangle_left, right_triangle_top);
1036 right_triangle.lineTo(SkIntToScalar(right_half.right), right_triangle_top);
1037 right_triangle.lineTo(right_triangle_left,
1038 SkIntToScalar(right_half.bottom));
1039 right_triangle.close();
1040 canvas->drawPath(right_triangle, paint);
1041 }
1042 }
1043
PaintProgressBarOverlayThemed(HDC hdc,HANDLE handle,RECT * bar_rect,RECT * value_rect,const ProgressBarExtraParams & extra) const1044 void NativeThemeWin::PaintProgressBarOverlayThemed(
1045 HDC hdc,
1046 HANDLE handle,
1047 RECT* bar_rect,
1048 RECT* value_rect,
1049 const ProgressBarExtraParams& extra) const {
1050 // There is no documentation about the animation speed, frame-rate, nor
1051 // size of moving overlay of the indeterminate progress bar.
1052 // So we just observed real-world programs and guessed following parameters.
1053 constexpr int kDeterminateOverlayWidth = 120;
1054 constexpr int kDeterminateOverlayPixelsPerSecond = 300;
1055 constexpr int kIndeterminateOverlayWidth = 120;
1056 constexpr int kIndeterminateOverlayPixelsPerSecond = 175;
1057
1058 int bar_width = bar_rect->right - bar_rect->left;
1059 if (!extra.determinate) {
1060 // The glossy overlay for the indeterminate progress bar has a small pause
1061 // after each animation. We emulate this by adding an invisible margin the
1062 // animation has to traverse.
1063 int width_with_margin = bar_width + kIndeterminateOverlayPixelsPerSecond;
1064 int overlay_width = kIndeterminateOverlayWidth;
1065 RECT overlay_rect = *bar_rect;
1066 overlay_rect.left += ComputeAnimationProgress(
1067 width_with_margin, overlay_width, kIndeterminateOverlayPixelsPerSecond,
1068 extra.animated_seconds);
1069 overlay_rect.right = overlay_rect.left + overlay_width;
1070 DrawThemeBackground(handle, hdc, PP_MOVEOVERLAY, 0, &overlay_rect,
1071 bar_rect);
1072 return;
1073 }
1074
1075 // We care about the direction here because PP_CHUNK painting is asymmetric.
1076 // TODO(morrita): This RTL guess can be wrong. We should pass in the
1077 // direction from WebKit.
1078 const bool mirror = bar_rect->right == value_rect->right &&
1079 bar_rect->left != value_rect->left;
1080 const DTBGOPTS value_draw_options = {sizeof(DTBGOPTS),
1081 mirror ? DTBG_MIRRORDC : 0u, *bar_rect};
1082
1083 // On Vista or later, the progress bar part has a single-block value part
1084 // and a glossy effect. The value part has exactly same height as the bar
1085 // part, so we don't need to shrink the rect.
1086 DrawThemeBackgroundEx(handle, hdc, PP_FILL, 0, value_rect,
1087 &value_draw_options);
1088
1089 RECT overlay_rect = *value_rect;
1090 overlay_rect.left += ComputeAnimationProgress(
1091 bar_width, kDeterminateOverlayWidth, kDeterminateOverlayPixelsPerSecond,
1092 extra.animated_seconds);
1093 overlay_rect.right = overlay_rect.left + kDeterminateOverlayWidth;
1094 DrawThemeBackground(handle, hdc, PP_MOVEOVERLAY, 0, &overlay_rect,
1095 value_rect);
1096 }
1097
PaintTextFieldThemed(HDC hdc,HANDLE handle,HBRUSH bg_brush,int part_id,int state_id,RECT * rect,const TextFieldExtraParams & extra) const1098 void NativeThemeWin::PaintTextFieldThemed(
1099 HDC hdc,
1100 HANDLE handle,
1101 HBRUSH bg_brush,
1102 int part_id,
1103 int state_id,
1104 RECT* rect,
1105 const TextFieldExtraParams& extra) const {
1106 static constexpr DTBGOPTS kOmitBorderOptions = {
1107 sizeof(DTBGOPTS), DTBG_OMITBORDER, {0, 0, 0, 0}};
1108 DrawThemeBackgroundEx(handle, hdc, part_id, state_id, rect,
1109 extra.draw_edges ? nullptr : &kOmitBorderOptions);
1110
1111 if (extra.fill_content_area) {
1112 RECT content_rect;
1113 GetThemeBackgroundContentRect(handle, hdc, part_id, state_id, rect,
1114 &content_rect);
1115 FillRect(hdc, &content_rect, bg_brush);
1116 }
1117 }
1118
PaintTextFieldClassic(HDC hdc,HBRUSH bg_brush,RECT * rect,const TextFieldExtraParams & extra) const1119 void NativeThemeWin::PaintTextFieldClassic(
1120 HDC hdc,
1121 HBRUSH bg_brush,
1122 RECT* rect,
1123 const TextFieldExtraParams& extra) const {
1124 if (extra.draw_edges)
1125 DrawEdge(hdc, rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
1126
1127 if (extra.fill_content_area) {
1128 if (extra.classic_state & DFCS_INACTIVE)
1129 bg_brush = reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1);
1130 FillRect(hdc, rect, bg_brush);
1131 }
1132 }
1133
PaintScaledTheme(HANDLE theme,HDC hdc,int part_id,int state_id,const gfx::Rect & rect) const1134 void NativeThemeWin::PaintScaledTheme(HANDLE theme,
1135 HDC hdc,
1136 int part_id,
1137 int state_id,
1138 const gfx::Rect& rect) const {
1139 // Correct the scaling and positioning of sub-components such as scrollbar
1140 // arrows and thumb grippers in the event that the world transform applies
1141 // scaling (e.g. in high-DPI mode).
1142 XFORM save_transform;
1143 if (GetWorldTransform(hdc, &save_transform)) {
1144 float scale = save_transform.eM11;
1145 if (scale != 1 && save_transform.eM12 == 0) {
1146 ModifyWorldTransform(hdc, NULL, MWT_IDENTITY);
1147 gfx::Rect scaled_rect = gfx::ScaleToEnclosedRect(rect, scale);
1148 scaled_rect.Offset(save_transform.eDx, save_transform.eDy);
1149 RECT bounds = scaled_rect.ToRECT();
1150 DrawThemeBackground(theme, hdc, part_id, state_id, &bounds, nullptr);
1151 SetWorldTransform(hdc, &save_transform);
1152 return;
1153 }
1154 }
1155 RECT bounds = rect.ToRECT();
1156 DrawThemeBackground(theme, hdc, part_id, state_id, &bounds, nullptr);
1157 }
1158
1159 // static
GetThemeName(Part part)1160 NativeThemeWin::ThemeName NativeThemeWin::GetThemeName(Part part) {
1161 switch (part) {
1162 case kCheckbox:
1163 case kPushButton:
1164 case kRadio:
1165 return BUTTON;
1166 case kMenuList:
1167 case kMenuCheck:
1168 case kMenuCheckBackground:
1169 case kMenuPopupArrow:
1170 case kMenuPopupGutter:
1171 case kMenuPopupSeparator:
1172 return MENU;
1173 case kProgressBar:
1174 return PROGRESS;
1175 case kScrollbarDownArrow:
1176 case kScrollbarLeftArrow:
1177 case kScrollbarRightArrow:
1178 case kScrollbarUpArrow:
1179 case kScrollbarHorizontalGripper:
1180 case kScrollbarVerticalGripper:
1181 case kScrollbarHorizontalThumb:
1182 case kScrollbarVerticalThumb:
1183 case kScrollbarHorizontalTrack:
1184 case kScrollbarVerticalTrack:
1185 return SCROLLBAR;
1186 case kInnerSpinButton:
1187 return SPIN;
1188 case kWindowResizeGripper:
1189 return STATUS;
1190 case kTabPanelBackground:
1191 return TAB;
1192 case kTextField:
1193 return TEXTFIELD;
1194 case kTrackbarThumb:
1195 case kTrackbarTrack:
1196 return TRACKBAR;
1197 case kMenuPopupBackground:
1198 case kMenuItemBackground:
1199 case kScrollbarCorner:
1200 case kSliderTrack:
1201 case kSliderThumb:
1202 case kMaxPart:
1203 NOTREACHED();
1204 }
1205 return LAST;
1206 }
1207
1208 // static
GetWindowsPart(Part part,State state,const ExtraParams & extra)1209 int NativeThemeWin::GetWindowsPart(Part part,
1210 State state,
1211 const ExtraParams& extra) {
1212 switch (part) {
1213 case kCheckbox:
1214 return BP_CHECKBOX;
1215 case kPushButton:
1216 return BP_PUSHBUTTON;
1217 case kRadio:
1218 return BP_RADIOBUTTON;
1219 case kMenuList:
1220 return CP_DROPDOWNBUTTON;
1221 case kTextField:
1222 return EP_EDITTEXT;
1223 case kMenuCheck:
1224 return MENU_POPUPCHECK;
1225 case kMenuCheckBackground:
1226 return MENU_POPUPCHECKBACKGROUND;
1227 case kMenuPopupGutter:
1228 return MENU_POPUPGUTTER;
1229 case kMenuPopupSeparator:
1230 return MENU_POPUPSEPARATOR;
1231 case kMenuPopupArrow:
1232 return MENU_POPUPSUBMENU;
1233 case kProgressBar:
1234 return PP_BAR;
1235 case kScrollbarDownArrow:
1236 case kScrollbarLeftArrow:
1237 case kScrollbarRightArrow:
1238 case kScrollbarUpArrow:
1239 return SBP_ARROWBTN;
1240 case kScrollbarHorizontalGripper:
1241 return SBP_GRIPPERHORZ;
1242 case kScrollbarVerticalGripper:
1243 return SBP_GRIPPERVERT;
1244 case kScrollbarHorizontalThumb:
1245 return SBP_THUMBBTNHORZ;
1246 case kScrollbarVerticalThumb:
1247 return SBP_THUMBBTNVERT;
1248 case kScrollbarHorizontalTrack:
1249 return extra.scrollbar_track.is_upper ? SBP_UPPERTRACKHORZ
1250 : SBP_LOWERTRACKHORZ;
1251 case kScrollbarVerticalTrack:
1252 return extra.scrollbar_track.is_upper ? SBP_UPPERTRACKVERT
1253 : SBP_LOWERTRACKVERT;
1254 case kWindowResizeGripper:
1255 // Use the status bar gripper. There doesn't seem to be a standard
1256 // gripper in Windows for the space between scrollbars. This is pretty
1257 // close, but it's supposed to be painted over a status bar.
1258 return SP_GRIPPER;
1259 case kInnerSpinButton:
1260 return extra.inner_spin.spin_up ? SPNP_UP : SPNP_DOWN;
1261 case kTabPanelBackground:
1262 return TABP_BODY;
1263 case kTrackbarThumb:
1264 return extra.trackbar.vertical ? TKP_THUMBVERT : TKP_THUMBBOTTOM;
1265 case kTrackbarTrack:
1266 return extra.trackbar.vertical ? TKP_TRACKVERT : TKP_TRACK;
1267 case kMenuPopupBackground:
1268 case kMenuItemBackground:
1269 case kScrollbarCorner:
1270 case kSliderTrack:
1271 case kSliderThumb:
1272 case kMaxPart:
1273 NOTREACHED();
1274 }
1275 return 0;
1276 }
1277
GetWindowsState(Part part,State state,const ExtraParams & extra)1278 int NativeThemeWin::GetWindowsState(Part part,
1279 State state,
1280 const ExtraParams& extra) {
1281 switch (part) {
1282 case kScrollbarDownArrow:
1283 switch (state) {
1284 case kDisabled:
1285 return ABS_DOWNDISABLED;
1286 case kHovered:
1287 return extra.scrollbar_arrow.is_hovering ? ABS_DOWNHOVER
1288 : ABS_DOWNHOT;
1289 case kNormal:
1290 return ABS_DOWNNORMAL;
1291 case kPressed:
1292 return ABS_DOWNPRESSED;
1293 case kNumStates:
1294 NOTREACHED();
1295 return 0;
1296 }
1297 case kScrollbarLeftArrow:
1298 switch (state) {
1299 case kDisabled:
1300 return ABS_LEFTDISABLED;
1301 case kHovered:
1302 return extra.scrollbar_arrow.is_hovering ? ABS_LEFTHOVER
1303 : ABS_LEFTHOT;
1304 case kNormal:
1305 return ABS_LEFTNORMAL;
1306 case kPressed:
1307 return ABS_LEFTPRESSED;
1308 case kNumStates:
1309 NOTREACHED();
1310 return 0;
1311 }
1312 case kScrollbarRightArrow:
1313 switch (state) {
1314 case kDisabled:
1315 return ABS_RIGHTDISABLED;
1316 case kHovered:
1317 return extra.scrollbar_arrow.is_hovering ? ABS_RIGHTHOVER
1318 : ABS_RIGHTHOT;
1319 case kNormal:
1320 return ABS_RIGHTNORMAL;
1321 case kPressed:
1322 return ABS_RIGHTPRESSED;
1323 case kNumStates:
1324 NOTREACHED();
1325 return 0;
1326 }
1327 case kScrollbarUpArrow:
1328 switch (state) {
1329 case kDisabled:
1330 return ABS_UPDISABLED;
1331 case kHovered:
1332 return extra.scrollbar_arrow.is_hovering ? ABS_UPHOVER : ABS_UPHOT;
1333 case kNormal:
1334 return ABS_UPNORMAL;
1335 case kPressed:
1336 return ABS_UPPRESSED;
1337 case kNumStates:
1338 NOTREACHED();
1339 return 0;
1340 }
1341 case kCheckbox: {
1342 const ButtonExtraParams& button = extra.button;
1343 switch (state) {
1344 case kDisabled:
1345 return button.checked
1346 ? CBS_CHECKEDDISABLED
1347 : (button.indeterminate ? CBS_MIXEDDISABLED
1348 : CBS_UNCHECKEDDISABLED);
1349 case kHovered:
1350 return button.checked
1351 ? CBS_CHECKEDHOT
1352 : (button.indeterminate ? CBS_MIXEDHOT : CBS_UNCHECKEDHOT);
1353 case kNormal:
1354 return button.checked ? CBS_CHECKEDNORMAL
1355 : (button.indeterminate ? CBS_MIXEDNORMAL
1356 : CBS_UNCHECKEDNORMAL);
1357 case kPressed:
1358 return button.checked ? CBS_CHECKEDPRESSED
1359 : (button.indeterminate ? CBS_MIXEDPRESSED
1360 : CBS_UNCHECKEDPRESSED);
1361 case kNumStates:
1362 NOTREACHED();
1363 return 0;
1364 }
1365 }
1366 case kMenuList:
1367 switch (state) {
1368 case kDisabled:
1369 return CBXS_DISABLED;
1370 case kHovered:
1371 return CBXS_HOT;
1372 case kNormal:
1373 return CBXS_NORMAL;
1374 case kPressed:
1375 return CBXS_PRESSED;
1376 case kNumStates:
1377 NOTREACHED();
1378 return 0;
1379 }
1380 case kTextField:
1381 switch (state) {
1382 case kDisabled:
1383 return ETS_DISABLED;
1384 case kHovered:
1385 return ETS_HOT;
1386 case kNormal:
1387 if (extra.text_field.is_read_only)
1388 return ETS_READONLY;
1389 return extra.text_field.is_focused ? ETS_FOCUSED : ETS_NORMAL;
1390 case kPressed:
1391 return ETS_SELECTED;
1392 case kNumStates:
1393 NOTREACHED();
1394 return 0;
1395 }
1396 case kMenuPopupArrow:
1397 return (state == kDisabled) ? MSM_DISABLED : MSM_NORMAL;
1398 case kMenuCheck:
1399 if (state == kDisabled) {
1400 return extra.menu_check.is_radio ? MC_BULLETDISABLED
1401 : MC_CHECKMARKDISABLED;
1402 }
1403 return extra.menu_check.is_radio ? MC_BULLETNORMAL : MC_CHECKMARKNORMAL;
1404 case kMenuCheckBackground:
1405 return (state == kDisabled) ? MCB_DISABLED : MCB_NORMAL;
1406 case kPushButton:
1407 switch (state) {
1408 case kDisabled:
1409 return PBS_DISABLED;
1410 case kHovered:
1411 return PBS_HOT;
1412 case kNormal:
1413 return extra.button.is_default ? PBS_DEFAULTED : PBS_NORMAL;
1414 case kPressed:
1415 return PBS_PRESSED;
1416 case kNumStates:
1417 NOTREACHED();
1418 return 0;
1419 }
1420 case kRadio: {
1421 const ButtonExtraParams& button = extra.button;
1422 switch (state) {
1423 case kDisabled:
1424 return button.checked ? RBS_CHECKEDDISABLED : RBS_UNCHECKEDDISABLED;
1425 case kHovered:
1426 return button.checked ? RBS_CHECKEDHOT : RBS_UNCHECKEDHOT;
1427 case kNormal:
1428 return button.checked ? RBS_CHECKEDNORMAL : RBS_UNCHECKEDNORMAL;
1429 case kPressed:
1430 return button.checked ? RBS_CHECKEDPRESSED : RBS_UNCHECKEDPRESSED;
1431 case kNumStates:
1432 NOTREACHED();
1433 return 0;
1434 }
1435 }
1436 case kScrollbarHorizontalGripper:
1437 case kScrollbarVerticalGripper:
1438 case kScrollbarHorizontalThumb:
1439 case kScrollbarVerticalThumb:
1440 if ((state == kHovered) && !extra.scrollbar_thumb.is_hovering)
1441 return SCRBS_HOT;
1442 FALLTHROUGH;
1443 case kScrollbarHorizontalTrack:
1444 case kScrollbarVerticalTrack:
1445 switch (state) {
1446 case kDisabled:
1447 return SCRBS_DISABLED;
1448 case kHovered:
1449 return SCRBS_HOVER;
1450 case kNormal:
1451 return SCRBS_NORMAL;
1452 case kPressed:
1453 return SCRBS_PRESSED;
1454 case kNumStates:
1455 NOTREACHED();
1456 return 0;
1457 }
1458 case kTrackbarThumb:
1459 case kTrackbarTrack:
1460 switch (state) {
1461 case kDisabled:
1462 return TUS_DISABLED;
1463 case kHovered:
1464 return TUS_HOT;
1465 case kNormal:
1466 return TUS_NORMAL;
1467 case kPressed:
1468 return TUS_PRESSED;
1469 case kNumStates:
1470 NOTREACHED();
1471 return 0;
1472 }
1473 case kInnerSpinButton:
1474 switch (state) {
1475 case kDisabled:
1476 return extra.inner_spin.spin_up ? UPS_DISABLED : DNS_DISABLED;
1477 case kHovered:
1478 return extra.inner_spin.spin_up ? UPS_HOT : DNS_HOT;
1479 case kNormal:
1480 return extra.inner_spin.spin_up ? UPS_NORMAL : DNS_NORMAL;
1481 case kPressed:
1482 return extra.inner_spin.spin_up ? UPS_PRESSED : DNS_PRESSED;
1483 case kNumStates:
1484 NOTREACHED();
1485 return 0;
1486 }
1487 case kMenuPopupGutter:
1488 case kMenuPopupSeparator:
1489 case kProgressBar:
1490 case kTabPanelBackground:
1491 case kWindowResizeGripper:
1492 switch (state) {
1493 case kDisabled:
1494 case kHovered:
1495 case kNormal:
1496 case kPressed:
1497 return 0;
1498 case kNumStates:
1499 NOTREACHED();
1500 return 0;
1501 }
1502 case kMenuPopupBackground:
1503 case kMenuItemBackground:
1504 case kScrollbarCorner:
1505 case kSliderTrack:
1506 case kSliderThumb:
1507 case kMaxPart:
1508 NOTREACHED();
1509 }
1510 return 0;
1511 }
1512
PaintFrameControl(HDC hdc,const gfx::Rect & rect,UINT type,UINT state,bool is_selected,State control_state) const1513 HRESULT NativeThemeWin::PaintFrameControl(HDC hdc,
1514 const gfx::Rect& rect,
1515 UINT type,
1516 UINT state,
1517 bool is_selected,
1518 State control_state) const {
1519 const int width = rect.width();
1520 const int height = rect.height();
1521
1522 // DrawFrameControl for menu arrow/check wants a monochrome bitmap.
1523 base::win::ScopedBitmap mask_bitmap(CreateBitmap(width, height, 1, 1, NULL));
1524
1525 if (mask_bitmap == NULL)
1526 return E_OUTOFMEMORY;
1527
1528 base::win::ScopedCreateDC bitmap_dc(CreateCompatibleDC(NULL));
1529 base::win::ScopedSelectObject select_bitmap(bitmap_dc.Get(),
1530 mask_bitmap.get());
1531 RECT local_rect = { 0, 0, width, height };
1532 DrawFrameControl(bitmap_dc.Get(), &local_rect, type, state);
1533
1534 // We're going to use BitBlt with a b&w mask. This results in using the dest
1535 // dc's text color for the black bits in the mask, and the dest dc's
1536 // background color for the white bits in the mask. DrawFrameControl draws the
1537 // check in black, and the background in white.
1538 int bg_color_key = COLOR_MENU;
1539 int text_color_key = COLOR_MENUTEXT;
1540 switch (control_state) {
1541 case kDisabled:
1542 bg_color_key = is_selected ? COLOR_HIGHLIGHT : COLOR_MENU;
1543 text_color_key = COLOR_GRAYTEXT;
1544 break;
1545 case kHovered:
1546 bg_color_key = COLOR_HIGHLIGHT;
1547 text_color_key = COLOR_HIGHLIGHTTEXT;
1548 break;
1549 case kNormal:
1550 break;
1551 case kPressed:
1552 case kNumStates:
1553 NOTREACHED();
1554 break;
1555 }
1556 COLORREF old_bg_color = SetBkColor(hdc, GetSysColor(bg_color_key));
1557 COLORREF old_text_color = SetTextColor(hdc, GetSysColor(text_color_key));
1558 BitBlt(hdc, rect.x(), rect.y(), width, height, bitmap_dc.Get(), 0, 0,
1559 SRCCOPY);
1560 SetBkColor(hdc, old_bg_color);
1561 SetTextColor(hdc, old_text_color);
1562
1563 return S_OK;
1564 }
1565
GetThemeHandle(ThemeName theme_name) const1566 HANDLE NativeThemeWin::GetThemeHandle(ThemeName theme_name) const {
1567 if (theme_name < 0 || theme_name >= LAST)
1568 return nullptr;
1569
1570 if (theme_handles_[theme_name])
1571 return theme_handles_[theme_name];
1572
1573 // Not found, try to load it.
1574 HANDLE handle = nullptr;
1575 switch (theme_name) {
1576 case BUTTON:
1577 handle = OpenThemeData(nullptr, L"Button");
1578 break;
1579 case LIST:
1580 handle = OpenThemeData(nullptr, L"Listview");
1581 break;
1582 case MENU:
1583 handle = OpenThemeData(nullptr, L"Menu");
1584 break;
1585 case MENULIST:
1586 handle = OpenThemeData(nullptr, L"Combobox");
1587 break;
1588 case SCROLLBAR:
1589 handle = OpenThemeData(nullptr, L"Scrollbar");
1590 break;
1591 case STATUS:
1592 handle = OpenThemeData(nullptr, L"Status");
1593 break;
1594 case TAB:
1595 handle = OpenThemeData(nullptr, L"Tab");
1596 break;
1597 case TEXTFIELD:
1598 handle = OpenThemeData(nullptr, L"Edit");
1599 break;
1600 case TRACKBAR:
1601 handle = OpenThemeData(nullptr, L"Trackbar");
1602 break;
1603 case WINDOW:
1604 handle = OpenThemeData(nullptr, L"Window");
1605 break;
1606 case PROGRESS:
1607 handle = OpenThemeData(nullptr, L"Progress");
1608 break;
1609 case SPIN:
1610 handle = OpenThemeData(nullptr, L"Spin");
1611 break;
1612 case LAST:
1613 NOTREACHED();
1614 break;
1615 }
1616 theme_handles_[theme_name] = handle;
1617 return handle;
1618 }
1619
RegisterThemeRegkeyObserver()1620 void NativeThemeWin::RegisterThemeRegkeyObserver() {
1621 DCHECK(hkcu_themes_regkey_.Valid());
1622 hkcu_themes_regkey_.StartWatching(base::BindOnce(
1623 [](NativeThemeWin* native_theme) {
1624 native_theme->UpdateDarkModeStatus();
1625 // RegKey::StartWatching only provides one notification. Reregistration
1626 // is required to get future notifications.
1627 native_theme->RegisterThemeRegkeyObserver();
1628 },
1629 base::Unretained(this)));
1630 }
1631
UpdateDarkModeStatus()1632 void NativeThemeWin::UpdateDarkModeStatus() {
1633 bool dark_mode_enabled = false;
1634 if (hkcu_themes_regkey_.Valid()) {
1635 DWORD apps_use_light_theme = 1;
1636 hkcu_themes_regkey_.ReadValueDW(L"AppsUseLightTheme",
1637 &apps_use_light_theme);
1638 dark_mode_enabled = (apps_use_light_theme == 0);
1639 }
1640 set_use_dark_colors(dark_mode_enabled);
1641 set_preferred_color_scheme(CalculatePreferredColorScheme());
1642 NotifyObservers();
1643 }
1644
1645 } // namespace ui
1646