diff --git a/change/react-native-windows-646abbfc-a05b-44db-a498-bf96a28aa1c7.json b/change/react-native-windows-646abbfc-a05b-44db-a498-bf96a28aa1c7.json new file mode 100644 index 00000000000..afa69f42104 --- /dev/null +++ b/change/react-native-windows-646abbfc-a05b-44db-a498-bf96a28aa1c7.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "add fabric switch path animation", + "packageName": "react-native-windows", + "email": "tatianakapos@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Microsoft.ReactNative/CompositionSwitcher.idl b/vnext/Microsoft.ReactNative/CompositionSwitcher.idl index 8a9aeb917de..c690dac67fa 100644 --- a/vnext/Microsoft.ReactNative/CompositionSwitcher.idl +++ b/vnext/Microsoft.ReactNative/CompositionSwitcher.idl @@ -110,6 +110,17 @@ namespace Microsoft.ReactNative.Composition void Color(Windows.UI.Color color); } + [webhosthidden] + [experimental] + interface ISwitchThumbVisual + { + IVisual InnerVisual { get; }; + void Size(Windows.Foundation.Numerics.Vector2 size); + void Position(Windows.Foundation.Numerics.Vector2 position); + Boolean IsVisible { get; set; }; + void Color(Windows.UI.Color color); + } + [webhosthidden] [experimental] interface IFocusVisual @@ -127,6 +138,7 @@ namespace Microsoft.ReactNative.Composition IScrollVisual CreateScrollerVisual(); IActivityVisual CreateActivityVisual(); ICaretVisual CreateCaretVisual(); + ISwitchThumbVisual CreateSwitchThumbVisual(); IFocusVisual CreateFocusVisual(); IDropShadow CreateDropShadow(); IBrush CreateColorBrush(Windows.UI.Color color); diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp index b0cc48aa64e..163ba109657 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp @@ -37,6 +37,7 @@ struct CompositionTypeTraits { using CompositionBackfaceVisibility = winrt::Windows::UI::Composition::CompositionBackfaceVisibility; using CompositionBrush = winrt::Windows::UI::Composition::CompositionBrush; using CompositionDrawingSurface = winrt::Windows::UI::Composition::CompositionDrawingSurface; + using CompositionEllipseGeometry = winrt::Windows::UI::Composition::CompositionEllipseGeometry; using CompositionNineGridBrush = winrt::Windows::UI::Composition::CompositionNineGridBrush; using CompositionPath = winrt::Windows::UI::Composition::CompositionPath; using CompositionSpriteShape = winrt::Windows::UI::Composition::CompositionSpriteShape; @@ -92,6 +93,7 @@ struct CompositionTypeTraits { using CompositionBackfaceVisibility = winrt::Microsoft::UI::Composition::CompositionBackfaceVisibility; using CompositionBrush = winrt::Microsoft::UI::Composition::CompositionBrush; using CompositionDrawingSurface = winrt::Microsoft::UI::Composition::CompositionDrawingSurface; + using CompositionEllipseGeometry = winrt::Microsoft::UI::Composition::CompositionEllipseGeometry; using CompositionNineGridBrush = winrt::Microsoft::UI::Composition::CompositionNineGridBrush; using CompositionPath = winrt::Microsoft::UI::Composition::CompositionPath; using CompositionSpriteShape = winrt::Microsoft::UI::Composition::CompositionSpriteShape; @@ -1029,6 +1031,87 @@ winrt::Microsoft::ReactNative::Composition::IVisual CompCaretVisual; #endif +// Switch Thumb animations +template +struct CompSwitchThumbVisual : winrt::implements< + CompSwitchThumbVisual, + winrt::Microsoft::ReactNative::Composition::ISwitchThumbVisual> { + CompSwitchThumbVisual(typename TTypeRedirects::Compositor const &compositor) + : m_compositor(compositor), m_compVisual(compositor.CreateSpriteVisual()) { + m_visual = CreateVisual(); + + // Create Thumb + m_geometry = m_compositor.CreateEllipseGeometry(); + m_spiritShape = m_compositor.CreateSpriteShape(); + m_spiritShape.Geometry(m_geometry); + auto circleShape = m_compositor.CreateShapeVisual(); + circleShape.Shapes().Append(m_spiritShape); + circleShape.Size({150.0f, 150.0f}); + + m_compVisual.Children().InsertAtTop(circleShape); + } + + winrt::Microsoft::ReactNative::Composition::IVisual CreateVisual() const noexcept; + + winrt::Microsoft::ReactNative::Composition::IVisual InnerVisual() const noexcept { + return m_visual; + } + + void Color(winrt::Windows::UI::Color color) noexcept { + m_spiritShape.FillBrush(m_compositor.CreateColorBrush(color)); + } + + void Size(winrt::Windows::Foundation::Numerics::float2 size) noexcept { + m_geometry.Radius(size); + m_spiritShape.Offset(size); + m_compVisual.Size(size); + } + + void Position(winrt::Windows::Foundation::Numerics::float2 position) noexcept { + if (!isDrawn) { + // we don't want to animate if this is the first time the switch is drawn on screen + isDrawn = true; + m_compVisual.Offset({position.x, position.y, 0.0f}); + } else { + auto animation = m_compositor.CreateVector3KeyFrameAnimation(); + animation.Duration(std::chrono::milliseconds(250)); + animation.Direction(TTypeRedirects::AnimationDirection::Normal); + animation.InsertKeyFrame(1.0f, {position.x, position.y, 0.0f}); + + m_compVisual.StartAnimation(L"Offset", animation); + } + } + + bool IsVisible() const noexcept { + return m_isVisible; + } + + void IsVisible(bool value) noexcept {} + + private: + bool m_isVisible{true}; + bool isDrawn{false}; + typename TTypeRedirects::SpriteVisual m_compVisual; + winrt::Microsoft::ReactNative::Composition::IVisual m_visual; + typename TTypeRedirects::Compositor m_compositor{nullptr}; + typename TTypeRedirects::CompositionSpriteShape m_spiritShape{nullptr}; + typename TTypeRedirects::CompositionEllipseGeometry m_geometry{nullptr}; +}; + +winrt::Microsoft::ReactNative::Composition::IVisual CompSwitchThumbVisual::CreateVisual() + const noexcept { + return winrt::make(m_compVisual); +} +using WindowsCompSwitchThumbVisual = CompSwitchThumbVisual; + +#ifdef USE_WINUI3 +winrt::Microsoft::ReactNative::Composition::IVisual CompSwitchThumbVisual::CreateVisual() + const noexcept { + return winrt::make(m_compVisual); +} +using MicrosoftCompSwitchThumbVisual = CompSwitchThumbVisual; +#endif + template struct CompFocusVisual : winrt::implements, winrt::Microsoft::ReactNative::Composition::IFocusVisual> { @@ -1186,6 +1269,8 @@ struct CompContext : winrt::implements< winrt::Microsoft::ReactNative::Composition::ICaretVisual CreateCaretVisual() noexcept; + winrt::Microsoft::ReactNative::Composition::ISwitchThumbVisual CreateSwitchThumbVisual() noexcept; + winrt::Microsoft::ReactNative::Composition::IFocusVisual CreateFocusVisual() noexcept; typename TTypeRedirects::CompositionGraphicsDevice CompositionGraphicsDevice() noexcept { @@ -1256,6 +1341,11 @@ CompContext::CreateCaretVisual() noexcept { return winrt::make(m_compositor); } +winrt::Microsoft::ReactNative::Composition::ISwitchThumbVisual +CompContext::CreateSwitchThumbVisual() noexcept { + return winrt::make(m_compositor); +} + winrt::Microsoft::ReactNative::Composition::IFocusVisual CompContext::CreateFocusVisual() noexcept { return winrt::make(m_compositor); @@ -1307,6 +1397,11 @@ CompContext::CreateCaretVisual() noexcept { return winrt::make(m_compositor); } +winrt::Microsoft::ReactNative::Composition::ISwitchThumbVisual +CompContext::CreateSwitchThumbVisual() noexcept { + return winrt::make(m_compositor); +} + winrt::Microsoft::ReactNative::Composition::IFocusVisual CompContext::CreateFocusVisual() noexcept { return winrt::make(m_compositor); diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.cpp index 9fe432f0a09..57a92538ead 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.cpp @@ -128,13 +128,6 @@ void SwitchComponentView::Draw() noexcept { offsetX + trackMarginX + trackWidth, offsetY + trackMarginY + trackHeight); - // switchProps->value = false - float thumbX = trackRect.left + thumbMargin + thumbRadius; - - if (switchProps->value) { - thumbX = trackRect.right - thumbMargin - thumbRadius; - } - winrt::com_ptr defaultBrush; D2D1_COLOR_F defaultColor = @@ -142,12 +135,11 @@ void SwitchComponentView::Draw() noexcept { winrt::check_hresult(d2dDeviceContext->CreateSolidColorBrush(defaultColor, defaultBrush.put())); - winrt::com_ptr thumbBrush; + winrt::Windows::UI::Color thumbColor; if (!switchProps->disabled && switchProps->thumbTintColor) { - winrt::check_hresult( - d2dDeviceContext->CreateSolidColorBrush(switchProps->thumbTintColor.AsD2DColor(), thumbBrush.put())); + thumbColor = switchProps->thumbTintColor.AsWindowsColor(); } else { - thumbBrush = defaultBrush; + thumbColor = winrt::Windows::UI::Colors::Gray(); } const auto dpi = m_layoutMetrics.pointScaleFactor * 96.0f; @@ -173,10 +165,18 @@ void SwitchComponentView::Draw() noexcept { d2dDeviceContext->FillRoundedRectangle(track, trackBrush.get()); } - // switch thumb - D2D1_POINT_2F thumbCenter = D2D1 ::Point2F(thumbX, (trackRect.top + trackRect.bottom) / 2); - D2D1_ELLIPSE thumb = D2D1::Ellipse(thumbCenter, thumbRadius, thumbRadius); - d2dDeviceContext->FillEllipse(thumb, thumbBrush.get()); + // switch thumb - made with composition + float thumbX = (trackMarginX + thumbMargin) * m_layoutMetrics.pointScaleFactor; + float thumbY = (trackMarginY + thumbMargin) * m_layoutMetrics.pointScaleFactor; + + if (switchProps->value) { + thumbX = (trackMarginX + trackWidth - thumbRadius - thumbRadius - thumbMargin) * m_layoutMetrics.pointScaleFactor; + } + + m_thumbVisual.Size( + {thumbRadius * m_layoutMetrics.pointScaleFactor, thumbRadius * m_layoutMetrics.pointScaleFactor}); + m_thumbVisual.Position({thumbX, thumbY}); + m_thumbVisual.Color(thumbColor); // Restore old dpi setting d2dDeviceContext->SetDpi(oldDpiX, oldDpiY); @@ -194,6 +194,11 @@ void SwitchComponentView::ensureVisual() noexcept { m_visual = m_compContext.CreateSpriteVisual(); OuterVisual().InsertAt(m_visual, 0); } + + if (!m_thumbVisual) { + m_thumbVisual = m_compContext.CreateSwitchThumbVisual(); + m_visual.InsertAt(m_thumbVisual.InnerVisual(), 0); + } } void SwitchComponentView::ensureDrawingSurface() noexcept { diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.h index 8fb3886d8e0..3358779b165 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.h @@ -63,6 +63,7 @@ struct SwitchComponentView : CompositionBaseComponentView { winrt::Microsoft::ReactNative::ReactContext m_context; facebook::react::SharedViewProps m_props; winrt::Microsoft::ReactNative::Composition::IDrawingSurfaceBrush m_drawingSurface; + winrt::Microsoft::ReactNative::Composition::ISwitchThumbVisual m_thumbVisual; }; } // namespace Microsoft::ReactNative