Skip to content

Commit

Permalink
Implement cursor property (#14141)
Browse files Browse the repository at this point in the history
* Implement cursor property

* Change files
  • Loading branch information
acoates-ms authored Dec 3, 2024
1 parent b051774 commit f4dd6f9
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Implement cursor property",
"packageName": "react-native-windows",
"email": "30809111+acoates-ms@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@
#include <Views/ShadowNodeBase.h>
#include <windows.h>
#include <windowsx.h>
#include <winrt/Windows.UI.Core.h>
#include <winrt/Windows.UI.Input.h>
#include "Composition.Input.h"
#include "CompositionViewComponentView.h"
#include "ReactNativeIsland.h"
#include "RootComponentView.h"

#ifdef USE_WINUI3
#include <winrt/Microsoft.UI.Input.h>
#endif
namespace ABI::Microsoft::UI::Input {
struct IInputCursor;
}

#include <Microsoft.UI.Input.InputCursor.Interop.h>

namespace Microsoft::ReactNative {

Expand Down Expand Up @@ -328,6 +331,11 @@ CompositionEventHandler::~CompositionEventHandler() {
}
}
#endif

if (m_hcursorOwned) {
::DestroyCursor(m_hcursor);
m_hcursor = nullptr;
}
}

facebook::react::SurfaceId CompositionEventHandler::SurfaceId() const noexcept {
Expand Down Expand Up @@ -507,6 +515,10 @@ int64_t CompositionEventHandler::SendMessage(HWND hwnd, uint32_t msg, uint64_t w
}
break;
}
case WM_SETCURSOR: {
UpdateCursor();
return 1;
}
}

return 0;
Expand Down Expand Up @@ -753,6 +765,151 @@ void CompositionEventHandler::HandleIncomingPointerEvent(
hoveredViews.emplace_back(ReactTaggedView(componentViewDescriptor.view));
}
m_currentlyHoveredViewsPerPointer[pointerId] = std::move(hoveredViews);

if (IsMousePointerEvent(event)) {
UpdateCursor();
}
}

void CompositionEventHandler::UpdateCursor() noexcept {
for (auto &taggedView : m_currentlyHoveredViewsPerPointer[MOUSE_POINTER_ID]) {
if (auto view = taggedView.view()) {
if (auto viewcomponent =
view.try_as<winrt::Microsoft::ReactNative::Composition::implementation::ComponentView>()) {
auto cursorInfo = viewcomponent->cursor();
if (cursorInfo.first != facebook::react::Cursor::Auto || cursorInfo.second != nullptr) {
SetCursor(cursorInfo.first, cursorInfo.second);
return;
}
}
}
}

SetCursor(facebook::react::Cursor::Auto, nullptr);
}

void CompositionEventHandler::SetCursor(facebook::react::Cursor cursor, HCURSOR hcur) noexcept {
if (m_currentCursor == cursor && m_hcursor == hcur)
return;

if (auto strongRootView = m_wkRootView.get()) {
if (auto island = strongRootView.Island()) {
auto pointerSource = winrt::Microsoft::UI::Input::InputPointerSource::GetForIsland(island);

if (!hcur) {
winrt::Windows::UI::Core::CoreCursorType type = winrt::Windows::UI::Core::CoreCursorType::Arrow;
switch (cursor) {
case facebook::react::Cursor::Pointer:
type = winrt::Windows::UI::Core::CoreCursorType::Hand;
break;
case facebook::react::Cursor::Help:
type = winrt::Windows::UI::Core::CoreCursorType::Help;
break;
case facebook::react::Cursor::NotAllowed:
type = winrt::Windows::UI::Core::CoreCursorType::UniversalNo;
break;
case facebook::react::Cursor::Wait:
type = winrt::Windows::UI::Core::CoreCursorType::Wait;
break;
case facebook::react::Cursor::Move:
type = winrt::Windows::UI::Core::CoreCursorType::SizeAll;
break;
case facebook::react::Cursor::NESWResize:
type = winrt::Windows::UI::Core::CoreCursorType::SizeNortheastSouthwest;
break;
case facebook::react::Cursor::NSResize:
type = winrt::Windows::UI::Core::CoreCursorType::SizeNorthSouth;
break;
case facebook::react::Cursor::NWSEResize:
type = winrt::Windows::UI::Core::CoreCursorType::SizeNorthwestSoutheast;
break;
case facebook::react::Cursor::EWResize:
type = winrt::Windows::UI::Core::CoreCursorType::SizeWestEast;
break;
case facebook::react::Cursor::Text:
type = winrt::Windows::UI::Core::CoreCursorType::IBeam;
break;
case facebook::react::Cursor::Progress:
type = winrt::Windows::UI::Core::CoreCursorType::Wait; // IDC_APPSTARTING not mapped to CoreCursor?
break;
case facebook::react::Cursor::Crosshair:
type = winrt::Windows::UI::Core::CoreCursorType::Cross;
break;
default:
break;
}

m_inputCursor = winrt::Microsoft::UI::Input::InputCursor::CreateFromCoreCursor(
winrt::Windows::UI::Core::CoreCursor(type, 0));
m_hcursor = hcur;
} else {
auto cursorInterop = winrt::get_activation_factory<
winrt::Microsoft::UI::Input::InputCursor,
ABI::Microsoft::UI::Input::IInputCursorStaticsInterop>();
winrt::com_ptr<IUnknown> spunk;
winrt::check_hresult(cursorInterop->CreateFromHCursor(
hcur, reinterpret_cast<ABI::Microsoft::UI::Input::IInputCursor **>(spunk.put_void())));
m_hcursor = hcur;
m_inputCursor = spunk.as<winrt::Microsoft::UI::Input::InputCursor>();
}

pointerSource.Cursor(m_inputCursor);
} else {
if (m_hcursorOwned) {
::DestroyCursor(m_hcursor);
m_hcursorOwned = false;
}
if (hcur == nullptr) {
const WCHAR *idc = IDC_ARROW;
switch (cursor) {
case facebook::react::Cursor::Pointer:
idc = IDC_HAND;
break;
case facebook::react::Cursor::Help:
idc = IDC_HELP;
break;
case facebook::react::Cursor::NotAllowed:
idc = IDC_NO;
break;
case facebook::react::Cursor::Wait:
idc = IDC_WAIT;
break;
case facebook::react::Cursor::Move:
idc = IDC_SIZEALL;
break;
case facebook::react::Cursor::NESWResize:
idc = IDC_SIZENESW;
break;
case facebook::react::Cursor::NSResize:
idc = IDC_SIZENS;
break;
case facebook::react::Cursor::NWSEResize:
idc = IDC_SIZENWSE;
break;
case facebook::react::Cursor::EWResize:
idc = IDC_SIZEWE;
break;
case facebook::react::Cursor::Text:
idc = IDC_IBEAM;
break;
case facebook::react::Cursor::Progress:
idc = IDC_APPSTARTING;
break;
case facebook::react::Cursor::Crosshair:
idc = IDC_CROSS;
break;
default:
break;
}
m_hcursor = ::LoadCursor(nullptr, idc);
m_hcursorOwned = true;
} else {
m_hcursor = hcur;
}
::SetCursor(m_hcursor);
}
m_currentCursor = cursor;
}
}

void CompositionEventHandler::UpdateActiveTouch(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE
static void
UpdateActiveTouch(ActiveTouch &activeTouch, facebook::react::Point ptScaled, facebook::react::Point ptLocal) noexcept;

void UpdateCursor() noexcept;
void SetCursor(facebook::react::Cursor cursor, HCURSOR hcur) noexcept;

std::map<PointerId, ActiveTouch> m_activeTouches; // iOS is map of touch event args to ActiveTouch..?
PointerId m_touchId = 0;
int m_fragmentTag = -1;
Expand All @@ -157,6 +160,10 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE

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

#ifdef USE_WINUI3
winrt::event_token m_pointerPressedToken;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,10 @@ void ComponentView::updateClippingPath(
}
}

std::pair<facebook::react::Cursor, HCURSOR> ComponentView::cursor() const noexcept {
return {viewProps()->cursor, nullptr};
}

void ComponentView::indexOffsetForBorder(uint32_t &index) const noexcept {
if (m_borderPrimitive) {
index += m_borderPrimitive->numberOfVisuals();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ struct ComponentView : public ComponentViewT<
void Toggle() noexcept override;
virtual winrt::Microsoft::ReactNative::implementation::ClipState getClipState() noexcept;

virtual std::pair<facebook::react::Cursor, HCURSOR> cursor() const noexcept;

const facebook::react::LayoutMetrics &layoutMetrics() const noexcept;

virtual std::string DefaultControlType() const noexcept;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ struct CompTextHost : public winrt::implements<CompTextHost, ITextHost> {

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

//@cmember Converts screen coordinates of a specified point to the client coordinates
Expand Down Expand Up @@ -732,6 +732,9 @@ void WindowsTextInputComponentView::OnPointerMoved(
auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
args.Handled(hr != S_FALSE);
}

m_textServices->OnTxSetCursor(
DVASPECT_CONTENT, -1, nullptr, nullptr, nullptr, nullptr, nullptr, ptContainer.x, ptContainer.y);
}

void WindowsTextInputComponentView::OnKeyDown(
Expand Down Expand Up @@ -1479,6 +1482,10 @@ WindowsTextInputComponentView::createVisual() noexcept {
return visual;
}

std::pair<facebook::react::Cursor, HCURSOR> WindowsTextInputComponentView::cursor() const noexcept {
return {viewProps()->cursor, m_hcursor};
}

void WindowsTextInputComponentView::onThemeChanged() noexcept {
const auto &props = windowsTextInputProps();
updateCursorColor(props.cursorColor, props.textAttributes.foregroundColor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ struct WindowsTextInputComponentView

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

std::pair<facebook::react::Cursor, HCURSOR> cursor() const noexcept override;

private:
struct DrawBlock {
DrawBlock(WindowsTextInputComponentView &view);
Expand Down Expand Up @@ -133,6 +135,7 @@ struct WindowsTextInputComponentView
bool m_multiline{false};
DWORD m_propBitsMask{0};
DWORD m_propBits{0};
HCURSOR m_hcursor{nullptr};
std::vector<facebook::react::CompWindowsTextInputSubmitKeyEventsStruct> m_submitKeyEvents;
};

Expand Down

0 comments on commit f4dd6f9

Please sign in to comment.