Merge mozilla-central to autoland on a CLOSED TREE
[gecko.git] / widget / windows / nsLookAndFeel.cpp
blob6122000c9eb22928e534a52daa121cdc7f70463f
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsLookAndFeel.h"
7 #include <stdint.h>
8 #include <windows.h>
9 #include <shellapi.h>
10 #include "nsStyleConsts.h"
11 #include "nsUXThemeData.h"
12 #include "nsUXThemeConstants.h"
13 #include "nsWindowsHelpers.h"
14 #include "WinUtils.h"
15 #include "WindowsUIUtils.h"
16 #include "mozilla/FontPropertyTypes.h"
17 #include "mozilla/Telemetry.h"
18 #include "mozilla/widget/WinRegistry.h"
20 using namespace mozilla;
21 using namespace mozilla::widget;
23 static Maybe<nscolor> GetColorFromTheme(nsUXThemeClass cls, int32_t aPart,
24 int32_t aState, int32_t aPropId) {
25 COLORREF color;
26 HRESULT hr = GetThemeColor(nsUXThemeData::GetTheme(cls), aPart, aState,
27 aPropId, &color);
28 if (hr == S_OK) {
29 return Some(COLOREF_2_NSRGB(color));
31 return Nothing();
34 static int32_t GetSystemParam(long flag, int32_t def) {
35 DWORD value;
36 return ::SystemParametersInfo(flag, 0, &value, 0) ? value : def;
39 static bool SystemWantsDarkTheme() {
40 if (nsUXThemeData::IsHighContrastOn()) {
41 return LookAndFeel::IsDarkColor(
42 LookAndFeel::Color(StyleSystemColor::Window, ColorScheme::Light,
43 LookAndFeel::UseStandins::No));
46 WinRegistry::Key key(
47 HKEY_CURRENT_USER,
48 u"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"_ns,
49 WinRegistry::KeyMode::QueryValue);
50 if (NS_WARN_IF(!key)) {
51 return false;
53 uint32_t light = key.GetValueAsDword(u"AppsUseLightTheme"_ns).valueOr(1);
54 return !light;
57 uint32_t nsLookAndFeel::SystemColorFilter() {
58 if (NS_WARN_IF(!mColorFilterWatcher)) {
59 return 0;
62 const auto& key = mColorFilterWatcher->GetKey();
63 if (!key.GetValueAsDword(u"Active"_ns).valueOr(0)) {
64 return 0;
66 return key.GetValueAsDword(u"FilterType"_ns).valueOr(0);
69 nsLookAndFeel::nsLookAndFeel() {
70 mozilla::Telemetry::Accumulate(mozilla::Telemetry::TOUCH_ENABLED_DEVICE,
71 WinUtils::IsTouchDeviceSupportPresent());
74 nsLookAndFeel::~nsLookAndFeel() = default;
76 void nsLookAndFeel::NativeInit() { EnsureInit(); }
78 /* virtual */
79 void nsLookAndFeel::RefreshImpl() {
80 mInitialized = false; // Fetch system colors next time they're used.
81 nsXPLookAndFeel::RefreshImpl();
84 static bool UseNonNativeMenuColors(ColorScheme aScheme) {
85 return !LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme) ||
86 aScheme == ColorScheme::Dark;
89 nsresult nsLookAndFeel::NativeGetColor(ColorID aID, ColorScheme aScheme,
90 nscolor& aColor) {
91 EnsureInit();
93 auto IsHighlightColor = [&] {
94 switch (aID) {
95 case ColorID::MozMenuhover:
96 return !UseNonNativeMenuColors(aScheme);
97 case ColorID::Highlight:
98 case ColorID::Selecteditem:
99 // We prefer the generic dark selection color if we don't have an
100 // explicit one.
101 return aScheme != ColorScheme::Dark || mDarkHighlight;
102 case ColorID::IMESelectedRawTextBackground:
103 case ColorID::IMESelectedConvertedTextBackground:
104 return true;
105 default:
106 return false;
110 auto IsHighlightTextColor = [&] {
111 switch (aID) {
112 case ColorID::MozMenubarhovertext:
113 if (UseNonNativeMenuColors(aScheme)) {
114 return false;
116 [[fallthrough]];
117 case ColorID::MozMenuhovertext:
118 if (UseNonNativeMenuColors(aScheme)) {
119 return false;
121 return !mColorMenuHoverText;
122 case ColorID::Highlighttext:
123 case ColorID::Selecteditemtext:
124 // We prefer the generic dark selection color if we don't have an
125 // explicit one.
126 return aScheme != ColorScheme::Dark || mDarkHighlightText;
127 case ColorID::IMESelectedRawTextForeground:
128 case ColorID::IMESelectedConvertedTextForeground:
129 return true;
130 default:
131 return false;
135 if (IsHighlightColor()) {
136 if (aScheme == ColorScheme::Dark && mDarkHighlight) {
137 aColor = *mDarkHighlight;
138 } else {
139 aColor = GetColorForSysColorIndex(COLOR_HIGHLIGHT);
141 return NS_OK;
144 if (IsHighlightTextColor()) {
145 if (aScheme == ColorScheme::Dark && mDarkHighlightText) {
146 aColor = *mDarkHighlightText;
147 } else {
148 aColor = GetColorForSysColorIndex(COLOR_HIGHLIGHTTEXT);
150 return NS_OK;
153 // Titlebar colors are color-scheme aware.
154 switch (aID) {
155 case ColorID::Activecaption:
156 aColor = mTitlebarColors.Get(aScheme, true).mBg;
157 return NS_OK;
158 case ColorID::Captiontext:
159 aColor = mTitlebarColors.Get(aScheme, true).mFg;
160 return NS_OK;
161 case ColorID::Activeborder:
162 aColor = mTitlebarColors.Get(aScheme, true).mBorder;
163 return NS_OK;
164 case ColorID::Inactivecaption:
165 aColor = mTitlebarColors.Get(aScheme, false).mBg;
166 return NS_OK;
167 case ColorID::Inactivecaptiontext:
168 aColor = mTitlebarColors.Get(aScheme, false).mFg;
169 return NS_OK;
170 case ColorID::Inactiveborder:
171 aColor = mTitlebarColors.Get(aScheme, false).mBorder;
172 return NS_OK;
173 default:
174 break;
177 if (aScheme == ColorScheme::Dark) {
178 if (auto color = GenericDarkColor(aID)) {
179 aColor = *color;
180 return NS_OK;
184 static constexpr auto kNonNativeMenuText = NS_RGB(0x15, 0x14, 0x1a);
185 nsresult res = NS_OK;
186 int idx;
187 switch (aID) {
188 case ColorID::IMERawInputBackground:
189 case ColorID::IMEConvertedTextBackground:
190 aColor = NS_TRANSPARENT;
191 return NS_OK;
192 case ColorID::IMERawInputForeground:
193 case ColorID::IMEConvertedTextForeground:
194 aColor = NS_SAME_AS_FOREGROUND_COLOR;
195 return NS_OK;
196 case ColorID::IMERawInputUnderline:
197 case ColorID::IMEConvertedTextUnderline:
198 aColor = NS_SAME_AS_FOREGROUND_COLOR;
199 return NS_OK;
200 case ColorID::IMESelectedRawTextUnderline:
201 case ColorID::IMESelectedConvertedTextUnderline:
202 aColor = NS_TRANSPARENT;
203 return NS_OK;
205 // New CSS 2 Color definitions
206 case ColorID::Appworkspace:
207 idx = COLOR_APPWORKSPACE;
208 break;
209 case ColorID::Background:
210 idx = COLOR_BACKGROUND;
211 break;
212 case ColorID::Buttonface:
213 case ColorID::MozButtonhoverface:
214 case ColorID::MozButtonactiveface:
215 case ColorID::MozButtondisabledface:
216 case ColorID::MozColheader:
217 case ColorID::MozColheaderhover:
218 case ColorID::MozColheaderactive:
219 idx = COLOR_BTNFACE;
220 break;
221 case ColorID::Buttonhighlight:
222 idx = COLOR_BTNHIGHLIGHT;
223 break;
224 case ColorID::Buttonshadow:
225 idx = COLOR_BTNSHADOW;
226 break;
227 case ColorID::Buttontext:
228 case ColorID::MozButtonhovertext:
229 case ColorID::MozButtonactivetext:
230 idx = COLOR_BTNTEXT;
231 break;
232 case ColorID::MozCellhighlighttext:
233 aColor = NS_RGB(0, 0, 0);
234 return NS_OK;
235 case ColorID::MozCellhighlight:
236 aColor = NS_RGB(206, 206, 206);
237 return NS_OK;
238 case ColorID::Graytext:
239 idx = COLOR_GRAYTEXT;
240 break;
241 case ColorID::MozMenubarhovertext:
242 if (UseNonNativeMenuColors(aScheme)) {
243 aColor = kNonNativeMenuText;
244 return NS_OK;
246 [[fallthrough]];
247 case ColorID::MozMenuhovertext:
248 if (UseNonNativeMenuColors(aScheme)) {
249 aColor = kNonNativeMenuText;
250 return NS_OK;
252 if (mColorMenuHoverText) {
253 aColor = *mColorMenuHoverText;
254 return NS_OK;
256 idx = COLOR_HIGHLIGHTTEXT;
257 break;
258 case ColorID::MozMenuhover:
259 MOZ_ASSERT(UseNonNativeMenuColors(aScheme));
260 aColor = NS_RGB(0xe0, 0xe0, 0xe6);
261 return NS_OK;
262 case ColorID::MozMenuhoverdisabled:
263 if (UseNonNativeMenuColors(aScheme)) {
264 aColor = NS_RGB(0xf0, 0xf0, 0xf3);
265 return NS_OK;
267 aColor = NS_TRANSPARENT;
268 return NS_OK;
269 case ColorID::Infobackground:
270 idx = COLOR_INFOBK;
271 break;
272 case ColorID::Infotext:
273 idx = COLOR_INFOTEXT;
274 break;
275 case ColorID::Menu:
276 if (UseNonNativeMenuColors(aScheme)) {
277 aColor = NS_RGB(0xf9, 0xf9, 0xfb);
278 return NS_OK;
280 idx = COLOR_MENU;
281 break;
282 case ColorID::Menutext:
283 if (UseNonNativeMenuColors(aScheme)) {
284 aColor = kNonNativeMenuText;
285 return NS_OK;
287 idx = COLOR_MENUTEXT;
288 break;
289 case ColorID::Scrollbar:
290 idx = COLOR_SCROLLBAR;
291 break;
292 case ColorID::Threeddarkshadow:
293 idx = COLOR_3DDKSHADOW;
294 break;
295 case ColorID::Threedface:
296 idx = COLOR_3DFACE;
297 break;
298 case ColorID::Threedhighlight:
299 idx = COLOR_3DHIGHLIGHT;
300 break;
301 case ColorID::Threedlightshadow:
302 case ColorID::Buttonborder:
303 case ColorID::MozDisabledfield:
304 case ColorID::MozSidebarborder:
305 idx = COLOR_3DLIGHT;
306 break;
307 case ColorID::Threedshadow:
308 idx = COLOR_3DSHADOW;
309 break;
310 case ColorID::Window:
311 idx = COLOR_WINDOW;
312 break;
313 case ColorID::Windowframe:
314 idx = COLOR_WINDOWFRAME;
315 break;
316 case ColorID::Windowtext:
317 idx = COLOR_WINDOWTEXT;
318 break;
319 case ColorID::MozEventreerow:
320 case ColorID::MozOddtreerow:
321 case ColorID::Field:
322 case ColorID::MozSidebar:
323 case ColorID::MozCombobox:
324 idx = COLOR_WINDOW;
325 break;
326 case ColorID::Fieldtext:
327 case ColorID::MozSidebartext:
328 case ColorID::MozComboboxtext:
329 idx = COLOR_WINDOWTEXT;
330 break;
331 case ColorID::MozHeaderbar:
332 case ColorID::MozHeaderbarinactive:
333 case ColorID::MozDialog:
334 idx = COLOR_3DFACE;
335 break;
336 case ColorID::Accentcolor:
337 aColor = mColorAccent;
338 return NS_OK;
339 case ColorID::Accentcolortext:
340 aColor = mColorAccentText;
341 return NS_OK;
342 case ColorID::MozHeaderbartext:
343 case ColorID::MozHeaderbarinactivetext:
344 case ColorID::MozDialogtext:
345 case ColorID::MozColheadertext:
346 case ColorID::MozColheaderhovertext:
347 case ColorID::MozColheaderactivetext:
348 idx = COLOR_WINDOWTEXT;
349 break;
350 case ColorID::MozNativehyperlinktext:
351 idx = COLOR_HOTLIGHT;
352 break;
353 case ColorID::Marktext:
354 case ColorID::Mark:
355 case ColorID::SpellCheckerUnderline:
356 aColor = GetStandinForNativeColor(aID, aScheme);
357 return NS_OK;
358 default:
359 idx = COLOR_WINDOW;
360 res = NS_ERROR_FAILURE;
361 break;
364 aColor = GetColorForSysColorIndex(idx);
366 return res;
369 nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
370 EnsureInit();
371 nsresult res = NS_OK;
373 switch (aID) {
374 case IntID::ScrollButtonLeftMouseButtonAction:
375 aResult = 0;
376 break;
377 case IntID::ScrollButtonMiddleMouseButtonAction:
378 case IntID::ScrollButtonRightMouseButtonAction:
379 aResult = 3;
380 break;
381 case IntID::CaretBlinkTime:
382 aResult = static_cast<int32_t>(::GetCaretBlinkTime());
383 break;
384 case IntID::CaretBlinkCount: {
385 int32_t timeout = GetSystemParam(SPI_GETCARETTIMEOUT, 5000);
386 auto blinkTime = ::GetCaretBlinkTime();
387 if (timeout <= 0 || blinkTime <= 0) {
388 aResult = -1;
389 break;
391 // 2 * blinkTime because this integer is a full blink cycle.
392 aResult = std::ceil(float(timeout) / (2.0f * float(blinkTime)));
393 break;
396 case IntID::CaretWidth:
397 aResult = 1;
398 break;
399 case IntID::ShowCaretDuringSelection:
400 aResult = 0;
401 break;
402 case IntID::SelectTextfieldsOnKeyFocus:
403 // Select textfield content when focused by kbd
404 // used by EventStateManager::sTextfieldSelectModel
405 aResult = 1;
406 break;
407 case IntID::SubmenuDelay:
408 // This will default to the Windows' default
409 // (400ms) on error.
410 aResult = GetSystemParam(SPI_GETMENUSHOWDELAY, 400);
411 break;
412 case IntID::MenusCanOverlapOSBar:
413 // we want XUL popups to be able to overlap the task bar.
414 aResult = 1;
415 break;
416 case IntID::DragThresholdX:
417 // The system metric is the number of pixels at which a drag should
418 // start. Our look and feel metric is the number of pixels you can
419 // move before starting a drag, so subtract 1.
420 aResult = ::GetSystemMetrics(SM_CXDRAG) - 1;
421 break;
422 case IntID::DragThresholdY:
423 aResult = ::GetSystemMetrics(SM_CYDRAG) - 1;
424 break;
425 case IntID::UseAccessibilityTheme:
426 // High contrast is a misnomer under Win32 -- any theme can be used with
427 // it, e.g. normal contrast with large fonts, low contrast, etc. The high
428 // contrast flag really means -- use this theme and don't override it.
429 aResult = nsUXThemeData::IsHighContrastOn();
430 break;
431 case IntID::ScrollArrowStyle:
432 aResult = eScrollArrowStyle_Single;
433 break;
434 case IntID::TreeOpenDelay:
435 aResult = 1000;
436 break;
437 case IntID::TreeCloseDelay:
438 aResult = 0;
439 break;
440 case IntID::TreeLazyScrollDelay:
441 aResult = 150;
442 break;
443 case IntID::TreeScrollDelay:
444 aResult = 100;
445 break;
446 case IntID::TreeScrollLinesMax:
447 aResult = 3;
448 break;
449 case IntID::WindowsAccentColorInTitlebar: {
450 aResult = mTitlebarColors.mUseAccent;
451 } break;
452 case IntID::AlertNotificationOrigin:
453 aResult = 0;
455 // Get task bar window handle
456 HWND shellWindow = FindWindowW(L"Shell_TrayWnd", nullptr);
458 if (shellWindow != nullptr) {
459 // Determine position
460 APPBARDATA appBarData;
461 appBarData.hWnd = shellWindow;
462 appBarData.cbSize = sizeof(appBarData);
463 if (SHAppBarMessage(ABM_GETTASKBARPOS, &appBarData)) {
464 // Set alert origin as a bit field - see LookAndFeel.h
465 // 0 represents bottom right, sliding vertically.
466 switch (appBarData.uEdge) {
467 case ABE_LEFT:
468 aResult = NS_ALERT_HORIZONTAL | NS_ALERT_LEFT;
469 break;
470 case ABE_RIGHT:
471 aResult = NS_ALERT_HORIZONTAL;
472 break;
473 case ABE_TOP:
474 aResult = NS_ALERT_TOP;
475 [[fallthrough]];
476 case ABE_BOTTOM:
477 // If the task bar is right-to-left,
478 // move the origin to the left
479 if (::GetWindowLong(shellWindow, GWL_EXSTYLE) & WS_EX_LAYOUTRTL)
480 aResult |= NS_ALERT_LEFT;
481 break;
486 break;
487 case IntID::IMERawInputUnderlineStyle:
488 case IntID::IMEConvertedTextUnderlineStyle:
489 aResult = static_cast<int32_t>(StyleTextDecorationStyle::Dashed);
490 break;
491 case IntID::IMESelectedRawTextUnderlineStyle:
492 case IntID::IMESelectedConvertedTextUnderline:
493 aResult = static_cast<int32_t>(StyleTextDecorationStyle::None);
494 break;
495 case IntID::SpellCheckerUnderlineStyle:
496 aResult = static_cast<int32_t>(StyleTextDecorationStyle::Wavy);
497 break;
498 case IntID::ScrollbarButtonAutoRepeatBehavior:
499 aResult = 0;
500 break;
501 case IntID::SwipeAnimationEnabled:
502 // Forcibly enable the swipe animation on Windows. It doesn't matter on
503 // platforms where "Drag two fingers to scroll" isn't supported since on
504 // the platforms we will never generate any swipe gesture events.
505 aResult = 1;
506 break;
507 case IntID::UseOverlayScrollbars:
508 aResult = WindowsUIUtils::ComputeOverlayScrollbars();
509 break;
510 case IntID::AllowOverlayScrollbarsOverlap:
511 aResult = 0;
512 break;
513 case IntID::ScrollbarDisplayOnMouseMove:
514 aResult = 1;
515 break;
516 case IntID::ScrollbarFadeBeginDelay:
517 aResult = 2500;
518 break;
519 case IntID::ScrollbarFadeDuration:
520 aResult = 350;
521 break;
522 case IntID::ContextMenuOffsetVertical:
523 case IntID::ContextMenuOffsetHorizontal:
524 aResult = 2;
525 break;
526 case IntID::SystemUsesDarkTheme:
527 aResult = SystemWantsDarkTheme();
528 break;
529 case IntID::SystemScrollbarSize:
530 aResult = std::max(WinUtils::GetSystemMetricsForDpi(SM_CXVSCROLL, 96),
531 WinUtils::GetSystemMetricsForDpi(SM_CXHSCROLL, 96));
532 break;
533 case IntID::PrefersReducedMotion: {
534 BOOL enable = TRUE;
535 ::SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &enable, 0);
536 aResult = !enable;
537 break;
539 case IntID::PrefersReducedTransparency: {
540 // Prefers reduced transparency if the option for "Transparency Effects"
541 // is disabled
542 aResult = !WindowsUIUtils::ComputeTransparencyEffects();
543 break;
545 case IntID::InvertedColors: {
546 // Color filter values
547 // 1: Inverted
548 // 2: Grayscale inverted
549 aResult = mCurrentColorFilter == 1 || mCurrentColorFilter == 2;
550 break;
552 case IntID::PrimaryPointerCapabilities: {
553 aResult = static_cast<int32_t>(
554 widget::WinUtils::GetPrimaryPointerCapabilities());
555 break;
557 case IntID::AllPointerCapabilities: {
558 aResult =
559 static_cast<int32_t>(widget::WinUtils::GetAllPointerCapabilities());
560 break;
562 case IntID::TouchDeviceSupportPresent:
563 aResult = !!WinUtils::IsTouchDeviceSupportPresent();
564 break;
565 case IntID::PanelAnimations:
566 aResult = 1;
567 break;
568 case IntID::HideCursorWhileTyping: {
569 BOOL enable = TRUE;
570 ::SystemParametersInfoW(SPI_GETMOUSEVANISH, 0, &enable, 0);
571 aResult = enable;
572 break;
574 default:
575 aResult = 0;
576 res = NS_ERROR_FAILURE;
578 return res;
581 nsresult nsLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) {
582 nsresult res = NS_OK;
584 switch (aID) {
585 case FloatID::IMEUnderlineRelativeSize:
586 aResult = 1.0f;
587 break;
588 case FloatID::SpellCheckerUnderlineRelativeSize:
589 aResult = 1.0f;
590 break;
591 case FloatID::TextScaleFactor:
592 aResult = WindowsUIUtils::ComputeTextScaleFactor();
593 break;
594 default:
595 aResult = -1.0;
596 res = NS_ERROR_FAILURE;
598 return res;
601 LookAndFeelFont nsLookAndFeel::GetLookAndFeelFontInternal(
602 const LOGFONTW& aLogFont, bool aUseShellDlg) {
603 LookAndFeelFont result{};
605 result.haveFont() = false;
607 // Get scaling factor from physical to logical pixels
608 double pixelScale =
609 1.0 / WinUtils::SystemScaleFactor() / LookAndFeel::GetTextScaleFactor();
611 // The lfHeight is in pixels, and it needs to be adjusted for the
612 // device it will be displayed on.
613 // Screens and Printers will differ in DPI
615 // So this accounts for the difference in the DeviceContexts
616 // The pixelScale will typically be 1.0 for the screen
617 // (though larger for hi-dpi screens where the Windows resolution
618 // scale factor is 125% or 150% or even more), and could be
619 // any value when going to a printer, for example pixelScale is
620 // 6.25 when going to a 600dpi printer.
621 float pixelHeight = -aLogFont.lfHeight;
622 if (pixelHeight < 0) {
623 nsAutoFont hFont(::CreateFontIndirectW(&aLogFont));
624 if (!hFont) {
625 return result;
628 nsAutoHDC dc(::GetDC(nullptr));
629 HGDIOBJ hObject = ::SelectObject(dc, hFont);
630 TEXTMETRIC tm;
631 ::GetTextMetrics(dc, &tm);
632 ::SelectObject(dc, hObject);
634 pixelHeight = tm.tmAscent;
637 pixelHeight *= pixelScale;
639 // we have problem on Simplified Chinese system because the system
640 // report the default font size is 8 points. but if we use 8, the text
641 // display very ugly. force it to be at 9 points (12 pixels) on that
642 // system (cp936), but leave other sizes alone.
643 if (pixelHeight < 12 && ::GetACP() == 936) {
644 pixelHeight = 12;
647 result.haveFont() = true;
649 if (aUseShellDlg) {
650 result.name() = u"MS Shell Dlg 2"_ns;
651 } else {
652 result.name() = aLogFont.lfFaceName;
655 result.size() = pixelHeight;
656 result.italic() = !!aLogFont.lfItalic;
657 // FIXME: Other weights?
658 result.weight() =
659 ((aLogFont.lfWeight == FW_BOLD) ? FontWeight::BOLD : FontWeight::NORMAL)
660 .ToFloat();
662 return result;
665 LookAndFeelFont nsLookAndFeel::GetLookAndFeelFont(LookAndFeel::FontID anID) {
666 LookAndFeelFont result{};
668 result.haveFont() = false;
670 // FontID::Icon is handled differently than the others
671 if (anID == LookAndFeel::FontID::Icon) {
672 LOGFONTW logFont;
673 if (::SystemParametersInfoW(SPI_GETICONTITLELOGFONT, sizeof(logFont),
674 (PVOID)&logFont, 0)) {
675 result = GetLookAndFeelFontInternal(logFont, false);
677 return result;
680 NONCLIENTMETRICSW ncm;
681 ncm.cbSize = sizeof(NONCLIENTMETRICSW);
682 if (!::SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm),
683 (PVOID)&ncm, 0)) {
684 return result;
687 switch (anID) {
688 case LookAndFeel::FontID::Menu:
689 case LookAndFeel::FontID::MozPullDownMenu:
690 result = GetLookAndFeelFontInternal(ncm.lfMenuFont, false);
691 break;
692 case LookAndFeel::FontID::Caption:
693 result = GetLookAndFeelFontInternal(ncm.lfCaptionFont, false);
694 break;
695 case LookAndFeel::FontID::SmallCaption:
696 result = GetLookAndFeelFontInternal(ncm.lfSmCaptionFont, false);
697 break;
698 case LookAndFeel::FontID::StatusBar:
699 result = GetLookAndFeelFontInternal(ncm.lfStatusFont, false);
700 break;
701 case LookAndFeel::FontID::MozButton:
702 case LookAndFeel::FontID::MozField:
703 case LookAndFeel::FontID::MozList:
704 // XXX It's not clear to me whether this is exactly the right
705 // set of LookAndFeel values to map to the dialog font; we may
706 // want to add or remove cases here after reviewing the visual
707 // results under various Windows versions.
708 result = GetLookAndFeelFontInternal(ncm.lfMessageFont, true);
709 break;
710 default:
711 result = GetLookAndFeelFontInternal(ncm.lfMessageFont, false);
712 break;
715 return result;
718 bool nsLookAndFeel::NativeGetFont(LookAndFeel::FontID anID, nsString& aFontName,
719 gfxFontStyle& aFontStyle) {
720 LookAndFeelFont font = GetLookAndFeelFont(anID);
721 return LookAndFeelFontToStyle(font, aFontName, aFontStyle);
724 /* virtual */
725 char16_t nsLookAndFeel::GetPasswordCharacterImpl() {
726 #define UNICODE_BLACK_CIRCLE_CHAR 0x25cf
727 return UNICODE_BLACK_CIRCLE_CHAR;
730 static nscolor GetAccentColorText(const nscolor aAccentColor) {
731 // We want the color that we return for text that will be drawn over
732 // a background that has the accent color to have good contrast with
733 // the accent color. Windows itself uses either white or black text
734 // depending on how light or dark the accent color is. We do the same
735 // here based on the luminance of the accent color with a threshhold
736 // value. This algorithm should match what Windows does. It comes from:
738 // https://docs.microsoft.com/en-us/windows/uwp/style/color
739 float luminance = (NS_GET_R(aAccentColor) * 2 + NS_GET_G(aAccentColor) * 5 +
740 NS_GET_B(aAccentColor)) /
742 return luminance <= 128 ? NS_RGB(255, 255, 255) : NS_RGB(0, 0, 0);
745 static Maybe<nscolor> GetAccentColorText(const Maybe<nscolor>& aAccentColor) {
746 if (!aAccentColor) {
747 return Nothing();
749 return Some(GetAccentColorText(*aAccentColor));
752 nscolor nsLookAndFeel::GetColorForSysColorIndex(int index) {
753 MOZ_ASSERT(index >= SYS_COLOR_MIN && index <= SYS_COLOR_MAX);
754 return mSysColorTable[index - SYS_COLOR_MIN];
757 auto nsLookAndFeel::ComputeTitlebarColors() -> TitlebarColors {
758 TitlebarColors result;
760 // Start with the native / non-accent-in-titlebar colors.
761 result.mActiveLight = {GetColorForSysColorIndex(COLOR_ACTIVECAPTION),
762 GetColorForSysColorIndex(COLOR_CAPTIONTEXT),
763 GetColorForSysColorIndex(COLOR_ACTIVEBORDER)};
765 result.mInactiveLight = {GetColorForSysColorIndex(COLOR_INACTIVECAPTION),
766 GetColorForSysColorIndex(COLOR_INACTIVECAPTIONTEXT),
767 GetColorForSysColorIndex(COLOR_INACTIVEBORDER)};
769 if (!nsUXThemeData::IsHighContrastOn()) {
770 // Use our non-native colors.
771 result.mActiveLight = {
772 GetStandinForNativeColor(ColorID::Activecaption, ColorScheme::Light),
773 GetStandinForNativeColor(ColorID::Captiontext, ColorScheme::Light),
774 GetStandinForNativeColor(ColorID::Activeborder, ColorScheme::Light)};
775 result.mInactiveLight = {
776 GetStandinForNativeColor(ColorID::Inactivecaption, ColorScheme::Light),
777 GetStandinForNativeColor(ColorID::Inactivecaptiontext,
778 ColorScheme::Light),
779 GetStandinForNativeColor(ColorID::Inactiveborder, ColorScheme::Light)};
782 // Our dark colors are always non-native.
783 result.mActiveDark = {*GenericDarkColor(ColorID::Activecaption),
784 *GenericDarkColor(ColorID::Captiontext),
785 *GenericDarkColor(ColorID::Activeborder)};
786 result.mInactiveDark = {*GenericDarkColor(ColorID::Inactivecaption),
787 *GenericDarkColor(ColorID::Inactivecaptiontext),
788 *GenericDarkColor(ColorID::Inactiveborder)};
790 // TODO(bug 1825241): Somehow get notified when this changes? Hopefully the
791 // sys color notification is enough.
792 WinRegistry::Key dwmKey(HKEY_CURRENT_USER,
793 u"SOFTWARE\\Microsoft\\Windows\\DWM"_ns,
794 WinRegistry::KeyMode::QueryValue);
795 if (NS_WARN_IF(!dwmKey)) {
796 return result;
799 // The order of the color components in the DWORD stored in the registry
800 // happens to be the same order as we store the components in nscolor
801 // so we can just assign directly here.
802 result.mAccent = dwmKey.GetValueAsDword(u"AccentColor"_ns);
803 result.mAccentText = GetAccentColorText(result.mAccent);
805 if (!result.mAccent) {
806 return result;
809 result.mAccentInactive = dwmKey.GetValueAsDword(u"AccentColorInactive"_ns);
810 result.mAccentInactiveText = GetAccentColorText(result.mAccentInactive);
812 // The ColorPrevalence value is set to 1 when the "Show color on title bar"
813 // setting in the Color section of Window's Personalization settings is
814 // turned on.
815 result.mUseAccent =
816 dwmKey.GetValueAsDword(u"ColorPrevalence"_ns).valueOr(0) == 1;
817 if (!result.mUseAccent) {
818 return result;
821 // TODO(emilio): Consider reading ColorizationColorBalance to compute a
822 // more correct border color, see [1]. Though for opaque accent colors this
823 // isn't needed.
825 // [1]:
826 // https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:ui/color/win/accent_color_observer.cc;l=42;drc=9d4eb7ed25296abba8fd525a6bdd0fdbf4bcdd9f
827 result.mActiveDark.mBorder = result.mActiveLight.mBorder = *result.mAccent;
828 result.mInactiveDark.mBorder = result.mInactiveLight.mBorder =
829 result.mAccentInactive.valueOr(NS_RGB(57, 57, 57));
830 result.mActiveLight.mBg = result.mActiveDark.mBg = *result.mAccent;
831 result.mActiveLight.mFg = result.mActiveDark.mFg = *result.mAccentText;
832 if (result.mAccentInactive) {
833 result.mInactiveLight.mBg = result.mInactiveDark.mBg =
834 *result.mAccentInactive;
835 result.mInactiveLight.mFg = result.mInactiveDark.mFg =
836 *result.mAccentInactiveText;
837 } else {
838 // This is hand-picked to .8 to change the accent color a bit but not too
839 // much.
840 constexpr uint8_t kBgAlpha = 208;
841 const auto BlendWithAlpha = [](nscolor aBg, nscolor aFg,
842 uint8_t aAlpha) -> nscolor {
843 return NS_ComposeColors(
844 aBg, NS_RGBA(NS_GET_R(aFg), NS_GET_G(aFg), NS_GET_B(aFg), aAlpha));
846 result.mInactiveLight.mBg =
847 BlendWithAlpha(NS_RGB(255, 255, 255), *result.mAccent, kBgAlpha);
848 result.mInactiveDark.mBg =
849 BlendWithAlpha(NS_RGB(0, 0, 0), *result.mAccent, kBgAlpha);
850 result.mInactiveLight.mFg = result.mInactiveDark.mFg = *result.mAccentText;
852 return result;
855 void nsLookAndFeel::EnsureInit() {
856 if (mInitialized) {
857 return;
859 mInitialized = true;
861 mColorMenuHoverText =
862 ::GetColorFromTheme(eUXMenu, MENU_POPUPITEM, MPI_HOT, TMT_TEXTCOLOR);
864 // Fill out the sys color table.
865 for (int i = SYS_COLOR_MIN; i <= SYS_COLOR_MAX; ++i) {
866 mSysColorTable[i - SYS_COLOR_MIN] = [&] {
867 if (auto c = WindowsUIUtils::GetSystemColor(ColorScheme::Light, i)) {
868 return *c;
870 DWORD color = ::GetSysColor(i);
871 return COLOREF_2_NSRGB(color);
872 }();
875 mDarkHighlight =
876 WindowsUIUtils::GetSystemColor(ColorScheme::Dark, COLOR_HIGHLIGHT);
877 mDarkHighlightText =
878 WindowsUIUtils::GetSystemColor(ColorScheme::Dark, COLOR_HIGHLIGHTTEXT);
880 mTitlebarColors = ComputeTitlebarColors();
882 mColorAccent = [&] {
883 if (auto accent = WindowsUIUtils::GetAccentColor()) {
884 return *accent;
886 // Try the titlebar accent as a fallback.
887 if (mTitlebarColors.mAccent) {
888 return *mTitlebarColors.mAccent;
890 // Seems to be the default color (hardcoded because of bug 1065998)
891 return NS_RGB(0, 120, 215);
892 }();
893 mColorAccentText = GetAccentColorText(mColorAccent);
895 if (!mColorFilterWatcher) {
896 WinRegistry::Key key(
897 HKEY_CURRENT_USER, u"Software\\Microsoft\\ColorFiltering"_ns,
898 WinRegistry::KeyMode::QueryValue | WinRegistry::KeyMode::Notify);
899 if (key) {
900 mColorFilterWatcher = MakeUnique<WinRegistry::KeyWatcher>(
901 std::move(key), GetCurrentSerialEventTarget(), [this] {
902 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
903 if (mCurrentColorFilter != SystemColorFilter()) {
904 LookAndFeel::NotifyChangedAllWindows(
905 widget::ThemeChangeKind::MediaQueriesOnly);
910 mCurrentColorFilter = SystemColorFilter();
912 RecordTelemetry();