From 4b35582b589ef22a14cb3c2cbf30c81026968a59 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Fri, 13 Aug 2021 22:37:44 -0400 Subject: [PATCH] Optimizes text tree updates to only DFS for highlight updates if needed --- .../Views/TextViewManager.cpp | 53 +++++++++++++------ .../Views/TextViewManager.h | 10 +++- .../Views/VirtualTextViewManager.cpp | 25 ++++++--- .../Views/VirtualTextViewManager.h | 3 +- 4 files changed, 67 insertions(+), 24 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Views/TextViewManager.cpp b/vnext/Microsoft.ReactNative/Views/TextViewManager.cpp index a594a6787ab..e49b8860e3c 100644 --- a/vnext/Microsoft.ReactNative/Views/TextViewManager.cpp +++ b/vnext/Microsoft.ReactNative/Views/TextViewManager.cpp @@ -54,6 +54,11 @@ class TextShadowNode final : public ShadowNodeBase { VirtualTextShadowNode::ApplyTextTransform( childNode, textTransform, /* forceUpdate = */ false, /* isRoot = */ false); + if (IsVirtualTextShadowNode(&childNode)) { + auto &textChildNode = static_cast(childNode); + m_hasDescendantBackgroundColor |= textChildNode.m_hasDescendantBackgroundColor; + } + auto addInline = true; if (index == 0) { auto run = childNode.GetView().try_as(); @@ -97,13 +102,22 @@ class TextShadowNode final : public ShadowNodeBase { textBlock.TextHighlighters().Clear(); auto nestedIndex = 0; - if (const auto uiManager = GetNativeUIManager(GetViewManager()->GetReactContext()).lock()) { - for (auto childTag : m_children) { - if (const auto childNode = uiManager->getHost()->FindShadowNodeForTag(childTag)) { - nestedIndex = AddNestedTextHighlighter( - m_backgroundColor, m_foregroundColor, static_cast(childNode), nestedIndex); + if (m_hasDescendantBackgroundColor) { + const auto highlighterCount = textBlock.TextHighlighters().Size(); + if (const auto uiManager = GetNativeUIManager(GetViewManager()->GetReactContext()).lock()) { + for (auto childTag : m_children) { + if (const auto childNode = uiManager->getHost()->FindShadowNodeForTag(childTag)) { + nestedIndex = AddNestedTextHighlighter( + m_backgroundColor, m_foregroundColor, static_cast(childNode), nestedIndex); + } } } + + if (textBlock.TextHighlighters().Size() == 0) { + m_hasDescendantBackgroundColor = false; + } + } else { + nestedIndex = textBlock.Text().size(); } if (m_backgroundColor) { @@ -125,6 +139,7 @@ class TextShadowNode final : public ShadowNodeBase { if (const auto run = node->GetView().try_as()) { return startIndex + run.Text().size(); } else if (const auto span = node->GetView().try_as()) { + const auto textBlock = GetView().as(); winrt::TextHighlighter highlighter{nullptr}; auto parentBackgroundColor = backgroundColor; auto parentForegroundColor = foregroundColor; @@ -134,13 +149,10 @@ class TextShadowNode final : public ShadowNodeBase { virtualTextNode->m_backgroundColor || (backgroundColor && virtualTextNode->m_foregroundColor); if (requiresHighlighter) { highlighter = {}; - if (virtualTextNode->m_backgroundColor) { - parentBackgroundColor = virtualTextNode->m_backgroundColor; - } - if (virtualTextNode->m_foregroundColor) { - parentForegroundColor = virtualTextNode->m_foregroundColor; - } - + parentBackgroundColor = + virtualTextNode->m_backgroundColor ? virtualTextNode->m_backgroundColor : parentBackgroundColor; + parentForegroundColor = + virtualTextNode->m_foregroundColor ? virtualTextNode->m_foregroundColor : parentForegroundColor; highlighter.Background(SolidBrushFromColor(parentBackgroundColor.value())); if (parentForegroundColor) { highlighter.Foreground(SolidBrushFromColor(parentForegroundColor.value())); @@ -148,6 +160,7 @@ class TextShadowNode final : public ShadowNodeBase { } } + const auto initialHighlighterCount = textBlock.TextHighlighters().Size(); auto nestedIndex = startIndex; if (const auto uiManager = GetNativeUIManager(node->GetViewManager()->GetReactContext()).lock()) { for (auto childTag : node->m_children) { @@ -160,7 +173,10 @@ class TextShadowNode final : public ShadowNodeBase { if (highlighter) { highlighter.Ranges().Append({startIndex, nestedIndex - startIndex}); - GetView().as().TextHighlighters().InsertAt(0, highlighter); + textBlock.TextHighlighters().InsertAt(0, highlighter); + } else if (IsVirtualTextShadowNode(node) && textBlock.TextHighlighters().Size() == initialHighlighterCount) { + const auto virtualTextNode = static_cast(node); + virtualTextNode->m_hasDescendantBackgroundColor = false; } return nestedIndex; @@ -170,6 +186,7 @@ class TextShadowNode final : public ShadowNodeBase { } TextTransform textTransform{TextTransform::Undefined}; + bool m_hasDescendantBackgroundColor{false}; }; TextViewManager::TextViewManager(const Mso::React::IReactContext &context) : Super(context) {} @@ -312,7 +329,7 @@ void TextViewManager::OnDescendantTextPropertyChanged(ShadowNodeBase *node, Prop if (IsTextShadowNode(node)) { const auto textNode = static_cast(node); - if (propertyChangeType == PropertyChangeType::Text) { + if ((propertyChangeType & PropertyChangeType::Text) == PropertyChangeType::Text) { const auto element = node->GetView().as(); // If name is set, it's controlled by accessibilityLabel, and it's already @@ -328,7 +345,13 @@ void TextViewManager::OnDescendantTextPropertyChanged(ShadowNodeBase *node, Prop } } - // Rebuild highlights + // If a property change added a background color to the text tree, update + // the flag to signal recursive highlighter updates are required. + if ((propertyChangeType & PropertyChangeType::AddBackgroundColor) == PropertyChangeType::AddBackgroundColor) { + textNode->m_hasDescendantBackgroundColor = true; + } + + // Recalculate text highlighters textNode->RecalculateTextHighlighters(); } } diff --git a/vnext/Microsoft.ReactNative/Views/TextViewManager.h b/vnext/Microsoft.ReactNative/Views/TextViewManager.h index b571304afd1..08e4a0df89e 100644 --- a/vnext/Microsoft.ReactNative/Views/TextViewManager.h +++ b/vnext/Microsoft.ReactNative/Views/TextViewManager.h @@ -8,12 +8,18 @@ namespace Microsoft::ReactNative { +enum class PropertyChangeType : std::uint_fast8_t { + None = 0, + Text = 1 << 0, + AddBackgroundColor = 1 << 1, +}; + +DEFINE_ENUM_FLAG_OPERATORS(PropertyChangeType); + class TextViewManager : public FrameworkElementViewManager { using Super = FrameworkElementViewManager; public: - enum class PropertyChangeType { Text = 0, Highlight }; - TextViewManager(const Mso::React::IReactContext &context); ShadowNode *createShadow() const override; diff --git a/vnext/Microsoft.ReactNative/Views/VirtualTextViewManager.cpp b/vnext/Microsoft.ReactNative/Views/VirtualTextViewManager.cpp index 8ef29098ac0..02163a9a11f 100644 --- a/vnext/Microsoft.ReactNative/Views/VirtualTextViewManager.cpp +++ b/vnext/Microsoft.ReactNative/Views/VirtualTextViewManager.cpp @@ -28,18 +28,25 @@ namespace Microsoft::ReactNative { void VirtualTextShadowNode::AddView(ShadowNode &child, int64_t index) { auto &childNode = static_cast(child); ApplyTextTransform(childNode, textTransform, /* forceUpdate = */ false, /* isRoot = */ false); + auto propertyChangeType = PropertyChangeType::Text; + if (IsVirtualTextShadowNode(&childNode)) { + const auto &childTextNode = static_cast(childNode); + m_hasDescendantBackgroundColor |= childTextNode.m_hasDescendantBackgroundColor; + propertyChangeType |= + childTextNode.m_backgroundColor ? PropertyChangeType::AddBackgroundColor : PropertyChangeType::None; + } Super::AddView(child, index); - NotifyAncestorsTextPropertyChanged(TextViewManager::PropertyChangeType::Text); + NotifyAncestorsTextPropertyChanged(propertyChangeType); } void VirtualTextShadowNode::RemoveChildAt(int64_t indexToRemove) { Super::RemoveChildAt(indexToRemove); - NotifyAncestorsTextPropertyChanged(TextViewManager::PropertyChangeType::Text); + NotifyAncestorsTextPropertyChanged(PropertyChangeType::Text); } void VirtualTextShadowNode::removeAllChildren() { Super::removeAllChildren(); - NotifyAncestorsTextPropertyChanged(TextViewManager::PropertyChangeType::Text); + NotifyAncestorsTextPropertyChanged(PropertyChangeType::Text); } void VirtualTextShadowNode::ApplyTextTransform( @@ -93,7 +100,7 @@ void VirtualTextShadowNode::ApplyTextTransform( } } -void VirtualTextShadowNode::NotifyAncestorsTextPropertyChanged(TextViewManager::PropertyChangeType propertyChangeType) { +void VirtualTextShadowNode::NotifyAncestorsTextPropertyChanged(PropertyChangeType propertyChangeType) { if (auto uiManager = GetNativeUIManager(GetViewManager()->GetReactContext()).lock()) { auto host = uiManager->getHost(); ShadowNodeBase *parent = static_cast(host->FindShadowNodeForTag(m_parent)); @@ -103,6 +110,9 @@ void VirtualTextShadowNode::NotifyAncestorsTextPropertyChanged(TextViewManager:: if (IsTextShadowNode(parent)) { (static_cast(viewManager))->OnDescendantTextPropertyChanged(parent, propertyChangeType); break; + } else if (IsVirtualTextShadowNode(parent)) { + auto textParent = static_cast(parent); + textParent->m_hasDescendantBackgroundColor |= m_hasDescendantBackgroundColor; } parent = static_cast(host->FindShadowNodeForTag(parent->GetParent())); } @@ -133,7 +143,7 @@ bool VirtualTextViewManager::UpdateProperty( auto node = static_cast(nodeToUpdate); if (IsValidOptionalColorValue(propertyValue)) { node->m_foregroundColor = OptionalColorFrom(propertyValue); - node->NotifyAncestorsTextPropertyChanged(TextViewManager::PropertyChangeType::Highlight); + node->NotifyAncestorsTextPropertyChanged(PropertyChangeType::None); } } else if (TryUpdateFontProperties(span, propertyName, propertyValue)) { } else if (TryUpdateCharacterSpacing(span, propertyName, propertyValue)) { @@ -147,7 +157,10 @@ bool VirtualTextViewManager::UpdateProperty( auto node = static_cast(nodeToUpdate); if (IsValidOptionalColorValue(propertyValue)) { node->m_backgroundColor = OptionalColorFrom(propertyValue); - node->NotifyAncestorsTextPropertyChanged(TextViewManager::PropertyChangeType::Highlight); + node->m_hasDescendantBackgroundColor |= node->m_backgroundColor.has_value(); + const auto propertyChangeType = + node->m_backgroundColor ? PropertyChangeType::AddBackgroundColor : PropertyChangeType::None; + node->NotifyAncestorsTextPropertyChanged(propertyChangeType); } } else { return Super::UpdateProperty(nodeToUpdate, propertyName, propertyValue); diff --git a/vnext/Microsoft.ReactNative/Views/VirtualTextViewManager.h b/vnext/Microsoft.ReactNative/Views/VirtualTextViewManager.h index 119dc6f9a76..028ef45f561 100644 --- a/vnext/Microsoft.ReactNative/Views/VirtualTextViewManager.h +++ b/vnext/Microsoft.ReactNative/Views/VirtualTextViewManager.h @@ -20,12 +20,13 @@ struct VirtualTextShadowNode final : public ShadowNodeBase { void RemoveChildAt(int64_t indexToRemove) override; void removeAllChildren() override; - void NotifyAncestorsTextPropertyChanged(TextViewManager::PropertyChangeType propertyChangeType); + void NotifyAncestorsTextPropertyChanged(PropertyChangeType propertyChangeType); static void ApplyTextTransform(ShadowNodeBase &node, TextTransform transform, bool forceUpdate, bool isRoot); std::optional m_backgroundColor; std::optional m_foregroundColor; + bool m_hasDescendantBackgroundColor{false}; }; class VirtualTextViewManager : public ViewManagerBase {