Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "[Fabric] LayoutDirection and FontSizeMultiplier support",
"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 @@ -51,6 +51,7 @@ void UpdateRootViewSizeToAppWindow(
if (window.Presenter().as<winrt::Microsoft::UI::Windowing::OverlappedPresenter>().State() !=
winrt::Microsoft::UI::Windowing::OverlappedPresenterState::Minimized) {
winrt::Microsoft::ReactNative::LayoutConstraints constraints;
constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::Undefined;
constraints.MaximumSize = constraints.MinimumSize = size;
rootView.Arrange(constraints, {0, 0});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ struct WindowData {
winrt::Microsoft::ReactNative::ReactInstanceSettings m_instanceSettings{nullptr};
bool m_useLiftedComposition{true};
bool m_sizeToContent{false};
bool m_forceRTL{false};
winrt::Windows::UI::Composition::Desktop::DesktopWindowTarget m_target{nullptr};
LONG m_height{0};
LONG m_width{0};
Expand Down Expand Up @@ -255,14 +256,18 @@ struct WindowData {
m_bridge = winrt::Microsoft::UI::Content::DesktopChildSiteBridge::Create(
g_liftedCompositor, winrt::Microsoft::UI::GetWindowIdFromWindow(hwnd));

if (m_forceRTL) {
m_bridge.LayoutDirectionOverride(winrt::Microsoft::UI::Content::ContentLayoutDirection::RightToLeft);
}

auto appContent = m_compRootView.Island();

m_bridge.Connect(appContent);
m_bridge.Show();

m_compRootView.ScaleFactor(ScaleFactor(hwnd));
winrt::Microsoft::ReactNative::LayoutConstraints constraints;
constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::LeftToRight;
constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::Undefined;
constraints.MaximumSize =
constraints.MinimumSize = {m_width / ScaleFactor(hwnd), m_height / ScaleFactor(hwnd)};

Expand Down Expand Up @@ -327,6 +332,7 @@ struct WindowData {
SystemCompositionContextHelper::CreateVisual(root));
m_compRootView.ScaleFactor(ScaleFactor(hwnd));
winrt::Microsoft::ReactNative::LayoutConstraints contraints;
contraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::Undefined;
contraints.MaximumSize =
contraints.MinimumSize = {m_width / ScaleFactor(hwnd), m_height / ScaleFactor(hwnd)};
m_compRootView.Arrange(contraints, {0, 0});
Expand Down Expand Up @@ -371,15 +377,25 @@ struct WindowData {
OutputDebugStringA("Instance Unload completed\n");

uidispatch.Post([&]() {
m_bridge.Close();
m_bridge = nullptr;
if (m_bridge) {
m_bridge.Close();
m_bridge = nullptr;
}
});
assert(asyncStatus == winrt::Windows::Foundation::AsyncStatus::Completed);
});
m_compRootView = nullptr;
m_instanceSettings = nullptr;
m_host = nullptr;
} 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;
Expand All @@ -400,7 +416,7 @@ struct WindowData {
winrt::Windows::Foundation::Size size{m_width / ScaleFactor(hwnd), m_height / ScaleFactor(hwnd)};
if (!IsIconic(hwnd)) {
winrt::Microsoft::ReactNative::LayoutConstraints constraints;
constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::LeftToRight;
constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::Undefined;
constraints.MinimumSize = constraints.MaximumSize = size;
if (m_sizeToContent) {
ApplyConstraintsForContentSizedWindow(constraints);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ BEGIN
MENUITEM "&New Window\tCtrl+N", IDM_NEWWINDOW
MENUITEM "&Refresh\tF5", IDM_REFRESH
MENUITEM "&Unload", IDM_UNLOAD
MENUITEM "Toggle Layout &Direction", IDM_TOGGLE_LAYOUT_DIRECTION
MENUITEM SEPARATOR
MENUITEM "&Settings...\tAlt+S", IDM_SETTINGS
MENUITEM SEPARATOR
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#define IDC_JSENGINELABEL 111
#define IDC_SIZETOCONTENT 112
#define IDM_UNLOAD 113
#define IDM_TOGGLE_LAYOUT_DIRECTION 114
#define IDI_ICON1 1008

// Next default values for new objects
Expand Down
4 changes: 2 additions & 2 deletions vnext/Microsoft.ReactNative/Fabric/ComponentView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,8 @@ void ComponentView::HandleCommand(const winrt::Microsoft::ReactNative::HandleCom
}
}

winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView *
ComponentView::rootComponentView() noexcept {
winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView *ComponentView::rootComponentView()
const noexcept {
if (m_rootView)
return m_rootView;

Expand Down
5 changes: 3 additions & 2 deletions vnext/Microsoft.ReactNative/Fabric/ComponentView.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ struct ComponentView : public ComponentViewT<ComponentView> {
facebook::react::LayoutMetrics const &oldLayoutMetrics) noexcept;
virtual void prepareForRecycle() 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 winrt::Microsoft::ReactNative::ComponentView Parent() const noexcept;
virtual winrt::IVectorView<winrt::Microsoft::ReactNative::ComponentView> Children() const noexcept;
Expand Down Expand Up @@ -253,7 +254,7 @@ struct ComponentView : public ComponentViewT<ComponentView> {
bool m_mounted : 1 {false};
const facebook::react::Tag m_tag;
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};
const winrt::Microsoft::ReactNative::ReactContext m_reactContext;
winrt::Microsoft::ReactNative::ComponentView m_parent{nullptr};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ void CompositionHwndHost::UpdateSize() noexcept {
// Do not relayout when minimized
if (!IsIconic(m_hwnd)) {
winrt::Microsoft::ReactNative::LayoutConstraints constraints;
constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::Undefined;
constraints.MinimumSize = constraints.MaximumSize = size;
m_compRootView.Arrange(constraints, {0, 0});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,21 +98,36 @@ void CompositionReactViewInstance::UninitRootView() noexcept {
}
}

void ApplyConstraints(
void ReactNativeIsland::ApplyConstraints(
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.maximumSize = {layoutConstraintsIn.MaximumSize.Width, layoutConstraintsIn.MaximumSize.Height};
layoutConstraintsOut.layoutDirection =
static_cast<facebook::react::LayoutDirection>(layoutConstraintsIn.LayoutDirection);
if (layoutConstraintsIn.LayoutDirection == winrt::Microsoft::ReactNative::LayoutDirection::Undefined) {
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
: m_compositor(compositor) {}
#endif
: m_compositor(compositor),
m_layoutConstraints({{0, 0}, {0, 0}, winrt::Microsoft::ReactNative::LayoutDirection::Undefined}) {
InitTextScaleMultiplier();
}

ReactNativeIsland::ReactNativeIsland() noexcept : ReactNativeIsland(nullptr) {}

ReactNativeIsland::~ReactNativeIsland() noexcept {
#ifdef USE_WINUI3
Expand Down Expand Up @@ -248,6 +263,10 @@ void ReactNativeIsland::ScaleFactor(float value) noexcept {
}
}

float ReactNativeIsland::FontSizeMultiplier() const noexcept {
return m_textScaleMultiplier;
}

int64_t ReactNativeIsland::RootTag() const noexcept {
return m_rootTag;
}
Expand Down Expand Up @@ -431,10 +450,9 @@ void ReactNativeIsland::UninitRootView() noexcept {
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
// instance destruction. Aligns with similar code in ReactInstanceWin::DetachRootView for paper Future: Instead this
// method should return a Promise, which should be resolved when the JS logic is complete.
// The task will auto set the event on destruction to ensure that the event is set if the JS Queue has already been
// shutdown
// instance destruction. Aligns with similar code in ReactInstanceWin::DetachRootView for paper Future: Instead
// this method should return a Promise, which should be resolved when the JS logic is complete. The task will auto
// set the event on destruction to ensure that the event is set if the JS Queue has already been shutdown
Mso::ManualResetEvent mre;
m_context.JSDispatcher().Post([autoMRE = std::make_unique<AutoMRE>(AutoMRE{mre})]() {});
mre.Wait();
Expand Down Expand Up @@ -499,9 +517,8 @@ facebook::react::AttributedStringBox CreateLoadingAttributedString() noexcept {
return facebook::react::AttributedStringBox{attributedString};
}

facebook::react::Size MeasureLoading(
const winrt::Microsoft::ReactNative::LayoutConstraints &layoutConstraints,
float scaleFactor) {
facebook::react::Size ReactNativeIsland::MeasureLoading(
const winrt::Microsoft::ReactNative::LayoutConstraints &layoutConstraints) const noexcept {
facebook::react::LayoutConstraints fbLayoutConstraints;
ApplyConstraints(layoutConstraints, fbLayoutConstraints);

Expand All @@ -514,7 +531,7 @@ facebook::react::Size MeasureLoading(
winrt::check_hresult(textLayout->GetMetrics(&tm));

return fbLayoutConstraints.clamp(
{loadingActivityHorizontalOffset * scaleFactor + tm.width, loadingBarHeight * scaleFactor});
{loadingActivityHorizontalOffset * m_scaleFactor + tm.width, loadingBarHeight * m_scaleFactor});
}

winrt::event_token ReactNativeIsland::SizeChanged(
Expand Down Expand Up @@ -544,7 +561,7 @@ void ReactNativeIsland::NotifySizeChanged() noexcept {
if (rootComponentView) {
size = rootComponentView->layoutMetrics().frame.size;
} else if (m_loadingVisual) {
size = MeasureLoading(m_layoutConstraints, m_scaleFactor);
size = MeasureLoading(m_layoutConstraints);
}

m_size = {size.width, size.height};
Expand Down Expand Up @@ -638,27 +655,49 @@ void ReactNativeIsland::ShowInstanceLoading() noexcept {
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(
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};

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;
ApplyConstraints(layoutConstraints, constraints);

if (m_isInitialized && m_rootTag != -1) {
if (auto fabricuiManager = ::Microsoft::ReactNative::FabricUIManager::FromProperties(
winrt::Microsoft::ReactNative::ReactPropertyBag(m_context.Properties()))) {
facebook::react::LayoutContext context;
// TODO scaling factor
context.fontSizeMultiplier = m_textScaleMultiplier;
context.pointScaleFactor = static_cast<facebook::react::Float>(m_scaleFactor);
context.fontSizeMultiplier = static_cast<facebook::react::Float>(m_scaleFactor);
context.viewportOffset = {viewportOffset.X, viewportOffset.Y};

size = fabricuiManager->measureSurface(static_cast<facebook::react::SurfaceId>(m_rootTag), constraints, context);
}
} else if (m_loadingVisual) {
size = MeasureLoading(layoutConstraints, m_scaleFactor);
size = MeasureLoading(layoutConstraints);
}

auto clampedSize = constraints.clamp(size);
Expand All @@ -667,7 +706,12 @@ winrt::Windows::Foundation::Size ReactNativeIsland::Measure(

void ReactNativeIsland::Arrange(
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_viewportOffset = viewportOffset;
facebook::react::LayoutConstraints fbLayoutConstraints;
Expand All @@ -677,21 +721,20 @@ void ReactNativeIsland::Arrange(
if (auto fabricuiManager = ::Microsoft::ReactNative::FabricUIManager::FromProperties(
winrt::Microsoft::ReactNative::ReactPropertyBag(m_context.Properties()))) {
facebook::react::LayoutContext context;
context.fontSizeMultiplier = m_textScaleMultiplier;
context.pointScaleFactor = static_cast<facebook::react::Float>(m_scaleFactor);
context.fontSizeMultiplier = static_cast<facebook::react::Float>(m_scaleFactor);
context.viewportOffset = {viewportOffset.X, viewportOffset.Y};

fabricuiManager->constraintSurfaceLayout(
static_cast<facebook::react::SurfaceId>(m_rootTag), fbLayoutConstraints, context);
}
} else if (m_loadingVisual) {
// TODO: Resize to align loading
auto s = fbLayoutConstraints.clamp(MeasureLoading(layoutConstraints, m_scaleFactor));
auto s = fbLayoutConstraints.clamp(MeasureLoading(layoutConstraints));
NotifySizeChanged();
}
}

#ifdef USE_WINUI3
winrt::Microsoft::UI::Content::ContentIsland ReactNativeIsland::Island() {
if (!m_compositor) {
return nullptr;
Expand Down Expand Up @@ -745,6 +788,9 @@ winrt::Microsoft::UI::Content::ContentIsland ReactNativeIsland::Island() {
if (args.DidRasterizationScaleChange()) {
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
// want, but it will do for now.
if (args.DidSiteVisibleChange()) {
Expand Down Expand Up @@ -777,7 +823,6 @@ winrt::Microsoft::UI::Content::ContentIsland ReactNativeIsland::Island() {
}
return m_island;
}
#endif

void ReactNativeIsland::OnMounted() noexcept {
if (m_mounted)
Expand Down
Loading