ComponentKit/Core/ComponentBuilder.h (534 lines of code) (raw):
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <ComponentKit/CKDefines.h>
#if CK_NOT_SWIFT
#import <ComponentKit/CKComponent.h>
#import <ComponentKit/CKComponentGestureActions.h>
#import <ComponentKit/CKNonNull.h>
#import <ComponentKit/CKPropBitmap.h>
#import <ComponentKit/CKTransitions.h>
#import <ComponentKit/CKOptional.h>
#import <ComponentKit/CKComponentSpecContext.h>
#import <ComponentKit/CKAccessibilityAggregation.h>
namespace CK {
namespace BuilderDetails {
namespace BuilderBasePropId {
constexpr static auto context = 1ULL << 0;
constexpr static auto transitions = context << 1;
constexpr static auto key = transitions << 1;
constexpr static auto __max = key;
};
template <template <PropsBitmapType> class Derived, PropsBitmapType PropsBitmap>
class __attribute__((__may_alias__)) BuilderBase {
CK::ComponentSpecContext _context;
id _key;
NS_RETURNS_RETAINED auto _buildComponentWithTransitionsIfNeeded() noexcept -> CKComponent *
{
const auto component = static_cast<Derived<PropsBitmap> &>(*this)._build();
if (PropBitmap::isSet(PropsBitmap, BuilderBasePropId::transitions)) {
return CKComponentWithTransitions(component, _transitions);
} else {
return component;
}
}
protected:
BuilderBase() = default;
BuilderBase(const CK::ComponentSpecContext& context) : _context(context) { }
public:
BuilderBase(const BuilderBase&) = default;
/**
Creates a new component instance and optionally wrap it with an animation component.
@note This method must @b not be called more than once on a given component builder instance.
*/
NS_RETURNS_RETAINED auto build() noexcept -> CKComponent *
{
const auto component = _buildComponentWithTransitionsIfNeeded();
if (PropBitmap::isSet(PropsBitmap, BuilderBasePropId::key)) {
_context.declareKey(_key, component);
}
if (_aggregatedAttributes != CKAccessibilityAggregatedAttributeNone) {
return CKComponentWithAccessibilityAggregationWrapper(component, _aggregatedAttributes);
}
return component;
}
/**
Specifies the key for the component. This key is only meant to be used from specs.
@note Calling this method on a builder that wasn't created with a context will trigger a compilation error.
@param key The key to reference the component built.
*/
auto &key(CK::RelaxedNonNull<id> key)
{
constexpr auto contextIsSet =
PropBitmap::isSet(PropsBitmap, BuilderBasePropId::context);
static_assert(contextIsSet, "Cannot set 'key' without specifying 'context'");
_key = key;
// `this` pointer needs adjustment since `BuilderBase` is not the first base class of `ComponentBuilderBase`
return reinterpret_cast<Derived<PropsBitmap | BuilderBasePropId::key> &>(*static_cast<Derived<PropsBitmap> *>(this));
}
/**
Specifies the animation on initial mount.
@param animationInitial The animation to trigger on initial mount.
*/
auto &animationInitial(CK::Animation::Initial animationInitial)
{
_transitions.onInitialMount = std::move(animationInitial);
// `this` pointer needs adjustment since `BuilderBase` is not the first base class of `ComponentBuilderBase`
return reinterpret_cast<Derived<PropsBitmap | BuilderBasePropId::transitions> &>(*static_cast<Derived<PropsBitmap> *>(this));
}
/**
Specifies the animation on final unmount.
@param animationFinal The animation to trigger on final unmount.
*/
auto &animationFinal(CK::Animation::Final animationFinal)
{
_transitions.onFinalUnmount = std::move(animationFinal);
// `this` pointer needs adjustment since `BuilderBase` is not the first base class of `ComponentBuilderBase`
return reinterpret_cast<Derived<PropsBitmap | BuilderBasePropId::transitions> &>(*static_cast<Derived<PropsBitmap> *>(this));
}
/**
Specifies what accessibility attributes will be aggregated.
@param attributes A OR-ed list of attributes that will be aggregated by this component.
*/
auto &accessibilityAggregateAttributes(CKAccessibilityAggregatedAttributes attributes = CKAccessibilityAggregatedAttributesAll)
{
_aggregatedAttributes = attributes;
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
private:
CKTransitions _transitions;
CKAccessibilityAggregatedAttributes _aggregatedAttributes = CKAccessibilityAggregatedAttributeNone;
};
namespace ViewConfigBuilderPropId {
constexpr static auto viewClass = BuilderBasePropId::__max << 1;
constexpr static auto viewConfig = viewClass << 1;
constexpr static auto __max = viewConfig;
}
template <template <PropsBitmapType> class Derived, PropsBitmapType PropsBitmap>
class __attribute__((__may_alias__)) ViewConfigBuilderBase {
public:
ViewConfigBuilderBase() = default;
~ViewConfigBuilderBase() = default;
/**
Specifies that the component should have a view of the given class. The class will be instantiated with UIView's
designated initializer @c-initWithFrame:.
*/
auto &viewClass(Class c)
{
constexpr auto viewClassOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!viewClassOverridesExistingViewConfiguration,
"Setting 'viewClass' overrides existing view configuration");
_viewClass = c;
return reinterpret_cast<Derived<PropsBitmap | ViewConfigBuilderPropId::viewClass> &>(*this);
}
/**
Specifies a view class that cannot be instantiated with @c-initWithFrame:.
@param f A pointer to a function that returns a new instance of a view.
*/
auto &viewClass(CKComponentViewFactoryFunc f)
{
constexpr auto viewClassOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!viewClassOverridesExistingViewConfiguration,
"Setting 'viewClass' overrides existing view configuration");
_viewClass = f;
return reinterpret_cast<Derived<PropsBitmap | ViewConfigBuilderPropId::viewClass> &>(*this);
}
/**
Specifies an arbitrary view class.
*/
auto &viewClass(CKComponentViewClass c)
{
constexpr auto viewClassOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!viewClassOverridesExistingViewConfiguration,
"Setting 'viewClass' overrides existing view configuration");
_viewClass = std::move(c);
return reinterpret_cast<Derived<PropsBitmap | ViewConfigBuilderPropId::viewClass> &>(*this);
}
/**
Specifies a background color that a view for the component should have.
@param c A background color to set
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &backgroundColor(NS_RELEASES_ARGUMENT UIColor *c)
{
constexpr auto backgroundColorOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!backgroundColorOverridesExistingViewConfiguration,
"Setting 'backgroundColor' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'backgroundColor' without setting 'viewClass' first");
_attributes.insert({ @selector(setBackgroundColor:), c });
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
/**
Specifies whether a view for the component should ignore user events.
@param enabled A Boolean value that determines whether user events are ignored
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &userInteractionEnabled(bool enabled)
{
constexpr auto userInteractionEnabledOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!userInteractionEnabledOverridesExistingViewConfiguration,
"Setting 'userInteractionEnabled' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'userInteractionEnabled' without setting 'viewClass' first");
_attributes.insert({ @selector(setUserInteractionEnabled:), enabled });
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
/**
Specifies whether subviews of a view for the component should be confined to its bounds.
@param clip A Boolean value that determines whether subviews are confined
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &clipsToBounds(bool clip)
{
constexpr auto clipsToBoundsOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!clipsToBoundsOverridesExistingViewConfiguration,
"Setting 'clipsToBounds' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'clipsToBounds' without setting 'viewClass' first");
_attributes.insert({ @selector(setClipsToBounds:), clip });
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
/**
Specifies the alpha value for this component's view.
@param a A floating-point number in the range 0.0 to 1.0, where 0.0 represents totally transparent and 1.0 represents
totally opaque.
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &alpha(CGFloat a)
{
constexpr auto alphaOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!alphaOverridesExistingViewConfiguration, "Setting 'alpha' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'alpha' without setting 'viewClass' first");
_attributes.insert({ @selector(setAlpha:), a });
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
/**
Specifies the width of the border for this component's view.
@param w The border width. When this value is greater than 0.0, the view draws a border using the current borderColor
value.
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &borderWidth(CGFloat w)
{
constexpr auto borderWidthOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!borderWidthOverridesExistingViewConfiguration,
"Setting 'borderWidth' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'borderWidth' without setting 'viewClass' first");
_attributes.insert({ CKComponentViewAttribute::LayerAttribute(@selector(setBorderWidth:)), w });
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
/**
Specifies the color of the border for this component's view.
@param c A @c UIColor value that determines the border color. The default value of this property is an opaque black.
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &borderColor(NS_RELEASES_ARGUMENT UIColor *c)
{
constexpr auto borderColorOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!borderColorOverridesExistingViewConfiguration,
"Setting 'borderColor' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'borderColor' without setting 'viewClass' first");
_attributes.insert({ CKComponentViewAttribute::LayerAttribute(@selector(setBorderColor:)), (id)c.CGColor });
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
/**
Specifies the radius to use when drawing rounded corners for the component's view background.
@param r A floating point value that determines the radius. Setting the radius to a value greater than 0.0 causes the
view to begin drawing rounded corners on its background.
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &cornerRadius(CGFloat r)
{
constexpr auto cornerRadiusOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!cornerRadiusOverridesExistingViewConfiguration,
"Setting 'cornerRadius' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'cornerRadius' without setting 'viewClass' first");
_attributes.insert({ CKComponentViewAttribute::LayerAttribute(@selector(setCornerRadius:)), r });
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
/**
Specifies the action that should be sent when the user performs a single tap gesture on the component's view.
@param a A @c CKAction instance to be sent when the gesture is recognized.
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &onTap(CKAction<UIGestureRecognizer *> a)
{
constexpr auto onTapOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!onTapOverridesExistingViewConfiguration, "Setting 'onTap' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'onTap' without setting 'viewClass' first");
_attributes.insert(CKComponentTapGestureAttribute(a));
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
/**
Used to determine how a view lays out its content when its bounds change. The default is @c UIViewContentModeScaleToFill .
@param m A mode to set.
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &contentMode(UIViewContentMode m)
{
constexpr auto contentModeOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!contentModeOverridesExistingViewConfiguration, "Setting 'contentMode' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'contentMode' without setting 'viewClass' first");
_attributes.insert({@selector(setContentMode:), m});
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
/**
Sets a value for an arbitrary view property by specifying a selector that corresponds to the property setter and the
value.
@param attr A selector corresponding to a setter.
@param value A value to set.
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &attribute(SEL attr, NS_RELEASES_ARGUMENT id value)
{
constexpr auto attributeOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!attributeOverridesExistingViewConfiguration,
"Setting 'attribute' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'attribute' without setting 'viewClass' first");
_attributes.insert({attr, value});
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
/**
Sets a value for an arbitrary view property by specifying a complete attribute descriptor and a value.
@param attr An view attribute descriptor.
@param value A value to set. Both expressions of boxable types, such as @c int or @c CGFloat, and ObjC objects are
supported.
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &attribute(const CKComponentViewAttribute &attr, const CKBoxedValue &value)
{
constexpr auto attributeOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!attributeOverridesExistingViewConfiguration,
"Setting 'attribute' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'attribute' without setting 'viewClass' first");
_attributes.insert({attr, value});
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
/**
Adds a complete attribute / value pair to the component view configuration, mostly for convenience for helper
functions that return both an attribute and a value.
@param v An attribute / value pair.
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &attribute(const CKComponentViewAttributeValue &v)
{
constexpr auto attributeOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!attributeOverridesExistingViewConfiguration,
"Setting 'attribute' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'attributeValue' without setting 'viewClass' first");
_attributes.insert(v);
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
/**
Specifies a selector that should be sent up the responder chain when the component's view receives a 'touch up
inside' event.
@param action An selector to send.
@note Setting this property on a view that is not a @c UIControl subclass will trigger a runtime error.
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &onTouchUpInside(SEL action)
{
constexpr auto onTouchUpInsideOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!onTouchUpInsideOverridesExistingViewConfiguration,
"Setting 'onTouchUpInside' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'onTouchUpInside' without setting 'viewClass' first");
_attributes.insert(CKComponentActionAttribute(action));
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
/**
Specifies an action that should be sent when the component's view receives a 'touch up inside' event.
@param action An action to send.
@note Setting this property on a view that is not a @c UIControl subclass will trigger a runtime error.
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &onTouchUpInside(const CKAction<UIEvent *> &action)
{
constexpr auto onTouchUpInsideOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!onTouchUpInsideOverridesExistingViewConfiguration,
"Setting 'onTouchUpInside' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'onTouchUpInside' without setting 'viewClass' first");
_attributes.insert(CKComponentActionAttribute(action));
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
/**
Specifies a selector that should be sent up the responder chain when the component's view receives any event that
matches a given event mask.
@param events Events that should trigger the action.
@param action An selector to send.
@note Setting this property on a view that is not a @c UIControl subclass will trigger a runtime error.
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &onControlEvents(UIControlEvents events, SEL action)
{
constexpr auto onControlEventsOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!onControlEventsOverridesExistingViewConfiguration,
"Setting 'onControlEvents' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'onControlEvents' without setting 'viewClass' first");
_attributes.insert(CKComponentActionAttribute(action, events));
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
/**
Specifies an action that should be sent when the component's view receives any event that matches a given event mask.
@param events Events that should trigger the action.
@param action An action to send.
@note Setting this property on a view that is not a @c UIControl subclass will trigger a runtime error.
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &onControlEvents(UIControlEvents events, const CKAction<UIEvent *> &action)
{
constexpr auto onControlEventsOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!onControlEventsOverridesExistingViewConfiguration,
"Setting 'onControlEvents' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'onControlEvents' without setting 'viewClass' first");
_attributes.insert(CKComponentActionAttribute(action, events));
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
/**
Specifies a complete attribute map for a view of this component.
@param a The attribute map to set.
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &attributes(CKViewComponentAttributeValueMap a)
{
constexpr auto attributesOverrideExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!attributesOverrideExistingViewConfiguration,
"Setting 'attributes' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'attributes' without setting 'viewClass' first");
_attributes = std::move(a);
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
/**
Specifies an accessibility configuration for a view of this component, which will be applied when accessibility is
enabled.
@param c Accessibility configuration to set.
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &accessibilityContext(RCAccessibilityContext c)
{
constexpr auto accessibilityContextOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!accessibilityContextOverridesExistingViewConfiguration,
"Setting 'accessibilityContext' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'accessibilityContext' without setting 'viewClass' first");
_accessibilityCtx = std::move(c);
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
/**
Specifies if changes to the component size can be animated automatically by ComponentKit infrastructure.
@param b A Boolean value that determines if such animations are blocked.
@note Calling this method on a builder that does not have a view class set will trigger a compilation error.
@note Calling this method on a builder that already has a complete view configuration set will trigger
a compilation error.
*/
auto &blockImplicitAnimations(bool b)
{
constexpr auto blockImplicitAnimationsOverridesExistingViewConfiguration =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig);
static_assert(!blockImplicitAnimationsOverridesExistingViewConfiguration,
"Setting 'blockImplicitAnimations' overrides existing view configuration");
constexpr auto viewClassIsSet = PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(viewClassIsSet, "Cannot set 'blockImplicitAnimations' without setting 'viewClass' first");
_blockImplicitAnimations = b;
return reinterpret_cast<Derived<PropsBitmap> &>(*this);
}
protected:
CKComponentViewClass _viewClass;
CKViewComponentAttributeValueMap _attributes;
RCAccessibilityContext _accessibilityCtx;
bool _blockImplicitAnimations;
};
template <PropsBitmapType PropsBitmap = 0>
class __attribute__((__may_alias__)) ViewConfigBuilder : public ViewConfigBuilderBase<ViewConfigBuilder, PropsBitmap> {
public:
auto build() noexcept -> CKComponentViewConfiguration {
return {
std::move(this->_viewClass),
std::move(this->_attributes),
std::move(this->_accessibilityCtx),
this->_blockImplicitAnimations
};
}
};
namespace ComponentBuilderBaseSizeOnlyPropId {
constexpr static auto size = BuilderBasePropId::__max << 1;
constexpr static auto __max = size;
}
template <template <PropsBitmapType> class Derived, PropsBitmapType PropsBitmap>
class __attribute__((__may_alias__)) ComponentBuilderBaseSizeOnly : public BuilderBase<Derived, PropsBitmap> {
public:
ComponentBuilderBaseSizeOnly() = default;
ComponentBuilderBaseSizeOnly(const CK::ComponentSpecContext& context)
: BuilderBase<Derived, PropsBitmap>{context} { }
~ComponentBuilderBaseSizeOnly() = default;
/**
The width of the component relative to its parent's size.
*/
auto &width(RCRelativeDimension w)
{
_size.width = w;
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBaseSizeOnlyPropId::size> &>(*this);
}
/**
The width of the component.
*/
auto &width(CGFloat w)
{
_size.width = w;
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBaseSizeOnlyPropId::size> &>(*this);
}
/**
The height of the component relative to its parent's size.
*/
auto &height(RCRelativeDimension h)
{
_size.height = h;
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBaseSizeOnlyPropId::size> &>(*this);
}
/**
The height of the component.
*/
auto &height(CGFloat h)
{
_size.height = h;
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBaseSizeOnlyPropId::size> &>(*this);
}
/**
The minumum allowable width of the component relative to its parent's size.
*/
auto &minWidth(RCRelativeDimension w)
{
_size.minWidth = w;
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBaseSizeOnlyPropId::size> &>(*this);
}
/**
The minumum allowable height of the component relative to its parent's size.
*/
auto &minHeight(RCRelativeDimension h)
{
_size.minHeight = h;
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBaseSizeOnlyPropId::size> &>(*this);
}
/**
The maximum allowable width of the component relative to its parent's size.
*/
auto &maxWidth(RCRelativeDimension w)
{
_size.maxWidth = w;
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBaseSizeOnlyPropId::size> &>(*this);
}
/**
The maximum allowable height of the component relative to its parent's size.
*/
auto &maxHeight(RCRelativeDimension h)
{
_size.maxHeight = h;
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBaseSizeOnlyPropId::size> &>(*this);
}
/**
Specifies a size constraint that should apply to this component.
*/
auto &size(RCComponentSize &&s)
{
_size = std::move(s);
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBaseSizeOnlyPropId::size> &>(*this);
}
/**
Specifies a size constraint that should apply to this component.
*/
auto &size(const RCComponentSize &s)
{
_size = s;
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBaseSizeOnlyPropId::size> &>(*this);
}
/**
Specifies a size constraint that should apply to this component.
*/
auto &size(const CGSize &s)
{
_size = RCComponentSize::fromCGSize(s);
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBaseSizeOnlyPropId::size> &>(*this);
}
protected:
RCComponentSize _size;
};
namespace ComponentBuilderBasePropId {
constexpr static auto size = ViewConfigBuilderPropId::__max << 1;
constexpr static auto __max = size;
}
template <template <PropsBitmapType> class Derived, PropsBitmapType PropsBitmap>
class __attribute__((__may_alias__)) ComponentBuilderBase : public ViewConfigBuilderBase<Derived, PropsBitmap>, public BuilderBase<Derived, PropsBitmap> {
protected:
ComponentBuilderBase() = default;
ComponentBuilderBase(const CK::ComponentSpecContext& context)
: BuilderBase<Derived, PropsBitmap>{context} { }
ComponentBuilderBase(const ComponentBuilderBase &) = default;
auto operator=(const ComponentBuilderBase &) -> ComponentBuilderBase& = default;
public:
/**
Specifies a complete view configuration which will be used to create a view for the component.
@param c A struct describing the view for this component.
@note Calling this method on a builder that already has a view class or any of the view properties set will trigger
a compilation error.
@note This method only accepts temporaries as its argument. If you need to pass an existing variable use
@c std::move().
*/
auto &view(CKComponentViewConfiguration &&c)
{
constexpr auto viewConfigurationOverridesExistingViewClass =
PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass);
static_assert(!viewConfigurationOverridesExistingViewClass,
"Setting view configuration overrides existing view class");
_viewConfig = std::move(c);
return reinterpret_cast<Derived<PropsBitmap | ViewConfigBuilderPropId::viewConfig> &>(*this);
}
/**
The width of the component relative to its parent's size.
*/
auto &width(RCRelativeDimension w)
{
_size.width = w;
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBasePropId::size> &>(*this);
}
/**
The width of the component.
*/
auto &width(CGFloat w)
{
_size.width = w;
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBasePropId::size> &>(*this);
}
/**
The height of the component relative to its parent's size.
*/
auto &height(RCRelativeDimension h)
{
_size.height = h;
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBasePropId::size> &>(*this);
}
/**
The height of the component.
*/
auto &height(CGFloat h)
{
_size.height = h;
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBasePropId::size> &>(*this);
}
/**
The minumum allowable width of the component relative to its parent's size.
*/
auto &minWidth(RCRelativeDimension w)
{
_size.minWidth = w;
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBasePropId::size> &>(*this);
}
/**
The minumum allowable height of the component relative to its parent's size.
*/
auto &minHeight(RCRelativeDimension h)
{
_size.minHeight = h;
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBasePropId::size> &>(*this);
}
/**
The maximum allowable width of the component relative to its parent's size.
*/
auto &maxWidth(RCRelativeDimension w)
{
_size.maxWidth = w;
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBasePropId::size> &>(*this);
}
/**
The maximum allowable height of the component relative to its parent's size.
*/
auto &maxHeight(RCRelativeDimension h)
{
_size.maxHeight = h;
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBasePropId::size> &>(*this);
}
/**
Specifies a size constraint that should apply to this component.
*/
auto &size(RCComponentSize &&s)
{
_size = std::move(s);
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBasePropId::size> &>(*this);
}
/**
Specifies a size constraint that should apply to this component.
*/
auto &size(const RCComponentSize &s)
{
_size = s;
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBasePropId::size> &>(*this);
}
/**
Specifies a size constraint that should apply to this component.
*/
auto &size(const CGSize &s)
{
_size = RCComponentSize::fromCGSize(s);
return reinterpret_cast<Derived<PropsBitmap | ComponentBuilderBasePropId::size> &>(*this);
}
protected:
CKComponentViewConfiguration _viewConfig{};
RCComponentSize _size{};
};
template <PropsBitmapType = 0>
class ComponentBuilder;
}
using ComponentBuilderEmpty = BuilderDetails::ComponentBuilder<>;
using ComponentBuilderContext = BuilderDetails::ComponentBuilder<BuilderDetails::BuilderBasePropId::context>;
/**
Provides a fluent API for creating instances of @c CKComponent base class.
@example A component that renders a red square:
@code
CK::ComponentBuilder()
.viewClass([UIView class])
.backgroundColor(UIColor.redColor)
.width(100)
.height(100)
.build()
*/
auto ComponentBuilder() -> ComponentBuilderEmpty;
/**
Provides a fluent API for creating instances of @c CKComponent base class.
@param c The spec context to use.
@note This factory overload is to be used when a key is required to reference the built component in a spec from the
`CK_ANIMATION` function.
@example A component that renders a red square:
@code
CK::ComponentBuilder(context)
.key(@"my_child")
.viewClass([UIView class])
.backgroundColor(UIColor.redColor)
.width(100)
.height(100)
.build()
*/
auto ComponentBuilder(const CK::ComponentSpecContext& c) -> ComponentBuilderContext;
using ViewConfig = BuilderDetails::ViewConfigBuilder<>;
namespace BuilderDetails {
template <PropsBitmapType PropsBitmap>
class __attribute__((__may_alias__)) ComponentBuilder : public ComponentBuilderBase<ComponentBuilder, PropsBitmap> {
public:
~ComponentBuilder() = default;
private:
ComponentBuilder() = default;
ComponentBuilder(const CK::ComponentSpecContext& context)
: ComponentBuilderBase<ComponentBuilder, PropsBitmap>{context} { }
friend auto CK::ComponentBuilder() -> ComponentBuilderEmpty;
friend auto CK::ComponentBuilder(const CK::ComponentSpecContext&) -> ComponentBuilderContext;
template <template <PropsBitmapType> class, PropsBitmapType>
friend class BuilderBase;
/**
Creates a new component instance with specified properties.
@note This method must @b not be called more than once on a given component builder instance.
*/
NS_RETURNS_RETAINED auto _build() noexcept -> CKComponent *
{
if (PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig, ComponentBuilderBasePropId::size)) {
return [CKComponent newWithView:this->_viewConfig size:this->_size];
} else if (PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass, ComponentBuilderBasePropId::size)) {
return [CKComponent newWithView:{std::move(this->_viewClass),
std::move(this->_attributes),
std::move(this->_accessibilityCtx),
this->_blockImplicitAnimations}
size:this->_size];
} else if (PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewConfig)) {
return [CKComponent newWithView:this->_viewConfig size:{}];
} else if (PropBitmap::isSet(PropsBitmap, ViewConfigBuilderPropId::viewClass)) {
return [CKComponent newWithView:{std::move(this->_viewClass),
std::move(this->_attributes),
std::move(this->_accessibilityCtx),
this->_blockImplicitAnimations}
size:{}];
} else if (PropBitmap::isSet(PropsBitmap, ComponentBuilderBasePropId::size)) {
return [CKComponent newWithView:{} size:this->_size];
} else {
return [CKComponent new];
}
}
};
}
}
#endif