Skip to content

Commit 0caaf1c

Browse files
Fixing box with corner radius doesn't have shadow (#15607)
* adding fix for shadow in rounded box * adding lint fix * Change files
1 parent 9d6a70c commit 0caaf1c

4 files changed

Lines changed: 118 additions & 10 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "adding fix for shadow in rounded box",
4+
"packageName": "react-native-windows",
5+
"email": "protikbiswas@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

vnext/Microsoft.ReactNative/CompositionSwitcher.idl

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,16 @@ enum SnapPointsAlignment {
4545
void Opacity(Single value);
4646
void BlurRadius(Single value);
4747
void Color(Windows.UI.Color value);
48+
void Mask(IBrush mask);
49+
void SourcePolicy(CompositionDropShadowSourcePolicy policy);
4850
}
4951

50-
[webhosthidden][experimental] interface IVisual {
52+
[webhosthidden][experimental] enum CompositionDropShadowSourcePolicy {
53+
Default = 0,
54+
InheritedOnly = 1
55+
};
56+
57+
[webhosthidden][experimental] interface IVisual {
5158
void InsertAt(IVisual visual, Int32 index);
5259
void Remove(IVisual visual);
5360
IVisual GetAt(UInt32 index);
@@ -72,14 +79,14 @@ enum SnapPointsAlignment {
7279
void AnimationClass(AnimationClass value);
7380
}
7481

75-
[webhosthidden][experimental] interface ISpriteVisual
82+
[webhosthidden][experimental] interface ISpriteVisual
7683
requires IVisual
7784
{
7885
void Brush(IBrush brush);
7986
void Shadow(IDropShadow shadow);
8087
}
8188

82-
[webhosthidden][experimental] interface IRoundedRectangleVisual
89+
[webhosthidden][experimental] interface IRoundedRectangleVisual
8390
requires IVisual
8491
{
8592
void Brush(IBrush brush);
@@ -88,13 +95,13 @@ enum SnapPointsAlignment {
8895
void StrokeThickness(Single value);
8996
}
9097

91-
[webhosthidden][experimental] interface IScrollPositionChangedArgs {
98+
[webhosthidden][experimental] interface IScrollPositionChangedArgs {
9299
Windows.Foundation.Numerics.Vector2 Position {
93100
get;
94101
};
95102
}
96103

97-
[webhosthidden][experimental] interface IScrollVisual
104+
[webhosthidden][experimental] interface IScrollVisual
98105
requires IVisual
99106
{
100107
void Brush(IBrush brush);
@@ -122,7 +129,7 @@ enum SnapPointsAlignment {
122129
void SnapToAlignment(SnapPointsAlignment alignment);
123130
}
124131

125-
[webhosthidden][experimental] interface IActivityVisual
132+
[webhosthidden][experimental] interface IActivityVisual
126133
requires IVisual
127134
{
128135
void Size(Single value);
@@ -131,7 +138,7 @@ enum SnapPointsAlignment {
131138
void StopAnimation();
132139
}
133140

134-
[webhosthidden][experimental] interface ICaretVisual {
141+
[webhosthidden][experimental] interface ICaretVisual {
135142
IVisual InnerVisual {
136143
get;
137144
};
@@ -144,7 +151,7 @@ enum SnapPointsAlignment {
144151
void Brush(IBrush brush);
145152
}
146153

147-
[webhosthidden][experimental] interface IFocusVisual {
154+
[webhosthidden][experimental] interface IFocusVisual {
148155
IVisual InnerVisual {
149156
get;
150157
};
@@ -158,7 +165,7 @@ enum SnapPointsAlignment {
158165
};
159166
}
160167

161-
[webhosthidden][experimental] interface ICompositionContext {
168+
[webhosthidden][experimental] interface ICompositionContext {
162169
ISpriteVisual CreateSpriteVisual();
163170
IScrollVisual CreateScrollerVisual();
164171
IRoundedRectangleVisual CreateRoundedRectangleVisual();

vnext/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ struct CompositionTypeTraits<WindowsTypeTag> {
5454
using CompositionStretch = winrt::Windows::UI::Composition::CompositionStretch;
5555
using CompositionStrokeCap = winrt::Windows::UI::Composition::CompositionStrokeCap;
5656
using CompositionSurfaceBrush = winrt::Windows::UI::Composition::CompositionSurfaceBrush;
57+
using CompositionDropShadowSourcePolicy = winrt::Windows::UI::Composition::CompositionDropShadowSourcePolicy;
5758
using Compositor = winrt::Windows::UI::Composition::Compositor;
5859
using ContainerVisual = winrt::Windows::UI::Composition::ContainerVisual;
5960
using CubicBezierEasingFunction = winrt::Windows::UI::Composition::CubicBezierEasingFunction;
@@ -127,6 +128,7 @@ struct CompositionTypeTraits<MicrosoftTypeTag> {
127128
using CompositionStretch = winrt::Microsoft::UI::Composition::CompositionStretch;
128129
using CompositionStrokeCap = winrt::Microsoft::UI::Composition::CompositionStrokeCap;
129130
using CompositionSurfaceBrush = winrt::Microsoft::UI::Composition::CompositionSurfaceBrush;
131+
using CompositionDropShadowSourcePolicy = winrt::Microsoft::UI::Composition::CompositionDropShadowSourcePolicy;
130132
using Compositor = winrt::Microsoft::UI::Composition::Compositor;
131133
using ContainerVisual = winrt::Microsoft::UI::Composition::ContainerVisual;
132134
using CubicBezierEasingFunction = winrt::Microsoft::UI::Composition::CubicBezierEasingFunction;
@@ -224,6 +226,19 @@ struct CompDropShadow : public winrt::implements<
224226
m_shadow.Color(color);
225227
}
226228

229+
void Mask(winrt::Microsoft::ReactNative::Composition::Experimental::IBrush const &mask) noexcept {
230+
if (mask) {
231+
m_shadow.Mask(mask.as<typename TTypeRedirects::IInnerCompositionBrush>()->InnerBrush());
232+
} else {
233+
m_shadow.Mask(nullptr);
234+
}
235+
}
236+
237+
void SourcePolicy(
238+
winrt::Microsoft::ReactNative::Composition::Experimental::CompositionDropShadowSourcePolicy policy) noexcept {
239+
m_shadow.SourcePolicy(static_cast<typename TTypeRedirects::CompositionDropShadowSourcePolicy>(policy));
240+
}
241+
227242
private:
228243
typename TTypeRedirects::DropShadow m_shadow;
229244
};

vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,86 @@ void ComponentView::applyShadowProps(const facebook::react::ViewProps &viewProps
710710
shadow.Color(theme()->Color(*viewProps.shadowColor));
711711
}
712712

713-
Visual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(shadow);
713+
// Check if any border radius is set
714+
auto borderMetrics = BorderPrimitive::resolveAndAlignBorderMetrics(m_layoutMetrics, viewProps);
715+
bool hasBorderRadius = borderMetrics.borderRadii.topLeft.horizontal != 0 ||
716+
borderMetrics.borderRadii.topRight.horizontal != 0 || borderMetrics.borderRadii.bottomLeft.horizontal != 0 ||
717+
borderMetrics.borderRadii.bottomRight.horizontal != 0 || borderMetrics.borderRadii.topLeft.vertical != 0 ||
718+
borderMetrics.borderRadii.topRight.vertical != 0 || borderMetrics.borderRadii.bottomLeft.vertical != 0 ||
719+
borderMetrics.borderRadii.bottomRight.vertical != 0;
720+
721+
if (hasBorderRadius) {
722+
// When borderRadius is set, we need to create a shadow mask that follows the rounded rectangle shape.
723+
// Use CompositionVisualSurface to capture the clipped visual's appearance as the shadow mask.
724+
bool maskSet = false;
725+
726+
// Try Microsoft (WinUI3) Composition first
727+
auto msCompositor =
728+
winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::InnerCompositor(
729+
m_compContext);
730+
if (msCompositor) {
731+
auto innerVisual =
732+
winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::InnerVisual(
733+
Visual());
734+
if (innerVisual) {
735+
// Create a VisualSurface that captures the visual (with its clip applied)
736+
auto visualSurface = msCompositor.CreateVisualSurface();
737+
visualSurface.SourceVisual(innerVisual);
738+
visualSurface.SourceSize(
739+
{m_layoutMetrics.frame.size.width * m_layoutMetrics.pointScaleFactor,
740+
m_layoutMetrics.frame.size.height * m_layoutMetrics.pointScaleFactor});
741+
742+
// Create a brush from the visual surface to use as shadow mask
743+
auto maskBrush = msCompositor.CreateSurfaceBrush(visualSurface);
744+
maskBrush.Stretch(winrt::Microsoft::UI::Composition::CompositionStretch::Fill);
745+
746+
// Get the inner shadow and set the mask
747+
auto innerShadow = winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::
748+
InnerDropShadow(shadow);
749+
if (innerShadow) {
750+
innerShadow.Mask(maskBrush);
751+
maskSet = true;
752+
}
753+
}
754+
}
755+
756+
// Fallback to System (Windows.UI) Composition if Microsoft Composition is not available
757+
if (!maskSet) {
758+
auto sysCompositor =
759+
winrt::Microsoft::ReactNative::Composition::Experimental::SystemCompositionContextHelper::InnerCompositor(
760+
m_compContext);
761+
if (sysCompositor) {
762+
auto innerVisual =
763+
winrt::Microsoft::ReactNative::Composition::Experimental::SystemCompositionContextHelper::InnerVisual(
764+
Visual());
765+
if (innerVisual) {
766+
auto visualSurface = sysCompositor.CreateVisualSurface();
767+
visualSurface.SourceVisual(innerVisual);
768+
visualSurface.SourceSize(
769+
{m_layoutMetrics.frame.size.width * m_layoutMetrics.pointScaleFactor,
770+
m_layoutMetrics.frame.size.height * m_layoutMetrics.pointScaleFactor});
771+
772+
auto maskBrush = sysCompositor.CreateSurfaceBrush(visualSurface);
773+
maskBrush.Stretch(winrt::Windows::UI::Composition::CompositionStretch::Fill);
774+
775+
auto innerShadow =
776+
winrt::Microsoft::ReactNative::Composition::Experimental::SystemCompositionContextHelper::InnerDropShadow(
777+
shadow);
778+
if (innerShadow) {
779+
innerShadow.Mask(maskBrush);
780+
}
781+
}
782+
}
783+
}
784+
785+
// Apply shadow to OuterVisual (which is not clipped) so the shadow can extend beyond the clip
786+
OuterVisual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(shadow);
787+
Visual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(nullptr);
788+
} else {
789+
// No border radius - apply shadow directly to Visual (original behavior)
790+
Visual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(shadow);
791+
OuterVisual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(nullptr);
792+
}
714793
}
715794

716795
void ComponentView::updateTransformProps(

0 commit comments

Comments
 (0)