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:
Valentin Shergin 2018-06-22 07:28:40 -07:00 committed by Facebook Github Bot
parent 979ea2094e
commit b09457b4d2
13 changed files with 598 additions and 0 deletions

View File

@ -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",
],
)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 &currentLocalData = 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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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 (