Skip to content

Commit

Permalink
Apply fast text optimization to multiple runs (#10811)
Browse files Browse the repository at this point in the history
* Apply fast text optimization to multiple runs

Currently, the fast text optimization is only applied for Text nodes
with a single run child. If you have text like the following:
```
<Text>Count: {count}</Text>
```
This will create two inline children.

We can instead validate that the Text node has only runs for children,
and concatenate the context of the runs together to keep using the
optimized text path (setting the xaml::TextBlock::TextProperty).

* Change files
  • Loading branch information
rozele committed Nov 3, 2022
1 parent 6d6d44c commit ef28671
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Apply fast text optimization to multiple runs",
"packageName": "react-native-windows",
"email": "erozell@outlook.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,7 @@ void TextPropertyChangedParentVisitor::VisitText(ShadowNodeBase *node) {
}
}

// Update fast text content
if (!m_isNested && node->m_children.size() == 1) {
if (const auto childNode = GetShadowNode(node->m_children[0])) {
const auto run = static_cast<ShadowNodeBase *>(childNode)->GetView().as<winrt::Run>();
element.Text(run.Text());
}
}
TextViewManager::UpdateOptimizedText(node);
}

// Refresh text highlighters
Expand Down
76 changes: 46 additions & 30 deletions vnext/Microsoft.ReactNative/Views/TextViewManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ class TextShadowNode final : public ShadowNodeBase {
friend TextViewManager;

private:
ShadowNode *m_firstChildNode;

bool m_isTextOptimized{true};
bool m_hasDescendantTextHighlighter{false};
bool m_hasDescendantPressable{false};
std::optional<winrt::Windows::UI::Color> m_backgroundColor{};
Expand All @@ -49,9 +48,6 @@ class TextShadowNode final : public ShadowNodeBase {
winrt::event_revoker<xaml::Controls::ITextBlock> m_selectionChangedRevoker;

public:
TextShadowNode() {
m_firstChildNode = nullptr;
};
bool ImplementsPadding() override {
return true;
}
Expand All @@ -66,54 +62,67 @@ class TextShadowNode final : public ShadowNodeBase {
m_hasDescendantPressable |= textChildNode.hasDescendantPressable;
}

auto addInline = true;
// Only convert to fast text when exactly one child is attached to the root Text node
if (index == 0 && m_children.size() == 1) {
auto run = childNode.GetView().try_as<winrt::Run>();
if (run != nullptr) {
m_firstChildNode = &child;
auto textBlock = this->GetView().as<xaml::Controls::TextBlock>();
textBlock.Text(run.Text());
addInline = false;
const auto wasOptimized = m_isTextOptimized;
m_isTextOptimized = IsRawTextShadowNode(&childNode) && m_isTextOptimized;
if (m_isTextOptimized) {
// Re-build optimized text from children
UpdateOptimizedText();
} else if (wasOptimized) {
// Remove optimized text and re-construct as Inline tree
UpdateOptimizedText();
if (const auto uiManager = GetNativeUIManager(GetViewManager()->GetReactContext()).lock()) {
for (size_t i = 0; i < m_children.size(); ++i) {
if (const auto childNode =
static_cast<ShadowNodeBase *>(uiManager->getHost()->FindShadowNodeForTag(m_children[i]))) {
Super::AddView(*childNode, i);
}
}
}
} else if (m_firstChildNode != nullptr) {
assert(m_children.size() == 2);
auto textBlock = this->GetView().as<xaml::Controls::TextBlock>();
textBlock.ClearValue(xaml::Controls::TextBlock::TextProperty());
Super::AddView(*m_firstChildNode, 0);
m_firstChildNode = nullptr;
}

if (addInline) {
} else {
Super::AddView(child, index);
}

RecalculateTextHighlighters();
}

void removeAllChildren() override {
if (m_firstChildNode) {
if (m_isTextOptimized) {
auto textBlock = this->GetView().as<xaml::Controls::TextBlock>();
textBlock.ClearValue(xaml::Controls::TextBlock::TextProperty());
m_firstChildNode = nullptr;
} else {
Super::removeAllChildren();
}
RecalculateTextHighlighters();
}

void RemoveChildAt(int64_t indexToRemove) override {
if (m_firstChildNode) {
assert(indexToRemove == 0);
auto textBlock = this->GetView().as<xaml::Controls::TextBlock>();
textBlock.ClearValue(xaml::Controls::TextBlock::TextProperty());
m_firstChildNode = nullptr;
if (m_isTextOptimized) {
UpdateOptimizedText();
} else {
Super::RemoveChildAt(indexToRemove);
}
RecalculateTextHighlighters();
}

void UpdateOptimizedText() {
if (m_children.size() > 0 && m_isTextOptimized) {
if (const auto uiManager = GetNativeUIManager(GetViewManager()->GetReactContext()).lock()) {
winrt::hstring text = L"";
for (const auto childTag : m_children) {
if (const auto childNode =
static_cast<ShadowNodeBase *>(uiManager->getHost()->FindShadowNodeForTag(childTag))) {
text = text + childNode->GetView().as<winrt::Run>().Text();
}
}
auto textBlock = this->GetView().as<xaml::Controls::TextBlock>();
textBlock.Text(text);
}
} else {
auto textBlock = this->GetView().as<xaml::Controls::TextBlock>();
textBlock.ClearValue(xaml::Controls::TextBlock::TextProperty());
}
}

void RecalculateTextHighlighters() {
const auto textBlock = this->GetView().as<xaml::Controls::TextBlock>();
textBlock.TextHighlighters().Clear();
Expand Down Expand Up @@ -389,6 +398,13 @@ void TextViewManager::OnPointerEvent(
}
}

/*static*/ void TextViewManager::UpdateOptimizedText(ShadowNodeBase *node) {
if (IsTextShadowNode(node)) {
const auto textNode = static_cast<TextShadowNode *>(node);
textNode->UpdateOptimizedText();
}
}

/*static*/ void TextViewManager::SetDescendantPressable(ShadowNodeBase *node) {
if (IsTextShadowNode(node)) {
const auto textNode = static_cast<TextShadowNode *>(node);
Expand Down
2 changes: 2 additions & 0 deletions vnext/Microsoft.ReactNative/Views/TextViewManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class TextViewManager : public FrameworkElementViewManager {

static void UpdateTextHighlighters(ShadowNodeBase *node, bool highlightAdded);

static void UpdateOptimizedText(ShadowNodeBase *node);

static void SetDescendantPressable(ShadowNodeBase *node);

static TextTransform GetTextTransformValue(ShadowNodeBase *node);
Expand Down

0 comments on commit ef28671

Please sign in to comment.