Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
}
1 change: 1 addition & 0 deletions packages/e2e-test-app-fabric/test/visitAllPages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ describe('visitAllPages', () => {
if (
component === 'Flyout' ||
component === 'XAML' ||
component === 'Xaml WinUI3 (Experimental, for Fabric)' ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change added?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The visitAllPages.test.ts E2E test iterates through all component examples in RNTester and visits each page. Some components are skipped because they have known issues with E2E automation (Flyout, XAML, SwipeableCard).

Please see the pipeline error in 0.82 I was getting without this change https://dev.azure.com/ms/react-native-windows/_build/results?buildId=628033&view=logs&s=843cf892-394e-5a95-58a2-f157705529be&j=91433fce-a6a7-5848-fbf7-7248349c63dd

component === 'SwipeableCard'
) {
console.log('Skipping: ' + component);
Expand Down
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 @@ -708,7 +708,86 @@ void ComponentView::applyShadowProps(const facebook::react::ViewProps &viewProps
shadow.Color(theme()->Color(*viewProps.shadowColor));
}

Visual().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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we doing this even for views that do not have a shadow? Looks expensive.

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