Skip to content

Commit f4dd6f9

Browse files
authored
Implement cursor property (#14141)
* Implement cursor property * Change files
1 parent b051774 commit f4dd6f9

File tree

7 files changed

+191
-4
lines changed

7 files changed

+191
-4
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Implement cursor property",
4+
"packageName": "react-native-windows",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

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

Lines changed: 160 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,18 @@
1212
#include <Views/ShadowNodeBase.h>
1313
#include <windows.h>
1414
#include <windowsx.h>
15+
#include <winrt/Windows.UI.Core.h>
1516
#include <winrt/Windows.UI.Input.h>
1617
#include "Composition.Input.h"
1718
#include "CompositionViewComponentView.h"
1819
#include "ReactNativeIsland.h"
1920
#include "RootComponentView.h"
2021

21-
#ifdef USE_WINUI3
22-
#include <winrt/Microsoft.UI.Input.h>
23-
#endif
22+
namespace ABI::Microsoft::UI::Input {
23+
struct IInputCursor;
24+
}
25+
26+
#include <Microsoft.UI.Input.InputCursor.Interop.h>
2427

2528
namespace Microsoft::ReactNative {
2629

@@ -328,6 +331,11 @@ CompositionEventHandler::~CompositionEventHandler() {
328331
}
329332
}
330333
#endif
334+
335+
if (m_hcursorOwned) {
336+
::DestroyCursor(m_hcursor);
337+
m_hcursor = nullptr;
338+
}
331339
}
332340

333341
facebook::react::SurfaceId CompositionEventHandler::SurfaceId() const noexcept {
@@ -507,6 +515,10 @@ int64_t CompositionEventHandler::SendMessage(HWND hwnd, uint32_t msg, uint64_t w
507515
}
508516
break;
509517
}
518+
case WM_SETCURSOR: {
519+
UpdateCursor();
520+
return 1;
521+
}
510522
}
511523

512524
return 0;
@@ -753,6 +765,151 @@ void CompositionEventHandler::HandleIncomingPointerEvent(
753765
hoveredViews.emplace_back(ReactTaggedView(componentViewDescriptor.view));
754766
}
755767
m_currentlyHoveredViewsPerPointer[pointerId] = std::move(hoveredViews);
768+
769+
if (IsMousePointerEvent(event)) {
770+
UpdateCursor();
771+
}
772+
}
773+
774+
void CompositionEventHandler::UpdateCursor() noexcept {
775+
for (auto &taggedView : m_currentlyHoveredViewsPerPointer[MOUSE_POINTER_ID]) {
776+
if (auto view = taggedView.view()) {
777+
if (auto viewcomponent =
778+
view.try_as<winrt::Microsoft::ReactNative::Composition::implementation::ComponentView>()) {
779+
auto cursorInfo = viewcomponent->cursor();
780+
if (cursorInfo.first != facebook::react::Cursor::Auto || cursorInfo.second != nullptr) {
781+
SetCursor(cursorInfo.first, cursorInfo.second);
782+
return;
783+
}
784+
}
785+
}
786+
}
787+
788+
SetCursor(facebook::react::Cursor::Auto, nullptr);
789+
}
790+
791+
void CompositionEventHandler::SetCursor(facebook::react::Cursor cursor, HCURSOR hcur) noexcept {
792+
if (m_currentCursor == cursor && m_hcursor == hcur)
793+
return;
794+
795+
if (auto strongRootView = m_wkRootView.get()) {
796+
if (auto island = strongRootView.Island()) {
797+
auto pointerSource = winrt::Microsoft::UI::Input::InputPointerSource::GetForIsland(island);
798+
799+
if (!hcur) {
800+
winrt::Windows::UI::Core::CoreCursorType type = winrt::Windows::UI::Core::CoreCursorType::Arrow;
801+
switch (cursor) {
802+
case facebook::react::Cursor::Pointer:
803+
type = winrt::Windows::UI::Core::CoreCursorType::Hand;
804+
break;
805+
case facebook::react::Cursor::Help:
806+
type = winrt::Windows::UI::Core::CoreCursorType::Help;
807+
break;
808+
case facebook::react::Cursor::NotAllowed:
809+
type = winrt::Windows::UI::Core::CoreCursorType::UniversalNo;
810+
break;
811+
case facebook::react::Cursor::Wait:
812+
type = winrt::Windows::UI::Core::CoreCursorType::Wait;
813+
break;
814+
case facebook::react::Cursor::Move:
815+
type = winrt::Windows::UI::Core::CoreCursorType::SizeAll;
816+
break;
817+
case facebook::react::Cursor::NESWResize:
818+
type = winrt::Windows::UI::Core::CoreCursorType::SizeNortheastSouthwest;
819+
break;
820+
case facebook::react::Cursor::NSResize:
821+
type = winrt::Windows::UI::Core::CoreCursorType::SizeNorthSouth;
822+
break;
823+
case facebook::react::Cursor::NWSEResize:
824+
type = winrt::Windows::UI::Core::CoreCursorType::SizeNorthwestSoutheast;
825+
break;
826+
case facebook::react::Cursor::EWResize:
827+
type = winrt::Windows::UI::Core::CoreCursorType::SizeWestEast;
828+
break;
829+
case facebook::react::Cursor::Text:
830+
type = winrt::Windows::UI::Core::CoreCursorType::IBeam;
831+
break;
832+
case facebook::react::Cursor::Progress:
833+
type = winrt::Windows::UI::Core::CoreCursorType::Wait; // IDC_APPSTARTING not mapped to CoreCursor?
834+
break;
835+
case facebook::react::Cursor::Crosshair:
836+
type = winrt::Windows::UI::Core::CoreCursorType::Cross;
837+
break;
838+
default:
839+
break;
840+
}
841+
842+
m_inputCursor = winrt::Microsoft::UI::Input::InputCursor::CreateFromCoreCursor(
843+
winrt::Windows::UI::Core::CoreCursor(type, 0));
844+
m_hcursor = hcur;
845+
} else {
846+
auto cursorInterop = winrt::get_activation_factory<
847+
winrt::Microsoft::UI::Input::InputCursor,
848+
ABI::Microsoft::UI::Input::IInputCursorStaticsInterop>();
849+
winrt::com_ptr<IUnknown> spunk;
850+
winrt::check_hresult(cursorInterop->CreateFromHCursor(
851+
hcur, reinterpret_cast<ABI::Microsoft::UI::Input::IInputCursor **>(spunk.put_void())));
852+
m_hcursor = hcur;
853+
m_inputCursor = spunk.as<winrt::Microsoft::UI::Input::InputCursor>();
854+
}
855+
856+
pointerSource.Cursor(m_inputCursor);
857+
} else {
858+
if (m_hcursorOwned) {
859+
::DestroyCursor(m_hcursor);
860+
m_hcursorOwned = false;
861+
}
862+
if (hcur == nullptr) {
863+
const WCHAR *idc = IDC_ARROW;
864+
switch (cursor) {
865+
case facebook::react::Cursor::Pointer:
866+
idc = IDC_HAND;
867+
break;
868+
case facebook::react::Cursor::Help:
869+
idc = IDC_HELP;
870+
break;
871+
case facebook::react::Cursor::NotAllowed:
872+
idc = IDC_NO;
873+
break;
874+
case facebook::react::Cursor::Wait:
875+
idc = IDC_WAIT;
876+
break;
877+
case facebook::react::Cursor::Move:
878+
idc = IDC_SIZEALL;
879+
break;
880+
case facebook::react::Cursor::NESWResize:
881+
idc = IDC_SIZENESW;
882+
break;
883+
case facebook::react::Cursor::NSResize:
884+
idc = IDC_SIZENS;
885+
break;
886+
case facebook::react::Cursor::NWSEResize:
887+
idc = IDC_SIZENWSE;
888+
break;
889+
case facebook::react::Cursor::EWResize:
890+
idc = IDC_SIZEWE;
891+
break;
892+
case facebook::react::Cursor::Text:
893+
idc = IDC_IBEAM;
894+
break;
895+
case facebook::react::Cursor::Progress:
896+
idc = IDC_APPSTARTING;
897+
break;
898+
case facebook::react::Cursor::Crosshair:
899+
idc = IDC_CROSS;
900+
break;
901+
default:
902+
break;
903+
}
904+
m_hcursor = ::LoadCursor(nullptr, idc);
905+
m_hcursorOwned = true;
906+
} else {
907+
m_hcursor = hcur;
908+
}
909+
::SetCursor(m_hcursor);
910+
}
911+
m_currentCursor = cursor;
912+
}
756913
}
757914

758915
void CompositionEventHandler::UpdateActiveTouch(

vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE
147147
static void
148148
UpdateActiveTouch(ActiveTouch &activeTouch, facebook::react::Point ptScaled, facebook::react::Point ptLocal) noexcept;
149149

150+
void UpdateCursor() noexcept;
151+
void SetCursor(facebook::react::Cursor cursor, HCURSOR hcur) noexcept;
152+
150153
std::map<PointerId, ActiveTouch> m_activeTouches; // iOS is map of touch event args to ActiveTouch..?
151154
PointerId m_touchId = 0;
152155
int m_fragmentTag = -1;
@@ -157,6 +160,10 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE
157160

158161
facebook::react::Tag m_pointerCapturingComponentTag{-1}; // Component that has captured input
159162
std::vector<PointerId> m_capturedPointers;
163+
HCURSOR m_hcursor{nullptr};
164+
bool m_hcursorOwned{false}; // If we create the cursor, so we need to destroy it
165+
facebook::react::Cursor m_currentCursor{facebook::react::Cursor::Auto};
166+
winrt::Microsoft::UI::Input::InputCursor m_inputCursor{nullptr};
160167

161168
#ifdef USE_WINUI3
162169
winrt::event_token m_pointerPressedToken;

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,10 @@ void ComponentView::updateClippingPath(
808808
}
809809
}
810810

811+
std::pair<facebook::react::Cursor, HCURSOR> ComponentView::cursor() const noexcept {
812+
return {viewProps()->cursor, nullptr};
813+
}
814+
811815
void ComponentView::indexOffsetForBorder(uint32_t &index) const noexcept {
812816
if (m_borderPrimitive) {
813817
index += m_borderPrimitive->numberOfVisuals();

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ struct ComponentView : public ComponentViewT<
106106
void Toggle() noexcept override;
107107
virtual winrt::Microsoft::ReactNative::implementation::ClipState getClipState() noexcept;
108108

109+
virtual std::pair<facebook::react::Cursor, HCURSOR> cursor() const noexcept;
110+
109111
const facebook::react::LayoutMetrics &layoutMetrics() const noexcept;
110112

111113
virtual std::string DefaultControlType() const noexcept;

vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ struct CompTextHost : public winrt::implements<CompTextHost, ITextHost> {
236236

237237
//@cmember Establish a new cursor shape
238238
void TxSetCursor(HCURSOR hcur, BOOL fText) override {
239-
assert(false);
239+
m_outer->m_hcursor = hcur;
240240
}
241241

242242
//@cmember Converts screen coordinates of a specified point to the client coordinates
@@ -732,6 +732,9 @@ void WindowsTextInputComponentView::OnPointerMoved(
732732
auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
733733
args.Handled(hr != S_FALSE);
734734
}
735+
736+
m_textServices->OnTxSetCursor(
737+
DVASPECT_CONTENT, -1, nullptr, nullptr, nullptr, nullptr, nullptr, ptContainer.x, ptContainer.y);
735738
}
736739

737740
void WindowsTextInputComponentView::OnKeyDown(
@@ -1479,6 +1482,10 @@ WindowsTextInputComponentView::createVisual() noexcept {
14791482
return visual;
14801483
}
14811484

1485+
std::pair<facebook::react::Cursor, HCURSOR> WindowsTextInputComponentView::cursor() const noexcept {
1486+
return {viewProps()->cursor, m_hcursor};
1487+
}
1488+
14821489
void WindowsTextInputComponentView::onThemeChanged() noexcept {
14831490
const auto &props = windowsTextInputProps();
14841491
updateCursorColor(props.cursorColor, props.textAttributes.foregroundColor);

vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ struct WindowsTextInputComponentView
7878

7979
winrt::Microsoft::ReactNative::Composition::Experimental::IVisual createVisual() noexcept;
8080

81+
std::pair<facebook::react::Cursor, HCURSOR> cursor() const noexcept override;
82+
8183
private:
8284
struct DrawBlock {
8385
DrawBlock(WindowsTextInputComponentView &view);
@@ -133,6 +135,7 @@ struct WindowsTextInputComponentView
133135
bool m_multiline{false};
134136
DWORD m_propBitsMask{0};
135137
DWORD m_propBits{0};
138+
HCURSOR m_hcursor{nullptr};
136139
std::vector<facebook::react::CompWindowsTextInputSubmitKeyEventsStruct> m_submitKeyEvents;
137140
};
138141

0 commit comments

Comments
 (0)