Skip to content

Commit

Permalink
Optimizes text tree updates to only DFS for highlight updates if needed
Browse files Browse the repository at this point in the history
  • Loading branch information
rozele committed Aug 14, 2021
1 parent f1c2daa commit d662c8d
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 24 deletions.
53 changes: 38 additions & 15 deletions vnext/Microsoft.ReactNative/Views/TextViewManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ class TextShadowNode final : public ShadowNodeBase {
VirtualTextShadowNode::ApplyTextTransform(
childNode, textTransform, /* forceUpdate = */ false, /* isRoot = */ false);

if (IsVirtualTextShadowNode(&childNode)) {
auto &textChildNode = static_cast<VirtualTextShadowNode &>(childNode);
m_hasDescendantBackgroundColor |= textChildNode.m_hasDescendantBackgroundColor;
}

auto addInline = true;
if (index == 0) {
auto run = childNode.GetView().try_as<winrt::Run>();
Expand Down Expand Up @@ -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<ShadowNodeBase *>(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<ShadowNodeBase *>(childNode), nestedIndex);
}
}
}

if (textBlock.TextHighlighters().Size() == 0) {
m_hasDescendantBackgroundColor = false;
}
} else {
nestedIndex = textBlock.Text().size();
}

if (m_backgroundColor) {
Expand All @@ -125,6 +139,7 @@ class TextShadowNode final : public ShadowNodeBase {
if (const auto run = node->GetView().try_as<winrt::Run>()) {
return startIndex + run.Text().size();
} else if (const auto span = node->GetView().try_as<winrt::Span>()) {
const auto textBlock = GetView().as<xaml::Controls::TextBlock>();
winrt::TextHighlighter highlighter{nullptr};
auto parentBackgroundColor = backgroundColor;
auto parentForegroundColor = foregroundColor;
Expand All @@ -134,20 +149,18 @@ 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()));
}
}
}

const auto initialHighlighterCount = textBlock.TextHighlighters().Size();
auto nestedIndex = startIndex;
if (const auto uiManager = GetNativeUIManager(node->GetViewManager()->GetReactContext()).lock()) {
for (auto childTag : node->m_children) {
Expand All @@ -160,7 +173,10 @@ class TextShadowNode final : public ShadowNodeBase {

if (highlighter) {
highlighter.Ranges().Append({startIndex, nestedIndex - startIndex});
GetView().as<xaml::Controls::TextBlock>().TextHighlighters().InsertAt(0, highlighter);
textBlock.TextHighlighters().InsertAt(0, highlighter);
} else if (IsVirtualTextShadowNode(node) && textBlock.TextHighlighters().Size() == initialHighlighterCount) {
const auto virtualTextNode = static_cast<VirtualTextShadowNode *>(node);
virtualTextNode->m_hasDescendantBackgroundColor = false;
}

return nestedIndex;
Expand All @@ -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) {}
Expand Down Expand Up @@ -312,7 +329,7 @@ void TextViewManager::OnDescendantTextPropertyChanged(ShadowNodeBase *node, Prop
if (IsTextShadowNode(node)) {
const auto textNode = static_cast<TextShadowNode *>(node);

if (propertyChangeType == PropertyChangeType::Text) {
if ((propertyChangeType & PropertyChangeType::Text) == PropertyChangeType::Text) {
const auto element = node->GetView().as<xaml::Controls::TextBlock>();

// If name is set, it's controlled by accessibilityLabel, and it's already
Expand All @@ -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();
}
}
Expand Down
10 changes: 8 additions & 2 deletions vnext/Microsoft.ReactNative/Views/TextViewManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
27 changes: 21 additions & 6 deletions vnext/Microsoft.ReactNative/Views/VirtualTextViewManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,25 @@ namespace Microsoft::ReactNative {
void VirtualTextShadowNode::AddView(ShadowNode &child, int64_t index) {
auto &childNode = static_cast<ShadowNodeBase &>(child);
ApplyTextTransform(childNode, textTransform, /* forceUpdate = */ false, /* isRoot = */ false);
auto propertyChangeType = PropertyChangeType::Text;
if (IsVirtualTextShadowNode(&childNode)) {
const auto &childTextNode = static_cast<VirtualTextShadowNode &>(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(
Expand Down Expand Up @@ -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<ShadowNodeBase *>(host->FindShadowNodeForTag(m_parent));
Expand All @@ -103,6 +110,9 @@ void VirtualTextShadowNode::NotifyAncestorsTextPropertyChanged(TextViewManager::
if (IsTextShadowNode(parent)) {
(static_cast<TextViewManager *>(viewManager))->OnDescendantTextPropertyChanged(parent, propertyChangeType);
break;
} else if (IsVirtualTextShadowNode(parent)) {
auto textParent = static_cast<VirtualTextShadowNode *>(parent);
textParent->m_hasDescendantBackgroundColor |= m_hasDescendantBackgroundColor;
}
parent = static_cast<ShadowNodeBase *>(host->FindShadowNodeForTag(parent->GetParent()));
}
Expand Down Expand Up @@ -133,7 +143,9 @@ bool VirtualTextViewManager::UpdateProperty(
auto node = static_cast<VirtualTextShadowNode *>(nodeToUpdate);
if (IsValidOptionalColorValue(propertyValue)) {
node->m_foregroundColor = OptionalColorFrom(propertyValue);
node->NotifyAncestorsTextPropertyChanged(TextViewManager::PropertyChangeType::Highlight);
const auto propertyChangeType =
node->m_foregroundColor ? PropertyChangeType::AddBackgroundColor : PropertyChangeType::None;
node->NotifyAncestorsTextPropertyChanged(propertyChangeType);
}
} else if (TryUpdateFontProperties<winrt::TextElement>(span, propertyName, propertyValue)) {
} else if (TryUpdateCharacterSpacing<winrt::TextElement>(span, propertyName, propertyValue)) {
Expand All @@ -147,7 +159,10 @@ bool VirtualTextViewManager::UpdateProperty(
auto node = static_cast<VirtualTextShadowNode *>(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);
Expand Down
3 changes: 2 additions & 1 deletion vnext/Microsoft.ReactNative/Views/VirtualTextViewManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<winrt::Windows::UI::Color> m_backgroundColor;
std::optional<winrt::Windows::UI::Color> m_foregroundColor;
bool m_hasDescendantBackgroundColor{false};
};

class VirtualTextViewManager : public ViewManagerBase {
Expand Down

0 comments on commit d662c8d

Please sign in to comment.