diff --git a/change/react-native-windows-580ee5e2-c630-4ce8-a1a3-14ee5a18a571.json b/change/react-native-windows-580ee5e2-c630-4ce8-a1a3-14ee5a18a571.json new file mode 100644 index 00000000000..30192ff94ee --- /dev/null +++ b/change/react-native-windows-580ee5e2-c630-4ce8-a1a3-14ee5a18a571.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Fix Modal title bar contrast when the native window is inactive", + "packageName": "react-native-windows", + "email": "anuagra@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp index 5770ab3422d..5207a7c73b8 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp @@ -15,6 +15,22 @@ namespace winrt::Microsoft::ReactNative::Composition::implementation { +static winrt::Windows::UI::Color CompositeOverOpaqueBackground( + const winrt::Windows::UI::Color &foreground, + const winrt::Windows::UI::Color &background) noexcept { + const uint32_t alpha = foreground.A; + const uint32_t inverseAlpha = 0xFF - alpha; + const auto compositeChannel = [alpha, inverseAlpha](uint8_t foregroundChannel, uint8_t backgroundChannel) { + return static_cast((foregroundChannel * alpha + backgroundChannel * inverseAlpha + 0x7F) / 0xFF); + }; + + return { + 0xFF, + compositeChannel(foreground.R, background.R), + compositeChannel(foreground.G, background.G), + compositeChannel(foreground.B, background.B)}; +} + struct ModalHostState : winrt::implements { ModalHostState(winrt::Microsoft::ReactNative::LayoutConstraints layoutConstraints, float scaleFactor) @@ -36,6 +52,8 @@ struct ModalHostState struct ModalHostView : public winrt::implements, ::Microsoft::ReactNativeSpecs::BaseModalHostView { ~ModalHostView() { + UnsubscribeFromThemeChanges(); + if (m_popUp) { // Unregister closing event handler if (m_appWindowClosingToken) { @@ -171,6 +189,7 @@ struct ModalHostView : public winrt::implements(); + m_themeSource = winrt::make_weak(themeSource); + m_theme = themeSource.Theme(); + const auto themeSubscriptionGeneration = m_themeSubscriptionGeneration; + m_themeChangedToken = themeSource.ThemeChanged([wkThis = get_weak(), themeSubscriptionGeneration]( + const winrt::Windows::Foundation::IInspectable &sender, + const winrt::Windows::Foundation::IInspectable & /*args*/) { + auto theme = sender.as().Theme(); + if (auto strongThis = wkThis.get()) { + strongThis->m_reactContext.UIDispatcher().Post([wkThis, theme, themeSubscriptionGeneration]() { + if (auto strongThis = wkThis.get()) { + if (strongThis->m_themeSubscriptionGeneration != themeSubscriptionGeneration) { + return; + } + strongThis->m_theme = theme; + strongThis->UpdateTitleBarColors(); + } + }); + } + }); + } + + void UnsubscribeFromThemeChanges() noexcept { + if (m_themeChangedToken) { + if (auto themeSource = m_themeSource.get()) { + themeSource.ThemeChanged(m_themeChangedToken); + } + m_themeChangedToken = {}; + } + m_themeSource = {}; + m_theme = nullptr; + ++m_themeSubscriptionGeneration; + } + void AdjustWindowSize(const winrt::Microsoft::ReactNative::LayoutMetrics &layoutMetrics) noexcept { if (!m_rnWindow) { return; @@ -318,6 +375,52 @@ struct ModalHostView : public winrt::implementshideTitleBar.value_or(false) || + !winrt::Microsoft::UI::Windowing::AppWindowTitleBar::IsCustomizationSupported()) { + return; + } + + winrt::Windows::UI::Color background; + winrt::Windows::UI::Color activeForeground; + winrt::Windows::UI::Color inactiveForeground; + winrt::Windows::UI::Color buttonHoverBackground; + winrt::Windows::UI::Color buttonPressedBackground; + winrt::Windows::UI::Color buttonPressedForeground; + if (!m_theme.TryGetPlatformColor(L"SolidBackgroundFillColorBase", background) || + !m_theme.TryGetPlatformColor(L"TextFillColorPrimary", activeForeground) || + !m_theme.TryGetPlatformColor(L"TextFillColorSecondary", inactiveForeground) || + !m_theme.TryGetPlatformColor(L"ControlFillColorSecondary", buttonHoverBackground) || + !m_theme.TryGetPlatformColor(L"ControlFillColorTertiary", buttonPressedBackground) || + !m_theme.TryGetPlatformColor(L"ButtonForegroundPressed", buttonPressedForeground)) { + return; + } + + // AppWindowTitleBar ignores alpha, so resolve RNW's translucent semantic text colors to opaque colors first. + background.A = 0xFF; + activeForeground = CompositeOverOpaqueBackground(activeForeground, background); + inactiveForeground = CompositeOverOpaqueBackground(inactiveForeground, background); + buttonHoverBackground = CompositeOverOpaqueBackground(buttonHoverBackground, background); + buttonPressedBackground = CompositeOverOpaqueBackground(buttonPressedBackground, background); + buttonPressedForeground = CompositeOverOpaqueBackground(buttonPressedForeground, buttonPressedBackground); + + auto titleBar = m_rnWindow.AppWindow().TitleBar(); + titleBar.ForegroundColor(activeForeground); + titleBar.BackgroundColor(background); + titleBar.InactiveForegroundColor(inactiveForeground); + titleBar.InactiveBackgroundColor(background); + titleBar.ButtonForegroundColor(activeForeground); + titleBar.ButtonBackgroundColor(background); + titleBar.ButtonHoverForegroundColor(activeForeground); + titleBar.ButtonHoverBackgroundColor(buttonHoverBackground); + titleBar.ButtonPressedForegroundColor(buttonPressedForeground); + titleBar.ButtonPressedBackgroundColor(buttonPressedBackground); + titleBar.ButtonInactiveForegroundColor(inactiveForeground); + titleBar.ButtonInactiveBackgroundColor(background); } // creates a new modal window @@ -453,6 +556,10 @@ struct ModalHostView : public winrt::implements m_themeSource; + winrt::event_token m_themeChangedToken; + uint64_t m_themeSubscriptionGeneration{0}; + winrt::Microsoft::ReactNative::Composition::Theme m_theme{nullptr}; winrt::com_ptr<::Microsoft::ReactNativeSpecs::ModalHostViewProps> m_localProps{nullptr}; };