From 1a56e26765f8f73a348c0c8235b41ddd23c01ceb Mon Sep 17 00:00:00 2001 From: goodusername123 <61405538+goodusername123@users.noreply.github.com> Date: Sun, 10 Dec 2023 04:01:57 -0600 Subject: [PATCH] Windows: Restore support for native scrollbars. Restores support for natively styled scrollbars when `widget.non-native-theme.enabled` is disabled. patch sourced from: https://github.com/ephemeralViolette/firefox-native-controls --- widget/windows/nsNativeThemeWin.cpp | 267 +++++++++++++++++++++++++++- widget/windows/nsUXThemeData.cpp | 2 + widget/windows/nsUXThemeData.h | 1 + 3 files changed, 267 insertions(+), 3 deletions(-) diff --git a/widget/windows/nsNativeThemeWin.cpp b/widget/windows/nsNativeThemeWin.cpp index 7b54e92bb4eb2..1e259caec16fa 100644 --- a/widget/windows/nsNativeThemeWin.cpp +++ b/widget/windows/nsNativeThemeWin.cpp @@ -40,6 +40,7 @@ #include "nsWindow.h" #include "prinrval.h" #include "WinUtils.h" +#include "ScrollbarDrawingWin.h" using namespace mozilla; using namespace mozilla::gfx; @@ -68,8 +69,11 @@ nsNativeThemeWin::~nsNativeThemeWin() { nsUXThemeData::Invalidate(); } auto nsNativeThemeWin::IsWidgetNonNative(nsIFrame* aFrame, StyleAppearance aAppearance) -> NonNative { - if (IsWidgetScrollbarPart(aAppearance) || - aAppearance == StyleAppearance::FocusOutline) { + if (IsWidgetScrollbarPart(aAppearance)) { + return NonNative::No; + } + + if (aAppearance == StyleAppearance::FocusOutline) { return NonNative::Always; } @@ -718,6 +722,16 @@ mozilla::Maybe nsNativeThemeWin::GetThemeClass( case StyleAppearance::Tabpanel: case StyleAppearance::Tabpanels: return Some(eUXTab); + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: + case StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::ScrollbarthumbHorizontal: + case StyleAppearance::Scrollcorner: + return Some(eUXScrollbar); case StyleAppearance::Range: case StyleAppearance::RangeThumb: return Some(eUXTrackbar); @@ -967,6 +981,66 @@ nsresult nsNativeThemeWin::GetThemePartAndState(nsIFrame* aFrame, aState = TS_NORMAL; return NS_OK; } + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: { + aPart = SP_BUTTON; + aState = (int(aAppearance) - int(StyleAppearance::ScrollbarbuttonUp)) * 4; + ElementState eventState = GetContentState(aFrame, aAppearance); + if (!aFrame) + aState += TS_NORMAL; + else if (eventState.HasState(ElementState::DISABLED)) + aState += TS_DISABLED; + else { + nsIFrame* parent = aFrame->GetParent(); + ElementState parentState = GetContentState( + parent, parent->StyleDisplay()->EffectiveAppearance()); + if (eventState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE)) + aState += TS_ACTIVE; + else if (eventState.HasState(ElementState::HOVER)) + aState += TS_HOVER; + else if (parentState.HasState(ElementState::HOVER)) + aState = + (int(aAppearance) - int(StyleAppearance::ScrollbarbuttonUp)) + + SP_BUTTON_IMPLICIT_HOVER_BASE; + else + aState += TS_NORMAL; + } + return NS_OK; + } + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::ScrollbarVertical: { + aPart = (aAppearance == StyleAppearance::ScrollbarHorizontal) + ? SP_TRACKSTARTHOR + : SP_TRACKSTARTVERT; + aState = TS_NORMAL; + return NS_OK; + } + case StyleAppearance::ScrollbarthumbHorizontal: + case StyleAppearance::ScrollbarthumbVertical: { + aPart = (aAppearance == StyleAppearance::ScrollbarthumbHorizontal) + ? SP_THUMBHOR + : SP_THUMBVERT; + ElementState eventState = GetContentState(aFrame, aAppearance); + if (!aFrame) + aState = TS_NORMAL; + else if (eventState.HasState(ElementState::DISABLED)) + aState = TS_DISABLED; + else { + if (eventState.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 (eventState.HasState(ElementState::HOVER)) + aState = TS_HOVER; + else + aState = TS_NORMAL; + } + return NS_OK; + } case StyleAppearance::Range: { if (IsRangeHorizontal(aFrame)) { aPart = TKP_TRACK; @@ -1004,6 +1078,11 @@ nsresult nsNativeThemeWin::GetThemePartAndState(nsIFrame* aFrame, } return NS_OK; } + case StyleAppearance::Scrollcorner: { + aState = 0; + aPart = RP_BACKGROUND; + return NS_OK; + } case StyleAppearance::SpinnerUpbutton: case StyleAppearance::SpinnerDownbutton: { aPart = (aAppearance == StyleAppearance::SpinnerUpbutton) ? SPNP_UP @@ -1696,6 +1775,26 @@ nsNativeThemeWin::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame, widgetRect.bottom = widgetRect.top + TB_SEPARATOR_HEIGHT; DrawThemeEdge(theme, hdc, RP_BAND, 0, &widgetRect, EDGE_ETCHED, BF_TOP, nullptr); + } else if (aAppearance == StyleAppearance::ScrollbarthumbHorizontal || + aAppearance == StyleAppearance::ScrollbarthumbVertical) { + // Draw the decorative gripper for the scrollbar thumb button, if it fits + + SIZE gripSize; + MARGINS thumbMgns; + int gripPart = (aAppearance == StyleAppearance::ScrollbarthumbHorizontal) + ? SP_GRIPPERHOR + : SP_GRIPPERVERT; + + if (GetThemePartSize(theme, hdc, gripPart, state, nullptr, TS_TRUE, + &gripSize) == S_OK && + GetThemeMargins(theme, hdc, part, state, TMT_CONTENTMARGINS, nullptr, + &thumbMgns) == S_OK && + gripSize.cx + thumbMgns.cxLeftWidth + thumbMgns.cxRightWidth <= + widgetRect.right - widgetRect.left && + gripSize.cy + thumbMgns.cyTopHeight + thumbMgns.cyBottomHeight <= + widgetRect.bottom - widgetRect.top) { + DrawThemeBackground(theme, hdc, gripPart, state, &widgetRect, &clipRect); + } } nativeDrawing.EndNativeDrawing(); @@ -1745,7 +1844,11 @@ LayoutDeviceIntMargin nsNativeThemeWin::GetWidgetBorder( if (!themeClass.isNothing()) { theme = nsUXThemeData::GetTheme(themeClass.value()); } - if (!theme) { + + // Classic scrollbar thumbs require classic borders. The theme procedure will + // break horizontal scrollbar thumbs otherwise. + if (aAppearance == StyleAppearance::ScrollbarthumbVertical || + aAppearance == StyleAppearance::ScrollbarthumbHorizontal || !theme) { result = ClassicGetWidgetBorder(aContext, aFrame, aAppearance); ScaleForFrameDPI(&result, aFrame); return result; @@ -1757,6 +1860,9 @@ LayoutDeviceIntMargin nsNativeThemeWin::GetWidgetBorder( aAppearance == StyleAppearance::MozWinCommunicationsToolbox || aAppearance == StyleAppearance::MozWinBrowsertabbarToolbox || aAppearance == StyleAppearance::Tabpanel || + aAppearance == StyleAppearance::ScrollbarHorizontal || + aAppearance == StyleAppearance::ScrollbarVertical || + aAppearance == StyleAppearance::Scrollcorner || aAppearance == StyleAppearance::Menuitem || aAppearance == StyleAppearance::Checkmenuitem || aAppearance == StyleAppearance::Radiomenuitem || @@ -2051,6 +2157,14 @@ LayoutDeviceIntSize nsNativeThemeWin::GetMinimumWidgetSize( // Windows appears to always use metrics when drawing standard scrollbars) THEMESIZE sizeReq = TS_TRUE; // Best-fit size switch (aAppearance) { + case StyleAppearance::ScrollbarthumbHorizontal: + case StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::ScrollbarVertical: case StyleAppearance::MozMenulistArrowButton: { auto result = ClassicGetMinimumWidgetSize(aFrame, aAppearance); ScaleForFrameDPI(&result, aFrame); @@ -2095,6 +2209,17 @@ LayoutDeviceIntSize nsNativeThemeWin::GetMinimumWidgetSize( return result; } + case StyleAppearance::Scrollcorner: { + if (nsLookAndFeel::GetInt(nsLookAndFeel::IntID::UseOverlayScrollbars) != + 0) { + LayoutDeviceIntSize result(::GetSystemMetrics(SM_CXHSCROLL), + ::GetSystemMetrics(SM_CYVSCROLL)); + ScaleForFrameDPI(&result, aFrame); + return result; + } + break; + } + case StyleAppearance::Separator: { // that's 2px left margin, 2px right margin and 2px separator // (the margin is drawn as part of the separator, though) @@ -2400,6 +2525,15 @@ bool nsNativeThemeWin::ClassicThemeSupportsWidget(nsIFrame* aFrame, case StyleAppearance::Range: case StyleAppearance::RangeThumb: case StyleAppearance::Groupbox: + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: + case StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::ScrollbarthumbHorizontal: + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::Scrollcorner: case StyleAppearance::Menulist: case StyleAppearance::MenulistButton: case StyleAppearance::MozMenulistArrowButton: @@ -2526,6 +2660,25 @@ LayoutDeviceIntSize nsNativeThemeWin::ClassicGetMinimumWidgetSize( result.width = ::GetSystemMetrics(SM_CXVSCROLL); result.height = 8; // No good metrics available for this break; + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + result.width = ::GetSystemMetrics(SM_CXVSCROLL); + result.height = ::GetSystemMetrics(SM_CYVSCROLL); + break; + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: + // For scrollbar-width:thin, we don't display the buttons. + if (!ScrollbarDrawing::IsScrollbarWidthThin(aFrame)) { + result.width = ::GetSystemMetrics(SM_CXHSCROLL); + result.height = ::GetSystemMetrics(SM_CYHSCROLL); + } + break; + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::ScrollbarHorizontal: + // Sizing code needed after removal of XUL layout (around ESR 115) + result.width = ::GetSystemMetrics(SM_CYHSCROLL); + result.height = ::GetSystemMetrics(SM_CYHSCROLL); + break; case StyleAppearance::RangeThumb: { if (IsRangeHorizontal(aFrame)) { result.width = 12; @@ -2536,6 +2689,35 @@ LayoutDeviceIntSize nsNativeThemeWin::ClassicGetMinimumWidgetSize( } break; } + case StyleAppearance::ScrollbarthumbVertical: + result.width = ::GetSystemMetrics(SM_CXVSCROLL); + result.height = ::GetSystemMetrics(SM_CYVTHUMB); + // Without theming, divide the thumb size by two in order to look more + // native + if (!GetTheme(aAppearance)) { + result.height >>= 1; + } + // If scrollbar-width is thin, divide the thickness by two to make + // it look more compact. + if (ScrollbarDrawing::IsScrollbarWidthThin(aFrame)) { + result.width >>= 1; + } + break; + case StyleAppearance::ScrollbarthumbHorizontal: + result.width = ::GetSystemMetrics(SM_CXHTHUMB); + result.height = ::GetSystemMetrics(SM_CYHSCROLL); + // Without theming, divide the thumb size by two in order to look more + // native + if (TRUE || !GetTheme(aAppearance)) { + result.width >>= 1; + } + // If scrollbar-width is thin, divide the thickness by two to make + // it look more compact. + if (ScrollbarDrawing::IsScrollbarWidthThin(aFrame)) { + result.height >>= 1; + } + + break; case StyleAppearance::MozMenulistArrowButton: result.width = ::GetSystemMetrics(SM_CXVSCROLL); break; @@ -2732,6 +2914,11 @@ nsresult nsNativeThemeWin::ClassicGetThemePartAndState( case StyleAppearance::MenulistButton: case StyleAppearance::Range: case StyleAppearance::RangeThumb: + case StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::ScrollbarthumbHorizontal: + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::Scrollcorner: case StyleAppearance::Progresschunk: case StyleAppearance::ProgressBar: case StyleAppearance::Tab: @@ -2777,6 +2964,39 @@ nsresult nsNativeThemeWin::ClassicGetThemePartAndState( return NS_OK; } + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: { + ElementState contentState = GetContentState(aFrame, aAppearance); + + aPart = DFC_SCROLL; + switch (aAppearance) { + case StyleAppearance::ScrollbarbuttonUp: + aState = DFCS_SCROLLUP; + break; + case StyleAppearance::ScrollbarbuttonDown: + aState = DFCS_SCROLLDOWN; + break; + case StyleAppearance::ScrollbarbuttonLeft: + aState = DFCS_SCROLLLEFT; + break; + case StyleAppearance::ScrollbarbuttonRight: + aState = DFCS_SCROLLRIGHT; + break; + default: + break; + } + + if (contentState.HasState(ElementState::DISABLED)) { + aState |= DFCS_INACTIVE; + } else if (contentState.HasAllStates(ElementState::HOVER | + ElementState::ACTIVE)) { + aState |= DFCS_PUSHED | DFCS_FLAT; + } + + return NS_OK; + } case StyleAppearance::SpinnerUpbutton: case StyleAppearance::SpinnerDownbutton: { ElementState contentState = GetContentState(aFrame, aAppearance); @@ -3055,6 +3275,10 @@ nsresult nsNativeThemeWin::ClassicDrawWidgetBackground( // Draw controls supported by DrawFrameControl case StyleAppearance::Checkbox: case StyleAppearance::Radio: + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: case StyleAppearance::SpinnerUpbutton: case StyleAppearance::SpinnerDownbutton: case StyleAppearance::MozMenulistArrowButton: { @@ -3110,6 +3334,12 @@ nsresult nsNativeThemeWin::ClassicDrawWidgetBackground( ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1)); break; } + // Draw scrollbar thumb + case StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::ScrollbarthumbHorizontal: + ::DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RECT | BF_MIDDLE); + + break; case StyleAppearance::RangeThumb: { ElementState elementState = GetContentState(aFrame, aAppearance); @@ -3147,6 +3377,37 @@ nsresult nsNativeThemeWin::ClassicDrawWidgetBackground( break; } + // Draw scrollbar track background + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::ScrollbarHorizontal: { + // Windows fills in the scrollbar track differently + // depending on whether these are equal + DWORD color3D, colorScrollbar, colorWindow; + + color3D = ::GetSysColor(COLOR_3DFACE); + colorWindow = ::GetSysColor(COLOR_WINDOW); + colorScrollbar = ::GetSysColor(COLOR_SCROLLBAR); + + if ((color3D != colorScrollbar) && (colorWindow != colorScrollbar)) + // Use solid brush + ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_SCROLLBAR + 1)); + else { + DrawCheckedRect(hdc, widgetRect, COLOR_3DHILIGHT, COLOR_3DFACE, + (HBRUSH)COLOR_SCROLLBAR + 1); + } + // XXX should invert the part of the track being clicked here + // but the track is never :active + + break; + } + case StyleAppearance::Scrollcorner: { + ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_SCROLLBAR + 1)); + + // Mozilla added this in 2018 + // (https://github.com/mozilla/gecko-dev/blob/7038d5f94456dcb558f7c7f6fe66d913070001c5/widget/windows/nsNativeThemeWin.cpp#L3793-L3795) + // and never fixed this fallthrough. + break; + } case StyleAppearance::Progresschunk: { nsIFrame* stateFrame = aFrame->GetParent(); ElementState elementState = GetContentState(stateFrame, aAppearance); diff --git a/widget/windows/nsUXThemeData.cpp b/widget/windows/nsUXThemeData.cpp index fe4061efb1787..52d0413f05ffd 100644 --- a/widget/windows/nsUXThemeData.cpp +++ b/widget/windows/nsUXThemeData.cpp @@ -94,6 +94,8 @@ const wchar_t* nsUXThemeData::GetClassName(nsUXThemeClass cls) { return L"Communications::Rebar"; case eUXBrowserTabBarRebar: return L"BrowserTabBar::Rebar"; + case eUXScrollbar: + return L"Scrollbar"; case eUXToolbar: return L"Toolbar"; case eUXMediaToolbar: diff --git a/widget/windows/nsUXThemeData.h b/widget/windows/nsUXThemeData.h index 33ab0354b767f..d2853e84c4699 100644 --- a/widget/windows/nsUXThemeData.h +++ b/widget/windows/nsUXThemeData.h @@ -28,6 +28,7 @@ enum nsUXThemeClass { eUXCommunicationsToolbar, eUXProgress, eUXTab, + eUXScrollbar, eUXTrackbar, eUXSpin, eUXCombobox,