summaryrefslogtreecommitdiffstats
path: root/widget/windows/nsNativeThemeWin.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /widget/windows/nsNativeThemeWin.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/windows/nsNativeThemeWin.cpp')
-rw-r--r--widget/windows/nsNativeThemeWin.cpp1975
1 files changed, 1975 insertions, 0 deletions
diff --git a/widget/windows/nsNativeThemeWin.cpp b/widget/windows/nsNativeThemeWin.cpp
new file mode 100644
index 0000000000..04883a833f
--- /dev/null
+++ b/widget/windows/nsNativeThemeWin.cpp
@@ -0,0 +1,1975 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNativeThemeWin.h"
+
+#include <algorithm>
+#include <malloc.h>
+
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "gfxWindowsNativeDrawing.h"
+#include "gfxWindowsPlatform.h"
+#include "gfxWindowsSurface.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/gfx/Types.h" // for Color::FromABGR
+#include "mozilla/Logging.h"
+#include "mozilla/RelativeLuminanceUtils.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/dom/XULButtonElement.h"
+#include "nsColor.h"
+#include "nsComboboxControlFrame.h"
+#include "nsDeviceContext.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsLookAndFeel.h"
+#include "nsNameSpaceManager.h"
+#include "Theme.h"
+#include "nsPresContext.h"
+#include "nsRect.h"
+#include "nsSize.h"
+#include "nsStyleConsts.h"
+#include "nsTransform2D.h"
+#include "nsWindow.h"
+#include "prinrval.h"
+#include "WinUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+
+using ElementState = dom::ElementState;
+
+extern mozilla::LazyLogModule gWindowsLog;
+
+namespace mozilla::widget {
+
+nsNativeThemeWin::nsNativeThemeWin()
+ : Theme(ScrollbarStyle()),
+ mProgressDeterminateTimeStamp(TimeStamp::Now()),
+ mProgressIndeterminateTimeStamp(TimeStamp::Now()),
+ mBorderCacheValid(),
+ mMinimumWidgetSizeCacheValid(),
+ mGutterSizeCacheValid(false) {}
+
+nsNativeThemeWin::~nsNativeThemeWin() { nsUXThemeData::Invalidate(); }
+
+bool nsNativeThemeWin::IsWidgetAlwaysNonNative(nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ return Theme::IsWidgetAlwaysNonNative(aFrame, aAppearance) ||
+ aAppearance == StyleAppearance::Checkbox ||
+ aAppearance == StyleAppearance::Radio ||
+ aAppearance == StyleAppearance::MozMenulistArrowButton ||
+ aAppearance == StyleAppearance::SpinnerUpbutton ||
+ aAppearance == StyleAppearance::SpinnerDownbutton;
+}
+
+auto nsNativeThemeWin::IsWidgetNonNative(nsIFrame* aFrame,
+ StyleAppearance aAppearance)
+ -> NonNative {
+ if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
+ return NonNative::Always;
+ }
+
+ // We only know how to draw light widgets, so we defer to the non-native
+ // theme when appropriate.
+ if (Theme::ThemeSupportsWidget(aFrame->PresContext(), aFrame, aAppearance) &&
+ LookAndFeel::ColorSchemeForFrame(aFrame) ==
+ LookAndFeel::ColorScheme::Dark) {
+ return NonNative::BecauseColorMismatch;
+ }
+ return NonNative::No;
+}
+
+static MARGINS GetCheckboxMargins(HANDLE theme, HDC hdc) {
+ MARGINS checkboxContent = {0};
+ GetThemeMargins(theme, hdc, MENU_POPUPCHECK, MCB_NORMAL, TMT_CONTENTMARGINS,
+ nullptr, &checkboxContent);
+ return checkboxContent;
+}
+
+static SIZE GetCheckboxBGSize(HANDLE theme, HDC hdc) {
+ SIZE checkboxSize;
+ GetThemePartSize(theme, hdc, MENU_POPUPCHECK, MC_CHECKMARKNORMAL, nullptr,
+ TS_TRUE, &checkboxSize);
+
+ MARGINS checkboxMargins = GetCheckboxMargins(theme, hdc);
+
+ int leftMargin = checkboxMargins.cxLeftWidth;
+ int rightMargin = checkboxMargins.cxRightWidth;
+ int topMargin = checkboxMargins.cyTopHeight;
+ int bottomMargin = checkboxMargins.cyBottomHeight;
+
+ int width = leftMargin + checkboxSize.cx + rightMargin;
+ int height = topMargin + checkboxSize.cy + bottomMargin;
+ SIZE ret;
+ ret.cx = width;
+ ret.cy = height;
+ return ret;
+}
+
+static SIZE GetCheckboxBGBounds(HANDLE theme, HDC hdc) {
+ MARGINS checkboxBGSizing = {0};
+ MARGINS checkboxBGContent = {0};
+ GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL,
+ TMT_SIZINGMARGINS, nullptr, &checkboxBGSizing);
+ GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL,
+ TMT_CONTENTMARGINS, nullptr, &checkboxBGContent);
+
+#define posdx(d) ((d) > 0 ? d : 0)
+
+ int dx =
+ posdx(checkboxBGContent.cxRightWidth - checkboxBGSizing.cxRightWidth) +
+ posdx(checkboxBGContent.cxLeftWidth - checkboxBGSizing.cxLeftWidth);
+ int dy =
+ posdx(checkboxBGContent.cyTopHeight - checkboxBGSizing.cyTopHeight) +
+ posdx(checkboxBGContent.cyBottomHeight - checkboxBGSizing.cyBottomHeight);
+
+#undef posdx
+
+ SIZE ret(GetCheckboxBGSize(theme, hdc));
+ ret.cx += dx;
+ ret.cy += dy;
+ return ret;
+}
+
+static SIZE GetGutterSize(HANDLE theme, HDC hdc) {
+ SIZE gutterSize;
+ GetThemePartSize(theme, hdc, MENU_POPUPGUTTER, 0, nullptr, TS_TRUE,
+ &gutterSize);
+
+ SIZE checkboxBGSize(GetCheckboxBGBounds(theme, hdc));
+
+ SIZE itemSize;
+ GetThemePartSize(theme, hdc, MENU_POPUPITEM, MPI_NORMAL, nullptr, TS_TRUE,
+ &itemSize);
+
+ // Figure out how big the menuitem's icon will be (if present) at current DPI
+ // Needs the system scale for consistency with Windows Theme API.
+ double scaleFactor = WinUtils::SystemScaleFactor();
+ int iconDevicePixels = NSToIntRound(16 * scaleFactor);
+ SIZE iconSize = {iconDevicePixels, iconDevicePixels};
+ // Not really sure what margins should be used here, but this seems to work in
+ // practice...
+ MARGINS margins = {0};
+ GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL,
+ TMT_CONTENTMARGINS, nullptr, &margins);
+ iconSize.cx += margins.cxLeftWidth + margins.cxRightWidth;
+ iconSize.cy += margins.cyTopHeight + margins.cyBottomHeight;
+
+ int width = std::max(
+ itemSize.cx, std::max(iconSize.cx, checkboxBGSize.cx) + gutterSize.cx);
+ int height = std::max(itemSize.cy, std::max(iconSize.cy, checkboxBGSize.cy));
+
+ SIZE ret;
+ ret.cx = width;
+ ret.cy = height;
+ return ret;
+}
+
+SIZE nsNativeThemeWin::GetCachedGutterSize(HANDLE theme) {
+ if (mGutterSizeCacheValid) {
+ return mGutterSizeCache;
+ }
+
+ mGutterSizeCache = GetGutterSize(theme, nullptr);
+ mGutterSizeCacheValid = true;
+
+ return mGutterSizeCache;
+}
+
+/*
+ * Notes on progress track and meter part constants:
+ * xp and up:
+ * PP_BAR(_VERT) - base progress track
+ * PP_TRANSPARENTBAR(_VERT) - transparent progress track. this only works if
+ * the underlying surface supports alpha. otherwise
+ * theme lib's DrawThemeBackground falls back on
+ * opaque PP_BAR. we currently don't use this.
+ * PP_CHUNK(_VERT) - xp progress meter. this does not draw an xp style
+ * progress w/chunks, it draws fill using the chunk
+ * graphic.
+ * vista and up:
+ * PP_FILL(_VERT) - progress meter. these have four states/colors.
+ * PP_PULSEOVERLAY(_VERT) - white reflection - an overlay, not sure what this
+ * is used for.
+ * PP_MOVEOVERLAY(_VERT) - green pulse - the pulse effect overlay on
+ * determined progress bars. we also use this for
+ * indeterminate chunk.
+ *
+ * Notes on state constants:
+ * PBBS_NORMAL - green progress
+ * PBBVS_PARTIAL/PBFVS_ERROR - red error progress
+ * PBFS_PAUSED - yellow paused progress
+ *
+ * There is no common controls style indeterminate part on vista and up.
+ */
+
+/*
+ * Progress bar related constants. These values are found by experimenting and
+ * comparing against native widgets used by the system. They are very unlikely
+ * exact but try to not be too wrong.
+ */
+// The amount of time we animate progress meters parts across the frame.
+static const double kProgressDeterminateTimeSpan = 3.0;
+static const double kProgressIndeterminateTimeSpan = 5.0;
+// The width of the overlay used to animate the horizontal progress bar (Vista
+// and later).
+static const int32_t kProgressHorizontalOverlaySize = 120;
+// The height of the overlay used to animate the vertical progress bar (Vista
+// and later).
+static const int32_t kProgressVerticalOverlaySize = 45;
+// The height of the overlay used for the vertical indeterminate progress bar
+// (Vista and later).
+static const int32_t kProgressVerticalIndeterminateOverlaySize = 60;
+// The width of the overlay used to animate the indeterminate progress bar
+// (Windows Classic).
+static const int32_t kProgressClassicOverlaySize = 40;
+
+/*
+ * GetProgressOverlayStyle - returns the proper overlay part for themed
+ * progress bars based on os and orientation.
+ */
+static int32_t GetProgressOverlayStyle(bool aIsVertical) {
+ return aIsVertical ? PP_MOVEOVERLAYVERT : PP_MOVEOVERLAY;
+}
+
+/*
+ * GetProgressOverlaySize - returns the minimum width or height for themed
+ * progress bar overlays. This includes the width of indeterminate chunks
+ * and vista pulse overlays.
+ */
+static int32_t GetProgressOverlaySize(bool aIsVertical, bool aIsIndeterminate) {
+ if (aIsVertical) {
+ return aIsIndeterminate ? kProgressVerticalIndeterminateOverlaySize
+ : kProgressVerticalOverlaySize;
+ }
+ return kProgressHorizontalOverlaySize;
+}
+
+/*
+ * IsProgressMeterFilled - Determines if a progress meter is at 100% fill based
+ * on a comparison of the current value and maximum.
+ */
+static bool IsProgressMeterFilled(nsIFrame* aFrame) {
+ NS_ENSURE_TRUE(aFrame, false);
+ nsIFrame* parentFrame = aFrame->GetParent();
+ NS_ENSURE_TRUE(parentFrame, false);
+ return nsNativeTheme::GetProgressValue(parentFrame) ==
+ nsNativeTheme::GetProgressMaxValue(parentFrame);
+}
+
+/*
+ * CalculateProgressOverlayRect - returns the padded overlay animation rect
+ * used in rendering progress bars. Resulting rects are used in rendering
+ * vista+ pulse overlays and indeterminate progress meters. Graphics should
+ * be rendered at the origin.
+ */
+RECT nsNativeThemeWin::CalculateProgressOverlayRect(nsIFrame* aFrame,
+ RECT* aWidgetRect,
+ bool aIsVertical,
+ bool aIsIndeterminate,
+ bool aIsClassic) {
+ NS_ASSERTION(aFrame, "bad frame pointer");
+ NS_ASSERTION(aWidgetRect, "bad rect pointer");
+
+ int32_t frameSize = aIsVertical ? aWidgetRect->bottom - aWidgetRect->top
+ : aWidgetRect->right - aWidgetRect->left;
+
+ // Recycle a set of progress pulse timers - these timers control the position
+ // of all progress overlays and indeterminate chunks that get rendered.
+ double span = aIsIndeterminate ? kProgressIndeterminateTimeSpan
+ : kProgressDeterminateTimeSpan;
+ TimeDuration period;
+ if (!aIsIndeterminate) {
+ if (TimeStamp::Now() >
+ (mProgressDeterminateTimeStamp + TimeDuration::FromSeconds(span))) {
+ mProgressDeterminateTimeStamp = TimeStamp::Now();
+ }
+ period = TimeStamp::Now() - mProgressDeterminateTimeStamp;
+ } else {
+ if (TimeStamp::Now() >
+ (mProgressIndeterminateTimeStamp + TimeDuration::FromSeconds(span))) {
+ mProgressIndeterminateTimeStamp = TimeStamp::Now();
+ }
+ period = TimeStamp::Now() - mProgressIndeterminateTimeStamp;
+ }
+
+ double percent = period / TimeDuration::FromSeconds(span);
+
+ if (!aIsVertical && IsFrameRTL(aFrame)) percent = 1 - percent;
+
+ RECT overlayRect = *aWidgetRect;
+ int32_t overlaySize;
+ if (!aIsClassic) {
+ overlaySize = GetProgressOverlaySize(aIsVertical, aIsIndeterminate);
+ } else {
+ overlaySize = kProgressClassicOverlaySize;
+ }
+
+ // Calculate a bounds that is larger than the meters frame such that the
+ // overlay starts and ends completely off the edge of the frame:
+ // [overlay][frame][overlay]
+ // This also yields a nice delay on rotation. Use overlaySize as the minimum
+ // size for [overlay] based on the graphics dims. If [frame] is larger, use
+ // the frame size instead.
+ int trackWidth = frameSize > overlaySize ? frameSize : overlaySize;
+ if (!aIsVertical) {
+ int xPos = aWidgetRect->left - trackWidth;
+ xPos += (int)ceil(((double)(trackWidth * 2) * percent));
+ overlayRect.left = xPos;
+ overlayRect.right = xPos + overlaySize;
+ } else {
+ int yPos = aWidgetRect->bottom + trackWidth;
+ yPos -= (int)ceil(((double)(trackWidth * 2) * percent));
+ overlayRect.bottom = yPos;
+ overlayRect.top = yPos - overlaySize;
+ }
+ return overlayRect;
+}
+
+/*
+ * DrawProgressMeter - render an appropriate progress meter based on progress
+ * meter style, orientation, and os. Note, this does not render the underlying
+ * progress track.
+ *
+ * @param aFrame the widget frame
+ * @param aAppearance type of widget
+ * @param aTheme progress theme handle
+ * @param aHdc hdc returned by gfxWindowsNativeDrawing
+ * @param aPart the PP_X progress part
+ * @param aState the theme state
+ * @param aWidgetRect bounding rect for the widget
+ * @param aClipRect dirty rect that needs drawing.
+ * @param aAppUnits app units per device pixel
+ */
+void nsNativeThemeWin::DrawThemedProgressMeter(
+ nsIFrame* aFrame, StyleAppearance aAppearance, HANDLE aTheme, HDC aHdc,
+ int aPart, int aState, RECT* aWidgetRect, RECT* aClipRect) {
+ if (!aFrame || !aTheme || !aHdc) return;
+
+ NS_ASSERTION(aWidgetRect, "bad rect pointer");
+ NS_ASSERTION(aClipRect, "bad clip rect pointer");
+
+ RECT adjWidgetRect, adjClipRect;
+ adjWidgetRect = *aWidgetRect;
+ adjClipRect = *aClipRect;
+
+ nsIFrame* parentFrame = aFrame->GetParent();
+ if (!parentFrame) {
+ // We have no parent to work with, just bail.
+ NS_WARNING("No parent frame for progress rendering. Can't paint.");
+ return;
+ }
+
+ ElementState elementState = GetContentState(parentFrame, aAppearance);
+ bool vertical = IsVerticalProgress(parentFrame);
+ bool indeterminate = elementState.HasState(ElementState::INDETERMINATE);
+ bool animate = indeterminate;
+
+ // Vista and up progress meter is fill style, rendered here. We render
+ // the pulse overlay in the follow up section below.
+ DrawThemeBackground(aTheme, aHdc, aPart, aState, &adjWidgetRect,
+ &adjClipRect);
+ if (!IsProgressMeterFilled(aFrame)) {
+ animate = true;
+ }
+
+ if (animate) {
+ // Indeterminate rendering
+ int32_t overlayPart = GetProgressOverlayStyle(vertical);
+ RECT overlayRect = CalculateProgressOverlayRect(
+ aFrame, &adjWidgetRect, vertical, indeterminate, false);
+ DrawThemeBackground(aTheme, aHdc, overlayPart, aState, &overlayRect,
+ &adjClipRect);
+
+ if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 60)) {
+ NS_WARNING("unable to animate progress widget!");
+ }
+ }
+}
+
+LayoutDeviceIntMargin nsNativeThemeWin::GetCachedWidgetBorder(
+ HTHEME aTheme, nsUXThemeClass aThemeClass, StyleAppearance aAppearance,
+ int32_t aPart, int32_t aState) {
+ int32_t cacheIndex = aThemeClass * THEME_PART_DISTINCT_VALUE_COUNT + aPart;
+ int32_t cacheBitIndex = cacheIndex / 8;
+ uint8_t cacheBit = 1u << (cacheIndex % 8);
+
+ if (mBorderCacheValid[cacheBitIndex] & cacheBit) {
+ return mBorderCache[cacheIndex];
+ }
+
+ // Get our info.
+ RECT outerRect; // Create a fake outer rect.
+ outerRect.top = outerRect.left = 100;
+ outerRect.right = outerRect.bottom = 200;
+ RECT contentRect(outerRect);
+ HRESULT res = GetThemeBackgroundContentRect(aTheme, nullptr, aPart, aState,
+ &outerRect, &contentRect);
+
+ if (FAILED(res)) {
+ return LayoutDeviceIntMargin();
+ }
+
+ // Now compute the delta in each direction and place it in our
+ // nsIntMargin struct.
+ LayoutDeviceIntMargin result;
+ result.top = contentRect.top - outerRect.top;
+ result.bottom = outerRect.bottom - contentRect.bottom;
+ result.left = contentRect.left - outerRect.left;
+ result.right = outerRect.right - contentRect.right;
+
+ mBorderCacheValid[cacheBitIndex] |= cacheBit;
+ mBorderCache[cacheIndex] = result;
+
+ return result;
+}
+
+nsresult nsNativeThemeWin::GetCachedMinimumWidgetSize(
+ nsIFrame* aFrame, HANDLE aTheme, nsUXThemeClass aThemeClass,
+ StyleAppearance aAppearance, int32_t aPart, int32_t aState,
+ THEMESIZE aSizeReq, mozilla::LayoutDeviceIntSize* aResult) {
+ int32_t cachePart = aPart;
+
+ if (aAppearance == StyleAppearance::Button && aSizeReq == TS_MIN) {
+ // In practice, StyleAppearance::Button is the only widget type which has an
+ // aSizeReq that varies for us, and it can only be TS_MIN or TS_TRUE. Just
+ // stuff that extra bit into the aPart part of the cache, since BP_Count is
+ // well below THEME_PART_DISTINCT_VALUE_COUNT anyway.
+ cachePart = BP_Count;
+ }
+
+ MOZ_ASSERT(aPart < THEME_PART_DISTINCT_VALUE_COUNT);
+ int32_t cacheIndex =
+ aThemeClass * THEME_PART_DISTINCT_VALUE_COUNT + cachePart;
+ int32_t cacheBitIndex = cacheIndex / 8;
+ uint8_t cacheBit = 1u << (cacheIndex % 8);
+
+ if (mMinimumWidgetSizeCacheValid[cacheBitIndex] & cacheBit) {
+ *aResult = mMinimumWidgetSizeCache[cacheIndex];
+ return NS_OK;
+ }
+
+ HDC hdc = ::GetDC(NULL);
+ if (!hdc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ SIZE sz;
+ GetThemePartSize(aTheme, hdc, aPart, aState, nullptr, aSizeReq, &sz);
+ aResult->width = sz.cx;
+ aResult->height = sz.cy;
+
+ ::ReleaseDC(nullptr, hdc);
+
+ mMinimumWidgetSizeCacheValid[cacheBitIndex] |= cacheBit;
+ mMinimumWidgetSizeCache[cacheIndex] = *aResult;
+
+ return NS_OK;
+}
+
+mozilla::Maybe<nsUXThemeClass> nsNativeThemeWin::GetThemeClass(
+ StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ return Some(eUXButton);
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ return Some(eUXEdit);
+ case StyleAppearance::Toolbox:
+ return Some(eUXRebar);
+ case StyleAppearance::Toolbar:
+ case StyleAppearance::Toolbarbutton:
+ case StyleAppearance::Separator:
+ return Some(eUXToolbar);
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Progresschunk:
+ return Some(eUXProgress);
+ case StyleAppearance::Tab:
+ case StyleAppearance::Tabpanel:
+ case StyleAppearance::Tabpanels:
+ return Some(eUXTab);
+ case StyleAppearance::Range:
+ case StyleAppearance::RangeThumb:
+ return Some(eUXTrackbar);
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ return Some(eUXCombobox);
+ case StyleAppearance::Treeheadercell:
+ return Some(eUXHeader);
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::Treetwistyopen:
+ case StyleAppearance::Treeitem:
+ return Some(eUXListview);
+ default:
+ return Nothing();
+ }
+}
+
+HANDLE
+nsNativeThemeWin::GetTheme(StyleAppearance aAppearance) {
+ mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aAppearance);
+ if (themeClass.isNothing()) {
+ return nullptr;
+ }
+ return nsUXThemeData::GetTheme(themeClass.value());
+}
+
+int32_t nsNativeThemeWin::StandardGetState(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ bool wantFocused) {
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+ if (elementState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE)) {
+ return TS_ACTIVE;
+ }
+ if (elementState.HasState(ElementState::HOVER)) {
+ return TS_HOVER;
+ }
+ if (wantFocused) {
+ if (elementState.HasState(ElementState::FOCUSRING)) {
+ return TS_FOCUSED;
+ }
+ // On Windows, focused buttons are always drawn as such by the native
+ // theme, that's why we check ElementState::FOCUS instead of
+ // ElementState::FOCUSRING.
+ if (aAppearance == StyleAppearance::Button &&
+ elementState.HasState(ElementState::FOCUS)) {
+ return TS_FOCUSED;
+ }
+ }
+
+ return TS_NORMAL;
+}
+
+bool nsNativeThemeWin::IsMenuActive(nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ nsIContent* content = aFrame->GetContent();
+ if (content->IsXULElement() &&
+ content->NodeInfo()->Equals(nsGkAtoms::richlistitem))
+ return CheckBooleanAttr(aFrame, nsGkAtoms::selected);
+
+ return CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
+}
+
+/**
+ * aPart is filled in with the UXTheme part code. On return, values > 0
+ * are the actual UXTheme part code; -1 means the widget will be drawn by
+ * us; 0 means that we should use part code 0, which isn't a real part code
+ * but elicits some kind of default behaviour from UXTheme when drawing
+ * (but isThemeBackgroundPartiallyTransparent may not work).
+ */
+nsresult nsNativeThemeWin::GetThemePartAndState(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ int32_t& aPart,
+ int32_t& aState) {
+ switch (aAppearance) {
+ case StyleAppearance::Button: {
+ aPart = BP_BUTTON;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+ if (elementState.HasState(ElementState::DISABLED)) {
+ aState = TS_DISABLED;
+ return NS_OK;
+ }
+ if (IsOpenButton(aFrame) || IsCheckedButton(aFrame)) {
+ aState = TS_ACTIVE;
+ return NS_OK;
+ }
+
+ aState = StandardGetState(aFrame, aAppearance, true);
+
+ // Check for default dialog buttons. These buttons should always look
+ // focused.
+ if (aState == TS_NORMAL && IsDefaultButton(aFrame)) aState = TS_FOCUSED;
+ return NS_OK;
+ }
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea: {
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+
+ /* Note: the NOSCROLL type has a rounded corner in each corner. The more
+ * specific HSCROLL, VSCROLL, HVSCROLL types have side and/or top/bottom
+ * edges rendered as straight horizontal lines with sharp corners to
+ * accommodate a scrollbar. However, the scrollbar gets rendered on top
+ * of this for us, so we don't care, and can just use NOSCROLL here.
+ */
+ aPart = TFP_EDITBORDER_NOSCROLL;
+
+ if (!aFrame) {
+ aState = TFS_EDITBORDER_NORMAL;
+ } else if (elementState.HasState(ElementState::DISABLED)) {
+ aState = TFS_EDITBORDER_DISABLED;
+ } else if (IsReadOnly(aFrame)) {
+ /* no special read-only state */
+ aState = TFS_EDITBORDER_NORMAL;
+ } else if (elementState.HasAtLeastOneOfStates(ElementState::ACTIVE |
+ ElementState::FOCUSRING)) {
+ aState = TFS_EDITBORDER_FOCUSED;
+ } else if (elementState.HasState(ElementState::HOVER)) {
+ aState = TFS_EDITBORDER_HOVER;
+ } else {
+ aState = TFS_EDITBORDER_NORMAL;
+ }
+
+ return NS_OK;
+ }
+ case StyleAppearance::ProgressBar: {
+ bool vertical = IsVerticalProgress(aFrame);
+ aPart = vertical ? PP_BARVERT : PP_BAR;
+ aState = PBBS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Progresschunk: {
+ nsIFrame* parentFrame = aFrame->GetParent();
+ if (IsVerticalProgress(parentFrame)) {
+ aPart = PP_FILLVERT;
+ } else {
+ aPart = PP_FILL;
+ }
+
+ aState = PBBVS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Toolbarbutton: {
+ aPart = BP_BUTTON;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+ if (elementState.HasState(ElementState::DISABLED)) {
+ aState = TS_DISABLED;
+ return NS_OK;
+ }
+ if (IsOpenButton(aFrame)) {
+ aState = TS_ACTIVE;
+ return NS_OK;
+ }
+
+ if (elementState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE))
+ aState = TS_ACTIVE;
+ else if (elementState.HasState(ElementState::HOVER)) {
+ if (IsCheckedButton(aFrame))
+ aState = TB_HOVER_CHECKED;
+ else
+ aState = TS_HOVER;
+ } else {
+ if (IsCheckedButton(aFrame))
+ aState = TB_CHECKED;
+ else
+ aState = TS_NORMAL;
+ }
+
+ return NS_OK;
+ }
+ case StyleAppearance::Separator: {
+ aPart = TP_SEPARATOR;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Range: {
+ if (IsRangeHorizontal(aFrame)) {
+ aPart = TKP_TRACK;
+ aState = TRS_NORMAL;
+ } else {
+ aPart = TKP_TRACKVERT;
+ aState = TRVS_NORMAL;
+ }
+ return NS_OK;
+ }
+ case StyleAppearance::RangeThumb: {
+ if (IsRangeHorizontal(aFrame)) {
+ aPart = TKP_THUMBBOTTOM;
+ } else {
+ aPart = IsFrameRTL(aFrame) ? TKP_THUMBLEFT : TKP_THUMBRIGHT;
+ }
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ } else if (elementState.HasState(ElementState::DISABLED)) {
+ aState = TKP_DISABLED;
+ } else {
+ if (elementState.HasState(
+ ElementState::ACTIVE)) // Hover is not also a requirement for
+ // the thumb, since the drag is not
+ // canceled when you move outside the
+ // thumb.
+ aState = TS_ACTIVE;
+ else if (elementState.HasState(ElementState::FOCUSRING))
+ aState = TKP_FOCUSED;
+ else if (elementState.HasState(ElementState::HOVER))
+ aState = TS_HOVER;
+ else
+ aState = TS_NORMAL;
+ }
+ return NS_OK;
+ }
+ case StyleAppearance::Toolbox: {
+ aState = 0;
+ aPart = RP_BACKGROUND;
+ return NS_OK;
+ }
+ case StyleAppearance::Toolbar: {
+ // Use -1 to indicate we don't wish to have the theme background drawn
+ // for this item. We will pass any nessessary information via aState,
+ // and will render the item using separate code.
+ aPart = -1;
+ aState = 0;
+ if (aFrame) {
+ nsIContent* content = aFrame->GetContent();
+ nsIContent* parent = content->GetParent();
+ // XXXzeniko hiding the first toolbar will result in an unwanted margin
+ if (parent && parent->GetFirstChild() == content) {
+ aState = 1;
+ }
+ }
+ return NS_OK;
+ }
+ case StyleAppearance::Treeview:
+ case StyleAppearance::Listbox: {
+ aPart = TREEVIEW_BODY;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Tabpanels: {
+ aPart = TABP_PANELS;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Tabpanel: {
+ aPart = TABP_PANEL;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Tab: {
+ aPart = TABP_TAB;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+ if (elementState.HasState(ElementState::DISABLED)) {
+ aState = TS_DISABLED;
+ return NS_OK;
+ }
+
+ if (IsSelectedTab(aFrame)) {
+ aPart = TABP_TAB_SELECTED;
+ aState = TS_ACTIVE; // The selected tab is always "pressed".
+ } else
+ aState = StandardGetState(aFrame, aAppearance, true);
+
+ return NS_OK;
+ }
+ case StyleAppearance::Treeheadercell: {
+ aPart = 1;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ aState = StandardGetState(aFrame, aAppearance, true);
+
+ return NS_OK;
+ }
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Menulist: {
+ nsIContent* content = aFrame->GetContent();
+ bool useDropBorder = content && content->IsHTMLElement();
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+
+ /* On Vista/Win7, we use CBP_DROPBORDER instead of DROPFRAME for HTML
+ * content or for editable menulists; this gives us the thin outline,
+ * instead of the gradient-filled background */
+ if (useDropBorder)
+ aPart = CBP_DROPBORDER;
+ else
+ aPart = CBP_DROPFRAME;
+
+ if (elementState.HasState(ElementState::DISABLED)) {
+ aState = TS_DISABLED;
+ } else if (IsReadOnly(aFrame)) {
+ aState = TS_NORMAL;
+ } else if (IsOpenButton(aFrame)) {
+ aState = TS_ACTIVE;
+ } else if (useDropBorder &&
+ elementState.HasState(ElementState::FOCUSRING)) {
+ aState = TS_ACTIVE;
+ } else if (elementState.HasAllStates(ElementState::HOVER |
+ ElementState::ACTIVE)) {
+ aState = TS_ACTIVE;
+ } else if (elementState.HasState(ElementState::HOVER)) {
+ aState = TS_HOVER;
+ } else {
+ aState = TS_NORMAL;
+ }
+
+ return NS_OK;
+ }
+ default:
+ aPart = 0;
+ aState = 0;
+ return NS_ERROR_FAILURE;
+ }
+}
+
+static bool AssumeThemePartAndStateAreTransparent(int32_t aPart,
+ int32_t aState) {
+ if (!nsUXThemeData::IsHighContrastOn() && aPart == MENU_POPUPITEM &&
+ aState == MBI_NORMAL) {
+ return true;
+ }
+ return false;
+}
+
+// When running with per-monitor DPI (on Win8.1+), and rendering on a display
+// with a different DPI setting from the system's default scaling, we need to
+// apply scaling to native-themed elements as the Windows theme APIs assume
+// the system default resolution.
+static inline double GetThemeDpiScaleFactor(nsPresContext* aPresContext) {
+ if (WinUtils::IsPerMonitorDPIAware() ||
+ StaticPrefs::layout_css_devPixelsPerPx() > 0.0) {
+ nsCOMPtr<nsIWidget> rootWidget = aPresContext->GetRootWidget();
+ if (rootWidget) {
+ double systemScale = WinUtils::SystemScaleFactor();
+ return rootWidget->GetDefaultScale().scale / systemScale;
+ }
+ }
+ return 1.0;
+}
+
+static inline double GetThemeDpiScaleFactor(nsIFrame* aFrame) {
+ return GetThemeDpiScaleFactor(aFrame->PresContext());
+}
+
+NS_IMETHODIMP
+nsNativeThemeWin::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect,
+ DrawOverflow aDrawOverflow) {
+ if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
+ return Theme::DrawWidgetBackground(aContext, aFrame, aAppearance, aRect,
+ aDirtyRect, aDrawOverflow);
+ }
+
+ HANDLE theme = GetTheme(aAppearance);
+ if (!theme)
+ return ClassicDrawWidgetBackground(aContext, aFrame, aAppearance, aRect,
+ aDirtyRect);
+
+ // ^^ without the right sdk, assume xp theming and fall through.
+ int32_t part, state;
+ nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
+ if (NS_FAILED(rv)) return rv;
+
+ if (AssumeThemePartAndStateAreTransparent(part, state)) {
+ return NS_OK;
+ }
+
+ gfxContextMatrixAutoSaveRestore save(aContext);
+
+ double themeScale = GetThemeDpiScaleFactor(aFrame);
+ if (themeScale != 1.0) {
+ aContext->SetMatrix(
+ aContext->CurrentMatrix().PreScale(themeScale, themeScale));
+ }
+
+ gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel());
+ RECT widgetRect;
+ RECT clipRect;
+ gfxRect tr(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()),
+ dr(aDirtyRect.X(), aDirtyRect.Y(), aDirtyRect.Width(),
+ aDirtyRect.Height());
+
+ tr.Scale(1.0 / (p2a * themeScale));
+ dr.Scale(1.0 / (p2a * themeScale));
+
+ gfxWindowsNativeDrawing nativeDrawing(
+ aContext, dr, GetWidgetNativeDrawingFlags(aAppearance));
+
+RENDER_AGAIN:
+
+ HDC hdc = nativeDrawing.BeginNativeDrawing();
+ if (!hdc) return NS_ERROR_FAILURE;
+
+ nativeDrawing.TransformToNativeRect(tr, widgetRect);
+ nativeDrawing.TransformToNativeRect(dr, clipRect);
+
+#if 0
+ {
+ MOZ_LOG(gWindowsLog, LogLevel::Error,
+ (stderr, "xform: %f %f %f %f [%f %f]\n", m._11, m._21, m._12, m._22,
+ m._31, m._32));
+ MOZ_LOG(gWindowsLog, LogLevel::Error,
+ (stderr, "tr: [%d %d %d %d]\ndr: [%d %d %d %d]\noff: [%f %f]\n",
+ tr.x, tr.y, tr.width, tr.height, dr.x, dr.y, dr.width, dr.height,
+ offset.x, offset.y));
+ }
+#endif
+
+ if (aAppearance == StyleAppearance::Tab) {
+ // For left edge and right edge tabs, we need to adjust the widget
+ // rects and clip rects so that the edges don't get drawn.
+ bool isLeft = IsLeftToSelectedTab(aFrame);
+ bool isRight = !isLeft && IsRightToSelectedTab(aFrame);
+
+ if (isLeft || isRight) {
+ // HACK ALERT: There appears to be no way to really obtain this value, so
+ // we're forced to just use the default value for Luna (which also happens
+ // to be correct for all the other skins I've tried).
+ int32_t edgeSize = 2;
+
+ // Armed with the size of the edge, we now need to either shift to the
+ // left or to the right. The clip rect won't include this extra area, so
+ // we know that we're effectively shifting the edge out of view (such that
+ // it won't be painted).
+ if (isLeft)
+ // The right edge should not be drawn. Extend our rect by the edge
+ // size.
+ widgetRect.right += edgeSize;
+ else
+ // The left edge should not be drawn. Move the widget rect's left coord
+ // back.
+ widgetRect.left -= edgeSize;
+ }
+ }
+
+ // widgetRect is the bounding box for a widget, yet the scale track is only
+ // a small portion of this size, so the edges of the scale need to be
+ // adjusted to the real size of the track.
+ if (aAppearance == StyleAppearance::Range) {
+ RECT contentRect;
+ GetThemeBackgroundContentRect(theme, hdc, part, state, &widgetRect,
+ &contentRect);
+
+ SIZE siz;
+ GetThemePartSize(theme, hdc, part, state, &widgetRect, TS_TRUE, &siz);
+
+ // When rounding is necessary, we round the position of the track
+ // away from the chevron of the thumb to make it look better.
+ if (IsRangeHorizontal(aFrame)) {
+ contentRect.top += (contentRect.bottom - contentRect.top - siz.cy) / 2;
+ contentRect.bottom = contentRect.top + siz.cy;
+ } else {
+ if (!IsFrameRTL(aFrame)) {
+ contentRect.left += (contentRect.right - contentRect.left - siz.cx) / 2;
+ contentRect.right = contentRect.left + siz.cx;
+ } else {
+ contentRect.right -=
+ (contentRect.right - contentRect.left - siz.cx) / 2;
+ contentRect.left = contentRect.right - siz.cx;
+ }
+ }
+
+ DrawThemeBackground(theme, hdc, part, state, &contentRect, &clipRect);
+ } else if (aAppearance == StyleAppearance::NumberInput ||
+ aAppearance == StyleAppearance::Textfield ||
+ aAppearance == StyleAppearance::Textarea) {
+ DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
+
+ if (state == TFS_EDITBORDER_DISABLED) {
+ InflateRect(&widgetRect, -1, -1);
+ ::FillRect(hdc, &widgetRect, reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1));
+ }
+ } else if (aAppearance == StyleAppearance::ProgressBar) {
+ // DrawThemeBackground renders each corner with a solid white pixel.
+ // Restore these pixels to the underlying color. Tracks are rendered
+ // using alpha recovery, so this makes the corners transparent.
+ COLORREF color;
+ color = GetPixel(hdc, widgetRect.left, widgetRect.top);
+ DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
+ SetPixel(hdc, widgetRect.left, widgetRect.top, color);
+ SetPixel(hdc, widgetRect.right - 1, widgetRect.top, color);
+ SetPixel(hdc, widgetRect.right - 1, widgetRect.bottom - 1, color);
+ SetPixel(hdc, widgetRect.left, widgetRect.bottom - 1, color);
+ } else if (aAppearance == StyleAppearance::Progresschunk) {
+ DrawThemedProgressMeter(aFrame, aAppearance, theme, hdc, part, state,
+ &widgetRect, &clipRect);
+ }
+ // If part is negative, the element wishes us to not render a themed
+ // background, instead opting to be drawn specially below.
+ else if (part >= 0) {
+ DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
+ }
+
+ // Draw focus rectangles for range elements
+ // XXX it'd be nice to draw these outside of the frame
+ if (aAppearance == StyleAppearance::Range) {
+ ElementState contentState = GetContentState(aFrame, aAppearance);
+
+ if (contentState.HasState(ElementState::FOCUSRING)) {
+ POINT vpOrg;
+ HPEN hPen = nullptr;
+
+ uint8_t id = SaveDC(hdc);
+
+ ::SelectClipRgn(hdc, nullptr);
+ ::GetViewportOrgEx(hdc, &vpOrg);
+ ::SetBrushOrgEx(hdc, vpOrg.x + widgetRect.left, vpOrg.y + widgetRect.top,
+ nullptr);
+ ::SetTextColor(hdc, 0);
+ ::DrawFocusRect(hdc, &widgetRect);
+ ::RestoreDC(hdc, id);
+ if (hPen) {
+ ::DeleteObject(hPen);
+ }
+ }
+ } else if (aAppearance == StyleAppearance::Toolbar && state == 0) {
+ // Draw toolbar separator lines above all toolbars except the first one.
+ // The lines are part of the Rebar theme, which is loaded for
+ // StyleAppearance::Toolbox.
+ theme = GetTheme(StyleAppearance::Toolbox);
+ if (!theme) return NS_ERROR_FAILURE;
+
+ widgetRect.bottom = widgetRect.top + TB_SEPARATOR_HEIGHT;
+ DrawThemeEdge(theme, hdc, RP_BAND, 0, &widgetRect, EDGE_ETCHED, BF_TOP,
+ nullptr);
+ }
+
+ nativeDrawing.EndNativeDrawing();
+
+ if (nativeDrawing.ShouldRenderAgain()) goto RENDER_AGAIN;
+
+ nativeDrawing.PaintToContext();
+
+ return NS_OK;
+}
+
+bool nsNativeThemeWin::CreateWebRenderCommandsForWidget(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const layers::StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
+ StyleAppearance aAppearance, const nsRect& aRect) {
+ if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
+ return Theme::CreateWebRenderCommandsForWidget(
+ aBuilder, aResources, aSc, aManager, aFrame, aAppearance, aRect);
+ }
+ return false;
+}
+
+static void ScaleForFrameDPI(LayoutDeviceIntMargin* aMargin, nsIFrame* aFrame) {
+ double themeScale = GetThemeDpiScaleFactor(aFrame);
+ if (themeScale != 1.0) {
+ aMargin->top = NSToIntRound(aMargin->top * themeScale);
+ aMargin->left = NSToIntRound(aMargin->left * themeScale);
+ aMargin->bottom = NSToIntRound(aMargin->bottom * themeScale);
+ aMargin->right = NSToIntRound(aMargin->right * themeScale);
+ }
+}
+
+static void ScaleForFrameDPI(LayoutDeviceIntSize* aSize, nsIFrame* aFrame) {
+ double themeScale = GetThemeDpiScaleFactor(aFrame);
+ if (themeScale != 1.0) {
+ aSize->width = NSToIntRound(aSize->width * themeScale);
+ aSize->height = NSToIntRound(aSize->height * themeScale);
+ }
+}
+
+LayoutDeviceIntMargin nsNativeThemeWin::GetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
+ if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
+ return Theme::GetWidgetBorder(aContext, aFrame, aAppearance);
+ }
+
+ LayoutDeviceIntMargin result;
+ mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aAppearance);
+ HTHEME theme = NULL;
+ if (!themeClass.isNothing()) {
+ theme = nsUXThemeData::GetTheme(themeClass.value());
+ }
+ if (!theme) {
+ result = ClassicGetWidgetBorder(aContext, aFrame, aAppearance);
+ ScaleForFrameDPI(&result, aFrame);
+ return result;
+ }
+
+ if (!WidgetIsContainer(aAppearance) ||
+ aAppearance == StyleAppearance::Toolbox ||
+ aAppearance == StyleAppearance::Tabpanel)
+ return result; // Don't worry about it.
+
+ int32_t part, state;
+ nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
+ if (NS_FAILED(rv)) return result;
+
+ if (aAppearance == StyleAppearance::Toolbar) {
+ // make space for the separator line above all toolbars but the first
+ if (state == 0) result.top = TB_SEPARATOR_HEIGHT;
+ return result;
+ }
+
+ result = GetCachedWidgetBorder(theme, themeClass.value(), aAppearance, part,
+ state);
+
+ // Remove the edges for tabs that are before or after the selected tab,
+ if (aAppearance == StyleAppearance::Tab) {
+ if (IsLeftToSelectedTab(aFrame))
+ // Remove the right edge, since we won't be drawing it.
+ result.right = 0;
+ else if (IsRightToSelectedTab(aFrame))
+ // Remove the left edge, since we won't be drawing it.
+ result.left = 0;
+ }
+
+ if (aFrame && (aAppearance == StyleAppearance::NumberInput ||
+ aAppearance == StyleAppearance::Textfield ||
+ aAppearance == StyleAppearance::Textarea)) {
+ nsIContent* content = aFrame->GetContent();
+ if (content && content->IsHTMLElement()) {
+ // We need to pad textfields by 1 pixel, since the caret will draw
+ // flush against the edge by default if we don't.
+ result.top.value++;
+ result.left.value++;
+ result.bottom.value++;
+ result.right.value++;
+ }
+ }
+
+ ScaleForFrameDPI(&result, aFrame);
+ return result;
+}
+
+bool nsNativeThemeWin::GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) {
+ if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
+ return Theme::GetWidgetPadding(aContext, aFrame, aAppearance, aResult);
+ }
+
+ bool ok = true;
+ HANDLE theme = GetTheme(aAppearance);
+ if (!theme) {
+ ok = ClassicGetWidgetPadding(aContext, aFrame, aAppearance, aResult);
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+ }
+
+ /* textfields need extra pixels on all sides, otherwise they wrap their
+ * content too tightly. The actual border is drawn 1px inside the specified
+ * rectangle, so Gecko will end up making the contents look too small.
+ * Instead, we add 2px padding for the contents and fix this. (Used to be 1px
+ * added, see bug 430212)
+ */
+ if (aAppearance == StyleAppearance::NumberInput ||
+ aAppearance == StyleAppearance::Textfield ||
+ aAppearance == StyleAppearance::Textarea) {
+ aResult->top = aResult->bottom = 2;
+ aResult->left = aResult->right = 2;
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+ } else if (IsHTMLContent(aFrame) &&
+ (aAppearance == StyleAppearance::Menulist ||
+ aAppearance == StyleAppearance::MenulistButton)) {
+ /* For content menulist controls, we need an extra pixel so that we have
+ * room to draw our focus rectangle stuff. Otherwise, the focus rect might
+ * overlap the control's border.
+ */
+ aResult->top = aResult->bottom = 1;
+ aResult->left = aResult->right = 1;
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+ }
+
+ int32_t right, left, top, bottom;
+ right = left = top = bottom = 0;
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ if (aFrame->GetContent()->IsXULElement()) {
+ top = 2;
+ bottom = 3;
+ }
+ left = right = 5;
+ break;
+ default:
+ return false;
+ }
+
+ if (IsFrameRTL(aFrame)) {
+ aResult->right = left;
+ aResult->left = right;
+ } else {
+ aResult->right = right;
+ aResult->left = left;
+ }
+ aResult->top = top;
+ aResult->bottom = bottom;
+
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+}
+
+bool nsNativeThemeWin::GetWidgetOverflow(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsRect* aOverflowRect) {
+ if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
+ return Theme::GetWidgetOverflow(aContext, aFrame, aAppearance,
+ aOverflowRect);
+ }
+
+ /* This is disabled for now, because it causes invalidation problems --
+ * see bug 420381. The effect of not updating the overflow area is that
+ * for dropdown buttons in content areas, there is a 1px border on 3 sides
+ * where, if invalidated, the dropdown control probably won't be repainted.
+ * This is fairly minor, as by default there is nothing in that area, and
+ * a border only shows up if the widget is being hovered.
+ *
+ * TODO(jwatt): Figure out what do to about
+ * StyleAppearance::MozMenulistArrowButton too.
+ */
+#if 0
+ /* We explicitly draw dropdown buttons in HTML content 1px bigger up, right,
+ * and bottom so that they overlap the dropdown's border like they're
+ * supposed to.
+ */
+ if (aAppearance == StyleAppearance::MenulistButton &&
+ IsHTMLContent(aFrame) &&
+ !IsWidgetStyled(aFrame->GetParent()->PresContext(),
+ aFrame->GetParent(),
+ StyleAppearance::Menulist))
+ {
+ int32_t p2a = aContext->AppUnitsPerDevPixel();
+ /* Note: no overflow on the left */
+ nsMargin m(p2a, p2a, p2a, 0);
+ aOverflowRect->Inflate (m);
+ return true;
+ }
+#endif
+
+ return false;
+}
+
+LayoutDeviceIntSize nsNativeThemeWin::GetMinimumWidgetSize(
+ nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
+ return Theme::GetMinimumWidgetSize(aPresContext, aFrame, aAppearance);
+ }
+
+ mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aAppearance);
+ HTHEME theme = NULL;
+ if (!themeClass.isNothing()) {
+ theme = nsUXThemeData::GetTheme(themeClass.value());
+ }
+ if (!theme) {
+ auto result = ClassicGetMinimumWidgetSize(aFrame, aAppearance);
+ ScaleForFrameDPI(&result, aFrame);
+ return result;
+ }
+
+ switch (aAppearance) {
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Toolbox:
+ case StyleAppearance::Toolbar:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::Tabpanels:
+ case StyleAppearance::Tabpanel:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ return {}; // Don't worry about it.
+ default:
+ break;
+ }
+
+ // Call GetSystemMetrics to determine size for WinXP scrollbars
+ // (GetThemeSysSize API returns the optimal size for the theme, but
+ // Windows appears to always use metrics when drawing standard scrollbars)
+ THEMESIZE sizeReq = TS_TRUE; // Best-fit size
+ switch (aAppearance) {
+ case StyleAppearance::ProgressBar:
+ // Best-fit size for progress meters is too large for most
+ // themes. We want these widgets to be able to really shrink
+ // down, so use the min-size request value (of 0).
+ sizeReq = TS_MIN;
+ break;
+
+ case StyleAppearance::RangeThumb: {
+ LayoutDeviceIntSize result(12, 20);
+ if (!IsRangeHorizontal(aFrame)) {
+ std::swap(result.width, result.height);
+ }
+ ScaleForFrameDPI(&result, aFrame);
+ return result;
+ }
+
+ case StyleAppearance::Separator: {
+ // that's 2px left margin, 2px right margin and 2px separator
+ // (the margin is drawn as part of the separator, though)
+ LayoutDeviceIntSize result(6, 0);
+ ScaleForFrameDPI(&result, aFrame);
+ return result;
+ }
+
+ case StyleAppearance::Button:
+ // We should let HTML buttons shrink to their min size.
+ // FIXME bug 403934: We should probably really separate
+ // GetPreferredWidgetSize from GetMinimumWidgetSize, so callers can
+ // use the one they want.
+ if (aFrame->GetContent()->IsHTMLElement()) {
+ sizeReq = TS_MIN;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ int32_t part, state;
+ nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
+ if (NS_FAILED(rv)) {
+ return {};
+ }
+
+ LayoutDeviceIntSize result;
+ rv = GetCachedMinimumWidgetSize(aFrame, theme, themeClass.value(),
+ aAppearance, part, state, sizeReq, &result);
+ ScaleForFrameDPI(&result, aFrame);
+ return result;
+}
+
+NS_IMETHODIMP
+nsNativeThemeWin::WidgetStateChanged(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) {
+ // Some widget types just never change state.
+ if (aAppearance == StyleAppearance::Toolbox ||
+ aAppearance == StyleAppearance::Toolbar ||
+ aAppearance == StyleAppearance::Progresschunk ||
+ aAppearance == StyleAppearance::ProgressBar ||
+ aAppearance == StyleAppearance::Tabpanels ||
+ aAppearance == StyleAppearance::Tabpanel ||
+ aAppearance == StyleAppearance::Separator) {
+ *aShouldRepaint = false;
+ return NS_OK;
+ }
+
+ // We need to repaint the dropdown arrow in vista HTML combobox controls when
+ // the control is closed to get rid of the hover effect.
+ if ((aAppearance == StyleAppearance::Menulist ||
+ aAppearance == StyleAppearance::MenulistButton) &&
+ nsNativeTheme::IsHTMLContent(aFrame)) {
+ *aShouldRepaint = true;
+ return NS_OK;
+ }
+
+ // XXXdwh Not sure what can really be done here. Can at least guess for
+ // specific widgets that they're highly unlikely to have certain states.
+ // For example, a toolbar doesn't care about any states.
+ if (!aAttribute) {
+ // Hover/focus/active changed. Always repaint.
+ *aShouldRepaint = true;
+ } else {
+ // Check the attribute to see if it's relevant.
+ // disabled, checked, dlgtype, default, etc.
+ *aShouldRepaint = false;
+ if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
+ aAttribute == nsGkAtoms::selected ||
+ aAttribute == nsGkAtoms::visuallyselected ||
+ aAttribute == nsGkAtoms::readonly || aAttribute == nsGkAtoms::open ||
+ aAttribute == nsGkAtoms::menuactive || aAttribute == nsGkAtoms::focused)
+ *aShouldRepaint = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNativeThemeWin::ThemeChanged() {
+ nsUXThemeData::Invalidate();
+ memset(mBorderCacheValid, 0, sizeof(mBorderCacheValid));
+ memset(mMinimumWidgetSizeCacheValid, 0, sizeof(mMinimumWidgetSizeCacheValid));
+ mGutterSizeCacheValid = false;
+ return NS_OK;
+}
+
+bool nsNativeThemeWin::ThemeSupportsWidget(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ // XXXdwh We can go even further and call the API to ask if support exists for
+ // specific widgets.
+
+ if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
+ return Theme::ThemeSupportsWidget(aPresContext, aFrame, aAppearance);
+ }
+
+ HANDLE theme = GetTheme(aAppearance);
+ if (theme || ClassicThemeSupportsWidget(aFrame, aAppearance))
+ // turn off theming for some HTML widgets styled by the page
+ return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
+
+ return false;
+}
+
+bool nsNativeThemeWin::ThemeDrawsFocusForWidget(nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
+ return Theme::ThemeDrawsFocusForWidget(aFrame, aAppearance);
+ }
+ switch (aAppearance) {
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::NumberInput:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool nsNativeThemeWin::ThemeNeedsComboboxDropmarker() { return true; }
+
+nsITheme::Transparency nsNativeThemeWin::GetWidgetTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
+ return Theme::GetWidgetTransparency(aFrame, aAppearance);
+ }
+
+ switch (aAppearance) {
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::Range:
+ return eTransparent;
+ default:
+ break;
+ }
+
+ HANDLE theme = GetTheme(aAppearance);
+ // For the classic theme we don't really have a way of knowing
+ if (!theme) {
+ return eUnknownTransparency;
+ }
+
+ int32_t part, state;
+ nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
+ // Fail conservatively
+ NS_ENSURE_SUCCESS(rv, eUnknownTransparency);
+
+ if (part <= 0) {
+ // Not a real part code, so IsThemeBackgroundPartiallyTransparent may
+ // not work, so don't call it.
+ return eUnknownTransparency;
+ }
+
+ if (IsThemeBackgroundPartiallyTransparent(theme, part, state))
+ return eTransparent;
+ return eOpaque;
+}
+
+/* Windows 9x/NT/2000/Classic XP Theme Support */
+
+bool nsNativeThemeWin::ClassicThemeSupportsWidget(nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Range:
+ case StyleAppearance::RangeThumb:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::Tab:
+ case StyleAppearance::Tabpanel:
+ case StyleAppearance::Tabpanels:
+ return true;
+ default:
+ return false;
+ }
+}
+
+LayoutDeviceIntMargin nsNativeThemeWin::ClassicGetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
+ LayoutDeviceIntMargin result;
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ result.top = result.left = result.bottom = result.right = 2;
+ break;
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Tab:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ result.top = result.left = result.bottom = result.right = 2;
+ break;
+ case StyleAppearance::ProgressBar:
+ result.top = result.left = result.bottom = result.right = 1;
+ break;
+ default:
+ result.top = result.bottom = result.left = result.right = 0;
+ break;
+ }
+ return result;
+}
+
+bool nsNativeThemeWin::ClassicGetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) {
+ switch (aAppearance) {
+ case StyleAppearance::ProgressBar:
+ (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right =
+ 1;
+ return true;
+ default:
+ return false;
+ }
+}
+
+LayoutDeviceIntSize nsNativeThemeWin::ClassicGetMinimumWidgetSize(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ LayoutDeviceIntSize result;
+ switch (aAppearance) {
+ case StyleAppearance::RangeThumb: {
+ if (IsRangeHorizontal(aFrame)) {
+ result.width = 12;
+ result.height = 20;
+ } else {
+ result.width = 20;
+ result.height = 12;
+ }
+ break;
+ }
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Button:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Tab:
+ case StyleAppearance::Tabpanel:
+ case StyleAppearance::Tabpanels:
+ // no minimum widget size
+ break;
+
+ default:
+ break;
+ }
+ return result;
+}
+
+nsresult nsNativeThemeWin::ClassicGetThemePartAndState(
+ nsIFrame* aFrame, StyleAppearance aAppearance, int32_t& aPart,
+ int32_t& aState, bool& aFocused) {
+ aFocused = false;
+ switch (aAppearance) {
+ case StyleAppearance::Button: {
+ aPart = DFC_BUTTON;
+ aState = DFCS_BUTTONPUSH;
+ aFocused = false;
+
+ ElementState contentState = GetContentState(aFrame, aAppearance);
+ if (contentState.HasState(ElementState::DISABLED)) {
+ aState |= DFCS_INACTIVE;
+ } else if (IsOpenButton(aFrame)) {
+ aState |= DFCS_PUSHED;
+ } else if (IsCheckedButton(aFrame)) {
+ aState |= DFCS_CHECKED;
+ } else {
+ if (contentState.HasAllStates(ElementState::ACTIVE |
+ ElementState::HOVER)) {
+ aState |= DFCS_PUSHED;
+ // The down state is flat if the button is focusable
+ if (aFrame->StyleUI()->UserFocus() == StyleUserFocus::Normal) {
+ if (!aFrame->GetContent()->IsHTMLElement()) aState |= DFCS_FLAT;
+
+ aFocused = true;
+ }
+ }
+ // On Windows, focused buttons are always drawn as such by the native
+ // theme, that's why we check ElementState::FOCUS instead of
+ // ElementState::FOCUSRING.
+ if (contentState.HasState(ElementState::FOCUS) ||
+ (aState == DFCS_BUTTONPUSH && IsDefaultButton(aFrame))) {
+ aFocused = true;
+ }
+ }
+
+ return NS_OK;
+ }
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Range:
+ case StyleAppearance::RangeThumb:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Tab:
+ case StyleAppearance::Tabpanel:
+ case StyleAppearance::Tabpanels:
+ // these don't use DrawFrameControl
+ return NS_OK;
+ default:
+ return NS_ERROR_FAILURE;
+ }
+}
+
+// Draw classic Windows tab
+// (no system API for this, but DrawEdge can draw all the parts of a tab)
+static void DrawTab(HDC hdc, const RECT& R, int32_t aPosition, bool aSelected,
+ bool aDrawLeft, bool aDrawRight) {
+ int32_t leftFlag, topFlag, rightFlag, lightFlag, shadeFlag;
+ RECT topRect, sideRect, bottomRect, lightRect, shadeRect;
+ int32_t selectedOffset, lOffset, rOffset;
+
+ selectedOffset = aSelected ? 1 : 0;
+ lOffset = aDrawLeft ? 2 : 0;
+ rOffset = aDrawRight ? 2 : 0;
+
+ // Get info for tab orientation/position (Left, Top, Right, Bottom)
+ switch (aPosition) {
+ case BF_LEFT:
+ leftFlag = BF_TOP;
+ topFlag = BF_LEFT;
+ rightFlag = BF_BOTTOM;
+ lightFlag = BF_DIAGONAL_ENDTOPRIGHT;
+ shadeFlag = BF_DIAGONAL_ENDBOTTOMRIGHT;
+
+ ::SetRect(&topRect, R.left, R.top + lOffset, R.right, R.bottom - rOffset);
+ ::SetRect(&sideRect, R.left + 2, R.top, R.right - 2 + selectedOffset,
+ R.bottom);
+ ::SetRect(&bottomRect, R.right - 2, R.top, R.right, R.bottom);
+ ::SetRect(&lightRect, R.left, R.top, R.left + 3, R.top + 3);
+ ::SetRect(&shadeRect, R.left + 1, R.bottom - 2, R.left + 2, R.bottom - 1);
+ break;
+ case BF_TOP:
+ leftFlag = BF_LEFT;
+ topFlag = BF_TOP;
+ rightFlag = BF_RIGHT;
+ lightFlag = BF_DIAGONAL_ENDTOPRIGHT;
+ shadeFlag = BF_DIAGONAL_ENDBOTTOMRIGHT;
+
+ ::SetRect(&topRect, R.left + lOffset, R.top, R.right - rOffset, R.bottom);
+ ::SetRect(&sideRect, R.left, R.top + 2, R.right,
+ R.bottom - 1 + selectedOffset);
+ ::SetRect(&bottomRect, R.left, R.bottom - 1, R.right, R.bottom);
+ ::SetRect(&lightRect, R.left, R.top, R.left + 3, R.top + 3);
+ ::SetRect(&shadeRect, R.right - 2, R.top + 1, R.right - 1, R.top + 2);
+ break;
+ case BF_RIGHT:
+ leftFlag = BF_TOP;
+ topFlag = BF_RIGHT;
+ rightFlag = BF_BOTTOM;
+ lightFlag = BF_DIAGONAL_ENDTOPLEFT;
+ shadeFlag = BF_DIAGONAL_ENDBOTTOMLEFT;
+
+ ::SetRect(&topRect, R.left, R.top + lOffset, R.right, R.bottom - rOffset);
+ ::SetRect(&sideRect, R.left + 2 - selectedOffset, R.top, R.right - 2,
+ R.bottom);
+ ::SetRect(&bottomRect, R.left, R.top, R.left + 2, R.bottom);
+ ::SetRect(&lightRect, R.right - 3, R.top, R.right - 1, R.top + 2);
+ ::SetRect(&shadeRect, R.right - 2, R.bottom - 3, R.right, R.bottom - 1);
+ break;
+ case BF_BOTTOM:
+ leftFlag = BF_LEFT;
+ topFlag = BF_BOTTOM;
+ rightFlag = BF_RIGHT;
+ lightFlag = BF_DIAGONAL_ENDTOPLEFT;
+ shadeFlag = BF_DIAGONAL_ENDBOTTOMLEFT;
+
+ ::SetRect(&topRect, R.left + lOffset, R.top, R.right - rOffset, R.bottom);
+ ::SetRect(&sideRect, R.left, R.top + 2 - selectedOffset, R.right,
+ R.bottom - 2);
+ ::SetRect(&bottomRect, R.left, R.top, R.right, R.top + 2);
+ ::SetRect(&lightRect, R.left, R.bottom - 3, R.left + 2, R.bottom - 1);
+ ::SetRect(&shadeRect, R.right - 2, R.bottom - 3, R.right, R.bottom - 1);
+ break;
+ default:
+ MOZ_CRASH();
+ }
+
+ // Background
+ ::FillRect(hdc, &R, (HBRUSH)(COLOR_3DFACE + 1));
+
+ // Tab "Top"
+ ::DrawEdge(hdc, &topRect, EDGE_RAISED, BF_SOFT | topFlag);
+
+ // Tab "Bottom"
+ if (!aSelected) ::DrawEdge(hdc, &bottomRect, EDGE_RAISED, BF_SOFT | topFlag);
+
+ // Tab "Sides"
+ if (!aDrawLeft) leftFlag = 0;
+ if (!aDrawRight) rightFlag = 0;
+ ::DrawEdge(hdc, &sideRect, EDGE_RAISED, BF_SOFT | leftFlag | rightFlag);
+
+ // Tab Diagonal Corners
+ if (aDrawLeft) ::DrawEdge(hdc, &lightRect, EDGE_RAISED, BF_SOFT | lightFlag);
+
+ if (aDrawRight) ::DrawEdge(hdc, &shadeRect, EDGE_RAISED, BF_SOFT | shadeFlag);
+}
+
+void nsNativeThemeWin::DrawCheckedRect(HDC hdc, const RECT& rc, int32_t fore,
+ int32_t back, HBRUSH defaultBack) {
+ static WORD patBits[8] = {0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55};
+
+ HBITMAP patBmp = ::CreateBitmap(8, 8, 1, 1, patBits);
+ if (patBmp) {
+ HBRUSH brush = (HBRUSH)::CreatePatternBrush(patBmp);
+ if (brush) {
+ COLORREF oldForeColor = ::SetTextColor(hdc, ::GetSysColor(fore));
+ COLORREF oldBackColor = ::SetBkColor(hdc, ::GetSysColor(back));
+ POINT vpOrg;
+
+ ::UnrealizeObject(brush);
+ ::GetViewportOrgEx(hdc, &vpOrg);
+ ::SetBrushOrgEx(hdc, vpOrg.x + rc.left, vpOrg.y + rc.top, nullptr);
+ HBRUSH oldBrush = (HBRUSH)::SelectObject(hdc, brush);
+ ::FillRect(hdc, &rc, brush);
+ ::SetTextColor(hdc, oldForeColor);
+ ::SetBkColor(hdc, oldBackColor);
+ ::SelectObject(hdc, oldBrush);
+ ::DeleteObject(brush);
+ } else
+ ::FillRect(hdc, &rc, defaultBack);
+
+ ::DeleteObject(patBmp);
+ }
+}
+
+nsresult nsNativeThemeWin::ClassicDrawWidgetBackground(
+ gfxContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance,
+ const nsRect& aRect, const nsRect& aDirtyRect) {
+ int32_t part, state;
+ bool focused;
+ nsresult rv;
+ rv = ClassicGetThemePartAndState(aFrame, aAppearance, part, state, focused);
+ if (NS_FAILED(rv)) return rv;
+
+ if (AssumeThemePartAndStateAreTransparent(part, state)) {
+ return NS_OK;
+ }
+
+ gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel());
+ RECT widgetRect;
+ gfxRect tr(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()),
+ dr(aDirtyRect.X(), aDirtyRect.Y(), aDirtyRect.Width(),
+ aDirtyRect.Height());
+
+ tr.Scale(1.0 / p2a);
+ dr.Scale(1.0 / p2a);
+
+ gfxWindowsNativeDrawing nativeDrawing(
+ aContext, dr, GetWidgetNativeDrawingFlags(aAppearance));
+
+RENDER_AGAIN:
+
+ HDC hdc = nativeDrawing.BeginNativeDrawing();
+ if (!hdc) return NS_ERROR_FAILURE;
+
+ nativeDrawing.TransformToNativeRect(tr, widgetRect);
+
+ rv = NS_OK;
+ switch (aAppearance) {
+ // Draw button
+ case StyleAppearance::Button: {
+ if (focused) {
+ // draw dark button focus border first
+ if (HBRUSH brush = ::GetSysColorBrush(COLOR_3DDKSHADOW)) {
+ ::FrameRect(hdc, &widgetRect, brush);
+ }
+ InflateRect(&widgetRect, -1, -1);
+ }
+ // setup DC to make DrawFrameControl draw correctly
+ int32_t oldTA = ::SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
+ ::DrawFrameControl(hdc, &widgetRect, part, state);
+ ::SetTextAlign(hdc, oldTA);
+ break;
+ }
+ // Draw controls with 2px 3D inset border
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton: {
+ // Draw inset edge
+ ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+
+ // Fill in background
+
+ if (elementState.HasState(ElementState::DISABLED) ||
+ (aFrame->GetContent()->IsXULElement() && IsReadOnly(aFrame)))
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1));
+ else
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_WINDOW + 1));
+
+ break;
+ }
+ case StyleAppearance::Treeview: {
+ // Draw inset edge
+ ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+
+ // Fill in window color background
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_WINDOW + 1));
+
+ break;
+ }
+ // Draw 3D face background controls
+ case StyleAppearance::ProgressBar:
+ // Draw 3D border
+ ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE);
+ InflateRect(&widgetRect, -1, -1);
+ [[fallthrough]];
+ case StyleAppearance::Tabpanel: {
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1));
+ break;
+ }
+ case StyleAppearance::RangeThumb: {
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+
+ ::DrawEdge(hdc, &widgetRect, EDGE_RAISED,
+ BF_RECT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
+ if (elementState.HasState(ElementState::DISABLED)) {
+ DrawCheckedRect(hdc, widgetRect, COLOR_3DFACE, COLOR_3DHILIGHT,
+ (HBRUSH)COLOR_3DHILIGHT);
+ }
+
+ break;
+ }
+ // Draw scale track background
+ case StyleAppearance::Range: {
+ const int32_t trackWidth = 4;
+ // When rounding is necessary, we round the position of the track
+ // away from the chevron of the thumb to make it look better.
+ if (IsRangeHorizontal(aFrame)) {
+ widgetRect.top += (widgetRect.bottom - widgetRect.top - trackWidth) / 2;
+ widgetRect.bottom = widgetRect.top + trackWidth;
+ } else {
+ if (!IsFrameRTL(aFrame)) {
+ widgetRect.left +=
+ (widgetRect.right - widgetRect.left - trackWidth) / 2;
+ widgetRect.right = widgetRect.left + trackWidth;
+ } else {
+ widgetRect.right -=
+ (widgetRect.right - widgetRect.left - trackWidth) / 2;
+ widgetRect.left = widgetRect.right - trackWidth;
+ }
+ }
+
+ ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+ ::FillRect(hdc, &widgetRect, (HBRUSH)GetStockObject(GRAY_BRUSH));
+
+ break;
+ }
+ case StyleAppearance::Progresschunk: {
+ nsIFrame* stateFrame = aFrame->GetParent();
+ ElementState elementState = GetContentState(stateFrame, aAppearance);
+
+ const bool indeterminate =
+ elementState.HasState(ElementState::INDETERMINATE);
+ bool vertical = IsVerticalProgress(stateFrame);
+
+ nsIContent* content = aFrame->GetContent();
+ if (!indeterminate || !content) {
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_HIGHLIGHT + 1));
+ break;
+ }
+
+ RECT overlayRect = CalculateProgressOverlayRect(
+ aFrame, &widgetRect, vertical, indeterminate, true);
+
+ ::FillRect(hdc, &overlayRect, (HBRUSH)(COLOR_HIGHLIGHT + 1));
+
+ if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
+ NS_WARNING("unable to animate progress widget!");
+ }
+ break;
+ }
+
+ // Draw Tab
+ case StyleAppearance::Tab: {
+ DrawTab(hdc, widgetRect, IsBottomTab(aFrame) ? BF_BOTTOM : BF_TOP,
+ IsSelectedTab(aFrame), !IsRightToSelectedTab(aFrame),
+ !IsLeftToSelectedTab(aFrame));
+
+ break;
+ }
+ case StyleAppearance::Tabpanels:
+ ::DrawEdge(hdc, &widgetRect, EDGE_RAISED,
+ BF_SOFT | BF_MIDDLE | BF_LEFT | BF_RIGHT | BF_BOTTOM);
+
+ break;
+
+ default:
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+
+ nativeDrawing.EndNativeDrawing();
+
+ if (NS_FAILED(rv)) return rv;
+
+ if (nativeDrawing.ShouldRenderAgain()) goto RENDER_AGAIN;
+
+ nativeDrawing.PaintToContext();
+
+ return rv;
+}
+
+uint32_t nsNativeThemeWin::GetWidgetNativeDrawingFlags(
+ StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ return gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
+ gfxWindowsNativeDrawing::CAN_AXIS_ALIGNED_SCALE |
+ gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
+
+ // need to check these others
+ default:
+ return gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
+ gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE |
+ gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
+ }
+}
+
+} // namespace mozilla::widget
+
+///////////////////////////////////////////
+// Creation Routine
+///////////////////////////////////////////
+
+already_AddRefed<Theme> do_CreateNativeThemeDoNotUseDirectly() {
+ return do_AddRef(new nsNativeThemeWin());
+}