Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement cursor property #14141

Merged
merged 2 commits into from
Dec 3, 2024
Merged
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 @@
{
"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
Loading