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