[Fabric] LayoutDirection and FontSizeMultiplier support (#13866)

* [Fabric] LayoutDirection and FontSizeMultiplier support

* Change files

* Missed a few places not setting LayoutDirection

* fix crash on logbox

* Fix textinput caret

* fix

* RichEdit has to be told its multiline before we can set a multiline string
This commit is contained in:
Andrew Coates 2024-09-25 16:44:20 -07:00 коммит произвёл GitHub
Родитель 0f4ade8357
Коммит e43ea51ea7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
23 изменённых файлов: 259 добавлений и 208 удалений

Просмотреть файл

@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "[Fabric] LayoutDirection and FontSizeMultiplier support",
"packageName": "react-native-windows",
"email": "30809111+acoates-ms@users.noreply.github.com",
"dependentChangeType": "patch"
}

Просмотреть файл

@ -51,6 +51,7 @@ void UpdateRootViewSizeToAppWindow(
if (window.Presenter().as<winrt::Microsoft::UI::Windowing::OverlappedPresenter>().State() != if (window.Presenter().as<winrt::Microsoft::UI::Windowing::OverlappedPresenter>().State() !=
winrt::Microsoft::UI::Windowing::OverlappedPresenterState::Minimized) { winrt::Microsoft::UI::Windowing::OverlappedPresenterState::Minimized) {
winrt::Microsoft::ReactNative::LayoutConstraints constraints; winrt::Microsoft::ReactNative::LayoutConstraints constraints;
constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::Undefined;
constraints.MaximumSize = constraints.MinimumSize = size; constraints.MaximumSize = constraints.MinimumSize = size;
rootView.Arrange(constraints, {0, 0}); rootView.Arrange(constraints, {0, 0});
} }

Просмотреть файл

@ -128,6 +128,7 @@ struct WindowData {
winrt::Microsoft::ReactNative::ReactInstanceSettings m_instanceSettings{nullptr}; winrt::Microsoft::ReactNative::ReactInstanceSettings m_instanceSettings{nullptr};
bool m_useLiftedComposition{true}; bool m_useLiftedComposition{true};
bool m_sizeToContent{false}; bool m_sizeToContent{false};
bool m_forceRTL{false};
winrt::Windows::UI::Composition::Desktop::DesktopWindowTarget m_target{nullptr}; winrt::Windows::UI::Composition::Desktop::DesktopWindowTarget m_target{nullptr};
LONG m_height{0}; LONG m_height{0};
LONG m_width{0}; LONG m_width{0};
@ -255,6 +256,10 @@ struct WindowData {
m_bridge = winrt::Microsoft::UI::Content::DesktopChildSiteBridge::Create( m_bridge = winrt::Microsoft::UI::Content::DesktopChildSiteBridge::Create(
g_liftedCompositor, winrt::Microsoft::UI::GetWindowIdFromWindow(hwnd)); g_liftedCompositor, winrt::Microsoft::UI::GetWindowIdFromWindow(hwnd));
if (m_forceRTL) {
m_bridge.LayoutDirectionOverride(winrt::Microsoft::UI::Content::ContentLayoutDirection::RightToLeft);
}
auto appContent = m_compRootView.Island(); auto appContent = m_compRootView.Island();
m_bridge.Connect(appContent); m_bridge.Connect(appContent);
@ -262,7 +267,7 @@ struct WindowData {
m_compRootView.ScaleFactor(ScaleFactor(hwnd)); m_compRootView.ScaleFactor(ScaleFactor(hwnd));
winrt::Microsoft::ReactNative::LayoutConstraints constraints; winrt::Microsoft::ReactNative::LayoutConstraints constraints;
constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::LeftToRight; constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::Undefined;
constraints.MaximumSize = constraints.MaximumSize =
constraints.MinimumSize = {m_width / ScaleFactor(hwnd), m_height / ScaleFactor(hwnd)}; constraints.MinimumSize = {m_width / ScaleFactor(hwnd), m_height / ScaleFactor(hwnd)};
@ -327,6 +332,7 @@ struct WindowData {
SystemCompositionContextHelper::CreateVisual(root)); SystemCompositionContextHelper::CreateVisual(root));
m_compRootView.ScaleFactor(ScaleFactor(hwnd)); m_compRootView.ScaleFactor(ScaleFactor(hwnd));
winrt::Microsoft::ReactNative::LayoutConstraints contraints; winrt::Microsoft::ReactNative::LayoutConstraints contraints;
contraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::Undefined;
contraints.MaximumSize = contraints.MaximumSize =
contraints.MinimumSize = {m_width / ScaleFactor(hwnd), m_height / ScaleFactor(hwnd)}; contraints.MinimumSize = {m_width / ScaleFactor(hwnd), m_height / ScaleFactor(hwnd)};
m_compRootView.Arrange(contraints, {0, 0}); m_compRootView.Arrange(contraints, {0, 0});
@ -371,8 +377,10 @@ struct WindowData {
OutputDebugStringA("Instance Unload completed\n"); OutputDebugStringA("Instance Unload completed\n");
uidispatch.Post([&]() { uidispatch.Post([&]() {
m_bridge.Close(); if (m_bridge) {
m_bridge = nullptr; m_bridge.Close();
m_bridge = nullptr;
}
}); });
assert(asyncStatus == winrt::Windows::Foundation::AsyncStatus::Completed); assert(asyncStatus == winrt::Windows::Foundation::AsyncStatus::Completed);
}); });
@ -380,6 +388,14 @@ struct WindowData {
m_instanceSettings = nullptr; m_instanceSettings = nullptr;
m_host = nullptr; m_host = nullptr;
} break; } break;
case IDM_TOGGLE_LAYOUT_DIRECTION: {
if (m_bridge) {
m_bridge.LayoutDirectionOverride(
(m_forceRTL) ? winrt::Microsoft::UI::Content::ContentLayoutDirection::LeftToRight
: winrt::Microsoft::UI::Content::ContentLayoutDirection::RightToLeft);
}
m_forceRTL = !m_forceRTL;
}
} }
return 0; return 0;
@ -400,7 +416,7 @@ struct WindowData {
winrt::Windows::Foundation::Size size{m_width / ScaleFactor(hwnd), m_height / ScaleFactor(hwnd)}; winrt::Windows::Foundation::Size size{m_width / ScaleFactor(hwnd), m_height / ScaleFactor(hwnd)};
if (!IsIconic(hwnd)) { if (!IsIconic(hwnd)) {
winrt::Microsoft::ReactNative::LayoutConstraints constraints; winrt::Microsoft::ReactNative::LayoutConstraints constraints;
constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::LeftToRight; constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::Undefined;
constraints.MinimumSize = constraints.MaximumSize = size; constraints.MinimumSize = constraints.MaximumSize = size;
if (m_sizeToContent) { if (m_sizeToContent) {
ApplyConstraintsForContentSizedWindow(constraints); ApplyConstraintsForContentSizedWindow(constraints);

Просмотреть файл

@ -58,6 +58,7 @@ BEGIN
MENUITEM "&New Window\tCtrl+N", IDM_NEWWINDOW MENUITEM "&New Window\tCtrl+N", IDM_NEWWINDOW
MENUITEM "&Refresh\tF5", IDM_REFRESH MENUITEM "&Refresh\tF5", IDM_REFRESH
MENUITEM "&Unload", IDM_UNLOAD MENUITEM "&Unload", IDM_UNLOAD
MENUITEM "Toggle Layout &Direction", IDM_TOGGLE_LAYOUT_DIRECTION
MENUITEM SEPARATOR MENUITEM SEPARATOR
MENUITEM "&Settings...\tAlt+S", IDM_SETTINGS MENUITEM "&Settings...\tAlt+S", IDM_SETTINGS
MENUITEM SEPARATOR MENUITEM SEPARATOR

Просмотреть файл

@ -25,6 +25,7 @@
#define IDC_JSENGINELABEL 111 #define IDC_JSENGINELABEL 111
#define IDC_SIZETOCONTENT 112 #define IDC_SIZETOCONTENT 112
#define IDM_UNLOAD 113 #define IDM_UNLOAD 113
#define IDM_TOGGLE_LAYOUT_DIRECTION 114
#define IDI_ICON1 1008 #define IDI_ICON1 1008
// Next default values for new objects // Next default values for new objects

Просмотреть файл

@ -262,8 +262,8 @@ void ComponentView::HandleCommand(const winrt::Microsoft::ReactNative::HandleCom
} }
} }
winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView * winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView *ComponentView::rootComponentView()
ComponentView::rootComponentView() noexcept { const noexcept {
if (m_rootView) if (m_rootView)
return m_rootView; return m_rootView;

Просмотреть файл

@ -93,7 +93,8 @@ struct ComponentView : public ComponentViewT<ComponentView> {
facebook::react::LayoutMetrics const &oldLayoutMetrics) noexcept; facebook::react::LayoutMetrics const &oldLayoutMetrics) noexcept;
virtual void prepareForRecycle() noexcept; virtual void prepareForRecycle() noexcept;
virtual facebook::react::Props::Shared props() noexcept; virtual facebook::react::Props::Shared props() noexcept;
virtual winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView *rootComponentView() noexcept; virtual winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView *rootComponentView()
const noexcept;
virtual void parent(const winrt::Microsoft::ReactNative::ComponentView &parent) noexcept; virtual void parent(const winrt::Microsoft::ReactNative::ComponentView &parent) noexcept;
virtual winrt::Microsoft::ReactNative::ComponentView Parent() const noexcept; virtual winrt::Microsoft::ReactNative::ComponentView Parent() const noexcept;
virtual winrt::IVectorView<winrt::Microsoft::ReactNative::ComponentView> Children() const noexcept; virtual winrt::IVectorView<winrt::Microsoft::ReactNative::ComponentView> Children() const noexcept;
@ -253,7 +254,7 @@ struct ComponentView : public ComponentViewT<ComponentView> {
bool m_mounted : 1 {false}; bool m_mounted : 1 {false};
const facebook::react::Tag m_tag; const facebook::react::Tag m_tag;
winrt::IInspectable m_userData; winrt::IInspectable m_userData;
winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView *m_rootView{nullptr}; mutable winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView *m_rootView{nullptr};
mutable winrt::Microsoft::ReactNative::Composition::implementation::Theme *m_theme{nullptr}; mutable winrt::Microsoft::ReactNative::Composition::implementation::Theme *m_theme{nullptr};
const winrt::Microsoft::ReactNative::ReactContext m_reactContext; const winrt::Microsoft::ReactNative::ReactContext m_reactContext;
winrt::Microsoft::ReactNative::ComponentView m_parent{nullptr}; winrt::Microsoft::ReactNative::ComponentView m_parent{nullptr};

Просмотреть файл

@ -102,6 +102,7 @@ void CompositionHwndHost::UpdateSize() noexcept {
// Do not relayout when minimized // Do not relayout when minimized
if (!IsIconic(m_hwnd)) { if (!IsIconic(m_hwnd)) {
winrt::Microsoft::ReactNative::LayoutConstraints constraints; winrt::Microsoft::ReactNative::LayoutConstraints constraints;
constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::Undefined;
constraints.MinimumSize = constraints.MaximumSize = size; constraints.MinimumSize = constraints.MaximumSize = size;
m_compRootView.Arrange(constraints, {0, 0}); m_compRootView.Arrange(constraints, {0, 0});
} }

Просмотреть файл

@ -98,21 +98,36 @@ void CompositionReactViewInstance::UninitRootView() noexcept {
} }
} }
void ApplyConstraints( void ReactNativeIsland::ApplyConstraints(
const winrt::Microsoft::ReactNative::LayoutConstraints &layoutConstraintsIn, const winrt::Microsoft::ReactNative::LayoutConstraints &layoutConstraintsIn,
facebook::react::LayoutConstraints &layoutConstraintsOut) noexcept { facebook::react::LayoutConstraints &layoutConstraintsOut) const noexcept {
layoutConstraintsOut.minimumSize = {layoutConstraintsIn.MinimumSize.Width, layoutConstraintsIn.MinimumSize.Height}; layoutConstraintsOut.minimumSize = {layoutConstraintsIn.MinimumSize.Width, layoutConstraintsIn.MinimumSize.Height};
layoutConstraintsOut.maximumSize = {layoutConstraintsIn.MaximumSize.Width, layoutConstraintsIn.MaximumSize.Height}; layoutConstraintsOut.maximumSize = {layoutConstraintsIn.MaximumSize.Width, layoutConstraintsIn.MaximumSize.Height};
layoutConstraintsOut.layoutDirection = if (layoutConstraintsIn.LayoutDirection == winrt::Microsoft::ReactNative::LayoutDirection::Undefined) {
static_cast<facebook::react::LayoutDirection>(layoutConstraintsIn.LayoutDirection); if (m_island) {
layoutConstraintsOut.layoutDirection =
(m_island.LayoutDirection() == winrt::Microsoft::UI::Content::ContentLayoutDirection::LeftToRight)
? facebook::react::LayoutDirection::LeftToRight
: facebook::react::LayoutDirection::RightToLeft;
} else if (m_hwnd) {
auto styles = GetWindowLongPtrW(m_hwnd, GWL_EXSTYLE);
layoutConstraintsOut.layoutDirection = ((styles & WS_EX_LAYOUTRTL) == WS_EX_LAYOUTRTL)
? facebook::react::LayoutDirection::RightToLeft
: facebook::react::LayoutDirection::LeftToRight;
}
} else {
layoutConstraintsOut.layoutDirection =
static_cast<facebook::react::LayoutDirection>(layoutConstraintsIn.LayoutDirection);
}
} }
ReactNativeIsland::ReactNativeIsland() noexcept {}
#ifdef USE_WINUI3
ReactNativeIsland::ReactNativeIsland(const winrt::Microsoft::UI::Composition::Compositor &compositor) noexcept ReactNativeIsland::ReactNativeIsland(const winrt::Microsoft::UI::Composition::Compositor &compositor) noexcept
: m_compositor(compositor) {} : m_compositor(compositor),
#endif m_layoutConstraints({{0, 0}, {0, 0}, winrt::Microsoft::ReactNative::LayoutDirection::Undefined}) {
InitTextScaleMultiplier();
}
ReactNativeIsland::ReactNativeIsland() noexcept : ReactNativeIsland(nullptr) {}
ReactNativeIsland::~ReactNativeIsland() noexcept { ReactNativeIsland::~ReactNativeIsland() noexcept {
#ifdef USE_WINUI3 #ifdef USE_WINUI3
@ -248,6 +263,10 @@ void ReactNativeIsland::ScaleFactor(float value) noexcept {
} }
} }
float ReactNativeIsland::FontSizeMultiplier() const noexcept {
return m_textScaleMultiplier;
}
int64_t ReactNativeIsland::RootTag() const noexcept { int64_t ReactNativeIsland::RootTag() const noexcept {
return m_rootTag; return m_rootTag;
} }
@ -431,10 +450,9 @@ void ReactNativeIsland::UninitRootView() noexcept {
uiManager->stopSurface(static_cast<facebook::react::SurfaceId>(RootTag())); uiManager->stopSurface(static_cast<facebook::react::SurfaceId>(RootTag()));
// This is needed to ensure that the unmount JS logic is completed before the the instance is shutdown during // This is needed to ensure that the unmount JS logic is completed before the the instance is shutdown during
// instance destruction. Aligns with similar code in ReactInstanceWin::DetachRootView for paper Future: Instead this // instance destruction. Aligns with similar code in ReactInstanceWin::DetachRootView for paper Future: Instead
// method should return a Promise, which should be resolved when the JS logic is complete. // this method should return a Promise, which should be resolved when the JS logic is complete. The task will auto
// The task will auto set the event on destruction to ensure that the event is set if the JS Queue has already been // set the event on destruction to ensure that the event is set if the JS Queue has already been shutdown
// shutdown
Mso::ManualResetEvent mre; Mso::ManualResetEvent mre;
m_context.JSDispatcher().Post([autoMRE = std::make_unique<AutoMRE>(AutoMRE{mre})]() {}); m_context.JSDispatcher().Post([autoMRE = std::make_unique<AutoMRE>(AutoMRE{mre})]() {});
mre.Wait(); mre.Wait();
@ -499,9 +517,8 @@ facebook::react::AttributedStringBox CreateLoadingAttributedString() noexcept {
return facebook::react::AttributedStringBox{attributedString}; return facebook::react::AttributedStringBox{attributedString};
} }
facebook::react::Size MeasureLoading( facebook::react::Size ReactNativeIsland::MeasureLoading(
const winrt::Microsoft::ReactNative::LayoutConstraints &layoutConstraints, const winrt::Microsoft::ReactNative::LayoutConstraints &layoutConstraints) const noexcept {
float scaleFactor) {
facebook::react::LayoutConstraints fbLayoutConstraints; facebook::react::LayoutConstraints fbLayoutConstraints;
ApplyConstraints(layoutConstraints, fbLayoutConstraints); ApplyConstraints(layoutConstraints, fbLayoutConstraints);
@ -514,7 +531,7 @@ facebook::react::Size MeasureLoading(
winrt::check_hresult(textLayout->GetMetrics(&tm)); winrt::check_hresult(textLayout->GetMetrics(&tm));
return fbLayoutConstraints.clamp( return fbLayoutConstraints.clamp(
{loadingActivityHorizontalOffset * scaleFactor + tm.width, loadingBarHeight * scaleFactor}); {loadingActivityHorizontalOffset * m_scaleFactor + tm.width, loadingBarHeight * m_scaleFactor});
} }
winrt::event_token ReactNativeIsland::SizeChanged( winrt::event_token ReactNativeIsland::SizeChanged(
@ -544,7 +561,7 @@ void ReactNativeIsland::NotifySizeChanged() noexcept {
if (rootComponentView) { if (rootComponentView) {
size = rootComponentView->layoutMetrics().frame.size; size = rootComponentView->layoutMetrics().frame.size;
} else if (m_loadingVisual) { } else if (m_loadingVisual) {
size = MeasureLoading(m_layoutConstraints, m_scaleFactor); size = MeasureLoading(m_layoutConstraints);
} }
m_size = {size.width, size.height}; m_size = {size.width, size.height};
@ -638,11 +655,34 @@ void ReactNativeIsland::ShowInstanceLoading() noexcept {
InternalRootVisual().InsertAt(m_loadingVisual, m_hasRenderedVisual ? 1 : 0); InternalRootVisual().InsertAt(m_loadingVisual, m_hasRenderedVisual ? 1 : 0);
} }
void ReactNativeIsland::InitTextScaleMultiplier() noexcept {
m_uiSettings = winrt::Windows::UI::ViewManagement::UISettings();
m_textScaleMultiplier = static_cast<float>(m_uiSettings.TextScaleFactor());
m_textScaleChangedRevoker = m_uiSettings.TextScaleFactorChanged(
winrt::auto_revoke,
[this](const winrt::Windows::UI::ViewManagement::UISettings &uiSettings, const winrt::IInspectable &) {
if (m_context) {
m_context.UIDispatcher().Post(
[wkThis = get_weak(), textScaleMultiplier = static_cast<float>(uiSettings.TextScaleFactor())]() {
if (auto strongThis = wkThis.get()) {
strongThis->m_textScaleMultiplier = textScaleMultiplier;
strongThis->Arrange(strongThis->m_layoutConstraints, strongThis->m_viewportOffset);
}
});
}
});
}
winrt::Windows::Foundation::Size ReactNativeIsland::Measure( winrt::Windows::Foundation::Size ReactNativeIsland::Measure(
const winrt::Microsoft::ReactNative::LayoutConstraints &layoutConstraints, const winrt::Microsoft::ReactNative::LayoutConstraints &layoutConstraints,
const winrt::Windows::Foundation::Point &viewportOffset) const noexcept { const winrt::Windows::Foundation::Point &viewportOffset) const {
facebook::react::Size size{0, 0}; facebook::react::Size size{0, 0};
if (layoutConstraints.LayoutDirection != winrt::Microsoft::ReactNative::LayoutDirection::LeftToRight &&
layoutConstraints.LayoutDirection != winrt::Microsoft::ReactNative::LayoutDirection::RightToLeft &&
layoutConstraints.LayoutDirection != winrt::Microsoft::ReactNative::LayoutDirection::Undefined)
winrt::throw_hresult(E_INVALIDARG);
facebook::react::LayoutConstraints constraints; facebook::react::LayoutConstraints constraints;
ApplyConstraints(layoutConstraints, constraints); ApplyConstraints(layoutConstraints, constraints);
@ -650,15 +690,14 @@ winrt::Windows::Foundation::Size ReactNativeIsland::Measure(
if (auto fabricuiManager = ::Microsoft::ReactNative::FabricUIManager::FromProperties( if (auto fabricuiManager = ::Microsoft::ReactNative::FabricUIManager::FromProperties(
winrt::Microsoft::ReactNative::ReactPropertyBag(m_context.Properties()))) { winrt::Microsoft::ReactNative::ReactPropertyBag(m_context.Properties()))) {
facebook::react::LayoutContext context; facebook::react::LayoutContext context;
// TODO scaling factor context.fontSizeMultiplier = m_textScaleMultiplier;
context.pointScaleFactor = static_cast<facebook::react::Float>(m_scaleFactor); context.pointScaleFactor = static_cast<facebook::react::Float>(m_scaleFactor);
context.fontSizeMultiplier = static_cast<facebook::react::Float>(m_scaleFactor);
context.viewportOffset = {viewportOffset.X, viewportOffset.Y}; context.viewportOffset = {viewportOffset.X, viewportOffset.Y};
size = fabricuiManager->measureSurface(static_cast<facebook::react::SurfaceId>(m_rootTag), constraints, context); size = fabricuiManager->measureSurface(static_cast<facebook::react::SurfaceId>(m_rootTag), constraints, context);
} }
} else if (m_loadingVisual) { } else if (m_loadingVisual) {
size = MeasureLoading(layoutConstraints, m_scaleFactor); size = MeasureLoading(layoutConstraints);
} }
auto clampedSize = constraints.clamp(size); auto clampedSize = constraints.clamp(size);
@ -667,7 +706,12 @@ winrt::Windows::Foundation::Size ReactNativeIsland::Measure(
void ReactNativeIsland::Arrange( void ReactNativeIsland::Arrange(
const winrt::Microsoft::ReactNative::LayoutConstraints &layoutConstraints, const winrt::Microsoft::ReactNative::LayoutConstraints &layoutConstraints,
const winrt::Windows::Foundation::Point &viewportOffset) noexcept { const winrt::Windows::Foundation::Point &viewportOffset) {
if (layoutConstraints.LayoutDirection != winrt::Microsoft::ReactNative::LayoutDirection::LeftToRight &&
layoutConstraints.LayoutDirection != winrt::Microsoft::ReactNative::LayoutDirection::RightToLeft &&
layoutConstraints.LayoutDirection != winrt::Microsoft::ReactNative::LayoutDirection::Undefined)
winrt::throw_hresult(E_INVALIDARG);
m_layoutConstraints = layoutConstraints; m_layoutConstraints = layoutConstraints;
m_viewportOffset = viewportOffset; m_viewportOffset = viewportOffset;
facebook::react::LayoutConstraints fbLayoutConstraints; facebook::react::LayoutConstraints fbLayoutConstraints;
@ -677,8 +721,8 @@ void ReactNativeIsland::Arrange(
if (auto fabricuiManager = ::Microsoft::ReactNative::FabricUIManager::FromProperties( if (auto fabricuiManager = ::Microsoft::ReactNative::FabricUIManager::FromProperties(
winrt::Microsoft::ReactNative::ReactPropertyBag(m_context.Properties()))) { winrt::Microsoft::ReactNative::ReactPropertyBag(m_context.Properties()))) {
facebook::react::LayoutContext context; facebook::react::LayoutContext context;
context.fontSizeMultiplier = m_textScaleMultiplier;
context.pointScaleFactor = static_cast<facebook::react::Float>(m_scaleFactor); context.pointScaleFactor = static_cast<facebook::react::Float>(m_scaleFactor);
context.fontSizeMultiplier = static_cast<facebook::react::Float>(m_scaleFactor);
context.viewportOffset = {viewportOffset.X, viewportOffset.Y}; context.viewportOffset = {viewportOffset.X, viewportOffset.Y};
fabricuiManager->constraintSurfaceLayout( fabricuiManager->constraintSurfaceLayout(
@ -686,12 +730,11 @@ void ReactNativeIsland::Arrange(
} }
} else if (m_loadingVisual) { } else if (m_loadingVisual) {
// TODO: Resize to align loading // TODO: Resize to align loading
auto s = fbLayoutConstraints.clamp(MeasureLoading(layoutConstraints, m_scaleFactor)); auto s = fbLayoutConstraints.clamp(MeasureLoading(layoutConstraints));
NotifySizeChanged(); NotifySizeChanged();
} }
} }
#ifdef USE_WINUI3
winrt::Microsoft::UI::Content::ContentIsland ReactNativeIsland::Island() { winrt::Microsoft::UI::Content::ContentIsland ReactNativeIsland::Island() {
if (!m_compositor) { if (!m_compositor) {
return nullptr; return nullptr;
@ -745,6 +788,9 @@ winrt::Microsoft::UI::Content::ContentIsland ReactNativeIsland::Island() {
if (args.DidRasterizationScaleChange()) { if (args.DidRasterizationScaleChange()) {
pThis->ScaleFactor(island.RasterizationScale()); pThis->ScaleFactor(island.RasterizationScale());
} }
if (args.DidLayoutDirectionChange()) {
pThis->Arrange(pThis->m_layoutConstraints, pThis->m_viewportOffset);
}
#ifndef USE_EXPERIMENTAL_WINUI3 // Use this in place of Connected/Disconnected events for now. -- Its not quite what we #ifndef USE_EXPERIMENTAL_WINUI3 // Use this in place of Connected/Disconnected events for now. -- Its not quite what we
// want, but it will do for now. // want, but it will do for now.
if (args.DidSiteVisibleChange()) { if (args.DidSiteVisibleChange()) {
@ -777,7 +823,6 @@ winrt::Microsoft::UI::Content::ContentIsland ReactNativeIsland::Island() {
} }
return m_island; return m_island;
} }
#endif
void ReactNativeIsland::OnMounted() noexcept { void ReactNativeIsland::OnMounted() noexcept {
if (m_mounted) if (m_mounted)

Просмотреть файл

@ -10,6 +10,7 @@
#include <react/renderer/core/LayoutConstraints.h> #include <react/renderer/core/LayoutConstraints.h>
#include <winrt/Microsoft.ReactNative.Composition.Experimental.h> #include <winrt/Microsoft.ReactNative.Composition.Experimental.h>
#include <winrt/Microsoft.ReactNative.h> #include <winrt/Microsoft.ReactNative.h>
#include <winrt/Windows.UI.ViewManagement.h>
#include "CompositionEventHandler.h" #include "CompositionEventHandler.h"
#include "ReactHost/React.h" #include "ReactHost/React.h"
@ -46,10 +47,8 @@ struct ReactNativeIsland
ReactNativeIsland() noexcept; ReactNativeIsland() noexcept;
~ReactNativeIsland() noexcept; ~ReactNativeIsland() noexcept;
#ifdef USE_WINUI3
ReactNativeIsland(const winrt::Microsoft::UI::Composition::Compositor &compositor) noexcept; ReactNativeIsland(const winrt::Microsoft::UI::Composition::Compositor &compositor) noexcept;
winrt::Microsoft::UI::Content::ContentIsland Island(); winrt::Microsoft::UI::Content::ContentIsland Island();
#endif
// property ReactViewHost // property ReactViewHost
ReactNative::IReactViewHost ReactViewHost() noexcept; ReactNative::IReactViewHost ReactViewHost() noexcept;
@ -72,6 +71,8 @@ struct ReactNativeIsland
float ScaleFactor() noexcept; float ScaleFactor() noexcept;
void ScaleFactor(float value) noexcept; void ScaleFactor(float value) noexcept;
float FontSizeMultiplier() const noexcept;
winrt::event_token SizeChanged( winrt::event_token SizeChanged(
winrt::Windows::Foundation::EventHandler<winrt::Microsoft::ReactNative::RootViewSizeChangedEventArgs> const winrt::Windows::Foundation::EventHandler<winrt::Microsoft::ReactNative::RootViewSizeChangedEventArgs> const
&handler) noexcept; &handler) noexcept;
@ -90,10 +91,10 @@ struct ReactNativeIsland
winrt::Windows::Foundation::Size Measure( winrt::Windows::Foundation::Size Measure(
const winrt::Microsoft::ReactNative::LayoutConstraints &layoutConstraints, const winrt::Microsoft::ReactNative::LayoutConstraints &layoutConstraints,
const winrt::Windows::Foundation::Point &viewportOffset) const noexcept; const winrt::Windows::Foundation::Point &viewportOffset) const;
void Arrange( void Arrange(
const winrt::Microsoft::ReactNative::LayoutConstraints &layoutConstraints, const winrt::Microsoft::ReactNative::LayoutConstraints &layoutConstraints,
const winrt::Windows::Foundation::Point &viewportOffset) noexcept; const winrt::Windows::Foundation::Point &viewportOffset);
winrt::Microsoft::ReactNative::FocusNavigationResult NavigateFocus( winrt::Microsoft::ReactNative::FocusNavigationResult NavigateFocus(
const winrt::Microsoft::ReactNative::FocusNavigationRequest &request) noexcept; const winrt::Microsoft::ReactNative::FocusNavigationRequest &request) noexcept;
@ -143,6 +144,9 @@ struct ReactNativeIsland
winrt::IInspectable m_uiaProvider{nullptr}; winrt::IInspectable m_uiaProvider{nullptr};
int64_t m_rootTag{-1}; int64_t m_rootTag{-1};
float m_scaleFactor{1.0}; float m_scaleFactor{1.0};
float m_textScaleMultiplier{1.0};
winrt::Windows::UI::ViewManagement::UISettings::TextScaleFactorChanged_revoker m_textScaleChangedRevoker;
winrt::Windows::UI::ViewManagement::UISettings m_uiSettings{nullptr};
winrt::Windows::Foundation::Size m_size{0, 0}; winrt::Windows::Foundation::Size m_size{0, 0};
winrt::Microsoft::ReactNative::ReactContext m_context; winrt::Microsoft::ReactNative::ReactContext m_context;
winrt::Microsoft::ReactNative::IReactViewHost m_reactViewHost; winrt::Microsoft::ReactNative::IReactViewHost m_reactViewHost;
@ -168,6 +172,12 @@ struct ReactNativeIsland
void UpdateRootVisualSize() noexcept; void UpdateRootVisualSize() noexcept;
void UpdateLoadingVisualSize() noexcept; void UpdateLoadingVisualSize() noexcept;
Composition::Experimental::IDrawingSurfaceBrush CreateLoadingVisualBrush() noexcept; Composition::Experimental::IDrawingSurfaceBrush CreateLoadingVisualBrush() noexcept;
void ApplyConstraints(
const winrt::Microsoft::ReactNative::LayoutConstraints &layoutConstraintsIn,
facebook::react::LayoutConstraints &layoutConstraintsOut) const noexcept;
facebook::react::Size MeasureLoading(
const winrt::Microsoft::ReactNative::LayoutConstraints &layoutConstraints) const noexcept;
void InitTextScaleMultiplier() noexcept;
}; };
} // namespace winrt::Microsoft::ReactNative::implementation } // namespace winrt::Microsoft::ReactNative::implementation

Просмотреть файл

@ -40,8 +40,8 @@ winrt::Microsoft::ReactNative::ComponentView RootComponentView::Create(
return winrt::make<RootComponentView>(compContext, tag, reactContext); return winrt::make<RootComponentView>(compContext, tag, reactContext);
} }
RootComponentView *RootComponentView::rootComponentView() noexcept { RootComponentView *RootComponentView::rootComponentView() const noexcept {
return this; return const_cast<RootComponentView *>(this);
} }
void RootComponentView::updateLayoutMetrics( void RootComponentView::updateLayoutMetrics(
@ -211,6 +211,15 @@ winrt::IInspectable RootComponentView::UiaProviderFromPoint(const POINT &ptPixel
return winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(view)->EnsureUiaProvider(); return winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(view)->EnsureUiaProvider();
} }
float RootComponentView::FontSizeMultiplier() const noexcept {
if (auto rootView = m_wkRootView.get()) {
return winrt::get_self<winrt::Microsoft::ReactNative::implementation::ReactNativeIsland>(rootView)
->FontSizeMultiplier();
}
assert(false);
return 1.0f;
}
winrt::Microsoft::UI::Content::ContentIsland RootComponentView::parentContentIsland() noexcept { winrt::Microsoft::UI::Content::ContentIsland RootComponentView::parentContentIsland() noexcept {
if (auto rootView = m_wkRootView.get()) { if (auto rootView = m_wkRootView.get()) {
return winrt::get_self<winrt::Microsoft::ReactNative::implementation::ReactNativeIsland>(rootView)->Island(); return winrt::get_self<winrt::Microsoft::ReactNative::implementation::ReactNativeIsland>(rootView)->Island();

Просмотреть файл

@ -36,7 +36,7 @@ struct RootComponentView : RootComponentViewT<RootComponentView, ViewComponentVi
bool TryMoveFocus(bool next) noexcept; bool TryMoveFocus(bool next) noexcept;
RootComponentView *rootComponentView() noexcept override; RootComponentView *rootComponentView() const noexcept override;
winrt::Microsoft::UI::Content::ContentIsland parentContentIsland() noexcept; winrt::Microsoft::UI::Content::ContentIsland parentContentIsland() noexcept;
@ -46,6 +46,7 @@ struct RootComponentView : RootComponentViewT<RootComponentView, ViewComponentVi
HRESULT GetFragmentRoot(IRawElementProviderFragmentRoot **pRetVal) noexcept; HRESULT GetFragmentRoot(IRawElementProviderFragmentRoot **pRetVal) noexcept;
winrt::Microsoft::ReactNative::implementation::ClipState getClipState() noexcept override; winrt::Microsoft::ReactNative::implementation::ClipState getClipState() noexcept override;
float FontSizeMultiplier() const noexcept;
void updateLayoutMetrics( void updateLayoutMetrics(
facebook::react::LayoutMetrics const &layoutMetrics, facebook::react::LayoutMetrics const &layoutMetrics,

Просмотреть файл

@ -176,9 +176,8 @@ struct CompTextHost : public winrt::implements<CompTextHost, ITextHost> {
return false; return false;
} }
m_outer->m_caretVisual.Position( auto pt = m_outer->getClientOffset();
{x - (m_outer->m_layoutMetrics.frame.origin.x * m_outer->m_layoutMetrics.pointScaleFactor), m_outer->m_caretVisual.Position({x - pt.x, y - pt.y});
y - (m_outer->m_layoutMetrics.frame.origin.y * m_outer->m_layoutMetrics.pointScaleFactor)});
return true; return true;
} }
@ -262,7 +261,7 @@ struct CompTextHost : public winrt::implements<CompTextHost, ITextHost> {
//@cmember Retrieves the coordinates of a window's client area //@cmember Retrieves the coordinates of a window's client area
HRESULT TxGetClientRect(LPRECT prc) override { HRESULT TxGetClientRect(LPRECT prc) override {
*prc = m_outer->m_rcClient; *prc = m_outer->getClientRect();
return S_OK; return S_OK;
} }
@ -455,6 +454,7 @@ facebook::react::AttributedString WindowsTextInputComponentView::getAttributedSt
// Use BaseTextShadowNode to get attributed string from children // Use BaseTextShadowNode to get attributed string from children
auto childTextAttributes = facebook::react::TextAttributes::defaultTextAttributes(); auto childTextAttributes = facebook::react::TextAttributes::defaultTextAttributes();
childTextAttributes.fontSizeMultiplier = m_fontSizeMultiplier;
childTextAttributes.apply(windowsTextInputProps().textAttributes); childTextAttributes.apply(windowsTextInputProps().textAttributes);
@ -464,9 +464,9 @@ facebook::react::AttributedString WindowsTextInputComponentView::getAttributedSt
// BaseTextShadowNode only gets children. We must detect and prepend text // BaseTextShadowNode only gets children. We must detect and prepend text
// value attributes manually. // value attributes manually.
auto text = GetTextFromRichEdit(); auto text = GetTextFromRichEdit();
// if (!m_props->text.empty()) {
if (!text.empty()) { if (!text.empty()) {
auto textAttributes = facebook::react::TextAttributes::defaultTextAttributes(); auto textAttributes = facebook::react::TextAttributes::defaultTextAttributes();
textAttributes.fontSizeMultiplier = m_fontSizeMultiplier;
textAttributes.apply(windowsTextInputProps().textAttributes); textAttributes.apply(windowsTextInputProps().textAttributes);
auto fragment = facebook::react::AttributedString::Fragment{}; auto fragment = facebook::react::AttributedString::Fragment{};
fragment.string = text; fragment.string = text;
@ -492,35 +492,7 @@ WindowsTextInputComponentView::WindowsTextInputComponentView(
compContext, compContext,
tag, tag,
reactContext, reactContext,
ComponentViewFeatures::Default & ~ComponentViewFeatures::Background) { ComponentViewFeatures::Default & ~ComponentViewFeatures::Background) {}
/*
m_textChangedRevoker =
m_element.TextChanged(winrt::auto_revoke, [this](auto sender, xaml::Controls::TextChangedEventArgs args) {
auto data = m_state->getData();
data.attributedString = getAttributedString();
data.mostRecentEventCount = m_nativeEventCount;
m_state->updateState(std::move(data));
if (m_eventEmitter && !m_comingFromJS) {
auto emitter = std::static_pointer_cast<const facebook::react::WindowsTextInputEventEmitter>(m_eventEmitter);
facebook::react::WindowsTextInputEventEmitter::OnChange onChangeArgs;
onChangeArgs.text = winrt::to_string(m_element.Text());
onChangeArgs.eventCount = ++m_nativeEventCount;
emitter->onChange(onChangeArgs);
}
});
m_SelectionChangedRevoker = m_element.SelectionChanged(winrt::auto_revoke, [this](auto sender, auto args) {
if (m_eventEmitter) {
auto emitter = std::static_pointer_cast<const facebook::react::WindowsTextInputEventEmitter>(m_eventEmitter);
facebook::react::WindowsTextInputEventEmitter::OnSelectionChange onSelectionChangeArgs;
onSelectionChangeArgs.selection.start = m_element.SelectionStart();
onSelectionChangeArgs.selection.end = m_element.SelectionStart() + m_element.SelectionLength();
emitter->onSelectionChange(onSelectionChangeArgs);
}
});
*/
}
void WindowsTextInputComponentView::HandleCommand( void WindowsTextInputComponentView::HandleCommand(
const winrt::Microsoft::ReactNative::HandleCommandArgs &args) noexcept { const winrt::Microsoft::ReactNative::HandleCommandArgs &args) noexcept {
@ -531,26 +503,27 @@ void WindowsTextInputComponentView::HandleCommand(
auto commandName = args.CommandName(); auto commandName = args.CommandName();
if (commandName == L"setTextAndSelection") { if (commandName == L"setTextAndSelection") {
int eventCount, begin, end; int eventCount, begin, end;
winrt::hstring text; std::optional<winrt::hstring> text;
winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), eventCount, text, begin, end); winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), eventCount, text, begin, end);
if (eventCount >= m_nativeEventCount) { if (eventCount >= m_nativeEventCount) {
m_comingFromJS = true; m_comingFromJS = true;
UpdateText(winrt::to_string(text)); {
if (text.has_value()) {
DrawBlock db(*this);
UpdateText(winrt::to_string(text.value()));
}
SELCHANGE sc; SELCHANGE sc;
memset(&sc, 0, sizeof(sc)); memset(&sc, 0, sizeof(sc));
sc.chrg.cpMin = static_cast<LONG>(begin); sc.chrg.cpMin = static_cast<LONG>(begin);
sc.chrg.cpMax = static_cast<LONG>(end); sc.chrg.cpMax = static_cast<LONG>(end);
sc.seltyp = (begin == end) ? SEL_EMPTY : SEL_TEXT; sc.seltyp = (begin == end) ? SEL_EMPTY : SEL_TEXT;
LRESULT res; LRESULT res;
/* winrt::check_hresult(
winrt::check_hresult(m_textServices->TxSendMessage( m_textServices->TxSendMessage(EM_SETSEL, static_cast<WPARAM>(begin), static_cast<LPARAM>(end), &res));
EM_SELCHANGE, 0 , reinterpret_cast<WPARAM>(&sc), &res)); }
*/
winrt::check_hresult(
m_textServices->TxSendMessage(EM_SETSEL, static_cast<WPARAM>(begin), static_cast<LPARAM>(end), &res));
m_comingFromJS = false; m_comingFromJS = false;
} }
@ -989,37 +962,35 @@ void WindowsTextInputComponentView::updateProps(
*std::static_pointer_cast<const facebook::react::WindowsTextInputProps>(oldProps ? oldProps : viewProps()); *std::static_pointer_cast<const facebook::react::WindowsTextInputProps>(oldProps ? oldProps : viewProps());
const auto &newTextInputProps = *std::static_pointer_cast<const facebook::react::WindowsTextInputProps>(props); const auto &newTextInputProps = *std::static_pointer_cast<const facebook::react::WindowsTextInputProps>(props);
DWORD propBitsMask = 0;
DWORD propBits = 0;
Super::updateProps(props, oldProps); Super::updateProps(props, oldProps);
if (!facebook::react::floatEquality( if (!facebook::react::floatEquality(
oldTextInputProps.textAttributes.fontSize, newTextInputProps.textAttributes.fontSize) || oldTextInputProps.textAttributes.fontSize, newTextInputProps.textAttributes.fontSize) ||
(oldTextInputProps.textAttributes.allowFontScaling != newTextInputProps.textAttributes.allowFontScaling) ||
oldTextInputProps.textAttributes.fontWeight != newTextInputProps.textAttributes.fontWeight) { oldTextInputProps.textAttributes.fontWeight != newTextInputProps.textAttributes.fontWeight) {
propBitsMask |= TXTBIT_CHARFORMATCHANGE; m_propBitsMask |= TXTBIT_CHARFORMATCHANGE;
propBits |= TXTBIT_CHARFORMATCHANGE; m_propBits |= TXTBIT_CHARFORMATCHANGE;
} }
if (oldTextInputProps.secureTextEntry != newTextInputProps.secureTextEntry) { if (oldTextInputProps.secureTextEntry != newTextInputProps.secureTextEntry) {
propBitsMask |= TXTBIT_USEPASSWORD; m_propBitsMask |= TXTBIT_USEPASSWORD;
if (newTextInputProps.secureTextEntry) { if (newTextInputProps.secureTextEntry) {
propBits |= TXTBIT_USEPASSWORD; m_propBits |= TXTBIT_USEPASSWORD;
} }
} }
if (oldTextInputProps.multiline != newTextInputProps.multiline) { if (oldTextInputProps.multiline != newTextInputProps.multiline) {
m_multiline = newTextInputProps.multiline; m_multiline = newTextInputProps.multiline;
propBitsMask |= TXTBIT_MULTILINE | TXTBIT_WORDWRAP; m_propBitsMask |= TXTBIT_MULTILINE | TXTBIT_WORDWRAP;
if (newTextInputProps.multiline) { if (newTextInputProps.multiline) {
propBits |= TXTBIT_MULTILINE | TXTBIT_WORDWRAP; m_propBits |= TXTBIT_MULTILINE | TXTBIT_WORDWRAP;
} }
} }
if (oldTextInputProps.editable != newTextInputProps.editable) { if (oldTextInputProps.editable != newTextInputProps.editable) {
propBitsMask |= TXTBIT_READONLY; m_propBitsMask |= TXTBIT_READONLY;
if (!newTextInputProps.editable) { if (!newTextInputProps.editable) {
propBits |= TXTBIT_READONLY; m_propBits |= TXTBIT_READONLY;
} }
} }
@ -1051,62 +1022,7 @@ void WindowsTextInputComponentView::updateProps(
m_submitKeyEvents.clear(); m_submitKeyEvents.clear();
} }
/* UpdatePropertyBits();
if (oldTextInputProps.textAttributes.foregroundColor != newTextInputProps.textAttributes.foregroundColor) {
if (newTextInputProps.textAttributes.foregroundColor)
m_element.Foreground(newTextInputProps.textAttributes.foregroundColor.AsWindowsBrush());
else
m_element.ClearValue(::xaml::Controls::TextBlock::ForegroundProperty());
}
if (oldTextInputProps.textAttributes.fontStyle != newTextInputProps.textAttributes.fontStyle) {
switch (newTextInputProps.textAttributes.fontStyle.value_or(facebook::react::FontStyle::Normal)) {
case facebook::react::FontStyle::Italic:
m_element.FontStyle(winrt::Windows::UI::Text::FontStyle::Italic);
break;
case facebook::react::FontStyle::Normal:
m_element.FontStyle(winrt::Windows::UI::Text::FontStyle::Normal);
break;
case facebook::react::FontStyle::Oblique:
m_element.FontStyle(winrt::Windows::UI::Text::FontStyle::Oblique);
break;
default:
assert(false);
}
}
if (oldTextInputProps.textAttributes.fontFamily != newTextInputProps.textAttributes.fontFamily) {
if (newTextInputProps.textAttributes.fontFamily.empty())
m_element.FontFamily(xaml::Media::FontFamily(L"Segoe UI"));
else
m_element.FontFamily(xaml::Media::FontFamily(
Microsoft::Common::Unicode::Utf8ToUtf16(newTextInputProps.textAttributes.fontFamily)));
}
if (oldTextInputProps.allowFontScaling != newTextInputProps.allowFontScaling) {
m_element.IsTextScaleFactorEnabled(newTextInputProps.allowFontScaling);
}
if (oldTextInputProps.selection.start != newTextInputProps.selection.start ||
oldTextInputProps.selection.end != newTextInputProps.selection.end) {
m_element.Select(
newTextInputProps.selection.start, newTextInputProps.selection.end - newTextInputProps.selection.start);
}
if (oldTextInputProps.autoCapitalize != newTextInputProps.autoCapitalize) {
if (newTextInputProps.autoCapitalize == "characters") {
m_element.CharacterCasing(xaml::Controls::CharacterCasing::Upper);
} else { // anything else turns off autoCap (should be "None" but
// we don't support "words"/"sentences" yet)
m_element.CharacterCasing(xaml::Controls::CharacterCasing::Normal);
}
}
*/
if (propBitsMask != 0) {
DrawBlock db(*this);
winrt::check_hresult(m_textServices->OnTxPropertyBitsChange(propBitsMask, propBits));
}
} }
void WindowsTextInputComponentView::updateState( void WindowsTextInputComponentView::updateState(
@ -1116,7 +1032,6 @@ void WindowsTextInputComponentView::updateState(
if (!m_state) { if (!m_state) {
assert(false && "State is `null` for <TextInput> component."); assert(false && "State is `null` for <TextInput> component.");
// m_element.Text(L"");
return; return;
} }
@ -1124,14 +1039,19 @@ void WindowsTextInputComponentView::updateState(
m_mostRecentEventCount = m_state->getData().mostRecentEventCount; m_mostRecentEventCount = m_state->getData().mostRecentEventCount;
} }
if (auto root = rootComponentView()) {
auto fontSizeMultiplier = root->FontSizeMultiplier();
if (fontSizeMultiplier != m_fontSizeMultiplier) {
fontSizeMultiplier = m_fontSizeMultiplier;
m_propBitsMask |= TXTBIT_CHARFORMATCHANGE;
m_propBits |= TXTBIT_CHARFORMATCHANGE;
}
}
if (m_mostRecentEventCount == m_state->getData().mostRecentEventCount) { if (m_mostRecentEventCount == m_state->getData().mostRecentEventCount) {
m_comingFromState = true; m_comingFromState = true;
// Only handle single/empty fragments right now -- ignore the other fragments auto &fragments = m_state->getData().attributedString.getFragments();
UpdateText(fragments.size() ? fragments[0].string : "");
UpdateText(
m_state->getData().attributedString.getFragments().size()
? m_state->getData().attributedString.getFragments()[0].string
: "");
m_comingFromState = false; m_comingFromState = false;
} }
@ -1246,12 +1166,41 @@ std::string WindowsTextInputComponentView::GetTextFromRichEdit() const noexcept
void WindowsTextInputComponentView::FinalizeUpdates( void WindowsTextInputComponentView::FinalizeUpdates(
winrt::Microsoft::ReactNative::ComponentViewUpdateMask updateMask) noexcept { winrt::Microsoft::ReactNative::ComponentViewUpdateMask updateMask) noexcept {
Super::FinalizeUpdates(updateMask); Super::FinalizeUpdates(updateMask);
ensureDrawingSurface(); InternalFinalize();
if (m_needsRedraw) { }
DrawText();
void WindowsTextInputComponentView::UpdatePropertyBits() noexcept {
if (m_propBitsMask != 0) {
DrawBlock db(*this);
winrt::check_hresult(m_textServices->OnTxPropertyBitsChange(m_propBitsMask, m_propBits));
m_propBitsMask = 0;
m_propBits = 0;
} }
} }
void WindowsTextInputComponentView::InternalFinalize() noexcept {
if (m_mounted) {
UpdatePropertyBits();
ensureDrawingSurface();
if (m_needsRedraw) {
DrawText();
}
}
}
void WindowsTextInputComponentView::onMounted() noexcept {
Super::onMounted();
auto fontSizeMultiplier = rootComponentView()->FontSizeMultiplier();
if (m_fontSizeMultiplier != fontSizeMultiplier) {
m_fontSizeMultiplier = fontSizeMultiplier;
m_propBitsMask |= TXTBIT_CHARFORMATCHANGE;
m_propBits |= TXTBIT_CHARFORMATCHANGE;
}
InternalFinalize();
}
std::optional<std::string> WindowsTextInputComponentView::getAccessiblityValue() noexcept { std::optional<std::string> WindowsTextInputComponentView::getAccessiblityValue() noexcept {
return GetTextFromRichEdit(); return GetTextFromRichEdit();
} }
@ -1294,9 +1243,9 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
// set font size -- 15 to convert twips to pt // set font size -- 15 to convert twips to pt
const auto &props = windowsTextInputProps(); const auto &props = windowsTextInputProps();
float fontSize = props.textAttributes.fontSize; float fontSize = m_fontSizeMultiplier *
if (std::isnan(fontSize)) (std::isnan(props.textAttributes.fontSize) ? facebook::react::TextAttributes::defaultTextAttributes().fontSize
fontSize = facebook::react::TextAttributes::defaultTextAttributes().fontSize; : props.textAttributes.fontSize);
// TODO get fontSize from props.textAttributes, or defaultTextAttributes, or fragment? // TODO get fontSize from props.textAttributes, or defaultTextAttributes, or fragment?
cfNew.dwMask |= CFM_SIZE; cfNew.dwMask |= CFM_SIZE;
cfNew.yHeight = static_cast<LONG>(fontSize * 15); cfNew.yHeight = static_cast<LONG>(fontSize * 15);
@ -1363,8 +1312,8 @@ void WindowsTextInputComponentView::ensureDrawingSurface() noexcept {
winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized, winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized,
winrt::Windows::Graphics::DirectX::DirectXAlphaMode::Premultiplied); winrt::Windows::Graphics::DirectX::DirectXAlphaMode::Premultiplied);
m_rcClient = getClientRect(); auto rc = getClientRect();
winrt::check_hresult(m_textServices->OnTxInPlaceActivate(&m_rcClient)); winrt::check_hresult(m_textServices->OnTxInPlaceActivate(&rc));
LRESULT lresult; LRESULT lresult;
winrt::check_hresult( winrt::check_hresult(
@ -1394,6 +1343,7 @@ winrt::com_ptr<::IDWriteTextLayout> WindowsTextInputComponentView::CreatePlaceho
if (std::isnan(props.textAttributes.fontSize)) { if (std::isnan(props.textAttributes.fontSize)) {
textAttributes.fontSize = 12.0f; textAttributes.fontSize = 12.0f;
} }
textAttributes.fontSizeMultiplier = m_fontSizeMultiplier;
fragment1.string = props.placeholder; fragment1.string = props.placeholder;
fragment1.textAttributes = textAttributes; fragment1.textAttributes = textAttributes;
attributedString.appendFragment(fragment1); attributedString.appendFragment(fragment1);
@ -1443,7 +1393,11 @@ void WindowsTextInputComponentView::DrawText() noexcept {
static_cast<LONG>(offset.x) + static_cast<LONG>(m_imgWidth), static_cast<LONG>(offset.x) + static_cast<LONG>(m_imgWidth),
static_cast<LONG>(offset.y) + static_cast<LONG>(m_imgHeight)}; static_cast<LONG>(offset.y) + static_cast<LONG>(m_imgHeight)};
winrt::check_hresult(m_textServices->OnTxInPlaceActivate(&rcClient)); {
m_cDrawBlock++; // Dont use AutoDrawBlock as we are already in draw, and dont need to draw again.
winrt::check_hresult(m_textServices->OnTxInPlaceActivate(&rcClient));
m_cDrawBlock--;
}
const auto &props = windowsTextInputProps(); const auto &props = windowsTextInputProps();
if (facebook::react::isColorMeaningful(props.backgroundColor)) { if (facebook::react::isColorMeaningful(props.backgroundColor)) {

Просмотреть файл

@ -65,6 +65,7 @@ struct WindowsTextInputComponentView
void OnKeyUp(const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept override; void OnKeyUp(const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept override;
void OnCharacterReceived(const winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs void OnCharacterReceived(const winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs
&args) noexcept override; &args) noexcept override;
void onMounted() noexcept override;
std::optional<std::string> getAccessiblityValue() noexcept override; std::optional<std::string> getAccessiblityValue() noexcept override;
void setAcccessiblityValue(std::string &&value) noexcept override; void setAcccessiblityValue(std::string &&value) noexcept override;
@ -100,6 +101,8 @@ struct WindowsTextInputComponentView
const facebook::react::SharedColor &foregroundColor) noexcept; const facebook::react::SharedColor &foregroundColor) noexcept;
bool ShouldSubmit( bool ShouldSubmit(
const winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs &args) noexcept; const winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs &args) noexcept;
void InternalFinalize() noexcept;
void UpdatePropertyBits() noexcept;
winrt::Windows::UI::Composition::CompositionSurfaceBrush m_brush{nullptr}; winrt::Windows::UI::Composition::CompositionSurfaceBrush m_brush{nullptr};
winrt::Microsoft::ReactNative::Composition::Experimental::ICaretVisual m_caretVisual{nullptr}; winrt::Microsoft::ReactNative::Composition::Experimental::ICaretVisual m_caretVisual{nullptr};
@ -113,7 +116,7 @@ struct WindowsTextInputComponentView
winrt::com_ptr<ITextServices2> m_textServices; winrt::com_ptr<ITextServices2> m_textServices;
unsigned int m_imgWidth{0}, m_imgHeight{0}; unsigned int m_imgWidth{0}, m_imgHeight{0};
std::shared_ptr<facebook::react::WindowsTextInputShadowNode::ConcreteState const> m_state; std::shared_ptr<facebook::react::WindowsTextInputShadowNode::ConcreteState const> m_state;
RECT m_rcClient; float m_fontSizeMultiplier{1.0};
int64_t m_mostRecentEventCount{0}; int64_t m_mostRecentEventCount{0};
int m_nativeEventCount{0}; int m_nativeEventCount{0};
bool m_comingFromJS{false}; bool m_comingFromJS{false};
@ -123,6 +126,8 @@ struct WindowsTextInputComponentView
bool m_drawing{false}; bool m_drawing{false};
bool m_clearTextOnSubmit{false}; bool m_clearTextOnSubmit{false};
bool m_multiline{false}; bool m_multiline{false};
DWORD m_propBitsMask{0};
DWORD m_propBits{0};
std::vector<facebook::react::CompWindowsTextInputSubmitKeyEventsStruct> m_submitKeyEvents; std::vector<facebook::react::CompWindowsTextInputSubmitKeyEventsStruct> m_submitKeyEvents;
}; };

Просмотреть файл

@ -23,10 +23,11 @@ void WindowsTextInputShadowNode::setContextContainer(ContextContainer *contextCo
m_contextContainer = contextContainer; m_contextContainer = contextContainer;
} }
AttributedString WindowsTextInputShadowNode::getAttributedString() const { AttributedString WindowsTextInputShadowNode::getAttributedString(const LayoutContext &layoutContext) const {
// Use BaseTextShadowNode to get attributed string from children // Use BaseTextShadowNode to get attributed string from children
auto childTextAttributes = TextAttributes::defaultTextAttributes(); auto childTextAttributes = TextAttributes::defaultTextAttributes();
childTextAttributes.fontSizeMultiplier = layoutContext.fontSizeMultiplier;
childTextAttributes.apply(getConcreteProps().textAttributes); childTextAttributes.apply(getConcreteProps().textAttributes);
// Don't propagate the background color of the TextInput onto the attributed // Don't propagate the background color of the TextInput onto the attributed
@ -42,6 +43,7 @@ AttributedString WindowsTextInputShadowNode::getAttributedString() const {
// value attributes manually. // value attributes manually.
if (!getConcreteProps().text.empty()) { if (!getConcreteProps().text.empty()) {
auto textAttributes = TextAttributes::defaultTextAttributes(); auto textAttributes = TextAttributes::defaultTextAttributes();
textAttributes.fontSizeMultiplier = layoutContext.fontSizeMultiplier;
textAttributes.apply(getConcreteProps().textAttributes); textAttributes.apply(getConcreteProps().textAttributes);
auto fragment = AttributedString::Fragment{}; auto fragment = AttributedString::Fragment{};
fragment.string = getConcreteProps().text; fragment.string = getConcreteProps().text;
@ -63,7 +65,7 @@ AttributedString WindowsTextInputShadowNode::getAttributedString() const {
// display at all. // display at all.
// TODO T67606511: We will redefine the measurement of empty strings as part // TODO T67606511: We will redefine the measurement of empty strings as part
// of T67606511 // of T67606511
AttributedString WindowsTextInputShadowNode::getPlaceholderAttributedString() const { AttributedString WindowsTextInputShadowNode::getPlaceholderAttributedString(const LayoutContext &layoutContext) const {
// Return placeholder text, since text and children are empty. // Return placeholder text, since text and children are empty.
auto textAttributedString = AttributedString{}; auto textAttributedString = AttributedString{};
auto fragment = AttributedString::Fragment{}; auto fragment = AttributedString::Fragment{};
@ -74,6 +76,7 @@ AttributedString WindowsTextInputShadowNode::getPlaceholderAttributedString() co
} }
auto textAttributes = TextAttributes::defaultTextAttributes(); auto textAttributes = TextAttributes::defaultTextAttributes();
textAttributes.fontSizeMultiplier = layoutContext.fontSizeMultiplier;
textAttributes.apply(getConcreteProps().textAttributes); textAttributes.apply(getConcreteProps().textAttributes);
// If there's no text, it's possible that this Fragment isn't actually // If there's no text, it's possible that this Fragment isn't actually
@ -90,10 +93,10 @@ void WindowsTextInputShadowNode::setTextLayoutManager(SharedTextLayoutManager te
m_textLayoutManager = std::move(textLayoutManager); m_textLayoutManager = std::move(textLayoutManager);
} }
AttributedString WindowsTextInputShadowNode::getMostRecentAttributedString() const { AttributedString WindowsTextInputShadowNode::getMostRecentAttributedString(const LayoutContext &layoutContext) const {
const auto &state = getStateData(); const auto &state = getStateData();
auto reactTreeAttributedString = getAttributedString(); auto reactTreeAttributedString = getAttributedString(layoutContext);
// Sometimes the treeAttributedString will only differ from the state // Sometimes the treeAttributedString will only differ from the state
// not by inherent properties (string or prop attributes), but by the frame of // not by inherent properties (string or prop attributes), but by the frame of
@ -105,10 +108,10 @@ AttributedString WindowsTextInputShadowNode::getMostRecentAttributedString() con
return (!treeAttributedStringChanged ? state.attributedString : reactTreeAttributedString); return (!treeAttributedStringChanged ? state.attributedString : reactTreeAttributedString);
} }
void WindowsTextInputShadowNode::updateStateIfNeeded() { void WindowsTextInputShadowNode::updateStateIfNeeded(const LayoutContext &layoutContext) {
ensureUnsealed(); ensureUnsealed();
auto reactTreeAttributedString = getAttributedString(); auto reactTreeAttributedString = getAttributedString(layoutContext);
const auto &state = getStateData(); const auto &state = getStateData();
// Tree is often out of sync with the value of the TextInput. // Tree is often out of sync with the value of the TextInput.
@ -129,13 +132,13 @@ void WindowsTextInputShadowNode::updateStateIfNeeded() {
// in the AttributedString, and when State is updated, it needs some way to // in the AttributedString, and when State is updated, it needs some way to
// reconstruct a Fragment with default TextAttributes. // reconstruct a Fragment with default TextAttributes.
auto defaultTextAttributes = TextAttributes::defaultTextAttributes(); auto defaultTextAttributes = TextAttributes::defaultTextAttributes();
defaultTextAttributes.fontSizeMultiplier = layoutContext.fontSizeMultiplier;
defaultTextAttributes.apply(getConcreteProps().textAttributes); defaultTextAttributes.apply(getConcreteProps().textAttributes);
auto newEventCount = state.reactTreeAttributedString.isContentEqual(reactTreeAttributedString) auto newEventCount = state.reactTreeAttributedString.isContentEqual(reactTreeAttributedString)
? 0 ? 0
: getConcreteProps().mostRecentEventCount; : getConcreteProps().mostRecentEventCount;
auto newAttributedString = getMostRecentAttributedString(); auto newAttributedString = getMostRecentAttributedString(layoutContext);
// Even if we're here and updating state, it may be only to update the layout // Even if we're here and updating state, it may be only to update the layout
// manager If that is the case, make sure we don't update text: pass in the // manager If that is the case, make sure we don't update text: pass in the
@ -172,10 +175,10 @@ Size WindowsTextInputShadowNode::measureContent(
// during layout, but not during `measure`. If State is out-of-date in layout, // during layout, but not during `measure`. If State is out-of-date in layout,
// it's too late: measure will have already operated on old State. Thus, we // it's too late: measure will have already operated on old State. Thus, we
// use the same value here that we *will* use in layout to update the state. // use the same value here that we *will* use in layout to update the state.
AttributedString attributedString = getMostRecentAttributedString(); AttributedString attributedString = getMostRecentAttributedString(layoutContext);
if (attributedString.isEmpty()) { if (attributedString.isEmpty()) {
attributedString = getPlaceholderAttributedString(); attributedString = getPlaceholderAttributedString(layoutContext);
} }
if (attributedString.isEmpty() && getStateData().mostRecentEventCount != 0) { if (attributedString.isEmpty() && getStateData().mostRecentEventCount != 0) {
@ -194,7 +197,7 @@ Size WindowsTextInputShadowNode::measureContent(
} }
void WindowsTextInputShadowNode::layout(LayoutContext layoutContext) { void WindowsTextInputShadowNode::layout(LayoutContext layoutContext) {
updateStateIfNeeded(); updateStateIfNeeded(layoutContext);
ConcreteViewShadowNode::layout(layoutContext); ConcreteViewShadowNode::layout(layoutContext);
} }

Просмотреть файл

@ -42,8 +42,8 @@ class WindowsTextInputShadowNode final : public ConcreteViewShadowNode<
/* /*
* Returns a `AttributedString` which represents text content of the node. * Returns a `AttributedString` which represents text content of the node.
*/ */
AttributedString getAttributedString() const; AttributedString getAttributedString(const LayoutContext &layoutContext) const;
AttributedString getPlaceholderAttributedString() const; AttributedString getPlaceholderAttributedString(const LayoutContext &layoutContext) const;
/* /*
* Associates a shared TextLayoutManager with the node. * Associates a shared TextLayoutManager with the node.
@ -63,13 +63,13 @@ class WindowsTextInputShadowNode final : public ConcreteViewShadowNode<
/** /**
* Get the most up-to-date attributed string for measurement and State. * Get the most up-to-date attributed string for measurement and State.
*/ */
AttributedString getMostRecentAttributedString() const; AttributedString getMostRecentAttributedString(const LayoutContext &layoutContext) const;
/* /*
* Creates a `State` object (with `AttributedText` and * Creates a `State` object (with `AttributedText` and
* `TextLayoutManager`) if needed. * `TextLayoutManager`) if needed.
*/ */
void updateStateIfNeeded(); void updateStateIfNeeded(const LayoutContext &layoutContext);
SharedTextLayoutManager m_textLayoutManager; SharedTextLayoutManager m_textLayoutManager;

Просмотреть файл

@ -31,17 +31,4 @@ WindowsTextInputState::WindowsTextInputState(
defaultThemePaddingTop(defaultThemePaddingTop), defaultThemePaddingTop(defaultThemePaddingTop),
defaultThemePaddingBottom(defaultThemePaddingBottom) {} defaultThemePaddingBottom(defaultThemePaddingBottom) {}
WindowsTextInputState::WindowsTextInputState(const WindowsTextInputState &previousState, const folly::dynamic &data)
: mostRecentEventCount(data.getDefault("mostRecentEventCount", previousState.mostRecentEventCount).getInt()),
cachedAttributedStringId(data.getDefault("opaqueCacheId", previousState.cachedAttributedStringId).getInt()),
attributedString(previousState.attributedString),
reactTreeAttributedString(previousState.reactTreeAttributedString),
paragraphAttributes(previousState.paragraphAttributes),
defaultThemePaddingStart(
data.getDefault("themePaddingStart", previousState.defaultThemePaddingStart).getDouble()),
defaultThemePaddingEnd(data.getDefault("themePaddingEnd", previousState.defaultThemePaddingEnd).getDouble()),
defaultThemePaddingTop(data.getDefault("themePaddingTop", previousState.defaultThemePaddingTop).getDouble()),
defaultThemePaddingBottom(
data.getDefault("themePaddingBottom", previousState.defaultThemePaddingBottom).getDouble()){};
} // namespace facebook::react } // namespace facebook::react

Просмотреть файл

@ -66,9 +66,6 @@ class WindowsTextInputState final {
double defaultThemePaddingBottom); double defaultThemePaddingBottom);
WindowsTextInputState() = default; WindowsTextInputState() = default;
WindowsTextInputState(const WindowsTextInputState &previousState, const folly::dynamic &data);
folly::dynamic getDynamic() const;
MapBuffer getMapBuffer() const;
}; };
} // namespace facebook::react } // namespace facebook::react

Просмотреть файл

@ -10,6 +10,7 @@
#include <Fabric/ComponentView.h> #include <Fabric/ComponentView.h>
#include <Fabric/Composition/CompositionUIService.h> #include <Fabric/Composition/CompositionUIService.h>
#include <Fabric/Composition/CompositionViewComponentView.h> #include <Fabric/Composition/CompositionViewComponentView.h>
#include <Fabric/Composition/ReactNativeIsland.h>
#include <Fabric/Composition/RootComponentView.h> #include <Fabric/Composition/RootComponentView.h>
#include <Fabric/FabricUIManagerModule.h> #include <Fabric/FabricUIManagerModule.h>
#include <Fabric/ReactNativeConfigProperties.h> #include <Fabric/ReactNativeConfigProperties.h>
@ -141,7 +142,7 @@ void FabricUIManager::startSurface(
facebook::react::LayoutContext layoutContext; facebook::react::LayoutContext layoutContext;
layoutContext.pointScaleFactor = rootView.ScaleFactor(); layoutContext.pointScaleFactor = rootView.ScaleFactor();
layoutContext.fontSizeMultiplier = rootView.ScaleFactor(); layoutContext.fontSizeMultiplier = rootView.FontSizeMultiplier();
m_surfaceManager->startSurface( m_surfaceManager->startSurface(
surfaceId, surfaceId,

Просмотреть файл

@ -41,7 +41,10 @@ void TextLayoutManager::GetTextLayout(
static_cast<facebook::react::FontWeight>(DWRITE_FONT_WEIGHT_REGULAR))), static_cast<facebook::react::FontWeight>(DWRITE_FONT_WEIGHT_REGULAR))),
style, style,
DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
outerFragment.textAttributes.fontSize, (outerFragment.textAttributes.allowFontScaling.value_or(true) &&
!std::isnan(outerFragment.textAttributes.fontSizeMultiplier))
? (outerFragment.textAttributes.fontSizeMultiplier * outerFragment.textAttributes.fontSize)
: outerFragment.textAttributes.fontSize,
L"", L"",
spTextFormat.put())); spTextFormat.put()));
@ -116,7 +119,11 @@ void TextLayoutManager::GetTextLayout(
attributes.fontWeight.value_or(static_cast<facebook::react::FontWeight>(DWRITE_FONT_WEIGHT_REGULAR))), attributes.fontWeight.value_or(static_cast<facebook::react::FontWeight>(DWRITE_FONT_WEIGHT_REGULAR))),
range)); range));
winrt::check_hresult(spTextLayout->SetFontStyle(fragmentStyle, range)); winrt::check_hresult(spTextLayout->SetFontStyle(fragmentStyle, range));
winrt::check_hresult(spTextLayout->SetFontSize(attributes.fontSize, range)); winrt::check_hresult(spTextLayout->SetFontSize(
(attributes.allowFontScaling.value_or(true) && !std::isnan(attributes.fontSizeMultiplier))
? (attributes.fontSizeMultiplier * attributes.fontSize)
: attributes.fontSize,
range));
if (!isnan(attributes.letterSpacing)) { if (!isnan(attributes.letterSpacing)) {
winrt::check_hresult( winrt::check_hresult(

Просмотреть файл

@ -25,6 +25,7 @@ void UpdateRootViewSizeToAppWindow(
if (window.Presenter().as<winrt::Microsoft::UI::Windowing::OverlappedPresenter>().State() != if (window.Presenter().as<winrt::Microsoft::UI::Windowing::OverlappedPresenter>().State() !=
winrt::Microsoft::UI::Windowing::OverlappedPresenterState::Minimized) { winrt::Microsoft::UI::Windowing::OverlappedPresenterState::Minimized) {
winrt::Microsoft::ReactNative::LayoutConstraints constraints; winrt::Microsoft::ReactNative::LayoutConstraints constraints;
constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::Undefined;
constraints.MaximumSize = constraints.MinimumSize = size; constraints.MaximumSize = constraints.MinimumSize = size;
rootView.Arrange(constraints, {0, 0}); rootView.Arrange(constraints, {0, 0});
} }

Просмотреть файл

@ -98,6 +98,8 @@ namespace Microsoft.ReactNative
DOC_STRING("ScaleFactor for this windows (DPI/96)") DOC_STRING("ScaleFactor for this windows (DPI/96)")
Single ScaleFactor {get; set;}; Single ScaleFactor {get; set;};
Single FontSizeMultiplier { get; };
DOC_STRING("Move focus to this @ReactNativeIsland") DOC_STRING("Move focus to this @ReactNativeIsland")
FocusNavigationResult NavigateFocus(FocusNavigationRequest request); FocusNavigationResult NavigateFocus(FocusNavigationRequest request);

Просмотреть файл

@ -35,6 +35,7 @@ void UpdateRootViewSizeToAppWindow(
if (window.Presenter().as<winrt::Microsoft::UI::Windowing::OverlappedPresenter>().State() != if (window.Presenter().as<winrt::Microsoft::UI::Windowing::OverlappedPresenter>().State() !=
winrt::Microsoft::UI::Windowing::OverlappedPresenterState::Minimized) { winrt::Microsoft::UI::Windowing::OverlappedPresenterState::Minimized) {
winrt::Microsoft::ReactNative::LayoutConstraints constraints; winrt::Microsoft::ReactNative::LayoutConstraints constraints;
constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::Undefined;
constraints.MaximumSize = constraints.MinimumSize = size; constraints.MaximumSize = constraints.MinimumSize = size;
rootView.Arrange(constraints, {0,0}); rootView.Arrange(constraints, {0,0});
} }