Skip to content

Commit

Permalink
[Fabric] Add various native focus APIs (#13234)
Browse files Browse the repository at this point in the history
* [Fabric] Add various native focus APIs

* format

* Change files

* fix
  • Loading branch information
acoates-ms authored May 20, 2024
1 parent 6c48a02 commit 0d5ad19
Show file tree
Hide file tree
Showing 18 changed files with 544 additions and 98 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "[Fabric] Add various native focus APIs",
"packageName": "react-native-windows",
"email": "30809111+acoates-ms@users.noreply.github.com",
"dependentChangeType": "patch"
}
7 changes: 7 additions & 0 deletions vnext/Desktop/module.g.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ void *winrt_make_Microsoft_ReactNative_Composition_Experimental_MicrosoftComposi
void *winrt_make_Microsoft_ReactNative_Composition_Experimental_SystemCompositionContextHelper();
void *winrt_make_Microsoft_ReactNative_Composition_CompositionUIService();
void* winrt_make_Microsoft_ReactNative_Composition_ViewComponentView();
void *winrt_make_Microsoft_ReactNative_Composition_FocusManager();
void* winrt_make_Microsoft_ReactNative_JsiRuntime();
void* winrt_make_Microsoft_ReactNative_ReactCoreInjection();
void* winrt_make_Microsoft_ReactNative_ReactDispatcherHelper();
Expand All @@ -30,6 +31,9 @@ void* winrt_make_facebook_react_NativeTraceEventSource();
void* winrt_make_Microsoft_ReactNative_Composition_ViewComponentView() {
winrt::throw_hresult(E_NOTIMPL);
}
void *winrt_make_Microsoft_ReactNative_Composition_FocusManager() {
winrt::throw_hresult(E_NOTIMPL);
}
#endif

bool __stdcall winrt_can_unload_now() noexcept
Expand Down Expand Up @@ -68,6 +72,9 @@ void* __stdcall winrt_get_activation_factory([[maybe_unused]] std::wstring_view
if (requal(name, L"Microsoft.ReactNative.Composition.CompositionUIService")) {
return winrt_make_Microsoft_ReactNative_Composition_CompositionUIService();
}
if (requal(name, L"Microsoft.ReactNative.Composition.FocusManager")) {
return winrt_make_Microsoft_ReactNative_Composition_FocusManager();
}
if (requal(name, L"Microsoft.ReactNative.Composition.ViewComponentView")) {
return winrt_make_Microsoft_ReactNative_Composition_ViewComponentView();
}
Expand Down
26 changes: 26 additions & 0 deletions vnext/Microsoft.ReactNative/ComponentView.idl
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,26 @@ namespace Microsoft.ReactNative
IReactContext ReactContext { get;};
};

[experimental]
[webhosthidden]
runtimeclass LosingFocusEventArgs : Microsoft.ReactNative.Composition.Input.RoutedEventArgs {
Microsoft.ReactNative.ComponentView NewFocusedComponent { get; };
Microsoft.ReactNative.ComponentView OldFocusedComponent { get; };

void TryCancel();
void TrySetNewFocusedComponent(Microsoft.ReactNative.ComponentView component);
};

[experimental]
[webhosthidden]
runtimeclass GettingFocusEventArgs : Microsoft.ReactNative.Composition.Input.RoutedEventArgs {
Microsoft.ReactNative.ComponentView NewFocusedComponent { get; };
Microsoft.ReactNative.ComponentView OldFocusedComponent { get; };

void TryCancel();
void TrySetNewFocusedComponent(Microsoft.ReactNative.ComponentView component);
};

[experimental]
[webhosthidden]
unsealed runtimeclass ComponentView {
Expand Down Expand Up @@ -84,6 +104,12 @@ namespace Microsoft.ReactNative
overridable void OnPointerExited(Microsoft.ReactNative.Composition.Input.PointerRoutedEventArgs args);
overridable void OnPointerCaptureLost();

Boolean TryFocus();

event Windows.Foundation.EventHandler<LosingFocusEventArgs> LosingFocus;
event Windows.Foundation.EventHandler<GettingFocusEventArgs> GettingFocus;
event Windows.Foundation.EventHandler<Microsoft.ReactNative.Composition.Input.RoutedEventArgs> LostFocus;
event Windows.Foundation.EventHandler<Microsoft.ReactNative.Composition.Input.RoutedEventArgs> GotFocus;
};

} // namespace Microsoft.ReactNative
2 changes: 2 additions & 0 deletions vnext/Microsoft.ReactNative/CompositionComponentView.idl
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ namespace Microsoft.ReactNative.Composition
ComponentView(CreateCompositionComponentViewArgs args);

Microsoft.UI.Composition.Compositor Compositor { get; };
RootComponentView Root { get; };
Theme Theme;
overridable void OnThemeChanged();
Boolean CapturePointer(Microsoft.ReactNative.Composition.Input.Pointer pointer);
Expand Down Expand Up @@ -88,6 +89,7 @@ namespace Microsoft.ReactNative.Composition
[webhosthidden]
[default_interface]
unsealed runtimeclass RootComponentView : ViewComponentView {
Microsoft.ReactNative.ComponentView GetFocusedComponent();
};

[experimental]
Expand Down
95 changes: 83 additions & 12 deletions vnext/Microsoft.ReactNative/Fabric/ComponentView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,19 +143,16 @@ ComponentView::rootComponentView() noexcept {
}

void ComponentView::parent(const winrt::Microsoft::ReactNative::ComponentView &parent) noexcept {
if (!parent) {
auto root = rootComponentView();
winrt::Microsoft::ReactNative::ComponentView view{nullptr};
winrt::check_hresult(
QueryInterface(winrt::guid_of<winrt::Microsoft::ReactNative::ComponentView>(), winrt::put_abi(view)));
if (root && root->GetFocusedComponent() == view) {
root->SetFocusedComponent(nullptr); // TODO need move focus logic - where should focus go?
}
}

if (m_parent != parent) {
auto oldRootView = rootComponentView();
m_rootView = nullptr;
auto oldParent = m_parent;
m_parent = parent;
if (!parent) {
if (oldRootView && oldRootView->GetFocusedComponent() == *this) {
oldRootView->TrySetFocusedComponent(oldParent);
}
}
if (parent) {
theme(winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(parent)->theme());
}
Expand Down Expand Up @@ -220,9 +217,83 @@ facebook::react::Point ComponentView::getClientOffset() const noexcept {
return {};
}

void ComponentView::onFocusLost() noexcept {}
void ComponentView::onLostFocus(
const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept {
m_lostFocusEvent(*this, args);
if (m_parent) {
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(m_parent)->onLostFocus(args);
}
}

void ComponentView::onGotFocus(
const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept {
m_gotFocusEvent(*this, args);
if (m_parent) {
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(m_parent)->onGotFocus(args);
}
}

void ComponentView::onLosingFocus(const winrt::Microsoft::ReactNative::LosingFocusEventArgs &args) noexcept {
m_losingFocusEvent(*this, args);
if (m_parent) {
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(m_parent)->onLosingFocus(args);
}
}

void ComponentView::onGettingFocus(const winrt::Microsoft::ReactNative::GettingFocusEventArgs &args) noexcept {
m_gettingFocusEvent(*this, args);
if (m_parent) {
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(m_parent)->onGettingFocus(args);
}
}

void ComponentView::onFocusGained() noexcept {}
winrt::event_token ComponentView::LosingFocus(
winrt::Windows::Foundation::EventHandler<winrt::Microsoft::ReactNative::LosingFocusEventArgs> const
&handler) noexcept {
return m_losingFocusEvent.add(handler);
}

void ComponentView::LosingFocus(winrt::event_token const &token) noexcept {
m_losingFocusEvent.remove(token);
}

winrt::event_token ComponentView::GettingFocus(
winrt::Windows::Foundation::EventHandler<winrt::Microsoft::ReactNative::GettingFocusEventArgs> const
&handler) noexcept {
return m_gettingFocusEvent.add(handler);
}

void ComponentView::GettingFocus(winrt::event_token const &token) noexcept {
m_gettingFocusEvent.remove(token);
}

winrt::event_token ComponentView::LostFocus(
winrt::Windows::Foundation::EventHandler<winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs> const
&handler) noexcept {
return m_lostFocusEvent.add(handler);
}

void ComponentView::LostFocus(winrt::event_token const &token) noexcept {
m_lostFocusEvent.remove(token);
}

winrt::event_token ComponentView::GotFocus(
winrt::Windows::Foundation::EventHandler<winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs> const
&handler) noexcept {
return m_gotFocusEvent.add(handler);
}

void ComponentView::GotFocus(winrt::event_token const &token) noexcept {
m_gotFocusEvent.remove(token);
}

bool ComponentView::TryFocus() noexcept {
if (auto root = rootComponentView()) {
return root->TrySetFocusedComponent(*get_strong());
}

return false;
}

void ComponentView::OnPointerEntered(
const winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs &args) noexcept {}
Expand Down
35 changes: 33 additions & 2 deletions vnext/Microsoft.ReactNative/Fabric/ComponentView.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,29 @@ struct ComponentView : public ComponentViewT<ComponentView> {
virtual RECT getClientRect() const noexcept;
// The offset from this elements parent to its children (accounts for things like scroll position)
virtual facebook::react::Point getClientOffset() const noexcept;
virtual void onFocusLost() noexcept;
virtual void onFocusGained() noexcept;
virtual void onLosingFocus(const winrt::Microsoft::ReactNative::LosingFocusEventArgs &args) noexcept;
virtual void onGettingFocus(const winrt::Microsoft::ReactNative::GettingFocusEventArgs &args) noexcept;
virtual void onLostFocus(const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept;
virtual void onGotFocus(const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept;

winrt::event_token LosingFocus(
winrt::Windows::Foundation::EventHandler<winrt::Microsoft::ReactNative::LosingFocusEventArgs> const
&handler) noexcept;
void LosingFocus(winrt::event_token const &token) noexcept;
winrt::event_token GettingFocus(
winrt::Windows::Foundation::EventHandler<winrt::Microsoft::ReactNative::GettingFocusEventArgs> const
&handler) noexcept;
void GettingFocus(winrt::event_token const &token) noexcept;
winrt::event_token LostFocus(
winrt::Windows::Foundation::EventHandler<winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs> const
&handler) noexcept;
void LostFocus(winrt::event_token const &token) noexcept;
winrt::event_token GotFocus(
winrt::Windows::Foundation::EventHandler<winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs> const
&handler) noexcept;
void GotFocus(winrt::event_token const &token) noexcept;

bool TryFocus() noexcept;

virtual bool focusable() const noexcept;
virtual facebook::react::SharedViewEventEmitter eventEmitterAtPoint(facebook::react::Point pt) noexcept;
Expand Down Expand Up @@ -158,6 +179,16 @@ struct ComponentView : public ComponentViewT<ComponentView> {
winrt::Microsoft::ReactNative::ComponentView m_parent{nullptr};
winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::ReactNative::ComponentView> m_children{
winrt::single_threaded_vector<winrt::Microsoft::ReactNative::ComponentView>()};
winrt::event<winrt::Windows::Foundation::EventHandler<winrt::Microsoft::ReactNative::LosingFocusEventArgs>>
m_losingFocusEvent;
winrt::event<winrt::Windows::Foundation::EventHandler<winrt::Microsoft::ReactNative::GettingFocusEventArgs>>
m_gettingFocusEvent;
winrt::event<
winrt::Windows::Foundation::EventHandler<winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs>>
m_lostFocusEvent;
winrt::event<
winrt::Windows::Foundation::EventHandler<winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs>>
m_gotFocusEvent;
};

// Run fn on all nodes of the component view tree starting from this one until fn returns true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ CompositionRootView::CompositionRootView(const winrt::Microsoft::UI::Composition
#endif

CompositionRootView::~CompositionRootView() noexcept {
#ifdef USE_WINUI3
if (m_island && m_island.IsConnected()) {
m_island.AutomationProviderRequested(m_islandAutomationProviderRequestedToken);
}
#endif

if (m_uiDispatcher) {
assert(m_uiDispatcher.HasThreadAccess());
UninitRootView();
Expand Down Expand Up @@ -193,7 +199,7 @@ void CompositionRootView::RemoveRenderedVisual(

bool CompositionRootView::TrySetFocus() noexcept {
#ifdef USE_WINUI3
if (m_island) {
if (m_island && m_island.IsConnected()) {
auto focusController = winrt::Microsoft::UI::Input::InputFocusController::GetForIsland(m_island);
return focusController.TrySetFocus();
}
Expand Down Expand Up @@ -664,20 +670,29 @@ winrt::Microsoft::UI::Content::ContentIsland CompositionRootView::Island() noexc
rootVisual));
m_island = winrt::Microsoft::UI::Content::ContentIsland::Create(rootVisual);

m_island.AutomationProviderRequested(
[this](
// ContentIsland does not support weak_ref, so we cannot use auto_revoke for these events
m_islandAutomationProviderRequestedToken = m_island.AutomationProviderRequested(
[weakThis = get_weak()](
winrt::Microsoft::UI::Content::ContentIsland const &,
winrt::Microsoft::UI::Content::ContentIslandAutomationProviderRequestedEventArgs const &args) {
auto provider = GetUiaProvider();
auto pRootProvider =
static_cast<winrt::Microsoft::ReactNative::implementation::CompositionRootAutomationProvider *>(
provider.as<IRawElementProviderSimple>().get());
if (pRootProvider != nullptr) {
pRootProvider->SetIsland(m_island);
if (auto pThis = weakThis.get()) {
auto provider = pThis->GetUiaProvider();
auto pRootProvider =
static_cast<winrt::Microsoft::ReactNative::implementation::CompositionRootAutomationProvider *>(
provider.as<IRawElementProviderSimple>().get());
if (pRootProvider != nullptr) {
pRootProvider->SetIsland(pThis->m_island);
}
args.AutomationProvider(std::move(provider));
args.Handled(true);
}
args.AutomationProvider(std::move(provider));
args.Handled(true);
});

m_islandFrameworkClosedToken = m_island.FrameworkClosed([weakThis = get_weak()]() {
if (auto pThis = weakThis.get()) {
pThis->m_island = nullptr;
}
});
}
return m_island;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ struct CompositionRootView
#ifdef USE_WINUI3
winrt::Microsoft::UI::Composition::Compositor m_compositor{nullptr};
winrt::Microsoft::UI::Content::ContentIsland m_island{nullptr};
winrt::event_token m_islandFrameworkClosedToken;
winrt::event_token m_islandAutomationProviderRequestedToken;
#endif

HWND m_hwnd{0};
Expand Down
Loading

0 comments on commit 0d5ad19

Please sign in to comment.