Skip to content

Commit efd1e23

Browse files
iamAbhi-916acoates-msvineethkuttan
authored
[0.82]cherry pick: Center single line textinputs (#15838)
* Center single line textinputs (#15754) * Center single line textinputs * Change files * snapshot * fix Playground (#15832) --------- Co-authored-by: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Co-authored-by: Vineeth <66076509+vineethkuttan@users.noreply.github.com>
1 parent 20f9405 commit efd1e23

5 files changed

Lines changed: 97 additions & 61 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Center single line textinputs",
4+
"packageName": "react-native-windows",
5+
"email": "30809111+acoates-ms@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}

packages/e2e-test-app-fabric/test/__snapshots__/TextInputComponentTest.test.ts.snap

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -381,9 +381,9 @@ exports[`TextInput Tests Text have cursorColor 1`] = `
381381
"Brush Type": "ColorBrush",
382382
"Color": "rgba(0, 128, 0, 255)",
383383
},
384-
"Offset": "83, 5, 0",
384+
"Offset": "89, 6, 0",
385385
"Opacity": 0,
386-
"Size": "1, 19",
386+
"Size": "1, 21",
387387
"Visual Type": "SpriteVisual",
388388
},
389389
],
@@ -1884,9 +1884,9 @@ exports[`TextInput Tests TextInputs can clear on submit 1`] = `
18841884
"Brush Type": "ColorBrush",
18851885
"Color": "rgba(0, 0, 0, 255)",
18861886
},
1887-
"Offset": "5, 5, 0",
1887+
"Offset": "5, 6, 0",
18881888
"Opacity": 0,
1889-
"Size": "1, 19",
1889+
"Size": "1, 21",
18901890
"Visual Type": "SpriteVisual",
18911891
},
18921892
],
@@ -2805,9 +2805,9 @@ exports[`TextInput Tests TextInputs can have caretHidden 1`] = `
28052805
"Brush Type": "ColorBrush",
28062806
"Color": "rgba(0, 0, 0, 255)",
28072807
},
2808-
"Offset": "83, 5, 0",
2808+
"Offset": "89, 6, 0",
28092809
"Opacity": 0,
2810-
"Size": "1, 19",
2810+
"Size": "1, 21",
28112811
"Visual Type": "SpriteVisual",
28122812
},
28132813
],
@@ -4993,9 +4993,9 @@ exports[`TextInput Tests TextInputs can select text on focus 1`] = `
49934993
"Brush Type": "ColorBrush",
49944994
"Color": "rgba(0, 0, 0, 255)",
49954995
},
4996-
"Offset": "83, 5, 0",
4996+
"Offset": "89, 6, 0",
49974997
"Opacity": 0,
4998-
"Size": "1, 19",
4998+
"Size": "1, 21",
49994999
"Visual Type": "SpriteVisual",
50005000
},
50015001
],
@@ -5226,7 +5226,7 @@ exports[`TextInput Tests TextInputs can submit with custom key, multilined and s
52265226
},
52275227
"Offset": "5, 5, 0",
52285228
"Opacity": 0,
5229-
"Size": "1, 19",
5229+
"Size": "1, 21",
52305230
"Visual Type": "SpriteVisual",
52315231
},
52325232
],

vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp

Lines changed: 70 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@ struct CompTextHost : public winrt::implements<CompTextHost, ITextHost> {
246246
//@cmember Converts screen coordinates of a specified point to the client coordinates
247247
BOOL TxScreenToClient(LPPOINT lppt) override {
248248
winrt::Windows::Foundation::Point pt{static_cast<float>(lppt->x), static_cast<float>(lppt->y)};
249+
pt.X -= m_outer->m_contentOffsetPx.x;
250+
pt.Y -= m_outer->m_contentOffsetPx.y;
249251
auto localpt = m_outer->ScreenToLocal(pt);
250252
lppt->x = static_cast<LONG>(localpt.X);
251253
lppt->y = static_cast<LONG>(localpt.Y);
@@ -255,9 +257,14 @@ struct CompTextHost : public winrt::implements<CompTextHost, ITextHost> {
255257
//@cmember Converts the client coordinates of a specified point to screen coordinates
256258
BOOL TxClientToScreen(LPPOINT lppt) override {
257259
winrt::Windows::Foundation::Point pt{static_cast<float>(lppt->x), static_cast<float>(lppt->y)};
260+
261+
if (!m_outer->m_parent) {
262+
return false;
263+
}
264+
258265
auto screenpt = m_outer->LocalToScreen(pt);
259-
lppt->x = static_cast<LONG>(screenpt.X);
260-
lppt->y = static_cast<LONG>(screenpt.Y);
266+
lppt->x = static_cast<LONG>(screenpt.X) + m_outer->m_contentOffsetPx.x;
267+
lppt->y = static_cast<LONG>(screenpt.Y) + m_outer->m_contentOffsetPx.y;
261268
return true;
262269
}
263270

@@ -276,20 +283,25 @@ struct CompTextHost : public winrt::implements<CompTextHost, ITextHost> {
276283
//@cmember Retrieves the coordinates of a window's client area
277284
HRESULT TxGetClientRect(LPRECT prc) override {
278285
*prc = m_outer->getClientRect();
286+
287+
prc->top += m_outer->m_contentOffsetPx.y;
288+
prc->bottom += m_outer->m_contentOffsetPx.y -
289+
static_cast<LONG>(m_outer->m_layoutMetrics.contentInsets.bottom * m_outer->m_layoutMetrics.pointScaleFactor);
290+
prc->left += m_outer->m_contentOffsetPx.x;
291+
prc->right += m_outer->m_contentOffsetPx.x -
292+
static_cast<LONG>(m_outer->m_layoutMetrics.contentInsets.right * m_outer->m_layoutMetrics.pointScaleFactor);
293+
279294
return S_OK;
280295
}
281296

282297
//@cmember Get the view rectangle relative to the inset
283298
HRESULT TxGetViewInset(LPRECT prc) override {
284299
// Inset is in HIMETRIC
285-
constexpr float HmPerInchF = 2540.0f;
286-
constexpr float PointsPerInch = 96.0f;
287-
constexpr float dipToHm = HmPerInchF / PointsPerInch;
288300

289-
prc->left = static_cast<LONG>(m_outer->m_layoutMetrics.contentInsets.left * dipToHm);
290-
prc->top = static_cast<LONG>(m_outer->m_layoutMetrics.contentInsets.top * dipToHm);
291-
prc->bottom = static_cast<LONG>(m_outer->m_layoutMetrics.contentInsets.bottom * dipToHm);
292-
prc->right = static_cast<LONG>(m_outer->m_layoutMetrics.contentInsets.right * dipToHm);
301+
prc->left = 0;
302+
prc->top = 0;
303+
prc->bottom = 0;
304+
prc->right = 0;
293305

294306
return NOERROR;
295307
}
@@ -492,11 +504,6 @@ AutoCorrectOffCallback(LANGID langid, const WCHAR *pszBefore, WCHAR *pszAfter, L
492504
facebook::react::AttributedString WindowsTextInputComponentView::getAttributedString() const {
493505
// Use BaseTextShadowNode to get attributed string from children
494506

495-
auto childTextAttributes = facebook::react::TextAttributes::defaultTextAttributes();
496-
childTextAttributes.fontSizeMultiplier = m_fontSizeMultiplier;
497-
498-
childTextAttributes.apply(windowsTextInputProps().textAttributes);
499-
500507
auto attributedString = facebook::react::AttributedString{};
501508
// auto attachments = facebook::react::BaseTextShadowNode::Attachments{};
502509

@@ -1114,6 +1121,9 @@ void WindowsTextInputComponentView::updateProps(
11141121
!facebook::react::floatEquality(
11151122
oldTextInputProps.textAttributes.letterSpacing, newTextInputProps.textAttributes.letterSpacing) ||
11161123
oldTextInputProps.textAttributes.fontFamily != newTextInputProps.textAttributes.fontFamily ||
1124+
oldTextInputProps.textAttributes.fontStyle != newTextInputProps.textAttributes.fontStyle ||
1125+
oldTextInputProps.textAttributes.textDecorationLineType !=
1126+
newTextInputProps.textAttributes.textDecorationLineType ||
11171127
!facebook::react::floatEquality(
11181128
oldTextInputProps.textAttributes.maxFontSizeMultiplier,
11191129
newTextInputProps.textAttributes.maxFontSizeMultiplier)) {
@@ -1129,6 +1139,7 @@ void WindowsTextInputComponentView::updateProps(
11291139
}
11301140

11311141
if (oldTextInputProps.multiline != newTextInputProps.multiline) {
1142+
m_recalculateContentVerticalOffset = true;
11321143
m_multiline = newTextInputProps.multiline;
11331144
m_propBitsMask |= TXTBIT_MULTILINE | TXTBIT_WORDWRAP;
11341145
if (newTextInputProps.multiline) {
@@ -1278,6 +1289,10 @@ void WindowsTextInputComponentView::updateLayoutMetrics(
12781289
unsigned int newWidth = static_cast<unsigned int>(layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor);
12791290
unsigned int newHeight = static_cast<unsigned int>(layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor);
12801291

1292+
if (newHeight != m_imgHeight || oldLayoutMetrics.pointScaleFactor != layoutMetrics.pointScaleFactor) {
1293+
m_recalculateContentVerticalOffset = true;
1294+
}
1295+
12811296
if (newWidth != m_imgWidth || newHeight != m_imgHeight) {
12821297
m_drawingSurface = nullptr; // Invalidate surface if we get a size change
12831298
}
@@ -1416,6 +1431,8 @@ void WindowsTextInputComponentView::FinalizeUpdates(
14161431

14171432
void WindowsTextInputComponentView::UpdatePropertyBits() noexcept {
14181433
if (m_propBitsMask != 0) {
1434+
if ((m_propBits & TXTBIT_CHARFORMATCHANGE) == TXTBIT_CHARFORMATCHANGE)
1435+
m_recalculateContentVerticalOffset = true;
14191436
DrawBlock db(*this);
14201437
winrt::check_hresult(m_textServices->OnTxPropertyBitsChange(m_propBitsMask, m_propBits));
14211438
m_propBitsMask = 0;
@@ -1427,6 +1444,10 @@ void WindowsTextInputComponentView::InternalFinalize() noexcept {
14271444
if (m_mounted) {
14281445
UpdatePropertyBits();
14291446

1447+
if (m_recalculateContentVerticalOffset) {
1448+
calculateContentVerticalOffset();
1449+
}
1450+
14301451
ensureDrawingSurface();
14311452
if (m_needsRedraw) {
14321453
DrawText();
@@ -1488,12 +1509,6 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
14881509
// m_crText = RemoveAlpha(fontDetails.FontColor);
14891510
// }
14901511

1491-
// set font face
1492-
// cfNew.dwMask |= CFM_FACE;
1493-
// NetUIWzCchCopy(cfNew.szFaceName, _countof(cfNew.szFaceName), fontDetails.FontName.c_str());
1494-
// cfNew.bPitchAndFamily = FF_DONTCARE;
1495-
1496-
// set font size -- 15 to convert twips to pt
14971512
const auto &props = windowsTextInputProps();
14981513
float fontSize =
14991514
(std::isnan(props.textAttributes.fontSize) ? facebook::react::TextAttributes::defaultTextAttributes().fontSize
@@ -1504,8 +1519,8 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
15041519
fontSize *=
15051520
(maxFontSizeMultiplier >= 1.0f) ? std::min(maxFontSizeMultiplier, m_fontSizeMultiplier) : m_fontSizeMultiplier;
15061521

1507-
// TODO get fontSize from props.textAttributes, or defaultTextAttributes, or fragment?
15081522
cfNew.dwMask |= CFM_SIZE;
1523+
// set font size -- 15 to convert twips to pt
15091524
cfNew.yHeight = static_cast<LONG>(fontSize * 15);
15101525

15111526
// set bold
@@ -1540,7 +1555,11 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
15401555
std::wstring fontFamily =
15411556
std::wstring(props.textAttributes.fontFamily.begin(), props.textAttributes.fontFamily.end());
15421557
wcsncpy_s(cfNew.szFaceName, fontFamily.c_str(), LF_FACESIZE);
1558+
} else {
1559+
cfNew.dwMask |= CFM_FACE;
1560+
wcsncpy_s(cfNew.szFaceName, L"Segoe UI\0", LF_FACESIZE);
15431561
}
1562+
cfNew.bPitchAndFamily = FF_DONTCARE;
15441563

15451564
// set char offset
15461565
cfNew.dwMask |= CFM_OFFSET;
@@ -1549,7 +1568,8 @@ void WindowsTextInputComponentView::UpdateCharFormat() noexcept {
15491568
// set letter spacing
15501569
float letterSpacing = props.textAttributes.letterSpacing;
15511570
if (!std::isnan(letterSpacing)) {
1552-
updateLetterSpacing(letterSpacing);
1571+
cfNew.dwMask |= CFM_SPACING;
1572+
cfNew.sSpacing = static_cast<SHORT>(letterSpacing * 20); // Convert to TWIPS
15531573
}
15541574

15551575
// set charset
@@ -1665,7 +1685,7 @@ winrt::com_ptr<::IDWriteTextLayout> WindowsTextInputComponentView::CreatePlaceho
16651685
const auto &props = windowsTextInputProps();
16661686
facebook::react::TextAttributes textAttributes = props.textAttributes;
16671687
if (std::isnan(props.textAttributes.fontSize)) {
1668-
textAttributes.fontSize = 12.0f;
1688+
facebook::react::TextAttributes::defaultTextAttributes().fontSize;
16691689
}
16701690
textAttributes.fontSizeMultiplier = m_fontSizeMultiplier;
16711691
fragment1.string = props.placeholder;
@@ -1682,6 +1702,26 @@ winrt::com_ptr<::IDWriteTextLayout> WindowsTextInputComponentView::CreatePlaceho
16821702
return textLayout;
16831703
}
16841704

1705+
void WindowsTextInputComponentView::calculateContentVerticalOffset() noexcept {
1706+
m_recalculateContentVerticalOffset = false;
1707+
1708+
const auto &props = windowsTextInputProps();
1709+
1710+
m_contentOffsetPx = {
1711+
static_cast<LONG>(m_layoutMetrics.contentInsets.left * m_layoutMetrics.pointScaleFactor),
1712+
static_cast<LONG>(m_layoutMetrics.contentInsets.top * m_layoutMetrics.pointScaleFactor)};
1713+
1714+
if (props.multiline) {
1715+
// Align to the top for multiline
1716+
return;
1717+
}
1718+
1719+
auto [contentWidth, contentHeight] = GetContentSize();
1720+
1721+
m_contentOffsetPx.y += static_cast<LONG>(std::round(
1722+
((m_layoutMetrics.getContentFrame().size.height - contentHeight) / 2) * m_layoutMetrics.pointScaleFactor));
1723+
}
1724+
16851725
void WindowsTextInputComponentView::DrawText() noexcept {
16861726
m_needsRedraw = true;
16871727
if (m_cDrawBlock || theme()->IsEmpty() || !m_textServices) {
@@ -1707,16 +1747,13 @@ void WindowsTextInputComponentView::DrawText() noexcept {
17071747
assert(d2dDeviceContext->GetUnitMode() == D2D1_UNIT_MODE_DIPS);
17081748

17091749
RECTL rc{
1710-
static_cast<LONG>(offset.x),
1711-
static_cast<LONG>(offset.y),
1712-
static_cast<LONG>(offset.x) + static_cast<LONG>(m_imgWidth),
1713-
static_cast<LONG>(offset.y) + static_cast<LONG>(m_imgHeight)};
1750+
offset.x + m_contentOffsetPx.x,
1751+
offset.y + m_contentOffsetPx.y,
1752+
offset.x + m_contentOffsetPx.x + static_cast<LONG>(m_imgWidth),
1753+
offset.y + m_contentOffsetPx.y + static_cast<LONG>(m_imgHeight)};
17141754

17151755
RECT rcClient{
1716-
static_cast<LONG>(offset.x),
1717-
static_cast<LONG>(offset.y),
1718-
static_cast<LONG>(offset.x) + static_cast<LONG>(m_imgWidth),
1719-
static_cast<LONG>(offset.y) + static_cast<LONG>(m_imgHeight)};
1756+
offset.x, offset.y, offset.x + static_cast<LONG>(m_imgWidth), offset.y + static_cast<LONG>(m_imgHeight)};
17201757

17211758
{
17221759
m_cDrawBlock++; // Dont use AutoDrawBlock as we are already in draw, and dont need to draw again.
@@ -1771,8 +1808,8 @@ void WindowsTextInputComponentView::DrawText() noexcept {
17711808
// draw text
17721809
d2dDeviceContext->DrawTextLayout(
17731810
D2D1::Point2F(
1774-
static_cast<FLOAT>((offset.x + m_layoutMetrics.contentInsets.left) / m_layoutMetrics.pointScaleFactor),
1775-
static_cast<FLOAT>((offset.y + m_layoutMetrics.contentInsets.top) / m_layoutMetrics.pointScaleFactor)),
1811+
static_cast<FLOAT>(offset.x + m_contentOffsetPx.x) / m_layoutMetrics.pointScaleFactor,
1812+
static_cast<FLOAT>(offset.y + m_contentOffsetPx.y) / m_layoutMetrics.pointScaleFactor),
17761813
textLayout.get(),
17771814
brush.get(),
17781815
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
@@ -1848,22 +1885,6 @@ void WindowsTextInputComponentView::autoCapitalizeOnUpdateProps(
18481885
}
18491886
}
18501887

1851-
void WindowsTextInputComponentView::updateLetterSpacing(float letterSpacing) noexcept {
1852-
CHARFORMAT2W cf = {};
1853-
cf.cbSize = sizeof(CHARFORMAT2W);
1854-
cf.dwMask = CFM_SPACING;
1855-
cf.sSpacing = static_cast<SHORT>(letterSpacing * 20); // Convert to TWIPS
1856-
1857-
LRESULT res;
1858-
1859-
// Apply to all existing text like placeholder
1860-
winrt::check_hresult(m_textServices->TxSendMessage(EM_SETCHARFORMAT, SCF_ALL, reinterpret_cast<LPARAM>(&cf), &res));
1861-
1862-
// Apply to future text input
1863-
winrt::check_hresult(
1864-
m_textServices->TxSendMessage(EM_SETCHARFORMAT, SCF_SELECTION, reinterpret_cast<LPARAM>(&cf), &res));
1865-
}
1866-
18671888
void WindowsTextInputComponentView::updateAutoCorrect(bool enable) noexcept {
18681889
LRESULT lresult;
18691890
winrt::check_hresult(m_textServices->TxSendMessage(

vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,10 @@ struct WindowsTextInputComponentView
117117
const std::string &previousCapitalizationType,
118118
const std::string &newcapitalizationType) noexcept;
119119

120-
void updateLetterSpacing(float letterSpacing) noexcept;
121120
void updateAutoCorrect(bool value) noexcept;
122121
void updateSpellCheck(bool value) noexcept;
123122
void ShowContextMenu(const winrt::Windows::Foundation::Point &position) noexcept;
123+
void calculateContentVerticalOffset() noexcept;
124124

125125
winrt::Windows::UI::Composition::CompositionSurfaceBrush m_brush{nullptr};
126126
winrt::Microsoft::ReactNative::Composition::Experimental::ICaretVisual m_caretVisual{nullptr};
@@ -145,6 +145,9 @@ struct WindowsTextInputComponentView
145145
bool m_hasFocus{false};
146146
bool m_clearTextOnSubmit{false};
147147
bool m_multiline{false};
148+
LONG m_contentVerticalOffsetPx{0}; // Used to center single line text within the client rect
149+
bool m_recalculateContentVerticalOffset{true};
150+
POINT m_contentOffsetPx{0, 0};
148151
DWORD m_propBitsMask{0};
149152
DWORD m_propBits{0};
150153
HCURSOR m_hcursor{nullptr};

vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/textlayoutmanager/WindowsTextLayoutManager.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,10 @@ void WindowsTextLayoutManager::GetTextLayout(
9696

9797
winrt::com_ptr<IDWriteTextFormat> spTextFormat;
9898

99-
float fontSizeText = outerFragment.textAttributes.fontSize;
99+
float fontSizeText =
100+
(std::isnan(outerFragment.textAttributes.fontSize)
101+
? facebook::react::TextAttributes::defaultTextAttributes().fontSize
102+
: outerFragment.textAttributes.fontSize);
100103
if (outerFragment.textAttributes.allowFontScaling.value_or(true) &&
101104
!std::isnan(outerFragment.textAttributes.fontSizeMultiplier)) {
102105
float maxFontSizeMultiplierText = cDefaultMaxFontSizeMultiplier;
@@ -287,7 +290,9 @@ void WindowsTextLayoutManager::GetTextLayout(
287290
maxFontSizeMultiplier =
288291
(!std::isnan(attributes.maxFontSizeMultiplier) ? attributes.maxFontSizeMultiplier
289292
: cDefaultMaxFontSizeMultiplier);
290-
float fontSize = attributes.fontSize;
293+
float fontSize =
294+
(std::isnan(attributes.fontSize) ? facebook::react::TextAttributes::defaultTextAttributes().fontSize
295+
: attributes.fontSize);
291296
if (attributes.allowFontScaling.value_or(true) && (!std::isnan(attributes.fontSizeMultiplier))) {
292297
fontSize *= (maxFontSizeMultiplier >= 1.0f) ? std::min(maxFontSizeMultiplier, attributes.fontSizeMultiplier)
293298
: attributes.fontSizeMultiplier;

0 commit comments

Comments
 (0)