Files
react-native/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.cpp
T
Valentin Shergin c4876d0313 Fabric: Introducing AttributedStringBox
Summary:
The diff implements a new class called `AttributedStringBox` that represents an object storing a shared `AttributedString` *or* a shared pointer to some opaque platform-specific object that can be used as an attributed string. The class serves two main purposes:
- Represent type-erased attributed string entity (which can be platform-specific or platform-independent);
- Represent a container that can be copied with constant complexity.

Why? Several reasons:
- Sometimes it makes sense to keep an attributed string as a shared resource. This way we don't need to pay for expensive copying and we also implement a copy-on-write semantics on top of that if needed.
- We need to extend a TextLayoutMeasure API to support measuring some platform-specific attributed string implementation to remove the necessity of converting a string back and forth between representations. That's especially important for TextInput because we will need to measure that very efficiently (and the source of measuring, in this case, is a platform attributed string).

In other words, we need something to store inside TextInputState to measure and update very efficiently. The source of this data might be a native TextInput control or a data from React, to represent that kinda object we need this data structure (and interfaces that deal with it).

Changelog: [Internal] Fabric-specific internal change.

Reviewed By: sammy-SC

Differential Revision: D18670793

fbshipit-source-id: bc0164f801f28642f7c6da340af12acf33b85d24
2019-12-04 18:36:48 -08:00

127 lines
4.0 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "AndroidTextInputShadowNode.h"
#include <fb/fbjni.h>
#include <react/attributedstring/AttributedStringBox.h>
#include <react/attributedstring/TextAttributes.h>
#include <react/components/text/BaseTextShadowNode.h>
#include <react/core/LayoutConstraints.h>
#include <react/core/LayoutContext.h>
#include <react/core/conversions.h>
#include <react/jni/ReadableNativeMap.h>
#include <Glog/logging.h>
using namespace facebook::jni;
namespace facebook {
namespace react {
extern const char AndroidTextInputComponentName[] = "AndroidTextInput";
void AndroidTextInputShadowNode::setContextContainer(
ContextContainer *contextContainer) {
ensureUnsealed();
contextContainer_ = contextContainer;
}
AttributedString AndroidTextInputShadowNode::getAttributedString(
bool usePlaceholders) const {
auto textAttributes = TextAttributes::defaultTextAttributes();
textAttributes.apply(getProps()->textAttributes);
// Use BaseTextShadowNode to get attributed string from children
auto const &attributedString =
BaseTextShadowNode::getAttributedString(textAttributes, *this);
if (!attributedString.isEmpty()) {
return attributedString;
}
if (!getProps()->text.empty() || usePlaceholders) {
// If the BaseTextShadowNode didn't detect any child Text nodes, we
// may actually just have a `text` attribute.
// TODO: figure out why BaseTextShadowNode doesn't pick this up, this
// is a bug. A minimal Playground example that triggers this: P122991121
auto textAttributedString = AttributedString{};
auto fragment = AttributedString::Fragment{};
fragment.string = getProps()->text;
if (usePlaceholders) {
// Return placeholder text instead, if text was empty.
if (fragment.string.empty()) {
fragment.string = getProps()->placeholder;
}
// For measurement purposes, we want to make sure that there's at least a
// single character in the string so that the measured height is greater
// than zero. Otherwise, empty TextInputs with no placeholder don't
// display at all.
if (fragment.string.empty()) {
fragment.string = " ";
}
}
fragment.textAttributes = textAttributes;
fragment.parentShadowView = ShadowView(*this);
textAttributedString.appendFragment(fragment);
return textAttributedString;
}
return attributedString;
}
void AndroidTextInputShadowNode::setTextLayoutManager(
SharedTextLayoutManager textLayoutManager) {
ensureUnsealed();
textLayoutManager_ = textLayoutManager;
}
void AndroidTextInputShadowNode::updateStateIfNeeded() {
ensureUnsealed();
auto attributedString = getAttributedString(false);
auto const &state = getStateData();
assert(textLayoutManager_);
assert(
(!state.layoutManager || state.layoutManager == textLayoutManager_) &&
"`StateData` refers to a different `TextLayoutManager`");
if (state.attributedString == attributedString &&
state.layoutManager == textLayoutManager_) {
return;
}
setStateData(AndroidTextInputState{state.mostRecentEventCount,
attributedString,
getProps()->paragraphAttributes,
textLayoutManager_});
}
#pragma mark - LayoutableShadowNode
Size AndroidTextInputShadowNode::measure(
LayoutConstraints layoutConstraints) const {
AttributedString attributedString = getAttributedString(true);
if (attributedString.isEmpty()) {
return {0, 0};
}
return textLayoutManager_->measure(
AttributedStringBox{attributedString},
getProps()->paragraphAttributes,
layoutConstraints);
}
void AndroidTextInputShadowNode::layout(LayoutContext layoutContext) {
updateStateIfNeeded();
ConcreteViewShadowNode::layout(layoutContext);
}
} // namespace react
} // namespace facebook