diff --git a/change/react-native-windows-d8175a4a-46c8-40d0-b42b-097f50a633c1.json b/change/react-native-windows-d8175a4a-46c8-40d0-b42b-097f50a633c1.json new file mode 100644 index 00000000000..3f8157d5613 --- /dev/null +++ b/change/react-native-windows-d8175a4a-46c8-40d0-b42b-097f50a633c1.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "[Fabric] Fix ScrollViewComponentView object leak", + "packageName": "react-native-windows", + "email": "30809111+acoates-ms@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/packages/playground/windows/playground-composition/Playground-Composition.cpp b/packages/playground/windows/playground-composition/Playground-Composition.cpp index e7d06832568..836c7e6e92b 100644 --- a/packages/playground/windows/playground-composition/Playground-Composition.cpp +++ b/packages/playground/windows/playground-composition/Playground-Composition.cpp @@ -123,6 +123,7 @@ struct WindowData { std::wstring m_bundleFile; winrt::Microsoft::ReactNative::ReactNativeIsland m_compRootView{nullptr}; + winrt::Microsoft::UI::Content::DesktopChildSiteBridge m_bridge{nullptr}; winrt::Microsoft::ReactNative::ReactNativeHost m_host{nullptr}; winrt::Microsoft::ReactNative::ReactInstanceSettings m_instanceSettings{nullptr}; bool m_useLiftedComposition{true}; @@ -248,13 +249,13 @@ struct WindowData { // Register ellipse:// uri hander for images host.PackageProviders().Append(winrt::make()); - auto bridge = winrt::Microsoft::UI::Content::DesktopChildSiteBridge::Create( + m_bridge = winrt::Microsoft::UI::Content::DesktopChildSiteBridge::Create( g_liftedCompositor, winrt::Microsoft::UI::GetWindowIdFromWindow(hwnd)); auto appContent = m_compRootView.Island(); - bridge.Connect(appContent); - bridge.Show(); + m_bridge.Connect(appContent); + m_bridge.Show(); m_compRootView.ScaleFactor(ScaleFactor(hwnd)); winrt::Microsoft::ReactNative::LayoutConstraints constraints; @@ -287,7 +288,7 @@ struct WindowData { } m_compRootView.Arrange(constraints, {0, 0}); - bridge.ResizePolicy(winrt::Microsoft::UI::Content::ContentSizePolicy::ResizeContentToParentWindow); + m_bridge.ResizePolicy(winrt::Microsoft::UI::Content::ContentSizePolicy::ResizeContentToParentWindow); } else if (!m_target) { // General users of RNW should never set CompositionContext - this is an advanced usage to inject another @@ -359,6 +360,23 @@ struct WindowData { case IDM_SETTINGS: DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_SETTINGSBOX), hwnd, &Settings, reinterpret_cast(this)); break; + case IDM_UNLOAD: { + auto async = Host().UnloadInstance(); + async.Completed([&, uidispatch = InstanceSettings().UIDispatcher()]( + auto asyncInfo, winrt::Windows::Foundation::AsyncStatus asyncStatus) { + asyncStatus; + OutputDebugStringA("Instance Unload completed\n"); + + uidispatch.Post([&]() { + m_bridge.Close(); + m_bridge = nullptr; + }); + assert(asyncStatus == winrt::Windows::Foundation::AsyncStatus::Completed); + }); + m_compRootView = nullptr; + m_instanceSettings = nullptr; + m_host = nullptr; + } break; } return 0; @@ -561,12 +579,22 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) bool shouldPostQuitMessage = true; if (data->m_host) { shouldPostQuitMessage = false; + + winrt::Microsoft::ReactNative::ReactPropertyBag properties(data->m_host.InstanceSettings().Properties()); + + properties.Remove(winrt::Microsoft::ReactNative::ReactPropertyId< + winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext>{ + L"ReactNative.Composition", L"CompositionContext"}); + auto async = data->m_host.UnloadInstance(); async.Completed([host = data->m_host](auto asyncInfo, winrt::Windows::Foundation::AsyncStatus asyncStatus) { asyncStatus; assert(asyncStatus == winrt::Windows::Foundation::AsyncStatus::Completed); host.InstanceSettings().UIDispatcher().Post([]() { PostQuitMessage(0); }); }); + data->m_compRootView = nullptr; + data->m_instanceSettings = nullptr; + data->m_host = nullptr; } delete WindowData::GetFromWindow(hwnd); diff --git a/packages/playground/windows/playground-composition/Playground-Composition.rc b/packages/playground/windows/playground-composition/Playground-Composition.rc index bcc1f139def..cff2595c7bd 100644 --- a/packages/playground/windows/playground-composition/Playground-Composition.rc +++ b/packages/playground/windows/playground-composition/Playground-Composition.rc @@ -57,6 +57,7 @@ BEGIN MENUITEM "&Open Javascript File...\tCtrl+O", IDM_OPENJSFILE MENUITEM "&New Window\tCtrl+N", IDM_NEWWINDOW MENUITEM "&Refresh\tF5", IDM_REFRESH + MENUITEM "&Unload", IDM_UNLOAD MENUITEM SEPARATOR MENUITEM "&Settings...\tAlt+S", IDM_SETTINGS MENUITEM SEPARATOR diff --git a/packages/playground/windows/playground-composition/resource.h b/packages/playground/windows/playground-composition/resource.h index 30955840a00..a542b5251d9 100644 --- a/packages/playground/windows/playground-composition/resource.h +++ b/packages/playground/windows/playground-composition/resource.h @@ -24,6 +24,7 @@ #define IDC_THEMELABEL 110 #define IDC_JSENGINELABEL 111 #define IDC_SIZETOCONTENT 112 +#define IDM_UNLOAD 113 #define IDI_ICON1 1008 // Next default values for new objects diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp index ffb82e6dd28..3acf0840507 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp @@ -140,152 +140,170 @@ struct CompositionInputKeyboardSource : winrt::implements< CompositionEventHandler::CompositionEventHandler( const winrt::Microsoft::ReactNative::ReactContext &context, const winrt::Microsoft::ReactNative::ReactNativeIsland &reactNativeIsland) - : m_context(context), m_wkRootView(reactNativeIsland) { + : m_context(context), m_wkRootView(reactNativeIsland) {} + +void CompositionEventHandler::Initialize() noexcept { #ifdef USE_WINUI3 - if (auto island = reactNativeIsland.Island()) { + if (auto island = m_wkRootView.get().Island()) { auto pointerSource = winrt::Microsoft::UI::Input::InputPointerSource::GetForIsland(island); m_pointerPressedToken = - pointerSource.PointerPressed([this]( + pointerSource.PointerPressed([wkThis = weak_from_this()]( winrt::Microsoft::UI::Input::InputPointerSource const &, winrt::Microsoft::UI::Input::PointerEventArgs const &args) { - if (auto strongRootView = m_wkRootView.get()) { - if (SurfaceId() == -1) - return; - - auto pp = winrt::make( - args.CurrentPoint(), strongRootView.ScaleFactor()); - onPointerPressed(pp, args.KeyModifiers()); + if (auto strongThis = wkThis.lock()) { + if (auto strongRootView = strongThis->m_wkRootView.get()) { + if (strongThis->SurfaceId() == -1) + return; + + auto pp = winrt::make( + args.CurrentPoint(), strongRootView.ScaleFactor()); + strongThis->onPointerPressed(pp, args.KeyModifiers()); + } } }); m_pointerReleasedToken = - pointerSource.PointerReleased([this]( + pointerSource.PointerReleased([wkThis = weak_from_this()]( winrt::Microsoft::UI::Input::InputPointerSource const &, winrt::Microsoft::UI::Input::PointerEventArgs const &args) { - if (auto strongRootView = m_wkRootView.get()) { - if (SurfaceId() == -1) - return; - - auto pp = winrt::make( - args.CurrentPoint(), strongRootView.ScaleFactor()); - onPointerReleased(pp, args.KeyModifiers()); + if (auto strongThis = wkThis.lock()) { + if (auto strongRootView = strongThis->m_wkRootView.get()) { + if (strongThis->SurfaceId() == -1) + return; + + auto pp = winrt::make( + args.CurrentPoint(), strongRootView.ScaleFactor()); + strongThis->onPointerReleased(pp, args.KeyModifiers()); + } } }); - m_pointerMovedToken = pointerSource.PointerMoved([this]( + m_pointerMovedToken = pointerSource.PointerMoved([wkThis = weak_from_this()]( winrt::Microsoft::UI::Input::InputPointerSource const &, winrt::Microsoft::UI::Input::PointerEventArgs const &args) { - if (auto strongRootView = m_wkRootView.get()) { - if (SurfaceId() == -1) - return; - - auto pp = winrt::make( - args.CurrentPoint(), strongRootView.ScaleFactor()); - onPointerMoved(pp, args.KeyModifiers()); + if (auto strongThis = wkThis.lock()) { + if (auto strongRootView = strongThis->m_wkRootView.get()) { + if (strongThis->SurfaceId() == -1) + return; + + auto pp = winrt::make( + args.CurrentPoint(), strongRootView.ScaleFactor()); + strongThis->onPointerMoved(pp, args.KeyModifiers()); + } } }); m_pointerCaptureLostToken = - pointerSource.PointerCaptureLost([this]( + pointerSource.PointerCaptureLost([wkThis = weak_from_this()]( winrt::Microsoft::UI::Input::InputPointerSource const &, winrt::Microsoft::UI::Input::PointerEventArgs const &args) { - if (auto strongRootView = m_wkRootView.get()) { - if (SurfaceId() == -1) - return; - - auto pp = winrt::make( - args.CurrentPoint(), strongRootView.ScaleFactor()); - onPointerCaptureLost(pp, args.KeyModifiers()); + if (auto strongThis = wkThis.lock()) { + if (auto strongRootView = strongThis->m_wkRootView.get()) { + if (strongThis->SurfaceId() == -1) + return; + + auto pp = winrt::make( + args.CurrentPoint(), strongRootView.ScaleFactor()); + strongThis->onPointerCaptureLost(pp, args.KeyModifiers()); + } } }); m_pointerWheelChangedToken = - pointerSource.PointerWheelChanged([this]( + pointerSource.PointerWheelChanged([wkThis = weak_from_this()]( winrt::Microsoft::UI::Input::InputPointerSource const &, winrt::Microsoft::UI::Input::PointerEventArgs const &args) { - if (auto strongRootView = m_wkRootView.get()) { - if (SurfaceId() == -1) - return; - - auto pp = winrt::make( - args.CurrentPoint(), strongRootView.ScaleFactor()); - onPointerWheelChanged(pp, args.KeyModifiers()); + if (auto strongThis = wkThis.lock()) { + if (auto strongRootView = strongThis->m_wkRootView.get()) { + if (strongThis->SurfaceId() == -1) + return; + + auto pp = winrt::make( + args.CurrentPoint(), strongRootView.ScaleFactor()); + strongThis->onPointerWheelChanged(pp, args.KeyModifiers()); + } } }); auto keyboardSource = winrt::Microsoft::UI::Input::InputKeyboardSource::GetForIsland(island); - m_keyDownToken = keyboardSource.KeyDown([this]( + m_keyDownToken = keyboardSource.KeyDown([wkThis = weak_from_this()]( winrt::Microsoft::UI::Input::InputKeyboardSource const &source, winrt::Microsoft::UI::Input::KeyEventArgs const &args) { - if (auto strongRootView = m_wkRootView.get()) { - if (SurfaceId() == -1) - return; - - auto focusedComponent = RootComponentView().GetFocusedComponent(); - auto keyboardSource = winrt::make(source); - auto keyArgs = - winrt::make( - focusedComponent - ? focusedComponent.Tag() - : static_cast( - winrt::get_self( - strongRootView) - ->RootTag()), - args, - keyboardSource); - onKeyDown(keyArgs); - winrt::get_self(keyboardSource)->Disconnect(); + if (auto strongThis = wkThis.lock()) { + if (auto strongRootView = strongThis->m_wkRootView.get()) { + if (strongThis->SurfaceId() == -1) + return; + + auto focusedComponent = strongThis->RootComponentView().GetFocusedComponent(); + auto keyboardSource = winrt::make(source); + auto keyArgs = + winrt::make( + focusedComponent + ? focusedComponent.Tag() + : static_cast( + winrt::get_self( + strongRootView) + ->RootTag()), + args, + keyboardSource); + strongThis->onKeyDown(keyArgs); + winrt::get_self(keyboardSource)->Disconnect(); + } } }); - m_keyUpToken = keyboardSource.KeyUp([this]( + m_keyUpToken = keyboardSource.KeyUp([wkThis = weak_from_this()]( winrt::Microsoft::UI::Input::InputKeyboardSource const &source, winrt::Microsoft::UI::Input::KeyEventArgs const &args) { - if (auto strongRootView = m_wkRootView.get()) { - if (SurfaceId() == -1) - return; - - auto focusedComponent = RootComponentView().GetFocusedComponent(); - auto keyboardSource = winrt::make(source); - auto keyArgs = - winrt::make( - focusedComponent - ? focusedComponent.Tag() - : static_cast( - winrt::get_self( - strongRootView) - ->RootTag()), - args, - keyboardSource); - onKeyUp(keyArgs); - winrt::get_self(keyboardSource)->Disconnect(); + if (auto strongThis = wkThis.lock()) { + if (auto strongRootView = strongThis->m_wkRootView.get()) { + if (strongThis->SurfaceId() == -1) + return; + + auto focusedComponent = strongThis->RootComponentView().GetFocusedComponent(); + auto keyboardSource = winrt::make(source); + auto keyArgs = + winrt::make( + focusedComponent + ? focusedComponent.Tag() + : static_cast( + winrt::get_self( + strongRootView) + ->RootTag()), + args, + keyboardSource); + strongThis->onKeyUp(keyArgs); + winrt::get_self(keyboardSource)->Disconnect(); + } } }); m_characterReceivedToken = - keyboardSource.CharacterReceived([this]( + keyboardSource.CharacterReceived([wkThis = weak_from_this()]( winrt::Microsoft::UI::Input::InputKeyboardSource const &source, winrt::Microsoft::UI::Input::CharacterReceivedEventArgs const &args) { - if (auto strongRootView = m_wkRootView.get()) { - if (SurfaceId() == -1) - return; - - auto focusedComponent = RootComponentView().GetFocusedComponent(); - auto keyboardSource = winrt::make(source); - auto charArgs = winrt::make< - winrt::Microsoft::ReactNative::Composition::Input::implementation::CharacterReceivedRoutedEventArgs>( - focusedComponent - ? focusedComponent.Tag() - : static_cast( - winrt::get_self( - strongRootView) - ->RootTag()), - args, - keyboardSource); - onCharacterReceived(charArgs); - winrt::get_self(keyboardSource)->Disconnect(); + if (auto strongThis = wkThis.lock()) { + if (auto strongRootView = strongThis->m_wkRootView.get()) { + if (strongThis->SurfaceId() == -1) + return; + + auto focusedComponent = strongThis->RootComponentView().GetFocusedComponent(); + auto keyboardSource = winrt::make(source); + auto charArgs = winrt::make< + winrt::Microsoft::ReactNative::Composition::Input::implementation::CharacterReceivedRoutedEventArgs>( + focusedComponent + ? focusedComponent.Tag() + : static_cast( + winrt::get_self( + strongRootView) + ->RootTag()), + args, + keyboardSource); + strongThis->onCharacterReceived(charArgs); + winrt::get_self(keyboardSource)->Disconnect(); + } } }); } diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h index db2f1db17fe..44a1c12b129 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h @@ -27,13 +27,14 @@ struct FabricUIManager; struct winrt::Microsoft::ReactNative::implementation::ComponentView; typedef int PointerId; -class CompositionEventHandler { +class CompositionEventHandler : public std::enable_shared_from_this { public: CompositionEventHandler( const winrt::Microsoft::ReactNative::ReactContext &context, const winrt::Microsoft::ReactNative::ReactNativeIsland &ReactNativeIsland); virtual ~CompositionEventHandler(); + void Initialize() noexcept; int64_t SendMessage(HWND hwnd, uint32_t msg, uint64_t wParam, int64_t lParam) noexcept; void RemoveTouchHandlers(); winrt::Microsoft::UI::Input::VirtualKeyStates GetKeyState(winrt::Windows::System::VirtualKey key) noexcept; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.cpp index fbbf303b693..7854bf00407 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.cpp @@ -408,6 +408,7 @@ void ReactNativeIsland::InitRootView( m_context = winrt::Microsoft::ReactNative::ReactContext(std::move(context)); m_reactViewOptions = std::move(viewOptions); m_CompositionEventHandler = std::make_shared<::Microsoft::ReactNative::CompositionEventHandler>(m_context, *this); + m_CompositionEventHandler->Initialize(); UpdateRootViewInternal(); diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp index b9e4b7e5a08..bb78727a75a 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp @@ -42,7 +42,7 @@ struct ScrollBarComponent { const winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext, winrt::Microsoft::ReactNative::ReactContext const &reactContext, bool vertical) - : m_outer(outer), m_compContext(compContext), m_reactContext(reactContext), m_vertical(vertical) { + : m_wkOuter(outer), m_compContext(compContext), m_reactContext(reactContext), m_vertical(vertical) { m_rootVisual = m_compContext.CreateSpriteVisual(); m_trackVisual = m_compContext.CreateRoundedRectangleVisual(); m_thumbVisual = m_compContext.CreateRoundedRectangleVisual(); @@ -73,9 +73,11 @@ struct ScrollBarComponent { updateHighlight(ScrollbarHitRegion::ArrowLast); updateHighlight(ScrollbarHitRegion::Thumb); - m_trackVisual.Brush( - winrt::get_self(m_outer.Theme()) - ->InternalPlatformBrush(L"ScrollBarTrackFill")); + if (auto outer = m_wkOuter.get()) { + m_trackVisual.Brush( + winrt::get_self(outer.Theme()) + ->InternalPlatformBrush(L"ScrollBarTrackFill")); + } } void ContentSize(winrt::Windows::Foundation::Size contentSize) noexcept { @@ -293,19 +295,21 @@ struct ScrollBarComponent { } void OnPointerReleased(const winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs &args) { - if (!m_visible) - return; - auto pt = args.GetCurrentPoint(m_outer.Tag()); - if (m_nTrackInputOffset != -1 && - pt.PointerDeviceType() == winrt::Microsoft::ReactNative::Composition::Input::PointerDeviceType::Mouse && - pt.Properties().PointerUpdateKind() == - winrt::Microsoft::ReactNative::Composition::Input::PointerUpdateKind::LeftButtonReleased) { - handleMoveThumb(args); - stopTrackingThumb(); - m_outer.ReleasePointerCapture(args.Pointer()); - - auto reg = HitTest(pt.Position()); - updateShy(reg == ScrollbarHitRegion::Unknown); + if (auto outer = m_wkOuter.get()) { + if (!m_visible) + return; + auto pt = args.GetCurrentPoint(outer.Tag()); + if (m_nTrackInputOffset != -1 && + pt.PointerDeviceType() == winrt::Microsoft::ReactNative::Composition::Input::PointerDeviceType::Mouse && + pt.Properties().PointerUpdateKind() == + winrt::Microsoft::ReactNative::Composition::Input::PointerUpdateKind::LeftButtonReleased) { + handleMoveThumb(args); + stopTrackingThumb(); + outer.ReleasePointerCapture(args.Pointer()); + + auto reg = HitTest(pt.Position()); + updateShy(reg == ScrollbarHitRegion::Unknown); + } } } @@ -318,78 +322,85 @@ struct ScrollBarComponent { } void handleMoveThumb(const winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs &args) { - auto pt = args.GetCurrentPoint(m_outer.Tag()); - auto pos = pt.Position(); - - auto newTrackingPosition = static_cast((m_vertical ? pos.Y : pos.X) * m_scaleFactor) - m_nTrackInputOffset; - winrt::get_self(m_outer)->scrollTo( - m_vertical ? winrt::Windows::Foundation::Numerics:: - float3{m_offset.x, scrollOffsetFromThumbPos(newTrackingPosition), m_offset.z} - : winrt::Windows::Foundation::Numerics:: - float3{scrollOffsetFromThumbPos(newTrackingPosition), m_offset.y, m_offset.z}, - false); + if (auto outer = m_wkOuter.get()) { + auto pt = args.GetCurrentPoint(outer.Tag()); + auto pos = pt.Position(); + + auto newTrackingPosition = static_cast((m_vertical ? pos.Y : pos.X) * m_scaleFactor) - m_nTrackInputOffset; + winrt::get_self(outer)->scrollTo( + m_vertical ? winrt::Windows::Foundation::Numerics:: + float3{m_offset.x, scrollOffsetFromThumbPos(newTrackingPosition), m_offset.z} + : winrt::Windows::Foundation::Numerics:: + float3{scrollOffsetFromThumbPos(newTrackingPosition), m_offset.y, m_offset.z}, + false); + } args.Handled(true); } void OnPointerPressed(const winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs &args) { - if (!m_visible) - return; - auto pt = args.GetCurrentPoint(m_outer.Tag()); - if (pt.PointerDeviceType() == winrt::Microsoft::ReactNative::Composition::Input::PointerDeviceType::Mouse) { - auto pos = pt.Position(); - auto reg = HitTest(pos); + if (auto outer = m_wkOuter.get()) { + if (!m_visible) + return; + auto pt = args.GetCurrentPoint(outer.Tag()); + if (pt.PointerDeviceType() == winrt::Microsoft::ReactNative::Composition::Input::PointerDeviceType::Mouse) { + auto pos = pt.Position(); + auto reg = HitTest(pos); - switch (reg) { - case ScrollbarHitRegion::ArrowFirst: - if (m_vertical) { - winrt::get_self(m_outer)->lineUp(false); - } else { - winrt::get_self(m_outer)->lineLeft(false); - } - args.Handled(true); - break; - case ScrollbarHitRegion::ArrowLast: - if (m_vertical) { - winrt::get_self(m_outer)->lineDown(false); - } else { - winrt::get_self(m_outer)->lineRight(false); - } - args.Handled(true); - break; - case ScrollbarHitRegion::PageUp: - if (m_vertical) { - winrt::get_self(m_outer)->pageUp(false); - } - args.Handled(true); - break; - case ScrollbarHitRegion::PageDown: - if (m_vertical) { - winrt::get_self(m_outer)->pageDown(false); + switch (reg) { + case ScrollbarHitRegion::ArrowFirst: + if (m_vertical) { + winrt::get_self(outer)->lineUp(false); + } else { + winrt::get_self(outer)->lineLeft(false); + } + args.Handled(true); + break; + case ScrollbarHitRegion::ArrowLast: + if (m_vertical) { + winrt::get_self(outer)->lineDown(false); + } else { + winrt::get_self(outer)->lineRight(false); + } + args.Handled(true); + break; + case ScrollbarHitRegion::PageUp: + if (m_vertical) { + winrt::get_self(outer)->pageUp(false); + } + args.Handled(true); + break; + case ScrollbarHitRegion::PageDown: + if (m_vertical) { + winrt::get_self(outer)->pageDown(false); + } + args.Handled(true); + break; + case ScrollbarHitRegion::Thumb: { + outer.CapturePointer(args.Pointer()); + m_nTrackInputOffset = static_cast((m_vertical ? pos.Y : pos.X) * m_scaleFactor) - m_thumbPos; + m_thumbVisual.AnimationClass( + winrt::Microsoft::ReactNative::Composition::Experimental::AnimationClass::None); + handleMoveThumb(args); } - args.Handled(true); - break; - case ScrollbarHitRegion::Thumb: { - m_outer.CapturePointer(args.Pointer()); - m_nTrackInputOffset = static_cast((m_vertical ? pos.Y : pos.X) * m_scaleFactor) - m_thumbPos; - m_thumbVisual.AnimationClass(winrt::Microsoft::ReactNative::Composition::Experimental::AnimationClass::None); - handleMoveThumb(args); } } } } void OnPointerMoved(const winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs &args) { - if (!m_visible) - return; - auto pt = args.GetCurrentPoint(m_outer.Tag()); - if (pt.PointerDeviceType() == winrt::Microsoft::ReactNative::Composition::Input::PointerDeviceType::Mouse) { - if (m_nTrackInputOffset != -1) { - handleMoveThumb(args); - } else { - auto pos = pt.Position(); - auto reg = HitTest(pos); - updateShy(reg == ScrollbarHitRegion::Unknown); - setHighlightedRegion(reg); + if (auto outer = m_wkOuter.get()) { + if (!m_visible) + return; + auto pt = args.GetCurrentPoint(outer.Tag()); + if (pt.PointerDeviceType() == winrt::Microsoft::ReactNative::Composition::Input::PointerDeviceType::Mouse) { + if (m_nTrackInputOffset != -1) { + handleMoveThumb(args); + } else { + auto pos = pt.Position(); + auto reg = HitTest(pos); + updateShy(reg == ScrollbarHitRegion::Unknown); + setHighlightedRegion(reg); + } } } } @@ -427,122 +438,126 @@ struct ScrollBarComponent { // Renders the text into our composition surface void drawArrow(ScrollbarHitRegion region, bool disabled, bool hovered) noexcept { - auto &drawingSurface = - (region == ScrollbarHitRegion::ArrowFirst) ? m_arrowFirstDrawingSurface : m_arrowLastDrawingSurface; - if (!drawingSurface) { - drawingSurface = m_compContext.CreateDrawingSurfaceBrush( - {m_arrowSize, m_arrowSize}, - winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized, - winrt::Windows::Graphics::DirectX::DirectXAlphaMode::Premultiplied); - } + if (auto outer = m_wkOuter.get()) { + auto &drawingSurface = + (region == ScrollbarHitRegion::ArrowFirst) ? m_arrowFirstDrawingSurface : m_arrowLastDrawingSurface; + if (!drawingSurface) { + drawingSurface = m_compContext.CreateDrawingSurfaceBrush( + {m_arrowSize, m_arrowSize}, + winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized, + winrt::Windows::Graphics::DirectX::DirectXAlphaMode::Premultiplied); + } - if (winrt::get_self(m_outer)->theme()->IsEmpty()) { - return; - } + if (winrt::get_self(outer)->theme()->IsEmpty()) { + return; + } - winrt::com_ptr spTextFormat; - winrt::check_hresult(::Microsoft::ReactNative::DWriteFactory()->CreateTextFormat( - L"Segoe Fluent Icons", - nullptr, // Font collection (nullptr sets it to use the system font collection). - DWRITE_FONT_WEIGHT_REGULAR, - DWRITE_FONT_STYLE_NORMAL, - DWRITE_FONT_STRETCH_NORMAL, - 8, // Xaml resource: ScrollBarButtonArrowIconFontSize - L"", - spTextFormat.put())); - - winrt::check_hresult(spTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER)); - - winrt::com_ptr spTextLayout; - winrt::check_hresult(::Microsoft::ReactNative::DWriteFactory()->CreateTextLayout( - m_vertical ? ((region == ScrollbarHitRegion::ArrowFirst) ? L"\uEDDB" : L"\uEDDC") - : ((region == ScrollbarHitRegion::ArrowFirst) ? L"\uEDD9" : L"\uEDDA"), - 1, // The length of the string. - spTextFormat.get(), // The text format to apply to the string (contains font information, etc). - (m_arrowSize / m_scaleFactor), // The width of the layout box. - (m_arrowSize / m_scaleFactor), // The height of the layout box. - spTextLayout.put() // The IDWriteTextLayout interface pointer. - )); - - POINT offset; - { - ::Microsoft::ReactNative::Composition::AutoDrawDrawingSurface autoDraw(drawingSurface, m_scaleFactor, &offset); - if (auto d2dDeviceContext = autoDraw.GetRenderTarget()) { - d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Black, 0.0f)); - assert(d2dDeviceContext->GetUnitMode() == D2D1_UNIT_MODE_DIPS); - - // Create a solid color brush for the text. A more sophisticated application might want - // to cache and reuse a brush across all text elements instead, taking care to recreate - // it in the event of device removed. - winrt::com_ptr brush; - - D2D1::ColorF color{0}; - if (disabled) { - color = winrt::get_self(m_outer)->theme()->D2DPlatformColor( - "ScrollBarButtonArrowForegroundDisabled"); - } else if (hovered) { - color = winrt::get_self(m_outer)->theme()->D2DPlatformColor( - "ScrollBarButtonArrowForegroundPointerOver"); - } else { - color = winrt::get_self(m_outer)->theme()->D2DPlatformColor( - "ScrollBarButtonArrowForeground"); - } - winrt::check_hresult(d2dDeviceContext->CreateSolidColorBrush(color, brush.put())); + winrt::com_ptr spTextFormat; + winrt::check_hresult(::Microsoft::ReactNative::DWriteFactory()->CreateTextFormat( + L"Segoe Fluent Icons", + nullptr, // Font collection (nullptr sets it to use the system font collection). + DWRITE_FONT_WEIGHT_REGULAR, + DWRITE_FONT_STYLE_NORMAL, + DWRITE_FONT_STRETCH_NORMAL, + 8, // Xaml resource: ScrollBarButtonArrowIconFontSize + L"", + spTextFormat.put())); + + winrt::check_hresult(spTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER)); + + winrt::com_ptr spTextLayout; + winrt::check_hresult(::Microsoft::ReactNative::DWriteFactory()->CreateTextLayout( + m_vertical ? ((region == ScrollbarHitRegion::ArrowFirst) ? L"\uEDDB" : L"\uEDDC") + : ((region == ScrollbarHitRegion::ArrowFirst) ? L"\uEDD9" : L"\uEDDA"), + 1, // The length of the string. + spTextFormat.get(), // The text format to apply to the string (contains font information, etc). + (m_arrowSize / m_scaleFactor), // The width of the layout box. + (m_arrowSize / m_scaleFactor), // The height of the layout box. + spTextLayout.put() // The IDWriteTextLayout interface pointer. + )); + + POINT offset; + { + ::Microsoft::ReactNative::Composition::AutoDrawDrawingSurface autoDraw(drawingSurface, m_scaleFactor, &offset); + if (auto d2dDeviceContext = autoDraw.GetRenderTarget()) { + d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Black, 0.0f)); + assert(d2dDeviceContext->GetUnitMode() == D2D1_UNIT_MODE_DIPS); + + // Create a solid color brush for the text. A more sophisticated application might want + // to cache and reuse a brush across all text elements instead, taking care to recreate + // it in the event of device removed. + winrt::com_ptr brush; + + D2D1::ColorF color{0}; + if (disabled) { + color = winrt::get_self(outer)->theme()->D2DPlatformColor( + "ScrollBarButtonArrowForegroundDisabled"); + } else if (hovered) { + color = winrt::get_self(outer)->theme()->D2DPlatformColor( + "ScrollBarButtonArrowForegroundPointerOver"); + } else { + color = winrt::get_self(outer)->theme()->D2DPlatformColor( + "ScrollBarButtonArrowForeground"); + } + winrt::check_hresult(d2dDeviceContext->CreateSolidColorBrush(color, brush.put())); - { - DWRITE_TEXT_METRICS dtm{}; - winrt::check_hresult(spTextLayout->GetMetrics(&dtm)); - offset.y += static_cast((m_arrowSize - dtm.height) / 2.0f); - } + { + DWRITE_TEXT_METRICS dtm{}; + winrt::check_hresult(spTextLayout->GetMetrics(&dtm)); + offset.y += static_cast((m_arrowSize - dtm.height) / 2.0f); + } - // Draw the line of text at the specified offset, which corresponds to the top-left - // corner of our drawing surface. Notice we don't call BeginDraw on the D2D device - // context; this has already been done for us by the composition API. - d2dDeviceContext->DrawTextLayout( - D2D1::Point2F( - static_cast((offset.x) / m_scaleFactor), static_cast((offset.y) / m_scaleFactor)), - spTextLayout.get(), - brush.get(), - D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT); + // Draw the line of text at the specified offset, which corresponds to the top-left + // corner of our drawing surface. Notice we don't call BeginDraw on the D2D device + // context; this has already been done for us by the composition API. + d2dDeviceContext->DrawTextLayout( + D2D1::Point2F( + static_cast((offset.x) / m_scaleFactor), static_cast((offset.y) / m_scaleFactor)), + spTextLayout.get(), + brush.get(), + D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT); + } + } + if (drawingSurface) { + drawingSurface.HorizontalAlignmentRatio(0.0f); + drawingSurface.VerticalAlignmentRatio(0.0f); + drawingSurface.Stretch(winrt::Microsoft::ReactNative::Composition::Experimental::CompositionStretch::None); } - } - if (drawingSurface) { - drawingSurface.HorizontalAlignmentRatio(0.0f); - drawingSurface.VerticalAlignmentRatio(0.0f); - drawingSurface.Stretch(winrt::Microsoft::ReactNative::Composition::Experimental::CompositionStretch::None); - } - auto &arrowVisual = (region == ScrollbarHitRegion::ArrowFirst) ? m_arrowVisualFirst : m_arrowVisualLast; - arrowVisual.Brush(drawingSurface); + auto &arrowVisual = (region == ScrollbarHitRegion::ArrowFirst) ? m_arrowVisualFirst : m_arrowVisualLast; + arrowVisual.Brush(drawingSurface); + } } void updateHighlight(ScrollbarHitRegion region) noexcept { - switch (region) { - case ScrollbarHitRegion::ArrowFirst: - case ScrollbarHitRegion::ArrowLast: { - auto disabled = !std::static_pointer_cast( - winrt::get_self(m_outer)->viewProps()) - ->scrollEnabled; - drawArrow(region, disabled, m_highlightedRegion == region); - } - case ScrollbarHitRegion::Thumb: { - if (!std::static_pointer_cast( - winrt::get_self(m_outer)->viewProps()) - ->scrollEnabled) { - m_thumbVisual.Brush( - winrt::get_self(m_outer.Theme())->InternalPlatformBrush(L"ScrollBarThumbFillDisabled")); - } else if (m_highlightedRegion == region) { - m_thumbVisual.Brush( - winrt::get_self(m_outer.Theme())->InternalPlatformBrush(L"ScrollBarThumbFillPointerOver")); - } else { - m_thumbVisual.Brush(winrt::get_self(m_outer.Theme())->InternalPlatformBrush(L"ScrollBarThumbFill")); + if (auto outer = m_wkOuter.get()) { + switch (region) { + case ScrollbarHitRegion::ArrowFirst: + case ScrollbarHitRegion::ArrowLast: { + auto disabled = !std::static_pointer_cast( + winrt::get_self(outer)->viewProps()) + ->scrollEnabled; + drawArrow(region, disabled, m_highlightedRegion == region); + } + case ScrollbarHitRegion::Thumb: { + if (!std::static_pointer_cast( + winrt::get_self(outer)->viewProps()) + ->scrollEnabled) { + m_thumbVisual.Brush( + winrt::get_self(outer.Theme())->InternalPlatformBrush(L"ScrollBarThumbFillDisabled")); + } else if (m_highlightedRegion == region) { + m_thumbVisual.Brush( + winrt::get_self(outer.Theme())->InternalPlatformBrush(L"ScrollBarThumbFillPointerOver")); + } else { + m_thumbVisual.Brush(winrt::get_self(outer.Theme())->InternalPlatformBrush(L"ScrollBarThumbFill")); + } } } } } private: - winrt::Microsoft::ReactNative::Composition::ScrollViewComponentView m_outer; + winrt::weak_ref m_wkOuter; winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext m_compContext; winrt::Microsoft::ReactNative::ReactContext m_reactContext; const bool m_vertical;