From 99659793cd1760559407c4841fdf0618571ed665 Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Thu, 29 Jan 2026 15:31:11 +0530 Subject: [PATCH 1/3] adding fix for shadow in rounded box --- .../CompositionSwitcher.idl | 25 ++++-- .../Composition/CompositionContextHelper.cpp | 22 ++++- .../CompositionViewComponentView.cpp | 90 +++++++++++++++++-- 3 files changed, 120 insertions(+), 17 deletions(-) diff --git a/vnext/Microsoft.ReactNative/CompositionSwitcher.idl b/vnext/Microsoft.ReactNative/CompositionSwitcher.idl index a0946aacd98..d233a265786 100644 --- a/vnext/Microsoft.ReactNative/CompositionSwitcher.idl +++ b/vnext/Microsoft.ReactNative/CompositionSwitcher.idl @@ -45,9 +45,16 @@ enum SnapPointsAlignment { void Opacity(Single value); void BlurRadius(Single value); void Color(Windows.UI.Color value); + void Mask(IBrush mask); + void SourcePolicy(CompositionDropShadowSourcePolicy policy); } - [webhosthidden][experimental] interface IVisual { + [webhosthidden][experimental] enum CompositionDropShadowSourcePolicy { + Default = 0, + InheritedOnly = 1 + }; + +[webhosthidden][experimental] interface IVisual { void InsertAt(IVisual visual, Int32 index); void Remove(IVisual visual); IVisual GetAt(UInt32 index); @@ -72,14 +79,14 @@ enum SnapPointsAlignment { void AnimationClass(AnimationClass value); } -[webhosthidden][experimental] interface ISpriteVisual + [webhosthidden][experimental] interface ISpriteVisual requires IVisual { void Brush(IBrush brush); void Shadow(IDropShadow shadow); } - [webhosthidden][experimental] interface IRoundedRectangleVisual +[webhosthidden][experimental] interface IRoundedRectangleVisual requires IVisual { void Brush(IBrush brush); @@ -88,13 +95,13 @@ enum SnapPointsAlignment { void StrokeThickness(Single value); } -[webhosthidden][experimental] interface IScrollPositionChangedArgs { + [webhosthidden][experimental] interface IScrollPositionChangedArgs { Windows.Foundation.Numerics.Vector2 Position { get; }; } - [webhosthidden][experimental] interface IScrollVisual +[webhosthidden][experimental] interface IScrollVisual requires IVisual { void Brush(IBrush brush); @@ -122,7 +129,7 @@ enum SnapPointsAlignment { void SnapToAlignment(SnapPointsAlignment alignment); } -[webhosthidden][experimental] interface IActivityVisual + [webhosthidden][experimental] interface IActivityVisual requires IVisual { void Size(Single value); @@ -131,7 +138,7 @@ enum SnapPointsAlignment { void StopAnimation(); } - [webhosthidden][experimental] interface ICaretVisual { +[webhosthidden][experimental] interface ICaretVisual { IVisual InnerVisual { get; }; @@ -144,7 +151,7 @@ enum SnapPointsAlignment { void Brush(IBrush brush); } -[webhosthidden][experimental] interface IFocusVisual { + [webhosthidden][experimental] interface IFocusVisual { IVisual InnerVisual { get; }; @@ -158,7 +165,7 @@ enum SnapPointsAlignment { }; } - [webhosthidden][experimental] interface ICompositionContext { +[webhosthidden][experimental] interface ICompositionContext { ISpriteVisual CreateSpriteVisual(); IScrollVisual CreateScrollerVisual(); IRoundedRectangleVisual CreateRoundedRectangleVisual(); diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp index 4f51c0a3578..f6cbf170f68 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp @@ -54,6 +54,7 @@ struct CompositionTypeTraits { using CompositionStretch = winrt::Windows::UI::Composition::CompositionStretch; using CompositionStrokeCap = winrt::Windows::UI::Composition::CompositionStrokeCap; using CompositionSurfaceBrush = winrt::Windows::UI::Composition::CompositionSurfaceBrush; + using CompositionDropShadowSourcePolicy = winrt::Windows::UI::Composition::CompositionDropShadowSourcePolicy; using Compositor = winrt::Windows::UI::Composition::Compositor; using ContainerVisual = winrt::Windows::UI::Composition::ContainerVisual; using CubicBezierEasingFunction = winrt::Windows::UI::Composition::CubicBezierEasingFunction; @@ -127,6 +128,7 @@ struct CompositionTypeTraits { using CompositionStretch = winrt::Microsoft::UI::Composition::CompositionStretch; using CompositionStrokeCap = winrt::Microsoft::UI::Composition::CompositionStrokeCap; using CompositionSurfaceBrush = winrt::Microsoft::UI::Composition::CompositionSurfaceBrush; + using CompositionDropShadowSourcePolicy = winrt::Microsoft::UI::Composition::CompositionDropShadowSourcePolicy; using Compositor = winrt::Microsoft::UI::Composition::Compositor; using ContainerVisual = winrt::Microsoft::UI::Composition::ContainerVisual; using CubicBezierEasingFunction = winrt::Microsoft::UI::Composition::CubicBezierEasingFunction; @@ -224,6 +226,19 @@ struct CompDropShadow : public winrt::implements< m_shadow.Color(color); } + void Mask(winrt::Microsoft::ReactNative::Composition::Experimental::IBrush const &mask) noexcept { + if (mask) { + m_shadow.Mask(mask.as()->InnerBrush()); + } else { + m_shadow.Mask(nullptr); + } + } + + void SourcePolicy( + winrt::Microsoft::ReactNative::Composition::Experimental::CompositionDropShadowSourcePolicy policy) noexcept { + m_shadow.SourcePolicy(static_cast(policy)); + } + private: typename TTypeRedirects::DropShadow m_shadow; }; @@ -698,7 +713,7 @@ struct CompScrollerVisual : winrt::implements< IVisualInterop> { struct ScrollInteractionTrackerOwner : public winrt::implements { - ScrollInteractionTrackerOwner(CompScrollerVisual *outer) : m_outer(outer){}; + ScrollInteractionTrackerOwner(CompScrollerVisual *outer) : m_outer(outer) {}; void CustomAnimationStateEntered( typename TTypeRedirects::InteractionTracker sender, @@ -1100,8 +1115,9 @@ struct CompScrollerVisual : winrt::implements< auto positionAnimation = compositor.CreateVector3KeyFrameAnimation(); positionAnimation.InsertKeyFrame(1.0f, {x, y, 0.0f}); - positionAnimation.Duration(std::chrono::milliseconds( - std::clamp(distance * s_offsetsChangeMsPerUnit, s_offsetsChangeMinMs, s_offsetsChangeMaxMs))); + positionAnimation.Duration( + std::chrono::milliseconds( + std::clamp(distance * s_offsetsChangeMsPerUnit, s_offsetsChangeMinMs, s_offsetsChangeMaxMs))); return positionAnimation; } diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp index 95a2e752c7c..838c5e5d96e 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp @@ -710,7 +710,86 @@ void ComponentView::applyShadowProps(const facebook::react::ViewProps &viewProps shadow.Color(theme()->Color(*viewProps.shadowColor)); } - Visual().as().Shadow(shadow); + // Check if any border radius is set + auto borderMetrics = BorderPrimitive::resolveAndAlignBorderMetrics(m_layoutMetrics, viewProps); + bool hasBorderRadius = borderMetrics.borderRadii.topLeft.horizontal != 0 || + borderMetrics.borderRadii.topRight.horizontal != 0 || borderMetrics.borderRadii.bottomLeft.horizontal != 0 || + borderMetrics.borderRadii.bottomRight.horizontal != 0 || borderMetrics.borderRadii.topLeft.vertical != 0 || + borderMetrics.borderRadii.topRight.vertical != 0 || borderMetrics.borderRadii.bottomLeft.vertical != 0 || + borderMetrics.borderRadii.bottomRight.vertical != 0; + + if (hasBorderRadius) { + // When borderRadius is set, we need to create a shadow mask that follows the rounded rectangle shape. + // Use CompositionVisualSurface to capture the clipped visual's appearance as the shadow mask. + bool maskSet = false; + + // Try Microsoft (WinUI3) Composition first + auto msCompositor = + winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::InnerCompositor( + m_compContext); + if (msCompositor) { + auto innerVisual = + winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::InnerVisual( + Visual()); + if (innerVisual) { + // Create a VisualSurface that captures the visual (with its clip applied) + auto visualSurface = msCompositor.CreateVisualSurface(); + visualSurface.SourceVisual(innerVisual); + visualSurface.SourceSize( + {m_layoutMetrics.frame.size.width * m_layoutMetrics.pointScaleFactor, + m_layoutMetrics.frame.size.height * m_layoutMetrics.pointScaleFactor}); + + // Create a brush from the visual surface to use as shadow mask + auto maskBrush = msCompositor.CreateSurfaceBrush(visualSurface); + maskBrush.Stretch(winrt::Microsoft::UI::Composition::CompositionStretch::Fill); + + // Get the inner shadow and set the mask + auto innerShadow = winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper:: + InnerDropShadow(shadow); + if (innerShadow) { + innerShadow.Mask(maskBrush); + maskSet = true; + } + } + } + + // Fallback to System (Windows.UI) Composition if Microsoft Composition is not available + if (!maskSet) { + auto sysCompositor = + winrt::Microsoft::ReactNative::Composition::Experimental::SystemCompositionContextHelper::InnerCompositor( + m_compContext); + if (sysCompositor) { + auto innerVisual = + winrt::Microsoft::ReactNative::Composition::Experimental::SystemCompositionContextHelper::InnerVisual( + Visual()); + if (innerVisual) { + auto visualSurface = sysCompositor.CreateVisualSurface(); + visualSurface.SourceVisual(innerVisual); + visualSurface.SourceSize( + {m_layoutMetrics.frame.size.width * m_layoutMetrics.pointScaleFactor, + m_layoutMetrics.frame.size.height * m_layoutMetrics.pointScaleFactor}); + + auto maskBrush = sysCompositor.CreateSurfaceBrush(visualSurface); + maskBrush.Stretch(winrt::Windows::UI::Composition::CompositionStretch::Fill); + + auto innerShadow = + winrt::Microsoft::ReactNative::Composition::Experimental::SystemCompositionContextHelper::InnerDropShadow( + shadow); + if (innerShadow) { + innerShadow.Mask(maskBrush); + } + } + } + } + + // Apply shadow to OuterVisual (which is not clipped) so the shadow can extend beyond the clip + OuterVisual().as().Shadow(shadow); + Visual().as().Shadow(nullptr); + } else { + // No border radius - apply shadow directly to Visual (original behavior) + Visual().as().Shadow(shadow); + OuterVisual().as().Shadow(nullptr); + } } void ComponentView::updateTransformProps( @@ -731,8 +810,9 @@ void ComponentView::updateTransformProps( static_cast( winrt::Microsoft::ReactNative::Composition::Experimental::BackfaceVisibility::Hidden) == facebook::react::BackfaceVisibility::Hidden); - visual.BackfaceVisibility(static_cast( - newViewProps.backfaceVisibility)); + visual.BackfaceVisibility( + static_cast( + newViewProps.backfaceVisibility)); } // Transform - TODO doesn't handle multiple of the same kind of transform -- Doesn't handle hittesting updates @@ -1361,8 +1441,8 @@ winrt::Windows::Foundation::IInspectable ComponentView::CreateAutomationProvider return *m_innerAutomationProvider; } -const winrt::com_ptr - &ComponentView::InnerAutomationProvider() const noexcept { +const winrt::com_ptr & +ComponentView::InnerAutomationProvider() const noexcept { return m_innerAutomationProvider; } From 3fda378106582854590e00f400ffc92fce65524e Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Thu, 29 Jan 2026 15:35:44 +0530 Subject: [PATCH 2/3] adding lint fix --- .../Fabric/Composition/CompositionContextHelper.cpp | 7 +++---- .../Fabric/Composition/CompositionViewComponentView.cpp | 9 ++++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp index f6cbf170f68..f8235adc392 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp @@ -713,7 +713,7 @@ struct CompScrollerVisual : winrt::implements< IVisualInterop> { struct ScrollInteractionTrackerOwner : public winrt::implements { - ScrollInteractionTrackerOwner(CompScrollerVisual *outer) : m_outer(outer) {}; + ScrollInteractionTrackerOwner(CompScrollerVisual *outer) : m_outer(outer){}; void CustomAnimationStateEntered( typename TTypeRedirects::InteractionTracker sender, @@ -1115,9 +1115,8 @@ struct CompScrollerVisual : winrt::implements< auto positionAnimation = compositor.CreateVector3KeyFrameAnimation(); positionAnimation.InsertKeyFrame(1.0f, {x, y, 0.0f}); - positionAnimation.Duration( - std::chrono::milliseconds( - std::clamp(distance * s_offsetsChangeMsPerUnit, s_offsetsChangeMinMs, s_offsetsChangeMaxMs))); + positionAnimation.Duration(std::chrono::milliseconds( + std::clamp(distance * s_offsetsChangeMsPerUnit, s_offsetsChangeMinMs, s_offsetsChangeMaxMs))); return positionAnimation; } diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp index 838c5e5d96e..b136f1b5850 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp @@ -810,9 +810,8 @@ void ComponentView::updateTransformProps( static_cast( winrt::Microsoft::ReactNative::Composition::Experimental::BackfaceVisibility::Hidden) == facebook::react::BackfaceVisibility::Hidden); - visual.BackfaceVisibility( - static_cast( - newViewProps.backfaceVisibility)); + visual.BackfaceVisibility(static_cast( + newViewProps.backfaceVisibility)); } // Transform - TODO doesn't handle multiple of the same kind of transform -- Doesn't handle hittesting updates @@ -1441,8 +1440,8 @@ winrt::Windows::Foundation::IInspectable ComponentView::CreateAutomationProvider return *m_innerAutomationProvider; } -const winrt::com_ptr & -ComponentView::InnerAutomationProvider() const noexcept { +const winrt::com_ptr + &ComponentView::InnerAutomationProvider() const noexcept { return m_innerAutomationProvider; } From 22212778a71c3166079f2ca047dcdc874ed60a89 Mon Sep 17 00:00:00 2001 From: Protik Biswas Date: Thu, 29 Jan 2026 15:36:06 +0530 Subject: [PATCH 3/3] Change files --- ...ative-windows-86e2acd5-8142-4d57-9265-ff213f3e99e6.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/react-native-windows-86e2acd5-8142-4d57-9265-ff213f3e99e6.json diff --git a/change/react-native-windows-86e2acd5-8142-4d57-9265-ff213f3e99e6.json b/change/react-native-windows-86e2acd5-8142-4d57-9265-ff213f3e99e6.json new file mode 100644 index 00000000000..bd0a9eccb81 --- /dev/null +++ b/change/react-native-windows-86e2acd5-8142-4d57-9265-ff213f3e99e6.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "adding fix for shadow in rounded box", + "packageName": "react-native-windows", + "email": "protikbiswas@microsoft.com", + "dependentChangeType": "patch" +}