Files
react-native/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.cpp
T
Samuel Susla 3ee1e5312a Back out "Rename measure to measureContent and pass it LayoutContext"
Summary:
Original commit changeset: 8928b59d5194

Changelog: [Internal]

Reviewed By: makovkastar

Differential Revision: D20246918

fbshipit-source-id: 0b9142d9bc4774a07304769126411a34cc8c33c5
2020-03-04 05:01:53 -08:00

186 lines
7.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 <fbjni/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>
using namespace facebook::jni;
namespace facebook {
namespace react {
extern const char AndroidTextInputComponentName[] = "AndroidTextInput";
void AndroidTextInputShadowNode::setContextContainer(
ContextContainer *contextContainer) {
ensureUnsealed();
contextContainer_ = contextContainer;
}
AttributedString AndroidTextInputShadowNode::getAttributedString() const {
// Use BaseTextShadowNode to get attributed string from children
auto childTextAttributes = TextAttributes::defaultTextAttributes();
childTextAttributes.apply(getConcreteProps().textAttributes);
auto attributedString =
BaseTextShadowNode::getAttributedString(childTextAttributes, *this);
// BaseTextShadowNode only gets children. We must detect and prepend text
// value attributes manually.
if (!getConcreteProps().text.empty()) {
auto textAttributes = TextAttributes::defaultTextAttributes();
textAttributes.apply(getConcreteProps().textAttributes);
auto fragment = AttributedString::Fragment{};
fragment.string = getConcreteProps().text;
fragment.textAttributes = textAttributes;
// If the TextInput opacity is 0 < n < 1, the opacity of the TextInput and
// text value's background will stack. This is a hack/workaround to prevent
// that effect.
fragment.textAttributes.backgroundColor = clearColor();
fragment.parentShadowView = ShadowView(*this);
attributedString.prependFragment(fragment);
}
return attributedString;
}
// 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.
AttributedString AndroidTextInputShadowNode::getPlaceholderAttributedString()
const {
// Return placeholder text, since text and children are empty.
auto textAttributedString = AttributedString{};
auto fragment = AttributedString::Fragment{};
fragment.string = getConcreteProps().placeholder;
if (fragment.string.empty()) {
fragment.string = " ";
}
auto textAttributes = TextAttributes::defaultTextAttributes();
textAttributes.apply(getConcreteProps().textAttributes);
// If there's no text, it's possible that this Fragment isn't actually
// appended to the AttributedString (see implementation of appendFragment)
fragment.textAttributes = textAttributes;
fragment.parentShadowView = ShadowView(*this);
textAttributedString.appendFragment(fragment);
return textAttributedString;
}
void AndroidTextInputShadowNode::setTextLayoutManager(
SharedTextLayoutManager textLayoutManager) {
ensureUnsealed();
textLayoutManager_ = textLayoutManager;
}
AttributedString AndroidTextInputShadowNode::getMostRecentAttributedString()
const {
auto const &state = getStateData();
auto reactTreeAttributedString = getAttributedString();
return (
state.reactTreeAttributedString == reactTreeAttributedString
? state.attributedString
: reactTreeAttributedString);
}
void AndroidTextInputShadowNode::updateStateIfNeeded() {
ensureUnsealed();
auto reactTreeAttributedString = getAttributedString();
auto const &state = getStateData();
assert(textLayoutManager_);
assert(
(!state.layoutManager || state.layoutManager == textLayoutManager_) &&
"`StateData` refers to a different `TextLayoutManager`");
// Tree is often out of sync with the value of the TextInput.
// This is by design - don't change the value of the TextInput in the State,
// and therefore in Java, unless the tree itself changes.
if (state.reactTreeAttributedString == reactTreeAttributedString &&
state.layoutManager == textLayoutManager_) {
return;
}
// Store default TextAttributes in state.
// In the case where the TextInput is completely empty (no value, no
// defaultValue, no placeholder, no children) there are therefore no fragments
// in the AttributedString, and when State is updated, it needs some way to
// reconstruct a Fragment with default TextAttributes.
auto defaultTextAttributes = TextAttributes::defaultTextAttributes();
defaultTextAttributes.apply(getConcreteProps().textAttributes);
auto newEventCount =
(state.reactTreeAttributedString == reactTreeAttributedString
? 0
: getConcreteProps().mostRecentEventCount);
auto newAttributedString = getMostRecentAttributedString();
// Even if we're here and updating state, it may be only to update the layout
// manager If that is the case, make sure we don't update text: pass in the
// current attributedString unchanged, and pass in zero for the "event count"
// so no changes are applied There's no way to prevent a state update from
// flowing to Java, so we just ensure it's a noop in those cases.
setStateData(AndroidTextInputState{newEventCount,
newAttributedString,
reactTreeAttributedString,
getConcreteProps().paragraphAttributes,
defaultTextAttributes,
ShadowView(*this),
textLayoutManager_,
state.defaultThemePaddingStart,
state.defaultThemePaddingEnd,
state.defaultThemePaddingTop,
state.defaultThemePaddingBottom});
}
#pragma mark - LayoutableShadowNode
Size AndroidTextInputShadowNode::measure(
LayoutConstraints layoutConstraints) const {
// Layout is called right after measure.
// Measure is marked as `const`, and `layout` is not; so State can be updated
// during layout, but not during `measure`. If State is out-of-date in layout,
// it's too late: measure will have already operated on old State. Thus, we
// use the same value here that we *will* use in layout to update the state.
AttributedString attributedString = getMostRecentAttributedString();
if (attributedString.isEmpty()) {
attributedString = getPlaceholderAttributedString();
}
if (attributedString.isEmpty()) {
return {0, 0};
}
return textLayoutManager_->measure(
AttributedStringBox{attributedString},
getConcreteProps().paragraphAttributes,
layoutConstraints);
}
void AndroidTextInputShadowNode::layout(LayoutContext layoutContext) {
updateStateIfNeeded();
ConcreteViewShadowNode::layout(layoutContext);
}
} // namespace react
} // namespace facebook