Skip to content

Commit 4425140

Browse files
committed
Fix font scale change wrong layout
1 parent c211822 commit 4425140

5 files changed

Lines changed: 30 additions & 94 deletions

File tree

packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ void YogaLayoutableShadowNode::updateYogaProps() {
480480

481481
void YogaLayoutableShadowNode::configureYogaTree(
482482
float pointScaleFactor,
483+
Float fontSizeMultiplier,
483484
YGErrata defaultErrata,
484485
bool swapLeftAndRight) {
485486
ensureUnsealed();
@@ -489,6 +490,17 @@ void YogaLayoutableShadowNode::configureYogaTree(
489490
YGConfigSetErrata(&yogaConfig_, errata);
490491
YGConfigSetPointScaleFactor(&yogaConfig_, pointScaleFactor);
491492

493+
// A measurable node's measurement depends on `fontSizeMultiplier`, but unlike
494+
// `pointScaleFactor` it is not part of the Yoga config, so a change does not
495+
// invalidate Yoga's layout cache. Dirty the node when it changes to force
496+
// re-measurement. We propagate up to the root so an unchanged, still-cached
497+
// ancestor isn't skipped by `calculateLayoutInternal` before reaching us.
498+
if (ReactNativeFeatureFlags::enableFontScaleChangesUpdatingLayout() &&
499+
getTraits().check(ShadowNodeTraits::Trait::MeasurableYogaNode) &&
500+
!floatEquality(getLayoutMetrics().fontSizeMultiplier, fontSizeMultiplier)) {
501+
yogaNode_.markDirtyAndPropagate();
502+
}
503+
492504
// TODO: `swapLeftAndRight` modified backing props and cannot be undone
493505
if (swapLeftAndRight) {
494506
swapStyleLeftAndRight();
@@ -507,6 +519,7 @@ void YogaLayoutableShadowNode::configureYogaTree(
507519

508520
if (child.yogaTreeHasBeenConfigured_ &&
509521
childLayoutMetrics.pointScaleFactor == pointScaleFactor &&
522+
childLayoutMetrics.fontSizeMultiplier == fontSizeMultiplier &&
510523
childLayoutMetrics.wasLeftAndRightSwapped == swapLeftAndRight &&
511524
childErrata == child.resolveErrata(errata)) {
512525
continue;
@@ -515,10 +528,13 @@ void YogaLayoutableShadowNode::configureYogaTree(
515528
if (doesOwn(child)) {
516529
auto& mutableChild = const_cast<YogaLayoutableShadowNode&>(child);
517530
mutableChild.configureYogaTree(
518-
pointScaleFactor, child.resolveErrata(errata), swapLeftAndRight);
531+
pointScaleFactor,
532+
fontSizeMultiplier,
533+
child.resolveErrata(errata),
534+
swapLeftAndRight);
519535
} else {
520536
cloneChildInPlace(i).configureYogaTree(
521-
pointScaleFactor, errata, swapLeftAndRight);
537+
pointScaleFactor, fontSizeMultiplier, errata, swapLeftAndRight);
522538
}
523539
}
524540
}
@@ -627,6 +643,7 @@ void YogaLayoutableShadowNode::layoutTree(
627643
TraceSection s2("YogaLayoutableShadowNode::configureYogaTree");
628644
configureYogaTree(
629645
layoutContext.pointScaleFactor,
646+
layoutContext.fontSizeMultiplier,
630647
YGErrataAll /*defaultErrata*/,
631648
swapLeftAndRight);
632649
}
@@ -692,6 +709,7 @@ void YogaLayoutableShadowNode::layoutTree(
692709
if (yogaNode_.getHasNewLayout()) {
693710
auto layoutMetrics = layoutMetricsFromYogaNode(yogaNode_);
694711
layoutMetrics.pointScaleFactor = layoutContext.pointScaleFactor;
712+
layoutMetrics.fontSizeMultiplier = layoutContext.fontSizeMultiplier;
695713
layoutMetrics.wasLeftAndRightSwapped = swapLeftAndRight;
696714
setLayoutMetrics(layoutMetrics);
697715
yogaNode_.setHasNewLayout(false);
@@ -746,6 +764,7 @@ void YogaLayoutableShadowNode::layout(LayoutContext layoutContext) {
746764

747765
auto newLayoutMetrics = layoutMetricsFromYogaNode(*childYogaNode);
748766
newLayoutMetrics.pointScaleFactor = layoutContext.pointScaleFactor;
767+
newLayoutMetrics.fontSizeMultiplier = layoutContext.fontSizeMultiplier;
749768
newLayoutMetrics.wasLeftAndRightSwapped =
750769
layoutContext.swapLeftAndRightInRTL &&
751770
newLayoutMetrics.layoutDirection == LayoutDirection::RightToLeft;

packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,11 @@ class YogaLayoutableShadowNode : public LayoutableShadowNode {
134134
* ShadowTree has been constructed, but before it has been is laid out or
135135
* committed.
136136
*/
137-
void configureYogaTree(float pointScaleFactor, YGErrata defaultErrata, bool swapLeftAndRight);
137+
void configureYogaTree(
138+
float pointScaleFactor,
139+
Float fontSizeMultiplier,
140+
YGErrata defaultErrata,
141+
bool swapLeftAndRight);
138142

139143
/**
140144
* Return an errata based on a `layoutConformance` prop if given, otherwise

packages/react-native/ReactCommon/react/renderer/core/LayoutMetrics.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ struct LayoutMetrics {
4040
bool wasLeftAndRightSwapped{false};
4141
// Pixel density. Number of device pixels per density-independent pixel.
4242
Float pointScaleFactor{1.0};
43+
// Surface font scale this node was last laid out with.
44+
Float fontSizeMultiplier{1.0};
4345
// How much the children of the node actually overflow in each direction.
4446
// Positive values indicate that children are overflowing outside of the node.
4547
// Negative values indicate that children are clipped inside the node
@@ -120,6 +122,7 @@ struct hash<facebook::react::LayoutMetrics> {
120122
layoutMetrics.displayType,
121123
layoutMetrics.layoutDirection,
122124
layoutMetrics.pointScaleFactor,
125+
layoutMetrics.fontSizeMultiplier,
123126
layoutMetrics.overflowInset);
124127
}
125128
};

packages/react-native/ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp

Lines changed: 1 addition & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
#include <cxxreact/TraceSection.h>
1111
#include <react/debug/react_native_assert.h>
12-
#include <react/featureflags/ReactNativeFeatureFlags.h>
1312
#include <react/renderer/uimanager/UIManager.h>
1413

1514
namespace facebook::react {
@@ -213,78 +212,6 @@ Size SurfaceHandler::measure(
213212
return rootShadowNode->getLayoutMetrics().frame.size;
214213
}
215214

216-
std::shared_ptr<const ShadowNode> SurfaceHandler::dirtyMeasurableNodesRecursive(
217-
std::shared_ptr<const ShadowNode> node) const {
218-
const auto nodeHasChildren = !node->getChildren().empty();
219-
const auto isMeasurableYogaNode =
220-
node->getTraits().check(ShadowNodeTraits::Trait::MeasurableYogaNode);
221-
222-
// Node is not measurable and has no children, its layout will not be affected
223-
if (!nodeHasChildren && !isMeasurableYogaNode) {
224-
return nullptr;
225-
}
226-
227-
std::shared_ptr<const std::vector<std::shared_ptr<const ShadowNode>>>
228-
newChildren = ShadowNodeFragment::childrenPlaceholder();
229-
230-
if (nodeHasChildren) {
231-
std::shared_ptr<std::vector<std::shared_ptr<const ShadowNode>>>
232-
newChildrenMutable = nullptr;
233-
for (size_t i = 0; i < node->getChildren().size(); i++) {
234-
const auto& child = node->getChildren()[i];
235-
236-
if (const auto& layoutableNode =
237-
std::dynamic_pointer_cast<const YogaLayoutableShadowNode>(
238-
child)) {
239-
auto newChild = dirtyMeasurableNodesRecursive(layoutableNode);
240-
241-
if (newChild != nullptr) {
242-
if (newChildrenMutable == nullptr) {
243-
newChildrenMutable = std::make_shared<
244-
std::vector<std::shared_ptr<const ShadowNode>>>(
245-
node->getChildren());
246-
newChildren = newChildrenMutable;
247-
}
248-
249-
(*newChildrenMutable)[i] = newChild;
250-
}
251-
}
252-
}
253-
254-
// Node is not measurable and its children were not dirtied, its layout will
255-
// not be affected
256-
if (!isMeasurableYogaNode && newChildrenMutable == nullptr) {
257-
return nullptr;
258-
}
259-
}
260-
261-
const auto newNode = node->getComponentDescriptor().cloneShadowNode(
262-
*node,
263-
{
264-
.children = newChildren,
265-
// Preserve the original state of the node
266-
.state = node->getState(),
267-
});
268-
269-
if (isMeasurableYogaNode) {
270-
std::static_pointer_cast<YogaLayoutableShadowNode>(newNode)->dirtyLayout();
271-
}
272-
273-
return newNode;
274-
}
275-
276-
void SurfaceHandler::dirtyMeasurableNodes(ShadowNode& root) const {
277-
for (const auto& child : root.getChildren()) {
278-
if (const auto& layoutableNode =
279-
std::dynamic_pointer_cast<const YogaLayoutableShadowNode>(child)) {
280-
const auto newChild = dirtyMeasurableNodesRecursive(layoutableNode);
281-
if (newChild != nullptr) {
282-
root.replaceChild(*child, newChild);
283-
}
284-
}
285-
}
286-
}
287-
288215
void SurfaceHandler::constraintLayout(
289216
const LayoutConstraints& layoutConstraints,
290217
const LayoutContext& layoutContext) const {
@@ -315,19 +242,8 @@ void SurfaceHandler::constraintLayout(
315242
link_.shadowTree && "`link_.shadowTree` must not be null.");
316243
link_.shadowTree->commit(
317244
[&](const RootShadowNode& oldRootShadowNode) {
318-
auto newRoot = oldRootShadowNode.clone(
245+
return oldRootShadowNode.clone(
319246
propsParserContext, layoutConstraints, layoutContext);
320-
321-
// Dirty all measurable nodes when the fontSizeMultiplier changes to
322-
// trigger re-measurement.
323-
if (ReactNativeFeatureFlags::enableFontScaleChangesUpdatingLayout() &&
324-
layoutContext.fontSizeMultiplier !=
325-
oldRootShadowNode.getConcreteProps()
326-
.layoutContext.fontSizeMultiplier) {
327-
dirtyMeasurableNodes(*newRoot);
328-
}
329-
330-
return newRoot;
331247
},
332248
{/* default commit options */});
333249
}

packages/react-native/ReactCommon/react/renderer/scheduler/SurfaceHandler.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,6 @@ class SurfaceHandler {
151151

152152
void applyDisplayMode(DisplayMode displayMode) const;
153153

154-
/*
155-
* An utility for dirtying all measurable shadow nodes present in the tree.
156-
*/
157-
void dirtyMeasurableNodes(ShadowNode &root) const;
158-
std::shared_ptr<const ShadowNode> dirtyMeasurableNodesRecursive(std::shared_ptr<const ShadowNode> node) const;
159-
160154
#pragma mark - Link & Parameters
161155

162156
/*

0 commit comments

Comments
 (0)