Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"comment": "adding fix for shadow in rounded box",
"type": "prerelease",
"packageName": "react-native-windows",
"email": "protikbiswas@microsoft.com",
"dependentChangeType": "patch"
}
9 changes: 8 additions & 1 deletion vnext/Microsoft.ReactNative/CompositionSwitcher.idl
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ struct CompositionTypeTraits<WindowsTypeTag> {
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;
Expand Down Expand Up @@ -122,6 +123,7 @@ struct CompositionTypeTraits<MicrosoftTypeTag> {
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;
Expand Down Expand Up @@ -218,6 +220,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<typename TTypeRedirects::IInnerCompositionBrush>()->InnerBrush());
} else {
m_shadow.Mask(nullptr);
}
}

void SourcePolicy(
winrt::Microsoft::ReactNative::Composition::Experimental::CompositionDropShadowSourcePolicy policy) noexcept {
m_shadow.SourcePolicy(static_cast<typename TTypeRedirects::CompositionDropShadowSourcePolicy>(policy));
}

private:
typename TTypeRedirects::DropShadow m_shadow;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -709,9 +709,86 @@ void ComponentView::applyShadowProps(const facebook::react::ViewProps &viewProps
shadow.Color(theme()->Color(*viewProps.shadowColor));
}

// Apply shadow to OuterVisual (not Visual) because Visual may have a rounded-corner clip
// from updateClippingPath, which would clip the shadow. OuterVisual is not clipped.
OuterVisual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().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<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(shadow);
Visual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(nullptr);
} else {
// No border radius - apply shadow directly to Visual (original behavior)
Visual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(shadow);
OuterVisual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(nullptr);
}
}

void ComponentView::updateTransformProps(
Expand Down
Loading