diff --git a/.rive_head b/.rive_head index ca5ae4a3..6eef3f70 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -3dd8f44cf2d303d43c1aea514be6b0382a0c4bb9 +c23d37a730a0496a4923bd85b8ffe0af90b738d7 diff --git a/dev/defs/data_bind/converters/data_converter_interpolator.json b/dev/defs/data_bind/converters/data_converter_interpolator.json new file mode 100644 index 00000000..dd43bf99 --- /dev/null +++ b/dev/defs/data_bind/converters/data_converter_interpolator.json @@ -0,0 +1,40 @@ +{ + "name": "DataConverterInterpolator", + "key": { + "int": 534, + "string": "dataconverterinterpolator" + }, + "extends": "data_bind/converters/data_converter.json", + "properties": { + "interpolationType": { + "type": "uint", + "initialValue": "1", + "key": { + "int": 757, + "string": "interpolationtype" + }, + "description": "The type of interpolation index in KeyframeInterpolation applied to this layout." + }, + "interpolatorId": { + "type": "Id", + "typeRuntime": "uint", + "initialValue": "Core.missingId", + "initialValueRuntime": "-1", + "key": { + "int": 758, + "string": "interpolatorid" + }, + "description": "The id of the custom interpolator used when interpolation is Cubic." + }, + "duration": { + "type": "double", + "initialValue": "1", + "key": { + "int": 756, + "string": "duration" + }, + "description": "Duration of the interpolation", + "bindable": true + } + } +} \ No newline at end of file diff --git a/include/rive/data_bind/converters/data_converter.hpp b/include/rive/data_bind/converters/data_converter.hpp index a721c091..4db1e399 100644 --- a/include/rive/data_bind/converters/data_converter.hpp +++ b/include/rive/data_bind/converters/data_converter.hpp @@ -27,6 +27,7 @@ class DataConverter : public DataConverterBase void addDirt(ComponentDirt dirt); virtual void update(); void copy(const DataConverter& object); + virtual bool advance(float elapsedTime); private: std::vector m_dataBinds; diff --git a/include/rive/data_bind/converters/data_converter_group.hpp b/include/rive/data_bind/converters/data_converter_group.hpp index 1d8c4d84..ae2244ff 100644 --- a/include/rive/data_bind/converters/data_converter_group.hpp +++ b/include/rive/data_bind/converters/data_converter_group.hpp @@ -25,6 +25,7 @@ class DataConverterGroup : public DataConverterGroupBase Core* clone() const override; void bindFromContext(DataContext* dataContext, DataBind* dataBind) override; void update() override; + bool advance(float elapsedSeconds) override; private: std::vector m_items; diff --git a/include/rive/data_bind/converters/data_converter_interpolator.hpp b/include/rive/data_bind/converters/data_converter_interpolator.hpp new file mode 100644 index 00000000..7135d005 --- /dev/null +++ b/include/rive/data_bind/converters/data_converter_interpolator.hpp @@ -0,0 +1,52 @@ +#ifndef _RIVE_DATA_CONVERTER_INTERPOLATOR_HPP_ +#define _RIVE_DATA_CONVERTER_INTERPOLATOR_HPP_ +#include "rive/generated/data_bind/converters/data_converter_interpolator_base.hpp" +#include "rive/data_bind/data_values/data_value_number.hpp" +#include "rive/data_bind/data_values/data_value.hpp" +#include "rive/animation/keyframe_interpolator.hpp" +#include +namespace rive +{ + +struct InterpolatorAnimationData +{ + float elapsedSeconds = 0.0f; + float from; + float to; + float interpolate(float f) const + { + float fi = 1.0f - f; + return to * f + from * fi; + } + void copy(const InterpolatorAnimationData& source); +}; +class DataConverterInterpolator : public DataConverterInterpolatorBase +{ +protected: + KeyFrameInterpolator* m_interpolator = nullptr; + +public: + void interpolator(KeyFrameInterpolator* interpolator); + KeyFrameInterpolator* interpolator() const { return m_interpolator; }; + DataType outputType() override { return DataType::number; }; + DataValue* convert(DataValue* value, DataBind* dataBind) override; + DataValue* reverseConvert(DataValue* value, DataBind* dataBind) override; + bool advance(float elapsedTime) override; + void copy(const DataConverterInterpolatorBase& object); + +private: + DataValueNumber m_output; + float m_currentValue; + bool m_isFirstRun = true; + + InterpolatorAnimationData m_animationDataA; + InterpolatorAnimationData m_animationDataB; + bool m_isSmoothingAnimation = false; + InterpolatorAnimationData* currentAnimationData(); + void advanceAnimationData(float elapsedTime); + +public: +}; +} // namespace rive + +#endif \ No newline at end of file diff --git a/include/rive/data_bind/data_bind.hpp b/include/rive/data_bind/data_bind.hpp index e0c24f50..7908dacc 100644 --- a/include/rive/data_bind/data_bind.hpp +++ b/include/rive/data_bind/data_bind.hpp @@ -34,6 +34,7 @@ class DataBind : public DataBindBase ViewModelInstanceValue* source() const { return m_Source; }; bool toSource(); bool toTarget(); + bool advance(float elapsedTime); protected: ComponentDirt m_Dirt = ComponentDirt::Filthy; diff --git a/include/rive/generated/core_registry.hpp b/include/rive/generated/core_registry.hpp index a221f568..968916d1 100644 --- a/include/rive/generated/core_registry.hpp +++ b/include/rive/generated/core_registry.hpp @@ -133,6 +133,7 @@ #include "rive/data_bind/converters/data_converter.hpp" #include "rive/data_bind/converters/data_converter_group.hpp" #include "rive/data_bind/converters/data_converter_group_item.hpp" +#include "rive/data_bind/converters/data_converter_interpolator.hpp" #include "rive/data_bind/converters/data_converter_operation.hpp" #include "rive/data_bind/converters/data_converter_operation_value.hpp" #include "rive/data_bind/converters/data_converter_operation_viewmodel.hpp" @@ -549,6 +550,8 @@ class CoreRegistry return new DataConverterSystemDegsToRads(); case DataConverterRangeMapperBase::typeKey: return new DataConverterRangeMapper(); + case DataConverterInterpolatorBase::typeKey: + return new DataConverterInterpolator(); case DataConverterSystemNormalizerBase::typeKey: return new DataConverterSystemNormalizer(); case DataConverterGroupItemBase::typeKey: @@ -1262,6 +1265,14 @@ class CoreRegistry case DataConverterRangeMapperBase::flagsPropertyKey: object->as()->flags(value); break; + case DataConverterInterpolatorBase::interpolationTypePropertyKey: + object->as()->interpolationType( + value); + break; + case DataConverterInterpolatorBase::interpolatorIdPropertyKey: + object->as()->interpolatorId( + value); + break; case DataConverterGroupItemBase::converterIdPropertyKey: object->as()->converterId(value); break; @@ -1891,6 +1902,9 @@ class CoreRegistry case DataConverterRangeMapperBase::maxOutputPropertyKey: object->as()->maxOutput(value); break; + case DataConverterInterpolatorBase::durationPropertyKey: + object->as()->duration(value); + break; case BindablePropertyNumberBase::propertyValuePropertyKey: object->as()->propertyValue(value); break; @@ -2501,6 +2515,12 @@ class CoreRegistry ->interpolatorId(); case DataConverterRangeMapperBase::flagsPropertyKey: return object->as()->flags(); + case DataConverterInterpolatorBase::interpolationTypePropertyKey: + return object->as() + ->interpolationType(); + case DataConverterInterpolatorBase::interpolatorIdPropertyKey: + return object->as() + ->interpolatorId(); case DataConverterGroupItemBase::converterIdPropertyKey: return object->as()->converterId(); case DataConverterRounderBase::decimalsPropertyKey: @@ -2942,6 +2962,8 @@ class CoreRegistry return object->as()->minOutput(); case DataConverterRangeMapperBase::maxOutputPropertyKey: return object->as()->maxOutput(); + case DataConverterInterpolatorBase::durationPropertyKey: + return object->as()->duration(); case BindablePropertyNumberBase::propertyValuePropertyKey: return object->as() ->propertyValue(); @@ -3232,6 +3254,8 @@ class CoreRegistry case DataConverterRangeMapperBase::interpolationTypePropertyKey: case DataConverterRangeMapperBase::interpolatorIdPropertyKey: case DataConverterRangeMapperBase::flagsPropertyKey: + case DataConverterInterpolatorBase::interpolationTypePropertyKey: + case DataConverterInterpolatorBase::interpolatorIdPropertyKey: case DataConverterGroupItemBase::converterIdPropertyKey: case DataConverterRounderBase::decimalsPropertyKey: case DataConverterStringPadBase::lengthPropertyKey: @@ -3438,6 +3462,7 @@ class CoreRegistry case DataConverterRangeMapperBase::maxInputPropertyKey: case DataConverterRangeMapperBase::minOutputPropertyKey: case DataConverterRangeMapperBase::maxOutputPropertyKey: + case DataConverterInterpolatorBase::durationPropertyKey: case BindablePropertyNumberBase::propertyValuePropertyKey: case NestedArtboardLeafBase::alignmentXPropertyKey: case NestedArtboardLeafBase::alignmentYPropertyKey: @@ -3890,6 +3915,10 @@ class CoreRegistry return object->is(); case DataConverterRangeMapperBase::flagsPropertyKey: return object->is(); + case DataConverterInterpolatorBase::interpolationTypePropertyKey: + return object->is(); + case DataConverterInterpolatorBase::interpolatorIdPropertyKey: + return object->is(); case DataConverterGroupItemBase::converterIdPropertyKey: return object->is(); case DataConverterRounderBase::decimalsPropertyKey: @@ -4294,6 +4323,8 @@ class CoreRegistry return object->is(); case DataConverterRangeMapperBase::maxOutputPropertyKey: return object->is(); + case DataConverterInterpolatorBase::durationPropertyKey: + return object->is(); case BindablePropertyNumberBase::propertyValuePropertyKey: return object->is(); case NestedArtboardLeafBase::alignmentXPropertyKey: diff --git a/include/rive/generated/data_bind/converters/data_converter_interpolator_base.hpp b/include/rive/generated/data_bind/converters/data_converter_interpolator_base.hpp new file mode 100644 index 00000000..ceaec48c --- /dev/null +++ b/include/rive/generated/data_bind/converters/data_converter_interpolator_base.hpp @@ -0,0 +1,108 @@ +#ifndef _RIVE_DATA_CONVERTER_INTERPOLATOR_BASE_HPP_ +#define _RIVE_DATA_CONVERTER_INTERPOLATOR_BASE_HPP_ +#include "rive/core/field_types/core_double_type.hpp" +#include "rive/core/field_types/core_uint_type.hpp" +#include "rive/data_bind/converters/data_converter.hpp" +namespace rive +{ +class DataConverterInterpolatorBase : public DataConverter +{ +protected: + typedef DataConverter Super; + +public: + static const uint16_t typeKey = 534; + + /// Helper to quickly determine if a core object extends another without + /// RTTI at runtime. + bool isTypeOf(uint16_t typeKey) const override + { + switch (typeKey) + { + case DataConverterInterpolatorBase::typeKey: + case DataConverterBase::typeKey: + return true; + default: + return false; + } + } + + uint16_t coreType() const override { return typeKey; } + + static const uint16_t interpolationTypePropertyKey = 757; + static const uint16_t interpolatorIdPropertyKey = 758; + static const uint16_t durationPropertyKey = 756; + +protected: + uint32_t m_InterpolationType = 1; + uint32_t m_InterpolatorId = -1; + float m_Duration = 1.0f; + +public: + inline uint32_t interpolationType() const { return m_InterpolationType; } + void interpolationType(uint32_t value) + { + if (m_InterpolationType == value) + { + return; + } + m_InterpolationType = value; + interpolationTypeChanged(); + } + + inline uint32_t interpolatorId() const { return m_InterpolatorId; } + void interpolatorId(uint32_t value) + { + if (m_InterpolatorId == value) + { + return; + } + m_InterpolatorId = value; + interpolatorIdChanged(); + } + + inline float duration() const { return m_Duration; } + void duration(float value) + { + if (m_Duration == value) + { + return; + } + m_Duration = value; + durationChanged(); + } + + Core* clone() const override; + void copy(const DataConverterInterpolatorBase& object) + { + m_InterpolationType = object.m_InterpolationType; + m_InterpolatorId = object.m_InterpolatorId; + m_Duration = object.m_Duration; + DataConverter::copy(object); + } + + bool deserialize(uint16_t propertyKey, BinaryReader& reader) override + { + switch (propertyKey) + { + case interpolationTypePropertyKey: + m_InterpolationType = CoreUintType::deserialize(reader); + return true; + case interpolatorIdPropertyKey: + m_InterpolatorId = CoreUintType::deserialize(reader); + return true; + case durationPropertyKey: + m_Duration = CoreDoubleType::deserialize(reader); + return true; + } + return DataConverter::deserialize(propertyKey, reader); + } + +protected: + virtual void interpolationTypeChanged() {} + virtual void interpolatorIdChanged() {} + virtual void durationChanged() {} +}; +} // namespace rive + +#endif \ No newline at end of file diff --git a/src/artboard.cpp b/src/artboard.cpp index 3144b5d5..dfcedcb7 100644 --- a/src/artboard.cpp +++ b/src/artboard.cpp @@ -903,6 +903,13 @@ bool Artboard::advanceInternal(float elapsedSeconds, AdvanceFlags flags) didUpdate = true; } } + for (auto dataBind : m_AllDataBinds) + { + if (dataBind->advance(elapsedSeconds)) + { + didUpdate = true; + } + } return didUpdate; } diff --git a/src/data_bind/converters/data_converter.cpp b/src/data_bind/converters/data_converter.cpp index d088da98..5ceed403 100644 --- a/src/data_bind/converters/data_converter.cpp +++ b/src/data_bind/converters/data_converter.cpp @@ -78,4 +78,6 @@ void DataConverter::copy(const DataConverter& object) addDataBind(dataBindClone); } DataConverterBase::copy(object); -} \ No newline at end of file +} + +bool DataConverter::advance(float elapsedTime) { return false; } \ No newline at end of file diff --git a/src/data_bind/converters/data_converter_group.cpp b/src/data_bind/converters/data_converter_group.cpp index a4763234..e3465484 100644 --- a/src/data_bind/converters/data_converter_group.cpp +++ b/src/data_bind/converters/data_converter_group.cpp @@ -7,7 +7,7 @@ using namespace rive; DataConverterGroup::~DataConverterGroup() { - for (auto item : m_items) + for (auto& item : m_items) { delete item; } @@ -21,9 +21,12 @@ void DataConverterGroup::addItem(DataConverterGroupItem* item) DataValue* DataConverterGroup::convert(DataValue* input, DataBind* dataBind) { DataValue* value = input; - for (auto item : m_items) + for (auto& item : m_items) { - value = item->converter()->convert(value, dataBind); + if (item->converter() != nullptr) + { + value = item->converter()->convert(value, dataBind); + } } return value; } @@ -34,7 +37,10 @@ DataValue* DataConverterGroup::reverseConvert(DataValue* input, DataValue* value = input; for (auto it = m_items.rbegin(); it != m_items.rend(); ++it) { - value = (*it)->converter()->reverseConvert(value, dataBind); + if ((*it)->converter() != nullptr) + { + value = (*it)->converter()->reverseConvert(value, dataBind); + } } return value; } @@ -42,8 +48,12 @@ DataValue* DataConverterGroup::reverseConvert(DataValue* input, Core* DataConverterGroup::clone() const { auto cloned = DataConverterGroupBase::clone()->as(); - for (auto item : m_items) + for (auto& item : m_items) { + if (item->converter() == nullptr) + { + continue; + } auto clonedItem = item->clone()->as(); cloned->addItem(clonedItem); } @@ -53,7 +63,7 @@ Core* DataConverterGroup::clone() const void DataConverterGroup::bindFromContext(DataContext* dataContext, DataBind* dataBind) { - for (auto item : m_items) + for (auto& item : m_items) { auto converter = item->converter(); if (converter != nullptr) @@ -65,7 +75,7 @@ void DataConverterGroup::bindFromContext(DataContext* dataContext, void DataConverterGroup::update() { - for (auto item : m_items) + for (auto& item : m_items) { auto converter = item->converter(); if (converter != nullptr) @@ -73,4 +83,21 @@ void DataConverterGroup::update() converter->update(); } } +} + +bool DataConverterGroup::advance(float elapsedSeconds) +{ + bool didUpdate = false; + for (auto& item : m_items) + { + auto converter = item->converter(); + if (converter != nullptr) + { + if (converter->advance(elapsedSeconds)) + { + didUpdate = true; + } + } + } + return didUpdate; } \ No newline at end of file diff --git a/src/data_bind/converters/data_converter_interpolator.cpp b/src/data_bind/converters/data_converter_interpolator.cpp new file mode 100644 index 00000000..3c80176a --- /dev/null +++ b/src/data_bind/converters/data_converter_interpolator.cpp @@ -0,0 +1,149 @@ +#include "rive/math/math_types.hpp" +#include "rive/data_bind/converters/data_converter_interpolator.hpp" +#include "rive/data_bind/data_values/data_value_number.hpp" + +using namespace rive; + +void DataConverterInterpolator::copy( + const DataConverterInterpolatorBase& object) +{ + interpolator(object.as()->interpolator()); + DataConverterInterpolatorBase::copy(object); +} + +InterpolatorAnimationData* DataConverterInterpolator::currentAnimationData() +{ + return m_isSmoothingAnimation ? &m_animationDataB : &m_animationDataA; +} + +void DataConverterInterpolator::interpolator(KeyFrameInterpolator* interpolator) +{ + m_interpolator = interpolator; +} + +void DataConverterInterpolator::advanceAnimationData(float elapsedTime) +{ + auto animationData = currentAnimationData(); + if (m_isSmoothingAnimation) + { + float f = std::fmin(1.0f, + duration() > 0 + ? m_animationDataA.elapsedSeconds / duration() + : 1.0f); + if (m_interpolator != nullptr) + { + f = m_interpolator->transform(f); + } + m_animationDataB.from = m_animationDataA.interpolate(f); + if (f == 1) + { + m_animationDataA.copy(m_animationDataB); + m_isSmoothingAnimation = false; + } + else + { + m_animationDataA.elapsedSeconds += elapsedTime; + } + } + if (animationData->elapsedSeconds >= duration()) + { + m_currentValue = animationData->to; + + if (m_isSmoothingAnimation) + { + m_isSmoothingAnimation = false; + m_animationDataA.copy(m_animationDataB); + m_animationDataA.elapsedSeconds = m_animationDataB.elapsedSeconds = + 0; + } + else + { + m_animationDataA.elapsedSeconds = 0; + } + return; + } + float f = std::fmin( + 1.0f, + duration() > 0 ? animationData->elapsedSeconds / duration() : 1.0f); + if (m_interpolator != nullptr) + { + f = m_interpolator->transform(f); + } + auto current = animationData->interpolate(f); + if (m_currentValue != current) + { + m_currentValue = current; + } + + animationData->elapsedSeconds += elapsedTime; +} + +bool DataConverterInterpolator::advance(float elapsedTime) +{ + auto animationData = currentAnimationData(); + if (animationData->to == m_currentValue) + { + return false; + } + advanceAnimationData(elapsedTime); + if (animationData->elapsedSeconds < duration()) + { + addDirt(ComponentDirt::Dependents); + return true; + } + return false; +} + +DataValue* DataConverterInterpolator::convert(DataValue* input, + DataBind* dataBind) +{ + if (input->is()) + { + auto animationData = currentAnimationData(); + auto numberInput = input->as(); + if (m_isFirstRun) + { + animationData->from = numberInput->value(); + animationData->to = numberInput->value(); + m_currentValue = numberInput->value(); + m_isFirstRun = false; + } + else + { + if (animationData->to != numberInput->value()) + { + if (animationData->elapsedSeconds != 0) + { + if (m_isSmoothingAnimation) + { + m_animationDataA.copy(m_animationDataB); + } + m_isSmoothingAnimation = true; + } + else + { + m_isSmoothingAnimation = false; + } + animationData = currentAnimationData(); + animationData->from = m_currentValue; + animationData->to = numberInput->value(); + animationData->elapsedSeconds = 0; + } + } + m_output.value(m_currentValue); + } + return &m_output; +} + +DataValue* DataConverterInterpolator::reverseConvert(DataValue* input, + DataBind* dataBind) +{ + return convert(input, dataBind); +} + +void InterpolatorAnimationData::copy(const InterpolatorAnimationData& source) +{ + from = source.from; + to = source.to; + elapsedSeconds = source.elapsedSeconds; +} diff --git a/src/data_bind/data_bind.cpp b/src/data_bind/data_bind.cpp index f69063a2..1b8e94a2 100644 --- a/src/data_bind/data_bind.cpp +++ b/src/data_bind/data_bind.cpp @@ -261,4 +261,13 @@ bool DataBind::toTarget() auto flagsValue = static_cast(flags()); return (flagsValue & DataBindFlags::TwoWay) != 0 || (flagsValue & DataBindFlags::ToSource) == 0; +} + +bool DataBind::advance(float elapsedTime) +{ + if (converter()) + { + return converter()->advance(elapsedTime); + } + return false; } \ No newline at end of file diff --git a/src/data_bind/data_context.cpp b/src/data_bind/data_context.cpp index ec177274..913f1741 100644 --- a/src/data_bind/data_context.cpp +++ b/src/data_bind/data_context.cpp @@ -30,8 +30,9 @@ ViewModelInstanceValue* DataContext::getViewModelProperty( if (viewModelInstanceValue != nullptr && viewModelInstanceValue->is()) { - instance = viewModelInstanceValue->as() - ->referenceViewModelInstance(); + instance = + viewModelInstanceValue->as() + ->referenceViewModelInstance(); } else { @@ -67,8 +68,9 @@ ViewModelInstance* DataContext::getViewModelInstance( if (viewModelInstanceValue != nullptr && viewModelInstanceValue->is()) { - instance = viewModelInstanceValue->as() - ->referenceViewModelInstance(); + instance = + viewModelInstanceValue->as() + ->referenceViewModelInstance(); } else { diff --git a/src/generated/data_bind/converters/data_converter_interpolator_base.cpp b/src/generated/data_bind/converters/data_converter_interpolator_base.cpp new file mode 100644 index 00000000..ad4607a7 --- /dev/null +++ b/src/generated/data_bind/converters/data_converter_interpolator_base.cpp @@ -0,0 +1,11 @@ +#include "rive/generated/data_bind/converters/data_converter_interpolator_base.hpp" +#include "rive/data_bind/converters/data_converter_interpolator.hpp" + +using namespace rive; + +Core* DataConverterInterpolatorBase::clone() const +{ + auto cloned = new DataConverterInterpolator(); + cloned->copy(*this); + return cloned; +} diff --git a/src/importers/backboard_importer.cpp b/src/importers/backboard_importer.cpp index 4c035c40..a0e7ab1f 100644 --- a/src/importers/backboard_importer.cpp +++ b/src/importers/backboard_importer.cpp @@ -11,6 +11,7 @@ #include "rive/data_bind/converters/data_converter.hpp" #include "rive/data_bind/converters/data_converter_group_item.hpp" #include "rive/data_bind/converters/data_converter_range_mapper.hpp" +#include "rive/data_bind/converters/data_converter_interpolator.hpp" #include "rive/data_bind/data_bind.hpp" #include @@ -103,6 +104,16 @@ StatusCode BackboardImporter::resolve() m_interpolators[converterId]); } } + else if (converter->is()) + { + size_t converterId = + converter->as()->interpolatorId(); + if (converterId != -1 && converterId < m_interpolators.size()) + { + converter->as()->interpolator( + m_interpolators[converterId]); + } + } } for (auto referencer : m_DataConverterGroupItemReferencers) { diff --git a/src/text/text.cpp b/src/text/text.cpp index 0e7a9c6e..9b559494 100644 --- a/src/text/text.cpp +++ b/src/text/text.cpp @@ -735,7 +735,11 @@ void Text::draw(Renderer* renderer) } if (clipResult != ClipResult::emptyClip) { - if (overflow() == TextOverflow::clipped && !m_clipPath.empty()) + // For now we need to check both empty() and hasRenderPath() in + // ShapePaintPath because the raw path gets cleared when the render path + // is created. + if (overflow() == TextOverflow::clipped && + (!m_clipPath.empty() || m_clipPath.hasRenderPath())) { renderer->clipPath(m_clipPath.renderPath(this)); }