Skip to content

Commit

Permalink
[Fabric] implement tooltip property (microsoft#13941)
Browse files Browse the repository at this point in the history
* [Fabric] implement view tooltip property

* format

* Change files

* update

* Fix lingering tooltip if component is unmounted while tooltip showing

* snapshot

---------

Co-authored-by: Jon Thysell <jthysell@microsoft.com>
  • Loading branch information
acoates-ms and jonthysell committed Oct 16, 2024
1 parent 10abaca commit 12997d1
Show file tree
Hide file tree
Showing 24 changed files with 553 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "[Fabric] implement view tooltip 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 @@ -6023,12 +6023,24 @@ exports[`View Tests Views can have tooltips 1`] = `
"_Props": {},
},
{
"Type": "Microsoft.ReactNative.Composition.ParagraphComponentView",
"Type": "Microsoft.ReactNative.Composition.ViewComponentView",
"_Props": {},
"__Children": [
{
"Type": "Microsoft.ReactNative.Composition.ParagraphComponentView",
"_Props": {},
},
],
},
{
"Type": "Microsoft.ReactNative.Composition.ParagraphComponentView",
"Type": "Microsoft.ReactNative.Composition.ViewComponentView",
"_Props": {},
"__Children": [
{
"Type": "Microsoft.ReactNative.Composition.ParagraphComponentView",
"_Props": {},
},
],
},
],
},
Expand Down Expand Up @@ -6064,6 +6076,25 @@ exports[`View Tests Views can have tooltips 1`] = `
"Offset": "0, 0, 0",
"Size": "916, 15",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
"Size": "916, 15",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
"Size": "916, 15",
"Visual Type": "SpriteVisual",
},
{
"Offset": "0, 0, 0",
"Size": "0, 0",
"Visual Type": "SpriteVisual",
},
],
},
],
},
{
"Offset": "0, 0, 0",
Expand All @@ -6074,13 +6105,32 @@ exports[`View Tests Views can have tooltips 1`] = `
},
{
"Offset": "0, 29, 0",
"Size": "916, 16",
"Size": "916, 14",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
"Size": "916, 16",
"Size": "916, 14",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
"Size": "916, 16",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
"Size": "916, 16",
"Visual Type": "SpriteVisual",
},
{
"Offset": "0, 0, 0",
"Size": "0, 0",
"Visual Type": "SpriteVisual",
},
],
},
],
},
{
"Offset": "0, 0, 0",
Expand Down
1 change: 1 addition & 0 deletions vnext/Desktop.DLL/React.Windows.Desktop.DLL.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
comsuppw.lib;
Shlwapi.lib;
Version.lib;
Dwmapi.lib;
WindowsApp_downlevel.lib;
%(AdditionalDependencies)
</AdditionalDependencies>
Expand Down
12 changes: 12 additions & 0 deletions vnext/Microsoft.ReactNative/Fabric/ComponentView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <Fabric/Composition/RootComponentView.h>
#include "AbiEventEmitter.h"
#include "AbiShadowNode.h"
#include "ReactCoreInjection.h"

namespace winrt::Microsoft::ReactNative::Composition::implementation {
struct RootComponentView;
Expand Down Expand Up @@ -262,6 +263,17 @@ void ComponentView::HandleCommand(const winrt::Microsoft::ReactNative::HandleCom
}
}

HWND ComponentView::GetHwndForParenting() noexcept {
if (m_parent) {
return winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(m_parent)
->GetHwndForParenting();
}

// Fallback if we do not know any more specific HWND
return reinterpret_cast<HWND>(winrt::Microsoft::ReactNative::implementation::ReactCoreInjection::GetTopLevelWindowId(
m_reactContext.Properties().Handle()));
}

winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView *ComponentView::rootComponentView()
const noexcept {
if (m_rootView)
Expand Down
3 changes: 3 additions & 0 deletions vnext/Microsoft.ReactNative/Fabric/ComponentView.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ struct ComponentView : public ComponentViewT<ComponentView> {
// Notify up the tree to bring the rect into view by scrolling as needed
virtual void StartBringIntoView(BringIntoViewOptions &&args) noexcept;

// Eventually PopupContentLink and similar APIs will remove the need for this.
virtual HWND GetHwndForParenting() noexcept;

virtual const winrt::Microsoft::ReactNative::IComponentProps userProps(
facebook::react::Props::Shared const &props) noexcept;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "CompositionHelpers.h"
#include "RootComponentView.h"
#include "Theme.h"
#include "TooltipService.h"
#include "UiaHelpers.h"
#include "d2d1helper.h"

Expand All @@ -43,6 +44,13 @@ ComponentView::ComponentView(
m_outerVisual.InsertAt(m_focusVisual.InnerVisual(), 0);
}

ComponentView::~ComponentView() {
if (m_tooltipTracked) {
TooltipService::GetCurrent(m_reactContext.Properties())->StopTracking(*this);
m_tooltipTracked = false;
}
}

facebook::react::Tag ComponentView::Tag() const noexcept {
return m_tag;
}
Expand Down Expand Up @@ -130,6 +138,16 @@ void ComponentView::updateProps(
updateShadowProps(oldViewProps, newViewProps);
}

if (oldViewProps.tooltip != newViewProps.tooltip) {
if (!m_tooltipTracked && newViewProps.tooltip) {
TooltipService::GetCurrent(m_reactContext.Properties())->StartTracking(*this);
m_tooltipTracked = true;
} else if (m_tooltipTracked && !newViewProps.tooltip) {
TooltipService::GetCurrent(m_reactContext.Properties())->StopTracking(*this);
m_tooltipTracked = false;
}
}

base_type::updateProps(props, oldProps);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ struct ComponentView : public ComponentViewT<
facebook::react::Tag tag,
winrt::Microsoft::ReactNative::ReactContext const &reactContext,
ComponentViewFeatures flags);
virtual ~ComponentView();

virtual winrt::Microsoft::ReactNative::Composition::Experimental::IVisual Visual() const noexcept {
return nullptr;
Expand Down Expand Up @@ -151,6 +152,7 @@ struct ComponentView : public ComponentViewT<
const facebook::react::ViewProps &viewProps) noexcept;

bool m_FinalizeTransform{false};
bool m_tooltipTracked{false};
ComponentViewFeatures m_flags;
void showFocusVisual(bool show) noexcept;
winrt::Microsoft::ReactNative::Composition::Experimental::IFocusVisual m_focusVisual{nullptr};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ winrt::IInspectable ReactNativeIsland::GetUiaProvider() noexcept {
if (m_uiaProvider == nullptr) {
m_uiaProvider =
winrt::make<winrt::Microsoft::ReactNative::implementation::CompositionRootAutomationProvider>(*this);
if (m_hwnd) {
if (m_hwnd && !m_island) {
auto pRootProvider =
static_cast<winrt::Microsoft::ReactNative::implementation::CompositionRootAutomationProvider *>(
m_uiaProvider.as<IRawElementProviderSimple>().get());
Expand All @@ -349,6 +349,10 @@ void ReactNativeIsland::SetWindow(uint64_t hwnd) noexcept {
m_hwnd = reinterpret_cast<HWND>(hwnd);
}

HWND ReactNativeIsland::GetHwndForParenting() noexcept {
return m_hwnd;
}

int64_t ReactNativeIsland::SendMessage(uint32_t msg, uint64_t wParam, int64_t lParam) noexcept {
if (m_rootTag == -1)
return 0;
Expand All @@ -368,7 +372,7 @@ int64_t ReactNativeIsland::SendMessage(uint32_t msg, uint64_t wParam, int64_t lP
bool ReactNativeIsland::CapturePointer(
const winrt::Microsoft::ReactNative::Composition::Input::Pointer &pointer,
facebook::react::Tag tag) noexcept {
if (m_hwnd) {
if (m_hwnd && !m_island) {
SetCapture(m_hwnd);
}
return m_CompositionEventHandler->CapturePointer(pointer, tag);
Expand All @@ -378,7 +382,7 @@ void ReactNativeIsland::ReleasePointerCapture(
const winrt::Microsoft::ReactNative::Composition::Input::Pointer &pointer,
facebook::react::Tag tag) noexcept {
if (m_CompositionEventHandler->ReleasePointerCapture(pointer, tag)) {
if (m_hwnd) {
if (m_hwnd && !m_island) {
if (m_hwnd == GetCapture()) {
ReleaseCapture();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ struct ReactNativeIsland
void AddRenderedVisual(const winrt::Microsoft::ReactNative::Composition::Experimental::IVisual &visual) noexcept;
void RemoveRenderedVisual(const winrt::Microsoft::ReactNative::Composition::Experimental::IVisual &visual) noexcept;
bool TrySetFocus() noexcept;
HWND GetHwndForParenting() noexcept;

winrt::Microsoft::ReactNative::Composition::ICustomResourceLoader Resources() noexcept;
void Resources(const winrt::Microsoft::ReactNative::Composition::ICustomResourceLoader &resources) noexcept;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,4 +231,15 @@ winrt::Microsoft::ReactNative::implementation::ClipState RootComponentView::getC
return winrt::Microsoft::ReactNative::implementation::ClipState::NoClip;
}

HWND RootComponentView::GetHwndForParenting() noexcept {
if (auto rootView = m_wkRootView.get()) {
auto hwnd = winrt::get_self<winrt::Microsoft::ReactNative::implementation::ReactNativeIsland>(rootView)
->GetHwndForParenting();
if (hwnd)
return hwnd;
}

return base_type::GetHwndForParenting();
}

} // namespace winrt::Microsoft::ReactNative::Composition::implementation
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ struct RootComponentView : RootComponentViewT<RootComponentView, ViewComponentVi
winrt::Microsoft::ReactNative::ComponentView FindFirstFocusableElement() noexcept;
winrt::Microsoft::ReactNative::ComponentView FindLastFocusableElement() noexcept;

HWND GetHwndForParenting() noexcept override;

private:
// should this be a ReactTaggedView? - It shouldn't actually matter since if the view is going away it should always
// be clearing its focus But being a reactTaggedView might make it easier to identify cases where that isn't
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
namespace winrt::Microsoft::ReactNative::Composition {

void RenderText(
ID2D1DeviceContext &deviceContext,
ID2D1RenderTarget &deviceContext,
::IDWriteTextLayout &textLayout,
const facebook::react::AttributedString &attributedString,
const facebook::react::TextAttributes &textAttributes,
Expand All @@ -26,7 +26,6 @@ void RenderText(
float offsetX = offset.x / pointScaleFactor;
float offsetY = offset.y / pointScaleFactor;

assert(deviceContext.GetUnitMode() == D2D1_UNIT_MODE_DIPS);
const auto dpi = pointScaleFactor * 96.0f;
float oldDpiX, oldDpiY;
deviceContext.GetDpi(&oldDpiX, &oldDpiY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
namespace winrt::Microsoft::ReactNative::Composition {

void RenderText(
ID2D1DeviceContext &deviceContext,
ID2D1RenderTarget &deviceContext,
::IDWriteTextLayout &textLayout,
const facebook::react::AttributedString &attributedString,
const facebook::react::TextAttributes &textAttributes,
Expand Down
16 changes: 12 additions & 4 deletions vnext/Microsoft.ReactNative/Fabric/Composition/Theme.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,9 @@ bool Theme::TryGetPlatformColor(const std::string &platformColor, winrt::Windows
{"ScrollBarThumbFillDisabled", "ControlStrongFillColorDisabled"},
{"ScrollBarTrackFill",
"AcrylicInAppFillColorDefault"}, // TODO make AcrylicInAppFillColorDefault a real acrylic brush
};
{"ToolTipBackground", "SystemChromeMediumLowColor"},
{"ToolTipForeground", "SystemControlForegroundBaseHighColor"},
{"ToolTipBorderBrush", "SystemControlTransientBorderColor"}};

static std::unordered_map<std::string, winrt::Windows::UI::Color, std::hash<std::string_view>, std::equal_to<>>
s_lightColors = {
Expand Down Expand Up @@ -326,7 +328,9 @@ bool Theme::TryGetPlatformColor(const std::string &platformColor, winrt::Windows
{"ControlStrongFillColorDefault", {0x72, 0x00, 0x00, 0x00}},
{"ControlStrongFillColorDisabled", {0x51, 0x00, 0x00, 0x00}},
{"AcrylicInAppFillColorDefault", {0x9E, 0xFF, 0xFF, 0xFF}},
};
{"SystemChromeMediumLowColor", {0xFF, 0xF2, 0xF2, 0xF2}},
{"SystemControlForegroundBaseHighColor", {0xFF, 0x00, 0x00, 0x00}},
{"SystemControlTransientBorderColor", {0x24, 0x00, 0x00, 0x00}}};

static std::unordered_map<std::string, winrt::Windows::UI::Color, std::hash<std::string_view>, std::equal_to<>>
s_darkColors = {
Expand Down Expand Up @@ -356,7 +360,9 @@ bool Theme::TryGetPlatformColor(const std::string &platformColor, winrt::Windows
{"ControlStrongFillColorDefault", {0x8B, 0xFF, 0xFF, 0xFF}},
{"ControlStrongFillColorDisabled", {0x3F, 0xFF, 0xFF, 0xFF}},
{"AcrylicInAppFillColorDefault", {0x9E, 0x00, 0x00, 0x00}},
};
{"SystemChromeMediumLowColor", {0xFF, 0x2B, 0x2B, 0x2B}},
{"SystemControlForegroundBaseHighColor", {0xFF, 0xFF, 0xFF, 0xFF}},
{"SystemControlTransientBorderColor", {0x5C, 0x00, 0x00, 0x00}}};

static std::unordered_map<
std::string,
Expand Down Expand Up @@ -391,7 +397,9 @@ bool Theme::TryGetPlatformColor(const std::string &platformColor, winrt::Windows
{"SubtleFillColorSecondary", {winrt::Windows::UI::ViewManagement::UIElementType::ButtonFace, {}}},
{"ControlStrongFillColorDefault", {winrt::Windows::UI::ViewManagement::UIElementType::ButtonFace, {}}},
{"ControlStrongFillColorDisabled", {winrt::Windows::UI::ViewManagement::UIElementType::ButtonFace, {}}},
};
{"SystemChromeMediumLowColor", {winrt::Windows::UI::ViewManagement::UIElementType::ButtonFace, {}}},
{"SystemControlForegroundBaseHighColor", {winrt::Windows::UI::ViewManagement::UIElementType::ButtonText, {}}},
{"SystemControlTransientBorderColor", {winrt::Windows::UI::ViewManagement::UIElementType::ButtonText, {}}}};

auto alias = s_xamlAliasedColors.find(platformColor);
if (alias != s_xamlAliasedColors.end()) {
Expand Down
Loading

0 comments on commit 12997d1

Please sign in to comment.