Fabric: <Image> component, xplat part
Summary: @public This diff implements basics of cross-platform part of <Image> component. Known issues: - Events does not work yet. - Some quite specific image source parameters (like custom http headers) are not supported yet. Reviewed By: fkgozali Differential Revision: D8526575 fbshipit-source-id: ecc97d9fda2b2e65bb1b079af057f8e176a161e5
This commit is contained in:
parent
979ea2094e
commit
b09457b4d2
|
@ -0,0 +1,80 @@
|
|||
load("//configurations/buck/apple:flag_defs.bzl", "OBJC_ARC_PREPROCESSOR_FLAGS", "get_application_ios_flags", "get_debug_preprocessor_flags")
|
||||
load("//ReactNative:DEFS.bzl", "ANDROID", "APPLE", "IS_OSS_BUILD", "get_apple_inspector_flags", "react_native_xplat_target", "rn_xplat_cxx_library")
|
||||
|
||||
APPLE_COMPILER_FLAGS = []
|
||||
|
||||
if not IS_OSS_BUILD:
|
||||
load("@xplat//configurations/buck/apple:flag_defs.bzl", "flags", "get_static_library_ios_flags")
|
||||
|
||||
APPLE_COMPILER_FLAGS = flags.get_flag_value(get_static_library_ios_flags(), "compiler_flags")
|
||||
|
||||
rn_xplat_cxx_library(
|
||||
name = "image",
|
||||
srcs = glob(
|
||||
["**/*.cpp"],
|
||||
exclude = glob(["tests/**/*.cpp"]),
|
||||
),
|
||||
headers = [],
|
||||
header_namespace = "",
|
||||
exported_headers = subdir_glob(
|
||||
[
|
||||
("", "*.h"),
|
||||
],
|
||||
prefix = "fabric/components/image",
|
||||
),
|
||||
compiler_flags = [
|
||||
"-fexceptions",
|
||||
"-frtti",
|
||||
"-std=c++14",
|
||||
"-Wall",
|
||||
],
|
||||
fbobjc_compiler_flags = APPLE_COMPILER_FLAGS,
|
||||
fbobjc_preprocessor_flags = get_debug_preprocessor_flags() + get_apple_inspector_flags(),
|
||||
fbobjc_tests = [
|
||||
":tests",
|
||||
],
|
||||
macosx_tests_override = [],
|
||||
platforms = (ANDROID, APPLE),
|
||||
preprocessor_flags = [
|
||||
"-DLOG_TAG=\"ReactNative\"",
|
||||
"-DWITH_FBSYSTRACE=1",
|
||||
],
|
||||
tests = [],
|
||||
visibility = ["PUBLIC"],
|
||||
deps = [
|
||||
"xplat//fbsystrace:fbsystrace",
|
||||
"xplat//folly:futures",
|
||||
"xplat//folly:headers_only",
|
||||
"xplat//folly:memory",
|
||||
"xplat//folly:molly",
|
||||
"xplat//third-party/glog:glog",
|
||||
"xplat//yoga:yoga",
|
||||
react_native_xplat_target("fabric/debug:debug"),
|
||||
react_native_xplat_target("fabric/core:core"),
|
||||
react_native_xplat_target("fabric/graphics:graphics"),
|
||||
react_native_xplat_target("fabric/imagemanager:imagemanager"),
|
||||
react_native_xplat_target("fabric/view:view"),
|
||||
],
|
||||
)
|
||||
|
||||
if not IS_OSS_BUILD:
|
||||
load("@xplat//build_defs:fb_xplat_cxx_test.bzl", "fb_xplat_cxx_test")
|
||||
|
||||
fb_xplat_cxx_test(
|
||||
name = "tests",
|
||||
srcs = glob(["tests/**/*.cpp"]),
|
||||
headers = glob(["tests/**/*.h"]),
|
||||
contacts = ["oncall+react_native@xmail.facebook.com"],
|
||||
compiler_flags = [
|
||||
"-fexceptions",
|
||||
"-frtti",
|
||||
"-std=c++14",
|
||||
"-Wall",
|
||||
],
|
||||
platforms = APPLE,
|
||||
deps = [
|
||||
"xplat//folly:molly",
|
||||
"xplat//third-party/gmock:gtest",
|
||||
":image",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* 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 <fabric/components/image/ImageShadowNode.h>
|
||||
#include <fabric/core/ConcreteComponentDescriptor.h>
|
||||
#include <fabric/imagemanager/ImageManager.h>
|
||||
#include <fabric/uimanager/ContextContainer.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
/*
|
||||
* Descriptor for <Image> component.
|
||||
*/
|
||||
class ImageComponentDescriptor final:
|
||||
public ConcreteComponentDescriptor<ImageShadowNode> {
|
||||
|
||||
public:
|
||||
ImageComponentDescriptor(SharedEventDispatcher eventDispatcher, const SharedContextContainer &contextContainer):
|
||||
ConcreteComponentDescriptor(eventDispatcher),
|
||||
imageManager_(std::static_pointer_cast<ImageManager>(contextContainer->at(typeid(ImageManager)))) {}
|
||||
|
||||
void adopt(UnsharedShadowNode shadowNode) const override {
|
||||
ConcreteComponentDescriptor::adopt(shadowNode);
|
||||
|
||||
assert(std::dynamic_pointer_cast<ImageShadowNode>(shadowNode));
|
||||
auto imageShadowNode = std::static_pointer_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 react
|
||||
} // namespace facebook
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* 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 {
|
||||
namespace react {
|
||||
|
||||
void ImageEventEmitter::onLoadStart() const {
|
||||
dispatchEvent("loadStart");
|
||||
}
|
||||
|
||||
void ImageEventEmitter::onLoad() const {
|
||||
dispatchEvent("load");
|
||||
}
|
||||
|
||||
void ImageEventEmitter::onLoadEnd() const {
|
||||
dispatchEvent("loadEnd");
|
||||
}
|
||||
|
||||
void ImageEventEmitter::onProgress() const {
|
||||
dispatchEvent("progress");
|
||||
}
|
||||
|
||||
void ImageEventEmitter::onError() const {
|
||||
dispatchEvent("error");
|
||||
}
|
||||
|
||||
void ImageEventEmitter::onPartialLoad() const {
|
||||
dispatchEvent("partialLoad");
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* 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 <fabric/view/ViewEventEmitter.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class ImageEventEmitter:
|
||||
public ViewEventEmitter {
|
||||
|
||||
public:
|
||||
using ViewEventEmitter::ViewEventEmitter;
|
||||
|
||||
void onLoadStart() const;
|
||||
void onLoad() const;
|
||||
void onLoadEnd() const;
|
||||
void onProgress() const;
|
||||
void onError() const;
|
||||
void onPartialLoad() const;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "ImageLocalData.h"
|
||||
|
||||
#include <fabric/components/image/conversions.h>
|
||||
#include <fabric/debug/debugStringConvertibleUtils.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
ImageSource ImageLocalData::getImageSource() const {
|
||||
return imageSource_;
|
||||
}
|
||||
|
||||
const ImageRequest &ImageLocalData::getImageRequest() const {
|
||||
return imageRequest_;
|
||||
}
|
||||
|
||||
#pragma mark - DebugStringConvertible
|
||||
|
||||
std::string ImageLocalData::getDebugName() const {
|
||||
return "ImageLocalData";
|
||||
}
|
||||
|
||||
SharedDebugStringConvertibleList ImageLocalData::getDebugProps() const {
|
||||
return {
|
||||
debugStringConvertibleItem("imageSource", imageSource_)
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* 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 <fabric/core/LocalData.h>
|
||||
#include <fabric/imagemanager/primitives.h>
|
||||
#include <fabric/imagemanager/ImageRequest.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class ImageLocalData;
|
||||
|
||||
using SharedImageLocalData = std::shared_ptr<const ImageLocalData>;
|
||||
|
||||
/*
|
||||
* LocalData for <Image> component.
|
||||
* Represents the image request state and (possible) retrieved image bitmap.
|
||||
*/
|
||||
class ImageLocalData:
|
||||
public LocalData {
|
||||
|
||||
public:
|
||||
|
||||
ImageLocalData(const ImageSource &imageSource, ImageRequest imageRequest):
|
||||
imageSource_(imageSource),
|
||||
imageRequest_(std::move(imageRequest)) {};
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
#pragma mark - DebugStringConvertible
|
||||
|
||||
std::string getDebugName() const override;
|
||||
SharedDebugStringConvertibleList getDebugProps() const override;
|
||||
|
||||
private:
|
||||
|
||||
ImageSource imageSource_;
|
||||
ImageRequest imageRequest_;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include <fabric/components/image/conversions.h>
|
||||
#include <fabric/components/image/ImageProps.h>
|
||||
#include <fabric/core/propsConversions.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
ImageProps::ImageProps(const ImageProps &sourceProps, const RawProps &rawProps):
|
||||
ViewProps(sourceProps, rawProps),
|
||||
sources(convertRawProp(rawProps, "source", sourceProps.sources)),
|
||||
defaultSources(convertRawProp(rawProps, "defaultSource", sourceProps.defaultSources)),
|
||||
resizeMode(convertRawProp(rawProps, "resizeMode", sourceProps.resizeMode, ImageResizeMode::Stretch)),
|
||||
blurRadius(convertRawProp(rawProps, "blurRadius", sourceProps.blurRadius)),
|
||||
capInsets(convertRawProp(rawProps, "capInsets", sourceProps.capInsets)),
|
||||
tintColor(convertRawProp(rawProps, "tintColor", sourceProps.tintColor)) {}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include <fabric/graphics/Color.h>
|
||||
#include <fabric/imagemanager/primitives.h>
|
||||
#include <fabric/view/ViewProps.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
// TODO (T28334063): Consider for codegen.
|
||||
class ImageProps final:
|
||||
public ViewProps {
|
||||
|
||||
public:
|
||||
ImageProps() = default;
|
||||
ImageProps(const ImageProps &sourceProps, const RawProps &rawProps);
|
||||
|
||||
#pragma mark - Props
|
||||
|
||||
const ImageSources sources {};
|
||||
const ImageSources defaultSources {};
|
||||
const ImageResizeMode resizeMode {ImageResizeMode::Stretch};
|
||||
const Float blurRadius {};
|
||||
const EdgeInsets capInsets {};
|
||||
const SharedColor tintColor {};
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* 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 <fabric/components/image/ImageShadowNode.h>
|
||||
#include <fabric/components/image/ImageLocalData.h>
|
||||
#include <fabric/core/LayoutContext.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
ComponentName ImageShadowNode::getComponentName() const {
|
||||
return ComponentName("Image");
|
||||
}
|
||||
|
||||
void ImageShadowNode::setImageManager(const SharedImageManager &imageManager) {
|
||||
ensureUnsealed();
|
||||
imageManager_ = imageManager;
|
||||
}
|
||||
|
||||
void ImageShadowNode::updateLocalData() {
|
||||
const auto &imageSource = getImageSource();
|
||||
const auto ¤tLocalData = getLocalData();
|
||||
if (currentLocalData) {
|
||||
assert(std::dynamic_pointer_cast<const ImageLocalData>(currentLocalData));
|
||||
auto currentImageLocalData = std::static_pointer_cast<const ImageLocalData>(currentLocalData);
|
||||
if (currentImageLocalData->getImageSource() == imageSource) {
|
||||
// Same `imageSource` is already in `localData`,
|
||||
// no need to (re)request an image resource.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Now we are about to mutate the Shadow Node.
|
||||
ensureUnsealed();
|
||||
|
||||
auto imageRequest = imageManager_->requestImage(imageSource);
|
||||
auto imageLocalData = std::make_shared<ImageLocalData>(imageSource, std::move(imageRequest));
|
||||
setLocalData(imageLocalData);
|
||||
}
|
||||
|
||||
ImageSource ImageShadowNode::getImageSource() const {
|
||||
auto sources = getProps()->sources;
|
||||
|
||||
if (sources.size() == 0) {
|
||||
return {.type = ImageSource::Type::Invalid};
|
||||
}
|
||||
|
||||
if (sources.size() == 1) {
|
||||
return sources[0];
|
||||
}
|
||||
|
||||
auto layoutMetrics = getLayoutMetrics();
|
||||
auto size = layoutMetrics.getContentFrame().size;
|
||||
auto scale = layoutMetrics.pointScaleFactor;
|
||||
auto targetImageArea = size.width * size.height * scale * scale;
|
||||
auto bestFit = kFloatMax;
|
||||
|
||||
ImageSource bestSource;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return bestSource;
|
||||
}
|
||||
|
||||
#pragma mark - LayoutableShadowNode
|
||||
|
||||
void ImageShadowNode::layout(LayoutContext layoutContext) {
|
||||
updateLocalData();
|
||||
ConcreteViewShadowNode::layout(layoutContext);
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* 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 <fabric/components/image/ImageEventEmitter.h>
|
||||
#include <fabric/components/image/ImageProps.h>
|
||||
#include <fabric/imagemanager/ImageManager.h>
|
||||
#include <fabric/imagemanager/primitives.h>
|
||||
#include <fabric/view/ConcreteViewShadowNode.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
/*
|
||||
* `ShadowNode` for <Image> component.
|
||||
*/
|
||||
class ImageShadowNode final:
|
||||
public ConcreteViewShadowNode<ImageProps, ImageEventEmitter> {
|
||||
|
||||
public:
|
||||
|
||||
using ConcreteViewShadowNode::ConcreteViewShadowNode;
|
||||
|
||||
ComponentName getComponentName() const override;
|
||||
|
||||
/*
|
||||
* Associates a shared `ImageManager` with the node.
|
||||
*/
|
||||
void setImageManager(const SharedImageManager &imageManager);
|
||||
|
||||
#pragma mark - LayoutableShadowNode
|
||||
|
||||
void layout(LayoutContext layoutContext) override;
|
||||
|
||||
private:
|
||||
|
||||
/*
|
||||
* (Re)Creates a `LocalData` object (with `ImageRequest`) if needed.
|
||||
*/
|
||||
void updateLocalData();
|
||||
|
||||
ImageSource getImageSource() const;
|
||||
|
||||
SharedImageManager imageManager_;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* 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 <fabric/imagemanager/primitives.h>
|
||||
#include <fabric/graphics/conversions.h>
|
||||
#include <folly/dynamic.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
inline void fromDynamic(const folly::dynamic &value, ImageSource &result) {
|
||||
if (value.isString()) {
|
||||
result = {
|
||||
.type = ImageSource::Type::Remote,
|
||||
.uri = value.asString()
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.isObject()) {
|
||||
result = {};
|
||||
|
||||
result.type = ImageSource::Type::Remote;
|
||||
|
||||
if (value.count("__packager_asset")) {
|
||||
result.type = ImageSource::Type::Local;
|
||||
}
|
||||
|
||||
if (value.count("width") && value.count("height")) {
|
||||
fromDynamic(value, result.size);
|
||||
}
|
||||
|
||||
if (value.count("scale")) {
|
||||
result.scale = (Float)value["scale"].asDouble();
|
||||
} else {
|
||||
result.scale = value.count("deprecated") ? 0.0 : 1.0;
|
||||
}
|
||||
|
||||
if (value.count("url")) {
|
||||
result.uri = value["url"].asString();
|
||||
}
|
||||
|
||||
if (value.count("uri")) {
|
||||
result.uri = value["uri"].asString();
|
||||
}
|
||||
|
||||
if (value.count("bundle")) {
|
||||
result.bundle = value["bundle"].asString();
|
||||
result.type = ImageSource::Type::Local;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
abort();
|
||||
}
|
||||
|
||||
inline std::string toString(const ImageSource &value) {
|
||||
return "{uri: " + value.uri + "}";
|
||||
}
|
||||
|
||||
inline void fromDynamic(const folly::dynamic &value, ImageResizeMode &result) {
|
||||
assert(value.isString());
|
||||
auto stringValue = value.asString();
|
||||
if (stringValue == "cover") { result = ImageResizeMode::Cover; return; }
|
||||
if (stringValue == "contain") { result = ImageResizeMode::Contain; return; }
|
||||
if (stringValue == "stretch") { result = ImageResizeMode::Stretch; return; }
|
||||
if (stringValue == "center") { result = ImageResizeMode::Center; return; }
|
||||
if (stringValue == "repeat") { result = ImageResizeMode::Repeat; return; }
|
||||
abort();
|
||||
}
|
||||
|
||||
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 react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* 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(SwitchTest, testSomething) {
|
||||
// TODO
|
||||
}
|
|
@ -60,6 +60,10 @@ static const std::string componentNameByReactViewName(std::string viewName) {
|
|||
return "Text";
|
||||
}
|
||||
|
||||
if (viewName == "ImageView") {
|
||||
return "Image";
|
||||
}
|
||||
|
||||
// We need this temporarly for testing purposes until we have proper
|
||||
// implementation of core components: <Image>, <ScrollContentView>.
|
||||
if (
|
||||
|
|
Loading…
Reference in New Issue