@@ -20,92 +20,81 @@ namespace facebook::react {
20
20
21
21
extern const char AndroidTextInputComponentName[] = " AndroidTextInput" ;
22
22
23
- AttributedString AndroidTextInputShadowNode::getAttributedString () const {
24
- // Use BaseTextShadowNode to get attributed string from children
25
- auto childTextAttributes = TextAttributes::defaultTextAttributes ();
26
- childTextAttributes.apply (getConcreteProps ().textAttributes );
27
- // Don't propagate the background color of the TextInput onto the attributed
28
- // string. Android tries to render shadow of the background alongside the
29
- // shadow of the text which results in weird artifacts.
30
- childTextAttributes.backgroundColor = HostPlatformColor::UndefinedColor;
23
+ void AndroidTextInputShadowNode::setTextLayoutManager (
24
+ std::shared_ptr<const TextLayoutManager> textLayoutManager) {
25
+ ensureUnsealed ();
26
+ textLayoutManager_ = std::move (textLayoutManager);
27
+ }
31
28
32
- auto attributedString = AttributedString{};
33
- auto attachments = BaseTextShadowNode::Attachments{};
34
- BaseTextShadowNode::buildAttributedString (
35
- childTextAttributes, *this , attributedString, attachments);
36
- attributedString.setBaseTextAttributes (childTextAttributes);
29
+ Size AndroidTextInputShadowNode::measureContent (
30
+ const LayoutContext& layoutContext,
31
+ const LayoutConstraints& layoutConstraints) const {
32
+ auto textConstraints = getTextConstraints (layoutConstraints);
37
33
38
- // BaseTextShadowNode only gets children. We must detect and prepend text
39
- // value attributes manually.
40
- if (!getConcreteProps ().text .empty ()) {
41
- auto textAttributes = TextAttributes::defaultTextAttributes ();
42
- textAttributes.apply (getConcreteProps ().textAttributes );
43
- auto fragment = AttributedString::Fragment{};
44
- fragment.string = getConcreteProps ().text ;
45
- fragment.textAttributes = textAttributes;
46
- // If the TextInput opacity is 0 < n < 1, the opacity of the TextInput and
47
- // text value's background will stack. This is a hack/workaround to prevent
48
- // that effect.
49
- fragment.textAttributes .backgroundColor = clearColor ();
50
- fragment.parentShadowView = ShadowView (*this );
51
- attributedString.prependFragment (std::move (fragment));
34
+ if (getStateData ().cachedAttributedStringId != 0 ) {
35
+ auto textSize = textLayoutManager_
36
+ ->measureCachedSpannableById (
37
+ getStateData ().cachedAttributedStringId ,
38
+ getConcreteProps ().paragraphAttributes ,
39
+ textConstraints)
40
+ .size ;
41
+ return layoutConstraints.clamp (textSize);
52
42
}
53
43
54
- return attributedString;
55
- }
56
-
57
- // For measurement purposes, we want to make sure that there's at least a
58
- // single character in the string so that the measured height is greater
59
- // than zero. Otherwise, empty TextInputs with no placeholder don't
60
- // display at all.
61
- // TODO T67606511: We will redefine the measurement of empty strings as part
62
- // of T67606511
63
- AttributedString AndroidTextInputShadowNode::getPlaceholderAttributedString ()
64
- const {
65
- // Return placeholder text, since text and children are empty.
66
- auto textAttributedString = AttributedString{};
67
- auto fragment = AttributedString::Fragment{};
68
- fragment.string = getConcreteProps ().placeholder ;
44
+ // Layout is called right after measure.
45
+ // Measure is marked as `const`, and `layout` is not; so State can be
46
+ // updated during layout, but not during `measure`. If State is out-of-date
47
+ // in layout, it's too late: measure will have already operated on old
48
+ // State. Thus, we use the same value here that we *will* use in layout to
49
+ // update the state.
50
+ AttributedString attributedString = getMostRecentAttributedString ();
69
51
70
- if (fragment. string . empty ()) {
71
- fragment. string = BaseTextShadowNode::getEmptyPlaceholder ();
52
+ if (attributedString. isEmpty ()) {
53
+ attributedString = getPlaceholderAttributedString ();
72
54
}
73
55
74
- auto textAttributes = TextAttributes::defaultTextAttributes ();
75
- textAttributes.apply (getConcreteProps ().textAttributes );
76
-
77
- // If there's no text, it's possible that this Fragment isn't actually
78
- // appended to the AttributedString (see implementation of appendFragment)
79
- fragment.textAttributes = textAttributes;
80
- fragment.parentShadowView = ShadowView (*this );
81
- textAttributedString.appendFragment (std::move (fragment));
56
+ if (attributedString.isEmpty () && getStateData ().mostRecentEventCount != 0 ) {
57
+ return {.width = 0 , .height = 0 };
58
+ }
82
59
83
- return textAttributedString;
60
+ TextLayoutContext textLayoutContext;
61
+ textLayoutContext.pointScaleFactor = layoutContext.pointScaleFactor ;
62
+ auto textSize = textLayoutManager_
63
+ ->measure (
64
+ AttributedStringBox{attributedString},
65
+ getConcreteProps ().paragraphAttributes ,
66
+ textLayoutContext,
67
+ textConstraints)
68
+ .size ;
69
+ return layoutConstraints.clamp (textSize);
84
70
}
85
71
86
- void AndroidTextInputShadowNode::setTextLayoutManager (
87
- std::shared_ptr<const TextLayoutManager> textLayoutManager) {
88
- ensureUnsealed ();
89
- textLayoutManager_ = std::move (textLayoutManager);
72
+ void AndroidTextInputShadowNode::layout (LayoutContext layoutContext) {
73
+ updateStateIfNeeded ();
74
+ ConcreteViewShadowNode::layout (layoutContext);
90
75
}
91
76
92
- AttributedString AndroidTextInputShadowNode::getMostRecentAttributedString ()
93
- const {
94
- const auto & state = getStateData ();
77
+ Float AndroidTextInputShadowNode::baseline (
78
+ const LayoutContext& /* layoutContext*/ ,
79
+ Size size) const {
80
+ AttributedString attributedString = getMostRecentAttributedString ();
95
81
96
- auto reactTreeAttributedString = getAttributedString ();
82
+ if (attributedString.isEmpty ()) {
83
+ attributedString = getPlaceholderAttributedString ();
84
+ }
97
85
98
- // Sometimes the treeAttributedString will only differ from the state
99
- // not by inherent properties (string or prop attributes), but by the frame of
100
- // the parent which has changed Thus, we can't directly compare the entire
101
- // AttributedString
102
- bool treeAttributedStringChanged =
103
- !state.reactTreeAttributedString .compareTextAttributesWithoutFrame (
104
- reactTreeAttributedString);
86
+ // Yoga expects a baseline relative to the Node's border-box edge instead of
87
+ // the content, so we need to adjust by the padding and border widths, which
88
+ // have already been set by the time of baseline alignment
89
+ auto top = YGNodeLayoutGetBorder (&yogaNode_, YGEdgeTop) +
90
+ YGNodeLayoutGetPadding (&yogaNode_, YGEdgeTop);
105
91
106
- return (
107
- !treeAttributedStringChanged ? state.attributedStringBox .getValue ()
108
- : reactTreeAttributedString);
92
+ AttributedStringBox attributedStringBox{attributedString};
93
+ return textLayoutManager_->baseline (
94
+ attributedStringBox,
95
+ getConcreteProps ().paragraphAttributes ,
96
+ size) +
97
+ top;
109
98
}
110
99
111
100
LayoutConstraints AndroidTextInputShadowNode::getTextConstraints (
@@ -165,77 +154,86 @@ void AndroidTextInputShadowNode::updateStateIfNeeded() {
165
154
newEventCount});
166
155
}
167
156
168
- #pragma mark - LayoutableShadowNode
157
+ AttributedString AndroidTextInputShadowNode::getAttributedString () const {
158
+ // Use BaseTextShadowNode to get attributed string from children
159
+ auto childTextAttributes = TextAttributes::defaultTextAttributes ();
160
+ childTextAttributes.apply (getConcreteProps ().textAttributes );
161
+ // Don't propagate the background color of the TextInput onto the attributed
162
+ // string. Android tries to render shadow of the background alongside the
163
+ // shadow of the text which results in weird artifacts.
164
+ childTextAttributes.backgroundColor = HostPlatformColor::UndefinedColor;
169
165
170
- Size AndroidTextInputShadowNode::measureContent (
171
- const LayoutContext& layoutContext,
172
- const LayoutConstraints& layoutConstraints) const {
173
- auto textConstraints = getTextConstraints (layoutConstraints);
166
+ auto attributedString = AttributedString{};
167
+ auto attachments = BaseTextShadowNode::Attachments{};
168
+ BaseTextShadowNode::buildAttributedString (
169
+ childTextAttributes, *this , attributedString, attachments);
170
+ attributedString.setBaseTextAttributes (childTextAttributes);
174
171
175
- if (getStateData ().cachedAttributedStringId != 0 ) {
176
- auto textSize = textLayoutManager_
177
- ->measureCachedSpannableById (
178
- getStateData ().cachedAttributedStringId ,
179
- getConcreteProps ().paragraphAttributes ,
180
- textConstraints)
181
- .size ;
182
- return layoutConstraints.clamp (textSize);
172
+ // BaseTextShadowNode only gets children. We must detect and prepend text
173
+ // value attributes manually.
174
+ if (!getConcreteProps ().text .empty ()) {
175
+ auto textAttributes = TextAttributes::defaultTextAttributes ();
176
+ textAttributes.apply (getConcreteProps ().textAttributes );
177
+ auto fragment = AttributedString::Fragment{};
178
+ fragment.string = getConcreteProps ().text ;
179
+ fragment.textAttributes = textAttributes;
180
+ // If the TextInput opacity is 0 < n < 1, the opacity of the TextInput and
181
+ // text value's background will stack. This is a hack/workaround to prevent
182
+ // that effect.
183
+ fragment.textAttributes .backgroundColor = clearColor ();
184
+ fragment.parentShadowView = ShadowView (*this );
185
+ attributedString.prependFragment (std::move (fragment));
183
186
}
184
187
185
- // Layout is called right after measure.
186
- // Measure is marked as `const`, and `layout` is not; so State can be
187
- // updated during layout, but not during `measure`. If State is out-of-date
188
- // in layout, it's too late: measure will have already operated on old
189
- // State. Thus, we use the same value here that we *will* use in layout to
190
- // update the state.
191
- AttributedString attributedString = getMostRecentAttributedString ();
188
+ return attributedString;
189
+ }
192
190
193
- if (attributedString. isEmpty ()) {
194
- attributedString = getPlaceholderAttributedString ();
195
- }
191
+ AttributedString AndroidTextInputShadowNode::getMostRecentAttributedString ()
192
+ const {
193
+ const auto & state = getStateData ();
196
194
197
- if (attributedString.isEmpty () && getStateData ().mostRecentEventCount != 0 ) {
198
- return {0 , 0 };
199
- }
195
+ auto reactTreeAttributedString = getAttributedString ();
200
196
201
- TextLayoutContext textLayoutContext;
202
- textLayoutContext.pointScaleFactor = layoutContext.pointScaleFactor ;
203
- auto textSize = textLayoutManager_
204
- ->measure (
205
- AttributedStringBox{attributedString},
206
- getConcreteProps ().paragraphAttributes ,
207
- textLayoutContext,
208
- textConstraints)
209
- .size ;
210
- return layoutConstraints.clamp (textSize);
197
+ // Sometimes the treeAttributedString will only differ from the state
198
+ // not by inherent properties (string or prop attributes), but by the frame of
199
+ // the parent which has changed Thus, we can't directly compare the entire
200
+ // AttributedString
201
+ bool treeAttributedStringChanged =
202
+ !state.reactTreeAttributedString .compareTextAttributesWithoutFrame (
203
+ reactTreeAttributedString);
204
+
205
+ return (
206
+ !treeAttributedStringChanged ? state.attributedStringBox .getValue ()
207
+ : reactTreeAttributedString);
211
208
}
212
209
213
- Float AndroidTextInputShadowNode::baseline (
214
- const LayoutContext& layoutContext,
215
- Size size) const {
216
- AttributedString attributedString = getMostRecentAttributedString ();
210
+ // For measurement purposes, we want to make sure that there's at least a
211
+ // single character in the string so that the measured height is greater
212
+ // than zero. Otherwise, empty TextInputs with no placeholder don't
213
+ // display at all.
214
+ // TODO T67606511: We will redefine the measurement of empty strings as part
215
+ // of T67606511
216
+ AttributedString AndroidTextInputShadowNode::getPlaceholderAttributedString ()
217
+ const {
218
+ // Return placeholder text, since text and children are empty.
219
+ auto textAttributedString = AttributedString{};
220
+ auto fragment = AttributedString::Fragment{};
221
+ fragment.string = getConcreteProps ().placeholder ;
217
222
218
- if (attributedString. isEmpty ()) {
219
- attributedString = getPlaceholderAttributedString ();
223
+ if (fragment. string . empty ()) {
224
+ fragment. string = BaseTextShadowNode::getEmptyPlaceholder ();
220
225
}
221
226
222
- // Yoga expects a baseline relative to the Node's border-box edge instead of
223
- // the content, so we need to adjust by the padding and border widths, which
224
- // have already been set by the time of baseline alignment
225
- auto top = YGNodeLayoutGetBorder (&yogaNode_, YGEdgeTop) +
226
- YGNodeLayoutGetPadding (&yogaNode_, YGEdgeTop);
227
+ auto textAttributes = TextAttributes::defaultTextAttributes ();
228
+ textAttributes.apply (getConcreteProps ().textAttributes );
227
229
228
- AttributedStringBox attributedStringBox{attributedString};
229
- return textLayoutManager_->baseline (
230
- attributedStringBox,
231
- getConcreteProps ().paragraphAttributes ,
232
- size) +
233
- top;
234
- }
230
+ // If there's no text, it's possible that this Fragment isn't actually
231
+ // appended to the AttributedString (see implementation of appendFragment)
232
+ fragment.textAttributes = textAttributes;
233
+ fragment.parentShadowView = ShadowView (*this );
234
+ textAttributedString.appendFragment (std::move (fragment));
235
235
236
- void AndroidTextInputShadowNode::layout (LayoutContext layoutContext) {
237
- updateStateIfNeeded ();
238
- ConcreteViewShadowNode::layout (layoutContext);
236
+ return textAttributedString;
239
237
}
240
238
241
239
} // namespace facebook::react
0 commit comments