Bug 1861709 replace AudioCallbackDriver::ThreadRunning() assertions that mean to...
[gecko.git] / widget / windows / nsNativeThemeWin.cpp
blob1a8af27882e5009a58021f8222813cf644eb8fc8
1 /* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsNativeThemeWin.h"
8 #include <algorithm>
9 #include <malloc.h>
11 #include "gfxContext.h"
12 #include "gfxPlatform.h"
13 #include "gfxWindowsNativeDrawing.h"
14 #include "gfxWindowsPlatform.h"
15 #include "gfxWindowsSurface.h"
16 #include "mozilla/ClearOnShutdown.h"
17 #include "mozilla/gfx/Types.h" // for Color::FromABGR
18 #include "mozilla/Logging.h"
19 #include "mozilla/RelativeLuminanceUtils.h"
20 #include "mozilla/StaticPrefs_layout.h"
21 #include "mozilla/StaticPrefs_widget.h"
22 #include "mozilla/dom/XULButtonElement.h"
23 #include "nsColor.h"
24 #include "nsComboboxControlFrame.h"
25 #include "nsDeviceContext.h"
26 #include "nsGkAtoms.h"
27 #include "nsIContent.h"
28 #include "nsIContentInlines.h"
29 #include "nsIFrame.h"
30 #include "nsLayoutUtils.h"
31 #include "nsLookAndFeel.h"
32 #include "nsNameSpaceManager.h"
33 #include "Theme.h"
34 #include "nsPresContext.h"
35 #include "nsRect.h"
36 #include "nsSize.h"
37 #include "nsStyleConsts.h"
38 #include "nsTransform2D.h"
39 #include "nsWindow.h"
40 #include "prinrval.h"
41 #include "WinUtils.h"
43 using namespace mozilla;
44 using namespace mozilla::gfx;
45 using namespace mozilla::widget;
47 using ElementState = dom::ElementState;
49 extern mozilla::LazyLogModule gWindowsLog;
51 namespace mozilla::widget {
53 nsNativeThemeWin::nsNativeThemeWin()
54 : Theme(ScrollbarStyle()),
55 mProgressDeterminateTimeStamp(TimeStamp::Now()),
56 mProgressIndeterminateTimeStamp(TimeStamp::Now()),
57 mBorderCacheValid(),
58 mMinimumWidgetSizeCacheValid(),
59 mGutterSizeCacheValid(false) {}
61 nsNativeThemeWin::~nsNativeThemeWin() { nsUXThemeData::Invalidate(); }
63 bool nsNativeThemeWin::IsWidgetAlwaysNonNative(nsIFrame* aFrame,
64 StyleAppearance aAppearance) {
65 return Theme::IsWidgetAlwaysNonNative(aFrame, aAppearance) ||
66 aAppearance == StyleAppearance::Checkbox ||
67 aAppearance == StyleAppearance::Radio ||
68 aAppearance == StyleAppearance::MozMenulistArrowButton ||
69 aAppearance == StyleAppearance::SpinnerUpbutton ||
70 aAppearance == StyleAppearance::SpinnerDownbutton;
73 auto nsNativeThemeWin::IsWidgetNonNative(nsIFrame* aFrame,
74 StyleAppearance aAppearance)
75 -> NonNative {
76 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
77 return NonNative::Always;
80 // We only know how to draw light widgets, so we defer to the non-native
81 // theme when appropriate.
82 if (Theme::ThemeSupportsWidget(aFrame->PresContext(), aFrame, aAppearance) &&
83 LookAndFeel::ColorSchemeForFrame(aFrame) ==
84 LookAndFeel::ColorScheme::Dark) {
85 return NonNative::BecauseColorMismatch;
87 return NonNative::No;
90 static MARGINS GetCheckboxMargins(HANDLE theme, HDC hdc) {
91 MARGINS checkboxContent = {0};
92 GetThemeMargins(theme, hdc, MENU_POPUPCHECK, MCB_NORMAL, TMT_CONTENTMARGINS,
93 nullptr, &checkboxContent);
94 return checkboxContent;
97 static SIZE GetCheckboxBGSize(HANDLE theme, HDC hdc) {
98 SIZE checkboxSize;
99 GetThemePartSize(theme, hdc, MENU_POPUPCHECK, MC_CHECKMARKNORMAL, nullptr,
100 TS_TRUE, &checkboxSize);
102 MARGINS checkboxMargins = GetCheckboxMargins(theme, hdc);
104 int leftMargin = checkboxMargins.cxLeftWidth;
105 int rightMargin = checkboxMargins.cxRightWidth;
106 int topMargin = checkboxMargins.cyTopHeight;
107 int bottomMargin = checkboxMargins.cyBottomHeight;
109 int width = leftMargin + checkboxSize.cx + rightMargin;
110 int height = topMargin + checkboxSize.cy + bottomMargin;
111 SIZE ret;
112 ret.cx = width;
113 ret.cy = height;
114 return ret;
117 static SIZE GetCheckboxBGBounds(HANDLE theme, HDC hdc) {
118 MARGINS checkboxBGSizing = {0};
119 MARGINS checkboxBGContent = {0};
120 GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL,
121 TMT_SIZINGMARGINS, nullptr, &checkboxBGSizing);
122 GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL,
123 TMT_CONTENTMARGINS, nullptr, &checkboxBGContent);
125 #define posdx(d) ((d) > 0 ? d : 0)
127 int dx =
128 posdx(checkboxBGContent.cxRightWidth - checkboxBGSizing.cxRightWidth) +
129 posdx(checkboxBGContent.cxLeftWidth - checkboxBGSizing.cxLeftWidth);
130 int dy =
131 posdx(checkboxBGContent.cyTopHeight - checkboxBGSizing.cyTopHeight) +
132 posdx(checkboxBGContent.cyBottomHeight - checkboxBGSizing.cyBottomHeight);
134 #undef posdx
136 SIZE ret(GetCheckboxBGSize(theme, hdc));
137 ret.cx += dx;
138 ret.cy += dy;
139 return ret;
142 static SIZE GetGutterSize(HANDLE theme, HDC hdc) {
143 SIZE gutterSize;
144 GetThemePartSize(theme, hdc, MENU_POPUPGUTTER, 0, nullptr, TS_TRUE,
145 &gutterSize);
147 SIZE checkboxBGSize(GetCheckboxBGBounds(theme, hdc));
149 SIZE itemSize;
150 GetThemePartSize(theme, hdc, MENU_POPUPITEM, MPI_NORMAL, nullptr, TS_TRUE,
151 &itemSize);
153 // Figure out how big the menuitem's icon will be (if present) at current DPI
154 // Needs the system scale for consistency with Windows Theme API.
155 double scaleFactor = WinUtils::SystemScaleFactor();
156 int iconDevicePixels = NSToIntRound(16 * scaleFactor);
157 SIZE iconSize = {iconDevicePixels, iconDevicePixels};
158 // Not really sure what margins should be used here, but this seems to work in
159 // practice...
160 MARGINS margins = {0};
161 GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL,
162 TMT_CONTENTMARGINS, nullptr, &margins);
163 iconSize.cx += margins.cxLeftWidth + margins.cxRightWidth;
164 iconSize.cy += margins.cyTopHeight + margins.cyBottomHeight;
166 int width = std::max(
167 itemSize.cx, std::max(iconSize.cx, checkboxBGSize.cx) + gutterSize.cx);
168 int height = std::max(itemSize.cy, std::max(iconSize.cy, checkboxBGSize.cy));
170 SIZE ret;
171 ret.cx = width;
172 ret.cy = height;
173 return ret;
176 SIZE nsNativeThemeWin::GetCachedGutterSize(HANDLE theme) {
177 if (mGutterSizeCacheValid) {
178 return mGutterSizeCache;
181 mGutterSizeCache = GetGutterSize(theme, nullptr);
182 mGutterSizeCacheValid = true;
184 return mGutterSizeCache;
188 * Notes on progress track and meter part constants:
189 * xp and up:
190 * PP_BAR(_VERT) - base progress track
191 * PP_TRANSPARENTBAR(_VERT) - transparent progress track. this only works if
192 * the underlying surface supports alpha. otherwise
193 * theme lib's DrawThemeBackground falls back on
194 * opaque PP_BAR. we currently don't use this.
195 * PP_CHUNK(_VERT) - xp progress meter. this does not draw an xp style
196 * progress w/chunks, it draws fill using the chunk
197 * graphic.
198 * vista and up:
199 * PP_FILL(_VERT) - progress meter. these have four states/colors.
200 * PP_PULSEOVERLAY(_VERT) - white reflection - an overlay, not sure what this
201 * is used for.
202 * PP_MOVEOVERLAY(_VERT) - green pulse - the pulse effect overlay on
203 * determined progress bars. we also use this for
204 * indeterminate chunk.
206 * Notes on state constants:
207 * PBBS_NORMAL - green progress
208 * PBBVS_PARTIAL/PBFVS_ERROR - red error progress
209 * PBFS_PAUSED - yellow paused progress
211 * There is no common controls style indeterminate part on vista and up.
215 * Progress bar related constants. These values are found by experimenting and
216 * comparing against native widgets used by the system. They are very unlikely
217 * exact but try to not be too wrong.
219 // The amount of time we animate progress meters parts across the frame.
220 static const double kProgressDeterminateTimeSpan = 3.0;
221 static const double kProgressIndeterminateTimeSpan = 5.0;
222 // The width of the overlay used to animate the horizontal progress bar (Vista
223 // and later).
224 static const int32_t kProgressHorizontalOverlaySize = 120;
225 // The height of the overlay used to animate the vertical progress bar (Vista
226 // and later).
227 static const int32_t kProgressVerticalOverlaySize = 45;
228 // The height of the overlay used for the vertical indeterminate progress bar
229 // (Vista and later).
230 static const int32_t kProgressVerticalIndeterminateOverlaySize = 60;
231 // The width of the overlay used to animate the indeterminate progress bar
232 // (Windows Classic).
233 static const int32_t kProgressClassicOverlaySize = 40;
236 * GetProgressOverlayStyle - returns the proper overlay part for themed
237 * progress bars based on os and orientation.
239 static int32_t GetProgressOverlayStyle(bool aIsVertical) {
240 return aIsVertical ? PP_MOVEOVERLAYVERT : PP_MOVEOVERLAY;
244 * GetProgressOverlaySize - returns the minimum width or height for themed
245 * progress bar overlays. This includes the width of indeterminate chunks
246 * and vista pulse overlays.
248 static int32_t GetProgressOverlaySize(bool aIsVertical, bool aIsIndeterminate) {
249 if (aIsVertical) {
250 return aIsIndeterminate ? kProgressVerticalIndeterminateOverlaySize
251 : kProgressVerticalOverlaySize;
253 return kProgressHorizontalOverlaySize;
257 * IsProgressMeterFilled - Determines if a progress meter is at 100% fill based
258 * on a comparison of the current value and maximum.
260 static bool IsProgressMeterFilled(nsIFrame* aFrame) {
261 NS_ENSURE_TRUE(aFrame, false);
262 nsIFrame* parentFrame = aFrame->GetParent();
263 NS_ENSURE_TRUE(parentFrame, false);
264 return nsNativeTheme::GetProgressValue(parentFrame) ==
265 nsNativeTheme::GetProgressMaxValue(parentFrame);
269 * CalculateProgressOverlayRect - returns the padded overlay animation rect
270 * used in rendering progress bars. Resulting rects are used in rendering
271 * vista+ pulse overlays and indeterminate progress meters. Graphics should
272 * be rendered at the origin.
274 RECT nsNativeThemeWin::CalculateProgressOverlayRect(nsIFrame* aFrame,
275 RECT* aWidgetRect,
276 bool aIsVertical,
277 bool aIsIndeterminate,
278 bool aIsClassic) {
279 NS_ASSERTION(aFrame, "bad frame pointer");
280 NS_ASSERTION(aWidgetRect, "bad rect pointer");
282 int32_t frameSize = aIsVertical ? aWidgetRect->bottom - aWidgetRect->top
283 : aWidgetRect->right - aWidgetRect->left;
285 // Recycle a set of progress pulse timers - these timers control the position
286 // of all progress overlays and indeterminate chunks that get rendered.
287 double span = aIsIndeterminate ? kProgressIndeterminateTimeSpan
288 : kProgressDeterminateTimeSpan;
289 TimeDuration period;
290 if (!aIsIndeterminate) {
291 if (TimeStamp::Now() >
292 (mProgressDeterminateTimeStamp + TimeDuration::FromSeconds(span))) {
293 mProgressDeterminateTimeStamp = TimeStamp::Now();
295 period = TimeStamp::Now() - mProgressDeterminateTimeStamp;
296 } else {
297 if (TimeStamp::Now() >
298 (mProgressIndeterminateTimeStamp + TimeDuration::FromSeconds(span))) {
299 mProgressIndeterminateTimeStamp = TimeStamp::Now();
301 period = TimeStamp::Now() - mProgressIndeterminateTimeStamp;
304 double percent = period / TimeDuration::FromSeconds(span);
306 if (!aIsVertical && IsFrameRTL(aFrame)) percent = 1 - percent;
308 RECT overlayRect = *aWidgetRect;
309 int32_t overlaySize;
310 if (!aIsClassic) {
311 overlaySize = GetProgressOverlaySize(aIsVertical, aIsIndeterminate);
312 } else {
313 overlaySize = kProgressClassicOverlaySize;
316 // Calculate a bounds that is larger than the meters frame such that the
317 // overlay starts and ends completely off the edge of the frame:
318 // [overlay][frame][overlay]
319 // This also yields a nice delay on rotation. Use overlaySize as the minimum
320 // size for [overlay] based on the graphics dims. If [frame] is larger, use
321 // the frame size instead.
322 int trackWidth = frameSize > overlaySize ? frameSize : overlaySize;
323 if (!aIsVertical) {
324 int xPos = aWidgetRect->left - trackWidth;
325 xPos += (int)ceil(((double)(trackWidth * 2) * percent));
326 overlayRect.left = xPos;
327 overlayRect.right = xPos + overlaySize;
328 } else {
329 int yPos = aWidgetRect->bottom + trackWidth;
330 yPos -= (int)ceil(((double)(trackWidth * 2) * percent));
331 overlayRect.bottom = yPos;
332 overlayRect.top = yPos - overlaySize;
334 return overlayRect;
338 * DrawProgressMeter - render an appropriate progress meter based on progress
339 * meter style, orientation, and os. Note, this does not render the underlying
340 * progress track.
342 * @param aFrame the widget frame
343 * @param aAppearance type of widget
344 * @param aTheme progress theme handle
345 * @param aHdc hdc returned by gfxWindowsNativeDrawing
346 * @param aPart the PP_X progress part
347 * @param aState the theme state
348 * @param aWidgetRect bounding rect for the widget
349 * @param aClipRect dirty rect that needs drawing.
350 * @param aAppUnits app units per device pixel
352 void nsNativeThemeWin::DrawThemedProgressMeter(
353 nsIFrame* aFrame, StyleAppearance aAppearance, HANDLE aTheme, HDC aHdc,
354 int aPart, int aState, RECT* aWidgetRect, RECT* aClipRect) {
355 if (!aFrame || !aTheme || !aHdc) return;
357 NS_ASSERTION(aWidgetRect, "bad rect pointer");
358 NS_ASSERTION(aClipRect, "bad clip rect pointer");
360 RECT adjWidgetRect, adjClipRect;
361 adjWidgetRect = *aWidgetRect;
362 adjClipRect = *aClipRect;
364 nsIFrame* parentFrame = aFrame->GetParent();
365 if (!parentFrame) {
366 // We have no parent to work with, just bail.
367 NS_WARNING("No parent frame for progress rendering. Can't paint.");
368 return;
371 ElementState elementState = GetContentState(parentFrame, aAppearance);
372 bool vertical = IsVerticalProgress(parentFrame);
373 bool indeterminate = elementState.HasState(ElementState::INDETERMINATE);
374 bool animate = indeterminate;
376 // Vista and up progress meter is fill style, rendered here. We render
377 // the pulse overlay in the follow up section below.
378 DrawThemeBackground(aTheme, aHdc, aPart, aState, &adjWidgetRect,
379 &adjClipRect);
380 if (!IsProgressMeterFilled(aFrame)) {
381 animate = true;
384 if (animate) {
385 // Indeterminate rendering
386 int32_t overlayPart = GetProgressOverlayStyle(vertical);
387 RECT overlayRect = CalculateProgressOverlayRect(
388 aFrame, &adjWidgetRect, vertical, indeterminate, false);
389 DrawThemeBackground(aTheme, aHdc, overlayPart, aState, &overlayRect,
390 &adjClipRect);
392 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 60)) {
393 NS_WARNING("unable to animate progress widget!");
398 LayoutDeviceIntMargin nsNativeThemeWin::GetCachedWidgetBorder(
399 HTHEME aTheme, nsUXThemeClass aThemeClass, StyleAppearance aAppearance,
400 int32_t aPart, int32_t aState) {
401 int32_t cacheIndex = aThemeClass * THEME_PART_DISTINCT_VALUE_COUNT + aPart;
402 int32_t cacheBitIndex = cacheIndex / 8;
403 uint8_t cacheBit = 1u << (cacheIndex % 8);
405 if (mBorderCacheValid[cacheBitIndex] & cacheBit) {
406 return mBorderCache[cacheIndex];
409 // Get our info.
410 RECT outerRect; // Create a fake outer rect.
411 outerRect.top = outerRect.left = 100;
412 outerRect.right = outerRect.bottom = 200;
413 RECT contentRect(outerRect);
414 HRESULT res = GetThemeBackgroundContentRect(aTheme, nullptr, aPart, aState,
415 &outerRect, &contentRect);
417 if (FAILED(res)) {
418 return LayoutDeviceIntMargin();
421 // Now compute the delta in each direction and place it in our
422 // nsIntMargin struct.
423 LayoutDeviceIntMargin result;
424 result.top = contentRect.top - outerRect.top;
425 result.bottom = outerRect.bottom - contentRect.bottom;
426 result.left = contentRect.left - outerRect.left;
427 result.right = outerRect.right - contentRect.right;
429 mBorderCacheValid[cacheBitIndex] |= cacheBit;
430 mBorderCache[cacheIndex] = result;
432 return result;
435 nsresult nsNativeThemeWin::GetCachedMinimumWidgetSize(
436 nsIFrame* aFrame, HANDLE aTheme, nsUXThemeClass aThemeClass,
437 StyleAppearance aAppearance, int32_t aPart, int32_t aState,
438 THEMESIZE aSizeReq, mozilla::LayoutDeviceIntSize* aResult) {
439 int32_t cachePart = aPart;
441 if (aAppearance == StyleAppearance::Button && aSizeReq == TS_MIN) {
442 // In practice, StyleAppearance::Button is the only widget type which has an
443 // aSizeReq that varies for us, and it can only be TS_MIN or TS_TRUE. Just
444 // stuff that extra bit into the aPart part of the cache, since BP_Count is
445 // well below THEME_PART_DISTINCT_VALUE_COUNT anyway.
446 cachePart = BP_Count;
449 MOZ_ASSERT(aPart < THEME_PART_DISTINCT_VALUE_COUNT);
450 int32_t cacheIndex =
451 aThemeClass * THEME_PART_DISTINCT_VALUE_COUNT + cachePart;
452 int32_t cacheBitIndex = cacheIndex / 8;
453 uint8_t cacheBit = 1u << (cacheIndex % 8);
455 if (mMinimumWidgetSizeCacheValid[cacheBitIndex] & cacheBit) {
456 *aResult = mMinimumWidgetSizeCache[cacheIndex];
457 return NS_OK;
460 HDC hdc = ::GetDC(NULL);
461 if (!hdc) {
462 return NS_ERROR_FAILURE;
465 SIZE sz;
466 GetThemePartSize(aTheme, hdc, aPart, aState, nullptr, aSizeReq, &sz);
467 aResult->width = sz.cx;
468 aResult->height = sz.cy;
470 ::ReleaseDC(nullptr, hdc);
472 mMinimumWidgetSizeCacheValid[cacheBitIndex] |= cacheBit;
473 mMinimumWidgetSizeCache[cacheIndex] = *aResult;
475 return NS_OK;
478 mozilla::Maybe<nsUXThemeClass> nsNativeThemeWin::GetThemeClass(
479 StyleAppearance aAppearance) {
480 switch (aAppearance) {
481 case StyleAppearance::Button:
482 return Some(eUXButton);
483 case StyleAppearance::NumberInput:
484 case StyleAppearance::Textfield:
485 case StyleAppearance::Textarea:
486 return Some(eUXEdit);
487 case StyleAppearance::Toolbox:
488 return Some(eUXRebar);
489 case StyleAppearance::Toolbar:
490 case StyleAppearance::Toolbarbutton:
491 case StyleAppearance::Separator:
492 return Some(eUXToolbar);
493 case StyleAppearance::ProgressBar:
494 case StyleAppearance::Progresschunk:
495 return Some(eUXProgress);
496 case StyleAppearance::Tab:
497 case StyleAppearance::Tabpanel:
498 case StyleAppearance::Tabpanels:
499 return Some(eUXTab);
500 case StyleAppearance::Range:
501 case StyleAppearance::RangeThumb:
502 return Some(eUXTrackbar);
503 case StyleAppearance::Menulist:
504 case StyleAppearance::MenulistButton:
505 return Some(eUXCombobox);
506 case StyleAppearance::Treeheadercell:
507 case StyleAppearance::Treeheadersortarrow:
508 return Some(eUXHeader);
509 case StyleAppearance::Listbox:
510 case StyleAppearance::Treeview:
511 case StyleAppearance::Treetwistyopen:
512 case StyleAppearance::Treeitem:
513 return Some(eUXListview);
514 default:
515 return Nothing();
519 HANDLE
520 nsNativeThemeWin::GetTheme(StyleAppearance aAppearance) {
521 mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aAppearance);
522 if (themeClass.isNothing()) {
523 return nullptr;
525 return nsUXThemeData::GetTheme(themeClass.value());
528 int32_t nsNativeThemeWin::StandardGetState(nsIFrame* aFrame,
529 StyleAppearance aAppearance,
530 bool wantFocused) {
531 ElementState elementState = GetContentState(aFrame, aAppearance);
532 if (elementState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE)) {
533 return TS_ACTIVE;
535 if (elementState.HasState(ElementState::HOVER)) {
536 return TS_HOVER;
538 if (wantFocused) {
539 if (elementState.HasState(ElementState::FOCUSRING)) {
540 return TS_FOCUSED;
542 // On Windows, focused buttons are always drawn as such by the native
543 // theme, that's why we check ElementState::FOCUS instead of
544 // ElementState::FOCUSRING.
545 if (aAppearance == StyleAppearance::Button &&
546 elementState.HasState(ElementState::FOCUS)) {
547 return TS_FOCUSED;
551 return TS_NORMAL;
554 bool nsNativeThemeWin::IsMenuActive(nsIFrame* aFrame,
555 StyleAppearance aAppearance) {
556 nsIContent* content = aFrame->GetContent();
557 if (content->IsXULElement() &&
558 content->NodeInfo()->Equals(nsGkAtoms::richlistitem))
559 return CheckBooleanAttr(aFrame, nsGkAtoms::selected);
561 return CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
565 * aPart is filled in with the UXTheme part code. On return, values > 0
566 * are the actual UXTheme part code; -1 means the widget will be drawn by
567 * us; 0 means that we should use part code 0, which isn't a real part code
568 * but elicits some kind of default behaviour from UXTheme when drawing
569 * (but isThemeBackgroundPartiallyTransparent may not work).
571 nsresult nsNativeThemeWin::GetThemePartAndState(nsIFrame* aFrame,
572 StyleAppearance aAppearance,
573 int32_t& aPart,
574 int32_t& aState) {
575 switch (aAppearance) {
576 case StyleAppearance::Button: {
577 aPart = BP_BUTTON;
578 if (!aFrame) {
579 aState = TS_NORMAL;
580 return NS_OK;
583 ElementState elementState = GetContentState(aFrame, aAppearance);
584 if (elementState.HasState(ElementState::DISABLED)) {
585 aState = TS_DISABLED;
586 return NS_OK;
588 if (IsOpenButton(aFrame) || IsCheckedButton(aFrame)) {
589 aState = TS_ACTIVE;
590 return NS_OK;
593 aState = StandardGetState(aFrame, aAppearance, true);
595 // Check for default dialog buttons. These buttons should always look
596 // focused.
597 if (aState == TS_NORMAL && IsDefaultButton(aFrame)) aState = TS_FOCUSED;
598 return NS_OK;
600 case StyleAppearance::NumberInput:
601 case StyleAppearance::Textfield:
602 case StyleAppearance::Textarea: {
603 ElementState elementState = GetContentState(aFrame, aAppearance);
605 /* Note: the NOSCROLL type has a rounded corner in each corner. The more
606 * specific HSCROLL, VSCROLL, HVSCROLL types have side and/or top/bottom
607 * edges rendered as straight horizontal lines with sharp corners to
608 * accommodate a scrollbar. However, the scrollbar gets rendered on top
609 * of this for us, so we don't care, and can just use NOSCROLL here.
611 aPart = TFP_EDITBORDER_NOSCROLL;
613 if (!aFrame) {
614 aState = TFS_EDITBORDER_NORMAL;
615 } else if (elementState.HasState(ElementState::DISABLED)) {
616 aState = TFS_EDITBORDER_DISABLED;
617 } else if (IsReadOnly(aFrame)) {
618 /* no special read-only state */
619 aState = TFS_EDITBORDER_NORMAL;
620 } else if (elementState.HasAtLeastOneOfStates(ElementState::ACTIVE |
621 ElementState::FOCUSRING)) {
622 aState = TFS_EDITBORDER_FOCUSED;
623 } else if (elementState.HasState(ElementState::HOVER)) {
624 aState = TFS_EDITBORDER_HOVER;
625 } else {
626 aState = TFS_EDITBORDER_NORMAL;
629 return NS_OK;
631 case StyleAppearance::ProgressBar: {
632 bool vertical = IsVerticalProgress(aFrame);
633 aPart = vertical ? PP_BARVERT : PP_BAR;
634 aState = PBBS_NORMAL;
635 return NS_OK;
637 case StyleAppearance::Progresschunk: {
638 nsIFrame* parentFrame = aFrame->GetParent();
639 if (IsVerticalProgress(parentFrame)) {
640 aPart = PP_FILLVERT;
641 } else {
642 aPart = PP_FILL;
645 aState = PBBVS_NORMAL;
646 return NS_OK;
648 case StyleAppearance::Toolbarbutton: {
649 aPart = BP_BUTTON;
650 if (!aFrame) {
651 aState = TS_NORMAL;
652 return NS_OK;
655 ElementState elementState = GetContentState(aFrame, aAppearance);
656 if (elementState.HasState(ElementState::DISABLED)) {
657 aState = TS_DISABLED;
658 return NS_OK;
660 if (IsOpenButton(aFrame)) {
661 aState = TS_ACTIVE;
662 return NS_OK;
665 if (elementState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE))
666 aState = TS_ACTIVE;
667 else if (elementState.HasState(ElementState::HOVER)) {
668 if (IsCheckedButton(aFrame))
669 aState = TB_HOVER_CHECKED;
670 else
671 aState = TS_HOVER;
672 } else {
673 if (IsCheckedButton(aFrame))
674 aState = TB_CHECKED;
675 else
676 aState = TS_NORMAL;
679 return NS_OK;
681 case StyleAppearance::Separator: {
682 aPart = TP_SEPARATOR;
683 aState = TS_NORMAL;
684 return NS_OK;
686 case StyleAppearance::Range: {
687 if (IsRangeHorizontal(aFrame)) {
688 aPart = TKP_TRACK;
689 aState = TRS_NORMAL;
690 } else {
691 aPart = TKP_TRACKVERT;
692 aState = TRVS_NORMAL;
694 return NS_OK;
696 case StyleAppearance::RangeThumb: {
697 if (IsRangeHorizontal(aFrame)) {
698 aPart = TKP_THUMBBOTTOM;
699 } else {
700 aPart = IsFrameRTL(aFrame) ? TKP_THUMBLEFT : TKP_THUMBRIGHT;
702 ElementState elementState = GetContentState(aFrame, aAppearance);
703 if (!aFrame) {
704 aState = TS_NORMAL;
705 } else if (elementState.HasState(ElementState::DISABLED)) {
706 aState = TKP_DISABLED;
707 } else {
708 if (elementState.HasState(
709 ElementState::ACTIVE)) // Hover is not also a requirement for
710 // the thumb, since the drag is not
711 // canceled when you move outside the
712 // thumb.
713 aState = TS_ACTIVE;
714 else if (elementState.HasState(ElementState::FOCUSRING))
715 aState = TKP_FOCUSED;
716 else if (elementState.HasState(ElementState::HOVER))
717 aState = TS_HOVER;
718 else
719 aState = TS_NORMAL;
721 return NS_OK;
723 case StyleAppearance::Toolbox: {
724 aState = 0;
725 aPart = RP_BACKGROUND;
726 return NS_OK;
728 case StyleAppearance::Toolbar: {
729 // Use -1 to indicate we don't wish to have the theme background drawn
730 // for this item. We will pass any nessessary information via aState,
731 // and will render the item using separate code.
732 aPart = -1;
733 aState = 0;
734 if (aFrame) {
735 nsIContent* content = aFrame->GetContent();
736 nsIContent* parent = content->GetParent();
737 // XXXzeniko hiding the first toolbar will result in an unwanted margin
738 if (parent && parent->GetFirstChild() == content) {
739 aState = 1;
742 return NS_OK;
744 case StyleAppearance::Treeview:
745 case StyleAppearance::Listbox: {
746 aPart = TREEVIEW_BODY;
747 aState = TS_NORMAL;
748 return NS_OK;
750 case StyleAppearance::Tabpanels: {
751 aPart = TABP_PANELS;
752 aState = TS_NORMAL;
753 return NS_OK;
755 case StyleAppearance::Tabpanel: {
756 aPart = TABP_PANEL;
757 aState = TS_NORMAL;
758 return NS_OK;
760 case StyleAppearance::Tab: {
761 aPart = TABP_TAB;
762 if (!aFrame) {
763 aState = TS_NORMAL;
764 return NS_OK;
767 ElementState elementState = GetContentState(aFrame, aAppearance);
768 if (elementState.HasState(ElementState::DISABLED)) {
769 aState = TS_DISABLED;
770 return NS_OK;
773 if (IsSelectedTab(aFrame)) {
774 aPart = TABP_TAB_SELECTED;
775 aState = TS_ACTIVE; // The selected tab is always "pressed".
776 } else
777 aState = StandardGetState(aFrame, aAppearance, true);
779 return NS_OK;
781 case StyleAppearance::Treeheadersortarrow: {
782 // XXX Probably will never work due to a bug in the Luna theme.
783 aPart = 4;
784 aState = 1;
785 return NS_OK;
787 case StyleAppearance::Treeheadercell: {
788 aPart = 1;
789 if (!aFrame) {
790 aState = TS_NORMAL;
791 return NS_OK;
794 aState = StandardGetState(aFrame, aAppearance, true);
796 return NS_OK;
798 case StyleAppearance::MenulistButton:
799 case StyleAppearance::Menulist: {
800 nsIContent* content = aFrame->GetContent();
801 bool useDropBorder = content && content->IsHTMLElement();
802 ElementState elementState = GetContentState(aFrame, aAppearance);
804 /* On Vista/Win7, we use CBP_DROPBORDER instead of DROPFRAME for HTML
805 * content or for editable menulists; this gives us the thin outline,
806 * instead of the gradient-filled background */
807 if (useDropBorder)
808 aPart = CBP_DROPBORDER;
809 else
810 aPart = CBP_DROPFRAME;
812 if (elementState.HasState(ElementState::DISABLED)) {
813 aState = TS_DISABLED;
814 } else if (IsReadOnly(aFrame)) {
815 aState = TS_NORMAL;
816 } else if (IsOpenButton(aFrame)) {
817 aState = TS_ACTIVE;
818 } else if (useDropBorder &&
819 elementState.HasState(ElementState::FOCUSRING)) {
820 aState = TS_ACTIVE;
821 } else if (elementState.HasAllStates(ElementState::HOVER |
822 ElementState::ACTIVE)) {
823 aState = TS_ACTIVE;
824 } else if (elementState.HasState(ElementState::HOVER)) {
825 aState = TS_HOVER;
826 } else {
827 aState = TS_NORMAL;
830 return NS_OK;
832 default:
833 aPart = 0;
834 aState = 0;
835 return NS_ERROR_FAILURE;
839 static bool AssumeThemePartAndStateAreTransparent(int32_t aPart,
840 int32_t aState) {
841 if (!nsUXThemeData::IsHighContrastOn() && aPart == MENU_POPUPITEM &&
842 aState == MBI_NORMAL) {
843 return true;
845 return false;
848 // When running with per-monitor DPI (on Win8.1+), and rendering on a display
849 // with a different DPI setting from the system's default scaling, we need to
850 // apply scaling to native-themed elements as the Windows theme APIs assume
851 // the system default resolution.
852 static inline double GetThemeDpiScaleFactor(nsPresContext* aPresContext) {
853 if (WinUtils::IsPerMonitorDPIAware() ||
854 StaticPrefs::layout_css_devPixelsPerPx() > 0.0) {
855 nsCOMPtr<nsIWidget> rootWidget = aPresContext->GetRootWidget();
856 if (rootWidget) {
857 double systemScale = WinUtils::SystemScaleFactor();
858 return rootWidget->GetDefaultScale().scale / systemScale;
861 return 1.0;
864 static inline double GetThemeDpiScaleFactor(nsIFrame* aFrame) {
865 return GetThemeDpiScaleFactor(aFrame->PresContext());
868 NS_IMETHODIMP
869 nsNativeThemeWin::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
870 StyleAppearance aAppearance,
871 const nsRect& aRect,
872 const nsRect& aDirtyRect,
873 DrawOverflow aDrawOverflow) {
874 if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
875 return Theme::DrawWidgetBackground(aContext, aFrame, aAppearance, aRect,
876 aDirtyRect, aDrawOverflow);
879 HANDLE theme = GetTheme(aAppearance);
880 if (!theme)
881 return ClassicDrawWidgetBackground(aContext, aFrame, aAppearance, aRect,
882 aDirtyRect);
884 // ^^ without the right sdk, assume xp theming and fall through.
885 int32_t part, state;
886 nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
887 if (NS_FAILED(rv)) return rv;
889 if (AssumeThemePartAndStateAreTransparent(part, state)) {
890 return NS_OK;
893 gfxContextMatrixAutoSaveRestore save(aContext);
895 double themeScale = GetThemeDpiScaleFactor(aFrame);
896 if (themeScale != 1.0) {
897 aContext->SetMatrix(
898 aContext->CurrentMatrix().PreScale(themeScale, themeScale));
901 gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel());
902 RECT widgetRect;
903 RECT clipRect;
904 gfxRect tr(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()),
905 dr(aDirtyRect.X(), aDirtyRect.Y(), aDirtyRect.Width(),
906 aDirtyRect.Height());
908 tr.Scale(1.0 / (p2a * themeScale));
909 dr.Scale(1.0 / (p2a * themeScale));
911 gfxWindowsNativeDrawing nativeDrawing(
912 aContext, dr, GetWidgetNativeDrawingFlags(aAppearance));
914 RENDER_AGAIN:
916 HDC hdc = nativeDrawing.BeginNativeDrawing();
917 if (!hdc) return NS_ERROR_FAILURE;
919 nativeDrawing.TransformToNativeRect(tr, widgetRect);
920 nativeDrawing.TransformToNativeRect(dr, clipRect);
922 #if 0
924 MOZ_LOG(gWindowsLog, LogLevel::Error,
925 (stderr, "xform: %f %f %f %f [%f %f]\n", m._11, m._21, m._12, m._22,
926 m._31, m._32));
927 MOZ_LOG(gWindowsLog, LogLevel::Error,
928 (stderr, "tr: [%d %d %d %d]\ndr: [%d %d %d %d]\noff: [%f %f]\n",
929 tr.x, tr.y, tr.width, tr.height, dr.x, dr.y, dr.width, dr.height,
930 offset.x, offset.y));
932 #endif
934 if (aAppearance == StyleAppearance::Tab) {
935 // For left edge and right edge tabs, we need to adjust the widget
936 // rects and clip rects so that the edges don't get drawn.
937 bool isLeft = IsLeftToSelectedTab(aFrame);
938 bool isRight = !isLeft && IsRightToSelectedTab(aFrame);
940 if (isLeft || isRight) {
941 // HACK ALERT: There appears to be no way to really obtain this value, so
942 // we're forced to just use the default value for Luna (which also happens
943 // to be correct for all the other skins I've tried).
944 int32_t edgeSize = 2;
946 // Armed with the size of the edge, we now need to either shift to the
947 // left or to the right. The clip rect won't include this extra area, so
948 // we know that we're effectively shifting the edge out of view (such that
949 // it won't be painted).
950 if (isLeft)
951 // The right edge should not be drawn. Extend our rect by the edge
952 // size.
953 widgetRect.right += edgeSize;
954 else
955 // The left edge should not be drawn. Move the widget rect's left coord
956 // back.
957 widgetRect.left -= edgeSize;
961 // widgetRect is the bounding box for a widget, yet the scale track is only
962 // a small portion of this size, so the edges of the scale need to be
963 // adjusted to the real size of the track.
964 if (aAppearance == StyleAppearance::Range) {
965 RECT contentRect;
966 GetThemeBackgroundContentRect(theme, hdc, part, state, &widgetRect,
967 &contentRect);
969 SIZE siz;
970 GetThemePartSize(theme, hdc, part, state, &widgetRect, TS_TRUE, &siz);
972 // When rounding is necessary, we round the position of the track
973 // away from the chevron of the thumb to make it look better.
974 if (IsRangeHorizontal(aFrame)) {
975 contentRect.top += (contentRect.bottom - contentRect.top - siz.cy) / 2;
976 contentRect.bottom = contentRect.top + siz.cy;
977 } else {
978 if (!IsFrameRTL(aFrame)) {
979 contentRect.left += (contentRect.right - contentRect.left - siz.cx) / 2;
980 contentRect.right = contentRect.left + siz.cx;
981 } else {
982 contentRect.right -=
983 (contentRect.right - contentRect.left - siz.cx) / 2;
984 contentRect.left = contentRect.right - siz.cx;
988 DrawThemeBackground(theme, hdc, part, state, &contentRect, &clipRect);
989 } else if (aAppearance == StyleAppearance::NumberInput ||
990 aAppearance == StyleAppearance::Textfield ||
991 aAppearance == StyleAppearance::Textarea) {
992 DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
994 if (state == TFS_EDITBORDER_DISABLED) {
995 InflateRect(&widgetRect, -1, -1);
996 ::FillRect(hdc, &widgetRect, reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1));
998 } else if (aAppearance == StyleAppearance::ProgressBar) {
999 // DrawThemeBackground renders each corner with a solid white pixel.
1000 // Restore these pixels to the underlying color. Tracks are rendered
1001 // using alpha recovery, so this makes the corners transparent.
1002 COLORREF color;
1003 color = GetPixel(hdc, widgetRect.left, widgetRect.top);
1004 DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
1005 SetPixel(hdc, widgetRect.left, widgetRect.top, color);
1006 SetPixel(hdc, widgetRect.right - 1, widgetRect.top, color);
1007 SetPixel(hdc, widgetRect.right - 1, widgetRect.bottom - 1, color);
1008 SetPixel(hdc, widgetRect.left, widgetRect.bottom - 1, color);
1009 } else if (aAppearance == StyleAppearance::Progresschunk) {
1010 DrawThemedProgressMeter(aFrame, aAppearance, theme, hdc, part, state,
1011 &widgetRect, &clipRect);
1013 // If part is negative, the element wishes us to not render a themed
1014 // background, instead opting to be drawn specially below.
1015 else if (part >= 0) {
1016 DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
1019 // Draw focus rectangles for range elements
1020 // XXX it'd be nice to draw these outside of the frame
1021 if (aAppearance == StyleAppearance::Range) {
1022 ElementState contentState = GetContentState(aFrame, aAppearance);
1024 if (contentState.HasState(ElementState::FOCUSRING)) {
1025 POINT vpOrg;
1026 HPEN hPen = nullptr;
1028 uint8_t id = SaveDC(hdc);
1030 ::SelectClipRgn(hdc, nullptr);
1031 ::GetViewportOrgEx(hdc, &vpOrg);
1032 ::SetBrushOrgEx(hdc, vpOrg.x + widgetRect.left, vpOrg.y + widgetRect.top,
1033 nullptr);
1034 ::SetTextColor(hdc, 0);
1035 ::DrawFocusRect(hdc, &widgetRect);
1036 ::RestoreDC(hdc, id);
1037 if (hPen) {
1038 ::DeleteObject(hPen);
1041 } else if (aAppearance == StyleAppearance::Toolbar && state == 0) {
1042 // Draw toolbar separator lines above all toolbars except the first one.
1043 // The lines are part of the Rebar theme, which is loaded for
1044 // StyleAppearance::Toolbox.
1045 theme = GetTheme(StyleAppearance::Toolbox);
1046 if (!theme) return NS_ERROR_FAILURE;
1048 widgetRect.bottom = widgetRect.top + TB_SEPARATOR_HEIGHT;
1049 DrawThemeEdge(theme, hdc, RP_BAND, 0, &widgetRect, EDGE_ETCHED, BF_TOP,
1050 nullptr);
1053 nativeDrawing.EndNativeDrawing();
1055 if (nativeDrawing.ShouldRenderAgain()) goto RENDER_AGAIN;
1057 nativeDrawing.PaintToContext();
1059 return NS_OK;
1062 bool nsNativeThemeWin::CreateWebRenderCommandsForWidget(
1063 wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
1064 const layers::StackingContextHelper& aSc,
1065 layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
1066 StyleAppearance aAppearance, const nsRect& aRect) {
1067 if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
1068 return Theme::CreateWebRenderCommandsForWidget(
1069 aBuilder, aResources, aSc, aManager, aFrame, aAppearance, aRect);
1071 return false;
1074 static void ScaleForFrameDPI(LayoutDeviceIntMargin* aMargin, nsIFrame* aFrame) {
1075 double themeScale = GetThemeDpiScaleFactor(aFrame);
1076 if (themeScale != 1.0) {
1077 aMargin->top = NSToIntRound(aMargin->top * themeScale);
1078 aMargin->left = NSToIntRound(aMargin->left * themeScale);
1079 aMargin->bottom = NSToIntRound(aMargin->bottom * themeScale);
1080 aMargin->right = NSToIntRound(aMargin->right * themeScale);
1084 static void ScaleForFrameDPI(LayoutDeviceIntSize* aSize, nsIFrame* aFrame) {
1085 double themeScale = GetThemeDpiScaleFactor(aFrame);
1086 if (themeScale != 1.0) {
1087 aSize->width = NSToIntRound(aSize->width * themeScale);
1088 aSize->height = NSToIntRound(aSize->height * themeScale);
1092 LayoutDeviceIntMargin nsNativeThemeWin::GetWidgetBorder(
1093 nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
1094 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
1095 return Theme::GetWidgetBorder(aContext, aFrame, aAppearance);
1098 LayoutDeviceIntMargin result;
1099 mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aAppearance);
1100 HTHEME theme = NULL;
1101 if (!themeClass.isNothing()) {
1102 theme = nsUXThemeData::GetTheme(themeClass.value());
1104 if (!theme) {
1105 result = ClassicGetWidgetBorder(aContext, aFrame, aAppearance);
1106 ScaleForFrameDPI(&result, aFrame);
1107 return result;
1110 if (!WidgetIsContainer(aAppearance) ||
1111 aAppearance == StyleAppearance::Toolbox ||
1112 aAppearance == StyleAppearance::Tabpanel)
1113 return result; // Don't worry about it.
1115 int32_t part, state;
1116 nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
1117 if (NS_FAILED(rv)) return result;
1119 if (aAppearance == StyleAppearance::Toolbar) {
1120 // make space for the separator line above all toolbars but the first
1121 if (state == 0) result.top = TB_SEPARATOR_HEIGHT;
1122 return result;
1125 result = GetCachedWidgetBorder(theme, themeClass.value(), aAppearance, part,
1126 state);
1128 // Remove the edges for tabs that are before or after the selected tab,
1129 if (aAppearance == StyleAppearance::Tab) {
1130 if (IsLeftToSelectedTab(aFrame))
1131 // Remove the right edge, since we won't be drawing it.
1132 result.right = 0;
1133 else if (IsRightToSelectedTab(aFrame))
1134 // Remove the left edge, since we won't be drawing it.
1135 result.left = 0;
1138 if (aFrame && (aAppearance == StyleAppearance::NumberInput ||
1139 aAppearance == StyleAppearance::Textfield ||
1140 aAppearance == StyleAppearance::Textarea)) {
1141 nsIContent* content = aFrame->GetContent();
1142 if (content && content->IsHTMLElement()) {
1143 // We need to pad textfields by 1 pixel, since the caret will draw
1144 // flush against the edge by default if we don't.
1145 result.top.value++;
1146 result.left.value++;
1147 result.bottom.value++;
1148 result.right.value++;
1152 ScaleForFrameDPI(&result, aFrame);
1153 return result;
1156 bool nsNativeThemeWin::GetWidgetPadding(nsDeviceContext* aContext,
1157 nsIFrame* aFrame,
1158 StyleAppearance aAppearance,
1159 LayoutDeviceIntMargin* aResult) {
1160 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
1161 return Theme::GetWidgetPadding(aContext, aFrame, aAppearance, aResult);
1164 bool ok = true;
1165 HANDLE theme = GetTheme(aAppearance);
1166 if (!theme) {
1167 ok = ClassicGetWidgetPadding(aContext, aFrame, aAppearance, aResult);
1168 ScaleForFrameDPI(aResult, aFrame);
1169 return ok;
1172 /* textfields need extra pixels on all sides, otherwise they wrap their
1173 * content too tightly. The actual border is drawn 1px inside the specified
1174 * rectangle, so Gecko will end up making the contents look too small.
1175 * Instead, we add 2px padding for the contents and fix this. (Used to be 1px
1176 * added, see bug 430212)
1178 if (aAppearance == StyleAppearance::NumberInput ||
1179 aAppearance == StyleAppearance::Textfield ||
1180 aAppearance == StyleAppearance::Textarea) {
1181 aResult->top = aResult->bottom = 2;
1182 aResult->left = aResult->right = 2;
1183 ScaleForFrameDPI(aResult, aFrame);
1184 return ok;
1185 } else if (IsHTMLContent(aFrame) &&
1186 (aAppearance == StyleAppearance::Menulist ||
1187 aAppearance == StyleAppearance::MenulistButton)) {
1188 /* For content menulist controls, we need an extra pixel so that we have
1189 * room to draw our focus rectangle stuff. Otherwise, the focus rect might
1190 * overlap the control's border.
1192 aResult->top = aResult->bottom = 1;
1193 aResult->left = aResult->right = 1;
1194 ScaleForFrameDPI(aResult, aFrame);
1195 return ok;
1198 int32_t right, left, top, bottom;
1199 right = left = top = bottom = 0;
1200 switch (aAppearance) {
1201 case StyleAppearance::Button:
1202 if (aFrame->GetContent()->IsXULElement()) {
1203 top = 2;
1204 bottom = 3;
1206 left = right = 5;
1207 break;
1208 default:
1209 return false;
1212 if (IsFrameRTL(aFrame)) {
1213 aResult->right = left;
1214 aResult->left = right;
1215 } else {
1216 aResult->right = right;
1217 aResult->left = left;
1219 aResult->top = top;
1220 aResult->bottom = bottom;
1222 ScaleForFrameDPI(aResult, aFrame);
1223 return ok;
1226 bool nsNativeThemeWin::GetWidgetOverflow(nsDeviceContext* aContext,
1227 nsIFrame* aFrame,
1228 StyleAppearance aAppearance,
1229 nsRect* aOverflowRect) {
1230 if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
1231 return Theme::GetWidgetOverflow(aContext, aFrame, aAppearance,
1232 aOverflowRect);
1235 /* This is disabled for now, because it causes invalidation problems --
1236 * see bug 420381. The effect of not updating the overflow area is that
1237 * for dropdown buttons in content areas, there is a 1px border on 3 sides
1238 * where, if invalidated, the dropdown control probably won't be repainted.
1239 * This is fairly minor, as by default there is nothing in that area, and
1240 * a border only shows up if the widget is being hovered.
1242 * TODO(jwatt): Figure out what do to about
1243 * StyleAppearance::MozMenulistArrowButton too.
1245 #if 0
1246 /* We explicitly draw dropdown buttons in HTML content 1px bigger up, right,
1247 * and bottom so that they overlap the dropdown's border like they're
1248 * supposed to.
1250 if (aAppearance == StyleAppearance::MenulistButton &&
1251 IsHTMLContent(aFrame) &&
1252 !IsWidgetStyled(aFrame->GetParent()->PresContext(),
1253 aFrame->GetParent(),
1254 StyleAppearance::Menulist))
1256 int32_t p2a = aContext->AppUnitsPerDevPixel();
1257 /* Note: no overflow on the left */
1258 nsMargin m(p2a, p2a, p2a, 0);
1259 aOverflowRect->Inflate (m);
1260 return true;
1262 #endif
1264 return false;
1267 LayoutDeviceIntSize nsNativeThemeWin::GetMinimumWidgetSize(
1268 nsPresContext* aPresContext, nsIFrame* aFrame,
1269 StyleAppearance aAppearance) {
1270 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
1271 return Theme::GetMinimumWidgetSize(aPresContext, aFrame, aAppearance);
1274 mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aAppearance);
1275 HTHEME theme = NULL;
1276 if (!themeClass.isNothing()) {
1277 theme = nsUXThemeData::GetTheme(themeClass.value());
1279 if (!theme) {
1280 auto result = ClassicGetMinimumWidgetSize(aFrame, aAppearance);
1281 ScaleForFrameDPI(&result, aFrame);
1282 return result;
1285 switch (aAppearance) {
1286 case StyleAppearance::NumberInput:
1287 case StyleAppearance::Textfield:
1288 case StyleAppearance::Toolbox:
1289 case StyleAppearance::Toolbar:
1290 case StyleAppearance::Progresschunk:
1291 case StyleAppearance::Tabpanels:
1292 case StyleAppearance::Tabpanel:
1293 case StyleAppearance::Listbox:
1294 case StyleAppearance::Treeview:
1295 return {}; // Don't worry about it.
1296 default:
1297 break;
1300 // Call GetSystemMetrics to determine size for WinXP scrollbars
1301 // (GetThemeSysSize API returns the optimal size for the theme, but
1302 // Windows appears to always use metrics when drawing standard scrollbars)
1303 THEMESIZE sizeReq = TS_TRUE; // Best-fit size
1304 switch (aAppearance) {
1305 case StyleAppearance::ProgressBar:
1306 // Best-fit size for progress meters is too large for most
1307 // themes. We want these widgets to be able to really shrink
1308 // down, so use the min-size request value (of 0).
1309 sizeReq = TS_MIN;
1310 break;
1312 case StyleAppearance::RangeThumb: {
1313 LayoutDeviceIntSize result(12, 20);
1314 if (!IsRangeHorizontal(aFrame)) {
1315 std::swap(result.width, result.height);
1317 ScaleForFrameDPI(&result, aFrame);
1318 return result;
1321 case StyleAppearance::Separator: {
1322 // that's 2px left margin, 2px right margin and 2px separator
1323 // (the margin is drawn as part of the separator, though)
1324 LayoutDeviceIntSize result(6, 0);
1325 ScaleForFrameDPI(&result, aFrame);
1326 return result;
1329 case StyleAppearance::Button:
1330 // We should let HTML buttons shrink to their min size.
1331 // FIXME bug 403934: We should probably really separate
1332 // GetPreferredWidgetSize from GetMinimumWidgetSize, so callers can
1333 // use the one they want.
1334 if (aFrame->GetContent()->IsHTMLElement()) {
1335 sizeReq = TS_MIN;
1337 break;
1339 default:
1340 break;
1343 int32_t part, state;
1344 nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
1345 if (NS_FAILED(rv)) {
1346 return {};
1349 LayoutDeviceIntSize result;
1350 rv = GetCachedMinimumWidgetSize(aFrame, theme, themeClass.value(),
1351 aAppearance, part, state, sizeReq, &result);
1352 ScaleForFrameDPI(&result, aFrame);
1353 return result;
1356 NS_IMETHODIMP
1357 nsNativeThemeWin::WidgetStateChanged(nsIFrame* aFrame,
1358 StyleAppearance aAppearance,
1359 nsAtom* aAttribute, bool* aShouldRepaint,
1360 const nsAttrValue* aOldValue) {
1361 // Some widget types just never change state.
1362 if (aAppearance == StyleAppearance::Toolbox ||
1363 aAppearance == StyleAppearance::Toolbar ||
1364 aAppearance == StyleAppearance::Progresschunk ||
1365 aAppearance == StyleAppearance::ProgressBar ||
1366 aAppearance == StyleAppearance::Tabpanels ||
1367 aAppearance == StyleAppearance::Tabpanel ||
1368 aAppearance == StyleAppearance::Separator) {
1369 *aShouldRepaint = false;
1370 return NS_OK;
1373 // We need to repaint the dropdown arrow in vista HTML combobox controls when
1374 // the control is closed to get rid of the hover effect.
1375 if ((aAppearance == StyleAppearance::Menulist ||
1376 aAppearance == StyleAppearance::MenulistButton) &&
1377 nsNativeTheme::IsHTMLContent(aFrame)) {
1378 *aShouldRepaint = true;
1379 return NS_OK;
1382 // XXXdwh Not sure what can really be done here. Can at least guess for
1383 // specific widgets that they're highly unlikely to have certain states.
1384 // For example, a toolbar doesn't care about any states.
1385 if (!aAttribute) {
1386 // Hover/focus/active changed. Always repaint.
1387 *aShouldRepaint = true;
1388 } else {
1389 // Check the attribute to see if it's relevant.
1390 // disabled, checked, dlgtype, default, etc.
1391 *aShouldRepaint = false;
1392 if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
1393 aAttribute == nsGkAtoms::selected ||
1394 aAttribute == nsGkAtoms::visuallyselected ||
1395 aAttribute == nsGkAtoms::readonly || aAttribute == nsGkAtoms::open ||
1396 aAttribute == nsGkAtoms::menuactive || aAttribute == nsGkAtoms::focused)
1397 *aShouldRepaint = true;
1400 return NS_OK;
1403 NS_IMETHODIMP
1404 nsNativeThemeWin::ThemeChanged() {
1405 nsUXThemeData::Invalidate();
1406 memset(mBorderCacheValid, 0, sizeof(mBorderCacheValid));
1407 memset(mMinimumWidgetSizeCacheValid, 0, sizeof(mMinimumWidgetSizeCacheValid));
1408 mGutterSizeCacheValid = false;
1409 return NS_OK;
1412 bool nsNativeThemeWin::ThemeSupportsWidget(nsPresContext* aPresContext,
1413 nsIFrame* aFrame,
1414 StyleAppearance aAppearance) {
1415 // XXXdwh We can go even further and call the API to ask if support exists for
1416 // specific widgets.
1418 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
1419 return Theme::ThemeSupportsWidget(aPresContext, aFrame, aAppearance);
1422 HANDLE theme = GetTheme(aAppearance);
1423 if (theme || ClassicThemeSupportsWidget(aFrame, aAppearance))
1424 // turn off theming for some HTML widgets styled by the page
1425 return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
1427 return false;
1430 bool nsNativeThemeWin::ThemeDrawsFocusForWidget(nsIFrame* aFrame,
1431 StyleAppearance aAppearance) {
1432 if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
1433 return Theme::ThemeDrawsFocusForWidget(aFrame, aAppearance);
1435 switch (aAppearance) {
1436 case StyleAppearance::Menulist:
1437 case StyleAppearance::MenulistButton:
1438 case StyleAppearance::Textarea:
1439 case StyleAppearance::Textfield:
1440 case StyleAppearance::NumberInput:
1441 return true;
1442 default:
1443 return false;
1447 bool nsNativeThemeWin::ThemeNeedsComboboxDropmarker() { return true; }
1449 nsITheme::Transparency nsNativeThemeWin::GetWidgetTransparency(
1450 nsIFrame* aFrame, StyleAppearance aAppearance) {
1451 if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
1452 return Theme::GetWidgetTransparency(aFrame, aAppearance);
1455 switch (aAppearance) {
1456 case StyleAppearance::ProgressBar:
1457 case StyleAppearance::Progresschunk:
1458 case StyleAppearance::Range:
1459 return eTransparent;
1460 default:
1461 break;
1464 HANDLE theme = GetTheme(aAppearance);
1465 // For the classic theme we don't really have a way of knowing
1466 if (!theme) {
1467 return eUnknownTransparency;
1470 int32_t part, state;
1471 nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
1472 // Fail conservatively
1473 NS_ENSURE_SUCCESS(rv, eUnknownTransparency);
1475 if (part <= 0) {
1476 // Not a real part code, so IsThemeBackgroundPartiallyTransparent may
1477 // not work, so don't call it.
1478 return eUnknownTransparency;
1481 if (IsThemeBackgroundPartiallyTransparent(theme, part, state))
1482 return eTransparent;
1483 return eOpaque;
1486 /* Windows 9x/NT/2000/Classic XP Theme Support */
1488 bool nsNativeThemeWin::ClassicThemeSupportsWidget(nsIFrame* aFrame,
1489 StyleAppearance aAppearance) {
1490 switch (aAppearance) {
1491 case StyleAppearance::Button:
1492 case StyleAppearance::NumberInput:
1493 case StyleAppearance::Textfield:
1494 case StyleAppearance::Textarea:
1495 case StyleAppearance::Range:
1496 case StyleAppearance::RangeThumb:
1497 case StyleAppearance::Menulist:
1498 case StyleAppearance::MenulistButton:
1499 case StyleAppearance::Listbox:
1500 case StyleAppearance::Treeview:
1501 case StyleAppearance::ProgressBar:
1502 case StyleAppearance::Progresschunk:
1503 case StyleAppearance::Tab:
1504 case StyleAppearance::Tabpanel:
1505 case StyleAppearance::Tabpanels:
1506 return true;
1507 default:
1508 return false;
1512 LayoutDeviceIntMargin nsNativeThemeWin::ClassicGetWidgetBorder(
1513 nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
1514 LayoutDeviceIntMargin result;
1515 switch (aAppearance) {
1516 case StyleAppearance::Button:
1517 result.top = result.left = result.bottom = result.right = 2;
1518 break;
1519 case StyleAppearance::Listbox:
1520 case StyleAppearance::Treeview:
1521 case StyleAppearance::Menulist:
1522 case StyleAppearance::MenulistButton:
1523 case StyleAppearance::Tab:
1524 case StyleAppearance::NumberInput:
1525 case StyleAppearance::Textfield:
1526 case StyleAppearance::Textarea:
1527 result.top = result.left = result.bottom = result.right = 2;
1528 break;
1529 case StyleAppearance::ProgressBar:
1530 result.top = result.left = result.bottom = result.right = 1;
1531 break;
1532 default:
1533 result.top = result.bottom = result.left = result.right = 0;
1534 break;
1536 return result;
1539 bool nsNativeThemeWin::ClassicGetWidgetPadding(nsDeviceContext* aContext,
1540 nsIFrame* aFrame,
1541 StyleAppearance aAppearance,
1542 LayoutDeviceIntMargin* aResult) {
1543 switch (aAppearance) {
1544 case StyleAppearance::ProgressBar:
1545 (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right =
1547 return true;
1548 default:
1549 return false;
1553 LayoutDeviceIntSize nsNativeThemeWin::ClassicGetMinimumWidgetSize(
1554 nsIFrame* aFrame, StyleAppearance aAppearance) {
1555 LayoutDeviceIntSize result;
1556 switch (aAppearance) {
1557 case StyleAppearance::RangeThumb: {
1558 if (IsRangeHorizontal(aFrame)) {
1559 result.width = 12;
1560 result.height = 20;
1561 } else {
1562 result.width = 20;
1563 result.height = 12;
1565 break;
1567 case StyleAppearance::Menulist:
1568 case StyleAppearance::MenulistButton:
1569 case StyleAppearance::Button:
1570 case StyleAppearance::Listbox:
1571 case StyleAppearance::Treeview:
1572 case StyleAppearance::NumberInput:
1573 case StyleAppearance::Textfield:
1574 case StyleAppearance::Textarea:
1575 case StyleAppearance::Progresschunk:
1576 case StyleAppearance::ProgressBar:
1577 case StyleAppearance::Tab:
1578 case StyleAppearance::Tabpanel:
1579 case StyleAppearance::Tabpanels:
1580 // no minimum widget size
1581 break;
1583 default:
1584 break;
1586 return result;
1589 nsresult nsNativeThemeWin::ClassicGetThemePartAndState(
1590 nsIFrame* aFrame, StyleAppearance aAppearance, int32_t& aPart,
1591 int32_t& aState, bool& aFocused) {
1592 aFocused = false;
1593 switch (aAppearance) {
1594 case StyleAppearance::Button: {
1595 aPart = DFC_BUTTON;
1596 aState = DFCS_BUTTONPUSH;
1597 aFocused = false;
1599 ElementState contentState = GetContentState(aFrame, aAppearance);
1600 if (contentState.HasState(ElementState::DISABLED)) {
1601 aState |= DFCS_INACTIVE;
1602 } else if (IsOpenButton(aFrame)) {
1603 aState |= DFCS_PUSHED;
1604 } else if (IsCheckedButton(aFrame)) {
1605 aState |= DFCS_CHECKED;
1606 } else {
1607 if (contentState.HasAllStates(ElementState::ACTIVE |
1608 ElementState::HOVER)) {
1609 aState |= DFCS_PUSHED;
1610 // The down state is flat if the button is focusable
1611 if (aFrame->StyleUI()->UserFocus() == StyleUserFocus::Normal) {
1612 if (!aFrame->GetContent()->IsHTMLElement()) aState |= DFCS_FLAT;
1614 aFocused = true;
1617 // On Windows, focused buttons are always drawn as such by the native
1618 // theme, that's why we check ElementState::FOCUS instead of
1619 // ElementState::FOCUSRING.
1620 if (contentState.HasState(ElementState::FOCUS) ||
1621 (aState == DFCS_BUTTONPUSH && IsDefaultButton(aFrame))) {
1622 aFocused = true;
1626 return NS_OK;
1628 case StyleAppearance::Listbox:
1629 case StyleAppearance::Treeview:
1630 case StyleAppearance::NumberInput:
1631 case StyleAppearance::Textfield:
1632 case StyleAppearance::Textarea:
1633 case StyleAppearance::Menulist:
1634 case StyleAppearance::MenulistButton:
1635 case StyleAppearance::Range:
1636 case StyleAppearance::RangeThumb:
1637 case StyleAppearance::Progresschunk:
1638 case StyleAppearance::ProgressBar:
1639 case StyleAppearance::Tab:
1640 case StyleAppearance::Tabpanel:
1641 case StyleAppearance::Tabpanels:
1642 // these don't use DrawFrameControl
1643 return NS_OK;
1644 default:
1645 return NS_ERROR_FAILURE;
1649 // Draw classic Windows tab
1650 // (no system API for this, but DrawEdge can draw all the parts of a tab)
1651 static void DrawTab(HDC hdc, const RECT& R, int32_t aPosition, bool aSelected,
1652 bool aDrawLeft, bool aDrawRight) {
1653 int32_t leftFlag, topFlag, rightFlag, lightFlag, shadeFlag;
1654 RECT topRect, sideRect, bottomRect, lightRect, shadeRect;
1655 int32_t selectedOffset, lOffset, rOffset;
1657 selectedOffset = aSelected ? 1 : 0;
1658 lOffset = aDrawLeft ? 2 : 0;
1659 rOffset = aDrawRight ? 2 : 0;
1661 // Get info for tab orientation/position (Left, Top, Right, Bottom)
1662 switch (aPosition) {
1663 case BF_LEFT:
1664 leftFlag = BF_TOP;
1665 topFlag = BF_LEFT;
1666 rightFlag = BF_BOTTOM;
1667 lightFlag = BF_DIAGONAL_ENDTOPRIGHT;
1668 shadeFlag = BF_DIAGONAL_ENDBOTTOMRIGHT;
1670 ::SetRect(&topRect, R.left, R.top + lOffset, R.right, R.bottom - rOffset);
1671 ::SetRect(&sideRect, R.left + 2, R.top, R.right - 2 + selectedOffset,
1672 R.bottom);
1673 ::SetRect(&bottomRect, R.right - 2, R.top, R.right, R.bottom);
1674 ::SetRect(&lightRect, R.left, R.top, R.left + 3, R.top + 3);
1675 ::SetRect(&shadeRect, R.left + 1, R.bottom - 2, R.left + 2, R.bottom - 1);
1676 break;
1677 case BF_TOP:
1678 leftFlag = BF_LEFT;
1679 topFlag = BF_TOP;
1680 rightFlag = BF_RIGHT;
1681 lightFlag = BF_DIAGONAL_ENDTOPRIGHT;
1682 shadeFlag = BF_DIAGONAL_ENDBOTTOMRIGHT;
1684 ::SetRect(&topRect, R.left + lOffset, R.top, R.right - rOffset, R.bottom);
1685 ::SetRect(&sideRect, R.left, R.top + 2, R.right,
1686 R.bottom - 1 + selectedOffset);
1687 ::SetRect(&bottomRect, R.left, R.bottom - 1, R.right, R.bottom);
1688 ::SetRect(&lightRect, R.left, R.top, R.left + 3, R.top + 3);
1689 ::SetRect(&shadeRect, R.right - 2, R.top + 1, R.right - 1, R.top + 2);
1690 break;
1691 case BF_RIGHT:
1692 leftFlag = BF_TOP;
1693 topFlag = BF_RIGHT;
1694 rightFlag = BF_BOTTOM;
1695 lightFlag = BF_DIAGONAL_ENDTOPLEFT;
1696 shadeFlag = BF_DIAGONAL_ENDBOTTOMLEFT;
1698 ::SetRect(&topRect, R.left, R.top + lOffset, R.right, R.bottom - rOffset);
1699 ::SetRect(&sideRect, R.left + 2 - selectedOffset, R.top, R.right - 2,
1700 R.bottom);
1701 ::SetRect(&bottomRect, R.left, R.top, R.left + 2, R.bottom);
1702 ::SetRect(&lightRect, R.right - 3, R.top, R.right - 1, R.top + 2);
1703 ::SetRect(&shadeRect, R.right - 2, R.bottom - 3, R.right, R.bottom - 1);
1704 break;
1705 case BF_BOTTOM:
1706 leftFlag = BF_LEFT;
1707 topFlag = BF_BOTTOM;
1708 rightFlag = BF_RIGHT;
1709 lightFlag = BF_DIAGONAL_ENDTOPLEFT;
1710 shadeFlag = BF_DIAGONAL_ENDBOTTOMLEFT;
1712 ::SetRect(&topRect, R.left + lOffset, R.top, R.right - rOffset, R.bottom);
1713 ::SetRect(&sideRect, R.left, R.top + 2 - selectedOffset, R.right,
1714 R.bottom - 2);
1715 ::SetRect(&bottomRect, R.left, R.top, R.right, R.top + 2);
1716 ::SetRect(&lightRect, R.left, R.bottom - 3, R.left + 2, R.bottom - 1);
1717 ::SetRect(&shadeRect, R.right - 2, R.bottom - 3, R.right, R.bottom - 1);
1718 break;
1719 default:
1720 MOZ_CRASH();
1723 // Background
1724 ::FillRect(hdc, &R, (HBRUSH)(COLOR_3DFACE + 1));
1726 // Tab "Top"
1727 ::DrawEdge(hdc, &topRect, EDGE_RAISED, BF_SOFT | topFlag);
1729 // Tab "Bottom"
1730 if (!aSelected) ::DrawEdge(hdc, &bottomRect, EDGE_RAISED, BF_SOFT | topFlag);
1732 // Tab "Sides"
1733 if (!aDrawLeft) leftFlag = 0;
1734 if (!aDrawRight) rightFlag = 0;
1735 ::DrawEdge(hdc, &sideRect, EDGE_RAISED, BF_SOFT | leftFlag | rightFlag);
1737 // Tab Diagonal Corners
1738 if (aDrawLeft) ::DrawEdge(hdc, &lightRect, EDGE_RAISED, BF_SOFT | lightFlag);
1740 if (aDrawRight) ::DrawEdge(hdc, &shadeRect, EDGE_RAISED, BF_SOFT | shadeFlag);
1743 void nsNativeThemeWin::DrawCheckedRect(HDC hdc, const RECT& rc, int32_t fore,
1744 int32_t back, HBRUSH defaultBack) {
1745 static WORD patBits[8] = {0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55};
1747 HBITMAP patBmp = ::CreateBitmap(8, 8, 1, 1, patBits);
1748 if (patBmp) {
1749 HBRUSH brush = (HBRUSH)::CreatePatternBrush(patBmp);
1750 if (brush) {
1751 COLORREF oldForeColor = ::SetTextColor(hdc, ::GetSysColor(fore));
1752 COLORREF oldBackColor = ::SetBkColor(hdc, ::GetSysColor(back));
1753 POINT vpOrg;
1755 ::UnrealizeObject(brush);
1756 ::GetViewportOrgEx(hdc, &vpOrg);
1757 ::SetBrushOrgEx(hdc, vpOrg.x + rc.left, vpOrg.y + rc.top, nullptr);
1758 HBRUSH oldBrush = (HBRUSH)::SelectObject(hdc, brush);
1759 ::FillRect(hdc, &rc, brush);
1760 ::SetTextColor(hdc, oldForeColor);
1761 ::SetBkColor(hdc, oldBackColor);
1762 ::SelectObject(hdc, oldBrush);
1763 ::DeleteObject(brush);
1764 } else
1765 ::FillRect(hdc, &rc, defaultBack);
1767 ::DeleteObject(patBmp);
1771 nsresult nsNativeThemeWin::ClassicDrawWidgetBackground(
1772 gfxContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance,
1773 const nsRect& aRect, const nsRect& aDirtyRect) {
1774 int32_t part, state;
1775 bool focused;
1776 nsresult rv;
1777 rv = ClassicGetThemePartAndState(aFrame, aAppearance, part, state, focused);
1778 if (NS_FAILED(rv)) return rv;
1780 if (AssumeThemePartAndStateAreTransparent(part, state)) {
1781 return NS_OK;
1784 gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel());
1785 RECT widgetRect;
1786 gfxRect tr(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()),
1787 dr(aDirtyRect.X(), aDirtyRect.Y(), aDirtyRect.Width(),
1788 aDirtyRect.Height());
1790 tr.Scale(1.0 / p2a);
1791 dr.Scale(1.0 / p2a);
1793 gfxWindowsNativeDrawing nativeDrawing(
1794 aContext, dr, GetWidgetNativeDrawingFlags(aAppearance));
1796 RENDER_AGAIN:
1798 HDC hdc = nativeDrawing.BeginNativeDrawing();
1799 if (!hdc) return NS_ERROR_FAILURE;
1801 nativeDrawing.TransformToNativeRect(tr, widgetRect);
1803 rv = NS_OK;
1804 switch (aAppearance) {
1805 // Draw button
1806 case StyleAppearance::Button: {
1807 if (focused) {
1808 // draw dark button focus border first
1809 if (HBRUSH brush = ::GetSysColorBrush(COLOR_3DDKSHADOW)) {
1810 ::FrameRect(hdc, &widgetRect, brush);
1812 InflateRect(&widgetRect, -1, -1);
1814 // setup DC to make DrawFrameControl draw correctly
1815 int32_t oldTA = ::SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
1816 ::DrawFrameControl(hdc, &widgetRect, part, state);
1817 ::SetTextAlign(hdc, oldTA);
1818 break;
1820 // Draw controls with 2px 3D inset border
1821 case StyleAppearance::NumberInput:
1822 case StyleAppearance::Textfield:
1823 case StyleAppearance::Textarea:
1824 case StyleAppearance::Listbox:
1825 case StyleAppearance::Menulist:
1826 case StyleAppearance::MenulistButton: {
1827 // Draw inset edge
1828 ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
1830 ElementState elementState = GetContentState(aFrame, aAppearance);
1832 // Fill in background
1834 if (elementState.HasState(ElementState::DISABLED) ||
1835 (aFrame->GetContent()->IsXULElement() && IsReadOnly(aFrame)))
1836 ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1));
1837 else
1838 ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_WINDOW + 1));
1840 break;
1842 case StyleAppearance::Treeview: {
1843 // Draw inset edge
1844 ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
1846 // Fill in window color background
1847 ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_WINDOW + 1));
1849 break;
1851 // Draw 3D face background controls
1852 case StyleAppearance::ProgressBar:
1853 // Draw 3D border
1854 ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE);
1855 InflateRect(&widgetRect, -1, -1);
1856 [[fallthrough]];
1857 case StyleAppearance::Tabpanel: {
1858 ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1));
1859 break;
1861 case StyleAppearance::RangeThumb: {
1862 ElementState elementState = GetContentState(aFrame, aAppearance);
1864 ::DrawEdge(hdc, &widgetRect, EDGE_RAISED,
1865 BF_RECT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
1866 if (elementState.HasState(ElementState::DISABLED)) {
1867 DrawCheckedRect(hdc, widgetRect, COLOR_3DFACE, COLOR_3DHILIGHT,
1868 (HBRUSH)COLOR_3DHILIGHT);
1871 break;
1873 // Draw scale track background
1874 case StyleAppearance::Range: {
1875 const int32_t trackWidth = 4;
1876 // When rounding is necessary, we round the position of the track
1877 // away from the chevron of the thumb to make it look better.
1878 if (IsRangeHorizontal(aFrame)) {
1879 widgetRect.top += (widgetRect.bottom - widgetRect.top - trackWidth) / 2;
1880 widgetRect.bottom = widgetRect.top + trackWidth;
1881 } else {
1882 if (!IsFrameRTL(aFrame)) {
1883 widgetRect.left +=
1884 (widgetRect.right - widgetRect.left - trackWidth) / 2;
1885 widgetRect.right = widgetRect.left + trackWidth;
1886 } else {
1887 widgetRect.right -=
1888 (widgetRect.right - widgetRect.left - trackWidth) / 2;
1889 widgetRect.left = widgetRect.right - trackWidth;
1893 ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
1894 ::FillRect(hdc, &widgetRect, (HBRUSH)GetStockObject(GRAY_BRUSH));
1896 break;
1898 case StyleAppearance::Progresschunk: {
1899 nsIFrame* stateFrame = aFrame->GetParent();
1900 ElementState elementState = GetContentState(stateFrame, aAppearance);
1902 const bool indeterminate =
1903 elementState.HasState(ElementState::INDETERMINATE);
1904 bool vertical = IsVerticalProgress(stateFrame);
1906 nsIContent* content = aFrame->GetContent();
1907 if (!indeterminate || !content) {
1908 ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_HIGHLIGHT + 1));
1909 break;
1912 RECT overlayRect = CalculateProgressOverlayRect(
1913 aFrame, &widgetRect, vertical, indeterminate, true);
1915 ::FillRect(hdc, &overlayRect, (HBRUSH)(COLOR_HIGHLIGHT + 1));
1917 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
1918 NS_WARNING("unable to animate progress widget!");
1920 break;
1923 // Draw Tab
1924 case StyleAppearance::Tab: {
1925 DrawTab(hdc, widgetRect, IsBottomTab(aFrame) ? BF_BOTTOM : BF_TOP,
1926 IsSelectedTab(aFrame), !IsRightToSelectedTab(aFrame),
1927 !IsLeftToSelectedTab(aFrame));
1929 break;
1931 case StyleAppearance::Tabpanels:
1932 ::DrawEdge(hdc, &widgetRect, EDGE_RAISED,
1933 BF_SOFT | BF_MIDDLE | BF_LEFT | BF_RIGHT | BF_BOTTOM);
1935 break;
1937 default:
1938 rv = NS_ERROR_FAILURE;
1939 break;
1942 nativeDrawing.EndNativeDrawing();
1944 if (NS_FAILED(rv)) return rv;
1946 if (nativeDrawing.ShouldRenderAgain()) goto RENDER_AGAIN;
1948 nativeDrawing.PaintToContext();
1950 return rv;
1953 uint32_t nsNativeThemeWin::GetWidgetNativeDrawingFlags(
1954 StyleAppearance aAppearance) {
1955 switch (aAppearance) {
1956 case StyleAppearance::Button:
1957 case StyleAppearance::NumberInput:
1958 case StyleAppearance::Textfield:
1959 case StyleAppearance::Textarea:
1960 case StyleAppearance::Menulist:
1961 case StyleAppearance::MenulistButton:
1962 return gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
1963 gfxWindowsNativeDrawing::CAN_AXIS_ALIGNED_SCALE |
1964 gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
1966 // need to check these others
1967 default:
1968 return gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
1969 gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE |
1970 gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
1974 } // namespace mozilla::widget
1976 ///////////////////////////////////////////
1977 // Creation Routine
1978 ///////////////////////////////////////////
1980 already_AddRefed<Theme> do_CreateNativeThemeDoNotUseDirectly() {
1981 return do_AddRef(new nsNativeThemeWin());