From 5abe5843e2b659ed21ca87abfa5b549394a1ca0e Mon Sep 17 00:00:00 2001 From: Joshua Gross Date: Fri, 30 Aug 2019 19:02:33 -0700 Subject: [PATCH] Add C++ AndroidTextInput component for backwards-compatible Fabric support of TextInput on Android Summary: Support existing, backwards-compatible AndroidTextInput component for minimal support of TextInput on Android. Reviewed By: shergin, mdvacca Differential Revision: D17086758 fbshipit-source-id: 25726f22229e0d5dfe96eb36b386a5317601283d --- .../textinput/ReactTextInputManager.java | 21 + .../components/slider/SliderShadowNode.cpp | 3 +- .../text/basetext/BaseTextShadowNode.cpp | 2 +- .../text/basetext/BaseTextShadowNode.h | 8 +- ReactCommon/fabric/components/textinput/BUCK | 98 +++++ .../AndroidTextInputComponentDescriptor.h | 46 +++ .../AndroidTextInputEventEmitter.cpp | 179 ++++++++ .../AndroidTextInputEventEmitter.h | 148 +++++++ .../AndroidTextInputProps.cpp | 383 ++++++++++++++++++ .../androidtextinput/AndroidTextInputProps.h | 163 ++++++++ .../AndroidTextInputShadowNode.cpp | 102 +++++ .../AndroidTextInputShadowNode.h | 50 +++ .../view/yoga/YogaLayoutableShadowNode.cpp | 2 + 13 files changed, 1200 insertions(+), 5 deletions(-) create mode 100644 ReactCommon/fabric/components/textinput/BUCK create mode 100644 ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputComponentDescriptor.h create mode 100644 ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputEventEmitter.cpp create mode 100644 ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputEventEmitter.h create mode 100644 ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputProps.cpp create mode 100644 ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputProps.h create mode 100644 ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.cpp create mode 100644 ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.h diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java index 11b4d950d8e..d1b76b9f272 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java @@ -6,6 +6,7 @@ */ package com.facebook.react.views.textinput; +import android.content.Context; import android.graphics.PorterDuff; import android.graphics.Typeface; import android.graphics.drawable.Drawable; @@ -20,6 +21,7 @@ import android.view.Gravity; import android.view.KeyEvent; import android.view.View; import android.view.inputmethod.EditorInfo; +import android.widget.EditText; import android.widget.TextView; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; @@ -29,6 +31,7 @@ import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableType; import com.facebook.react.common.MapBuilder; import com.facebook.react.module.annotations.ReactModule; @@ -50,7 +53,9 @@ import com.facebook.react.views.text.DefaultStyleValuesUtil; import com.facebook.react.views.text.ReactFontManager; import com.facebook.react.views.text.ReactTextUpdate; import com.facebook.react.views.text.TextInlineImageSpan; +import com.facebook.react.views.text.TextLayoutManager; import com.facebook.yoga.YogaConstants; +import com.facebook.yoga.YogaMeasureMode; import java.lang.reflect.Field; import java.util.LinkedList; import java.util.Map; @@ -91,6 +96,8 @@ public class ReactTextInputManager extends BaseViewManager -#include #include namespace facebook { diff --git a/ReactCommon/fabric/components/text/basetext/BaseTextShadowNode.cpp b/ReactCommon/fabric/components/text/basetext/BaseTextShadowNode.cpp index c7443aead7c..5034d201988 100644 --- a/ReactCommon/fabric/components/text/basetext/BaseTextShadowNode.cpp +++ b/ReactCommon/fabric/components/text/basetext/BaseTextShadowNode.cpp @@ -19,7 +19,7 @@ namespace react { AttributedString BaseTextShadowNode::getAttributedString( const TextAttributes &textAttributes, - const SharedShadowNode &parentNode) const { + const SharedShadowNode &parentNode) { auto attributedString = AttributedString{}; for (const auto &childNode : parentNode->getChildren()) { diff --git a/ReactCommon/fabric/components/text/basetext/BaseTextShadowNode.h b/ReactCommon/fabric/components/text/basetext/BaseTextShadowNode.h index 7da118643cd..2f1862751c2 100644 --- a/ReactCommon/fabric/components/text/basetext/BaseTextShadowNode.h +++ b/ReactCommon/fabric/components/text/basetext/BaseTextShadowNode.h @@ -21,10 +21,14 @@ class BaseTextShadowNode { public: /* * Returns a `AttributedString` which represents text content of the node. + * This is static so that both Paragraph (which subclasses BaseText) and + * TextInput (which does not) can use this. + * TODO T53299884: decide if this should be moved out and made a static + * function, or if TextInput should inherit from BaseTextShadowNode. */ - AttributedString getAttributedString( + static AttributedString getAttributedString( const TextAttributes &baseTextAttributes, - const SharedShadowNode &parentNode) const; + const SharedShadowNode &parentNode); }; } // namespace react diff --git a/ReactCommon/fabric/components/textinput/BUCK b/ReactCommon/fabric/components/textinput/BUCK new file mode 100644 index 00000000000..1494bfdc7d1 --- /dev/null +++ b/ReactCommon/fabric/components/textinput/BUCK @@ -0,0 +1,98 @@ +load("@fbsource//tools/build_defs/apple:flag_defs.bzl", "get_debug_preprocessor_flags") +load( + "//tools/build_defs/oss:rn_defs.bzl", + "ANDROID", + "APPLE", + "CXX", + "YOGA_CXX_TARGET", + "fb_xplat_cxx_test", + "get_apple_compiler_flags", + "get_apple_inspector_flags", + "react_native_xplat_target", + "rn_xplat_cxx_library", + "subdir_glob", +) + +APPLE_COMPILER_FLAGS = get_apple_compiler_flags() + +rn_xplat_cxx_library( + name = "androidtextinput", + srcs = glob( + ["**/*.cpp"], + exclude = glob(["tests/**/*.cpp"]), + ), + headers = glob( + ["**/*.h"], + exclude = glob(["tests/**/*.h"]), + ), + header_namespace = "", + exported_headers = subdir_glob( + [ + ("", "*.h"), + ("androidtextinput", "*.h"), + ], + prefix = "react/components/androidtextinput", + ), + compiler_flags = [ + "-fexceptions", + "-frtti", + "-std=c++14", + "-Wall", + ], + cxx_tests = [":tests"], + fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, + fbobjc_preprocessor_flags = get_debug_preprocessor_flags() + get_apple_inspector_flags(), + force_static = True, + platforms = (ANDROID, APPLE, CXX), + preprocessor_flags = [ + "-DLOG_TAG=\"ReactNative\"", + "-DWITH_FBSYSTRACE=1", + ], + visibility = ["PUBLIC"], + deps = [ + "fbsource//xplat/fbsystrace:fbsystrace", + "fbsource//xplat/folly:evicting_cache_map", + "fbsource//xplat/folly:headers_only", + "fbsource//xplat/folly:memory", + "fbsource//xplat/folly:molly", + "fbsource//xplat/third-party/glog:glog", + YOGA_CXX_TARGET, + react_native_xplat_target("utils:utils"), + react_native_xplat_target("fabric/attributedstring:attributedstring"), + react_native_xplat_target("fabric/core:core"), + react_native_xplat_target("fabric/debug:debug"), + react_native_xplat_target("fabric/graphics:graphics"), + react_native_xplat_target("fabric/textlayoutmanager:textlayoutmanager"), + react_native_xplat_target("fabric/components/text:text"), + react_native_xplat_target("fabric/components/view:view"), + react_native_xplat_target("fabric/components/image:image"), + react_native_xplat_target("fabric/uimanager:uimanager"), + react_native_xplat_target("fabric/imagemanager:imagemanager"), + ], +) + +fb_xplat_cxx_test( + name = "tests", + srcs = glob(["tests/**/*.cpp"]), + headers = glob(["tests/**/*.h"]), + compiler_flags = [ + "-fexceptions", + "-frtti", + "-std=c++14", + "-Wall", + ], + contacts = ["oncall+react_native@xmail.facebook.com"], + platforms = ( + # `Apple` and `Android` flavors are disabled because the module depends on `textlayoutmanager` which requires real an Emulator/Simulator to run. + # At the same time, the code of tests does not rely on the simulator capabilities and it would be wasteful to add `fbandroid_use_instrumentation_test = True`. + # (Beware of this option though.) + # ANDROID, + # APPLE, + CXX + ), + deps = [ + "fbsource//xplat/folly:molly", + "fbsource//xplat/third-party/gmock:gtest", + ":androidtextinput", + ], +) diff --git a/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputComponentDescriptor.h b/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputComponentDescriptor.h new file mode 100644 index 00000000000..d43edb6217b --- /dev/null +++ b/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputComponentDescriptor.h @@ -0,0 +1,46 @@ +/** + * 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. + */ + +#pragma once + +#include +#include "AndroidTextInputShadowNode.h" + +namespace facebook { +namespace react { + +/* + * Descriptor for component. + */ +class AndroidTextInputComponentDescriptor final + : public ConcreteComponentDescriptor { + public: + AndroidTextInputComponentDescriptor( + EventDispatcher::Shared eventDispatcher, + const ContextContainer::Shared &contextContainer) + : ConcreteComponentDescriptor( + eventDispatcher, + contextContainer) {} + + protected: + void adopt(UnsharedShadowNode shadowNode) const override { + assert(std::dynamic_pointer_cast(shadowNode)); + auto concreteShadowNode = + std::static_pointer_cast(shadowNode); + + concreteShadowNode->setContextContainer( + const_cast(getContextContainer().get())); + + concreteShadowNode->dirtyLayout(); + concreteShadowNode->enableMeasurement(); + + ConcreteComponentDescriptor::adopt(shadowNode); + } +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputEventEmitter.cpp b/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputEventEmitter.cpp new file mode 100644 index 00000000000..36959cf022e --- /dev/null +++ b/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputEventEmitter.cpp @@ -0,0 +1,179 @@ +/** + * 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 "AndroidTextInputEventEmitter.h" + +namespace facebook { +namespace react { + +void AndroidTextInputEventEmitter::onBlur( + AndroidTextInputOnBlurStruct event) const { + dispatchEvent("blur", [event = std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "target", event.target); + return payload; + }); +} +void AndroidTextInputEventEmitter::onFocus( + AndroidTextInputOnFocusStruct event) const { + dispatchEvent("focus", [event = std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "target", event.target); + return payload; + }); +} +void AndroidTextInputEventEmitter::onChange( + AndroidTextInputOnChangeStruct event) const { + dispatchEvent("change", [event = std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "target", event.target); + payload.setProperty(runtime, "eventCount", event.eventCount); + payload.setProperty(runtime, "text", event.text); + return payload; + }); +} +void AndroidTextInputEventEmitter::onChangeText( + AndroidTextInputOnChangeTextStruct event) const { + dispatchEvent( + "changeText", [event = std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "target", event.target); + payload.setProperty(runtime, "eventCount", event.eventCount); + payload.setProperty(runtime, "text", event.text); + return payload; + }); +} +void AndroidTextInputEventEmitter::onContentSizeChange( + AndroidTextInputOnContentSizeChangeStruct event) const { + dispatchEvent( + "contentSizeChange", [event = std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "target", event.target); + { + auto contentSize = jsi::Object(runtime); + contentSize.setProperty(runtime, "width", event.contentSize.width); + contentSize.setProperty(runtime, "height", event.contentSize.height); + + payload.setProperty(runtime, "contentSize", contentSize); + } + return payload; + }); +} +void AndroidTextInputEventEmitter::onTextInput( + AndroidTextInputOnTextInputStruct event) const { + dispatchEvent("textInput", [event = std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "target", event.target); + payload.setProperty(runtime, "text", event.text); + payload.setProperty(runtime, "previousText", event.previousText); + { + auto range = jsi::Object(runtime); + range.setProperty(runtime, "start", event.range.start); + range.setProperty(runtime, "end", event.range.end); + + payload.setProperty(runtime, "range", range); + } + return payload; + }); +} +void AndroidTextInputEventEmitter::onEndEditing( + AndroidTextInputOnEndEditingStruct event) const { + dispatchEvent( + "endEditing", [event = std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "target", event.target); + payload.setProperty(runtime, "text", event.text); + return payload; + }); +} +void AndroidTextInputEventEmitter::onSelectionChange( + AndroidTextInputOnSelectionChangeStruct event) const { + dispatchEvent( + "selectionChange", [event = std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "target", event.target); + { + auto selection = jsi::Object(runtime); + selection.setProperty(runtime, "start", event.selection.start); + selection.setProperty(runtime, "end", event.selection.end); + + payload.setProperty(runtime, "selection", selection); + } + return payload; + }); +} +void AndroidTextInputEventEmitter::onSubmitEditing( + AndroidTextInputOnSubmitEditingStruct event) const { + dispatchEvent( + "submitEditing", [event = std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "target", event.target); + payload.setProperty(runtime, "text", event.text); + return payload; + }); +} +void AndroidTextInputEventEmitter::onKeyPress( + AndroidTextInputOnKeyPressStruct event) const { + dispatchEvent("keyPress", [event = std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "target", event.target); + payload.setProperty(runtime, "key", event.key); + return payload; + }); +} +void AndroidTextInputEventEmitter::onScroll( + AndroidTextInputOnScrollStruct event) const { + dispatchEvent("scroll", [event = std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "target", event.target); + payload.setProperty( + runtime, "responderIgnoreScroll", event.responderIgnoreScroll); + { + auto contentInset = jsi::Object(runtime); + contentInset.setProperty(runtime, "top", event.contentInset.top); + contentInset.setProperty(runtime, "bottom", event.contentInset.bottom); + contentInset.setProperty(runtime, "left", event.contentInset.left); + contentInset.setProperty(runtime, "right", event.contentInset.right); + + payload.setProperty(runtime, "contentInset", contentInset); + } + { + auto contentOffset = jsi::Object(runtime); + contentOffset.setProperty(runtime, "x", event.contentOffset.x); + contentOffset.setProperty(runtime, "y", event.contentOffset.y); + + payload.setProperty(runtime, "contentOffset", contentOffset); + } + { + auto contentSize = jsi::Object(runtime); + contentSize.setProperty(runtime, "width", event.contentSize.width); + contentSize.setProperty(runtime, "height", event.contentSize.height); + + payload.setProperty(runtime, "contentSize", contentSize); + } + { + auto layoutMeasurement = jsi::Object(runtime); + layoutMeasurement.setProperty( + runtime, "width", event.layoutMeasurement.width); + layoutMeasurement.setProperty( + runtime, "height", event.layoutMeasurement.height); + + payload.setProperty(runtime, "layoutMeasurement", layoutMeasurement); + } + { + auto velocity = jsi::Object(runtime); + velocity.setProperty(runtime, "x", event.velocity.x); + velocity.setProperty(runtime, "y", event.velocity.y); + + payload.setProperty(runtime, "velocity", velocity); + } + return payload; + }); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputEventEmitter.h b/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputEventEmitter.h new file mode 100644 index 00000000000..c06df23c96d --- /dev/null +++ b/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputEventEmitter.h @@ -0,0 +1,148 @@ + +/** + * 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. + */ +#pragma once + +#include + +namespace facebook { +namespace react { + +struct AndroidTextInputOnBlurStruct { + int target; +}; + +struct AndroidTextInputOnFocusStruct { + int target; +}; + +struct AndroidTextInputOnChangeStruct { + int target; + int eventCount; + std::string text; +}; + +struct AndroidTextInputOnChangeTextStruct { + int target; + int eventCount; + std::string text; +}; + +struct AndroidTextInputOnContentSizeChangeContentSizeStruct { + double width; + double height; +}; + +struct AndroidTextInputOnContentSizeChangeStruct { + int target; + AndroidTextInputOnContentSizeChangeContentSizeStruct contentSize; +}; + +struct AndroidTextInputOnTextInputRangeStruct { + double start; + double end; +}; + +struct AndroidTextInputOnTextInputStruct { + int target; + std::string text; + std::string previousText; + AndroidTextInputOnTextInputRangeStruct range; +}; + +struct AndroidTextInputOnEndEditingStruct { + int target; + std::string text; +}; + +struct AndroidTextInputOnSelectionChangeSelectionStruct { + double start; + double end; +}; + +struct AndroidTextInputOnSelectionChangeStruct { + int target; + AndroidTextInputOnSelectionChangeSelectionStruct selection; +}; + +struct AndroidTextInputOnSubmitEditingStruct { + int target; + std::string text; +}; + +struct AndroidTextInputOnKeyPressStruct { + int target; + std::string key; +}; + +struct AndroidTextInputOnScrollContentInsetStruct { + double top; + double bottom; + double left; + double right; +}; + +struct AndroidTextInputOnScrollContentOffsetStruct { + double x; + double y; +}; + +struct AndroidTextInputOnScrollContentSizeStruct { + double width; + double height; +}; + +struct AndroidTextInputOnScrollLayoutMeasurementStruct { + double width; + double height; +}; + +struct AndroidTextInputOnScrollVelocityStruct { + double x; + double y; +}; + +struct AndroidTextInputOnScrollStruct { + int target; + bool responderIgnoreScroll; + AndroidTextInputOnScrollContentInsetStruct contentInset; + AndroidTextInputOnScrollContentOffsetStruct contentOffset; + AndroidTextInputOnScrollContentSizeStruct contentSize; + AndroidTextInputOnScrollLayoutMeasurementStruct layoutMeasurement; + AndroidTextInputOnScrollVelocityStruct velocity; +}; + +class AndroidTextInputEventEmitter : public ViewEventEmitter { + public: + using ViewEventEmitter::ViewEventEmitter; + + void onBlur(AndroidTextInputOnBlurStruct value) const; + + void onFocus(AndroidTextInputOnFocusStruct value) const; + + void onChange(AndroidTextInputOnChangeStruct value) const; + + void onChangeText(AndroidTextInputOnChangeTextStruct value) const; + + void onContentSizeChange( + AndroidTextInputOnContentSizeChangeStruct value) const; + + void onTextInput(AndroidTextInputOnTextInputStruct value) const; + + void onEndEditing(AndroidTextInputOnEndEditingStruct value) const; + + void onSelectionChange(AndroidTextInputOnSelectionChangeStruct value) const; + + void onSubmitEditing(AndroidTextInputOnSubmitEditingStruct value) const; + + void onKeyPress(AndroidTextInputOnKeyPressStruct value) const; + + void onScroll(AndroidTextInputOnScrollStruct value) const; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputProps.cpp b/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputProps.cpp new file mode 100644 index 00000000000..79abf42ad5e --- /dev/null +++ b/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputProps.cpp @@ -0,0 +1,383 @@ +/** + * 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 "AndroidTextInputProps.h" +#include +#include +#include + +namespace facebook { +namespace react { + +/** + * This was cribbed from BaseTextProps. Maybe we can unify someday. + * TODO: we should probably just move this to BaseTextProps / subclass it + * + * @param rawProps + * @param defaultTextAttributes + * @return + */ +static TextAttributes convertRawProp( + const RawProps &rawProps, + const TextAttributes defaultTextAttributes) { + auto textAttributes = TextAttributes{}; + + // Color + textAttributes.foregroundColor = + convertRawProp(rawProps, "color", defaultTextAttributes.foregroundColor); + // Todo T53300333: not found in AndroidTextInput (java) and/or TextInput + textAttributes.backgroundColor = convertRawProp( + rawProps, "backgroundColor", defaultTextAttributes.backgroundColor); + // Todo T53300333: not found in AndroidTextInput (java) and/or TextInput + textAttributes.opacity = + convertRawProp(rawProps, "opacity", defaultTextAttributes.opacity); + + // Font + textAttributes.fontFamily = + convertRawProp(rawProps, "fontFamily", defaultTextAttributes.fontFamily); + textAttributes.fontSize = + convertRawProp(rawProps, "fontSize", defaultTextAttributes.fontSize); + // Todo T53300333: not found in AndroidTextInput (java) and/or TextInput + // is this maxFontSizeMultiplier? + textAttributes.fontSizeMultiplier = convertRawProp( + rawProps, "fontSizeMultiplier", defaultTextAttributes.fontSizeMultiplier); + textAttributes.fontWeight = + convertRawProp(rawProps, "fontWeight", defaultTextAttributes.fontWeight); + textAttributes.fontStyle = + convertRawProp(rawProps, "fontStyle", defaultTextAttributes.fontStyle); + // Todo T53300333: not found in AndroidTextInput (java) and/or TextInput + textAttributes.fontVariant = convertRawProp( + rawProps, "fontVariant", defaultTextAttributes.fontVariant); + textAttributes.allowFontScaling = convertRawProp( + rawProps, "allowFontScaling", defaultTextAttributes.allowFontScaling); + textAttributes.letterSpacing = convertRawProp( + rawProps, "letterSpacing", defaultTextAttributes.letterSpacing); + + // Paragraph + textAttributes.lineHeight = + convertRawProp(rawProps, "lineHeight", defaultTextAttributes.lineHeight); + textAttributes.alignment = + convertRawProp(rawProps, "textAlign", defaultTextAttributes.alignment); + // Todo T53300333: not found in AndroidTextInput (java) and/or TextInput + textAttributes.baseWritingDirection = convertRawProp( + rawProps, + "baseWritingDirection", + defaultTextAttributes.baseWritingDirection); + + // Decoration + // Todo T53300333: not found in AndroidTextInput (java) and/or TextInput + textAttributes.textDecorationColor = convertRawProp( + rawProps, + "textDecorationColor", + defaultTextAttributes.textDecorationColor); + textAttributes.textDecorationLineType = convertRawProp( + rawProps, + "textDecorationLine", + defaultTextAttributes.textDecorationLineType); + // Todo T53300333: not found in AndroidTextInput (java) and/or TextInput + textAttributes.textDecorationLineStyle = convertRawProp( + rawProps, + "textDecorationLineStyle", + defaultTextAttributes.textDecorationLineStyle); + // Todo T53300333: not found in AndroidTextInput (java) and/or TextInput + textAttributes.textDecorationLinePattern = convertRawProp( + rawProps, + "textDecorationLinePattern", + defaultTextAttributes.textDecorationLinePattern); + + // Shadow + textAttributes.textShadowOffset = convertRawProp( + rawProps, "textShadowOffset", defaultTextAttributes.textShadowOffset); + textAttributes.textShadowRadius = convertRawProp( + rawProps, "textShadowRadius", defaultTextAttributes.textShadowRadius); + textAttributes.textShadowColor = convertRawProp( + rawProps, "textShadowColor", defaultTextAttributes.textShadowColor); + + // Special + // Todo T53300333: not found in AndroidTextInput (java) and/or TextInput + textAttributes.isHighlighted = convertRawProp( + rawProps, "isHighlighted", defaultTextAttributes.isHighlighted); + + return textAttributes; +} + +AndroidTextInputProps::AndroidTextInputProps( + const AndroidTextInputProps &sourceProps, + const RawProps &rawProps) + : ViewProps(sourceProps, rawProps), + + autoCompleteType(convertRawProp( + rawProps, + "autoCompleteType", + sourceProps.autoCompleteType, + {})), + returnKeyLabel(convertRawProp( + rawProps, + "returnKeyLabel", + sourceProps.returnKeyLabel, + {})), + numberOfLines(convertRawProp( + rawProps, + "numberOfLines", + sourceProps.numberOfLines, + {0})), + disableFullscreenUI(convertRawProp( + rawProps, + "disableFullscreenUI", + sourceProps.disableFullscreenUI, + {false})), + textBreakStrategy(convertRawProp( + rawProps, + "textBreakStrategy", + sourceProps.textBreakStrategy, + {})), + underlineColorAndroid(convertRawProp( + rawProps, + "underlineColorAndroid", + sourceProps.underlineColorAndroid, + {})), + inlineImageLeft(convertRawProp( + rawProps, + "inlineImageLeft", + sourceProps.inlineImageLeft, + {})), + inlineImagePadding(convertRawProp( + rawProps, + "inlineImagePadding", + sourceProps.inlineImagePadding, + {0})), + importantForAutofill(convertRawProp( + rawProps, + "importantForAutofill", + sourceProps.importantForAutofill, + {})), + showSoftInputOnFocus(convertRawProp( + rawProps, + "showSoftInputOnFocus", + sourceProps.showSoftInputOnFocus, + {false})), + autoCapitalize(convertRawProp( + rawProps, + "autoCapitalize", + sourceProps.autoCapitalize, + {})), + autoCorrect(convertRawProp( + rawProps, + "autoCorrect", + sourceProps.autoCorrect, + {false})), + autoFocus(convertRawProp( + rawProps, + "autoFocus", + sourceProps.autoFocus, + {false})), + allowFontScaling(convertRawProp( + rawProps, + "allowFontScaling", + sourceProps.allowFontScaling, + {false})), + maxFontSizeMultiplier(convertRawProp( + rawProps, + "maxFontSizeMultiplier", + sourceProps.maxFontSizeMultiplier, + {0.0})), + editable( + convertRawProp(rawProps, "editable", sourceProps.editable, {false})), + keyboardType(convertRawProp( + rawProps, + "keyboardType", + sourceProps.keyboardType, + {})), + returnKeyType(convertRawProp( + rawProps, + "returnKeyType", + sourceProps.returnKeyType, + {})), + maxLength( + convertRawProp(rawProps, "maxLength", sourceProps.maxLength, {0})), + multiline(convertRawProp( + rawProps, + "multiline", + sourceProps.multiline, + {false})), + placeholder( + convertRawProp(rawProps, "placeholder", sourceProps.placeholder, {})), + placeholderTextColor(convertRawProp( + rawProps, + "placeholderTextColor", + sourceProps.placeholderTextColor, + {})), + secureTextEntry(convertRawProp( + rawProps, + "secureTextEntry", + sourceProps.secureTextEntry, + {false})), + selectionColor(convertRawProp( + rawProps, + "selectionColor", + sourceProps.selectionColor, + {})), + selection( + convertRawProp(rawProps, "selection", sourceProps.selection, {})), + value(convertRawProp(rawProps, "value", sourceProps.value, {})), + defaultValue(convertRawProp( + rawProps, + "defaultValue", + sourceProps.defaultValue, + {})), + selectTextOnFocus(convertRawProp( + rawProps, + "selectTextOnFocus", + sourceProps.selectTextOnFocus, + {false})), + blurOnSubmit(convertRawProp( + rawProps, + "blurOnSubmit", + sourceProps.blurOnSubmit, + {false})), + caretHidden(convertRawProp( + rawProps, + "caretHidden", + sourceProps.caretHidden, + {false})), + contextMenuHidden(convertRawProp( + rawProps, + "contextMenuHidden", + sourceProps.contextMenuHidden, + {false})), + textShadowColor(convertRawProp( + rawProps, + "textShadowColor", + sourceProps.textShadowColor, + {})), + textShadowRadius(convertRawProp( + rawProps, + "textShadowRadius", + sourceProps.textShadowRadius, + {0.0})), + textDecorationLine(convertRawProp( + rawProps, + "textDecorationLine", + sourceProps.textDecorationLine, + {})), + fontStyle( + convertRawProp(rawProps, "fontStyle", sourceProps.fontStyle, {})), + textShadowOffset(convertRawProp( + rawProps, + "textShadowOffset", + sourceProps.textShadowOffset, + {})), + lineHeight(convertRawProp( + rawProps, + "lineHeight", + sourceProps.lineHeight, + {0.0})), + textTransform(convertRawProp( + rawProps, + "textTransform", + sourceProps.textTransform, + {})), + color(convertRawProp(rawProps, "color", sourceProps.color, {0})), + letterSpacing(convertRawProp( + rawProps, + "letterSpacing", + sourceProps.letterSpacing, + {0.0})), + fontSize( + convertRawProp(rawProps, "fontSize", sourceProps.fontSize, {0.0})), + textAlign( + convertRawProp(rawProps, "textAlign", sourceProps.textAlign, {})), + includeFontPadding(convertRawProp( + rawProps, + "includeFontPadding", + sourceProps.includeFontPadding, + {false})), + fontWeight( + convertRawProp(rawProps, "fontWeight", sourceProps.fontWeight, {})), + fontFamily( + convertRawProp(rawProps, "fontFamily", sourceProps.fontFamily, {})), + textAlignVertical(convertRawProp( + rawProps, + "textAlignVertical", + sourceProps.textAlignVertical, + {})), + cursorColor( + convertRawProp(rawProps, "cursorColor", sourceProps.cursorColor, {})), + mostRecentEventCount(convertRawProp( + rawProps, + "mostRecentEventCount", + sourceProps.mostRecentEventCount, + {0})), + text(convertRawProp(rawProps, "text", sourceProps.text, {})), + textAttributes(convertRawProp(rawProps, sourceProps.textAttributes)) {} + +// TODO T53300085: support this in codegen; this was hand-written +folly::dynamic AndroidTextInputProps::getDynamic() const { + folly::dynamic props = folly::dynamic::object(); + props["autoCompleteType"] = autoCompleteType; + props["returnKeyLabel"] = returnKeyLabel; + props["numberOfLines"] = numberOfLines; + props["disableFullscreenUI"] = disableFullscreenUI; + props["textBreakStrategy"] = textBreakStrategy; + props["underlineColorAndroid"] = toDynamic(underlineColorAndroid); + props["inlineImageLeft"] = inlineImageLeft; + props["inlineImagePadding"] = inlineImagePadding; + props["importantForAutofill"] = importantForAutofill; + props["showSoftInputOnFocus"] = showSoftInputOnFocus; + props["autoCapitalize"] = autoCapitalize; + props["autoCorrect"] = autoCorrect; + props["autoFocus"] = autoFocus; + props["allowFontScaling"] = allowFontScaling; + props["maxFontSizeMultiplier"] = maxFontSizeMultiplier; + props["editable"] = editable; + props["keyboardType"] = keyboardType; + props["returnKeyType"] = returnKeyType; + props["maxLength"] = maxLength; + props["multiline"] = multiline; + props["placeholder"] = placeholder; + props["placeholderTextColor"] = toDynamic(placeholderTextColor); + props["secureTextEntry"] = secureTextEntry; + props["selectionColor"] = toDynamic(selectionColor); + props["selection"] = toDynamic(selection); + props["value"] = value; + props["defaultValue"] = defaultValue; + props["selectTextOnFocus"] = selectTextOnFocus; + props["blurOnSubmit"] = blurOnSubmit; + props["caretHidden"] = caretHidden; + props["contextMenuHidden"] = contextMenuHidden; + props["textShadowColor"] = toDynamic(textShadowColor); + props["textShadowRadius"] = textShadowRadius; + props["textDecorationLine"] = textDecorationLine; + props["fontStyle"] = fontStyle; + props["textShadowOffset"] = toDynamic(textShadowOffset); + props["lineHeight"] = lineHeight; + props["textTransform"] = textTransform; + props["color"] = color; + props["letterSpacing"] = letterSpacing; + props["fontSize"] = fontSize; + props["textAlign"] = textAlign; + props["includeFontPadding"] = includeFontPadding; + props["fontWeight"] = fontWeight; + props["fontFamily"] = fontFamily; + props["textAlignVertical"] = textAlignVertical; + props["cursorColor"] = toDynamic(cursorColor); + props["mostRecentEventCount"] = mostRecentEventCount; + props["text"] = text; + return props; +} + +#pragma mark - DebugStringConvertible + +#if RN_DEBUG_STRING_CONVERTIBLE +// TODO: codegen these +SharedDebugStringConvertibleList TextProps::getDebugProps() const { + return {}; +} +#endif + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputProps.h b/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputProps.h new file mode 100644 index 00000000000..75d45eda8f6 --- /dev/null +++ b/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputProps.h @@ -0,0 +1,163 @@ +/** + * 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. + */ + +#pragma once + +// #include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +struct AndroidTextInputSelectionStruct { + int start; + int end; +}; + +static inline void fromRawValue( + const RawValue &value, + AndroidTextInputSelectionStruct &result) { + auto map = (better::map)value; + + auto start = map.find("start"); + if (start != map.end()) { + fromRawValue(start->second, result.start); + } + auto end = map.find("end"); + if (end != map.end()) { + fromRawValue(end->second, result.end); + } +} + +static inline std::string toString( + const AndroidTextInputSelectionStruct &value) { + return "[Object AndroidTextInputSelectionStruct]"; +} + +struct AndroidTextInputTextShadowOffsetStruct { + double width; + double height; +}; + +static inline void fromRawValue( + const RawValue &value, + AndroidTextInputTextShadowOffsetStruct &result) { + auto map = (better::map)value; + + auto width = map.find("width"); + if (width != map.end()) { + fromRawValue(width->second, result.width); + } + auto height = map.find("height"); + if (height != map.end()) { + fromRawValue(height->second, result.height); + } +} + +static inline std::string toString( + const AndroidTextInputTextShadowOffsetStruct &value) { + return "[Object AndroidTextInputTextShadowOffsetStruct]"; +} + +#ifdef ANDROID +inline folly::dynamic toDynamic( + const AndroidTextInputTextShadowOffsetStruct &value) { + folly::dynamic dynamicValue = folly::dynamic::object(); + dynamicValue["width"] = value.width; + dynamicValue["height"] = value.height; + return dynamicValue; +} + +inline folly::dynamic toDynamic(const AndroidTextInputSelectionStruct &value) { + folly::dynamic dynamicValue = folly::dynamic::object(); + dynamicValue["start"] = value.start; + dynamicValue["end"] = value.end; + return dynamicValue; +} +#endif + +class AndroidTextInputProps final : public ViewProps { + public: + AndroidTextInputProps() = default; + AndroidTextInputProps( + const AndroidTextInputProps &sourceProps, + const RawProps &rawProps); + + folly::dynamic getDynamic() const; + +#pragma mark - Props + + const std::string autoCompleteType{}; + const std::string returnKeyLabel{}; + const int numberOfLines{0}; + const bool disableFullscreenUI{false}; + const std::string textBreakStrategy{}; + const SharedColor underlineColorAndroid{}; + const std::string inlineImageLeft{}; + const int inlineImagePadding{0}; + const std::string importantForAutofill{}; + const bool showSoftInputOnFocus{false}; + const std::string autoCapitalize{}; + const bool autoCorrect{false}; + const bool autoFocus{false}; + const bool allowFontScaling{false}; + const Float maxFontSizeMultiplier{0.0}; + const bool editable{false}; + const std::string keyboardType{}; + const std::string returnKeyType{}; + const int maxLength{0}; + const bool multiline{false}; + const std::string placeholder{}; + const SharedColor placeholderTextColor{}; + const bool secureTextEntry{false}; + const SharedColor selectionColor{}; + const AndroidTextInputSelectionStruct selection{}; + const std::string value{}; + const std::string defaultValue{}; + const bool selectTextOnFocus{false}; + const bool blurOnSubmit{false}; + const bool caretHidden{false}; + const bool contextMenuHidden{false}; + const SharedColor textShadowColor{}; + const Float textShadowRadius{0.0}; + const std::string textDecorationLine{}; + const std::string fontStyle{}; + const AndroidTextInputTextShadowOffsetStruct textShadowOffset{}; + const Float lineHeight{0.0}; + const std::string textTransform{}; + const int color{0}; + const Float letterSpacing{0.0}; + const Float fontSize{0.0}; + const std::string textAlign{}; + const bool includeFontPadding{false}; + const std::string fontWeight{}; + const std::string fontFamily{}; + const std::string textAlignVertical{}; + const SharedColor cursorColor{}; + const int mostRecentEventCount{0}; + const std::string text{}; + + /** + * TextAttributes: see all BaseText. These attributes are not set + * directly; see convertRawProps. + */ + const TextAttributes textAttributes{}; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.cpp b/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.cpp new file mode 100644 index 00000000000..e2ed08f4917 --- /dev/null +++ b/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.cpp @@ -0,0 +1,102 @@ +/** + * 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 +#include +#include +#include +#include +#include +#include + +#include + +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 { + auto textAttributes = TextAttributes::defaultTextAttributes(); + textAttributes.apply(getProps()->textAttributes); + + // Use BaseTextShadowNode to get attributed string from children + return BaseTextShadowNode::getAttributedString( + textAttributes, shared_from_this()); +} + +#pragma mark - LayoutableShadowNode + +Size AndroidTextInputShadowNode::measure( + LayoutConstraints layoutConstraints) const { + AttributedString attributedString = getAttributedString(); + + if (attributedString.isEmpty()) { + return {0, 0}; + } + + const jni::global_ref &fabricUIManager = + contextContainer_->at>("FabricUIManager"); + + static auto measure = + jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") + ->getMethod("measure"); + + auto minimumSize = layoutConstraints.minimumSize; + auto maximumSize = layoutConstraints.maximumSize; + + local_ref componentName = + make_jstring(AndroidTextInputComponentName); + + local_ref attributedStringRNM = + ReadableNativeMap::newObjectCxxArgs(toDynamic(attributedString)); + local_ref attributedStringRM = make_local( + reinterpret_cast(attributedStringRNM.get())); + + local_ref nativeLocalProps = make_local( + ReadableNativeMap::createWithContents(getProps()->getDynamic())); + local_ref props = make_local( + reinterpret_cast(nativeLocalProps.get())); + + // For AndroidTextInput purposes: + // localData == textAttributes + return yogaMeassureToSize(measure( + fabricUIManager, + componentName.get(), + attributedStringRM.get(), + props.get(), + nullptr, + minimumSize.width, + maximumSize.width, + minimumSize.height, + maximumSize.height)); +} + +void AndroidTextInputShadowNode::layout(LayoutContext layoutContext) { + ConcreteViewShadowNode::layout(layoutContext); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.h b/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.h new file mode 100644 index 00000000000..995110935b6 --- /dev/null +++ b/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.h @@ -0,0 +1,50 @@ +/** + * 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. + */ + +#pragma once + +#include "AndroidTextInputEventEmitter.h" +#include "AndroidTextInputProps.h" + +#include +#include + +#include + +namespace facebook { +namespace react { + +extern const char AndroidTextInputComponentName[]; + +/* + * `ShadowNode` for component. + */ +class AndroidTextInputShadowNode : public ConcreteViewShadowNode< + AndroidTextInputComponentName, + AndroidTextInputProps, + AndroidTextInputEventEmitter> { + public: + using ConcreteViewShadowNode::ConcreteViewShadowNode; + + void setContextContainer(ContextContainer *contextContainer); + + /* + * Returns a `AttributedString` which represents text content of the node. + */ + AttributedString getAttributedString() const; + +#pragma mark - LayoutableShadowNode + + Size measure(LayoutConstraints layoutConstraints) const override; + void layout(LayoutContext layoutContext) override; + + private: + ContextContainer *contextContainer_{}; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.cpp b/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.cpp index 62f9596ffc9..eabaaedd7d0 100644 --- a/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.cpp +++ b/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.cpp @@ -18,6 +18,8 @@ #include #include +#include + namespace facebook { namespace react {