feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,36 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB rrc_image_SRC CONFIGURE_DEPENDS *.cpp)
add_library(rrc_image SHARED ${rrc_image_SRC})
target_include_directories(rrc_image PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(rrc_image
glog
folly_runtime
glog_init
jsi
react_debug
react_utils
react_render_core
react_render_debug
react_render_graphics
react_render_imagemanager
react_render_mapbuffer
rrc_view
yoga
)

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/image/ImageShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/imagemanager/ImageManager.h>
#include <react/utils/ContextContainer.h>
namespace facebook::react {
/*
* Descriptor for <Image> component.
*/
class ImageComponentDescriptor final
: public ConcreteComponentDescriptor<ImageShadowNode> {
public:
ImageComponentDescriptor(const ComponentDescriptorParameters& parameters)
: ConcreteComponentDescriptor(parameters),
imageManager_(std::make_shared<ImageManager>(contextContainer_)){};
void adopt(ShadowNode& shadowNode) const override {
ConcreteComponentDescriptor::adopt(shadowNode);
auto& imageShadowNode = static_cast<ImageShadowNode&>(shadowNode);
// `ImageShadowNode` uses `ImageManager` to initiate image loading and
// communicate the loading state and results to mounting layer.
imageShadowNode.setImageManager(imageManager_);
}
private:
const SharedImageManager imageManager_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ImageEventEmitter.h"
namespace facebook::react {
void ImageEventEmitter::onLoadStart() const {
dispatchEvent("loadStart");
}
void ImageEventEmitter::onLoad() const {
dispatchEvent("load");
}
void ImageEventEmitter::onLoadEnd() const {
dispatchEvent("loadEnd");
}
void ImageEventEmitter::onProgress(double progress) const {
dispatchEvent("progress", [=](jsi::Runtime& runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "progress", progress);
return payload;
});
}
void ImageEventEmitter::onError() const {
dispatchEvent("error");
}
void ImageEventEmitter::onPartialLoad() const {
dispatchEvent("partialLoad");
}
} // namespace facebook::react

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/view/ViewEventEmitter.h>
namespace facebook::react {
class ImageEventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
void onLoadStart() const;
void onLoad() const;
void onLoadEnd() const;
void onProgress(double) const;
void onError() const;
void onPartialLoad() const;
};
} // namespace facebook::react

View File

@@ -0,0 +1,103 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <react/renderer/components/image/ImageProps.h>
#include <react/renderer/components/image/conversions.h>
#include <react/renderer/core/propsConversions.h>
#include <react/utils/CoreFeatures.h>
namespace facebook::react {
ImageProps::ImageProps(
const PropsParserContext& context,
const ImageProps& sourceProps,
const RawProps& rawProps)
: ViewProps(context, sourceProps, rawProps),
sources(
CoreFeatures::enablePropIteratorSetter ? sourceProps.sources
: convertRawProp(
context,
rawProps,
"source",
sourceProps.sources,
{})),
defaultSources(
CoreFeatures::enablePropIteratorSetter
? sourceProps.defaultSources
: convertRawProp(
context,
rawProps,
"defaultSource",
sourceProps.defaultSources,
{})),
resizeMode(
CoreFeatures::enablePropIteratorSetter
? sourceProps.resizeMode
: convertRawProp(
context,
rawProps,
"resizeMode",
sourceProps.resizeMode,
ImageResizeMode::Stretch)),
blurRadius(
CoreFeatures::enablePropIteratorSetter ? sourceProps.blurRadius
: convertRawProp(
context,
rawProps,
"blurRadius",
sourceProps.blurRadius,
{})),
capInsets(
CoreFeatures::enablePropIteratorSetter ? sourceProps.capInsets
: convertRawProp(
context,
rawProps,
"capInsets",
sourceProps.capInsets,
{})),
tintColor(
CoreFeatures::enablePropIteratorSetter ? sourceProps.tintColor
: convertRawProp(
context,
rawProps,
"tintColor",
sourceProps.tintColor,
{})),
internal_analyticTag(
CoreFeatures::enablePropIteratorSetter
? sourceProps.internal_analyticTag
: convertRawProp(
context,
rawProps,
"internal_analyticTag",
sourceProps.internal_analyticTag,
{})) {}
void ImageProps::setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
const char* propName,
const RawValue& value) {
// All Props structs setProp methods must always, unconditionally,
// call all super::setProp methods, since multiple structs may
// reuse the same values.
ViewProps::setProp(context, hash, propName, value);
static auto defaults = ImageProps{};
switch (hash) {
RAW_SET_PROP_SWITCH_CASE(sources, "source");
RAW_SET_PROP_SWITCH_CASE(defaultSources, "defaultSource");
RAW_SET_PROP_SWITCH_CASE_BASIC(resizeMode);
RAW_SET_PROP_SWITCH_CASE_BASIC(blurRadius);
RAW_SET_PROP_SWITCH_CASE_BASIC(capInsets);
RAW_SET_PROP_SWITCH_CASE_BASIC(tintColor);
RAW_SET_PROP_SWITCH_CASE_BASIC(internal_analyticTag);
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/graphics/Color.h>
#include <react/renderer/imagemanager/primitives.h>
namespace facebook::react {
// TODO (T28334063): Consider for codegen.
class ImageProps final : public ViewProps {
public:
ImageProps() = default;
ImageProps(
const PropsParserContext& context,
const ImageProps& sourceProps,
const RawProps& rawProps);
void setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
const char* propName,
const RawValue& value);
#pragma mark - Props
ImageSources sources{};
ImageSources defaultSources{};
ImageResizeMode resizeMode{ImageResizeMode::Stretch};
Float blurRadius{};
EdgeInsets capInsets{};
SharedColor tintColor{};
std::string internal_analyticTag{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,96 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <cstdlib>
#include <limits>
#include <react/renderer/components/image/ImageShadowNode.h>
#include <react/renderer/core/LayoutContext.h>
#include "ImageState.h"
namespace facebook::react {
const char ImageComponentName[] = "Image";
void ImageShadowNode::setImageManager(const SharedImageManager& imageManager) {
ensureUnsealed();
imageManager_ = imageManager;
}
void ImageShadowNode::updateStateIfNeeded() {
ensureUnsealed();
auto imageSource = getImageSource();
const auto& currentState = getStateData();
bool hasSameRadius =
getConcreteProps().blurRadius == currentState.getBlurRadius();
bool hasSameImageSource = currentState.getImageSource() == imageSource;
if (hasSameImageSource && hasSameRadius) {
return;
}
auto state = ImageState{
imageSource,
imageManager_->requestImage(imageSource, getSurfaceId()),
getConcreteProps().blurRadius};
setStateData(std::move(state));
}
ImageSource ImageShadowNode::getImageSource() const {
auto sources = getConcreteProps().sources;
if (sources.empty()) {
return {
/* .type = */ ImageSource::Type::Invalid,
};
}
auto layoutMetrics = getLayoutMetrics();
auto size = layoutMetrics.getContentFrame().size;
auto scale = layoutMetrics.pointScaleFactor;
if (sources.size() == 1) {
auto source = sources[0];
source.size = size;
source.scale = scale;
return source;
}
auto targetImageArea = size.width * size.height * scale * scale;
auto bestFit = std::numeric_limits<Float>::infinity();
auto bestSource = ImageSource{};
for (const auto& source : sources) {
auto sourceSize = source.size;
auto sourceScale = source.scale == 0 ? scale : source.scale;
auto sourceArea =
sourceSize.width * sourceSize.height * sourceScale * sourceScale;
auto fit = std::abs(1 - (sourceArea / targetImageArea));
if (fit < bestFit) {
bestFit = fit;
bestSource = source;
}
}
bestSource.size = size;
bestSource.scale = scale;
return bestSource;
}
#pragma mark - LayoutableShadowNode
void ImageShadowNode::layout(LayoutContext layoutContext) {
updateStateIfNeeded();
ConcreteViewShadowNode::layout(layoutContext);
}
} // namespace facebook::react

View File

@@ -0,0 +1,64 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/image/ImageEventEmitter.h>
#include <react/renderer/components/image/ImageProps.h>
#include <react/renderer/components/image/ImageState.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/core/ShadowNodeFamily.h>
#include <react/renderer/imagemanager/ImageManager.h>
#include <react/renderer/imagemanager/primitives.h>
namespace facebook::react {
extern const char ImageComponentName[];
/*
* `ShadowNode` for <Image> component.
*/
class ImageShadowNode final : public ConcreteViewShadowNode<
ImageComponentName,
ImageProps,
ImageEventEmitter,
ImageState> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
static ShadowNodeTraits BaseTraits() {
auto traits = ConcreteViewShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::LeafYogaNode);
return traits;
}
/*
* Associates a shared `ImageManager` with the node.
*/
void setImageManager(const SharedImageManager& imageManager);
static ImageState initialStateData(
const Props::Shared& props,
const ShadowNodeFamily::Shared& /*family*/,
const ComponentDescriptor& componentDescriptor) {
auto imageSource = ImageSource{ImageSource::Type::Invalid};
return {imageSource, {imageSource, nullptr, {}}, 0};
}
#pragma mark - LayoutableShadowNode
void layout(LayoutContext layoutContext) override;
private:
ImageSource getImageSource() const;
SharedImageManager imageManager_;
void updateStateIfNeeded();
};
} // namespace facebook::react

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ImageState.h"
namespace facebook::react {
ImageSource ImageState::getImageSource() const {
return imageSource_;
}
const ImageRequest& ImageState::getImageRequest() const {
return *imageRequest_;
}
Float ImageState::getBlurRadius() const {
return blurRadius_;
}
} // namespace facebook::react

View File

@@ -0,0 +1,68 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/imagemanager/ImageRequest.h>
#include <react/renderer/imagemanager/primitives.h>
#ifdef ANDROID
#include <react/renderer/mapbuffer/MapBuffer.h>
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
#endif
namespace facebook::react {
/*
* State for <Image> component.
*/
class ImageState final {
public:
ImageState(
const ImageSource& imageSource,
ImageRequest imageRequest,
const Float blurRadius)
: imageSource_(imageSource),
imageRequest_(std::make_shared<ImageRequest>(std::move(imageRequest))),
blurRadius_(blurRadius){};
/*
* Returns stored ImageSource object.
*/
ImageSource getImageSource() const;
/*
* Exposes for reading stored `ImageRequest` object.
* `ImageRequest` object cannot be copied or moved from `ImageLocalData`.
*/
const ImageRequest& getImageRequest() const;
Float getBlurRadius() const;
#ifdef ANDROID
ImageState(const ImageState& previousState, folly::dynamic data)
: blurRadius_{0} {};
/*
* Empty implementation for Android because it doesn't use this class.
*/
folly::dynamic getDynamic() const {
return {};
};
MapBuffer getMapBuffer() const {
return MapBufferBuilder::EMPTY();
};
#endif
private:
ImageSource imageSource_;
std::shared_ptr<ImageRequest> imageRequest_;
const Float blurRadius_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,142 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <unordered_map>
#include <folly/dynamic.h>
#include <glog/logging.h>
#include <react/debug/react_native_expect.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/renderer/imagemanager/primitives.h>
namespace facebook::react {
inline void fromRawValue(
const PropsParserContext& context,
const RawValue& value,
ImageSource& result) {
if (value.hasType<std::string>()) {
result = {
/* .type = */ ImageSource::Type::Remote,
/* .uri = */ (std::string)value,
};
return;
}
if (value.hasType<std::unordered_map<std::string, RawValue>>()) {
auto items = (std::unordered_map<std::string, RawValue>)value;
result = {};
result.type = ImageSource::Type::Remote;
if (items.find("__packager_asset") != items.end()) {
result.type = ImageSource::Type::Local;
}
if (items.find("width") != items.end() &&
items.find("height") != items.end() &&
// The following checks have to be removed after codegen is shipped.
// See T45151459.
items.at("width").hasType<Float>() &&
items.at("height").hasType<Float>()) {
result.size = {(Float)items.at("width"), (Float)items.at("height")};
}
if (items.find("scale") != items.end() &&
// The following checks have to be removed after codegen is shipped.
// See T45151459.
items.at("scale").hasType<Float>()) {
result.scale = (Float)items.at("scale");
} else {
result.scale = items.find("deprecated") != items.end() ? 0.0f : 1.0f;
}
if (items.find("url") != items.end() &&
// The following should be removed after codegen is shipped.
// See T45151459.
items.at("url").hasType<std::string>()) {
result.uri = (std::string)items.at("url");
}
if (items.find("uri") != items.end() &&
// The following should be removed after codegen is shipped.
// See T45151459.
items.at("uri").hasType<std::string>()) {
result.uri = (std::string)items.at("uri");
}
if (items.find("bundle") != items.end() &&
// The following should be removed after codegen is shipped.
// See T45151459.
items.at("bundle").hasType<std::string>()) {
result.bundle = (std::string)items.at("bundle");
result.type = ImageSource::Type::Local;
}
return;
}
// The following should be removed after codegen is shipped.
// See T45151459.
result = {};
result.type = ImageSource::Type::Invalid;
}
inline std::string toString(const ImageSource& value) {
return "{uri: " + value.uri + "}";
}
inline void fromRawValue(
const PropsParserContext& context,
const RawValue& value,
ImageResizeMode& result) {
react_native_expect(value.hasType<std::string>());
if (!value.hasType<std::string>()) {
LOG(ERROR) << "Unsupported ImageResizeMode type";
// "cover" is default in non-Fabric web and iOS
result = ImageResizeMode::Cover;
return;
}
auto stringValue = (std::string)value;
if (stringValue == "cover") {
result = ImageResizeMode::Cover;
} else if (stringValue == "contain") {
result = ImageResizeMode::Contain;
} else if (stringValue == "stretch") {
result = ImageResizeMode::Stretch;
} else if (stringValue == "center") {
result = ImageResizeMode::Center;
} else if (stringValue == "repeat") {
result = ImageResizeMode::Repeat;
} else {
LOG(ERROR) << "Unsupported ImageResizeMode value: " << stringValue;
react_native_expect(false);
// "cover" is default in non-Fabric web and iOS
result = ImageResizeMode::Cover;
}
}
inline std::string toString(const ImageResizeMode& value) {
switch (value) {
case ImageResizeMode::Cover:
return "cover";
case ImageResizeMode::Contain:
return "contain";
case ImageResizeMode::Stretch:
return "stretch";
case ImageResizeMode::Center:
return "center";
case ImageResizeMode::Repeat:
return "repeat";
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <memory>
#include <gtest/gtest.h>
TEST(ImageTest, testSomething) {
// TODO
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/debug/react_native_assert.h>
#include <react/renderer/components/inputaccessory/InputAccessoryShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
/*
* Descriptor for <InputAccessoryView> component.
*/
class InputAccessoryComponentDescriptor final
: public ConcreteComponentDescriptor<InputAccessoryShadowNode> {
public:
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
void adopt(ShadowNode& shadowNode) const override {
auto& layoutableShadowNode =
static_cast<YogaLayoutableShadowNode&>(shadowNode);
auto& stateData =
static_cast<const InputAccessoryShadowNode::ConcreteState&>(
*shadowNode.getState())
.getData();
layoutableShadowNode.setSize(
Size{stateData.viewportSize.width, stateData.viewportSize.height});
layoutableShadowNode.setPositionType(YGPositionTypeAbsolute);
ConcreteComponentDescriptor::adopt(shadowNode);
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "InputAccessoryShadowNode.h"
namespace facebook::react {
extern const char InputAccessoryComponentName[] = "InputAccessoryView";
} // namespace facebook::react

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/inputaccessory/InputAccessoryState.h>
#include <react/renderer/components/rncore/EventEmitters.h>
#include <react/renderer/components/rncore/Props.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
namespace facebook::react {
extern const char InputAccessoryComponentName[];
/*
* `ShadowNode` for <InputAccessory> component.
*/
class InputAccessoryShadowNode final : public ConcreteViewShadowNode<
InputAccessoryComponentName,
InputAccessoryProps,
InputAccessoryEventEmitter,
InputAccessoryState> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
static ShadowNodeTraits BaseTraits() {
auto traits = ConcreteViewShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::RootNodeKind);
return traits;
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/core/graphicsConversions.h>
#include <react/renderer/graphics/Float.h>
namespace facebook::react {
/*
* State for <InputAccessoryView> component.
*/
class InputAccessoryState final {
public:
InputAccessoryState(){};
InputAccessoryState(Size viewportSize_) : viewportSize(viewportSize_){};
const Size viewportSize{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,30 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB rrc_legacyviewmanagerinterop_SRC CONFIGURE_DEPENDS *.cpp)
add_library(rrc_legacyviewmanagerinterop SHARED ${rrc_legacyviewmanagerinterop_SRC})
target_include_directories(rrc_legacyviewmanagerinterop PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(rrc_legacyviewmanagerinterop
glog
glog_init
folly_runtime
jsi
react_render_core
rrc_view
yoga
)

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
class LegacyViewManagerInteropComponentDescriptor final
: public ConcreteComponentDescriptor<LegacyViewManagerInteropShadowNode> {
public:
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
LegacyViewManagerInteropComponentDescriptor(
const ComponentDescriptorParameters& parameters);
/*
* Returns `name` and `handle` based on a `flavor`, not on static data from
* `LegacyViewManagerInteropShadowNode`.
*/
ComponentHandle getComponentHandle() const override;
ComponentName getComponentName() const override;
protected:
void adopt(ShadowNode& shadowNode) const override;
private:
const std::shared_ptr<void> _coordinator;
};
} // namespace facebook::react

View File

@@ -0,0 +1,165 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "LegacyViewManagerInteropComponentDescriptor.h"
#include <React/RCTBridge.h>
#include <React/RCTBridgeModuleDecorator.h>
#include <React/RCTBridgeProxy.h>
#include <React/RCTComponentData.h>
#include <React/RCTEventDispatcher.h>
#include <React/RCTModuleData.h>
#include <react/utils/ContextContainer.h>
#include <react/utils/ManagedObjectWrapper.h>
#include "LegacyViewManagerInteropState.h"
#include "RCTLegacyViewManagerInteropCoordinator.h"
namespace facebook::react {
static std::string moduleNameFromComponentNameNoRCTPrefix(const std::string &componentName)
{
// TODO: remove FB specific code (T56174424)
if (componentName == "StickerInputView") {
return "FBStickerInputViewManager";
}
if (componentName == "FDSTooltipView") {
return "FBReactFDSTooltipViewManager";
}
std::string fbPrefix("FB");
if (std::mismatch(fbPrefix.begin(), fbPrefix.end(), componentName.begin()).first == fbPrefix.end()) {
// If `moduleName` has "FB" prefix.
return componentName + "Manager";
}
std::string artPrefix("ART");
if (std::mismatch(artPrefix.begin(), artPrefix.end(), componentName.begin()).first == artPrefix.end()) {
return componentName + "Manager";
}
std::string rnPrefix("RN");
if (std::mismatch(rnPrefix.begin(), rnPrefix.end(), componentName.begin()).first == rnPrefix.end()) {
return componentName + "Manager";
}
return componentName + "Manager";
}
inline NSString *RCTNSStringFromString(const std::string &string)
{
return [NSString stringWithUTF8String:string.c_str()];
}
static Class getViewManagerFromComponentName(const std::string &componentName)
{
auto viewManagerName = moduleNameFromComponentNameNoRCTPrefix(componentName);
// 1. Try to get the manager with the RCT prefix.
auto rctViewManagerName = "RCT" + viewManagerName;
Class viewManagerClass = NSClassFromString(RCTNSStringFromString(rctViewManagerName));
if (viewManagerClass) {
return viewManagerClass;
}
// 2. Try to get the manager without the prefix.
viewManagerClass = NSClassFromString(RCTNSStringFromString(viewManagerName));
if (viewManagerClass) {
return viewManagerClass;
}
return nil;
}
static Class getViewManagerClass(const std::string &componentName, RCTBridge *bridge, RCTBridgeProxy *bridgeProxy)
{
Class viewManager = getViewManagerFromComponentName(componentName);
if (viewManager != nil) {
return viewManager;
}
// If all the heuristics fail, let's try to retrieve the view manager from the bridge/bridgeProxy
if (bridge != nil) {
return [[bridge moduleForName:RCTNSStringFromString(componentName)] class];
}
if (bridgeProxy != nil) {
return [[bridgeProxy moduleForName:RCTNSStringFromString(componentName) lazilyLoadIfNecessary:YES] class];
}
return nil;
}
static const std::shared_ptr<void> constructCoordinator(
const ContextContainer::Shared &contextContainer,
const ComponentDescriptor::Flavor &flavor)
{
auto optionalBridge = contextContainer->find<std::shared_ptr<void>>("Bridge");
RCTBridge *bridge;
if (optionalBridge) {
bridge = unwrapManagedObjectWeakly(optionalBridge.value());
}
RCTBridgeProxy *bridgeProxy;
auto optionalBridgeProxy = contextContainer->find<std::shared_ptr<void>>("RCTBridgeProxy");
if (optionalBridgeProxy) {
bridgeProxy = unwrapManagedObjectWeakly(optionalBridgeProxy.value());
}
auto componentName = *std::static_pointer_cast<std::string const>(flavor);
Class viewManagerClass = getViewManagerClass(componentName, bridge, bridgeProxy);
assert(viewManagerClass);
auto optionalEventDispatcher = contextContainer->find<std::shared_ptr<void>>("RCTEventDispatcher");
RCTEventDispatcher *eventDispatcher;
if (optionalEventDispatcher) {
eventDispatcher = unwrapManagedObject(optionalEventDispatcher.value());
}
auto optionalModuleDecorator = contextContainer->find<std::shared_ptr<void>>("RCTBridgeModuleDecorator");
RCTBridgeModuleDecorator *bridgeModuleDecorator;
if (optionalModuleDecorator) {
bridgeModuleDecorator = unwrapManagedObject(optionalModuleDecorator.value());
}
RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:viewManagerClass
bridge:bridge
eventDispatcher:eventDispatcher];
return wrapManagedObject([[RCTLegacyViewManagerInteropCoordinator alloc]
initWithComponentData:componentData
bridge:bridge
bridgeProxy:bridgeProxy
bridgelessInteropData:bridgeModuleDecorator]);
}
LegacyViewManagerInteropComponentDescriptor::LegacyViewManagerInteropComponentDescriptor(
const ComponentDescriptorParameters &parameters)
: ConcreteComponentDescriptor(parameters), _coordinator(constructCoordinator(contextContainer_, flavor_))
{
}
ComponentHandle LegacyViewManagerInteropComponentDescriptor::getComponentHandle() const
{
return reinterpret_cast<ComponentHandle>(getComponentName());
}
ComponentName LegacyViewManagerInteropComponentDescriptor::getComponentName() const
{
return static_cast<const std::string *>(flavor_.get())->c_str();
}
void LegacyViewManagerInteropComponentDescriptor::adopt(ShadowNode &shadowNode) const
{
ConcreteComponentDescriptor::adopt(shadowNode);
auto &legacyViewManagerInteropShadowNode = static_cast<LegacyViewManagerInteropShadowNode &>(shadowNode);
auto state = LegacyViewManagerInteropState{};
state.coordinator = _coordinator;
legacyViewManagerInteropShadowNode.setStateData(std::move(state));
}
} // namespace facebook::react

View File

@@ -0,0 +1,13 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
namespace facebook::react {
extern const char LegacyViewManagerInteropComponentName[] =
"LegacyViewManagerInterop";
} // namespace facebook::react

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropState.h>
#include <react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropViewEventEmitter.h>
#include <react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropViewProps.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
namespace facebook::react {
extern const char LegacyViewManagerInteropComponentName[];
using LegacyViewManagerInteropShadowNode = ConcreteViewShadowNode<
LegacyViewManagerInteropComponentName,
LegacyViewManagerInteropViewProps,
LegacyViewManagerInteropViewEventEmitter,
LegacyViewManagerInteropState>;
} // namespace facebook::react

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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
#import <memory>
namespace facebook::react {
/*
* State for <LegacyViewManagerInterop> component.
*/
class LegacyViewManagerInteropState final {
public:
std::shared_ptr<void> coordinator;
};
} // namespace facebook::react

View File

@@ -0,0 +1,11 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "LegacyViewManagerInteropState.h"
namespace facebook::react {
} // namespace facebook::react

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "LegacyViewManagerInteropViewEventEmitter.h"
#include <iostream>
namespace facebook::react {
void LegacyViewManagerInteropViewEventEmitter::dispatchEvent(
const std::string& type,
const folly::dynamic& payload) const {
EventEmitter::dispatchEvent(type, payload);
}
} // namespace facebook::react

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <memory>
#include <folly/dynamic.h>
#include <react/renderer/components/view/ViewEventEmitter.h>
#include <react/renderer/core/EventEmitter.h>
namespace facebook::react {
class LegacyViewManagerInteropViewEventEmitter;
using SharedLegacyViewManagerInteropViewEventEmitter =
std::shared_ptr<const LegacyViewManagerInteropViewEventEmitter>;
class LegacyViewManagerInteropViewEventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
void dispatchEvent(const std::string& type, const folly::dynamic& payload)
const;
};
} // namespace facebook::react

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "LegacyViewManagerInteropViewProps.h"
#include <react/renderer/core/DynamicPropsUtilities.h>
namespace facebook::react {
LegacyViewManagerInteropViewProps::LegacyViewManagerInteropViewProps(
const PropsParserContext& context,
const LegacyViewManagerInteropViewProps& sourceProps,
const RawProps& rawProps)
: ViewProps(context, sourceProps, rawProps),
otherProps(
mergeDynamicProps(sourceProps.otherProps, (folly::dynamic)rawProps)) {
}
} // namespace facebook::react

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <folly/dynamic.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/PropsParserContext.h>
#include <unordered_map>
namespace facebook::react {
class LegacyViewManagerInteropViewProps final : public ViewProps {
public:
LegacyViewManagerInteropViewProps() = default;
LegacyViewManagerInteropViewProps(
const PropsParserContext& context,
const LegacyViewManagerInteropViewProps& sourceProps,
const RawProps& rawProps);
#pragma mark - Props
folly::dynamic const otherProps;
};
} // namespace facebook::react

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModuleDecorator.h>
#import <UIKit/UIKit.h>
#include <folly/dynamic.h>
NS_ASSUME_NONNULL_BEGIN
@class RCTComponentData;
@class RCTBridge;
@class RCTBridgeProxy;
typedef void (^InterceptorBlock)(std::string eventName, folly::dynamic event);
@interface RCTLegacyViewManagerInteropCoordinator : NSObject
- (instancetype)initWithComponentData:(RCTComponentData *)componentData
bridge:(nullable RCTBridge *)bridge
bridgeProxy:(nullable RCTBridgeProxy *)bridgeProxy
bridgelessInteropData:(RCTBridgeModuleDecorator *)bridgelessInteropData;
- (UIView *)createPaperViewWithTag:(NSInteger)tag;
- (void)addObserveForTag:(NSInteger)tag usingBlock:(InterceptorBlock)block;
- (void)removeObserveForTag:(NSInteger)tag;
- (void)setProps:(NSDictionary<NSString *, id> *)props forView:(UIView *)view;
- (NSString *)componentViewName;
- (void)handleCommand:(NSString *)commandName
args:(NSArray *)args
reactTag:(NSInteger)tag
paperView:(UIView *)paperView;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,223 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "RCTLegacyViewManagerInteropCoordinator.h"
#include <React/RCTBridge+Private.h>
#include <React/RCTBridgeMethod.h>
#include <React/RCTBridgeProxy.h>
#include <React/RCTComponentData.h>
#include <React/RCTEventDispatcherProtocol.h>
#include <React/RCTFollyConvert.h>
#include <React/RCTModuleData.h>
#include <React/RCTModuleMethod.h>
#include <React/RCTUIManager.h>
#include <React/RCTUIManagerUtils.h>
#include <React/RCTUtils.h>
#include <React/RCTViewManager.h>
#include <folly/json.h>
#include <objc/runtime.h>
using namespace facebook::react;
@implementation RCTLegacyViewManagerInteropCoordinator {
RCTComponentData *_componentData;
__weak RCTBridge *_bridge;
__weak RCTBridgeModuleDecorator *_bridgelessInteropData;
__weak RCTBridgeProxy *_bridgeProxy;
/*
Each instance of `RCTLegacyViewManagerInteropComponentView` registers a block to which events are dispatched.
This is the container that maps unretained UIView pointer to a block to which the event is dispatched.
*/
NSMutableDictionary<NSNumber *, InterceptorBlock> *_eventInterceptors;
/*
* In bridgeless mode, instead of using the bridge to look up RCTModuleData,
* store that information locally.
*/
NSMutableArray<id<RCTBridgeMethod>> *_moduleMethods;
NSMutableDictionary<NSString *, id<RCTBridgeMethod>> *_moduleMethodsByName;
}
- (instancetype)initWithComponentData:(RCTComponentData *)componentData
bridge:(nullable RCTBridge *)bridge
bridgeProxy:(nullable RCTBridgeProxy *)bridgeProxy
bridgelessInteropData:(RCTBridgeModuleDecorator *)bridgelessInteropData
{
if (self = [super init]) {
_componentData = componentData;
_bridge = bridge;
_bridgelessInteropData = bridgelessInteropData;
_bridgeProxy = bridgeProxy;
if (bridgelessInteropData) {
// During bridge mode, RCTBridgeModules will be decorated with these APIs by the bridge.
RCTAssert(
_bridge == nil,
@"RCTLegacyViewManagerInteropCoordinator should not be initialized with RCTBridgeModuleDecorator in bridge mode.");
}
_eventInterceptors = [NSMutableDictionary new];
__weak __typeof(self) weakSelf = self;
_componentData.eventInterceptor = ^(NSString *eventName, NSDictionary *event, NSNumber *reactTag) {
__typeof(self) strongSelf = weakSelf;
if (strongSelf) {
InterceptorBlock block = [strongSelf->_eventInterceptors objectForKey:reactTag];
if (block) {
block(
std::string([RCTNormalizeInputEventName(eventName) UTF8String]),
convertIdToFollyDynamic(event ? event : @{}));
}
}
};
}
return self;
}
- (void)addObserveForTag:(NSInteger)tag usingBlock:(InterceptorBlock)block
{
[_eventInterceptors setObject:block forKey:[NSNumber numberWithInteger:tag]];
}
- (void)removeObserveForTag:(NSInteger)tag
{
[_eventInterceptors removeObjectForKey:[NSNumber numberWithInteger:tag]];
}
- (UIView *)createPaperViewWithTag:(NSInteger)tag
{
UIView *view = [_componentData createViewWithTag:[NSNumber numberWithInteger:tag] rootTag:NULL];
[_bridgelessInteropData attachInteropAPIsToModule:(id<RCTBridgeModule>)_componentData.bridgelessViewManager];
return view;
}
- (void)setProps:(NSDictionary<NSString *, id> *)props forView:(UIView *)view
{
[_componentData setProps:props forView:view];
if ([view respondsToSelector:@selector(didSetProps:)]) {
[view performSelector:@selector(didSetProps:) withObject:[props allKeys]];
}
}
- (NSString *)componentViewName
{
return RCTDropReactPrefixes(_componentData.name);
}
- (void)handleCommand:(NSString *)commandName
args:(NSArray *)args
reactTag:(NSInteger)tag
paperView:(nonnull UIView *)paperView
{
Class managerClass = _componentData.managerClass;
[self _lookupModuleMethodsIfNecessary];
RCTModuleData *moduleData = [_bridge.batchedBridge moduleDataForName:RCTBridgeModuleNameForClass(managerClass)];
id<RCTBridgeMethod> method;
// We can't use `[NSString intValue]` as "0" is a valid command,
// but also a falsy value. [NSNumberFormatter numberFromString] returns a
// `NSNumber *` which is NULL when it's to be NULL
// and it points to 0 when the string is @"0" (not a falsy value).
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
if ([commandName isKindOfClass:[NSNumber class]] || [formatter numberFromString:commandName] != NULL) {
method = moduleData ? moduleData.methods[[commandName intValue]] : _moduleMethods[[commandName intValue]];
} else if ([commandName isKindOfClass:[NSString class]]) {
method = moduleData ? moduleData.methodsByName[commandName] : _moduleMethodsByName[commandName];
if (method == nil) {
RCTLogError(@"No command found with name \"%@\"", commandName);
}
} else {
RCTLogError(@"dispatchViewManagerCommand must be called with a string or integer command");
return;
}
NSArray *newArgs = [@[ [NSNumber numberWithInteger:tag] ] arrayByAddingObjectsFromArray:args];
if (_bridge) {
[self _handleCommandsOnBridge:method withArgs:newArgs];
} else {
[self _handleCommandsOnBridgeless:method withArgs:newArgs];
}
}
#pragma mark - Private
- (void)_handleCommandsOnBridge:(id<RCTBridgeMethod>)method withArgs:(NSArray *)newArgs
{
[_bridge.batchedBridge
dispatchBlock:^{
[method invokeWithBridge:self->_bridge module:self->_componentData.manager arguments:newArgs];
[self->_bridge.uiManager setNeedsLayout];
}
queue:RCTGetUIManagerQueue()];
}
- (void)_handleCommandsOnBridgeless:(id<RCTBridgeMethod>)method withArgs:(NSArray *)newArgs
{
RCTViewManager *componentViewManager = self->_componentData.manager;
[componentViewManager setValue:_bridgeProxy forKey:@"bridge"];
[self->_bridgeProxy.uiManager
addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
[method invokeWithBridge:nil module:componentViewManager arguments:newArgs];
}];
}
- (void)_addUIBlock:(RCTViewManagerUIBlock)block
{
if (_bridge) {
[self _addUIBlockOnBridge:block];
} else {
[self->_bridgeProxy.uiManager addUIBlock:block];
}
}
- (void)_addUIBlockOnBridge:(RCTViewManagerUIBlock)block
{
__weak __typeof__(self) weakSelf = self;
[_bridge.batchedBridge
dispatchBlock:^{
__typeof__(self) strongSelf = weakSelf;
[strongSelf->_bridge.uiManager addUIBlock:block];
}
queue:RCTGetUIManagerQueue()];
}
// This is copy-pasta from RCTModuleData.
- (void)_lookupModuleMethodsIfNecessary
{
if (!_bridge && !_moduleMethods) {
_moduleMethods = [NSMutableArray new];
_moduleMethodsByName = [NSMutableDictionary new];
unsigned int methodCount;
Class cls = _componentData.managerClass;
while (cls && cls != [NSObject class] && cls != [NSProxy class]) {
Method *methods = class_copyMethodList(object_getClass(cls), &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
Method method = methods[i];
SEL selector = method_getName(method);
if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
IMP imp = method_getImplementation(method);
auto exportedMethod = ((const RCTMethodInfo *(*)(id, SEL))imp)(_componentData.managerClass, selector);
id<RCTBridgeMethod> moduleMethod =
[[RCTModuleMethod alloc] initWithExportedMethod:exportedMethod moduleClass:_componentData.managerClass];
[_moduleMethodsByName setValue:moduleMethod forKey:[NSString stringWithUTF8String:moduleMethod.JSMethodName]];
[_moduleMethods addObject:moduleMethod];
}
}
free(methods);
cls = class_getSuperclass(cls);
}
}
}
@end

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "UnstableLegacyViewManagerAutomaticComponentDescriptor.h"
#include <react/renderer/components/legacyviewmanagerinterop/UnstableLegacyViewManagerAutomaticShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <string>
namespace facebook::react {
ComponentName
UnstableLegacyViewManagerAutomaticComponentDescriptor::getComponentName()
const {
return legacyComponentName_.c_str();
}
ComponentHandle
UnstableLegacyViewManagerAutomaticComponentDescriptor::getComponentHandle()
const {
return reinterpret_cast<ComponentHandle>(getComponentName());
}
} // namespace facebook::react

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/legacyviewmanagerinterop/UnstableLegacyViewManagerAutomaticShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <string>
namespace facebook::react {
class UnstableLegacyViewManagerAutomaticComponentDescriptor final
: public ConcreteComponentDescriptor<
LegacyViewManagerAndroidInteropShadowNode> {
public:
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
UnstableLegacyViewManagerAutomaticComponentDescriptor(
const ComponentDescriptorParameters& parameters,
std::string legacyComponentName)
: ConcreteComponentDescriptor(parameters),
legacyComponentName_(std::move(legacyComponentName)) {}
ComponentHandle getComponentHandle() const override;
ComponentName getComponentName() const override;
private:
std::string legacyComponentName_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,13 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
namespace facebook::react {
extern const char LegacyViewManagerAndroidInteropComponentName[] =
"LegacyViewManagerInterop";
} // namespace facebook::react

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropViewEventEmitter.h>
#include <react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropViewProps.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
namespace facebook::react {
extern const char LegacyViewManagerAndroidInteropComponentName[];
using LegacyViewManagerAndroidInteropShadowNode = ConcreteViewShadowNode<
LegacyViewManagerAndroidInteropComponentName,
LegacyViewManagerInteropViewProps>;
} // namespace facebook::react

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
/*
* Descriptor for <UnstableReactLegacyComponent> component.
*
* This component is part of the Fabric Interop Layer and is subject to future
* changes (hence the "Unstable" prefix).
*/
template <const char* concreteComponentName>
class UnstableLegacyViewManagerInteropComponentDescriptor
: public ConcreteComponentDescriptor<
ConcreteViewShadowNode<concreteComponentName, ViewProps>> {
public:
UnstableLegacyViewManagerInteropComponentDescriptor<concreteComponentName>(
const ComponentDescriptorParameters& parameters)
: ConcreteComponentDescriptor<
ConcreteViewShadowNode<concreteComponentName, ViewProps>>(
parameters) {}
private:
};
} // namespace facebook::react

View File

@@ -0,0 +1,37 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB rrc_modal_SRC CONFIGURE_DEPENDS *.cpp)
add_library(rrc_modal STATIC ${rrc_modal_SRC})
target_include_directories(rrc_modal PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(rrc_modal
glog
folly_runtime
glog_init
react_codegen_rncore
react_render_componentregistry
react_render_core
react_render_debug
react_render_graphics
react_render_imagemanager
react_render_mapbuffer
react_render_uimanager
rrc_image
rrc_view
yoga
)

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <glog/logging.h>
#include <react/renderer/components/modal/ModalHostViewShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
/*
* Descriptor for <ModalHostView> component.
*/
class ModalHostViewComponentDescriptor final
: public ConcreteComponentDescriptor<ModalHostViewShadowNode> {
public:
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
void adopt(ShadowNode& shadowNode) const override {
auto& layoutableShadowNode =
static_cast<YogaLayoutableShadowNode&>(shadowNode);
auto& stateData =
static_cast<const ModalHostViewShadowNode::ConcreteState&>(
*shadowNode.getState())
.getData();
layoutableShadowNode.setSize(
Size{stateData.screenSize.width, stateData.screenSize.height});
layoutableShadowNode.setPositionType(YGPositionTypeAbsolute);
ConcreteComponentDescriptor::adopt(shadowNode);
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ModalHostViewShadowNode.h"
#include <react/renderer/components/modal/ModalHostViewShadowNode.h>
#include <react/renderer/core/LayoutContext.h>
namespace facebook::react {
extern const char ModalHostViewComponentName[] = "ModalHostView";
} // namespace facebook::react

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/modal/ModalHostViewState.h>
#include <react/renderer/components/rncore/EventEmitters.h>
#include <react/renderer/components/rncore/Props.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
namespace facebook::react {
extern const char ModalHostViewComponentName[];
/*
* `ShadowNode` for <ModalHostView> component.
*/
class ModalHostViewShadowNode final : public ConcreteViewShadowNode<
ModalHostViewComponentName,
ModalHostViewProps,
ModalHostViewEventEmitter,
ModalHostViewState> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
static ShadowNodeTraits BaseTraits() {
auto traits = ConcreteViewShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::RootNodeKind);
return traits;
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ModalHostViewState.h"
namespace facebook::react {
#ifdef ANDROID
folly::dynamic ModalHostViewState::getDynamic() const {
return folly::dynamic::object("screenWidth", screenSize.width)(
"screenHeight", screenSize.height);
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/core/graphicsConversions.h>
#include <react/renderer/graphics/Float.h>
#ifdef ANDROID
#include <folly/dynamic.h>
#include <react/renderer/mapbuffer/MapBuffer.h>
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
#endif
namespace facebook::react {
/*
* State for <ModalHostView> component.
*/
class ModalHostViewState final {
public:
using Shared = std::shared_ptr<const ModalHostViewState>;
ModalHostViewState(){};
ModalHostViewState(Size screenSize_) : screenSize(screenSize_){};
#ifdef ANDROID
ModalHostViewState(
const ModalHostViewState& previousState,
folly::dynamic data)
: screenSize(Size{
(Float)data["screenWidth"].getDouble(),
(Float)data["screenHeight"].getDouble()}){};
#endif
const Size screenSize{};
#ifdef ANDROID
folly::dynamic getDynamic() const;
MapBuffer getMapBuffer() const {
return MapBufferBuilder::EMPTY();
};
#endif
#pragma mark - Getters
};
} // namespace facebook::react

View File

@@ -0,0 +1,40 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB rrc_progressbar_SRC CONFIGURE_DEPENDS android/react/renderer/components/progressbar/*.cpp)
add_library(rrc_progressbar STATIC ${rrc_progressbar_SRC})
target_include_directories(rrc_progressbar
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/android/
)
target_link_libraries(rrc_progressbar
glog
fbjni
folly_runtime
glog_init
react_codegen_rncore
react_debug
react_render_componentregistry
react_render_core
react_render_debug
react_render_graphics
react_render_uimanager
reactnativejni
rrc_view
yoga
)

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/core/ConcreteComponentDescriptor.h>
#include "AndroidProgressBarMeasurementsManager.h"
#include "AndroidProgressBarShadowNode.h"
namespace facebook::react {
/*
* Descriptor for <AndroidProgressBar> component.
*/
class AndroidProgressBarComponentDescriptor final
: public ConcreteComponentDescriptor<AndroidProgressBarShadowNode> {
public:
AndroidProgressBarComponentDescriptor(
const ComponentDescriptorParameters& parameters)
: ConcreteComponentDescriptor(parameters),
measurementsManager_(
std::make_shared<AndroidProgressBarMeasurementsManager>(
contextContainer_)) {}
void adopt(ShadowNode& shadowNode) const override {
ConcreteComponentDescriptor::adopt(shadowNode);
auto& androidProgressBarShadowNode =
static_cast<AndroidProgressBarShadowNode&>(shadowNode);
// `AndroidProgressBarShadowNode` uses
// `AndroidProgressBarMeasurementsManager` to provide measurements to Yoga.
androidProgressBarShadowNode.setAndroidProgressBarMeasurementsManager(
measurementsManager_);
// All `AndroidProgressBarShadowNode`s must have leaf Yoga nodes with
// properly setup measure function.
androidProgressBarShadowNode.enableMeasurement();
}
private:
const std::shared_ptr<AndroidProgressBarMeasurementsManager>
measurementsManager_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "AndroidProgressBarMeasurementsManager.h"
#include <fbjni/fbjni.h>
#include <react/jni/ReadableNativeMap.h>
#include <react/renderer/core/conversions.h>
using namespace facebook::jni;
namespace facebook::react {
Size AndroidProgressBarMeasurementsManager::measure(
SurfaceId surfaceId,
const AndroidProgressBarProps& props,
LayoutConstraints layoutConstraints) const {
{
std::scoped_lock lock(mutex_);
if (hasBeenMeasured_) {
return cachedMeasurement_;
}
}
const jni::global_ref<jobject>& fabricUIManager =
contextContainer_->at<jni::global_ref<jobject>>("FabricUIManager");
static auto measure = facebook::jni::findClassStatic(
"com/facebook/react/fabric/FabricUIManager")
->getMethod<jlong(
jint,
jstring,
ReadableMap::javaobject,
ReadableMap::javaobject,
ReadableMap::javaobject,
jfloat,
jfloat,
jfloat,
jfloat)>("measure");
auto minimumSize = layoutConstraints.minimumSize;
auto maximumSize = layoutConstraints.maximumSize;
local_ref<JString> componentName = make_jstring("AndroidProgressBar");
auto serialiazedProps = toDynamic(props);
local_ref<ReadableNativeMap::javaobject> propsRNM =
ReadableNativeMap::newObjectCxxArgs(serialiazedProps);
local_ref<ReadableMap::javaobject> propsRM =
make_local(reinterpret_cast<ReadableMap::javaobject>(propsRNM.get()));
auto measurement = yogaMeassureToSize(measure(
fabricUIManager,
surfaceId,
componentName.get(),
nullptr,
propsRM.get(),
nullptr,
minimumSize.width,
maximumSize.width,
minimumSize.height,
maximumSize.height));
std::scoped_lock lock(mutex_);
cachedMeasurement_ = measurement;
return measurement;
}
} // namespace facebook::react

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/progressbar/conversions.h>
#include <react/renderer/components/rncore/Props.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/core/LayoutConstraints.h>
#include <react/utils/ContextContainer.h>
namespace facebook::react {
class AndroidProgressBarMeasurementsManager {
public:
AndroidProgressBarMeasurementsManager(
const ContextContainer::Shared& contextContainer)
: contextContainer_(contextContainer) {}
Size measure(
SurfaceId surfaceId,
const AndroidProgressBarProps& props,
LayoutConstraints layoutConstraints) const;
private:
const ContextContainer::Shared contextContainer_;
mutable std::mutex mutex_;
mutable bool hasBeenMeasured_ = false;
mutable Size cachedMeasurement_{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "AndroidProgressBarShadowNode.h"
#include <react/renderer/components/progressbar/AndroidProgressBarShadowNode.h>
#include <react/renderer/core/LayoutContext.h>
namespace facebook::react {
extern const char AndroidProgressBarComponentName[] = "AndroidProgressBar";
void AndroidProgressBarShadowNode::setAndroidProgressBarMeasurementsManager(
const std::shared_ptr<AndroidProgressBarMeasurementsManager>&
measurementsManager) {
ensureUnsealed();
measurementsManager_ = measurementsManager;
}
#pragma mark - LayoutableShadowNode
Size AndroidProgressBarShadowNode::measureContent(
const LayoutContext& /*layoutContext*/,
const LayoutConstraints& layoutConstraints) const {
return measurementsManager_->measure(
getSurfaceId(), getConcreteProps(), layoutConstraints);
}
} // namespace facebook::react

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/progressbar/AndroidProgressBarMeasurementsManager.h>
#include <react/renderer/components/rncore/EventEmitters.h>
#include <react/renderer/components/rncore/Props.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
namespace facebook::react {
extern const char AndroidProgressBarComponentName[];
/*
* `ShadowNode` for <AndroidProgressBar> component.
*/
class AndroidProgressBarShadowNode final : public ConcreteViewShadowNode<
AndroidProgressBarComponentName,
AndroidProgressBarProps,
AndroidProgressBarEventEmitter> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
// Associates a shared `AndroidProgressBarMeasurementsManager` with the node.
void setAndroidProgressBarMeasurementsManager(
const std::shared_ptr<AndroidProgressBarMeasurementsManager>&
measurementsManager);
#pragma mark - LayoutableShadowNode
Size measureContent(
const LayoutContext& layoutContext,
const LayoutConstraints& layoutConstraints) const override;
private:
std::shared_ptr<AndroidProgressBarMeasurementsManager> measurementsManager_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <folly/dynamic.h>
#include <react/renderer/components/rncore/Props.h>
#include <react/renderer/core/propsConversions.h>
namespace facebook::react {
#ifdef ANDROID
inline folly::dynamic toDynamic(const AndroidProgressBarProps& props) {
folly::dynamic serializedProps = folly::dynamic::object();
serializedProps["styleAttr"] = props.styleAttr;
serializedProps["typeAttr"] = props.typeAttr;
serializedProps["indeterminate"] = props.indeterminate;
serializedProps["progress"] = props.progress;
serializedProps["animating"] = props.animating;
serializedProps["color"] = toAndroidRepr(props.color);
serializedProps["testID"] = props.testID;
return serializedProps;
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,32 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB rrc_root_SRC CONFIGURE_DEPENDS *.cpp)
add_library(rrc_root STATIC ${rrc_root_SRC})
target_include_directories(rrc_root PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(rrc_root
folly_runtime
glog
glog_init
react_debug
react_render_core
react_render_debug
react_render_graphics
rrc_view
yoga
)

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/root/RootShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
using RootComponentDescriptor = ConcreteComponentDescriptor<RootShadowNode>;
} // namespace facebook::react

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "RootProps.h"
#include <react/renderer/components/view/YogaLayoutableShadowNode.h>
#include <react/renderer/components/view/conversions.h>
namespace facebook::react {
// Note that a default/empty context may be passed here from RootShadowNode.
// If that's a problem and the context is necessary here, refactor
// RootShadowNode first.
RootProps::RootProps(
const PropsParserContext& context,
const RootProps& sourceProps,
const RawProps& rawProps)
: ViewProps(context, sourceProps, rawProps) {}
// Note that a default/empty context may be passed here from RootShadowNode.
// If that's a problem and the context is necessary here, refactor
// RootShadowNode first.
RootProps::RootProps(
const PropsParserContext& /*context*/,
const RootProps& /*sourceProps*/,
const LayoutConstraints& layoutConstraints,
const LayoutContext& layoutContext)
: ViewProps(),
layoutConstraints(layoutConstraints),
layoutContext(layoutContext){};
} // namespace facebook::react

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <memory>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/LayoutConstraints.h>
#include <react/renderer/core/LayoutContext.h>
#include <react/renderer/core/PropsParserContext.h>
namespace facebook::react {
class RootProps final : public ViewProps {
public:
RootProps() = default;
RootProps(
const PropsParserContext& context,
const RootProps& sourceProps,
const RawProps& rawProps);
RootProps(
const PropsParserContext& context,
const RootProps& sourceProps,
const LayoutConstraints& layoutConstraints,
const LayoutContext& layoutContext);
#pragma mark - Props
LayoutConstraints layoutConstraints{};
LayoutContext layoutContext{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "RootShadowNode.h"
#include <react/renderer/components/view/conversions.h>
#include <react/renderer/debug/SystraceSection.h>
namespace facebook::react {
const char RootComponentName[] = "RootView";
bool RootShadowNode::layoutIfNeeded(
std::vector<const LayoutableShadowNode*>* affectedNodes) {
SystraceSection s("RootShadowNode::layout");
if (getIsLayoutClean()) {
return false;
}
ensureUnsealed();
auto layoutContext = getConcreteProps().layoutContext;
layoutContext.affectedNodes = affectedNodes;
layoutTree(layoutContext, getConcreteProps().layoutConstraints);
return true;
}
Transform RootShadowNode::getTransform() const {
auto viewportOffset = getConcreteProps().layoutContext.viewportOffset;
return Transform::Translate(viewportOffset.x, viewportOffset.y, 0);
}
RootShadowNode::Unshared RootShadowNode::clone(
const PropsParserContext& propsParserContext,
const LayoutConstraints& layoutConstraints,
const LayoutContext& layoutContext) const {
auto props = std::make_shared<const RootProps>(
propsParserContext, getConcreteProps(), layoutConstraints, layoutContext);
auto newRootShadowNode = std::make_shared<RootShadowNode>(
*this,
ShadowNodeFragment{
/* .props = */ props,
});
if (layoutConstraints != getConcreteProps().layoutConstraints) {
newRootShadowNode->dirtyLayout();
}
return newRootShadowNode;
}
} // namespace facebook::react

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <memory>
#include <react/renderer/components/root/RootProps.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/core/LayoutContext.h>
#include <react/renderer/core/PropsParserContext.h>
namespace facebook::react {
class RootShadowNode;
extern const char RootComponentName[];
/*
* `ShadowNode` for the root component.
* Besides all functionality of the `View` component, `RootShadowNode` contains
* props which represent external layout constraints and context of the
* shadow tree.
*/
class RootShadowNode final
: public ConcreteViewShadowNode<RootComponentName, RootProps> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
using Shared = std::shared_ptr<const RootShadowNode>;
using Unshared = std::shared_ptr<RootShadowNode>;
static ShadowNodeTraits BaseTraits() {
auto traits = ConcreteViewShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::RootNodeKind);
return traits;
}
/*
* Layouts the shadow tree if needed.
* Returns `false` if the three is already laid out.
*/
bool layoutIfNeeded(
std::vector<const LayoutableShadowNode*>* affectedNodes = {});
/*
* Clones the node with given `layoutConstraints` and `layoutContext`.
*/
RootShadowNode::Unshared clone(
const PropsParserContext& propsParserContext,
const LayoutConstraints& layoutConstraints,
const LayoutContext& layoutContext) const;
Transform getTransform() const override;
};
} // namespace facebook::react

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <react/renderer/components/root/RootComponentDescriptor.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/element/ComponentBuilder.h>
#include <gtest/gtest.h>
#include <react/renderer/element/Element.h>
#include <react/renderer/element/testUtils.h>
namespace facebook::react {
TEST(RootShadowNodeTest, cloneWithLayoutConstraints) {
ContextContainer contextContainer{};
PropsParserContext parserContext{-1, contextContainer};
auto builder = simpleComponentBuilder();
std::shared_ptr<RootShadowNode> rootShadowNode;
LayoutConstraints defaultLayoutConstraints = {};
auto element =
Element<RootShadowNode>().reference(rootShadowNode).tag(1).props([&] {
auto sharedProps = std::make_shared<RootProps>();
sharedProps->layoutConstraints = defaultLayoutConstraints;
return sharedProps;
});
builder.build(element);
EXPECT_FALSE(rootShadowNode->getIsLayoutClean());
EXPECT_TRUE(rootShadowNode->layoutIfNeeded());
EXPECT_TRUE(rootShadowNode->getIsLayoutClean());
auto clonedWithDifferentLayoutConstraints = rootShadowNode->clone(
parserContext, LayoutConstraints{{0, 0}, {10, 10}}, {});
EXPECT_FALSE(clonedWithDifferentLayoutConstraints->getIsLayoutClean());
EXPECT_TRUE(clonedWithDifferentLayoutConstraints->layoutIfNeeded());
}
} // namespace facebook::react

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/debug/react_native_assert.h>
#include <react/renderer/components/safeareaview/SafeAreaViewShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
/*
* Descriptor for <SafeAreaView> component.
*/
class SafeAreaViewComponentDescriptor final
: public ConcreteComponentDescriptor<SafeAreaViewShadowNode> {
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
void adopt(ShadowNode& shadowNode) const override {
auto& layoutableShadowNode =
static_cast<YogaLayoutableShadowNode&>(shadowNode);
auto& stateData = static_cast<const SafeAreaViewShadowNode::ConcreteState&>(
*shadowNode.getState())
.getData();
layoutableShadowNode.setPadding(stateData.padding);
ConcreteComponentDescriptor::adopt(shadowNode);
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "SafeAreaViewShadowNode.h"
namespace facebook::react {
extern const char SafeAreaViewComponentName[] = "SafeAreaView";
} // namespace facebook::react

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/rncore/EventEmitters.h>
#include <react/renderer/components/rncore/Props.h>
#include <react/renderer/components/safeareaview/SafeAreaViewState.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
namespace facebook::react {
extern const char SafeAreaViewComponentName[];
/*
* `ShadowNode` for <SafeAreaView> component.
*/
class SafeAreaViewShadowNode final : public ConcreteViewShadowNode<
SafeAreaViewComponentName,
SafeAreaViewProps,
ViewEventEmitter,
SafeAreaViewState> {
using ConcreteViewShadowNode::ConcreteViewShadowNode;
};
} // namespace facebook::react

View File

@@ -0,0 +1,10 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "SafeAreaViewState.h"
namespace facebook::react {} // namespace facebook::react

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/graphics/RectangleEdges.h>
namespace facebook::react {
/*
* State for <SafeAreaView> component.
*/
class SafeAreaViewState final {
public:
EdgeInsets padding{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,34 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB rrc_scrollview_SRC CONFIGURE_DEPENDS *.cpp)
add_library(rrc_scrollview STATIC ${rrc_scrollview_SRC})
target_include_directories(rrc_scrollview PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(rrc_scrollview
glog
folly_runtime
glog_init
jsi
react_debug
react_render_core
react_render_debug
react_render_graphics
react_render_mapbuffer
rrc_view
yoga
)

View File

@@ -0,0 +1,133 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTDefines.h>
#import <React/RCTLog.h>
NS_ASSUME_NONNULL_BEGIN
@protocol RCTScrollViewProtocol <NSObject>
- (void)flashScrollIndicators;
- (void)scrollTo:(double)x y:(double)y animated:(BOOL)animated;
- (void)scrollToEnd:(BOOL)animated;
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated;
@end
RCT_EXTERN inline void
RCTScrollViewHandleCommand(id<RCTScrollViewProtocol> componentView, const NSString *commandName, const NSArray *args)
{
if ([commandName isEqualToString:@"flashScrollIndicators"]) {
#if RCT_DEBUG
if ([args count] != 0) {
RCTLogError(
@"%@ command %@ received %d arguments, expected %d.", @"ScrollView", commandName, (int)[args count], 1);
return;
}
#endif
[componentView flashScrollIndicators];
return;
}
if ([commandName isEqualToString:@"scrollTo"]) {
#if RCT_DEBUG
if ([args count] != 3) {
RCTLogError(
@"%@ command %@ received %d arguments, expected %d.", @"ScrollView", commandName, (int)[args count], 3);
return;
}
#endif
NSObject *arg0 = args[0];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg0, [NSNumber class], @"float", @"ScrollView", commandName, @"1st")) {
return;
}
#endif
NSObject *arg1 = args[1];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg1, [NSNumber class], @"float", @"ScrollView", commandName, @"2nd")) {
return;
}
#endif
NSObject *arg2 = args[2];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg2, [NSNumber class], @"boolean", @"ScrollView", commandName, @"3rd")) {
return;
}
#endif
double x = [(NSNumber *)arg0 doubleValue];
double y = [(NSNumber *)arg1 doubleValue];
BOOL animated = [(NSNumber *)arg2 boolValue];
[componentView scrollTo:x y:y animated:animated];
return;
}
if ([commandName isEqualToString:@"scrollToEnd"]) {
#if RCT_DEBUG
if ([args count] != 1) {
RCTLogError(
@"%@ command %@ received %d arguments, expected %d.", @"ScrollView", commandName, (int)[args count], 1);
return;
}
#endif
NSObject *arg0 = args[0];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg0, [NSNumber class], @"boolean", @"ScrollView", commandName, @"1st")) {
return;
}
#endif
BOOL animated = [(NSNumber *)arg0 boolValue];
[componentView scrollToEnd:animated];
return;
}
if ([commandName isEqualToString:@"zoomToRect"]) {
#if RCT_DEBUG
if ([args count] != 2) {
RCTLogError(
@"%@ command %@ received %d arguments, expected %d.", @"ScrollView", commandName, (int)[args count], 2);
return;
}
#endif
NSObject *arg0 = args[0];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(
arg0, [NSDictionary class], @"dictionary", @"ScrollView", commandName, @"1st")) {
return;
}
#endif
NSDictionary *rectDict = (NSDictionary *)arg0;
NSNumber *x = rectDict[@"x"];
NSNumber *y = rectDict[@"y"];
NSNumber *width = rectDict[@"width"];
NSNumber *height = rectDict[@"height"];
CGRect rect = CGRectMake(x.doubleValue, y.doubleValue, width.doubleValue, height.doubleValue);
NSObject *arg1 = args[1];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg1, [NSNumber class], @"boolean", @"ScrollView", commandName, @"2nd")) {
return;
}
#endif
BOOL animated = [(NSNumber *)arg1 boolValue];
[componentView zoomToRect:rect animated:animated];
return;
}
}
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/scrollview/ScrollViewShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
using ScrollViewComponentDescriptor =
ConcreteComponentDescriptor<ScrollViewShadowNode>;
} // namespace facebook::react

View File

@@ -0,0 +1,107 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ScrollViewEventEmitter.h"
namespace facebook::react {
static jsi::Value scrollViewMetricsPayload(
jsi::Runtime& runtime,
const ScrollViewMetrics& scrollViewMetrics) {
auto payload = jsi::Object(runtime);
{
auto contentOffset = jsi::Object(runtime);
contentOffset.setProperty(runtime, "x", scrollViewMetrics.contentOffset.x);
contentOffset.setProperty(runtime, "y", scrollViewMetrics.contentOffset.y);
payload.setProperty(runtime, "contentOffset", contentOffset);
}
{
auto contentInset = jsi::Object(runtime);
contentInset.setProperty(
runtime, "top", scrollViewMetrics.contentInset.top);
contentInset.setProperty(
runtime, "left", scrollViewMetrics.contentInset.left);
contentInset.setProperty(
runtime, "bottom", scrollViewMetrics.contentInset.bottom);
contentInset.setProperty(
runtime, "right", scrollViewMetrics.contentInset.right);
payload.setProperty(runtime, "contentInset", contentInset);
}
{
auto contentSize = jsi::Object(runtime);
contentSize.setProperty(
runtime, "width", scrollViewMetrics.contentSize.width);
contentSize.setProperty(
runtime, "height", scrollViewMetrics.contentSize.height);
payload.setProperty(runtime, "contentSize", contentSize);
}
{
auto containerSize = jsi::Object(runtime);
containerSize.setProperty(
runtime, "width", scrollViewMetrics.containerSize.width);
containerSize.setProperty(
runtime, "height", scrollViewMetrics.containerSize.height);
payload.setProperty(runtime, "layoutMeasurement", containerSize);
}
payload.setProperty(runtime, "zoomScale", scrollViewMetrics.zoomScale);
return payload;
}
void ScrollViewEventEmitter::onScroll(
const ScrollViewMetrics& scrollViewMetrics) const {
dispatchUniqueEvent("scroll", [scrollViewMetrics](jsi::Runtime& runtime) {
return scrollViewMetricsPayload(runtime, scrollViewMetrics);
});
}
void ScrollViewEventEmitter::onScrollToTop(
const ScrollViewMetrics& scrollViewMetrics) const {
dispatchUniqueEvent(
"scrollToTop", [scrollViewMetrics](jsi::Runtime& runtime) {
return scrollViewMetricsPayload(runtime, scrollViewMetrics);
});
}
void ScrollViewEventEmitter::onScrollBeginDrag(
const ScrollViewMetrics& scrollViewMetrics) const {
dispatchScrollViewEvent("scrollBeginDrag", scrollViewMetrics);
}
void ScrollViewEventEmitter::onScrollEndDrag(
const ScrollViewMetrics& scrollViewMetrics) const {
dispatchScrollViewEvent("scrollEndDrag", scrollViewMetrics);
}
void ScrollViewEventEmitter::onMomentumScrollBegin(
const ScrollViewMetrics& scrollViewMetrics) const {
dispatchScrollViewEvent("momentumScrollBegin", scrollViewMetrics);
}
void ScrollViewEventEmitter::onMomentumScrollEnd(
const ScrollViewMetrics& scrollViewMetrics) const {
dispatchScrollViewEvent("momentumScrollEnd", scrollViewMetrics);
}
void ScrollViewEventEmitter::dispatchScrollViewEvent(
std::string name,
const ScrollViewMetrics& scrollViewMetrics,
EventPriority priority) const {
dispatchEvent(
std::move(name),
[scrollViewMetrics](jsi::Runtime& runtime) {
return scrollViewMetricsPayload(runtime, scrollViewMetrics);
},
priority);
}
} // namespace facebook::react

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <memory>
#include <folly/dynamic.h>
#include <react/renderer/components/view/ViewEventEmitter.h>
#include <react/renderer/core/EventEmitter.h>
namespace facebook::react {
class ScrollViewMetrics {
public:
Size contentSize;
Point contentOffset;
EdgeInsets contentInset;
Size containerSize;
Float zoomScale;
};
class ScrollViewEventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
void onScroll(const ScrollViewMetrics& scrollViewMetrics) const;
void onScrollBeginDrag(const ScrollViewMetrics& scrollViewMetrics) const;
void onScrollEndDrag(const ScrollViewMetrics& scrollViewMetrics) const;
void onMomentumScrollBegin(const ScrollViewMetrics& scrollViewMetrics) const;
void onMomentumScrollEnd(const ScrollViewMetrics& scrollViewMetrics) const;
void onScrollToTop(const ScrollViewMetrics& scrollViewMetrics) const;
private:
void dispatchScrollViewEvent(
std::string name,
const ScrollViewMetrics& scrollViewMetrics,
EventPriority priority = EventPriority::AsynchronousBatched) const;
};
} // namespace facebook::react

View File

@@ -0,0 +1,531 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ScrollViewProps.h"
#include <react/renderer/components/scrollview/conversions.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/renderer/debug/debugStringConvertibleUtils.h>
#include <react/utils/CoreFeatures.h>
#include <react/renderer/core/propsConversions.h>
namespace facebook::react {
ScrollViewProps::ScrollViewProps(
const PropsParserContext& context,
const ScrollViewProps& sourceProps,
const RawProps& rawProps)
: ViewProps(context, sourceProps, rawProps),
alwaysBounceHorizontal(
CoreFeatures::enablePropIteratorSetter
? sourceProps.alwaysBounceHorizontal
: convertRawProp(
context,
rawProps,
"alwaysBounceHorizontal",
sourceProps.alwaysBounceHorizontal,
{})),
alwaysBounceVertical(
CoreFeatures::enablePropIteratorSetter
? sourceProps.alwaysBounceVertical
: convertRawProp(
context,
rawProps,
"alwaysBounceVertical",
sourceProps.alwaysBounceVertical,
{})),
bounces(
CoreFeatures::enablePropIteratorSetter ? sourceProps.bounces
: convertRawProp(
context,
rawProps,
"bounces",
sourceProps.bounces,
true)),
bouncesZoom(
CoreFeatures::enablePropIteratorSetter ? sourceProps.bouncesZoom
: convertRawProp(
context,
rawProps,
"bouncesZoom",
sourceProps.bouncesZoom,
true)),
canCancelContentTouches(
CoreFeatures::enablePropIteratorSetter
? sourceProps.canCancelContentTouches
: convertRawProp(
context,
rawProps,
"canCancelContentTouches",
sourceProps.canCancelContentTouches,
true)),
centerContent(
CoreFeatures::enablePropIteratorSetter
? sourceProps.centerContent
: convertRawProp(
context,
rawProps,
"centerContent",
sourceProps.centerContent,
{})),
automaticallyAdjustContentInsets(
CoreFeatures::enablePropIteratorSetter
? sourceProps.automaticallyAdjustContentInsets
: convertRawProp(
context,
rawProps,
"automaticallyAdjustContentInsets",
sourceProps.automaticallyAdjustContentInsets,
{})),
automaticallyAdjustsScrollIndicatorInsets(
CoreFeatures::enablePropIteratorSetter
? sourceProps.automaticallyAdjustsScrollIndicatorInsets
: convertRawProp(
context,
rawProps,
"automaticallyAdjustsScrollIndicatorInsets",
sourceProps.automaticallyAdjustsScrollIndicatorInsets,
true)),
decelerationRate(
CoreFeatures::enablePropIteratorSetter
? sourceProps.decelerationRate
: convertRawProp(
context,
rawProps,
"decelerationRate",
sourceProps.decelerationRate,
(Float)0.998)),
endDraggingSensitivityMultiplier(
CoreFeatures::enablePropIteratorSetter
? sourceProps.endDraggingSensitivityMultiplier
: convertRawProp(
context,
rawProps,
"endDraggingSensitivityMultiplier",
sourceProps.endDraggingSensitivityMultiplier,
1)),
endDraggingSensitivityVelocityMultiplier(
CoreFeatures::enablePropIteratorSetter
? sourceProps.endDraggingSensitivityVelocityMultiplier
: convertRawProp(
context,
rawProps,
"endDraggingSensitivityVelocityMultiplier",
sourceProps.endDraggingSensitivityVelocityMultiplier,
0)),
directionalLockEnabled(
CoreFeatures::enablePropIteratorSetter
? sourceProps.directionalLockEnabled
: convertRawProp(
context,
rawProps,
"directionalLockEnabled",
sourceProps.directionalLockEnabled,
{})),
indicatorStyle(
CoreFeatures::enablePropIteratorSetter
? sourceProps.indicatorStyle
: convertRawProp(
context,
rawProps,
"indicatorStyle",
sourceProps.indicatorStyle,
{})),
keyboardDismissMode(
CoreFeatures::enablePropIteratorSetter
? sourceProps.keyboardDismissMode
: convertRawProp(
context,
rawProps,
"keyboardDismissMode",
sourceProps.keyboardDismissMode,
{})),
maintainVisibleContentPosition(
CoreFeatures::enablePropIteratorSetter
? sourceProps.maintainVisibleContentPosition
: convertRawProp(
context,
rawProps,
"maintainVisibleContentPosition",
sourceProps.maintainVisibleContentPosition,
{})),
maximumZoomScale(
CoreFeatures::enablePropIteratorSetter
? sourceProps.maximumZoomScale
: convertRawProp(
context,
rawProps,
"maximumZoomScale",
sourceProps.maximumZoomScale,
(Float)1.0)),
minimumZoomScale(
CoreFeatures::enablePropIteratorSetter
? sourceProps.minimumZoomScale
: convertRawProp(
context,
rawProps,
"minimumZoomScale",
sourceProps.minimumZoomScale,
(Float)1.0)),
scrollEnabled(
CoreFeatures::enablePropIteratorSetter
? sourceProps.scrollEnabled
: convertRawProp(
context,
rawProps,
"scrollEnabled",
sourceProps.scrollEnabled,
true)),
pagingEnabled(
CoreFeatures::enablePropIteratorSetter
? sourceProps.pagingEnabled
: convertRawProp(
context,
rawProps,
"pagingEnabled",
sourceProps.pagingEnabled,
{})),
pinchGestureEnabled(
CoreFeatures::enablePropIteratorSetter
? sourceProps.pinchGestureEnabled
: convertRawProp(
context,
rawProps,
"pinchGestureEnabled",
sourceProps.pinchGestureEnabled,
true)),
scrollsToTop(
CoreFeatures::enablePropIteratorSetter ? sourceProps.scrollsToTop
: convertRawProp(
context,
rawProps,
"scrollsToTop",
sourceProps.scrollsToTop,
true)),
showsHorizontalScrollIndicator(
CoreFeatures::enablePropIteratorSetter
? sourceProps.showsHorizontalScrollIndicator
: convertRawProp(
context,
rawProps,
"showsHorizontalScrollIndicator",
sourceProps.showsHorizontalScrollIndicator,
true)),
showsVerticalScrollIndicator(
CoreFeatures::enablePropIteratorSetter
? sourceProps.showsVerticalScrollIndicator
: convertRawProp(
context,
rawProps,
"showsVerticalScrollIndicator",
sourceProps.showsVerticalScrollIndicator,
true)),
scrollEventThrottle(
CoreFeatures::enablePropIteratorSetter
? sourceProps.scrollEventThrottle
: convertRawProp(
context,
rawProps,
"scrollEventThrottle",
sourceProps.scrollEventThrottle,
{})),
zoomScale(
CoreFeatures::enablePropIteratorSetter ? sourceProps.zoomScale
: convertRawProp(
context,
rawProps,
"zoomScale",
sourceProps.zoomScale,
(Float)1.0)),
contentInset(
CoreFeatures::enablePropIteratorSetter ? sourceProps.contentInset
: convertRawProp(
context,
rawProps,
"contentInset",
sourceProps.contentInset,
{})),
contentOffset(
CoreFeatures::enablePropIteratorSetter
? sourceProps.contentOffset
: convertRawProp(
context,
rawProps,
"contentOffset",
sourceProps.contentOffset,
{})),
scrollIndicatorInsets(
CoreFeatures::enablePropIteratorSetter
? sourceProps.scrollIndicatorInsets
: convertRawProp(
context,
rawProps,
"scrollIndicatorInsets",
sourceProps.scrollIndicatorInsets,
{})),
snapToInterval(
CoreFeatures::enablePropIteratorSetter
? sourceProps.snapToInterval
: convertRawProp(
context,
rawProps,
"snapToInterval",
sourceProps.snapToInterval,
{})),
snapToAlignment(
CoreFeatures::enablePropIteratorSetter
? sourceProps.snapToAlignment
: convertRawProp(
context,
rawProps,
"snapToAlignment",
sourceProps.snapToAlignment,
{})),
disableIntervalMomentum(
CoreFeatures::enablePropIteratorSetter
? sourceProps.disableIntervalMomentum
: convertRawProp(
context,
rawProps,
"disableIntervalMomentum",
sourceProps.disableIntervalMomentum,
{})),
snapToOffsets(
CoreFeatures::enablePropIteratorSetter
? sourceProps.snapToOffsets
: convertRawProp(
context,
rawProps,
"snapToOffsets",
sourceProps.snapToOffsets,
{})),
snapToStart(
CoreFeatures::enablePropIteratorSetter ? sourceProps.snapToStart
: convertRawProp(
context,
rawProps,
"snapToStart",
sourceProps.snapToStart,
true)),
snapToEnd(
CoreFeatures::enablePropIteratorSetter ? sourceProps.snapToEnd
: convertRawProp(
context,
rawProps,
"snapToEnd",
sourceProps.snapToEnd,
true)),
contentInsetAdjustmentBehavior(
CoreFeatures::enablePropIteratorSetter
? sourceProps.contentInsetAdjustmentBehavior
: convertRawProp(
context,
rawProps,
"contentInsetAdjustmentBehavior",
sourceProps.contentInsetAdjustmentBehavior,
{ContentInsetAdjustmentBehavior::Never})),
scrollToOverflowEnabled(
CoreFeatures::enablePropIteratorSetter
? sourceProps.scrollToOverflowEnabled
: convertRawProp(
context,
rawProps,
"scrollToOverflowEnabled",
sourceProps.scrollToOverflowEnabled,
{})),
isInvertedVirtualizedList(
CoreFeatures::enablePropIteratorSetter
? sourceProps.isInvertedVirtualizedList
: convertRawProp(
context,
rawProps,
"isInvertedVirtualizedList",
sourceProps.isInvertedVirtualizedList,
{})) {}
void ScrollViewProps::setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
const char* propName,
const RawValue& value) {
// All Props structs setProp methods must always, unconditionally,
// call all super::setProp methods, since multiple structs may
// reuse the same values.
ViewProps::setProp(context, hash, propName, value);
static auto defaults = ScrollViewProps{};
switch (hash) {
RAW_SET_PROP_SWITCH_CASE_BASIC(alwaysBounceHorizontal);
RAW_SET_PROP_SWITCH_CASE_BASIC(alwaysBounceVertical);
RAW_SET_PROP_SWITCH_CASE_BASIC(bounces);
RAW_SET_PROP_SWITCH_CASE_BASIC(bouncesZoom);
RAW_SET_PROP_SWITCH_CASE_BASIC(canCancelContentTouches);
RAW_SET_PROP_SWITCH_CASE_BASIC(centerContent);
RAW_SET_PROP_SWITCH_CASE_BASIC(automaticallyAdjustContentInsets);
RAW_SET_PROP_SWITCH_CASE_BASIC(automaticallyAdjustsScrollIndicatorInsets);
RAW_SET_PROP_SWITCH_CASE_BASIC(decelerationRate);
RAW_SET_PROP_SWITCH_CASE_BASIC(directionalLockEnabled);
RAW_SET_PROP_SWITCH_CASE_BASIC(indicatorStyle);
RAW_SET_PROP_SWITCH_CASE_BASIC(keyboardDismissMode);
RAW_SET_PROP_SWITCH_CASE_BASIC(maintainVisibleContentPosition);
RAW_SET_PROP_SWITCH_CASE_BASIC(maximumZoomScale);
RAW_SET_PROP_SWITCH_CASE_BASIC(minimumZoomScale);
RAW_SET_PROP_SWITCH_CASE_BASIC(scrollEnabled);
RAW_SET_PROP_SWITCH_CASE_BASIC(pagingEnabled);
RAW_SET_PROP_SWITCH_CASE_BASIC(pinchGestureEnabled);
RAW_SET_PROP_SWITCH_CASE_BASIC(scrollsToTop);
RAW_SET_PROP_SWITCH_CASE_BASIC(showsHorizontalScrollIndicator);
RAW_SET_PROP_SWITCH_CASE_BASIC(showsVerticalScrollIndicator);
RAW_SET_PROP_SWITCH_CASE_BASIC(scrollEventThrottle);
RAW_SET_PROP_SWITCH_CASE_BASIC(zoomScale);
RAW_SET_PROP_SWITCH_CASE_BASIC(contentInset);
RAW_SET_PROP_SWITCH_CASE_BASIC(contentOffset);
RAW_SET_PROP_SWITCH_CASE_BASIC(scrollIndicatorInsets);
RAW_SET_PROP_SWITCH_CASE_BASIC(snapToInterval);
RAW_SET_PROP_SWITCH_CASE_BASIC(snapToAlignment);
RAW_SET_PROP_SWITCH_CASE_BASIC(disableIntervalMomentum);
RAW_SET_PROP_SWITCH_CASE_BASIC(snapToOffsets);
RAW_SET_PROP_SWITCH_CASE_BASIC(snapToStart);
RAW_SET_PROP_SWITCH_CASE_BASIC(snapToEnd);
RAW_SET_PROP_SWITCH_CASE_BASIC(contentInsetAdjustmentBehavior);
RAW_SET_PROP_SWITCH_CASE_BASIC(scrollToOverflowEnabled);
RAW_SET_PROP_SWITCH_CASE_BASIC(isInvertedVirtualizedList);
}
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList ScrollViewProps::getDebugProps() const {
auto defaultScrollViewProps = ScrollViewProps{};
return ViewProps::getDebugProps() +
SharedDebugStringConvertibleList{
debugStringConvertibleItem(
"alwaysBounceHorizontal",
alwaysBounceHorizontal,
defaultScrollViewProps.alwaysBounceHorizontal),
debugStringConvertibleItem(
"alwaysBounceVertical",
alwaysBounceVertical,
defaultScrollViewProps.alwaysBounceVertical),
debugStringConvertibleItem(
"bounces", bounces, defaultScrollViewProps.bounces),
debugStringConvertibleItem(
"bouncesZoom", bouncesZoom, defaultScrollViewProps.bouncesZoom),
debugStringConvertibleItem(
"canCancelContentTouches",
canCancelContentTouches,
defaultScrollViewProps.canCancelContentTouches),
debugStringConvertibleItem(
"centerContent",
centerContent,
defaultScrollViewProps.centerContent),
debugStringConvertibleItem(
"automaticallyAdjustContentInsets",
automaticallyAdjustContentInsets,
defaultScrollViewProps.automaticallyAdjustContentInsets),
debugStringConvertibleItem(
"automaticallyAdjustsScrollIndicatorInsets",
automaticallyAdjustsScrollIndicatorInsets,
defaultScrollViewProps.automaticallyAdjustsScrollIndicatorInsets),
debugStringConvertibleItem(
"decelerationRate",
decelerationRate,
defaultScrollViewProps.decelerationRate),
debugStringConvertibleItem(
"directionalLockEnabled",
directionalLockEnabled,
defaultScrollViewProps.directionalLockEnabled),
debugStringConvertibleItem(
"indicatorStyle",
indicatorStyle,
defaultScrollViewProps.indicatorStyle),
debugStringConvertibleItem(
"keyboardDismissMode",
keyboardDismissMode,
defaultScrollViewProps.keyboardDismissMode),
debugStringConvertibleItem(
"maintainVisibleContentPosition",
maintainVisibleContentPosition,
defaultScrollViewProps.maintainVisibleContentPosition),
debugStringConvertibleItem(
"maximumZoomScale",
maximumZoomScale,
defaultScrollViewProps.maximumZoomScale),
debugStringConvertibleItem(
"minimumZoomScale",
minimumZoomScale,
defaultScrollViewProps.minimumZoomScale),
debugStringConvertibleItem(
"scrollEnabled",
scrollEnabled,
defaultScrollViewProps.scrollEnabled),
debugStringConvertibleItem(
"pagingEnabled",
pagingEnabled,
defaultScrollViewProps.pagingEnabled),
debugStringConvertibleItem(
"pinchGestureEnabled",
pinchGestureEnabled,
defaultScrollViewProps.pinchGestureEnabled),
debugStringConvertibleItem(
"scrollsToTop",
scrollsToTop,
defaultScrollViewProps.scrollsToTop),
debugStringConvertibleItem(
"showsHorizontalScrollIndicator",
showsHorizontalScrollIndicator,
defaultScrollViewProps.showsHorizontalScrollIndicator),
debugStringConvertibleItem(
"showsVerticalScrollIndicator",
showsVerticalScrollIndicator,
defaultScrollViewProps.showsVerticalScrollIndicator),
debugStringConvertibleItem(
"scrollEventThrottle",
scrollEventThrottle,
defaultScrollViewProps.scrollEventThrottle),
debugStringConvertibleItem(
"zoomScale", zoomScale, defaultScrollViewProps.zoomScale),
debugStringConvertibleItem(
"contentInset",
contentInset,
defaultScrollViewProps.contentInset),
debugStringConvertibleItem(
"contentOffset",
contentOffset,
defaultScrollViewProps.contentOffset),
debugStringConvertibleItem(
"scrollIndicatorInsets",
scrollIndicatorInsets,
defaultScrollViewProps.scrollIndicatorInsets),
debugStringConvertibleItem(
"snapToInterval",
snapToInterval,
defaultScrollViewProps.snapToInterval),
debugStringConvertibleItem(
"snapToAlignment",
snapToAlignment,
defaultScrollViewProps.snapToAlignment),
debugStringConvertibleItem(
"disableIntervalMomentum",
disableIntervalMomentum,
defaultScrollViewProps.disableIntervalMomentum),
debugStringConvertibleItem(
"snapToStart", snapToStart, defaultScrollViewProps.snapToStart),
debugStringConvertibleItem(
"snapToEnd", snapToEnd, defaultScrollViewProps.snapToEnd),
debugStringConvertibleItem(
"isInvertedVirtualizedList",
snapToEnd,
defaultScrollViewProps.isInvertedVirtualizedList)};
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,82 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/scrollview/primitives.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/PropsParserContext.h>
#include <optional>
namespace facebook::react {
// TODO (T28334063): Consider for codegen.
class ScrollViewProps final : public ViewProps {
public:
ScrollViewProps() = default;
ScrollViewProps(
const PropsParserContext& context,
const ScrollViewProps& sourceProps,
const RawProps& rawProps);
void setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
const char* propName,
const RawValue& value);
#pragma mark - Props
bool alwaysBounceHorizontal{};
bool alwaysBounceVertical{};
bool bounces{true};
bool bouncesZoom{true};
bool canCancelContentTouches{true};
bool centerContent{};
bool automaticallyAdjustContentInsets{};
bool automaticallyAdjustsScrollIndicatorInsets{true};
Float decelerationRate{0.998f};
Float endDraggingSensitivityMultiplier{1};
Float endDraggingSensitivityVelocityMultiplier{0};
bool directionalLockEnabled{};
ScrollViewIndicatorStyle indicatorStyle{};
ScrollViewKeyboardDismissMode keyboardDismissMode{};
std::optional<ScrollViewMaintainVisibleContentPosition>
maintainVisibleContentPosition{};
Float maximumZoomScale{1.0f};
Float minimumZoomScale{1.0f};
bool scrollEnabled{true};
bool pagingEnabled{};
bool pinchGestureEnabled{true};
bool scrollsToTop{true};
bool showsHorizontalScrollIndicator{true};
bool showsVerticalScrollIndicator{true};
Float scrollEventThrottle{};
Float zoomScale{1.0f};
EdgeInsets contentInset{};
Point contentOffset{};
EdgeInsets scrollIndicatorInsets{};
Float snapToInterval{};
ScrollViewSnapToAlignment snapToAlignment{};
bool disableIntervalMomentum{false};
std::vector<Float> snapToOffsets{};
bool snapToStart{true};
bool snapToEnd{true};
ContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior{
ContentInsetAdjustmentBehavior::Never};
bool scrollToOverflowEnabled{false};
bool isInvertedVirtualizedList{false};
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
};
} // namespace facebook::react

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ScrollViewShadowNode.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/core/LayoutMetrics.h>
namespace facebook::react {
const char ScrollViewComponentName[] = "ScrollView";
void ScrollViewShadowNode::updateStateIfNeeded() {
ensureUnsealed();
auto contentBoundingRect = Rect{};
for (const auto& childNode : getLayoutableChildNodes()) {
contentBoundingRect.unionInPlace(childNode->getLayoutMetrics().frame);
}
auto state = getStateData();
if (state.contentBoundingRect != contentBoundingRect) {
state.contentBoundingRect = contentBoundingRect;
setStateData(std::move(state));
}
}
void ScrollViewShadowNode::updateScrollContentOffsetIfNeeded() {
#ifndef ANDROID
if (getLayoutMetrics().layoutDirection == LayoutDirection::RightToLeft) {
// Yoga places `contentView` on the right side of `scrollView` when RTL
// layout is enforced. To correct for this, in RTL setting, correct the
// frame's origin. React Native Classic does this as well in
// `RCTScrollContentShadowView.m`.
for (auto layoutableNode : getLayoutableChildNodes()) {
auto layoutMetrics = layoutableNode->getLayoutMetrics();
if (layoutMetrics.frame.origin.x != 0) {
layoutMetrics.frame.origin.x = 0;
layoutableNode->setLayoutMetrics(layoutMetrics);
}
}
}
#endif
}
ScrollViewState ScrollViewShadowNode::initialStateData(
const Props::Shared& props,
const ShadowNodeFamily::Shared& /*family*/,
const ComponentDescriptor& /*componentDescriptor*/) {
return {static_cast<const ScrollViewProps&>(*props).contentOffset, {}, 0};
}
#pragma mark - LayoutableShadowNode
void ScrollViewShadowNode::layout(LayoutContext layoutContext) {
ConcreteViewShadowNode::layout(layoutContext);
updateScrollContentOffsetIfNeeded();
updateStateIfNeeded();
}
Point ScrollViewShadowNode::getContentOriginOffset() const {
auto stateData = getStateData();
auto contentOffset = stateData.contentOffset;
return {-contentOffset.x, -contentOffset.y + stateData.scrollAwayPaddingTop};
}
} // namespace facebook::react

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/scrollview/ScrollViewEventEmitter.h>
#include <react/renderer/components/scrollview/ScrollViewProps.h>
#include <react/renderer/components/scrollview/ScrollViewState.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/core/LayoutContext.h>
#include <react/renderer/core/ShadowNodeFamily.h>
namespace facebook::react {
extern const char ScrollViewComponentName[];
/*
* `ShadowNode` for <ScrollView> component.
*/
class ScrollViewShadowNode final : public ConcreteViewShadowNode<
ScrollViewComponentName,
ScrollViewProps,
ScrollViewEventEmitter,
ScrollViewState> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
static ScrollViewState initialStateData(
const Props::Shared& props,
const ShadowNodeFamily::Shared& family,
const ComponentDescriptor& componentDescriptor);
#pragma mark - LayoutableShadowNode
void layout(LayoutContext layoutContext) override;
Point getContentOriginOffset() const override;
private:
void updateStateIfNeeded();
void updateScrollContentOffsetIfNeeded();
};
} // namespace facebook::react

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ScrollViewState.h"
namespace facebook::react {
ScrollViewState::ScrollViewState(
Point contentOffset,
Rect contentBoundingRect,
int scrollAwayPaddingTop)
: contentOffset(contentOffset),
contentBoundingRect(contentBoundingRect),
scrollAwayPaddingTop(scrollAwayPaddingTop) {}
Size ScrollViewState::getContentSize() const {
return contentBoundingRect.size;
}
} // namespace facebook::react

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/graphics/Float.h>
#include <react/renderer/graphics/Point.h>
#include <react/renderer/graphics/Rect.h>
#include <react/renderer/graphics/Size.h>
#ifdef ANDROID
#include <folly/dynamic.h>
#include <react/renderer/mapbuffer/MapBuffer.h>
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
#endif
namespace facebook::react {
/*
* State for <ScrollView> component.
*/
class ScrollViewState final {
public:
ScrollViewState(
Point contentOffset,
Rect contentBoundingRect,
int scrollAwayPaddingTop);
ScrollViewState() = default;
Point contentOffset;
Rect contentBoundingRect;
int scrollAwayPaddingTop;
/*
* Returns size of scrollable area.
*/
Size getContentSize() const;
#ifdef ANDROID
ScrollViewState(const ScrollViewState& previousState, folly::dynamic data)
: contentOffset(
{(Float)data["contentOffsetLeft"].getDouble(),
(Float)data["contentOffsetTop"].getDouble()}),
contentBoundingRect({}),
scrollAwayPaddingTop((Float)data["scrollAwayPaddingTop"].getDouble()){};
folly::dynamic getDynamic() const {
return folly::dynamic::object("contentOffsetLeft", contentOffset.x)(
"contentOffsetTop", contentOffset.y)(
"scrollAwayPaddingTop", scrollAwayPaddingTop);
};
MapBuffer getMapBuffer() const {
return MapBufferBuilder::EMPTY();
};
#endif
};
} // namespace facebook::react

View File

@@ -0,0 +1,182 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <folly/dynamic.h>
#include <react/renderer/components/scrollview/primitives.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/propsConversions.h>
#include <unordered_map>
namespace facebook::react {
inline void fromRawValue(
const PropsParserContext& context,
const RawValue& value,
ScrollViewSnapToAlignment& result) {
auto string = (std::string)value;
if (string == "start") {
result = ScrollViewSnapToAlignment::Start;
return;
}
if (string == "center") {
result = ScrollViewSnapToAlignment::Center;
return;
}
if (string == "end") {
result = ScrollViewSnapToAlignment::End;
return;
}
abort();
}
inline void fromRawValue(
const PropsParserContext& context,
const RawValue& value,
ScrollViewIndicatorStyle& result) {
auto string = (std::string)value;
if (string == "default") {
result = ScrollViewIndicatorStyle::Default;
return;
}
if (string == "black") {
result = ScrollViewIndicatorStyle::Black;
return;
}
if (string == "white") {
result = ScrollViewIndicatorStyle::White;
return;
}
abort();
}
inline void fromRawValue(
const PropsParserContext& context,
const RawValue& value,
ScrollViewKeyboardDismissMode& result) {
auto string = (std::string)value;
if (string == "none") {
result = ScrollViewKeyboardDismissMode::None;
return;
}
if (string == "on-drag") {
result = ScrollViewKeyboardDismissMode::OnDrag;
return;
}
if (string == "interactive") {
result = ScrollViewKeyboardDismissMode::Interactive;
return;
}
abort();
}
inline void fromRawValue(
const PropsParserContext& context,
const RawValue& value,
ContentInsetAdjustmentBehavior& result) {
auto string = (std::string)value;
if (string == "never") {
result = ContentInsetAdjustmentBehavior::Never;
return;
}
if (string == "automatic") {
result = ContentInsetAdjustmentBehavior::Automatic;
return;
}
if (string == "scrollableAxes") {
result = ContentInsetAdjustmentBehavior::ScrollableAxes;
return;
}
if (string == "always") {
result = ContentInsetAdjustmentBehavior::Always;
return;
}
abort();
}
inline void fromRawValue(
const PropsParserContext& context,
const RawValue& value,
ScrollViewMaintainVisibleContentPosition& result) {
auto map = (std::unordered_map<std::string, RawValue>)value;
auto minIndexForVisible = map.find("minIndexForVisible");
if (minIndexForVisible != map.end()) {
fromRawValue(
context, minIndexForVisible->second, result.minIndexForVisible);
}
auto autoscrollToTopThreshold = map.find("autoscrollToTopThreshold");
if (autoscrollToTopThreshold != map.end()) {
fromRawValue(
context,
autoscrollToTopThreshold->second,
result.autoscrollToTopThreshold);
}
}
inline std::string toString(const ScrollViewSnapToAlignment& value) {
switch (value) {
case ScrollViewSnapToAlignment::Start:
return "start";
case ScrollViewSnapToAlignment::Center:
return "center";
case ScrollViewSnapToAlignment::End:
return "end";
}
}
#if RN_DEBUG_STRING_CONVERTIBLE
inline std::string toString(const ScrollViewIndicatorStyle& value) {
switch (value) {
case ScrollViewIndicatorStyle::Default:
return "default";
case ScrollViewIndicatorStyle::Black:
return "black";
case ScrollViewIndicatorStyle::White:
return "white";
}
}
inline std::string toString(const ScrollViewKeyboardDismissMode& value) {
switch (value) {
case ScrollViewKeyboardDismissMode::None:
return "none";
case ScrollViewKeyboardDismissMode::OnDrag:
return "on-drag";
case ScrollViewKeyboardDismissMode::Interactive:
return "interactive";
}
}
inline std::string toString(const ContentInsetAdjustmentBehavior& value) {
switch (value) {
case ContentInsetAdjustmentBehavior::Never:
return "never";
case ContentInsetAdjustmentBehavior::Automatic:
return "automatic";
case ContentInsetAdjustmentBehavior::ScrollableAxes:
return "scrollableAxes";
case ContentInsetAdjustmentBehavior::Always:
return "always";
}
}
inline std::string toString(
const std::optional<ScrollViewMaintainVisibleContentPosition>& value) {
if (!value) {
return "null";
}
return "{minIndexForVisible: " + toString(value.value().minIndexForVisible) +
", autoscrollToTopThreshold: " +
toString(value.value().autoscrollToTopThreshold) + "}";
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <optional>
#include <tuple>
namespace facebook::react {
enum class ScrollViewSnapToAlignment { Start, Center, End };
enum class ScrollViewIndicatorStyle { Default, Black, White };
enum class ScrollViewKeyboardDismissMode { None, OnDrag, Interactive };
enum class ContentInsetAdjustmentBehavior {
Never,
Automatic,
ScrollableAxes,
Always
};
class ScrollViewMaintainVisibleContentPosition final {
public:
int minIndexForVisible{0};
std::optional<int> autoscrollToTopThreshold{};
bool operator==(const ScrollViewMaintainVisibleContentPosition& rhs) const {
return std::tie(this->minIndexForVisible, this->autoscrollToTopThreshold) ==
std::tie(rhs.minIndexForVisible, rhs.autoscrollToTopThreshold);
}
bool operator!=(const ScrollViewMaintainVisibleContentPosition& rhs) const {
return !(*this == rhs);
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <memory>
#include <gtest/gtest.h>
TEST(ScrollViewTest, testSomething) {
// TODO
}

View File

@@ -0,0 +1,45 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
file(GLOB rrc_switch_SRCS CONFIGURE_DEPENDS androidswitch/react/renderer/components/androidswitch/*.cpp)
add_library(
rrc_switch
STATIC
${rrc_switch_SRCS}
)
target_include_directories(rrc_switch PUBLIC androidswitch/)
target_link_libraries(
rrc_switch
glog
fbjni
folly_runtime
glog_init
react_codegen_rncore
react_debug
react_render_componentregistry
react_render_core
react_render_debug
react_render_graphics
react_render_uimanager
reactnativejni
rrc_view
yoga
)
target_compile_options(
rrc_switch
PRIVATE
-DLOG_TAG=\"Fabric\"
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
)

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 "AndroidSwitchMeasurementsManager.h"
#include "AndroidSwitchShadowNode.h"
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
/*
* Descriptor for <AndroidSwitch> component.
*/
class AndroidSwitchComponentDescriptor final
: public ConcreteComponentDescriptor<AndroidSwitchShadowNode> {
public:
AndroidSwitchComponentDescriptor(
const ComponentDescriptorParameters& parameters)
: ConcreteComponentDescriptor(parameters),
measurementsManager_(std::make_shared<AndroidSwitchMeasurementsManager>(
contextContainer_)) {}
void adopt(ShadowNode& shadowNode) const override {
ConcreteComponentDescriptor::adopt(shadowNode);
auto& androidSwitchShadowNode =
static_cast<AndroidSwitchShadowNode&>(shadowNode);
// `AndroidSwitchShadowNode` uses `AndroidSwitchMeasurementsManager` to
// provide measurements to Yoga.
androidSwitchShadowNode.setAndroidSwitchMeasurementsManager(
measurementsManager_);
// All `AndroidSwitchShadowNode`s must have leaf Yoga nodes with properly
// setup measure function.
androidSwitchShadowNode.enableMeasurement();
}
private:
const std::shared_ptr<AndroidSwitchMeasurementsManager> measurementsManager_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,66 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "AndroidSwitchMeasurementsManager.h"
#include <fbjni/fbjni.h>
#include <react/jni/ReadableNativeMap.h>
#include <react/renderer/core/conversions.h>
using namespace facebook::jni;
namespace facebook::react {
Size AndroidSwitchMeasurementsManager::measure(
SurfaceId surfaceId,
LayoutConstraints layoutConstraints) const {
{
std::scoped_lock lock(mutex_);
if (hasBeenMeasured_) {
return cachedMeasurement_;
}
}
const jni::global_ref<jobject>& fabricUIManager =
contextContainer_->at<jni::global_ref<jobject>>("FabricUIManager");
static auto measure =
jni::findClassStatic("com/facebook/react/fabric/FabricUIManager")
->getMethod<jlong(
jint,
jstring,
ReadableMap::javaobject,
ReadableMap::javaobject,
ReadableMap::javaobject,
jfloat,
jfloat,
jfloat,
jfloat)>("measure");
auto minimumSize = layoutConstraints.minimumSize;
auto maximumSize = layoutConstraints.maximumSize;
local_ref<JString> componentName = make_jstring("AndroidSwitch");
auto measurement = yogaMeassureToSize(measure(
fabricUIManager,
surfaceId,
componentName.get(),
nullptr,
nullptr,
nullptr,
minimumSize.width,
maximumSize.width,
minimumSize.height,
maximumSize.height));
std::scoped_lock lock(mutex_);
cachedMeasurement_ = measurement;
return measurement;
}
} // namespace facebook::react

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/core/LayoutConstraints.h>
#include <react/utils/ContextContainer.h>
namespace facebook::react {
class AndroidSwitchMeasurementsManager {
public:
AndroidSwitchMeasurementsManager(
const ContextContainer::Shared& contextContainer)
: contextContainer_(contextContainer) {}
Size measure(SurfaceId surfaceId, LayoutConstraints layoutConstraints) const;
private:
const ContextContainer::Shared contextContainer_;
mutable std::mutex mutex_;
mutable bool hasBeenMeasured_ = false;
mutable Size cachedMeasurement_{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "AndroidSwitchShadowNode.h"
namespace facebook::react {
extern const char AndroidSwitchComponentName[] = "AndroidSwitch";
void AndroidSwitchShadowNode::setAndroidSwitchMeasurementsManager(
const std::shared_ptr<AndroidSwitchMeasurementsManager>&
measurementsManager) {
ensureUnsealed();
measurementsManager_ = measurementsManager;
}
#pragma mark - LayoutableShadowNode
Size AndroidSwitchShadowNode::measureContent(
const LayoutContext& /*layoutContext*/,
const LayoutConstraints& layoutConstraints) const {
return measurementsManager_->measure(getSurfaceId(), layoutConstraints);
}
} // namespace facebook::react

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 "AndroidSwitchMeasurementsManager.h"
#include <react/renderer/components/rncore/EventEmitters.h>
#include <react/renderer/components/rncore/Props.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
namespace facebook::react {
extern const char AndroidSwitchComponentName[];
/*
* `ShadowNode` for <AndroidSwitch> component.
*/
class AndroidSwitchShadowNode final : public ConcreteViewShadowNode<
AndroidSwitchComponentName,
AndroidSwitchProps,
AndroidSwitchEventEmitter> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
// Associates a shared `AndroidSwitchMeasurementsManager` with the node.
void setAndroidSwitchMeasurementsManager(
const std::shared_ptr<AndroidSwitchMeasurementsManager>&
measurementsManager);
#pragma mark - LayoutableShadowNode
Size measureContent(
const LayoutContext& layoutContext,
const LayoutConstraints& layoutConstraints) const override;
private:
std::shared_ptr<AndroidSwitchMeasurementsManager> measurementsManager_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,327 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "BaseTextProps.h"
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/renderer/core/propsConversions.h>
#include <react/renderer/debug/DebugStringConvertibleItem.h>
#include <react/utils/CoreFeatures.h>
namespace facebook::react {
static TextAttributes convertRawProp(
const PropsParserContext& context,
const RawProps& rawProps,
const TextAttributes& sourceTextAttributes,
const TextAttributes& defaultTextAttributes) {
auto textAttributes = TextAttributes{};
// Color (not accessed by ViewProps)
textAttributes.foregroundColor = convertRawProp(
context,
rawProps,
"color",
sourceTextAttributes.foregroundColor,
defaultTextAttributes.foregroundColor);
// Font
textAttributes.fontFamily = convertRawProp(
context,
rawProps,
"fontFamily",
sourceTextAttributes.fontFamily,
defaultTextAttributes.fontFamily);
textAttributes.fontSize = convertRawProp(
context,
rawProps,
"fontSize",
sourceTextAttributes.fontSize,
defaultTextAttributes.fontSize);
textAttributes.fontSizeMultiplier = convertRawProp(
context,
rawProps,
"fontSizeMultiplier",
sourceTextAttributes.fontSizeMultiplier,
defaultTextAttributes.fontSizeMultiplier);
textAttributes.fontWeight = convertRawProp(
context,
rawProps,
"fontWeight",
sourceTextAttributes.fontWeight,
defaultTextAttributes.fontWeight);
textAttributes.fontStyle = convertRawProp(
context,
rawProps,
"fontStyle",
sourceTextAttributes.fontStyle,
defaultTextAttributes.fontStyle);
textAttributes.fontVariant = convertRawProp(
context,
rawProps,
"fontVariant",
sourceTextAttributes.fontVariant,
defaultTextAttributes.fontVariant);
textAttributes.allowFontScaling = convertRawProp(
context,
rawProps,
"allowFontScaling",
sourceTextAttributes.allowFontScaling,
defaultTextAttributes.allowFontScaling);
textAttributes.dynamicTypeRamp = convertRawProp(
context,
rawProps,
"dynamicTypeRamp",
sourceTextAttributes.dynamicTypeRamp,
defaultTextAttributes.dynamicTypeRamp);
textAttributes.letterSpacing = convertRawProp(
context,
rawProps,
"letterSpacing",
sourceTextAttributes.letterSpacing,
defaultTextAttributes.letterSpacing);
textAttributes.textTransform = convertRawProp(
context,
rawProps,
"textTransform",
sourceTextAttributes.textTransform,
defaultTextAttributes.textTransform);
// Paragraph
textAttributes.lineHeight = convertRawProp(
context,
rawProps,
"lineHeight",
sourceTextAttributes.lineHeight,
defaultTextAttributes.lineHeight);
textAttributes.alignment = convertRawProp(
context,
rawProps,
"textAlign",
sourceTextAttributes.alignment,
defaultTextAttributes.alignment);
textAttributes.baseWritingDirection = convertRawProp(
context,
rawProps,
"baseWritingDirection",
sourceTextAttributes.baseWritingDirection,
defaultTextAttributes.baseWritingDirection);
textAttributes.lineBreakStrategy = convertRawProp(
context,
rawProps,
"lineBreakStrategyIOS",
sourceTextAttributes.lineBreakStrategy,
defaultTextAttributes.lineBreakStrategy);
// Decoration
textAttributes.textDecorationColor = convertRawProp(
context,
rawProps,
"textDecorationColor",
sourceTextAttributes.textDecorationColor,
defaultTextAttributes.textDecorationColor);
textAttributes.textDecorationLineType = convertRawProp(
context,
rawProps,
"textDecorationLine",
sourceTextAttributes.textDecorationLineType,
defaultTextAttributes.textDecorationLineType);
textAttributes.textDecorationStyle = convertRawProp(
context,
rawProps,
"textDecorationStyle",
sourceTextAttributes.textDecorationStyle,
defaultTextAttributes.textDecorationStyle);
// Shadow
textAttributes.textShadowOffset = convertRawProp(
context,
rawProps,
"textShadowOffset",
sourceTextAttributes.textShadowOffset,
defaultTextAttributes.textShadowOffset);
textAttributes.textShadowRadius = convertRawProp(
context,
rawProps,
"textShadowRadius",
sourceTextAttributes.textShadowRadius,
defaultTextAttributes.textShadowRadius);
textAttributes.textShadowColor = convertRawProp(
context,
rawProps,
"textShadowColor",
sourceTextAttributes.textShadowColor,
defaultTextAttributes.textShadowColor);
// Special
textAttributes.isHighlighted = convertRawProp(
context,
rawProps,
"isHighlighted",
sourceTextAttributes.isHighlighted,
defaultTextAttributes.isHighlighted);
textAttributes.isPressable = convertRawProp(
context,
rawProps,
"isPressable",
sourceTextAttributes.isPressable,
defaultTextAttributes.isPressable);
// In general, we want this class to access props in the same order
// that ViewProps accesses them in, so that RawPropParser can optimize
// accesses. This is both theoretical, and ParagraphProps takes advantage
// of this.
// In particular: accessibilityRole, opacity, and backgroundColor also
// are parsed first by ViewProps (and indirectly AccessibilityProps).
// However, since RawPropsParser will always store these props /before/
// the unique BaseTextProps props, it is most efficient to parse these, in
// order, /after/ all of the other BaseTextProps, so that the RawPropsParser
// index rolls over only once instead of twice.
textAttributes.accessibilityRole = convertRawProp(
context,
rawProps,
"accessibilityRole",
sourceTextAttributes.accessibilityRole,
defaultTextAttributes.accessibilityRole);
textAttributes.role = convertRawProp(
context,
rawProps,
"role",
sourceTextAttributes.role,
defaultTextAttributes.role);
// Color (accessed in this order by ViewProps)
textAttributes.opacity = convertRawProp(
context,
rawProps,
"opacity",
sourceTextAttributes.opacity,
defaultTextAttributes.opacity);
textAttributes.backgroundColor = convertRawProp(
context,
rawProps,
"backgroundColor",
sourceTextAttributes.backgroundColor,
defaultTextAttributes.backgroundColor);
return textAttributes;
}
BaseTextProps::BaseTextProps(
const PropsParserContext& context,
const BaseTextProps& sourceProps,
const RawProps& rawProps)
: textAttributes(
CoreFeatures::enablePropIteratorSetter
? sourceProps.textAttributes
: convertRawProp(
context,
rawProps,
sourceProps.textAttributes,
TextAttributes{})){};
void BaseTextProps::setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
const char* /*propName*/,
const RawValue& value) {
static auto defaults = TextAttributes{};
switch (hash) {
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, foregroundColor, "color");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, fontFamily, "fontFamily");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, fontSize, "fontSize");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
textAttributes,
fontSizeMultiplier,
"fontSizeMultiplier");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, fontWeight, "fontWeight");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, fontStyle, "fontStyle");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, fontVariant, "fontVariant");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, allowFontScaling, "allowFontScaling");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, letterSpacing, "letterSpacing");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, textTransform, "textTransform");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, lineHeight, "lineHeight");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, alignment, "textAlign");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
textAttributes,
baseWritingDirection,
"baseWritingDirection");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
textAttributes,
lineBreakStrategy,
"lineBreakStrategyIOS");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
textAttributes,
textDecorationColor,
"textDecorationColor");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
textAttributes,
textDecorationLineType,
"textDecorationLine");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
textAttributes,
textDecorationStyle,
"textDecorationStyle");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, textShadowOffset, "textShadowOffset");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, textShadowRadius, "textShadowRadius");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, textShadowColor, "textShadowColor");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, isHighlighted, "isHighlighted");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, isPressable, "isPressable");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
textAttributes,
accessibilityRole,
"accessibilityRole");
REBUILD_FIELD_SWITCH_CASE(defaults, value, textAttributes, role, "role");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, opacity, "opacity");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, backgroundColor, "backgroundColor");
}
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList BaseTextProps::getDebugProps() const {
return textAttributes.getDebugProps();
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/attributedstring/TextAttributes.h>
#include <react/renderer/core/Props.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/graphics/Color.h>
namespace facebook::react {
/*
* `Props`-like class which is used as a base class for all Props classes
* that can have text attributes (such as Text and Paragraph).
*/
class BaseTextProps {
public:
BaseTextProps() = default;
BaseTextProps(
const PropsParserContext& context,
const BaseTextProps& sourceProps,
const RawProps& rawProps);
void setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
const char* propName,
const RawValue& value);
#pragma mark - Props
TextAttributes textAttributes{};
#pragma mark - DebugStringConvertible (partially)
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const;
#endif
};
} // namespace facebook::react

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "BaseTextShadowNode.h"
#include <react/renderer/components/text/RawTextProps.h>
#include <react/renderer/components/text/RawTextShadowNode.h>
#include <react/renderer/components/text/TextProps.h>
#include <react/renderer/components/text/TextShadowNode.h>
#include <react/renderer/mounting/ShadowView.h>
namespace facebook::react {
inline ShadowView shadowViewFromShadowNode(const ShadowNode& shadowNode) {
auto shadowView = ShadowView{shadowNode};
// Clearing `props` and `state` (which we don't use) allows avoiding retain
// cycles.
shadowView.props = nullptr;
shadowView.state = nullptr;
return shadowView;
}
void BaseTextShadowNode::buildAttributedString(
const TextAttributes& baseTextAttributes,
const ShadowNode& parentNode,
AttributedString& outAttributedString,
Attachments& outAttachments) {
for (const auto& childNode : parentNode.getChildren()) {
// RawShadowNode
auto rawTextShadowNode =
dynamic_cast<const RawTextShadowNode*>(childNode.get());
if (rawTextShadowNode != nullptr) {
auto fragment = AttributedString::Fragment{};
fragment.string = rawTextShadowNode->getConcreteProps().text;
fragment.textAttributes = baseTextAttributes;
// Storing a retaining pointer to `ParagraphShadowNode` inside
// `attributedString` causes a retain cycle (besides that fact that we
// don't need it at all). Storing a `ShadowView` instance instead of
// `ShadowNode` should properly fix this problem.
fragment.parentShadowView = shadowViewFromShadowNode(parentNode);
outAttributedString.appendFragment(fragment);
continue;
}
// TextShadowNode
auto textShadowNode = dynamic_cast<const TextShadowNode*>(childNode.get());
if (textShadowNode != nullptr) {
auto localTextAttributes = baseTextAttributes;
localTextAttributes.apply(
textShadowNode->getConcreteProps().textAttributes);
buildAttributedString(
localTextAttributes,
*textShadowNode,
outAttributedString,
outAttachments);
continue;
}
// Any *other* kind of ShadowNode
auto fragment = AttributedString::Fragment{};
fragment.string = AttributedString::Fragment::AttachmentCharacter();
fragment.parentShadowView = shadowViewFromShadowNode(*childNode);
fragment.textAttributes = baseTextAttributes;
outAttributedString.appendFragment(fragment);
outAttachments.push_back(Attachment{
childNode.get(), outAttributedString.getFragments().size() - 1});
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,68 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/attributedstring/AttributedString.h>
#include <react/renderer/attributedstring/TextAttributes.h>
namespace facebook::react {
/*
* Base class (one of) for shadow nodes that represents attributed text,
* such as Text and Paragraph (but not RawText).
*/
class BaseTextShadowNode {
public:
/*
* Represents additional information associated with some fragments which
* represent embedded into text component (such as an image or inline view).
*/
class Attachment final {
public:
/*
* Unowning pointer to a `ShadowNode` that represents the attachment.
* Cannot be `null`.
*/
const ShadowNode* shadowNode;
/*
* Index of the fragment in `AttributedString` that represents the
* the attachment.
*/
size_t fragmentIndex;
};
/*
* A list of `Attachment`s.
* Performance-wise, the prevailing case is when there are no attachments
* at all, therefore we don't need an inline buffer (`small_vector`).
*/
using Attachments = std::vector<Attachment>;
/*
* Builds an `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.
*/
static void buildAttributedString(
const TextAttributes& baseTextAttributes,
const ShadowNode& parentNode,
AttributedString& outAttributedString,
Attachments& outAttachments);
/**
* Returns a character used to measure empty strings in native platforms.
*/
inline static std::string getEmptyPlaceholder() {
return "I";
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,39 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB rrc_text_SRC CONFIGURE_DEPENDS *.cpp)
add_library(rrc_text STATIC ${rrc_text_SRC})
target_include_directories(rrc_text PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(rrc_text
glog
folly_runtime
glog_init
jsi
react_debug
react_render_attributedstring
react_render_core
react_render_debug
react_render_graphics
react_render_mapbuffer
react_render_mounting
react_render_textlayoutmanager
react_render_uimanager
react_utils
rrc_view
yoga
)

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/text/ParagraphShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/textlayoutmanager/TextLayoutManager.h>
#include <react/utils/ContextContainer.h>
namespace facebook::react {
/*
* Descriptor for <Paragraph> component.
*/
class ParagraphComponentDescriptor final
: public ConcreteComponentDescriptor<ParagraphShadowNode> {
public:
ParagraphComponentDescriptor(const ComponentDescriptorParameters& parameters)
: ConcreteComponentDescriptor<ParagraphShadowNode>(parameters) {
// Every single `ParagraphShadowNode` will have a reference to
// a shared `TextLayoutManager`.
textLayoutManager_ = std::make_shared<TextLayoutManager>(contextContainer_);
}
protected:
void adopt(ShadowNode& shadowNode) const override {
ConcreteComponentDescriptor::adopt(shadowNode);
auto& paragraphShadowNode = static_cast<ParagraphShadowNode&>(shadowNode);
// `ParagraphShadowNode` uses `TextLayoutManager` to measure text content
// and communicate text rendering metrics to mounting layer.
paragraphShadowNode.setTextLayoutManager(textLayoutManager_);
}
private:
std::shared_ptr<const TextLayoutManager> textLayoutManager_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ParagraphEventEmitter.h"
namespace facebook::react {
static jsi::Value linesMeasurementsPayload(
jsi::Runtime& runtime,
const LinesMeasurements& linesMeasurements) {
auto payload = jsi::Object(runtime);
auto lines = jsi::Array(runtime, linesMeasurements.size());
for (size_t i = 0; i < linesMeasurements.size(); ++i) {
const auto& lineMeasurement = linesMeasurements[i];
auto jsiLine = jsi::Object(runtime);
jsiLine.setProperty(runtime, "text", lineMeasurement.text);
jsiLine.setProperty(runtime, "x", lineMeasurement.frame.origin.x);
jsiLine.setProperty(runtime, "y", lineMeasurement.frame.origin.y);
jsiLine.setProperty(runtime, "width", lineMeasurement.frame.size.width);
jsiLine.setProperty(runtime, "height", lineMeasurement.frame.size.height);
jsiLine.setProperty(runtime, "descender", lineMeasurement.descender);
jsiLine.setProperty(runtime, "capHeight", lineMeasurement.capHeight);
jsiLine.setProperty(runtime, "ascender", lineMeasurement.ascender);
jsiLine.setProperty(runtime, "xHeight", lineMeasurement.xHeight);
lines.setValueAtIndex(runtime, i, jsiLine);
}
payload.setProperty(runtime, "lines", lines);
return payload;
}
void ParagraphEventEmitter::onTextLayout(
const LinesMeasurements& linesMeasurements) const {
{
std::scoped_lock guard(linesMeasurementsMutex_);
if (linesMeasurementsMetrics_ == linesMeasurements) {
return;
}
linesMeasurementsMetrics_ = linesMeasurements;
}
dispatchEvent("textLayout", [linesMeasurements](jsi::Runtime& runtime) {
return linesMeasurementsPayload(runtime, linesMeasurements);
});
}
} // namespace facebook::react

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/view/ViewEventEmitter.h>
#include <react/renderer/textlayoutmanager/TextMeasureCache.h>
namespace facebook::react {
class ParagraphEventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
void onTextLayout(const LinesMeasurements& linesMeasurements) const;
private:
mutable std::mutex linesMeasurementsMutex_;
mutable LinesMeasurements linesMeasurementsMetrics_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,100 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ParagraphLayoutManager.h"
#include <react/utils/CoreFeatures.h>
#include <react/utils/hash_combine.h>
namespace facebook::react {
TextMeasurement ParagraphLayoutManager::measure(
const AttributedString& attributedString,
const ParagraphAttributes& paragraphAttributes,
const TextLayoutContext& layoutContext,
LayoutConstraints layoutConstraints) const {
if (CoreFeatures::cacheLastTextMeasurement) {
bool shouldMeasure = shouldMeasureString(
attributedString, paragraphAttributes, layoutConstraints);
if (shouldMeasure) {
cachedTextMeasurement_ = textLayoutManager_->measure(
AttributedStringBox(attributedString),
paragraphAttributes,
layoutContext,
layoutConstraints,
hostTextStorage_);
lastAvailableWidth_ = layoutConstraints.maximumSize.width;
}
return cachedTextMeasurement_;
} else {
return textLayoutManager_->measure(
AttributedStringBox(attributedString),
paragraphAttributes,
layoutContext,
layoutConstraints,
nullptr);
}
}
bool ParagraphLayoutManager::shouldMeasureString(
const AttributedString& attributedString,
const ParagraphAttributes& paragraphAttributes,
LayoutConstraints layoutConstraints) const {
size_t newParagraphInputHash =
hash_combine(attributedString, paragraphAttributes);
if (newParagraphInputHash != paragraphInputHash_) {
// AttributedString or ParagraphAttributes have changed.
// Must create new host text storage and trigger measure.
hostTextStorage_ = textLayoutManager_->getHostTextStorage(
attributedString, paragraphAttributes, layoutConstraints);
paragraphInputHash_ = newParagraphInputHash;
return true; // Must measure again.
}
// Detect the case when available width for Paragraph meaningfully changes.
// This is to prevent unnecessary re-creation of NSTextStorage on iOS.
// On Android, this is no-op.
bool hasMaximumSizeChanged =
layoutConstraints.maximumSize.width != lastAvailableWidth_;
Float threshold = 0.01f;
bool doesMaximumSizeMatchLastMeasurement =
std::abs(
layoutConstraints.maximumSize.width -
cachedTextMeasurement_.size.width) < threshold;
if (hasMaximumSizeChanged && !doesMaximumSizeMatchLastMeasurement) {
hostTextStorage_ = textLayoutManager_->getHostTextStorage(
attributedString, paragraphAttributes, layoutConstraints);
return true;
}
return false;
}
LinesMeasurements ParagraphLayoutManager::measureLines(
const AttributedString& attributedString,
const ParagraphAttributes& paragraphAttributes,
Size size) const {
return textLayoutManager_->measureLines(
attributedString, paragraphAttributes, size);
}
void ParagraphLayoutManager::setTextLayoutManager(
std::shared_ptr<const TextLayoutManager> textLayoutManager) const {
textLayoutManager_ = std::move(textLayoutManager);
}
std::shared_ptr<const TextLayoutManager>
ParagraphLayoutManager::getTextLayoutManager() const {
return textLayoutManager_;
}
std::shared_ptr<void> ParagraphLayoutManager::getHostTextStorage() const {
return hostTextStorage_;
}
} // namespace facebook::react

View File

@@ -0,0 +1,94 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/attributedstring/AttributedString.h>
#include <react/renderer/attributedstring/ParagraphAttributes.h>
#include <react/renderer/core/LayoutConstraints.h>
#include <react/renderer/textlayoutmanager/TextLayoutContext.h>
#include <react/renderer/textlayoutmanager/TextLayoutManager.h>
namespace facebook::react {
/*
* Serves as a middle man between `ParagraphShadowNode` and `TextLayoutManager`.
* On iOS, caches `NSTextStorage` for individual `ParagraphShadowNode` to make
* sure only one `NSTextStorage` is created for every string. `NSTextStorage`
* can be re created on native views layer but it is expensive. On Android, this
* class does not cache anything.
*/
class ParagraphLayoutManager {
public:
TextMeasurement measure(
const AttributedString& attributedString,
const ParagraphAttributes& paragraphAttributes,
const TextLayoutContext& layoutContext,
LayoutConstraints layoutConstraints) const;
LinesMeasurements measureLines(
const AttributedString& attributedString,
const ParagraphAttributes& paragraphAttributes,
Size size) const;
void setTextLayoutManager(
std::shared_ptr<const TextLayoutManager> textLayoutManager) const;
/*
* Returns an opaque pointer to platform-specific `TextLayoutManager`.
* Is used on a native views layer to delegate text rendering to the manager.
*/
std::shared_ptr<const TextLayoutManager> getTextLayoutManager() const;
/*
* Returns opaque shared_ptr holding `NSTextStorage`.
* May be nullptr.
* Is used on a native views layer to prevent `NSTextStorage` from being
* created twice.
*/
std::shared_ptr<void> getHostTextStorage() const;
private:
std::shared_ptr<const TextLayoutManager> mutable textLayoutManager_{};
/*
* Stores opaque pointer to `NSTextStorage` on iOS. nullptr on Android.
* TODO: In the future, we may want to cache Android's text storage.
*/
std::shared_ptr<void> mutable hostTextStorage_{};
/*
* Hash of AttributedString and ParagraphAttributes last used to
* measure. Result of that measure is stored in cachedTextMeasurement_.
* The available width defined for the measurement is stored in
* lastAvailableWidth_.
*/
size_t mutable paragraphInputHash_{};
/* The width Yoga set as maximum width.
* Yoga calls measure twice with two
* different maximum width. One of available space.
* The other one is exact space needed for the string.
* This happens when node is dirtied but its size is not affected.
* To deal with this inefficiency, we cache `TextMeasurement` for each
* `ParagraphShadowNode`. If Yoga tries to re-measure with available width
* or exact width, we provide it with the cached value.
*/
Float mutable lastAvailableWidth_{};
TextMeasurement mutable cachedTextMeasurement_{};
/*
* Checks whether the inputs into text measurement meaningfully affect
* text measurement result. Returns true if inputs have changed and measure is
* needed.
*/
bool shouldMeasureString(
const AttributedString& attributedString,
const ParagraphAttributes& paragraphAttributes,
LayoutConstraints layoutConstraints) const;
};
} // namespace facebook::react

View File

@@ -0,0 +1,147 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ParagraphProps.h"
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/attributedstring/primitives.h>
#include <react/renderer/core/propsConversions.h>
#include <react/renderer/debug/debugStringConvertibleUtils.h>
#include <react/utils/CoreFeatures.h>
#include <glog/logging.h>
namespace facebook::react {
ParagraphProps::ParagraphProps(
const PropsParserContext& context,
const ParagraphProps& sourceProps,
const RawProps& rawProps)
: ViewProps(context, sourceProps, rawProps),
BaseTextProps(context, sourceProps, rawProps),
paragraphAttributes(
CoreFeatures::enablePropIteratorSetter
? sourceProps.paragraphAttributes
: convertRawProp(
context,
rawProps,
sourceProps.paragraphAttributes,
{})),
isSelectable(
CoreFeatures::enablePropIteratorSetter ? sourceProps.isSelectable
: convertRawProp(
context,
rawProps,
"selectable",
sourceProps.isSelectable,
false)),
onTextLayout(
CoreFeatures::enablePropIteratorSetter ? sourceProps.onTextLayout
: convertRawProp(
context,
rawProps,
"onTextLayout",
sourceProps.onTextLayout,
{})) {
/*
* These props are applied to `View`, therefore they must not be a part of
* base text attributes.
*/
textAttributes.opacity = std::numeric_limits<Float>::quiet_NaN();
textAttributes.backgroundColor = {};
};
void ParagraphProps::setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
const char* propName,
const RawValue& value) {
// All Props structs setProp methods must always, unconditionally,
// call all super::setProp methods, since multiple structs may
// reuse the same values.
ViewProps::setProp(context, hash, propName, value);
BaseTextProps::setProp(context, hash, propName, value);
static auto defaults = ParagraphProps{};
// ParagraphAttributes has its own switch statement - to keep all
// of these fields together, and because there are some collisions between
// propnames parsed here and outside of ParagraphAttributes.
// This code is also duplicated in AndroidTextInput.
static auto paDefaults = ParagraphAttributes{};
switch (hash) {
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
maximumNumberOfLines,
"numberOfLines");
REBUILD_FIELD_SWITCH_CASE(
paDefaults, value, paragraphAttributes, ellipsizeMode, "ellipsizeMode");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
textBreakStrategy,
"textBreakStrategy");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
adjustsFontSizeToFit,
"adjustsFontSizeToFit");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
minimumFontSize,
"minimumFontSize");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
maximumFontSize,
"maximumFontSize");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
includeFontPadding,
"includeFontPadding");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
android_hyphenationFrequency,
"android_hyphenationFrequency");
}
switch (hash) {
RAW_SET_PROP_SWITCH_CASE_BASIC(isSelectable);
RAW_SET_PROP_SWITCH_CASE_BASIC(onTextLayout);
}
/*
* These props are applied to `View`, therefore they must not be a part of
* base text attributes.
*/
textAttributes.opacity = std::numeric_limits<Float>::quiet_NaN();
textAttributes.backgroundColor = {};
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList ParagraphProps::getDebugProps() const {
return ViewProps::getDebugProps() + BaseTextProps::getDebugProps() +
paragraphAttributes.getDebugProps() +
SharedDebugStringConvertibleList{
debugStringConvertibleItem("isSelectable", isSelectable)};
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <limits>
#include <memory>
#include <react/renderer/attributedstring/ParagraphAttributes.h>
#include <react/renderer/components/text/BaseTextProps.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/Props.h>
#include <react/renderer/core/PropsParserContext.h>
namespace facebook::react {
/*
* Props of <Paragraph> component.
* Most of the props are directly stored in composed `ParagraphAttributes`
* object.
*/
class ParagraphProps : public ViewProps, public BaseTextProps {
public:
ParagraphProps() = default;
ParagraphProps(
const PropsParserContext& context,
const ParagraphProps& sourceProps,
const RawProps& rawProps);
void setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
const char* propName,
const RawValue& value);
#pragma mark - Props
/*
* Contains all prop values that affect visual representation of the
* paragraph.
*/
ParagraphAttributes paragraphAttributes{};
/*
* Defines can the text be selected (and copied) or not.
*/
bool isSelectable{};
bool onTextLayout{};
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
};
} // namespace facebook::react

View File

@@ -0,0 +1,263 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ParagraphShadowNode.h"
#include <cmath>
#include <react/debug/react_native_assert.h>
#include <react/renderer/attributedstring/AttributedStringBox.h>
#include <react/renderer/components/view/ViewShadowNode.h>
#include <react/renderer/components/view/conversions.h>
#include <react/renderer/graphics/rounding.h>
#include <react/renderer/telemetry/TransactionTelemetry.h>
#include <react/renderer/textlayoutmanager/TextLayoutContext.h>
#include "ParagraphState.h"
namespace facebook::react {
using Content = ParagraphShadowNode::Content;
const char ParagraphComponentName[] = "Paragraph";
ParagraphShadowNode::ParagraphShadowNode(
const ShadowNode& sourceShadowNode,
const ShadowNodeFragment& fragment)
: ConcreteViewShadowNode(sourceShadowNode, fragment) {
auto& sourceParagraphShadowNode =
dynamic_cast<const ParagraphShadowNode&>(sourceShadowNode);
if (!fragment.children && !fragment.props &&
sourceParagraphShadowNode.getIsLayoutClean()) {
// This ParagraphShadowNode was cloned but did not change
// in a way that affects its layout. Let's mark it clean
// to stop Yoga from traversing it.
cleanLayout();
}
}
const Content& ParagraphShadowNode::getContent(
const LayoutContext& layoutContext) const {
if (content_.has_value()) {
return content_.value();
}
ensureUnsealed();
auto textAttributes = TextAttributes::defaultTextAttributes();
textAttributes.fontSizeMultiplier = layoutContext.fontSizeMultiplier;
textAttributes.apply(getConcreteProps().textAttributes);
textAttributes.layoutDirection =
YGNodeLayoutGetDirection(&yogaNode_) == YGDirectionRTL
? LayoutDirection::RightToLeft
: LayoutDirection::LeftToRight;
auto attributedString = AttributedString{};
auto attachments = Attachments{};
buildAttributedString(textAttributes, *this, attributedString, attachments);
content_ = Content{
attributedString, getConcreteProps().paragraphAttributes, attachments};
return content_.value();
}
Content ParagraphShadowNode::getContentWithMeasuredAttachments(
const LayoutContext& layoutContext,
const LayoutConstraints& layoutConstraints) const {
auto content = getContent(layoutContext);
if (content.attachments.empty()) {
// Base case: No attachments, nothing to do.
return content;
}
auto localLayoutConstraints = layoutConstraints;
// Having enforced minimum size for text fragments doesn't make much sense.
localLayoutConstraints.minimumSize = Size{0, 0};
auto& fragments = content.attributedString.getFragments();
for (const auto& attachment : content.attachments) {
auto laytableShadowNode =
dynamic_cast<const LayoutableShadowNode*>(attachment.shadowNode);
if (laytableShadowNode == nullptr) {
continue;
}
auto size =
laytableShadowNode->measure(layoutContext, localLayoutConstraints);
// Rounding to *next* value on the pixel grid.
size.width += 0.01f;
size.height += 0.01f;
size = roundToPixel<&ceil>(size, layoutContext.pointScaleFactor);
auto fragmentLayoutMetrics = LayoutMetrics{};
fragmentLayoutMetrics.pointScaleFactor = layoutContext.pointScaleFactor;
fragmentLayoutMetrics.frame.size = size;
fragments[attachment.fragmentIndex].parentShadowView.layoutMetrics =
fragmentLayoutMetrics;
}
return content;
}
void ParagraphShadowNode::setTextLayoutManager(
std::shared_ptr<const TextLayoutManager> textLayoutManager) {
ensureUnsealed();
getStateData().paragraphLayoutManager.setTextLayoutManager(
std::move(textLayoutManager));
}
void ParagraphShadowNode::updateStateIfNeeded(const Content& content) {
ensureUnsealed();
auto& state = getStateData();
react_native_assert(state.paragraphLayoutManager.getTextLayoutManager());
if (state.attributedString == content.attributedString) {
return;
}
setStateData(ParagraphState{
content.attributedString,
content.paragraphAttributes,
state.paragraphLayoutManager});
}
#pragma mark - LayoutableShadowNode
Size ParagraphShadowNode::measureContent(
const LayoutContext& layoutContext,
const LayoutConstraints& layoutConstraints) const {
auto content =
getContentWithMeasuredAttachments(layoutContext, layoutConstraints);
auto attributedString = content.attributedString;
if (attributedString.isEmpty()) {
// Note: `zero-width space` is insufficient in some cases (e.g. when we need
// to measure the "height" of the font).
// TODO T67606511: We will redefine the measurement of empty strings as part
// of T67606511
auto string = BaseTextShadowNode::getEmptyPlaceholder();
auto textAttributes = TextAttributes::defaultTextAttributes();
textAttributes.fontSizeMultiplier = layoutContext.fontSizeMultiplier;
textAttributes.apply(getConcreteProps().textAttributes);
attributedString.appendFragment({string, textAttributes, {}});
}
TextLayoutContext textLayoutContext{};
textLayoutContext.pointScaleFactor = layoutContext.pointScaleFactor;
return getStateData()
.paragraphLayoutManager
.measure(
attributedString,
content.paragraphAttributes,
textLayoutContext,
layoutConstraints)
.size;
}
void ParagraphShadowNode::layout(LayoutContext layoutContext) {
ensureUnsealed();
auto layoutMetrics = getLayoutMetrics();
auto availableSize = layoutMetrics.getContentFrame().size;
auto layoutConstraints = LayoutConstraints{
availableSize, availableSize, layoutMetrics.layoutDirection};
auto content =
getContentWithMeasuredAttachments(layoutContext, layoutConstraints);
updateStateIfNeeded(content);
TextLayoutContext textLayoutContext{};
textLayoutContext.pointScaleFactor = layoutContext.pointScaleFactor;
auto measurement = getStateData().paragraphLayoutManager.measure(
content.attributedString,
content.paragraphAttributes,
textLayoutContext,
layoutConstraints);
if (getConcreteProps().onTextLayout) {
auto linesMeasurements = getStateData().paragraphLayoutManager.measureLines(
content.attributedString,
content.paragraphAttributes,
measurement.size);
getConcreteEventEmitter().onTextLayout(linesMeasurements);
}
if (content.attachments.empty()) {
// No attachments to layout.
return;
}
// Iterating on attachments, we clone shadow nodes and moving
// `paragraphShadowNode` that represents clones of `this` object.
auto paragraphShadowNode = static_cast<ParagraphShadowNode*>(this);
// `paragraphOwningShadowNode` is owning pointer to`paragraphShadowNode`
// (besides the initial case when `paragraphShadowNode == this`), we need this
// only to keep it in memory for a while.
auto paragraphOwningShadowNode = ShadowNode::Unshared{};
react_native_assert(
content.attachments.size() == measurement.attachments.size());
for (size_t i = 0; i < content.attachments.size(); i++) {
auto& attachment = content.attachments.at(i);
if (dynamic_cast<const LayoutableShadowNode*>(attachment.shadowNode) ==
nullptr) {
// Not a layoutable `ShadowNode`, no need to lay it out.
continue;
}
auto clonedShadowNode = ShadowNode::Unshared{};
paragraphOwningShadowNode = paragraphShadowNode->cloneTree(
attachment.shadowNode->getFamily(),
[&](const ShadowNode& oldShadowNode) {
clonedShadowNode = oldShadowNode.clone({});
return clonedShadowNode;
});
paragraphShadowNode =
static_cast<ParagraphShadowNode*>(paragraphOwningShadowNode.get());
auto& layoutableShadowNode =
dynamic_cast<LayoutableShadowNode&>(*clonedShadowNode);
auto attachmentFrame = measurement.attachments[i].frame;
auto attachmentSize = roundToPixel<&ceil>(
attachmentFrame.size, layoutMetrics.pointScaleFactor);
auto attachmentOrigin = roundToPixel<&round>(
attachmentFrame.origin, layoutMetrics.pointScaleFactor);
auto attachmentLayoutContext = layoutContext;
auto attachmentLayoutConstrains = LayoutConstraints{
attachmentSize, attachmentSize, layoutConstraints.layoutDirection};
// Laying out the `ShadowNode` and the subtree starting from it.
layoutableShadowNode.layoutTree(
attachmentLayoutContext, attachmentLayoutConstrains);
// Altering the origin of the `ShadowNode` (which is defined by text layout,
// not by internal styles and state).
auto attachmentLayoutMetrics = layoutableShadowNode.getLayoutMetrics();
attachmentLayoutMetrics.frame.origin = attachmentOrigin;
layoutableShadowNode.setLayoutMetrics(attachmentLayoutMetrics);
}
// If we ended up cloning something, we need to update the list of children to
// reflect the changes that we made.
if (paragraphShadowNode != this) {
this->children_ =
static_cast<const ParagraphShadowNode*>(paragraphShadowNode)->children_;
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/text/BaseTextShadowNode.h>
#include <react/renderer/components/text/ParagraphEventEmitter.h>
#include <react/renderer/components/text/ParagraphProps.h>
#include <react/renderer/components/text/ParagraphState.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/core/LayoutContext.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/textlayoutmanager/TextLayoutManager.h>
namespace facebook::react {
extern const char ParagraphComponentName[];
/*
* `ShadowNode` for <Paragraph> component, represents <View>-like component
* containing and displaying text. Text content is represented as nested <Text>
* and <RawText> components.
*/
class ParagraphShadowNode final : public ConcreteViewShadowNode<
ParagraphComponentName,
ParagraphProps,
ParagraphEventEmitter,
ParagraphState>,
public BaseTextShadowNode {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
ParagraphShadowNode(
const ShadowNode& sourceShadowNode,
const ShadowNodeFragment& fragment);
static ShadowNodeTraits BaseTraits() {
auto traits = ConcreteViewShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::LeafYogaNode);
traits.set(ShadowNodeTraits::Trait::MeasurableYogaNode);
#ifdef ANDROID
// Unsetting `FormsStackingContext` trait is essential on Android where we
// can't mount views inside `TextView`.
traits.unset(ShadowNodeTraits::Trait::FormsStackingContext);
#endif
return traits;
}
/*
* Associates a shared TextLayoutManager with the node.
* `ParagraphShadowNode` uses the manager to measure text content
* and construct `ParagraphState` objects.
*/
void setTextLayoutManager(
std::shared_ptr<const TextLayoutManager> textLayoutManager);
#pragma mark - LayoutableShadowNode
void layout(LayoutContext layoutContext) override;
Size measureContent(
const LayoutContext& layoutContext,
const LayoutConstraints& layoutConstraints) const override;
/*
* Internal representation of the nested content of the node in a format
* suitable for future processing.
*/
class Content final {
public:
AttributedString attributedString;
ParagraphAttributes paragraphAttributes;
Attachments attachments;
};
private:
/*
* Builds (if needed) and returns a reference to a `Content` object.
*/
const Content& getContent(const LayoutContext& layoutContext) const;
/*
* Builds and returns a `Content` object with given `layoutConstraints`.
*/
Content getContentWithMeasuredAttachments(
const LayoutContext& layoutContext,
const LayoutConstraints& layoutConstraints) const;
/*
* Creates a `State` object (with `AttributedText` and
* `TextLayoutManager`) if needed.
*/
void updateStateIfNeeded(const Content& content);
/*
* Cached content of the subtree started from the node.
*/
mutable std::optional<Content> content_{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ParagraphState.h"
#include <react/renderer/components/text/conversions.h>
#include <react/renderer/debug/debugStringConvertibleUtils.h>
namespace facebook::react {
#ifdef ANDROID
folly::dynamic ParagraphState::getDynamic() const {
return toDynamic(*this);
}
MapBuffer ParagraphState::getMapBuffer() const {
return toMapBuffer(*this);
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/debug/react_native_assert.h>
#include <react/renderer/attributedstring/AttributedString.h>
#include <react/renderer/attributedstring/ParagraphAttributes.h>
#include <react/renderer/components/text/ParagraphLayoutManager.h>
#ifdef ANDROID
#include <folly/dynamic.h>
#include <react/renderer/mapbuffer/MapBuffer.h>
#endif
namespace facebook::react {
#ifdef ANDROID
// constants for Text State serialization
constexpr static MapBuffer::Key TX_STATE_KEY_ATTRIBUTED_STRING = 0;
constexpr static MapBuffer::Key TX_STATE_KEY_PARAGRAPH_ATTRIBUTES = 1;
// Used for TextInput only
constexpr static MapBuffer::Key TX_STATE_KEY_HASH = 2;
constexpr static MapBuffer::Key TX_STATE_KEY_MOST_RECENT_EVENT_COUNT = 3;
#endif
/*
* State for <Paragraph> component.
* Represents what to render and how to render.
*/
struct ParagraphState {
/*
* All content of <Paragraph> component represented as an `AttributedString`.
*/
AttributedString attributedString;
/*
* Represents all visual attributes of a paragraph of text represented as
* a ParagraphAttributes.
*/
ParagraphAttributes paragraphAttributes;
/*
* `ParagraphLayoutManager` provides a connection to platform-specific
* text rendering infrastructure which is capable to render the
* `AttributedString`.
* This is not on every platform. This is not used on Android, but is
* used on the iOS mounting layer.
*/
ParagraphLayoutManager paragraphLayoutManager;
#ifdef ANDROID
ParagraphState(
const AttributedString& attributedString,
const ParagraphAttributes& paragraphAttributes,
const ParagraphLayoutManager& paragraphLayoutManager)
: attributedString(attributedString),
paragraphAttributes(paragraphAttributes),
paragraphLayoutManager(paragraphLayoutManager) {}
ParagraphState() = default;
ParagraphState(
const ParagraphState& previousState,
const folly::dynamic& data) {
react_native_assert(false && "Not supported");
};
folly::dynamic getDynamic() const;
MapBuffer getMapBuffer() const;
#endif
};
} // namespace facebook::react

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/text/RawTextShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
using RawTextComponentDescriptor =
ConcreteComponentDescriptor<RawTextShadowNode>;
} // namespace facebook::react

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "RawTextProps.h"
#include <react/renderer/core/propsConversions.h>
#include <react/renderer/debug/debugStringConvertibleUtils.h>
namespace facebook::react {
RawTextProps::RawTextProps(
const PropsParserContext& context,
const RawTextProps& sourceProps,
const RawProps& rawProps)
: Props(context, sourceProps, rawProps),
text(convertRawProp(context, rawProps, "text", sourceProps.text, {})){};
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList RawTextProps::getDebugProps() const {
return SharedDebugStringConvertibleList{
debugStringConvertibleItem("text", text)};
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <memory>
#include <react/renderer/core/Props.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/debug/DebugStringConvertible.h>
namespace facebook::react {
class RawTextProps;
using SharedRawTextProps = std::shared_ptr<const RawTextProps>;
class RawTextProps : public Props {
public:
RawTextProps() = default;
RawTextProps(
const PropsParserContext& context,
const RawTextProps& sourceProps,
const RawProps& rawProps);
#pragma mark - Props
std::string text{};
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
};
} // namespace facebook::react

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "RawTextShadowNode.h"
namespace facebook::react {
extern const char RawTextComponentName[] = "RawText";
} // namespace facebook::react

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/text/RawTextProps.h>
#include <react/renderer/core/ConcreteShadowNode.h>
namespace facebook::react {
extern const char RawTextComponentName[];
/*
* `ShadowNode` for <RawText> component, represents a purely regular string
* object in React. In a code fragment `<Text>Hello!</Text>`, "Hello!" part
* is represented as `<RawText text="Hello!"/>`.
* <RawText> component must not have any children.
*/
class RawTextShadowNode : public ConcreteShadowNode<
RawTextComponentName,
ShadowNode,
RawTextProps> {
public:
using ConcreteShadowNode::ConcreteShadowNode;
};
} // namespace facebook::react

Some files were not shown because too many files have changed in this diff Show More