Merge mozilla-central to autoland on a CLOSED TREE
[gecko.git] / widget / windows / nsNativeThemeWin.cpp
blob9cbc26bf043107c79b4ad21e3819af10626a7f0c
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/WindowsVersion.h"
23 #include "mozilla/dom/XULButtonElement.h"
24 #include "nsColor.h"
25 #include "nsComboboxControlFrame.h"
26 #include "nsDeviceContext.h"
27 #include "nsGkAtoms.h"
28 #include "nsIContent.h"
29 #include "nsIContentInlines.h"
30 #include "nsIFrame.h"
31 #include "nsLayoutUtils.h"
32 #include "nsLookAndFeel.h"
33 #include "nsNameSpaceManager.h"
34 #include "Theme.h"
35 #include "nsPresContext.h"
36 #include "nsRect.h"
37 #include "nsSize.h"
38 #include "nsStyleConsts.h"
39 #include "nsTransform2D.h"
40 #include "nsWindow.h"
41 #include "prinrval.h"
42 #include "WinUtils.h"
44 using namespace mozilla;
45 using namespace mozilla::gfx;
46 using namespace mozilla::widget;
48 using ElementState = dom::ElementState;
50 extern mozilla::LazyLogModule gWindowsLog;
52 namespace mozilla::widget {
54 nsNativeThemeWin::nsNativeThemeWin()
55 : Theme(ScrollbarStyle()),
56 mProgressDeterminateTimeStamp(TimeStamp::Now()),
57 mProgressIndeterminateTimeStamp(TimeStamp::Now()),
58 mBorderCacheValid(),
59 mMinimumWidgetSizeCacheValid(),
60 mGutterSizeCacheValid(false) {
61 // If there is a relevant change in forms.css for windows platform,
62 // static widget style variables (e.g. sButtonBorderSize) should be
63 // reinitialized here.
66 nsNativeThemeWin::~nsNativeThemeWin() { nsUXThemeData::Invalidate(); }
68 auto nsNativeThemeWin::IsWidgetNonNative(nsIFrame* aFrame,
69 StyleAppearance aAppearance)
70 -> NonNative {
71 if (IsWidgetScrollbarPart(aAppearance) ||
72 aAppearance == StyleAppearance::FocusOutline) {
73 return NonNative::Always;
76 // We only know how to draw light widgets, so we defer to the non-native
77 // theme when appropriate.
78 if (Theme::ThemeSupportsWidget(aFrame->PresContext(), aFrame, aAppearance) &&
79 LookAndFeel::ColorSchemeForFrame(aFrame) ==
80 LookAndFeel::ColorScheme::Dark) {
81 return NonNative::BecauseColorMismatch;
83 return NonNative::No;
86 static int32_t GetTopLevelWindowActiveState(nsIFrame* aFrame) {
87 // Used by window frame and button box rendering. We can end up in here in
88 // the content process when rendering one of these moz styles freely in a
89 // page. Bail in this case, there is no applicable window focus state.
90 if (!XRE_IsParentProcess()) {
91 return mozilla::widget::themeconst::FS_INACTIVE;
93 // All headless windows are considered active so they are painted.
94 if (gfxPlatform::IsHeadless()) {
95 return mozilla::widget::themeconst::FS_ACTIVE;
97 // Get the widget. nsIFrame's GetNearestWidget walks up the view chain
98 // until it finds a real window.
99 nsIWidget* widget = aFrame->GetNearestWidget();
100 nsWindow* window = static_cast<nsWindow*>(widget);
101 if (!window) return mozilla::widget::themeconst::FS_INACTIVE;
102 if (widget && !window->IsTopLevelWidget() &&
103 !(window = window->GetParentWindowBase(false)))
104 return mozilla::widget::themeconst::FS_INACTIVE;
106 if (window->GetWindowHandle() == ::GetActiveWindow())
107 return mozilla::widget::themeconst::FS_ACTIVE;
108 return mozilla::widget::themeconst::FS_INACTIVE;
111 static int32_t GetWindowFrameButtonState(nsIFrame* aFrame,
112 ElementState elementState) {
113 if (GetTopLevelWindowActiveState(aFrame) ==
114 mozilla::widget::themeconst::FS_INACTIVE) {
115 if (elementState.HasState(ElementState::HOVER))
116 return mozilla::widget::themeconst::BS_HOT;
117 return mozilla::widget::themeconst::BS_INACTIVE;
120 if (elementState.HasState(ElementState::HOVER)) {
121 if (elementState.HasState(ElementState::ACTIVE))
122 return mozilla::widget::themeconst::BS_PUSHED;
123 return mozilla::widget::themeconst::BS_HOT;
125 return mozilla::widget::themeconst::BS_NORMAL;
128 static int32_t GetClassicWindowFrameButtonState(ElementState elementState) {
129 if (elementState.HasState(ElementState::ACTIVE) &&
130 elementState.HasState(ElementState::HOVER))
131 return DFCS_BUTTONPUSH | DFCS_PUSHED;
132 return DFCS_BUTTONPUSH;
135 static bool IsTopLevelMenu(nsIFrame* aFrame) {
136 auto* menu = dom::XULButtonElement::FromNodeOrNull(aFrame->GetContent());
137 return menu && menu->IsOnMenuBar();
140 static MARGINS GetCheckboxMargins(HANDLE theme, HDC hdc) {
141 MARGINS checkboxContent = {0};
142 GetThemeMargins(theme, hdc, MENU_POPUPCHECK, MCB_NORMAL, TMT_CONTENTMARGINS,
143 nullptr, &checkboxContent);
144 return checkboxContent;
147 static SIZE GetCheckboxBGSize(HANDLE theme, HDC hdc) {
148 SIZE checkboxSize;
149 GetThemePartSize(theme, hdc, MENU_POPUPCHECK, MC_CHECKMARKNORMAL, nullptr,
150 TS_TRUE, &checkboxSize);
152 MARGINS checkboxMargins = GetCheckboxMargins(theme, hdc);
154 int leftMargin = checkboxMargins.cxLeftWidth;
155 int rightMargin = checkboxMargins.cxRightWidth;
156 int topMargin = checkboxMargins.cyTopHeight;
157 int bottomMargin = checkboxMargins.cyBottomHeight;
159 int width = leftMargin + checkboxSize.cx + rightMargin;
160 int height = topMargin + checkboxSize.cy + bottomMargin;
161 SIZE ret;
162 ret.cx = width;
163 ret.cy = height;
164 return ret;
167 static SIZE GetCheckboxBGBounds(HANDLE theme, HDC hdc) {
168 MARGINS checkboxBGSizing = {0};
169 MARGINS checkboxBGContent = {0};
170 GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL,
171 TMT_SIZINGMARGINS, nullptr, &checkboxBGSizing);
172 GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL,
173 TMT_CONTENTMARGINS, nullptr, &checkboxBGContent);
175 #define posdx(d) ((d) > 0 ? d : 0)
177 int dx =
178 posdx(checkboxBGContent.cxRightWidth - checkboxBGSizing.cxRightWidth) +
179 posdx(checkboxBGContent.cxLeftWidth - checkboxBGSizing.cxLeftWidth);
180 int dy =
181 posdx(checkboxBGContent.cyTopHeight - checkboxBGSizing.cyTopHeight) +
182 posdx(checkboxBGContent.cyBottomHeight - checkboxBGSizing.cyBottomHeight);
184 #undef posdx
186 SIZE ret(GetCheckboxBGSize(theme, hdc));
187 ret.cx += dx;
188 ret.cy += dy;
189 return ret;
192 static SIZE GetGutterSize(HANDLE theme, HDC hdc) {
193 SIZE gutterSize;
194 GetThemePartSize(theme, hdc, MENU_POPUPGUTTER, 0, nullptr, TS_TRUE,
195 &gutterSize);
197 SIZE checkboxBGSize(GetCheckboxBGBounds(theme, hdc));
199 SIZE itemSize;
200 GetThemePartSize(theme, hdc, MENU_POPUPITEM, MPI_NORMAL, nullptr, TS_TRUE,
201 &itemSize);
203 // Figure out how big the menuitem's icon will be (if present) at current DPI
204 // Needs the system scale for consistency with Windows Theme API.
205 double scaleFactor = WinUtils::SystemScaleFactor();
206 int iconDevicePixels = NSToIntRound(16 * scaleFactor);
207 SIZE iconSize = {iconDevicePixels, iconDevicePixels};
208 // Not really sure what margins should be used here, but this seems to work in
209 // practice...
210 MARGINS margins = {0};
211 GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL,
212 TMT_CONTENTMARGINS, nullptr, &margins);
213 iconSize.cx += margins.cxLeftWidth + margins.cxRightWidth;
214 iconSize.cy += margins.cyTopHeight + margins.cyBottomHeight;
216 int width = std::max(
217 itemSize.cx, std::max(iconSize.cx, checkboxBGSize.cx) + gutterSize.cx);
218 int height = std::max(itemSize.cy, std::max(iconSize.cy, checkboxBGSize.cy));
220 SIZE ret;
221 ret.cx = width;
222 ret.cy = height;
223 return ret;
226 SIZE nsNativeThemeWin::GetCachedGutterSize(HANDLE theme) {
227 if (mGutterSizeCacheValid) {
228 return mGutterSizeCache;
231 mGutterSizeCache = GetGutterSize(theme, nullptr);
232 mGutterSizeCacheValid = true;
234 return mGutterSizeCache;
237 /* DrawThemeBGRTLAware - render a theme part based on rtl state.
238 * Some widgets are not direction-neutral and need to be drawn reversed for
239 * RTL. Windows provides a way to do this with SetLayout, but this reverses
240 * the entire drawing area of a given device context, which means that its
241 * use will also affect the positioning of the widget. There are two ways
242 * to work around this:
244 * Option 1: Alter the position of the rect that we send so that we cancel
245 * out the positioning effects of SetLayout
246 * Option 2: Create a memory DC with the widgetRect's dimensions, draw onto
247 * that, and then transfer the results back to our DC
249 * This function tries to implement option 1, under the assumption that the
250 * correct way to reverse the effects of SetLayout is to translate the rect
251 * such that the offset from the DC bitmap's left edge to the old rect's
252 * left edge is equal to the offset from the DC bitmap's right edge to the
253 * new rect's right edge. In other words,
254 * (oldRect.left + vpOrg.x) == ((dcBMP.width - vpOrg.x) - newRect.right)
256 static HRESULT DrawThemeBGRTLAware(HANDLE aTheme, HDC aHdc, int aPart,
257 int aState, const RECT* aWidgetRect,
258 const RECT* aClipRect, bool aIsRtl) {
259 NS_ASSERTION(aTheme, "Bad theme handle.");
260 NS_ASSERTION(aHdc, "Bad hdc.");
261 NS_ASSERTION(aWidgetRect, "Bad rect.");
262 NS_ASSERTION(aClipRect, "Bad clip rect.");
264 if (!aIsRtl) {
265 return DrawThemeBackground(aTheme, aHdc, aPart, aState, aWidgetRect,
266 aClipRect);
269 HGDIOBJ hObj = GetCurrentObject(aHdc, OBJ_BITMAP);
270 BITMAP bitmap;
271 POINT vpOrg;
273 if (hObj && GetObject(hObj, sizeof(bitmap), &bitmap) &&
274 GetViewportOrgEx(aHdc, &vpOrg)) {
275 RECT newWRect(*aWidgetRect);
276 newWRect.left = bitmap.bmWidth - (aWidgetRect->right + 2 * vpOrg.x);
277 newWRect.right = bitmap.bmWidth - (aWidgetRect->left + 2 * vpOrg.x);
279 RECT newCRect;
280 RECT* newCRectPtr = nullptr;
282 if (aClipRect) {
283 newCRect.top = aClipRect->top;
284 newCRect.bottom = aClipRect->bottom;
285 newCRect.left = bitmap.bmWidth - (aClipRect->right + 2 * vpOrg.x);
286 newCRect.right = bitmap.bmWidth - (aClipRect->left + 2 * vpOrg.x);
287 newCRectPtr = &newCRect;
290 SetLayout(aHdc, LAYOUT_RTL);
291 HRESULT hr = DrawThemeBackground(aTheme, aHdc, aPart, aState, &newWRect,
292 newCRectPtr);
293 SetLayout(aHdc, 0);
294 if (SUCCEEDED(hr)) {
295 return hr;
298 return DrawThemeBackground(aTheme, aHdc, aPart, aState, aWidgetRect,
299 aClipRect);
303 * Caption button padding data - 'hot' button padding.
304 * These areas are considered hot, in that they activate
305 * a button when hovered or clicked. The button graphic
306 * is drawn inside the padding border. Unrecognized themes
307 * are treated as their recognized counterparts for now.
308 * left top right bottom
309 * classic min 1 2 0 1
310 * classic max 0 2 1 1
311 * classic close 1 2 2 1
313 * aero basic min 1 2 0 2
314 * aero basic max 0 2 1 2
315 * aero basic close 1 2 1 2
317 * 'cold' button padding - generic button padding, should
318 * be handled in css.
319 * left top right bottom
320 * classic min 0 0 0 0
321 * classic max 0 0 0 0
322 * classic close 0 0 0 0
324 * aero basic min 0 0 1 0
325 * aero basic max 1 0 0 0
326 * aero basic close 0 0 0 0
329 enum CaptionDesktopTheme {
330 CAPTION_CLASSIC = 0,
331 CAPTION_BASIC,
334 enum CaptionButton {
335 CAPTIONBUTTON_MINIMIZE = 0,
336 CAPTIONBUTTON_RESTORE,
337 CAPTIONBUTTON_CLOSE,
340 struct CaptionButtonPadding {
341 RECT hotPadding[3];
344 // RECT: left, top, right, bottom
345 static CaptionButtonPadding buttonData[3] = {
346 {{{1, 2, 0, 1}, {0, 2, 1, 1}, {1, 2, 2, 1}}},
347 {{{1, 2, 0, 2}, {0, 2, 1, 2}, {1, 2, 2, 2}}},
348 {{{0, 2, 0, 2}, {0, 2, 1, 2}, {1, 2, 2, 2}}}};
350 // Adds "hot" caption button padding to minimum widget size.
351 static void AddPaddingRect(LayoutDeviceIntSize* aSize, CaptionButton button) {
352 if (!aSize) return;
353 RECT offset = buttonData[CAPTION_BASIC].hotPadding[button];
354 aSize->width += offset.left + offset.right;
355 aSize->height += offset.top + offset.bottom;
358 // If we've added padding to the minimum widget size, offset
359 // the area we draw into to compensate.
360 static void OffsetBackgroundRect(RECT& rect, CaptionButton button) {
361 RECT offset = buttonData[CAPTION_BASIC].hotPadding[button];
362 rect.left += offset.left;
363 rect.top += offset.top;
364 rect.right -= offset.right;
365 rect.bottom -= offset.bottom;
369 * Notes on progress track and meter part constants:
370 * xp and up:
371 * PP_BAR(_VERT) - base progress track
372 * PP_TRANSPARENTBAR(_VERT) - transparent progress track. this only works if
373 * the underlying surface supports alpha. otherwise
374 * theme lib's DrawThemeBackground falls back on
375 * opaque PP_BAR. we currently don't use this.
376 * PP_CHUNK(_VERT) - xp progress meter. this does not draw an xp style
377 * progress w/chunks, it draws fill using the chunk
378 * graphic.
379 * vista and up:
380 * PP_FILL(_VERT) - progress meter. these have four states/colors.
381 * PP_PULSEOVERLAY(_VERT) - white reflection - an overlay, not sure what this
382 * is used for.
383 * PP_MOVEOVERLAY(_VERT) - green pulse - the pulse effect overlay on
384 * determined progress bars. we also use this for
385 * indeterminate chunk.
387 * Notes on state constants:
388 * PBBS_NORMAL - green progress
389 * PBBVS_PARTIAL/PBFVS_ERROR - red error progress
390 * PBFS_PAUSED - yellow paused progress
392 * There is no common controls style indeterminate part on vista and up.
396 * Progress bar related constants. These values are found by experimenting and
397 * comparing against native widgets used by the system. They are very unlikely
398 * exact but try to not be too wrong.
400 // The amount of time we animate progress meters parts across the frame.
401 static const double kProgressDeterminateTimeSpan = 3.0;
402 static const double kProgressIndeterminateTimeSpan = 5.0;
403 // The width of the overlay used to animate the horizontal progress bar (Vista
404 // and later).
405 static const int32_t kProgressHorizontalOverlaySize = 120;
406 // The height of the overlay used to animate the vertical progress bar (Vista
407 // and later).
408 static const int32_t kProgressVerticalOverlaySize = 45;
409 // The height of the overlay used for the vertical indeterminate progress bar
410 // (Vista and later).
411 static const int32_t kProgressVerticalIndeterminateOverlaySize = 60;
412 // The width of the overlay used to animate the indeterminate progress bar
413 // (Windows Classic).
414 static const int32_t kProgressClassicOverlaySize = 40;
417 * GetProgressOverlayStyle - returns the proper overlay part for themed
418 * progress bars based on os and orientation.
420 static int32_t GetProgressOverlayStyle(bool aIsVertical) {
421 return aIsVertical ? PP_MOVEOVERLAYVERT : PP_MOVEOVERLAY;
425 * GetProgressOverlaySize - returns the minimum width or height for themed
426 * progress bar overlays. This includes the width of indeterminate chunks
427 * and vista pulse overlays.
429 static int32_t GetProgressOverlaySize(bool aIsVertical, bool aIsIndeterminate) {
430 if (aIsVertical) {
431 return aIsIndeterminate ? kProgressVerticalIndeterminateOverlaySize
432 : kProgressVerticalOverlaySize;
434 return kProgressHorizontalOverlaySize;
438 * IsProgressMeterFilled - Determines if a progress meter is at 100% fill based
439 * on a comparison of the current value and maximum.
441 static bool IsProgressMeterFilled(nsIFrame* aFrame) {
442 NS_ENSURE_TRUE(aFrame, false);
443 nsIFrame* parentFrame = aFrame->GetParent();
444 NS_ENSURE_TRUE(parentFrame, false);
445 return nsNativeTheme::GetProgressValue(parentFrame) ==
446 nsNativeTheme::GetProgressMaxValue(parentFrame);
450 * CalculateProgressOverlayRect - returns the padded overlay animation rect
451 * used in rendering progress bars. Resulting rects are used in rendering
452 * vista+ pulse overlays and indeterminate progress meters. Graphics should
453 * be rendered at the origin.
455 RECT nsNativeThemeWin::CalculateProgressOverlayRect(nsIFrame* aFrame,
456 RECT* aWidgetRect,
457 bool aIsVertical,
458 bool aIsIndeterminate,
459 bool aIsClassic) {
460 NS_ASSERTION(aFrame, "bad frame pointer");
461 NS_ASSERTION(aWidgetRect, "bad rect pointer");
463 int32_t frameSize = aIsVertical ? aWidgetRect->bottom - aWidgetRect->top
464 : aWidgetRect->right - aWidgetRect->left;
466 // Recycle a set of progress pulse timers - these timers control the position
467 // of all progress overlays and indeterminate chunks that get rendered.
468 double span = aIsIndeterminate ? kProgressIndeterminateTimeSpan
469 : kProgressDeterminateTimeSpan;
470 TimeDuration period;
471 if (!aIsIndeterminate) {
472 if (TimeStamp::Now() >
473 (mProgressDeterminateTimeStamp + TimeDuration::FromSeconds(span))) {
474 mProgressDeterminateTimeStamp = TimeStamp::Now();
476 period = TimeStamp::Now() - mProgressDeterminateTimeStamp;
477 } else {
478 if (TimeStamp::Now() >
479 (mProgressIndeterminateTimeStamp + TimeDuration::FromSeconds(span))) {
480 mProgressIndeterminateTimeStamp = TimeStamp::Now();
482 period = TimeStamp::Now() - mProgressIndeterminateTimeStamp;
485 double percent = period / TimeDuration::FromSeconds(span);
487 if (!aIsVertical && IsFrameRTL(aFrame)) percent = 1 - percent;
489 RECT overlayRect = *aWidgetRect;
490 int32_t overlaySize;
491 if (!aIsClassic) {
492 overlaySize = GetProgressOverlaySize(aIsVertical, aIsIndeterminate);
493 } else {
494 overlaySize = kProgressClassicOverlaySize;
497 // Calculate a bounds that is larger than the meters frame such that the
498 // overlay starts and ends completely off the edge of the frame:
499 // [overlay][frame][overlay]
500 // This also yields a nice delay on rotation. Use overlaySize as the minimum
501 // size for [overlay] based on the graphics dims. If [frame] is larger, use
502 // the frame size instead.
503 int trackWidth = frameSize > overlaySize ? frameSize : overlaySize;
504 if (!aIsVertical) {
505 int xPos = aWidgetRect->left - trackWidth;
506 xPos += (int)ceil(((double)(trackWidth * 2) * percent));
507 overlayRect.left = xPos;
508 overlayRect.right = xPos + overlaySize;
509 } else {
510 int yPos = aWidgetRect->bottom + trackWidth;
511 yPos -= (int)ceil(((double)(trackWidth * 2) * percent));
512 overlayRect.bottom = yPos;
513 overlayRect.top = yPos - overlaySize;
515 return overlayRect;
519 * DrawProgressMeter - render an appropriate progress meter based on progress
520 * meter style, orientation, and os. Note, this does not render the underlying
521 * progress track.
523 * @param aFrame the widget frame
524 * @param aAppearance type of widget
525 * @param aTheme progress theme handle
526 * @param aHdc hdc returned by gfxWindowsNativeDrawing
527 * @param aPart the PP_X progress part
528 * @param aState the theme state
529 * @param aWidgetRect bounding rect for the widget
530 * @param aClipRect dirty rect that needs drawing.
531 * @param aAppUnits app units per device pixel
533 void nsNativeThemeWin::DrawThemedProgressMeter(
534 nsIFrame* aFrame, StyleAppearance aAppearance, HANDLE aTheme, HDC aHdc,
535 int aPart, int aState, RECT* aWidgetRect, RECT* aClipRect) {
536 if (!aFrame || !aTheme || !aHdc) return;
538 NS_ASSERTION(aWidgetRect, "bad rect pointer");
539 NS_ASSERTION(aClipRect, "bad clip rect pointer");
541 RECT adjWidgetRect, adjClipRect;
542 adjWidgetRect = *aWidgetRect;
543 adjClipRect = *aClipRect;
545 nsIFrame* parentFrame = aFrame->GetParent();
546 if (!parentFrame) {
547 // We have no parent to work with, just bail.
548 NS_WARNING("No parent frame for progress rendering. Can't paint.");
549 return;
552 ElementState elementState = GetContentState(parentFrame, aAppearance);
553 bool vertical = IsVerticalProgress(parentFrame);
554 bool indeterminate = elementState.HasState(ElementState::INDETERMINATE);
555 bool animate = indeterminate;
557 // Vista and up progress meter is fill style, rendered here. We render
558 // the pulse overlay in the follow up section below.
559 DrawThemeBackground(aTheme, aHdc, aPart, aState, &adjWidgetRect,
560 &adjClipRect);
561 if (!IsProgressMeterFilled(aFrame)) {
562 animate = true;
565 if (animate) {
566 // Indeterminate rendering
567 int32_t overlayPart = GetProgressOverlayStyle(vertical);
568 RECT overlayRect = CalculateProgressOverlayRect(
569 aFrame, &adjWidgetRect, vertical, indeterminate, false);
570 DrawThemeBackground(aTheme, aHdc, overlayPart, aState, &overlayRect,
571 &adjClipRect);
573 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 60)) {
574 NS_WARNING("unable to animate progress widget!");
579 LayoutDeviceIntMargin nsNativeThemeWin::GetCachedWidgetBorder(
580 HTHEME aTheme, nsUXThemeClass aThemeClass, StyleAppearance aAppearance,
581 int32_t aPart, int32_t aState) {
582 int32_t cacheIndex = aThemeClass * THEME_PART_DISTINCT_VALUE_COUNT + aPart;
583 int32_t cacheBitIndex = cacheIndex / 8;
584 uint8_t cacheBit = 1u << (cacheIndex % 8);
586 if (mBorderCacheValid[cacheBitIndex] & cacheBit) {
587 return mBorderCache[cacheIndex];
590 // Get our info.
591 RECT outerRect; // Create a fake outer rect.
592 outerRect.top = outerRect.left = 100;
593 outerRect.right = outerRect.bottom = 200;
594 RECT contentRect(outerRect);
595 HRESULT res = GetThemeBackgroundContentRect(aTheme, nullptr, aPart, aState,
596 &outerRect, &contentRect);
598 if (FAILED(res)) {
599 return LayoutDeviceIntMargin();
602 // Now compute the delta in each direction and place it in our
603 // nsIntMargin struct.
604 LayoutDeviceIntMargin result;
605 result.top = contentRect.top - outerRect.top;
606 result.bottom = outerRect.bottom - contentRect.bottom;
607 result.left = contentRect.left - outerRect.left;
608 result.right = outerRect.right - contentRect.right;
610 mBorderCacheValid[cacheBitIndex] |= cacheBit;
611 mBorderCache[cacheIndex] = result;
613 return result;
616 nsresult nsNativeThemeWin::GetCachedMinimumWidgetSize(
617 nsIFrame* aFrame, HANDLE aTheme, nsUXThemeClass aThemeClass,
618 StyleAppearance aAppearance, int32_t aPart, int32_t aState,
619 THEMESIZE aSizeReq, mozilla::LayoutDeviceIntSize* aResult) {
620 int32_t cachePart = aPart;
622 if (aAppearance == StyleAppearance::Button && aSizeReq == TS_MIN) {
623 // In practice, StyleAppearance::Button is the only widget type which has an
624 // aSizeReq that varies for us, and it can only be TS_MIN or TS_TRUE. Just
625 // stuff that extra bit into the aPart part of the cache, since BP_Count is
626 // well below THEME_PART_DISTINCT_VALUE_COUNT anyway.
627 cachePart = BP_Count;
630 MOZ_ASSERT(aPart < THEME_PART_DISTINCT_VALUE_COUNT);
631 int32_t cacheIndex =
632 aThemeClass * THEME_PART_DISTINCT_VALUE_COUNT + cachePart;
633 int32_t cacheBitIndex = cacheIndex / 8;
634 uint8_t cacheBit = 1u << (cacheIndex % 8);
636 if (mMinimumWidgetSizeCacheValid[cacheBitIndex] & cacheBit) {
637 *aResult = mMinimumWidgetSizeCache[cacheIndex];
638 return NS_OK;
641 HDC hdc = ::GetDC(NULL);
642 if (!hdc) {
643 return NS_ERROR_FAILURE;
646 SIZE sz;
647 GetThemePartSize(aTheme, hdc, aPart, aState, nullptr, aSizeReq, &sz);
648 aResult->width = sz.cx;
649 aResult->height = sz.cy;
651 switch (aAppearance) {
652 case StyleAppearance::SpinnerUpbutton:
653 case StyleAppearance::SpinnerDownbutton:
654 aResult->width++;
655 aResult->height = aResult->height / 2 + 1;
656 break;
658 case StyleAppearance::Menuseparator: {
659 SIZE gutterSize(GetGutterSize(aTheme, hdc));
660 aResult->width += gutterSize.cx;
661 break;
664 case StyleAppearance::Menuarrow:
665 // Use the width of the arrow glyph as padding. See the drawing
666 // code for details.
667 aResult->width *= 2;
668 break;
670 default:
671 break;
674 ::ReleaseDC(nullptr, hdc);
676 mMinimumWidgetSizeCacheValid[cacheBitIndex] |= cacheBit;
677 mMinimumWidgetSizeCache[cacheIndex] = *aResult;
679 return NS_OK;
682 mozilla::Maybe<nsUXThemeClass> nsNativeThemeWin::GetThemeClass(
683 StyleAppearance aAppearance) {
684 switch (aAppearance) {
685 case StyleAppearance::Button:
686 case StyleAppearance::Radio:
687 case StyleAppearance::Checkbox:
688 case StyleAppearance::Groupbox:
689 return Some(eUXButton);
690 case StyleAppearance::NumberInput:
691 case StyleAppearance::Textfield:
692 case StyleAppearance::Textarea:
693 return Some(eUXEdit);
694 case StyleAppearance::Toolbox:
695 return Some(eUXRebar);
696 case StyleAppearance::MozWinMediaToolbox:
697 return Some(eUXMediaRebar);
698 case StyleAppearance::MozWinCommunicationsToolbox:
699 return Some(eUXCommunicationsRebar);
700 case StyleAppearance::MozWinBrowsertabbarToolbox:
701 return Some(eUXBrowserTabBarRebar);
702 case StyleAppearance::Toolbar:
703 case StyleAppearance::Toolbarbutton:
704 case StyleAppearance::Separator:
705 return Some(eUXToolbar);
706 case StyleAppearance::ProgressBar:
707 case StyleAppearance::Progresschunk:
708 return Some(eUXProgress);
709 case StyleAppearance::Tab:
710 case StyleAppearance::Tabpanel:
711 case StyleAppearance::Tabpanels:
712 return Some(eUXTab);
713 case StyleAppearance::Range:
714 case StyleAppearance::RangeThumb:
715 return Some(eUXTrackbar);
716 case StyleAppearance::SpinnerUpbutton:
717 case StyleAppearance::SpinnerDownbutton:
718 return Some(eUXSpin);
719 case StyleAppearance::Menulist:
720 case StyleAppearance::MenulistButton:
721 case StyleAppearance::MozMenulistArrowButton:
722 return Some(eUXCombobox);
723 case StyleAppearance::Treeheadercell:
724 case StyleAppearance::Treeheadersortarrow:
725 return Some(eUXHeader);
726 case StyleAppearance::Listbox:
727 case StyleAppearance::Treeview:
728 case StyleAppearance::Treetwistyopen:
729 case StyleAppearance::Treeitem:
730 return Some(eUXListview);
731 case StyleAppearance::Menubar:
732 case StyleAppearance::Menupopup:
733 case StyleAppearance::Menuitem:
734 case StyleAppearance::Checkmenuitem:
735 case StyleAppearance::Radiomenuitem:
736 case StyleAppearance::Menucheckbox:
737 case StyleAppearance::Menuradio:
738 case StyleAppearance::Menuseparator:
739 case StyleAppearance::Menuarrow:
740 case StyleAppearance::Menuimage:
741 case StyleAppearance::Menuitemtext:
742 return Some(eUXMenu);
743 case StyleAppearance::MozWindowTitlebar:
744 case StyleAppearance::MozWindowTitlebarMaximized:
745 case StyleAppearance::MozWindowButtonClose:
746 case StyleAppearance::MozWindowButtonMinimize:
747 case StyleAppearance::MozWindowButtonMaximize:
748 case StyleAppearance::MozWindowButtonRestore:
749 return Some(eUXWindowFrame);
750 default:
751 return Nothing();
755 HANDLE
756 nsNativeThemeWin::GetTheme(StyleAppearance aAppearance) {
757 mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aAppearance);
758 if (themeClass.isNothing()) {
759 return nullptr;
761 return nsUXThemeData::GetTheme(themeClass.value());
764 int32_t nsNativeThemeWin::StandardGetState(nsIFrame* aFrame,
765 StyleAppearance aAppearance,
766 bool wantFocused) {
767 ElementState elementState = GetContentState(aFrame, aAppearance);
768 if (elementState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE)) {
769 return TS_ACTIVE;
771 if (elementState.HasState(ElementState::HOVER)) {
772 return TS_HOVER;
774 if (wantFocused) {
775 if (elementState.HasState(ElementState::FOCUSRING)) {
776 return TS_FOCUSED;
778 // On Windows, focused buttons are always drawn as such by the native
779 // theme, that's why we check ElementState::FOCUS instead of
780 // ElementState::FOCUSRING.
781 if (aAppearance == StyleAppearance::Button &&
782 elementState.HasState(ElementState::FOCUS)) {
783 return TS_FOCUSED;
787 return TS_NORMAL;
790 bool nsNativeThemeWin::IsMenuActive(nsIFrame* aFrame,
791 StyleAppearance aAppearance) {
792 nsIContent* content = aFrame->GetContent();
793 if (content->IsXULElement() &&
794 content->NodeInfo()->Equals(nsGkAtoms::richlistitem))
795 return CheckBooleanAttr(aFrame, nsGkAtoms::selected);
797 return CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
801 * aPart is filled in with the UXTheme part code. On return, values > 0
802 * are the actual UXTheme part code; -1 means the widget will be drawn by
803 * us; 0 means that we should use part code 0, which isn't a real part code
804 * but elicits some kind of default behaviour from UXTheme when drawing
805 * (but isThemeBackgroundPartiallyTransparent may not work).
807 nsresult nsNativeThemeWin::GetThemePartAndState(nsIFrame* aFrame,
808 StyleAppearance aAppearance,
809 int32_t& aPart,
810 int32_t& aState) {
811 switch (aAppearance) {
812 case StyleAppearance::Button: {
813 aPart = BP_BUTTON;
814 if (!aFrame) {
815 aState = TS_NORMAL;
816 return NS_OK;
819 ElementState elementState = GetContentState(aFrame, aAppearance);
820 if (elementState.HasState(ElementState::DISABLED)) {
821 aState = TS_DISABLED;
822 return NS_OK;
824 if (IsOpenButton(aFrame) || IsCheckedButton(aFrame)) {
825 aState = TS_ACTIVE;
826 return NS_OK;
829 aState = StandardGetState(aFrame, aAppearance, true);
831 // Check for default dialog buttons. These buttons should always look
832 // focused.
833 if (aState == TS_NORMAL && IsDefaultButton(aFrame)) aState = TS_FOCUSED;
834 return NS_OK;
836 case StyleAppearance::Checkbox:
837 case StyleAppearance::Radio: {
838 bool isCheckbox = (aAppearance == StyleAppearance::Checkbox);
839 aPart = isCheckbox ? BP_CHECKBOX : BP_RADIO;
841 enum InputState { UNCHECKED = 0, CHECKED, INDETERMINATE };
842 InputState inputState = UNCHECKED;
844 if (!aFrame) {
845 aState = TS_NORMAL;
846 } else {
847 ElementState elementState = GetContentState(aFrame, aAppearance);
848 if (elementState.HasState(ElementState::CHECKED)) {
849 inputState = CHECKED;
851 if (isCheckbox && elementState.HasState(ElementState::INDETERMINATE)) {
852 inputState = INDETERMINATE;
855 if (elementState.HasState(ElementState::DISABLED)) {
856 aState = TS_DISABLED;
857 } else {
858 aState = StandardGetState(aFrame, aAppearance, false);
862 // 4 unchecked states, 4 checked states, 4 indeterminate states.
863 aState += inputState * 4;
864 return NS_OK;
866 case StyleAppearance::Groupbox: {
867 aPart = BP_GROUPBOX;
868 aState = TS_NORMAL;
869 // Since we don't support groupbox disabled and GBS_DISABLED looks the
870 // same as GBS_NORMAL don't bother supporting GBS_DISABLED.
871 return NS_OK;
873 case StyleAppearance::NumberInput:
874 case StyleAppearance::Textfield:
875 case StyleAppearance::Textarea: {
876 ElementState elementState = GetContentState(aFrame, aAppearance);
878 /* Note: the NOSCROLL type has a rounded corner in each corner. The more
879 * specific HSCROLL, VSCROLL, HVSCROLL types have side and/or top/bottom
880 * edges rendered as straight horizontal lines with sharp corners to
881 * accommodate a scrollbar. However, the scrollbar gets rendered on top
882 * of this for us, so we don't care, and can just use NOSCROLL here.
884 aPart = TFP_EDITBORDER_NOSCROLL;
886 if (!aFrame) {
887 aState = TFS_EDITBORDER_NORMAL;
888 } else if (elementState.HasState(ElementState::DISABLED)) {
889 aState = TFS_EDITBORDER_DISABLED;
890 } else if (IsReadOnly(aFrame)) {
891 /* no special read-only state */
892 aState = TFS_EDITBORDER_NORMAL;
893 } else if (elementState.HasAtLeastOneOfStates(ElementState::ACTIVE |
894 ElementState::FOCUSRING)) {
895 aState = TFS_EDITBORDER_FOCUSED;
896 } else if (elementState.HasState(ElementState::HOVER)) {
897 aState = TFS_EDITBORDER_HOVER;
898 } else {
899 aState = TFS_EDITBORDER_NORMAL;
902 return NS_OK;
904 case StyleAppearance::ProgressBar: {
905 bool vertical = IsVerticalProgress(aFrame);
906 aPart = vertical ? PP_BARVERT : PP_BAR;
907 aState = PBBS_NORMAL;
908 return NS_OK;
910 case StyleAppearance::Progresschunk: {
911 nsIFrame* parentFrame = aFrame->GetParent();
912 if (IsVerticalProgress(parentFrame)) {
913 aPart = PP_FILLVERT;
914 } else {
915 aPart = PP_FILL;
918 aState = PBBVS_NORMAL;
919 return NS_OK;
921 case StyleAppearance::Toolbarbutton: {
922 aPart = BP_BUTTON;
923 if (!aFrame) {
924 aState = TS_NORMAL;
925 return NS_OK;
928 ElementState elementState = GetContentState(aFrame, aAppearance);
929 if (elementState.HasState(ElementState::DISABLED)) {
930 aState = TS_DISABLED;
931 return NS_OK;
933 if (IsOpenButton(aFrame)) {
934 aState = TS_ACTIVE;
935 return NS_OK;
938 if (elementState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE))
939 aState = TS_ACTIVE;
940 else if (elementState.HasState(ElementState::HOVER)) {
941 if (IsCheckedButton(aFrame))
942 aState = TB_HOVER_CHECKED;
943 else
944 aState = TS_HOVER;
945 } else {
946 if (IsCheckedButton(aFrame))
947 aState = TB_CHECKED;
948 else
949 aState = TS_NORMAL;
952 return NS_OK;
954 case StyleAppearance::Separator: {
955 aPart = TP_SEPARATOR;
956 aState = TS_NORMAL;
957 return NS_OK;
959 case StyleAppearance::Range: {
960 if (IsRangeHorizontal(aFrame)) {
961 aPart = TKP_TRACK;
962 aState = TRS_NORMAL;
963 } else {
964 aPart = TKP_TRACKVERT;
965 aState = TRVS_NORMAL;
967 return NS_OK;
969 case StyleAppearance::RangeThumb: {
970 if (IsRangeHorizontal(aFrame)) {
971 aPart = TKP_THUMBBOTTOM;
972 } else {
973 aPart = IsFrameRTL(aFrame) ? TKP_THUMBLEFT : TKP_THUMBRIGHT;
975 ElementState elementState = GetContentState(aFrame, aAppearance);
976 if (!aFrame) {
977 aState = TS_NORMAL;
978 } else if (elementState.HasState(ElementState::DISABLED)) {
979 aState = TKP_DISABLED;
980 } else {
981 if (elementState.HasState(
982 ElementState::ACTIVE)) // Hover is not also a requirement for
983 // the thumb, since the drag is not
984 // canceled when you move outside the
985 // thumb.
986 aState = TS_ACTIVE;
987 else if (elementState.HasState(ElementState::FOCUSRING))
988 aState = TKP_FOCUSED;
989 else if (elementState.HasState(ElementState::HOVER))
990 aState = TS_HOVER;
991 else
992 aState = TS_NORMAL;
994 return NS_OK;
996 case StyleAppearance::SpinnerUpbutton:
997 case StyleAppearance::SpinnerDownbutton: {
998 aPart = (aAppearance == StyleAppearance::SpinnerUpbutton) ? SPNP_UP
999 : SPNP_DOWN;
1000 ElementState elementState = GetContentState(aFrame, aAppearance);
1001 if (!aFrame) {
1002 aState = TS_NORMAL;
1003 } else if (elementState.HasState(ElementState::DISABLED)) {
1004 aState = TS_DISABLED;
1005 } else {
1006 aState = StandardGetState(aFrame, aAppearance, false);
1008 return NS_OK;
1010 case StyleAppearance::Toolbox:
1011 case StyleAppearance::MozWinMediaToolbox:
1012 case StyleAppearance::MozWinCommunicationsToolbox:
1013 case StyleAppearance::MozWinBrowsertabbarToolbox: {
1014 aState = 0;
1015 aPart = RP_BACKGROUND;
1016 return NS_OK;
1018 case StyleAppearance::Toolbar: {
1019 // Use -1 to indicate we don't wish to have the theme background drawn
1020 // for this item. We will pass any nessessary information via aState,
1021 // and will render the item using separate code.
1022 aPart = -1;
1023 aState = 0;
1024 if (aFrame) {
1025 nsIContent* content = aFrame->GetContent();
1026 nsIContent* parent = content->GetParent();
1027 // XXXzeniko hiding the first toolbar will result in an unwanted margin
1028 if (parent && parent->GetFirstChild() == content) {
1029 aState = 1;
1032 return NS_OK;
1034 case StyleAppearance::Treeview:
1035 case StyleAppearance::Listbox: {
1036 aPart = TREEVIEW_BODY;
1037 aState = TS_NORMAL;
1038 return NS_OK;
1040 case StyleAppearance::Tabpanels: {
1041 aPart = TABP_PANELS;
1042 aState = TS_NORMAL;
1043 return NS_OK;
1045 case StyleAppearance::Tabpanel: {
1046 aPart = TABP_PANEL;
1047 aState = TS_NORMAL;
1048 return NS_OK;
1050 case StyleAppearance::Tab: {
1051 aPart = TABP_TAB;
1052 if (!aFrame) {
1053 aState = TS_NORMAL;
1054 return NS_OK;
1057 ElementState elementState = GetContentState(aFrame, aAppearance);
1058 if (elementState.HasState(ElementState::DISABLED)) {
1059 aState = TS_DISABLED;
1060 return NS_OK;
1063 if (IsSelectedTab(aFrame)) {
1064 aPart = TABP_TAB_SELECTED;
1065 aState = TS_ACTIVE; // The selected tab is always "pressed".
1066 } else
1067 aState = StandardGetState(aFrame, aAppearance, true);
1069 return NS_OK;
1071 case StyleAppearance::Treeheadersortarrow: {
1072 // XXX Probably will never work due to a bug in the Luna theme.
1073 aPart = 4;
1074 aState = 1;
1075 return NS_OK;
1077 case StyleAppearance::Treeheadercell: {
1078 aPart = 1;
1079 if (!aFrame) {
1080 aState = TS_NORMAL;
1081 return NS_OK;
1084 aState = StandardGetState(aFrame, aAppearance, true);
1086 return NS_OK;
1088 case StyleAppearance::MenulistButton:
1089 case StyleAppearance::Menulist: {
1090 nsIContent* content = aFrame->GetContent();
1091 bool useDropBorder = content && content->IsHTMLElement();
1092 ElementState elementState = GetContentState(aFrame, aAppearance);
1094 /* On Vista/Win7, we use CBP_DROPBORDER instead of DROPFRAME for HTML
1095 * content or for editable menulists; this gives us the thin outline,
1096 * instead of the gradient-filled background */
1097 if (useDropBorder)
1098 aPart = CBP_DROPBORDER;
1099 else
1100 aPart = CBP_DROPFRAME;
1102 if (elementState.HasState(ElementState::DISABLED)) {
1103 aState = TS_DISABLED;
1104 } else if (IsReadOnly(aFrame)) {
1105 aState = TS_NORMAL;
1106 } else if (IsOpenButton(aFrame)) {
1107 aState = TS_ACTIVE;
1108 } else if (useDropBorder &&
1109 elementState.HasState(ElementState::FOCUSRING)) {
1110 aState = TS_ACTIVE;
1111 } else if (elementState.HasAllStates(ElementState::HOVER |
1112 ElementState::ACTIVE)) {
1113 aState = TS_ACTIVE;
1114 } else if (elementState.HasState(ElementState::HOVER)) {
1115 aState = TS_HOVER;
1116 } else {
1117 aState = TS_NORMAL;
1120 return NS_OK;
1122 case StyleAppearance::MozMenulistArrowButton: {
1123 bool isOpen = false;
1125 // HTML select and XUL menulist dropdown buttons get state from the
1126 // parent.
1127 nsIFrame* parentFrame = aFrame->GetParent();
1128 aFrame = parentFrame;
1130 ElementState elementState = GetContentState(aFrame, aAppearance);
1131 aPart = CBP_DROPMARKER_VISTA;
1133 // For HTML controls with author styling, we should fall
1134 // back to the old dropmarker style to avoid clashes with
1135 // author-specified backgrounds and borders (bug #441034)
1136 if (IsWidgetStyled(aFrame->PresContext(), aFrame,
1137 StyleAppearance::Menulist)) {
1138 aPart = CBP_DROPMARKER;
1141 if (elementState.HasState(ElementState::DISABLED)) {
1142 aState = TS_DISABLED;
1143 return NS_OK;
1146 if (nsComboboxControlFrame* ccf = do_QueryFrame(aFrame)) {
1147 isOpen = ccf->IsDroppedDown();
1148 if (isOpen) {
1149 /* Hover is propagated, but we need to know whether we're hovering
1150 * just the combobox frame, not the dropdown frame. But, we can't get
1151 * that information, since hover is on the content node, and they
1152 * share the same content node. So, instead, we cheat -- if the
1153 * dropdown is open, we always show the hover state. This looks fine
1154 * in practice.
1156 aState = TS_HOVER;
1157 return NS_OK;
1159 } else {
1160 /* The dropdown indicator on a menulist button in chrome is not given a
1161 * hover effect. When the frame isn't isn't HTML content, we cheat and
1162 * force the dropdown state to be normal. (Bug 430434)
1164 isOpen = IsOpenButton(aFrame);
1165 aState = TS_NORMAL;
1166 return NS_OK;
1169 aState = TS_NORMAL;
1171 // Dropdown button active state doesn't need :hover.
1172 if (elementState.HasState(ElementState::ACTIVE)) {
1173 if (isOpen) {
1174 // XXX Button should look active until the mouse is released, but
1175 // without making it look active when the popup is clicked.
1176 return NS_OK;
1178 aState = TS_ACTIVE;
1179 } else if (elementState.HasState(ElementState::HOVER)) {
1180 // No hover effect for XUL menulists and autocomplete dropdown buttons
1181 // while the dropdown menu is open.
1182 if (isOpen) {
1183 // XXX HTML select dropdown buttons should have the hover effect when
1184 // hovering the combobox frame, but not the popup frame.
1185 return NS_OK;
1187 aState = TS_HOVER;
1189 return NS_OK;
1191 case StyleAppearance::Menupopup: {
1192 aPart = MENU_POPUPBACKGROUND;
1193 aState = MB_ACTIVE;
1194 return NS_OK;
1196 case StyleAppearance::Menuitem:
1197 case StyleAppearance::Checkmenuitem:
1198 case StyleAppearance::Radiomenuitem: {
1199 ElementState elementState = GetContentState(aFrame, aAppearance);
1201 auto* menu = dom::XULButtonElement::FromNodeOrNull(aFrame->GetContent());
1203 const bool isTopLevel = IsTopLevelMenu(aFrame);
1204 const bool isOpen = menu && menu->IsMenuPopupOpen();
1205 const bool isHover = IsMenuActive(aFrame, aAppearance);
1207 if (isTopLevel) {
1208 aPart = MENU_BARITEM;
1210 if (isOpen)
1211 aState = MBI_PUSHED;
1212 else if (isHover)
1213 aState = MBI_HOT;
1214 else
1215 aState = MBI_NORMAL;
1217 // the disabled states are offset by 3
1218 if (elementState.HasState(ElementState::DISABLED)) {
1219 aState += 3;
1221 } else {
1222 aPart = MENU_POPUPITEM;
1224 if (isHover)
1225 aState = MPI_HOT;
1226 else
1227 aState = MPI_NORMAL;
1229 // the disabled states are offset by 2
1230 if (elementState.HasState(ElementState::DISABLED)) {
1231 aState += 2;
1235 return NS_OK;
1237 case StyleAppearance::Menuseparator:
1238 aPart = MENU_POPUPSEPARATOR;
1239 aState = 0;
1240 return NS_OK;
1241 case StyleAppearance::Menuarrow: {
1242 aPart = MENU_POPUPSUBMENU;
1243 ElementState elementState = GetContentState(aFrame, aAppearance);
1244 aState = elementState.HasState(ElementState::DISABLED) ? MSM_DISABLED
1245 : MSM_NORMAL;
1246 return NS_OK;
1248 case StyleAppearance::Menucheckbox:
1249 case StyleAppearance::Menuradio: {
1250 ElementState elementState = GetContentState(aFrame, aAppearance);
1252 aPart = MENU_POPUPCHECK;
1253 aState = MC_CHECKMARKNORMAL;
1255 // Radio states are offset by 2
1256 if (aAppearance == StyleAppearance::Menuradio) aState += 2;
1258 // the disabled states are offset by 1
1259 if (elementState.HasState(ElementState::DISABLED)) {
1260 aState += 1;
1263 return NS_OK;
1265 case StyleAppearance::Menuitemtext:
1266 case StyleAppearance::Menuimage:
1267 aPart = -1;
1268 aState = 0;
1269 return NS_OK;
1271 case StyleAppearance::MozWindowTitlebar:
1272 aPart = mozilla::widget::themeconst::WP_CAPTION;
1273 aState = GetTopLevelWindowActiveState(aFrame);
1274 return NS_OK;
1275 case StyleAppearance::MozWindowTitlebarMaximized:
1276 aPart = mozilla::widget::themeconst::WP_MAXCAPTION;
1277 aState = GetTopLevelWindowActiveState(aFrame);
1278 return NS_OK;
1279 case StyleAppearance::MozWindowButtonClose:
1280 aPart = mozilla::widget::themeconst::WP_CLOSEBUTTON;
1281 aState = GetWindowFrameButtonState(aFrame,
1282 GetContentState(aFrame, aAppearance));
1283 return NS_OK;
1284 case StyleAppearance::MozWindowButtonMinimize:
1285 aPart = mozilla::widget::themeconst::WP_MINBUTTON;
1286 aState = GetWindowFrameButtonState(aFrame,
1287 GetContentState(aFrame, aAppearance));
1288 return NS_OK;
1289 case StyleAppearance::MozWindowButtonMaximize:
1290 aPart = mozilla::widget::themeconst::WP_MAXBUTTON;
1291 aState = GetWindowFrameButtonState(aFrame,
1292 GetContentState(aFrame, aAppearance));
1293 return NS_OK;
1294 case StyleAppearance::MozWindowButtonRestore:
1295 aPart = mozilla::widget::themeconst::WP_RESTOREBUTTON;
1296 aState = GetWindowFrameButtonState(aFrame,
1297 GetContentState(aFrame, aAppearance));
1298 return NS_OK;
1299 default:
1300 aPart = 0;
1301 aState = 0;
1302 return NS_ERROR_FAILURE;
1306 static bool AssumeThemePartAndStateAreTransparent(int32_t aPart,
1307 int32_t aState) {
1308 if (!(IsWin8Point1OrLater() && nsUXThemeData::IsHighContrastOn()) &&
1309 aPart == MENU_POPUPITEM && aState == MBI_NORMAL) {
1310 return true;
1312 return false;
1315 // When running with per-monitor DPI (on Win8.1+), and rendering on a display
1316 // with a different DPI setting from the system's default scaling, we need to
1317 // apply scaling to native-themed elements as the Windows theme APIs assume
1318 // the system default resolution.
1319 static inline double GetThemeDpiScaleFactor(nsPresContext* aPresContext) {
1320 if (WinUtils::IsPerMonitorDPIAware() ||
1321 StaticPrefs::layout_css_devPixelsPerPx() > 0.0) {
1322 nsCOMPtr<nsIWidget> rootWidget = aPresContext->GetRootWidget();
1323 if (rootWidget) {
1324 double systemScale = WinUtils::SystemScaleFactor();
1325 return rootWidget->GetDefaultScale().scale / systemScale;
1328 return 1.0;
1331 static inline double GetThemeDpiScaleFactor(nsIFrame* aFrame) {
1332 return GetThemeDpiScaleFactor(aFrame->PresContext());
1335 NS_IMETHODIMP
1336 nsNativeThemeWin::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
1337 StyleAppearance aAppearance,
1338 const nsRect& aRect,
1339 const nsRect& aDirtyRect,
1340 DrawOverflow aDrawOverflow) {
1341 if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
1342 return Theme::DrawWidgetBackground(aContext, aFrame, aAppearance, aRect,
1343 aDirtyRect, aDrawOverflow);
1346 HANDLE theme = GetTheme(aAppearance);
1347 if (!theme)
1348 return ClassicDrawWidgetBackground(aContext, aFrame, aAppearance, aRect,
1349 aDirtyRect);
1351 // ^^ without the right sdk, assume xp theming and fall through.
1352 switch (aAppearance) {
1353 case StyleAppearance::MozWindowTitlebar:
1354 case StyleAppearance::MozWindowTitlebarMaximized:
1355 // Nothing to draw, these areas are glass. Minimum dimensions
1356 // should be set, so xul content should be laid out correctly.
1357 return NS_OK;
1358 case StyleAppearance::MozWindowButtonClose:
1359 case StyleAppearance::MozWindowButtonMinimize:
1360 case StyleAppearance::MozWindowButtonMaximize:
1361 case StyleAppearance::MozWindowButtonRestore:
1362 // Not conventional bitmaps, can't be retrieved. If we fall
1363 // through here and call the theme library we'll get aero
1364 // basic bitmaps.
1365 return NS_OK;
1366 default:
1367 break;
1370 int32_t part, state;
1371 nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
1372 if (NS_FAILED(rv)) return rv;
1374 if (AssumeThemePartAndStateAreTransparent(part, state)) {
1375 return NS_OK;
1378 gfxContextMatrixAutoSaveRestore save(aContext);
1380 double themeScale = GetThemeDpiScaleFactor(aFrame);
1381 if (themeScale != 1.0) {
1382 aContext->SetMatrix(
1383 aContext->CurrentMatrix().PreScale(themeScale, themeScale));
1386 gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel());
1387 RECT widgetRect;
1388 RECT clipRect;
1389 gfxRect tr(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()),
1390 dr(aDirtyRect.X(), aDirtyRect.Y(), aDirtyRect.Width(),
1391 aDirtyRect.Height());
1393 tr.Scale(1.0 / (p2a * themeScale));
1394 dr.Scale(1.0 / (p2a * themeScale));
1396 gfxWindowsNativeDrawing nativeDrawing(
1397 aContext, dr, GetWidgetNativeDrawingFlags(aAppearance));
1399 RENDER_AGAIN:
1401 HDC hdc = nativeDrawing.BeginNativeDrawing();
1402 if (!hdc) return NS_ERROR_FAILURE;
1404 nativeDrawing.TransformToNativeRect(tr, widgetRect);
1405 nativeDrawing.TransformToNativeRect(dr, clipRect);
1407 #if 0
1409 MOZ_LOG(gWindowsLog, LogLevel::Error,
1410 (stderr, "xform: %f %f %f %f [%f %f]\n", m._11, m._21, m._12, m._22,
1411 m._31, m._32));
1412 MOZ_LOG(gWindowsLog, LogLevel::Error,
1413 (stderr, "tr: [%d %d %d %d]\ndr: [%d %d %d %d]\noff: [%f %f]\n",
1414 tr.x, tr.y, tr.width, tr.height, dr.x, dr.y, dr.width, dr.height,
1415 offset.x, offset.y));
1417 #endif
1419 if (aAppearance == StyleAppearance::MozWindowTitlebar) {
1420 // Clip out the left and right corners of the frame, all we want in
1421 // is the middle section.
1422 widgetRect.left -= GetSystemMetrics(SM_CXFRAME);
1423 widgetRect.right += GetSystemMetrics(SM_CXFRAME);
1424 } else if (aAppearance == StyleAppearance::MozWindowTitlebarMaximized) {
1425 // The origin of the window is off screen when maximized and windows
1426 // doesn't compensate for this in rendering the background. Push the
1427 // top of the bitmap down by SM_CYFRAME so we get the full graphic.
1428 widgetRect.top += GetSystemMetrics(SM_CYFRAME);
1429 } else if (aAppearance == StyleAppearance::Tab) {
1430 // For left edge and right edge tabs, we need to adjust the widget
1431 // rects and clip rects so that the edges don't get drawn.
1432 bool isLeft = IsLeftToSelectedTab(aFrame);
1433 bool isRight = !isLeft && IsRightToSelectedTab(aFrame);
1435 if (isLeft || isRight) {
1436 // HACK ALERT: There appears to be no way to really obtain this value, so
1437 // we're forced to just use the default value for Luna (which also happens
1438 // to be correct for all the other skins I've tried).
1439 int32_t edgeSize = 2;
1441 // Armed with the size of the edge, we now need to either shift to the
1442 // left or to the right. The clip rect won't include this extra area, so
1443 // we know that we're effectively shifting the edge out of view (such that
1444 // it won't be painted).
1445 if (isLeft)
1446 // The right edge should not be drawn. Extend our rect by the edge
1447 // size.
1448 widgetRect.right += edgeSize;
1449 else
1450 // The left edge should not be drawn. Move the widget rect's left coord
1451 // back.
1452 widgetRect.left -= edgeSize;
1454 } else if (aAppearance == StyleAppearance::MozWindowButtonMinimize) {
1455 OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_MINIMIZE);
1456 } else if (aAppearance == StyleAppearance::MozWindowButtonMaximize ||
1457 aAppearance == StyleAppearance::MozWindowButtonRestore) {
1458 OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_RESTORE);
1459 } else if (aAppearance == StyleAppearance::MozWindowButtonClose) {
1460 OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_CLOSE);
1463 // widgetRect is the bounding box for a widget, yet the scale track is only
1464 // a small portion of this size, so the edges of the scale need to be
1465 // adjusted to the real size of the track.
1466 if (aAppearance == StyleAppearance::Range) {
1467 RECT contentRect;
1468 GetThemeBackgroundContentRect(theme, hdc, part, state, &widgetRect,
1469 &contentRect);
1471 SIZE siz;
1472 GetThemePartSize(theme, hdc, part, state, &widgetRect, TS_TRUE, &siz);
1474 // When rounding is necessary, we round the position of the track
1475 // away from the chevron of the thumb to make it look better.
1476 if (IsRangeHorizontal(aFrame)) {
1477 contentRect.top += (contentRect.bottom - contentRect.top - siz.cy) / 2;
1478 contentRect.bottom = contentRect.top + siz.cy;
1479 } else {
1480 if (!IsFrameRTL(aFrame)) {
1481 contentRect.left += (contentRect.right - contentRect.left - siz.cx) / 2;
1482 contentRect.right = contentRect.left + siz.cx;
1483 } else {
1484 contentRect.right -=
1485 (contentRect.right - contentRect.left - siz.cx) / 2;
1486 contentRect.left = contentRect.right - siz.cx;
1490 DrawThemeBackground(theme, hdc, part, state, &contentRect, &clipRect);
1491 } else if (aAppearance == StyleAppearance::Menucheckbox ||
1492 aAppearance == StyleAppearance::Menuradio) {
1493 bool isChecked = false;
1494 isChecked = CheckBooleanAttr(aFrame, nsGkAtoms::checked);
1496 if (isChecked) {
1497 int bgState = MCB_NORMAL;
1498 ElementState elementState = GetContentState(aFrame, aAppearance);
1500 // the disabled states are offset by 1
1501 if (elementState.HasState(ElementState::DISABLED)) {
1502 bgState += 1;
1505 SIZE checkboxBGSize(GetCheckboxBGSize(theme, hdc));
1507 RECT checkBGRect = widgetRect;
1508 if (IsFrameRTL(aFrame)) {
1509 checkBGRect.left = checkBGRect.right - checkboxBGSize.cx;
1510 } else {
1511 checkBGRect.right = checkBGRect.left + checkboxBGSize.cx;
1514 // Center the checkbox background vertically in the menuitem
1515 checkBGRect.top +=
1516 (checkBGRect.bottom - checkBGRect.top) / 2 - checkboxBGSize.cy / 2;
1517 checkBGRect.bottom = checkBGRect.top + checkboxBGSize.cy;
1519 DrawThemeBackground(theme, hdc, MENU_POPUPCHECKBACKGROUND, bgState,
1520 &checkBGRect, &clipRect);
1522 MARGINS checkMargins = GetCheckboxMargins(theme, hdc);
1523 RECT checkRect = checkBGRect;
1524 checkRect.left += checkMargins.cxLeftWidth;
1525 checkRect.right -= checkMargins.cxRightWidth;
1526 checkRect.top += checkMargins.cyTopHeight;
1527 checkRect.bottom -= checkMargins.cyBottomHeight;
1528 DrawThemeBackground(theme, hdc, MENU_POPUPCHECK, state, &checkRect,
1529 &clipRect);
1531 } else if (aAppearance == StyleAppearance::Menupopup) {
1532 DrawThemeBackground(theme, hdc, MENU_POPUPBORDERS, /* state */ 0,
1533 &widgetRect, &clipRect);
1534 SIZE borderSize;
1535 GetThemePartSize(theme, hdc, MENU_POPUPBORDERS, 0, nullptr, TS_TRUE,
1536 &borderSize);
1538 RECT bgRect = widgetRect;
1539 bgRect.top += borderSize.cy;
1540 bgRect.bottom -= borderSize.cy;
1541 bgRect.left += borderSize.cx;
1542 bgRect.right -= borderSize.cx;
1544 DrawThemeBackground(theme, hdc, MENU_POPUPBACKGROUND, /* state */ 0,
1545 &bgRect, &clipRect);
1547 SIZE gutterSize(GetGutterSize(theme, hdc));
1549 RECT gutterRect;
1550 gutterRect.top = bgRect.top;
1551 gutterRect.bottom = bgRect.bottom;
1552 if (IsFrameRTL(aFrame)) {
1553 gutterRect.right = bgRect.right;
1554 gutterRect.left = gutterRect.right - gutterSize.cx;
1555 } else {
1556 gutterRect.left = bgRect.left;
1557 gutterRect.right = gutterRect.left + gutterSize.cx;
1560 DrawThemeBGRTLAware(theme, hdc, MENU_POPUPGUTTER, /* state */ 0,
1561 &gutterRect, &clipRect, IsFrameRTL(aFrame));
1562 } else if (aAppearance == StyleAppearance::Menuseparator) {
1563 SIZE gutterSize(GetGutterSize(theme, hdc));
1565 RECT sepRect = widgetRect;
1566 if (IsFrameRTL(aFrame))
1567 sepRect.right -= gutterSize.cx;
1568 else
1569 sepRect.left += gutterSize.cx;
1571 DrawThemeBackground(theme, hdc, MENU_POPUPSEPARATOR, /* state */ 0,
1572 &sepRect, &clipRect);
1573 } else if (aAppearance == StyleAppearance::Menuarrow) {
1574 // We're dpi aware and as such on systems that have dpi > 96 set, the
1575 // theme library expects us to do proper positioning and scaling of glyphs.
1576 // For StyleAppearance::Menuarrow, layout may hand us a widget rect larger
1577 // than the glyph rect we request in GetMinimumWidgetSize. To prevent
1578 // distortion we have to position and scale what we draw.
1580 SIZE glyphSize;
1581 GetThemePartSize(theme, hdc, part, state, nullptr, TS_TRUE, &glyphSize);
1583 int32_t widgetHeight = widgetRect.bottom - widgetRect.top;
1585 RECT renderRect = widgetRect;
1587 // We request (glyph width * 2, glyph height) in GetMinimumWidgetSize. In
1588 // Firefox some menu items provide the full height of the item to us, in
1589 // others our widget rect is the exact dims of our arrow glyph. Adjust the
1590 // vertical position by the added space, if any exists.
1591 renderRect.top += ((widgetHeight - glyphSize.cy) / 2);
1592 renderRect.bottom = renderRect.top + glyphSize.cy;
1593 // I'm using the width of the arrow glyph for the arrow-side padding.
1594 // AFAICT there doesn't appear to be a theme constant we can query
1595 // for this value. Generally this looks correct, and has the added
1596 // benefit of being a dpi adjusted value.
1597 if (!IsFrameRTL(aFrame)) {
1598 renderRect.right = widgetRect.right - glyphSize.cx;
1599 renderRect.left = renderRect.right - glyphSize.cx;
1600 } else {
1601 renderRect.left = glyphSize.cx;
1602 renderRect.right = renderRect.left + glyphSize.cx;
1604 DrawThemeBGRTLAware(theme, hdc, part, state, &renderRect, &clipRect,
1605 IsFrameRTL(aFrame));
1607 // The following widgets need to be RTL-aware
1608 else if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
1609 DrawThemeBGRTLAware(theme, hdc, part, state, &widgetRect, &clipRect,
1610 IsFrameRTL(aFrame));
1611 } else if (aAppearance == StyleAppearance::NumberInput ||
1612 aAppearance == StyleAppearance::Textfield ||
1613 aAppearance == StyleAppearance::Textarea) {
1614 DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
1616 if (state == TFS_EDITBORDER_DISABLED) {
1617 InflateRect(&widgetRect, -1, -1);
1618 ::FillRect(hdc, &widgetRect, reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1));
1620 } else if (aAppearance == StyleAppearance::ProgressBar) {
1621 // DrawThemeBackground renders each corner with a solid white pixel.
1622 // Restore these pixels to the underlying color. Tracks are rendered
1623 // using alpha recovery, so this makes the corners transparent.
1624 COLORREF color;
1625 color = GetPixel(hdc, widgetRect.left, widgetRect.top);
1626 DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
1627 SetPixel(hdc, widgetRect.left, widgetRect.top, color);
1628 SetPixel(hdc, widgetRect.right - 1, widgetRect.top, color);
1629 SetPixel(hdc, widgetRect.right - 1, widgetRect.bottom - 1, color);
1630 SetPixel(hdc, widgetRect.left, widgetRect.bottom - 1, color);
1631 } else if (aAppearance == StyleAppearance::Progresschunk) {
1632 DrawThemedProgressMeter(aFrame, aAppearance, theme, hdc, part, state,
1633 &widgetRect, &clipRect);
1635 // If part is negative, the element wishes us to not render a themed
1636 // background, instead opting to be drawn specially below.
1637 else if (part >= 0) {
1638 DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
1641 // Draw focus rectangles for range elements
1642 // XXX it'd be nice to draw these outside of the frame
1643 if (aAppearance == StyleAppearance::Range) {
1644 ElementState contentState = GetContentState(aFrame, aAppearance);
1646 if (contentState.HasState(ElementState::FOCUSRING)) {
1647 POINT vpOrg;
1648 HPEN hPen = nullptr;
1650 uint8_t id = SaveDC(hdc);
1652 ::SelectClipRgn(hdc, nullptr);
1653 ::GetViewportOrgEx(hdc, &vpOrg);
1654 ::SetBrushOrgEx(hdc, vpOrg.x + widgetRect.left, vpOrg.y + widgetRect.top,
1655 nullptr);
1656 ::SetTextColor(hdc, 0);
1657 ::DrawFocusRect(hdc, &widgetRect);
1658 ::RestoreDC(hdc, id);
1659 if (hPen) {
1660 ::DeleteObject(hPen);
1663 } else if (aAppearance == StyleAppearance::Toolbar && state == 0) {
1664 // Draw toolbar separator lines above all toolbars except the first one.
1665 // The lines are part of the Rebar theme, which is loaded for
1666 // StyleAppearance::Toolbox.
1667 theme = GetTheme(StyleAppearance::Toolbox);
1668 if (!theme) return NS_ERROR_FAILURE;
1670 widgetRect.bottom = widgetRect.top + TB_SEPARATOR_HEIGHT;
1671 DrawThemeEdge(theme, hdc, RP_BAND, 0, &widgetRect, EDGE_ETCHED, BF_TOP,
1672 nullptr);
1675 nativeDrawing.EndNativeDrawing();
1677 if (nativeDrawing.ShouldRenderAgain()) goto RENDER_AGAIN;
1679 nativeDrawing.PaintToContext();
1681 return NS_OK;
1684 bool nsNativeThemeWin::CreateWebRenderCommandsForWidget(
1685 wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
1686 const layers::StackingContextHelper& aSc,
1687 layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
1688 StyleAppearance aAppearance, const nsRect& aRect) {
1689 if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
1690 return Theme::CreateWebRenderCommandsForWidget(
1691 aBuilder, aResources, aSc, aManager, aFrame, aAppearance, aRect);
1693 return false;
1696 static void ScaleForFrameDPI(LayoutDeviceIntMargin* aMargin, nsIFrame* aFrame) {
1697 double themeScale = GetThemeDpiScaleFactor(aFrame);
1698 if (themeScale != 1.0) {
1699 aMargin->top = NSToIntRound(aMargin->top * themeScale);
1700 aMargin->left = NSToIntRound(aMargin->left * themeScale);
1701 aMargin->bottom = NSToIntRound(aMargin->bottom * themeScale);
1702 aMargin->right = NSToIntRound(aMargin->right * themeScale);
1706 static void ScaleForFrameDPI(LayoutDeviceIntSize* aSize, nsIFrame* aFrame) {
1707 double themeScale = GetThemeDpiScaleFactor(aFrame);
1708 if (themeScale != 1.0) {
1709 aSize->width = NSToIntRound(aSize->width * themeScale);
1710 aSize->height = NSToIntRound(aSize->height * themeScale);
1714 LayoutDeviceIntMargin nsNativeThemeWin::GetWidgetBorder(
1715 nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
1716 LayoutDeviceIntMargin result;
1717 mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aAppearance);
1718 HTHEME theme = NULL;
1719 if (!themeClass.isNothing()) {
1720 theme = nsUXThemeData::GetTheme(themeClass.value());
1722 if (!theme) {
1723 result = ClassicGetWidgetBorder(aContext, aFrame, aAppearance);
1724 ScaleForFrameDPI(&result, aFrame);
1725 return result;
1728 if (!WidgetIsContainer(aAppearance) ||
1729 aAppearance == StyleAppearance::Toolbox ||
1730 aAppearance == StyleAppearance::MozWinMediaToolbox ||
1731 aAppearance == StyleAppearance::MozWinCommunicationsToolbox ||
1732 aAppearance == StyleAppearance::MozWinBrowsertabbarToolbox ||
1733 aAppearance == StyleAppearance::Tabpanel ||
1734 aAppearance == StyleAppearance::Menuitem ||
1735 aAppearance == StyleAppearance::Checkmenuitem ||
1736 aAppearance == StyleAppearance::Radiomenuitem ||
1737 aAppearance == StyleAppearance::Menupopup ||
1738 aAppearance == StyleAppearance::Menuimage ||
1739 aAppearance == StyleAppearance::Menuitemtext ||
1740 aAppearance == StyleAppearance::Separator ||
1741 aAppearance == StyleAppearance::MozWindowTitlebar ||
1742 aAppearance == StyleAppearance::MozWindowTitlebarMaximized)
1743 return result; // Don't worry about it.
1745 int32_t part, state;
1746 nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
1747 if (NS_FAILED(rv)) return result;
1749 if (aAppearance == StyleAppearance::Toolbar) {
1750 // make space for the separator line above all toolbars but the first
1751 if (state == 0) result.top = TB_SEPARATOR_HEIGHT;
1752 return result;
1755 result = GetCachedWidgetBorder(theme, themeClass.value(), aAppearance, part,
1756 state);
1758 // Remove the edges for tabs that are before or after the selected tab,
1759 if (aAppearance == StyleAppearance::Tab) {
1760 if (IsLeftToSelectedTab(aFrame))
1761 // Remove the right edge, since we won't be drawing it.
1762 result.right = 0;
1763 else if (IsRightToSelectedTab(aFrame))
1764 // Remove the left edge, since we won't be drawing it.
1765 result.left = 0;
1768 if (aFrame && (aAppearance == StyleAppearance::NumberInput ||
1769 aAppearance == StyleAppearance::Textfield ||
1770 aAppearance == StyleAppearance::Textarea)) {
1771 nsIContent* content = aFrame->GetContent();
1772 if (content && content->IsHTMLElement()) {
1773 // We need to pad textfields by 1 pixel, since the caret will draw
1774 // flush against the edge by default if we don't.
1775 result.top.value++;
1776 result.left.value++;
1777 result.bottom.value++;
1778 result.right.value++;
1782 ScaleForFrameDPI(&result, aFrame);
1783 return result;
1786 bool nsNativeThemeWin::GetWidgetPadding(nsDeviceContext* aContext,
1787 nsIFrame* aFrame,
1788 StyleAppearance aAppearance,
1789 LayoutDeviceIntMargin* aResult) {
1790 switch (aAppearance) {
1791 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
1792 // and have a meaningful baseline, so they can't have
1793 // author-specified padding.
1794 case StyleAppearance::Checkbox:
1795 case StyleAppearance::Radio:
1796 aResult->SizeTo(0, 0, 0, 0);
1797 return true;
1798 default:
1799 break;
1802 bool ok = true;
1804 // Content padding
1805 if (aAppearance == StyleAppearance::MozWindowTitlebar ||
1806 aAppearance == StyleAppearance::MozWindowTitlebarMaximized) {
1807 aResult->SizeTo(0, 0, 0, 0);
1808 // Prior to Windows 10, a bug in DwmDefWindowProc would cause window
1809 // button presses/mouseovers to be missed. This bug is circumvented by
1810 // adding padding to the top of the window that is the size of the caption
1811 // area and then "removing" it when calculating the client area for
1812 // WM_NCCALCSIZE. See bug 618353,
1813 if (!IsWin10OrLater() &&
1814 aAppearance == StyleAppearance::MozWindowTitlebarMaximized) {
1815 nsCOMPtr<nsIWidget> rootWidget;
1816 if (WinUtils::HasSystemMetricsForDpi()) {
1817 rootWidget = aFrame->PresContext()->GetRootWidget();
1819 if (rootWidget) {
1820 double dpi = rootWidget->GetDPI();
1821 aResult->top = WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
1822 WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
1823 } else {
1824 aResult->top =
1825 GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
1828 return ok;
1831 HANDLE theme = GetTheme(aAppearance);
1832 if (!theme) {
1833 ok = ClassicGetWidgetPadding(aContext, aFrame, aAppearance, aResult);
1834 ScaleForFrameDPI(aResult, aFrame);
1835 return ok;
1838 if (aAppearance == StyleAppearance::Menupopup) {
1839 SIZE popupSize;
1840 GetThemePartSize(theme, nullptr, MENU_POPUPBORDERS, /* state */ 0, nullptr,
1841 TS_TRUE, &popupSize);
1842 aResult->top = aResult->bottom = popupSize.cy;
1843 aResult->left = aResult->right = popupSize.cx;
1844 ScaleForFrameDPI(aResult, aFrame);
1845 return ok;
1848 /* textfields need extra pixels on all sides, otherwise they wrap their
1849 * content too tightly. The actual border is drawn 1px inside the specified
1850 * rectangle, so Gecko will end up making the contents look too small.
1851 * Instead, we add 2px padding for the contents and fix this. (Used to be 1px
1852 * added, see bug 430212)
1854 if (aAppearance == StyleAppearance::NumberInput ||
1855 aAppearance == StyleAppearance::Textfield ||
1856 aAppearance == StyleAppearance::Textarea) {
1857 aResult->top = aResult->bottom = 2;
1858 aResult->left = aResult->right = 2;
1859 ScaleForFrameDPI(aResult, aFrame);
1860 return ok;
1861 } else if (IsHTMLContent(aFrame) &&
1862 (aAppearance == StyleAppearance::Menulist ||
1863 aAppearance == StyleAppearance::MenulistButton)) {
1864 /* For content menulist controls, we need an extra pixel so that we have
1865 * room to draw our focus rectangle stuff. Otherwise, the focus rect might
1866 * overlap the control's border.
1868 aResult->top = aResult->bottom = 1;
1869 aResult->left = aResult->right = 1;
1870 ScaleForFrameDPI(aResult, aFrame);
1871 return ok;
1874 int32_t right, left, top, bottom;
1875 right = left = top = bottom = 0;
1876 switch (aAppearance) {
1877 case StyleAppearance::Menuimage:
1878 right = 8;
1879 left = 3;
1880 break;
1881 case StyleAppearance::Menucheckbox:
1882 case StyleAppearance::Menuradio:
1883 right = 8;
1884 left = 0;
1885 break;
1886 case StyleAppearance::Menuitemtext:
1887 // There seem to be exactly 4 pixels from the edge
1888 // of the gutter to the text: 2px margin (CSS) + 2px padding (here)
1890 SIZE size(GetGutterSize(theme, nullptr));
1891 left = size.cx + 2;
1893 break;
1894 case StyleAppearance::Menuseparator: {
1895 SIZE size(GetGutterSize(theme, nullptr));
1896 left = size.cx + 5;
1897 } break;
1898 case StyleAppearance::Button:
1899 if (aFrame->GetContent()->IsXULElement()) {
1900 top = 2;
1901 bottom = 3;
1903 left = right = 5;
1904 break;
1905 default:
1906 return false;
1909 if (IsFrameRTL(aFrame)) {
1910 aResult->right = left;
1911 aResult->left = right;
1912 } else {
1913 aResult->right = right;
1914 aResult->left = left;
1916 aResult->top = top;
1917 aResult->bottom = bottom;
1919 ScaleForFrameDPI(aResult, aFrame);
1920 return ok;
1923 bool nsNativeThemeWin::GetWidgetOverflow(nsDeviceContext* aContext,
1924 nsIFrame* aFrame,
1925 StyleAppearance aAppearance,
1926 nsRect* aOverflowRect) {
1927 if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
1928 return Theme::GetWidgetOverflow(aContext, aFrame, aAppearance,
1929 aOverflowRect);
1932 /* This is disabled for now, because it causes invalidation problems --
1933 * see bug 420381. The effect of not updating the overflow area is that
1934 * for dropdown buttons in content areas, there is a 1px border on 3 sides
1935 * where, if invalidated, the dropdown control probably won't be repainted.
1936 * This is fairly minor, as by default there is nothing in that area, and
1937 * a border only shows up if the widget is being hovered.
1939 * TODO(jwatt): Figure out what do to about
1940 * StyleAppearance::MozMenulistArrowButton too.
1942 #if 0
1943 /* We explicitly draw dropdown buttons in HTML content 1px bigger up, right,
1944 * and bottom so that they overlap the dropdown's border like they're
1945 * supposed to.
1947 if (aAppearance == StyleAppearance::MenulistButton &&
1948 IsHTMLContent(aFrame) &&
1949 !IsWidgetStyled(aFrame->GetParent()->PresContext(),
1950 aFrame->GetParent(),
1951 StyleAppearance::Menulist))
1953 int32_t p2a = aContext->AppUnitsPerDevPixel();
1954 /* Note: no overflow on the left */
1955 nsMargin m(p2a, p2a, p2a, 0);
1956 aOverflowRect->Inflate (m);
1957 return true;
1959 #endif
1961 return false;
1964 LayoutDeviceIntSize nsNativeThemeWin::GetMinimumWidgetSize(
1965 nsPresContext* aPresContext, nsIFrame* aFrame,
1966 StyleAppearance aAppearance) {
1967 if (IsWidgetNonNative(aFrame, aAppearance) == NonNative::Always) {
1968 return Theme::GetMinimumWidgetSize(aPresContext, aFrame, aAppearance);
1971 mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aAppearance);
1972 HTHEME theme = NULL;
1973 if (!themeClass.isNothing()) {
1974 theme = nsUXThemeData::GetTheme(themeClass.value());
1976 if (!theme) {
1977 auto result = ClassicGetMinimumWidgetSize(aFrame, aAppearance);
1978 ScaleForFrameDPI(&result, aFrame);
1979 return result;
1982 switch (aAppearance) {
1983 case StyleAppearance::Groupbox:
1984 case StyleAppearance::NumberInput:
1985 case StyleAppearance::Textfield:
1986 case StyleAppearance::Toolbox:
1987 case StyleAppearance::MozWinMediaToolbox:
1988 case StyleAppearance::MozWinCommunicationsToolbox:
1989 case StyleAppearance::MozWinBrowsertabbarToolbox:
1990 case StyleAppearance::Toolbar:
1991 case StyleAppearance::Progresschunk:
1992 case StyleAppearance::Tabpanels:
1993 case StyleAppearance::Tabpanel:
1994 case StyleAppearance::Listbox:
1995 case StyleAppearance::Treeview:
1996 case StyleAppearance::Menuitemtext:
1997 return {}; // Don't worry about it.
1998 default:
1999 break;
2002 if (aAppearance == StyleAppearance::Menuitem && IsTopLevelMenu(aFrame)) {
2003 return {}; // Don't worry about it for top level menus
2006 // Call GetSystemMetrics to determine size for WinXP scrollbars
2007 // (GetThemeSysSize API returns the optimal size for the theme, but
2008 // Windows appears to always use metrics when drawing standard scrollbars)
2009 THEMESIZE sizeReq = TS_TRUE; // Best-fit size
2010 switch (aAppearance) {
2011 case StyleAppearance::MozMenulistArrowButton: {
2012 auto result = ClassicGetMinimumWidgetSize(aFrame, aAppearance);
2013 ScaleForFrameDPI(&result, aFrame);
2014 return result;
2016 case StyleAppearance::Menuitem:
2017 case StyleAppearance::Checkmenuitem:
2018 case StyleAppearance::Radiomenuitem:
2019 if (!IsTopLevelMenu(aFrame)) {
2020 SIZE gutterSize(GetCachedGutterSize(theme));
2021 LayoutDeviceIntSize result(gutterSize.cx, gutterSize.cy);
2022 ScaleForFrameDPI(&result, aFrame);
2023 return result;
2025 break;
2027 case StyleAppearance::Menuimage:
2028 case StyleAppearance::Menucheckbox:
2029 case StyleAppearance::Menuradio: {
2030 SIZE boxSize(GetCachedGutterSize(theme));
2031 LayoutDeviceIntSize result(boxSize.cx + 2, boxSize.cy);
2032 ScaleForFrameDPI(&result, aFrame);
2033 return result;
2036 case StyleAppearance::Menuitemtext:
2037 return {};
2039 case StyleAppearance::ProgressBar:
2040 // Best-fit size for progress meters is too large for most
2041 // themes. We want these widgets to be able to really shrink
2042 // down, so use the min-size request value (of 0).
2043 sizeReq = TS_MIN;
2044 break;
2046 case StyleAppearance::RangeThumb: {
2047 LayoutDeviceIntSize result(12, 20);
2048 if (!IsRangeHorizontal(aFrame)) {
2049 std::swap(result.width, result.height);
2051 ScaleForFrameDPI(&result, aFrame);
2052 return result;
2055 case StyleAppearance::Separator: {
2056 // that's 2px left margin, 2px right margin and 2px separator
2057 // (the margin is drawn as part of the separator, though)
2058 LayoutDeviceIntSize result(6, 0);
2059 ScaleForFrameDPI(&result, aFrame);
2060 return result;
2063 case StyleAppearance::Button:
2064 // We should let HTML buttons shrink to their min size.
2065 // FIXME bug 403934: We should probably really separate
2066 // GetPreferredWidgetSize from GetMinimumWidgetSize, so callers can
2067 // use the one they want.
2068 if (aFrame->GetContent()->IsHTMLElement()) {
2069 sizeReq = TS_MIN;
2071 break;
2073 case StyleAppearance::MozWindowTitlebar:
2074 case StyleAppearance::MozWindowTitlebarMaximized: {
2075 LayoutDeviceIntSize result;
2076 result.height = GetSystemMetrics(SM_CYCAPTION);
2077 result.height += GetSystemMetrics(SM_CYFRAME);
2078 result.height += GetSystemMetrics(SM_CXPADDEDBORDER);
2079 ScaleForFrameDPI(&result, aFrame);
2080 return result;
2083 default:
2084 break;
2087 int32_t part, state;
2088 nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
2089 if (NS_FAILED(rv)) {
2090 return {};
2093 LayoutDeviceIntSize result;
2094 rv = GetCachedMinimumWidgetSize(aFrame, theme, themeClass.value(),
2095 aAppearance, part, state, sizeReq, &result);
2096 ScaleForFrameDPI(&result, aFrame);
2097 return result;
2100 NS_IMETHODIMP
2101 nsNativeThemeWin::WidgetStateChanged(nsIFrame* aFrame,
2102 StyleAppearance aAppearance,
2103 nsAtom* aAttribute, bool* aShouldRepaint,
2104 const nsAttrValue* aOldValue) {
2105 // Some widget types just never change state.
2106 if (aAppearance == StyleAppearance::Toolbox ||
2107 aAppearance == StyleAppearance::MozWinMediaToolbox ||
2108 aAppearance == StyleAppearance::MozWinCommunicationsToolbox ||
2109 aAppearance == StyleAppearance::MozWinBrowsertabbarToolbox ||
2110 aAppearance == StyleAppearance::Toolbar ||
2111 aAppearance == StyleAppearance::Progresschunk ||
2112 aAppearance == StyleAppearance::ProgressBar ||
2113 aAppearance == StyleAppearance::Tabpanels ||
2114 aAppearance == StyleAppearance::Tabpanel ||
2115 aAppearance == StyleAppearance::Separator) {
2116 *aShouldRepaint = false;
2117 return NS_OK;
2120 if (aAppearance == StyleAppearance::MozWindowTitlebar ||
2121 aAppearance == StyleAppearance::MozWindowTitlebarMaximized ||
2122 aAppearance == StyleAppearance::MozWindowButtonClose ||
2123 aAppearance == StyleAppearance::MozWindowButtonMinimize ||
2124 aAppearance == StyleAppearance::MozWindowButtonMaximize ||
2125 aAppearance == StyleAppearance::MozWindowButtonRestore) {
2126 *aShouldRepaint = true;
2127 return NS_OK;
2130 // We need to repaint the dropdown arrow in vista HTML combobox controls when
2131 // the control is closed to get rid of the hover effect.
2132 if ((aAppearance == StyleAppearance::Menulist ||
2133 aAppearance == StyleAppearance::MenulistButton ||
2134 aAppearance == StyleAppearance::MozMenulistArrowButton) &&
2135 nsNativeTheme::IsHTMLContent(aFrame)) {
2136 *aShouldRepaint = true;
2137 return NS_OK;
2140 // XXXdwh Not sure what can really be done here. Can at least guess for
2141 // specific widgets that they're highly unlikely to have certain states.
2142 // For example, a toolbar doesn't care about any states.
2143 if (!aAttribute) {
2144 // Hover/focus/active changed. Always repaint.
2145 *aShouldRepaint = true;
2146 } else {
2147 // Check the attribute to see if it's relevant.
2148 // disabled, checked, dlgtype, default, etc.
2149 *aShouldRepaint = false;
2150 if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
2151 aAttribute == nsGkAtoms::selected ||
2152 aAttribute == nsGkAtoms::visuallyselected ||
2153 aAttribute == nsGkAtoms::readonly || aAttribute == nsGkAtoms::open ||
2154 aAttribute == nsGkAtoms::menuactive || aAttribute == nsGkAtoms::focused)
2155 *aShouldRepaint = true;
2158 return NS_OK;
2161 NS_IMETHODIMP
2162 nsNativeThemeWin::ThemeChanged() {
2163 nsUXThemeData::Invalidate();
2164 memset(mBorderCacheValid, 0, sizeof(mBorderCacheValid));
2165 memset(mMinimumWidgetSizeCacheValid, 0, sizeof(mMinimumWidgetSizeCacheValid));
2166 mGutterSizeCacheValid = false;
2167 return NS_OK;
2170 bool nsNativeThemeWin::ThemeSupportsWidget(nsPresContext* aPresContext,
2171 nsIFrame* aFrame,
2172 StyleAppearance aAppearance) {
2173 // XXXdwh We can go even further and call the API to ask if support exists for
2174 // specific widgets.
2176 if (IsWidgetNonNative(aFrame, aAppearance) == NonNative::Always) {
2177 return Theme::ThemeSupportsWidget(aPresContext, aFrame, aAppearance);
2180 HANDLE theme = nullptr;
2181 if (aAppearance == StyleAppearance::CheckboxContainer)
2182 theme = GetTheme(StyleAppearance::Checkbox);
2183 else if (aAppearance == StyleAppearance::RadioContainer)
2184 theme = GetTheme(StyleAppearance::Radio);
2185 else
2186 theme = GetTheme(aAppearance);
2188 if (theme || ClassicThemeSupportsWidget(aFrame, aAppearance))
2189 // turn off theming for some HTML widgets styled by the page
2190 return (!IsWidgetStyled(aPresContext, aFrame, aAppearance));
2192 return false;
2195 bool nsNativeThemeWin::WidgetIsContainer(StyleAppearance aAppearance) {
2196 // XXXdwh At some point flesh all of this out.
2197 if (aAppearance == StyleAppearance::MozMenulistArrowButton ||
2198 aAppearance == StyleAppearance::Radio ||
2199 aAppearance == StyleAppearance::Checkbox)
2200 return false;
2201 return true;
2204 bool nsNativeThemeWin::ThemeDrawsFocusForWidget(nsIFrame* aFrame,
2205 StyleAppearance aAppearance) {
2206 if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
2207 return Theme::ThemeDrawsFocusForWidget(aFrame, aAppearance);
2209 switch (aAppearance) {
2210 case StyleAppearance::Menulist:
2211 case StyleAppearance::MenulistButton:
2212 case StyleAppearance::Textarea:
2213 case StyleAppearance::Textfield:
2214 case StyleAppearance::NumberInput:
2215 return true;
2216 default:
2217 return false;
2221 bool nsNativeThemeWin::ThemeNeedsComboboxDropmarker() { return true; }
2223 bool nsNativeThemeWin::WidgetAppearanceDependsOnWindowFocus(
2224 StyleAppearance aAppearance) {
2225 switch (aAppearance) {
2226 case StyleAppearance::MozWindowTitlebar:
2227 case StyleAppearance::MozWindowTitlebarMaximized:
2228 case StyleAppearance::MozWindowButtonClose:
2229 case StyleAppearance::MozWindowButtonMinimize:
2230 case StyleAppearance::MozWindowButtonMaximize:
2231 case StyleAppearance::MozWindowButtonRestore:
2232 return true;
2233 default:
2234 return false;
2238 nsITheme::Transparency nsNativeThemeWin::GetWidgetTransparency(
2239 nsIFrame* aFrame, StyleAppearance aAppearance) {
2240 if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
2241 return Theme::GetWidgetTransparency(aFrame, aAppearance);
2244 switch (aAppearance) {
2245 case StyleAppearance::ProgressBar:
2246 case StyleAppearance::Progresschunk:
2247 case StyleAppearance::Range:
2248 return eTransparent;
2249 default:
2250 break;
2253 HANDLE theme = GetTheme(aAppearance);
2254 // For the classic theme we don't really have a way of knowing
2255 if (!theme) {
2256 // menu backgrounds which can't be themed are opaque
2257 if (aAppearance == StyleAppearance::Menupopup) {
2258 return eOpaque;
2260 return eUnknownTransparency;
2263 int32_t part, state;
2264 nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
2265 // Fail conservatively
2266 NS_ENSURE_SUCCESS(rv, eUnknownTransparency);
2268 if (part <= 0) {
2269 // Not a real part code, so IsThemeBackgroundPartiallyTransparent may
2270 // not work, so don't call it.
2271 return eUnknownTransparency;
2274 if (IsThemeBackgroundPartiallyTransparent(theme, part, state))
2275 return eTransparent;
2276 return eOpaque;
2279 /* Windows 9x/NT/2000/Classic XP Theme Support */
2281 bool nsNativeThemeWin::ClassicThemeSupportsWidget(nsIFrame* aFrame,
2282 StyleAppearance aAppearance) {
2283 switch (aAppearance) {
2284 case StyleAppearance::Menubar:
2285 case StyleAppearance::Menupopup:
2286 case StyleAppearance::Button:
2287 case StyleAppearance::NumberInput:
2288 case StyleAppearance::Textfield:
2289 case StyleAppearance::Textarea:
2290 case StyleAppearance::Checkbox:
2291 case StyleAppearance::Radio:
2292 case StyleAppearance::Range:
2293 case StyleAppearance::RangeThumb:
2294 case StyleAppearance::Groupbox:
2295 case StyleAppearance::Menulist:
2296 case StyleAppearance::MenulistButton:
2297 case StyleAppearance::MozMenulistArrowButton:
2298 case StyleAppearance::SpinnerUpbutton:
2299 case StyleAppearance::SpinnerDownbutton:
2300 case StyleAppearance::Listbox:
2301 case StyleAppearance::Treeview:
2302 case StyleAppearance::ProgressBar:
2303 case StyleAppearance::Progresschunk:
2304 case StyleAppearance::Tab:
2305 case StyleAppearance::Tabpanel:
2306 case StyleAppearance::Tabpanels:
2307 case StyleAppearance::Menuitem:
2308 case StyleAppearance::Checkmenuitem:
2309 case StyleAppearance::Radiomenuitem:
2310 case StyleAppearance::Menucheckbox:
2311 case StyleAppearance::Menuradio:
2312 case StyleAppearance::Menuarrow:
2313 case StyleAppearance::Menuseparator:
2314 case StyleAppearance::Menuitemtext:
2315 case StyleAppearance::MozWindowTitlebar:
2316 case StyleAppearance::MozWindowTitlebarMaximized:
2317 case StyleAppearance::MozWindowButtonClose:
2318 case StyleAppearance::MozWindowButtonMinimize:
2319 case StyleAppearance::MozWindowButtonMaximize:
2320 case StyleAppearance::MozWindowButtonRestore:
2321 return true;
2322 default:
2323 return false;
2327 LayoutDeviceIntMargin nsNativeThemeWin::ClassicGetWidgetBorder(
2328 nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
2329 LayoutDeviceIntMargin result;
2330 switch (aAppearance) {
2331 case StyleAppearance::Groupbox:
2332 case StyleAppearance::Button:
2333 result.top = result.left = result.bottom = result.right = 2;
2334 break;
2335 case StyleAppearance::Listbox:
2336 case StyleAppearance::Treeview:
2337 case StyleAppearance::Menulist:
2338 case StyleAppearance::MenulistButton:
2339 case StyleAppearance::Tab:
2340 case StyleAppearance::NumberInput:
2341 case StyleAppearance::Textfield:
2342 case StyleAppearance::Textarea:
2343 result.top = result.left = result.bottom = result.right = 2;
2344 break;
2345 case StyleAppearance::ProgressBar:
2346 result.top = result.left = result.bottom = result.right = 1;
2347 break;
2348 case StyleAppearance::Menubar:
2349 result.top = result.left = result.bottom = result.right = 0;
2350 break;
2351 case StyleAppearance::Menupopup:
2352 result.top = result.left = result.bottom = result.right = 3;
2353 break;
2354 default:
2355 result.top = result.bottom = result.left = result.right = 0;
2356 break;
2358 return result;
2361 bool nsNativeThemeWin::ClassicGetWidgetPadding(nsDeviceContext* aContext,
2362 nsIFrame* aFrame,
2363 StyleAppearance aAppearance,
2364 LayoutDeviceIntMargin* aResult) {
2365 switch (aAppearance) {
2366 case StyleAppearance::Menuitem:
2367 case StyleAppearance::Checkmenuitem:
2368 case StyleAppearance::Radiomenuitem: {
2369 int32_t part, state;
2370 bool focused;
2372 if (NS_FAILED(ClassicGetThemePartAndState(aFrame, aAppearance, part,
2373 state, focused)))
2374 return false;
2376 if (part == 1) { // top-level menu
2377 (*aResult).top = (*aResult).bottom = (*aResult).left =
2378 (*aResult).right = 2;
2379 } else {
2380 (*aResult).top = 0;
2381 (*aResult).bottom = (*aResult).left = (*aResult).right = 2;
2383 return true;
2385 case StyleAppearance::ProgressBar:
2386 (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right =
2388 return true;
2389 default:
2390 return false;
2394 LayoutDeviceIntSize nsNativeThemeWin::ClassicGetMinimumWidgetSize(
2395 nsIFrame* aFrame, StyleAppearance aAppearance) {
2396 LayoutDeviceIntSize result;
2397 switch (aAppearance) {
2398 case StyleAppearance::Radio:
2399 case StyleAppearance::Checkbox:
2400 result.width = result.height = 13;
2401 break;
2402 case StyleAppearance::Menucheckbox:
2403 case StyleAppearance::Menuradio:
2404 case StyleAppearance::Menuarrow:
2405 result.width = ::GetSystemMetrics(SM_CXMENUCHECK);
2406 result.height = ::GetSystemMetrics(SM_CYMENUCHECK);
2407 break;
2408 case StyleAppearance::SpinnerUpbutton:
2409 case StyleAppearance::SpinnerDownbutton:
2410 result.width = ::GetSystemMetrics(SM_CXVSCROLL);
2411 result.height = 8; // No good metrics available for this
2412 break;
2413 case StyleAppearance::RangeThumb: {
2414 if (IsRangeHorizontal(aFrame)) {
2415 result.width = 12;
2416 result.height = 20;
2417 } else {
2418 result.width = 20;
2419 result.height = 12;
2421 break;
2423 case StyleAppearance::MozMenulistArrowButton:
2424 result.width = ::GetSystemMetrics(SM_CXVSCROLL);
2425 break;
2426 case StyleAppearance::Menulist:
2427 case StyleAppearance::MenulistButton:
2428 case StyleAppearance::Button:
2429 case StyleAppearance::Groupbox:
2430 case StyleAppearance::Listbox:
2431 case StyleAppearance::Treeview:
2432 case StyleAppearance::NumberInput:
2433 case StyleAppearance::Textfield:
2434 case StyleAppearance::Textarea:
2435 case StyleAppearance::Progresschunk:
2436 case StyleAppearance::ProgressBar:
2437 case StyleAppearance::Tab:
2438 case StyleAppearance::Tabpanel:
2439 case StyleAppearance::Tabpanels:
2440 // no minimum widget size
2441 break;
2442 case StyleAppearance::Menuseparator: {
2443 result.width = 0;
2444 result.height = 10;
2445 break;
2448 case StyleAppearance::MozWindowTitlebarMaximized:
2449 case StyleAppearance::MozWindowTitlebar:
2450 result.height =
2451 GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYFRAME);
2452 break;
2453 case StyleAppearance::MozWindowButtonClose:
2454 case StyleAppearance::MozWindowButtonMinimize:
2455 case StyleAppearance::MozWindowButtonMaximize:
2456 case StyleAppearance::MozWindowButtonRestore:
2457 result.width = GetSystemMetrics(SM_CXSIZE);
2458 result.height = GetSystemMetrics(SM_CYSIZE);
2459 // XXX I have no idea why these caption metrics are always off,
2460 // but they are.
2461 result.width -= 2;
2462 result.height -= 4;
2463 if (aAppearance == StyleAppearance::MozWindowButtonMinimize) {
2464 AddPaddingRect(&result, CAPTIONBUTTON_MINIMIZE);
2465 } else if (aAppearance == StyleAppearance::MozWindowButtonMaximize ||
2466 aAppearance == StyleAppearance::MozWindowButtonRestore) {
2467 AddPaddingRect(&result, CAPTIONBUTTON_RESTORE);
2468 } else if (aAppearance == StyleAppearance::MozWindowButtonClose) {
2469 AddPaddingRect(&result, CAPTIONBUTTON_CLOSE);
2471 break;
2473 default:
2474 break;
2476 return result;
2479 nsresult nsNativeThemeWin::ClassicGetThemePartAndState(
2480 nsIFrame* aFrame, StyleAppearance aAppearance, int32_t& aPart,
2481 int32_t& aState, bool& aFocused) {
2482 aFocused = false;
2483 switch (aAppearance) {
2484 case StyleAppearance::Button: {
2485 aPart = DFC_BUTTON;
2486 aState = DFCS_BUTTONPUSH;
2487 aFocused = false;
2489 ElementState contentState = GetContentState(aFrame, aAppearance);
2490 if (contentState.HasState(ElementState::DISABLED)) {
2491 aState |= DFCS_INACTIVE;
2492 } else if (IsOpenButton(aFrame)) {
2493 aState |= DFCS_PUSHED;
2494 } else if (IsCheckedButton(aFrame)) {
2495 aState |= DFCS_CHECKED;
2496 } else {
2497 if (contentState.HasAllStates(ElementState::ACTIVE |
2498 ElementState::HOVER)) {
2499 aState |= DFCS_PUSHED;
2500 // The down state is flat if the button is focusable
2501 if (aFrame->StyleUI()->UserFocus() == StyleUserFocus::Normal) {
2502 if (!aFrame->GetContent()->IsHTMLElement()) aState |= DFCS_FLAT;
2504 aFocused = true;
2507 // On Windows, focused buttons are always drawn as such by the native
2508 // theme, that's why we check ElementState::FOCUS instead of
2509 // ElementState::FOCUSRING.
2510 if (contentState.HasState(ElementState::FOCUS) ||
2511 (aState == DFCS_BUTTONPUSH && IsDefaultButton(aFrame))) {
2512 aFocused = true;
2516 return NS_OK;
2518 case StyleAppearance::Checkbox:
2519 case StyleAppearance::Radio: {
2520 ElementState contentState = GetContentState(aFrame, aAppearance);
2521 aFocused = false;
2523 aPart = DFC_BUTTON;
2524 aState = 0;
2525 nsIContent* content = aFrame->GetContent();
2526 bool isCheckbox = (aAppearance == StyleAppearance::Checkbox);
2527 bool isChecked = contentState.HasState(ElementState::CHECKED);
2528 bool isIndeterminate = contentState.HasState(ElementState::INDETERMINATE);
2530 if (isCheckbox) {
2531 // indeterminate state takes precedence over checkedness.
2532 if (isIndeterminate) {
2533 aState = DFCS_BUTTON3STATE | DFCS_CHECKED;
2534 } else {
2535 aState = DFCS_BUTTONCHECK;
2537 } else {
2538 aState = DFCS_BUTTONRADIO;
2540 if (isChecked) {
2541 aState |= DFCS_CHECKED;
2544 if (!content->IsXULElement() &&
2545 contentState.HasState(ElementState::FOCUSRING)) {
2546 aFocused = true;
2549 if (contentState.HasState(ElementState::DISABLED)) {
2550 aState |= DFCS_INACTIVE;
2551 } else if (contentState.HasAllStates(ElementState::ACTIVE |
2552 ElementState::HOVER)) {
2553 aState |= DFCS_PUSHED;
2556 return NS_OK;
2558 case StyleAppearance::Menuitem:
2559 case StyleAppearance::Checkmenuitem:
2560 case StyleAppearance::Radiomenuitem: {
2561 ElementState elementState = GetContentState(aFrame, aAppearance);
2563 auto* menu = dom::XULButtonElement::FromNodeOrNull(aFrame->GetContent());
2565 const bool isTopLevel = IsTopLevelMenu(aFrame);
2566 const bool isOpen = menu && menu->IsMenuPopupOpen();
2568 // We indicate top-level-ness using aPart. 0 is a normal menu item,
2569 // 1 is a top-level menu item. The state of the item is composed of
2570 // DFCS_* flags only.
2571 aPart = 0;
2572 aState = 0;
2574 if (elementState.HasState(ElementState::DISABLED)) {
2575 aState |= DFCS_INACTIVE;
2578 if (isTopLevel) {
2579 aPart = 1;
2580 if (isOpen) {
2581 aState |= DFCS_PUSHED;
2585 if (IsMenuActive(aFrame, aAppearance)) {
2586 aState |= DFCS_HOT;
2589 return NS_OK;
2591 case StyleAppearance::Menucheckbox:
2592 case StyleAppearance::Menuradio:
2593 case StyleAppearance::Menuarrow: {
2594 aState = 0;
2595 ElementState elementState = GetContentState(aFrame, aAppearance);
2597 if (elementState.HasState(ElementState::DISABLED)) {
2598 aState |= DFCS_INACTIVE;
2600 if (IsMenuActive(aFrame, aAppearance)) aState |= DFCS_HOT;
2602 if (aAppearance == StyleAppearance::Menucheckbox ||
2603 aAppearance == StyleAppearance::Menuradio) {
2604 if (IsCheckedButton(aFrame)) aState |= DFCS_CHECKED;
2605 } else if (IsFrameRTL(aFrame)) {
2606 aState |= DFCS_RTL;
2608 return NS_OK;
2610 case StyleAppearance::Listbox:
2611 case StyleAppearance::Treeview:
2612 case StyleAppearance::NumberInput:
2613 case StyleAppearance::Textfield:
2614 case StyleAppearance::Textarea:
2615 case StyleAppearance::Menulist:
2616 case StyleAppearance::MenulistButton:
2617 case StyleAppearance::Range:
2618 case StyleAppearance::RangeThumb:
2619 case StyleAppearance::Progresschunk:
2620 case StyleAppearance::ProgressBar:
2621 case StyleAppearance::Tab:
2622 case StyleAppearance::Tabpanel:
2623 case StyleAppearance::Tabpanels:
2624 case StyleAppearance::Menubar:
2625 case StyleAppearance::Menupopup:
2626 case StyleAppearance::Groupbox:
2627 // these don't use DrawFrameControl
2628 return NS_OK;
2629 case StyleAppearance::MozMenulistArrowButton: {
2630 aPart = DFC_SCROLL;
2631 aState = DFCS_SCROLLCOMBOBOX;
2633 nsIFrame* parentFrame = aFrame->GetParent();
2634 // HTML select and XUL menulist dropdown buttons get state from the
2635 // parent.
2636 aFrame = parentFrame;
2638 ElementState elementState = GetContentState(aFrame, aAppearance);
2640 if (elementState.HasState(ElementState::DISABLED)) {
2641 aState |= DFCS_INACTIVE;
2642 return NS_OK;
2645 bool isOpen = false;
2646 if (nsComboboxControlFrame* ccf = do_QueryFrame(aFrame)) {
2647 isOpen = ccf->IsDroppedDown();
2648 } else {
2649 isOpen = IsOpenButton(aFrame);
2652 // XXX Button should look active until the mouse is released, but
2653 // without making it look active when the popup is clicked.
2654 if (isOpen) {
2655 return NS_OK;
2658 // Dropdown button active state doesn't need :hover.
2659 if (elementState.HasState(ElementState::ACTIVE))
2660 aState |= DFCS_PUSHED | DFCS_FLAT;
2662 return NS_OK;
2664 case StyleAppearance::SpinnerUpbutton:
2665 case StyleAppearance::SpinnerDownbutton: {
2666 ElementState contentState = GetContentState(aFrame, aAppearance);
2668 aPart = DFC_SCROLL;
2669 switch (aAppearance) {
2670 case StyleAppearance::SpinnerUpbutton:
2671 aState = DFCS_SCROLLUP;
2672 break;
2673 case StyleAppearance::SpinnerDownbutton:
2674 aState = DFCS_SCROLLDOWN;
2675 break;
2676 default:
2677 break;
2680 if (contentState.HasState(ElementState::DISABLED)) {
2681 aState |= DFCS_INACTIVE;
2682 } else {
2683 if (contentState.HasAllStates(ElementState::HOVER |
2684 ElementState::ACTIVE))
2685 aState |= DFCS_PUSHED;
2688 return NS_OK;
2690 case StyleAppearance::Menuseparator:
2691 aPart = 0;
2692 aState = 0;
2693 return NS_OK;
2694 case StyleAppearance::MozWindowTitlebar:
2695 aPart = mozilla::widget::themeconst::WP_CAPTION;
2696 aState = GetTopLevelWindowActiveState(aFrame);
2697 return NS_OK;
2698 case StyleAppearance::MozWindowTitlebarMaximized:
2699 aPart = mozilla::widget::themeconst::WP_MAXCAPTION;
2700 aState = GetTopLevelWindowActiveState(aFrame);
2701 return NS_OK;
2702 case StyleAppearance::MozWindowButtonClose:
2703 aPart = DFC_CAPTION;
2704 aState = DFCS_CAPTIONCLOSE | GetClassicWindowFrameButtonState(
2705 GetContentState(aFrame, aAppearance));
2706 return NS_OK;
2707 case StyleAppearance::MozWindowButtonMinimize:
2708 aPart = DFC_CAPTION;
2709 aState = DFCS_CAPTIONMIN | GetClassicWindowFrameButtonState(
2710 GetContentState(aFrame, aAppearance));
2711 return NS_OK;
2712 case StyleAppearance::MozWindowButtonMaximize:
2713 aPart = DFC_CAPTION;
2714 aState = DFCS_CAPTIONMAX | GetClassicWindowFrameButtonState(
2715 GetContentState(aFrame, aAppearance));
2716 return NS_OK;
2717 case StyleAppearance::MozWindowButtonRestore:
2718 aPart = DFC_CAPTION;
2719 aState = DFCS_CAPTIONRESTORE | GetClassicWindowFrameButtonState(
2720 GetContentState(aFrame, aAppearance));
2721 return NS_OK;
2722 default:
2723 return NS_ERROR_FAILURE;
2727 // Draw classic Windows tab
2728 // (no system API for this, but DrawEdge can draw all the parts of a tab)
2729 static void DrawTab(HDC hdc, const RECT& R, int32_t aPosition, bool aSelected,
2730 bool aDrawLeft, bool aDrawRight) {
2731 int32_t leftFlag, topFlag, rightFlag, lightFlag, shadeFlag;
2732 RECT topRect, sideRect, bottomRect, lightRect, shadeRect;
2733 int32_t selectedOffset, lOffset, rOffset;
2735 selectedOffset = aSelected ? 1 : 0;
2736 lOffset = aDrawLeft ? 2 : 0;
2737 rOffset = aDrawRight ? 2 : 0;
2739 // Get info for tab orientation/position (Left, Top, Right, Bottom)
2740 switch (aPosition) {
2741 case BF_LEFT:
2742 leftFlag = BF_TOP;
2743 topFlag = BF_LEFT;
2744 rightFlag = BF_BOTTOM;
2745 lightFlag = BF_DIAGONAL_ENDTOPRIGHT;
2746 shadeFlag = BF_DIAGONAL_ENDBOTTOMRIGHT;
2748 ::SetRect(&topRect, R.left, R.top + lOffset, R.right, R.bottom - rOffset);
2749 ::SetRect(&sideRect, R.left + 2, R.top, R.right - 2 + selectedOffset,
2750 R.bottom);
2751 ::SetRect(&bottomRect, R.right - 2, R.top, R.right, R.bottom);
2752 ::SetRect(&lightRect, R.left, R.top, R.left + 3, R.top + 3);
2753 ::SetRect(&shadeRect, R.left + 1, R.bottom - 2, R.left + 2, R.bottom - 1);
2754 break;
2755 case BF_TOP:
2756 leftFlag = BF_LEFT;
2757 topFlag = BF_TOP;
2758 rightFlag = BF_RIGHT;
2759 lightFlag = BF_DIAGONAL_ENDTOPRIGHT;
2760 shadeFlag = BF_DIAGONAL_ENDBOTTOMRIGHT;
2762 ::SetRect(&topRect, R.left + lOffset, R.top, R.right - rOffset, R.bottom);
2763 ::SetRect(&sideRect, R.left, R.top + 2, R.right,
2764 R.bottom - 1 + selectedOffset);
2765 ::SetRect(&bottomRect, R.left, R.bottom - 1, R.right, R.bottom);
2766 ::SetRect(&lightRect, R.left, R.top, R.left + 3, R.top + 3);
2767 ::SetRect(&shadeRect, R.right - 2, R.top + 1, R.right - 1, R.top + 2);
2768 break;
2769 case BF_RIGHT:
2770 leftFlag = BF_TOP;
2771 topFlag = BF_RIGHT;
2772 rightFlag = BF_BOTTOM;
2773 lightFlag = BF_DIAGONAL_ENDTOPLEFT;
2774 shadeFlag = BF_DIAGONAL_ENDBOTTOMLEFT;
2776 ::SetRect(&topRect, R.left, R.top + lOffset, R.right, R.bottom - rOffset);
2777 ::SetRect(&sideRect, R.left + 2 - selectedOffset, R.top, R.right - 2,
2778 R.bottom);
2779 ::SetRect(&bottomRect, R.left, R.top, R.left + 2, R.bottom);
2780 ::SetRect(&lightRect, R.right - 3, R.top, R.right - 1, R.top + 2);
2781 ::SetRect(&shadeRect, R.right - 2, R.bottom - 3, R.right, R.bottom - 1);
2782 break;
2783 case BF_BOTTOM:
2784 leftFlag = BF_LEFT;
2785 topFlag = BF_BOTTOM;
2786 rightFlag = BF_RIGHT;
2787 lightFlag = BF_DIAGONAL_ENDTOPLEFT;
2788 shadeFlag = BF_DIAGONAL_ENDBOTTOMLEFT;
2790 ::SetRect(&topRect, R.left + lOffset, R.top, R.right - rOffset, R.bottom);
2791 ::SetRect(&sideRect, R.left, R.top + 2 - selectedOffset, R.right,
2792 R.bottom - 2);
2793 ::SetRect(&bottomRect, R.left, R.top, R.right, R.top + 2);
2794 ::SetRect(&lightRect, R.left, R.bottom - 3, R.left + 2, R.bottom - 1);
2795 ::SetRect(&shadeRect, R.right - 2, R.bottom - 3, R.right, R.bottom - 1);
2796 break;
2797 default:
2798 MOZ_CRASH();
2801 // Background
2802 ::FillRect(hdc, &R, (HBRUSH)(COLOR_3DFACE + 1));
2804 // Tab "Top"
2805 ::DrawEdge(hdc, &topRect, EDGE_RAISED, BF_SOFT | topFlag);
2807 // Tab "Bottom"
2808 if (!aSelected) ::DrawEdge(hdc, &bottomRect, EDGE_RAISED, BF_SOFT | topFlag);
2810 // Tab "Sides"
2811 if (!aDrawLeft) leftFlag = 0;
2812 if (!aDrawRight) rightFlag = 0;
2813 ::DrawEdge(hdc, &sideRect, EDGE_RAISED, BF_SOFT | leftFlag | rightFlag);
2815 // Tab Diagonal Corners
2816 if (aDrawLeft) ::DrawEdge(hdc, &lightRect, EDGE_RAISED, BF_SOFT | lightFlag);
2818 if (aDrawRight) ::DrawEdge(hdc, &shadeRect, EDGE_RAISED, BF_SOFT | shadeFlag);
2821 void nsNativeThemeWin::DrawCheckedRect(HDC hdc, const RECT& rc, int32_t fore,
2822 int32_t back, HBRUSH defaultBack) {
2823 static WORD patBits[8] = {0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55};
2825 HBITMAP patBmp = ::CreateBitmap(8, 8, 1, 1, patBits);
2826 if (patBmp) {
2827 HBRUSH brush = (HBRUSH)::CreatePatternBrush(patBmp);
2828 if (brush) {
2829 COLORREF oldForeColor = ::SetTextColor(hdc, ::GetSysColor(fore));
2830 COLORREF oldBackColor = ::SetBkColor(hdc, ::GetSysColor(back));
2831 POINT vpOrg;
2833 ::UnrealizeObject(brush);
2834 ::GetViewportOrgEx(hdc, &vpOrg);
2835 ::SetBrushOrgEx(hdc, vpOrg.x + rc.left, vpOrg.y + rc.top, nullptr);
2836 HBRUSH oldBrush = (HBRUSH)::SelectObject(hdc, brush);
2837 ::FillRect(hdc, &rc, brush);
2838 ::SetTextColor(hdc, oldForeColor);
2839 ::SetBkColor(hdc, oldBackColor);
2840 ::SelectObject(hdc, oldBrush);
2841 ::DeleteObject(brush);
2842 } else
2843 ::FillRect(hdc, &rc, defaultBack);
2845 ::DeleteObject(patBmp);
2849 nsresult nsNativeThemeWin::ClassicDrawWidgetBackground(
2850 gfxContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance,
2851 const nsRect& aRect, const nsRect& aDirtyRect) {
2852 int32_t part, state;
2853 bool focused;
2854 nsresult rv;
2855 rv = ClassicGetThemePartAndState(aFrame, aAppearance, part, state, focused);
2856 if (NS_FAILED(rv)) return rv;
2858 if (AssumeThemePartAndStateAreTransparent(part, state)) {
2859 return NS_OK;
2862 gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel());
2863 RECT widgetRect;
2864 gfxRect tr(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()),
2865 dr(aDirtyRect.X(), aDirtyRect.Y(), aDirtyRect.Width(),
2866 aDirtyRect.Height());
2868 tr.Scale(1.0 / p2a);
2869 dr.Scale(1.0 / p2a);
2871 gfxWindowsNativeDrawing nativeDrawing(
2872 aContext, dr, GetWidgetNativeDrawingFlags(aAppearance));
2874 RENDER_AGAIN:
2876 HDC hdc = nativeDrawing.BeginNativeDrawing();
2877 if (!hdc) return NS_ERROR_FAILURE;
2879 nativeDrawing.TransformToNativeRect(tr, widgetRect);
2881 rv = NS_OK;
2882 switch (aAppearance) {
2883 // Draw button
2884 case StyleAppearance::Button: {
2885 if (focused) {
2886 // draw dark button focus border first
2887 HBRUSH brush;
2888 brush = ::GetSysColorBrush(COLOR_3DDKSHADOW);
2889 if (brush) ::FrameRect(hdc, &widgetRect, brush);
2890 InflateRect(&widgetRect, -1, -1);
2892 [[fallthrough]];
2894 // Draw controls supported by DrawFrameControl
2895 case StyleAppearance::Checkbox:
2896 case StyleAppearance::Radio:
2897 case StyleAppearance::SpinnerUpbutton:
2898 case StyleAppearance::SpinnerDownbutton:
2899 case StyleAppearance::MozMenulistArrowButton: {
2900 int32_t oldTA;
2901 // setup DC to make DrawFrameControl draw correctly
2902 oldTA = ::SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
2903 ::DrawFrameControl(hdc, &widgetRect, part, state);
2904 ::SetTextAlign(hdc, oldTA);
2905 break;
2907 // Draw controls with 2px 3D inset border
2908 case StyleAppearance::NumberInput:
2909 case StyleAppearance::Textfield:
2910 case StyleAppearance::Textarea:
2911 case StyleAppearance::Listbox:
2912 case StyleAppearance::Menulist:
2913 case StyleAppearance::MenulistButton: {
2914 // Draw inset edge
2915 ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
2917 ElementState elementState = GetContentState(aFrame, aAppearance);
2919 // Fill in background
2921 if (elementState.HasState(ElementState::DISABLED) ||
2922 (aFrame->GetContent()->IsXULElement() && IsReadOnly(aFrame)))
2923 ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1));
2924 else
2925 ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_WINDOW + 1));
2927 break;
2929 case StyleAppearance::Treeview: {
2930 // Draw inset edge
2931 ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
2933 // Fill in window color background
2934 ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_WINDOW + 1));
2936 break;
2938 case StyleAppearance::Groupbox:
2939 ::DrawEdge(hdc, &widgetRect, EDGE_ETCHED, BF_RECT | BF_ADJUST);
2940 ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1));
2941 break;
2942 // Draw 3D face background controls
2943 case StyleAppearance::ProgressBar:
2944 // Draw 3D border
2945 ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE);
2946 InflateRect(&widgetRect, -1, -1);
2947 [[fallthrough]];
2948 case StyleAppearance::Tabpanel: {
2949 ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1));
2950 break;
2952 case StyleAppearance::RangeThumb: {
2953 ElementState elementState = GetContentState(aFrame, aAppearance);
2955 ::DrawEdge(hdc, &widgetRect, EDGE_RAISED,
2956 BF_RECT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
2957 if (elementState.HasState(ElementState::DISABLED)) {
2958 DrawCheckedRect(hdc, widgetRect, COLOR_3DFACE, COLOR_3DHILIGHT,
2959 (HBRUSH)COLOR_3DHILIGHT);
2962 break;
2964 // Draw scale track background
2965 case StyleAppearance::Range: {
2966 const int32_t trackWidth = 4;
2967 // When rounding is necessary, we round the position of the track
2968 // away from the chevron of the thumb to make it look better.
2969 if (IsRangeHorizontal(aFrame)) {
2970 widgetRect.top += (widgetRect.bottom - widgetRect.top - trackWidth) / 2;
2971 widgetRect.bottom = widgetRect.top + trackWidth;
2972 } else {
2973 if (!IsFrameRTL(aFrame)) {
2974 widgetRect.left +=
2975 (widgetRect.right - widgetRect.left - trackWidth) / 2;
2976 widgetRect.right = widgetRect.left + trackWidth;
2977 } else {
2978 widgetRect.right -=
2979 (widgetRect.right - widgetRect.left - trackWidth) / 2;
2980 widgetRect.left = widgetRect.right - trackWidth;
2984 ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
2985 ::FillRect(hdc, &widgetRect, (HBRUSH)GetStockObject(GRAY_BRUSH));
2987 break;
2989 case StyleAppearance::Progresschunk: {
2990 nsIFrame* stateFrame = aFrame->GetParent();
2991 ElementState elementState = GetContentState(stateFrame, aAppearance);
2993 const bool indeterminate =
2994 elementState.HasState(ElementState::INDETERMINATE);
2995 bool vertical = IsVerticalProgress(stateFrame);
2997 nsIContent* content = aFrame->GetContent();
2998 if (!indeterminate || !content) {
2999 ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_HIGHLIGHT + 1));
3000 break;
3003 RECT overlayRect = CalculateProgressOverlayRect(
3004 aFrame, &widgetRect, vertical, indeterminate, true);
3006 ::FillRect(hdc, &overlayRect, (HBRUSH)(COLOR_HIGHLIGHT + 1));
3008 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
3009 NS_WARNING("unable to animate progress widget!");
3011 break;
3014 // Draw Tab
3015 case StyleAppearance::Tab: {
3016 DrawTab(hdc, widgetRect, IsBottomTab(aFrame) ? BF_BOTTOM : BF_TOP,
3017 IsSelectedTab(aFrame), !IsRightToSelectedTab(aFrame),
3018 !IsLeftToSelectedTab(aFrame));
3020 break;
3022 case StyleAppearance::Tabpanels:
3023 ::DrawEdge(hdc, &widgetRect, EDGE_RAISED,
3024 BF_SOFT | BF_MIDDLE | BF_LEFT | BF_RIGHT | BF_BOTTOM);
3026 break;
3027 case StyleAppearance::Menubar:
3028 break;
3029 case StyleAppearance::Menuseparator: {
3030 // separators are offset by a bit (see menu.css)
3031 widgetRect.left++;
3032 widgetRect.right--;
3034 // This magic number is brought to you by the value in menu.css
3035 widgetRect.top += 4;
3036 // Our rectangles are 1 pixel high (see border size in menu.css)
3037 widgetRect.bottom = widgetRect.top + 1;
3038 ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_3DSHADOW + 1));
3039 widgetRect.top++;
3040 widgetRect.bottom++;
3041 ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_3DHILIGHT + 1));
3042 break;
3045 case StyleAppearance::MozWindowTitlebar:
3046 case StyleAppearance::MozWindowTitlebarMaximized: {
3047 RECT rect = widgetRect;
3048 int32_t offset = GetSystemMetrics(SM_CXFRAME);
3050 // first fill the area to the color of the window background
3051 ::FillRect(hdc, &rect, (HBRUSH)(COLOR_3DFACE + 1));
3053 // inset the caption area so it doesn't overflow.
3054 rect.top += offset;
3055 // if enabled, draw a gradient titlebar background, otherwise
3056 // fill with a solid color.
3057 BOOL bFlag = TRUE;
3058 SystemParametersInfo(SPI_GETGRADIENTCAPTIONS, 0, &bFlag, 0);
3059 if (!bFlag) {
3060 if (state == mozilla::widget::themeconst::FS_ACTIVE)
3061 ::FillRect(hdc, &rect, (HBRUSH)(COLOR_ACTIVECAPTION + 1));
3062 else
3063 ::FillRect(hdc, &rect, (HBRUSH)(COLOR_INACTIVECAPTION + 1));
3064 } else {
3065 DWORD startColor, endColor;
3066 if (state == mozilla::widget::themeconst::FS_ACTIVE) {
3067 startColor = GetSysColor(COLOR_ACTIVECAPTION);
3068 endColor = GetSysColor(COLOR_GRADIENTACTIVECAPTION);
3069 } else {
3070 startColor = GetSysColor(COLOR_INACTIVECAPTION);
3071 endColor = GetSysColor(COLOR_GRADIENTINACTIVECAPTION);
3074 TRIVERTEX vertex[2];
3075 vertex[0].x = rect.left;
3076 vertex[0].y = rect.top;
3077 vertex[0].Red = GetRValue(startColor) << 8;
3078 vertex[0].Green = GetGValue(startColor) << 8;
3079 vertex[0].Blue = GetBValue(startColor) << 8;
3080 vertex[0].Alpha = 0;
3082 vertex[1].x = rect.right;
3083 vertex[1].y = rect.bottom;
3084 vertex[1].Red = GetRValue(endColor) << 8;
3085 vertex[1].Green = GetGValue(endColor) << 8;
3086 vertex[1].Blue = GetBValue(endColor) << 8;
3087 vertex[1].Alpha = 0;
3089 GRADIENT_RECT gRect;
3090 gRect.UpperLeft = 0;
3091 gRect.LowerRight = 1;
3092 // available on win2k & up
3093 GradientFill(hdc, vertex, 2, &gRect, 1, GRADIENT_FILL_RECT_H);
3096 if (aAppearance == StyleAppearance::MozWindowTitlebar) {
3097 // frame things up with a top raised border.
3098 DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_TOP);
3100 break;
3103 case StyleAppearance::MozWindowButtonClose:
3104 case StyleAppearance::MozWindowButtonMinimize:
3105 case StyleAppearance::MozWindowButtonMaximize:
3106 case StyleAppearance::MozWindowButtonRestore: {
3107 if (aAppearance == StyleAppearance::MozWindowButtonMinimize) {
3108 OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_MINIMIZE);
3109 } else if (aAppearance == StyleAppearance::MozWindowButtonMaximize ||
3110 aAppearance == StyleAppearance::MozWindowButtonRestore) {
3111 OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_RESTORE);
3112 } else if (aAppearance == StyleAppearance::MozWindowButtonClose) {
3113 OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_CLOSE);
3115 int32_t oldTA = SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
3116 DrawFrameControl(hdc, &widgetRect, part, state);
3117 SetTextAlign(hdc, oldTA);
3118 break;
3121 default:
3122 rv = NS_ERROR_FAILURE;
3123 break;
3126 nativeDrawing.EndNativeDrawing();
3128 if (NS_FAILED(rv)) return rv;
3130 if (nativeDrawing.ShouldRenderAgain()) goto RENDER_AGAIN;
3132 nativeDrawing.PaintToContext();
3134 return rv;
3137 uint32_t nsNativeThemeWin::GetWidgetNativeDrawingFlags(
3138 StyleAppearance aAppearance) {
3139 switch (aAppearance) {
3140 case StyleAppearance::Button:
3141 case StyleAppearance::NumberInput:
3142 case StyleAppearance::Textfield:
3143 case StyleAppearance::Textarea:
3144 case StyleAppearance::Menulist:
3145 case StyleAppearance::MenulistButton:
3146 return gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
3147 gfxWindowsNativeDrawing::CAN_AXIS_ALIGNED_SCALE |
3148 gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
3150 // the dropdown button /almost/ renders correctly with scaling,
3151 // except that the graphic in the dropdown button (the downward arrow)
3152 // doesn't get scaled up.
3153 case StyleAppearance::MozMenulistArrowButton:
3154 // these are definitely no; they're all graphics that don't get scaled up
3155 case StyleAppearance::Checkbox:
3156 case StyleAppearance::Radio:
3157 case StyleAppearance::Groupbox:
3158 case StyleAppearance::Checkmenuitem:
3159 case StyleAppearance::Radiomenuitem:
3160 case StyleAppearance::Menucheckbox:
3161 case StyleAppearance::Menuradio:
3162 case StyleAppearance::Menuarrow:
3163 return gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
3164 gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE |
3165 gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
3167 // need to check these others
3168 default:
3169 return gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
3170 gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE |
3171 gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
3175 } // namespace mozilla::widget
3177 ///////////////////////////////////////////
3178 // Creation Routine
3179 ///////////////////////////////////////////
3181 already_AddRefed<Theme> do_CreateNativeThemeDoNotUseDirectly() {
3182 return do_AddRef(new nsNativeThemeWin());