'submodule/include/animation/state_machine.hpp',
'submodule/include/animation/state_machine_bool.hpp',
'submodule/include/animation/state_machine_component.hpp',
- 'submodule/include/animation/state_machine_double.hpp',
'submodule/include/animation/state_machine_input.hpp',
'submodule/include/animation/state_machine_layer.hpp',
'submodule/include/animation/state_machine_layer_component.hpp',
'submodule/include/animation/state_transition.hpp',
'submodule/include/animation/transition_bool_condition.hpp',
'submodule/include/animation/transition_condition.hpp',
- 'submodule/include/animation/transition_double_condition.hpp',
'submodule/include/animation/transition_trigger_condition.hpp',
'submodule/include/animation/transition_value_condition.hpp'
], subdir: 'rive/animation')
'submodule/include/generated/animation/state_machine_base.hpp',
'submodule/include/generated/animation/state_machine_bool_base.hpp',
'submodule/include/generated/animation/state_machine_component_base.hpp',
- 'submodule/include/generated/animation/state_machine_double_base.hpp',
'submodule/include/generated/animation/state_machine_input_base.hpp',
'submodule/include/generated/animation/state_machine_layer_base.hpp',
'submodule/include/generated/animation/state_machine_layer_component_base.hpp',
'submodule/include/generated/animation/state_transition_base.hpp',
'submodule/include/generated/animation/transition_bool_condition_base.hpp',
'submodule/include/generated/animation/transition_condition_base.hpp',
- 'submodule/include/generated/animation/transition_double_condition_base.hpp',
'submodule/include/generated/animation/transition_trigger_condition_base.hpp',
'submodule/include/generated/animation/transition_value_condition_base.hpp'
], subdir: 'rive/generated/animation')
'submodule/src/animation/linear_animation.cpp',
'submodule/src/animation/linear_animation_instance.cpp',
'submodule/src/animation/layer_state.cpp',
+ 'submodule/src/animation/state_machine.cpp',
'submodule/src/animation/state_machine_input.cpp',
+ 'submodule/src/animation/state_machine_input_instance.cpp',
+ 'submodule/src/animation/state_machine_instance.cpp',
'submodule/src/animation/state_machine_layer.cpp',
- 'submodule/src/animation/state_machine_trigger.cpp',
'submodule/src/animation/state_transition.cpp',
+ 'submodule/src/animation/transition_bool_condition.cpp',
'submodule/src/animation/transition_condition.cpp',
+ 'submodule/src/animation/transition_number_condition.cpp',
+ 'submodule/src/animation/transition_trigger_condition.cpp',
'submodule/src/core/binary_reader.cpp',
'submodule/src/core/field_types/core_bool_type.cpp',
'submodule/src/core/field_types/core_color_type.cpp',
'submodule/src/importers/artboard_importer.cpp',
'submodule/src/importers/keyed_object_importer.cpp',
'submodule/src/importers/keyed_property_importer.cpp',
+ 'submodule/src/importers/layer_state_importer.cpp',
'submodule/src/importers/linear_animation_importer.cpp',
+ 'submodule/src/importers/state_machine_importer.cpp',
+ 'submodule/src/importers/state_machine_layer_importer.cpp',
+ 'submodule/src/importers/state_transition_importer.cpp',
'submodule/src/shapes/clipping_shape.cpp',
'submodule/src/shapes/cubic_mirrored_vertex.cpp',
'submodule/src/shapes/metrics_path.cpp',
BinPackParameters: false
AlignAfterOpenBracket: Align
BreakBeforeBraces: Custom
+SortIncludes: false
BraceWrapping:
AfterEnum: true
AfterClass: true
skia/renderer/build/bin
skia/**/build/bin
/skia/dependencies/glfw
+/skia/dependencies/gl3w
+/skia/dependencies/imgui
+/skia/viewer/imgui.ini
code.writeln('public:');
for (final property in properties) {
code.writeln(
- 'inline ${property.type.cppName} ${property.name}() const ' +
+ 'inline ${property.type.cppGetterName} ${property.name}() const ' +
(property.isGetOverride ? 'override' : '') +
'{ return m_${property.capitalizedName}; }');
String _cppName;
final String include;
String get cppName => _cppName;
+ String get cppGetterName => _cppName;
String _runtimeCoreType;
String get runtimeCoreType => _runtimeCoreType;
@override
String get defaultValue => '""';
+ @override
+ String get cppGetterName => 'const std::string&';
+
@override
String convertCpp(String value) {
var result = value;
+++ /dev/null
-{
- "name": "StateMachineDouble",
- "key": {
- "int": 56,
- "string": "statemachinedouble"
- },
- "extends": "animation/state_machine_input.json",
- "properties": {
- "value": {
- "type": "double",
- "initialValue": "0",
- "key": {
- "int": 140,
- "string": "value"
- }
- }
- }
-}
\ No newline at end of file
--- /dev/null
+{
+ "name": "StateMachineNumber",
+ "key": {
+ "int": 56,
+ "string": "statemachinenumber"
+ },
+ "extends": "animation/state_machine_input.json",
+ "properties": {
+ "value": {
+ "type": "double",
+ "initialValue": "0",
+ "key": {
+ "int": 140,
+ "string": "value"
+ }
+ }
+ }
+}
\ No newline at end of file
"int": 158,
"string": "duration"
},
- "description": "Duration of the trasition (mix time) in milliseconds."
+ "description": "Duration of the trasition (mix time) in milliseconds or percentage (0-100) based on flags."
},
"transitionOrder": {
"type": "FractionalIndex",
},
"description": "Order value for sorting transitions in states.",
"runtime": false
+ },
+ "exitTime": {
+ "type": "uint",
+ "initialValue": "0",
+ "key": {
+ "int": 160,
+ "string": "exittime"
+ },
+ "description": "Duration in milliseconds that must elapse before allowing the state to change. If the flags mark this property as being percentage based, the value is in 0-100% of the outgoing animation's duration"
}
}
}
\ No newline at end of file
+++ /dev/null
-{
- "name": "TransitionDoubleCondition",
- "key": {
- "int": 70,
- "string": "transitiondoublecondition"
- },
- "extends": "animation/transition_value_condition.json",
- "properties": {
- "value": {
- "type": "double",
- "initialValue": "0",
- "key": {
- "int": 157,
- "string": "value"
- }
- }
- }
-}
\ No newline at end of file
--- /dev/null
+{
+ "name": "TransitionNumberCondition",
+ "key": {
+ "int": 70,
+ "string": "transitionnumbercondition"
+ },
+ "extends": "animation/transition_value_condition.json",
+ "properties": {
+ "value": {
+ "type": "double",
+ "initialValue": "0",
+ "key": {
+ "int": 157,
+ "string": "value"
+ }
+ }
+ }
+}
\ No newline at end of file
#include <stdio.h>
namespace rive
{
+ class LinearAnimation;
+ class Artboard;
+ class StateMachineLayerImporter;
+
class AnimationState : public AnimationStateBase
{
+ friend class StateMachineLayerImporter;
+
+ private:
+ LinearAnimation* m_Animation;
+
public:
+ const LinearAnimation* animation() const { return m_Animation; }
};
} // namespace rive
#define _RIVE_LAYER_STATE_HPP_
#include "generated/animation/layer_state_base.hpp"
#include <stdio.h>
+#include <vector>
+
namespace rive
{
+ class StateTransition;
+ class LayerStateImporter;
+ class StateMachineLayerImporter;
+
class LayerState : public LayerStateBase
{
+ friend class LayerStateImporter;
+ friend class StateMachineLayerImporter;
+
+ private:
+ std::vector<StateTransition*> m_Transitions;
+ void addTransition(StateTransition* transition);
+
public:
+ ~LayerState();
StatusCode onAddedDirty(CoreContext* context) override;
StatusCode onAddedClean(CoreContext* context) override;
+
+ StatusCode import(ImportStack& importStack) override;
+
+ size_t transitionCount() const { return m_Transitions.size(); }
+ StateTransition* transition(size_t index) const
+ {
+ if (index < m_Transitions.size())
+ {
+ return m_Transitions[index];
+ }
+ return nullptr;
+ }
};
} // namespace rive
StatusCode onAddedDirty(CoreContext* context) override;
StatusCode onAddedClean(CoreContext* context) override;
void addKeyedObject(KeyedObject* object);
- void apply(Artboard* artboard, float time, float mix = 1.0f);
+ void apply(Artboard* artboard, float time, float mix = 1.0f) const;
Loop loop() const { return (Loop)loopValue(); }
StatusCode import(ImportStack& importStack) override;
+ float startSeconds() const;
+ float endSeconds() const;
+ float durationSeconds() const;
+
#ifdef TESTING
size_t numKeyedObjects() { return m_KeyedObjects.size(); }
#endif
#ifndef _RIVE_LINEAR_ANIMATION_INSTANCE_HPP_
#define _RIVE_LINEAR_ANIMATION_INSTANCE_HPP_
-#include "animation/linear_animation.hpp"
+#include "artboard.hpp"
namespace rive
{
class LinearAnimationInstance
{
private:
- LinearAnimation* m_Animation = nullptr;
+ const LinearAnimation* m_Animation = nullptr;
float m_Time;
+ float m_TotalTime;
+ float m_LastTotalTime;
+ float m_SpilledTime;
int m_Direction;
bool m_DidLoop;
public:
- LinearAnimationInstance(LinearAnimation* animation);
+ LinearAnimationInstance(const LinearAnimation* animation);
// Advance the animation by the specified time. Returns true if the
// animation will continue to animate after this advance.
bool advance(float seconds);
// Returns a pointer to the instance's animation
- LinearAnimation* animation() const { return m_Animation; }
+ const LinearAnimation* animation() const { return m_Animation; }
// Returns the current point in time at which this instance has advance
// to
// Set when the animation is advanced, true if the animation has stopped
// (oneShot), reached the end (loop), or changed direction (pingPong)
- bool didLoop() const { return m_DidLoop; }
+ bool didLoop() const { return m_DidLoop; }
+
+ float totalTime() const { return m_TotalTime; }
+ float lastTotalTime() const { return m_LastTotalTime; }
+ float spilledTime() const { return m_SpilledTime; }
};
} // namespace rive
#endif
\ No newline at end of file
#define _RIVE_STATE_MACHINE_HPP_
#include "generated/animation/state_machine_base.hpp"
#include <stdio.h>
+#include <vector>
+
namespace rive
{
+ class StateMachineLayer;
+ class StateMachineInput;
+ class StateMachineImporter;
class StateMachine : public StateMachineBase
{
+ friend class StateMachineImporter;
+
+ private:
+ std::vector<StateMachineLayer*> m_Layers;
+ std::vector<StateMachineInput*> m_Inputs;
+
+ void addLayer(StateMachineLayer* layer);
+ void addInput(StateMachineInput* input);
+
public:
+ ~StateMachine();
+ StatusCode import(ImportStack& importStack) override;
+
+ size_t layerCount() const { return m_Layers.size(); }
+ size_t inputCount() const { return m_Inputs.size(); }
+
+ const StateMachineInput* input(std::string name) const;
+ const StateMachineInput* input(size_t index) const;
+ const StateMachineLayer* layer(std::string name) const;
+ const StateMachineLayer* layer(size_t index) const;
+
+ StatusCode onAddedDirty(CoreContext* context) override;
+ StatusCode onAddedClean(CoreContext* context) override;
};
} // namespace rive
+++ /dev/null
-#ifndef _RIVE_STATE_MACHINE_DOUBLE_HPP_
-#define _RIVE_STATE_MACHINE_DOUBLE_HPP_
-#include "generated/animation/state_machine_double_base.hpp"
-#include <stdio.h>
-namespace rive
-{
- class StateMachineDouble : public StateMachineDoubleBase
- {
- public:
- };
-} // namespace rive
-
-#endif
\ No newline at end of file
public:
StatusCode onAddedDirty(CoreContext* context) override;
StatusCode onAddedClean(CoreContext* context) override;
+ StatusCode import(ImportStack& importStack) override;
};
} // namespace rive
--- /dev/null
+#ifndef _RIVE_STATE_MACHINE_INPUT_INSTANCE_HPP_
+#define _RIVE_STATE_MACHINE_INPUT_INSTANCE_HPP_
+
+#include <string>
+#include <stdint.h>
+
+namespace rive
+{
+ class StateMachineInstance;
+ class StateMachineInput;
+ class StateMachineBool;
+ class StateMachineNumber;
+ class StateMachineTrigger;
+ class TransitionTriggerCondition;
+
+ class SMIInput
+ {
+ friend class StateMachineInstance;
+
+ private:
+ StateMachineInstance* m_MachineInstance;
+ const StateMachineInput* m_Input;
+
+ virtual void advanced() {}
+
+ protected:
+ void valueChanged();
+
+ SMIInput(const StateMachineInput* input,
+ StateMachineInstance* machineInstance);
+
+ public:
+ virtual ~SMIInput() {}
+ const StateMachineInput* input() const { return m_Input; }
+
+ const std::string& name() const;
+ uint16_t inputCoreType() const;
+ };
+
+ class SMIBool : public SMIInput
+ {
+ friend class StateMachineInstance;
+
+ private:
+ bool m_Value;
+
+ SMIBool(const StateMachineBool* input,
+ StateMachineInstance* machineInstance);
+
+ public:
+ bool value() const { return m_Value; }
+ void value(bool newValue);
+ };
+
+ class SMINumber : public SMIInput
+ {
+ friend class StateMachineInstance;
+
+ private:
+ float m_Value;
+
+ SMINumber(const StateMachineNumber* input,
+ StateMachineInstance* machineInstance);
+
+ public:
+ float value() const { return m_Value; }
+ void value(float newValue);
+ };
+
+ class SMITrigger : public SMIInput
+ {
+ friend class StateMachineInstance;
+ friend class TransitionTriggerCondition;
+
+ private:
+ bool m_Fired = false;
+
+ SMITrigger(const StateMachineTrigger* input,
+ StateMachineInstance* machineInstance);
+ void advanced() override { m_Fired = false; }
+
+ public:
+ void fire();
+ };
+} // namespace rive
+#endif
\ No newline at end of file
--- /dev/null
+#ifndef _RIVE_STATE_MACHINE_INSTANCE_HPP_
+#define _RIVE_STATE_MACHINE_INSTANCE_HPP_
+
+#include <string>
+#include <stddef.h>
+
+namespace rive
+{
+ class StateMachine;
+ class SMIInput;
+ class Artboard;
+ class SMIBool;
+ class SMINumber;
+ class SMITrigger;
+
+ class StateMachineLayerInstance;
+
+ class StateMachineInstance
+ {
+ friend class SMIInput;
+
+ private:
+ StateMachine* m_Machine;
+ bool m_NeedsAdvance = false;
+
+ size_t m_InputCount;
+ SMIInput** m_InputInstances;
+ unsigned int m_LayerCount;
+ StateMachineLayerInstance* m_Layers;
+
+ void markNeedsAdvance();
+
+ public:
+ StateMachineInstance(StateMachine* machine);
+ ~StateMachineInstance();
+
+ // Advance the state machine by the specified time. Returns true if the
+ // state machine will continue to animate after this advance.
+ bool advance(float seconds);
+
+ // Applies the animations of the StateMachine to an artboard.
+ void apply(Artboard* artboard) const;
+
+ // Returns true when the StateMachineInstance has more data to process.
+ bool needsAdvance() const;
+
+ size_t inputCount() const { return m_InputCount; }
+ SMIInput* input(size_t index) const;
+
+ SMIBool* getBool(std::string name) const;
+ SMINumber* getNumber(std::string name) const;
+ SMITrigger* getTrigger(std::string name) const;
+ };
+} // namespace rive
+#endif
\ No newline at end of file
#define _RIVE_STATE_MACHINE_LAYER_HPP_
#include "generated/animation/state_machine_layer_base.hpp"
#include <stdio.h>
+#include <vector>
+
namespace rive
{
+ class LayerState;
+ class StateMachineLayerImporter;
+ class AnyState;
+ class EntryState;
+ class ExitState;
class StateMachineLayer : public StateMachineLayerBase
{
+ friend class StateMachineLayerImporter;
+
+ private:
+ std::vector<LayerState*> m_States;
+ AnyState* m_Any = nullptr;
+ EntryState* m_Entry = nullptr;
+ ExitState* m_Exit = nullptr;
+
+ void addState(LayerState* state);
+
public:
+ ~StateMachineLayer();
StatusCode onAddedDirty(CoreContext* context) override;
StatusCode onAddedClean(CoreContext* context) override;
+
+ StatusCode import(ImportStack& importStack) override;
+
+ const AnyState* anyState() const { return m_Any; }
+ const EntryState* entryState() const { return m_Entry; }
+ const ExitState* exitState() const { return m_Exit; }
+
+#ifdef TESTING
+ size_t stateCount() const { return m_States.size(); }
+ LayerState* state(size_t index) const
+ {
+ if (index < m_States.size())
+ {
+ return m_States[index];
+ }
+ return nullptr;
+ }
+#endif
};
} // namespace rive
--- /dev/null
+#ifndef _RIVE_STATE_MACHINE_NUMBER_HPP_
+#define _RIVE_STATE_MACHINE_NUMBER_HPP_
+#include "generated/animation/state_machine_number_base.hpp"
+#include <stdio.h>
+namespace rive
+{
+ class StateMachineNumber : public StateMachineNumberBase
+ {
+ public:
+ };
+} // namespace rive
+
+#endif
\ No newline at end of file
{
class StateMachineTrigger : public StateMachineTriggerBase
{
- private:
- bool m_Fired = false;
- void reset();
-
- public:
- void fire();
};
} // namespace rive
#ifndef _RIVE_STATE_TRANSITION_HPP_
#define _RIVE_STATE_TRANSITION_HPP_
+#include "animation/state_transition_flags.hpp"
#include "generated/animation/state_transition_base.hpp"
#include <stdio.h>
+#include <vector>
+
namespace rive
{
+ class LayerState;
+ class StateMachineLayerImporter;
+ class StateTransitionImporter;
+ class TransitionCondition;
class StateTransition : public StateTransitionBase
{
+ friend class StateMachineLayerImporter;
+ friend class StateTransitionImporter;
+
+ private:
+ StateTransitionFlags transitionFlags() const
+ {
+ return static_cast<StateTransitionFlags>(flags());
+ }
+ LayerState* m_StateTo = nullptr;
+
+ std::vector<TransitionCondition*> m_Conditions;
+ void addCondition(TransitionCondition* condition);
+
public:
+ ~StateTransition();
+ const LayerState* stateTo() const { return m_StateTo; }
+
StatusCode onAddedDirty(CoreContext* context) override;
StatusCode onAddedClean(CoreContext* context) override;
+
+ /// Whether the transition is marked disabled (usually done in the
+ /// editor).
+ bool isDisabled() const
+ {
+ return (transitionFlags() & StateTransitionFlags::Disabled) ==
+ StateTransitionFlags::Disabled;
+ }
+
+ /// Whether the animation is held at exit or if it keeps advancing
+ /// during mixing.
+ bool pauseOnExit() const
+ {
+ return (transitionFlags() & StateTransitionFlags::PauseOnExit) ==
+ StateTransitionFlags::PauseOnExit;
+ }
+
+ /// Whether exit time is enabled. All other conditions still apply, the
+ /// exit time is effectively an AND with the rest of the conditions.
+ bool enableExitTime() const
+ {
+ return (transitionFlags() & StateTransitionFlags::EnableExitTime) ==
+ StateTransitionFlags::EnableExitTime;
+ }
+
+ StatusCode import(ImportStack& importStack) override;
+
+ size_t conditionCount() const { return m_Conditions.size(); }
+ TransitionCondition* condition(size_t index) const
+ {
+ if (index < m_Conditions.size())
+ {
+ return m_Conditions[index];
+ }
+ return nullptr;
+ }
+
+ /// The amount of time to mix the outgoing animation onto the incoming
+ /// one when changing state. Only applies when going out from an
+ /// AnimationState.
+ float mixTime(const LayerState* stateFrom) const;
+
+ /// Exit time in seconds. Specify relativeToWorkArea to use the work
+ /// area start as the origin. Otherwise time 0 of the animation is the
+ /// origin.
+ float exitTimeSeconds(const LayerState* stateFrom,
+ bool relativeToWorkArea) const;
};
} // namespace rive
--- /dev/null
+#ifndef _RIVE_STATE_TRANSITION_FLAGS_HPP_
+#define _RIVE_STATE_TRANSITION_FLAGS_HPP_
+
+#include <type_traits>
+
+namespace rive
+{
+ enum class StateTransitionFlags : unsigned char
+ {
+ None = 0,
+
+ /// Whether the transition is disabled.
+ Disabled = 1 << 0,
+
+ /// Whether the transition duration is a percentage or time in ms.
+ DurationIsPercentage = 1 << 1,
+
+ /// Whether exit time is enabled.
+ EnableExitTime = 1 << 2,
+
+ /// Whether the exit time is a percentage or time in ms.
+ ExitTimeIsPercentage = 1 << 3,
+
+ /// Whether the animation is held at exit or if it keeps advancing
+ /// during mixing.
+ PauseOnExit = 1 << 4
+
+ };
+
+ inline constexpr StateTransitionFlags operator&(StateTransitionFlags lhs,
+ StateTransitionFlags rhs)
+ {
+ return static_cast<StateTransitionFlags>(
+ static_cast<std::underlying_type<StateTransitionFlags>::type>(lhs) &
+ static_cast<std::underlying_type<StateTransitionFlags>::type>(rhs));
+ }
+
+ inline constexpr StateTransitionFlags operator^(StateTransitionFlags lhs,
+ StateTransitionFlags rhs)
+ {
+ return static_cast<StateTransitionFlags>(
+ static_cast<std::underlying_type<StateTransitionFlags>::type>(lhs) ^
+ static_cast<std::underlying_type<StateTransitionFlags>::type>(rhs));
+ }
+
+ inline constexpr StateTransitionFlags operator|(StateTransitionFlags lhs,
+ StateTransitionFlags rhs)
+ {
+ return static_cast<StateTransitionFlags>(
+ static_cast<std::underlying_type<StateTransitionFlags>::type>(lhs) |
+ static_cast<std::underlying_type<StateTransitionFlags>::type>(rhs));
+ }
+
+ inline constexpr StateTransitionFlags operator~(StateTransitionFlags rhs)
+ {
+ return static_cast<StateTransitionFlags>(
+ ~static_cast<std::underlying_type<StateTransitionFlags>::type>(
+ rhs));
+ }
+
+ inline StateTransitionFlags& operator|=(StateTransitionFlags& lhs,
+ StateTransitionFlags rhs)
+ {
+ lhs = static_cast<StateTransitionFlags>(
+ static_cast<std::underlying_type<StateTransitionFlags>::type>(lhs) |
+ static_cast<std::underlying_type<StateTransitionFlags>::type>(rhs));
+
+ return lhs;
+ }
+
+ inline StateTransitionFlags& operator&=(StateTransitionFlags& lhs,
+ StateTransitionFlags rhs)
+ {
+ lhs = static_cast<StateTransitionFlags>(
+ static_cast<std::underlying_type<StateTransitionFlags>::type>(lhs) &
+ static_cast<std::underlying_type<StateTransitionFlags>::type>(rhs));
+
+ return lhs;
+ }
+
+ inline StateTransitionFlags& operator^=(StateTransitionFlags& lhs,
+ StateTransitionFlags rhs)
+ {
+ lhs = static_cast<StateTransitionFlags>(
+ static_cast<std::underlying_type<StateTransitionFlags>::type>(lhs) ^
+ static_cast<std::underlying_type<StateTransitionFlags>::type>(rhs));
+
+ return lhs;
+ }
+} // namespace rive
+#endif
\ No newline at end of file
class TransitionBoolCondition : public TransitionBoolConditionBase
{
public:
+ bool evaluate(const SMIInput* inputInstance) const override;
+
+ protected:
+ bool validateInputType(const StateMachineInput* input) const override;
};
} // namespace rive
#ifndef _RIVE_TRANSITION_CONDITION_HPP_
#define _RIVE_TRANSITION_CONDITION_HPP_
#include "generated/animation/transition_condition_base.hpp"
-#include <stdio.h>
+
namespace rive
{
+ class StateMachineInput;
+ class SMIInput;
+
class TransitionCondition : public TransitionConditionBase
{
public:
StatusCode onAddedDirty(CoreContext* context) override;
StatusCode onAddedClean(CoreContext* context) override;
+
+ StatusCode import(ImportStack& importStack) override;
+
+ virtual bool evaluate(const SMIInput* inputInstance) const
+ {
+ return true;
+ }
+
+ protected:
+ virtual bool validateInputType(const StateMachineInput* input) const
+ {
+ return true;
+ }
};
} // namespace rive
--- /dev/null
+#ifndef _RIVE_TRANSITION_CONDITION_OP_HPP_
+#define _RIVE_TRANSITION_CONDITION_OP_HPP_
+
+namespace rive
+{
+ enum class TransitionConditionOp : int
+ {
+ equal = 0,
+ notEqual = 1,
+ lessThanOrEqual = 2,
+ greaterThanOrEqual = 3,
+ lessThan = 4,
+ greaterThan = 5
+ };
+} // namespace rive
+
+#endif
\ No newline at end of file
+++ /dev/null
-#ifndef _RIVE_TRANSITION_DOUBLE_CONDITION_HPP_
-#define _RIVE_TRANSITION_DOUBLE_CONDITION_HPP_
-#include "generated/animation/transition_double_condition_base.hpp"
-#include <stdio.h>
-namespace rive
-{
- class TransitionDoubleCondition : public TransitionDoubleConditionBase
- {
- public:
- };
-} // namespace rive
-
-#endif
\ No newline at end of file
--- /dev/null
+#ifndef _RIVE_TRANSITION_NUMBER_CONDITION_HPP_
+#define _RIVE_TRANSITION_NUMBER_CONDITION_HPP_
+#include "generated/animation/transition_number_condition_base.hpp"
+#include <stdio.h>
+namespace rive
+{
+ class TransitionNumberCondition : public TransitionNumberConditionBase
+ {
+ protected:
+ bool validateInputType(const StateMachineInput* input) const override;
+
+ public:
+ bool evaluate(const SMIInput* inputInstance) const override;
+ };
+} // namespace rive
+
+#endif
\ No newline at end of file
class TransitionTriggerCondition : public TransitionTriggerConditionBase
{
public:
+ bool evaluate(const SMIInput* inputInstance) const override;
+
+ protected:
+ bool validateInputType(const StateMachineInput* input) const override;
};
} // namespace rive
#ifndef _RIVE_TRANSITION_VALUE_CONDITION_HPP_
#define _RIVE_TRANSITION_VALUE_CONDITION_HPP_
#include "generated/animation/transition_value_condition_base.hpp"
-#include <stdio.h>
+#include "animation/transition_condition_op.hpp"
+
namespace rive
{
class TransitionValueCondition : public TransitionValueConditionBase
{
public:
+ TransitionConditionOp op() const
+ {
+ return (TransitionConditionOp)opValue();
+ }
};
} // namespace rive
class Drawable;
class Node;
class DrawTarget;
+ class ArtboardImporter;
class Artboard : public ArtboardBase,
public CoreContext,
public ShapePaintContainer
{
friend class File;
+ friend class ArtboardImporter;
private:
std::vector<Core*> m_Objects;
void sortDependencies();
void sortDrawOrder();
+#ifdef TESTING
public:
- ~Artboard();
- StatusCode initialize();
+#endif
void addObject(Core* object);
void addAnimation(LinearAnimation* object);
void addStateMachine(StateMachine* object);
+ public:
+ ~Artboard();
+ StatusCode initialize();
+
Core* resolve(int id) const override;
StatusCode onAddedClean(CoreContext* context) override
return nullptr;
}
- LinearAnimation* firstAnimation();
- LinearAnimation* animation(std::string name);
- LinearAnimation* animation(size_t index);
- size_t animationCount() { return m_Animations.size(); }
+ LinearAnimation* firstAnimation() const;
+ LinearAnimation* animation(std::string name) const;
+ LinearAnimation* animation(size_t index) const;
+ size_t animationCount() const { return m_Animations.size(); }
- StateMachine* firstStateMachine();
- StateMachine* stateMachine(std::string name);
- StateMachine* stateMachine(size_t index);
- size_t stateMachineCount() { return m_StateMachines.size(); }
+ StateMachine* firstStateMachine() const;
+ StateMachine* stateMachine(std::string name) const;
+ StateMachine* stateMachine(size_t index) const;
+ size_t stateMachineCount() const { return m_StateMachines.size(); }
};
} // namespace rive
decode_double(const uint8_t* buf, const uint8_t* buf_end, double* r)
{
// Return zero bytes read on buffer overflow
- if (buf_end - buf < sizeof(double))
+ if (buf_end - buf < ((unsigned) sizeof(double)))
{
return 0;
}
inline size_t decode_float(const uint8_t* buf, const uint8_t* buf_end, float* r)
{
// Return zero bytes read on buffer overflow
- if (buf_end - buf < sizeof(float))
+ if (buf_end - buf < (unsigned) sizeof(float))
{
return 0;
}
decode_uint_8(const uint8_t* buf, const uint8_t* buf_end, uint8_t* r)
{
// Return zero bytes read on buffer overflow
- if (buf_end - buf < sizeof(uint8_t))
+ if (buf_end - buf < (unsigned) sizeof(uint8_t))
{
return 0;
}
decode_uint_32(const uint8_t* buf, const uint8_t* buf_end, uint32_t* r)
{
// Return zero bytes read on buffer overflow
- if (buf_end - buf < sizeof(uint32_t))
+ if (buf_end - buf < (unsigned) sizeof(uint32_t))
{
return 0;
}
memcpy(r, buf, sizeof(uint32_t));
}
return sizeof(uint32_t);
-}
\ No newline at end of file
+}
Artboard* artboard() const;
/// @returns the named artboard. If no artboard is found with that name,
- /// the null pointer is returned
+ /// the null pointer is returned.
Artboard* artboard(std::string name) const;
+ /// @returns the artboard at the specified index, or the nullptr if the
+ /// index is out of range.
+ Artboard* artboard(size_t index) const;
+
+ /// @returns the number of artboards in the file.
+ size_t artboardCount() const { return m_Artboards.size(); }
+
private:
ImportResult read(BinaryReader& reader, const RuntimeHeader& header);
};
#ifndef _RIVE_ANIMATION_BASE_HPP_
#define _RIVE_ANIMATION_BASE_HPP_
+#include <string>
#include "core.hpp"
#include "core/field_types/core_string_type.hpp"
-#include <string>
namespace rive
{
class AnimationBase : public Core
private:
std::string m_Name = "";
public:
- inline std::string name() const { return m_Name; }
+ inline const std::string& name() const { return m_Name; }
void name(std::string value)
{
if (m_Name == value)
#ifndef _RIVE_STATE_MACHINE_COMPONENT_BASE_HPP_
#define _RIVE_STATE_MACHINE_COMPONENT_BASE_HPP_
+#include <string>
#include "core.hpp"
#include "core/field_types/core_string_type.hpp"
-#include <string>
namespace rive
{
class StateMachineComponentBase : public Core
private:
std::string m_Name = "";
public:
- inline std::string name() const { return m_Name; }
+ inline const std::string& name() const { return m_Name; }
void name(std::string value)
{
if (m_Name == value)
+++ /dev/null
-#ifndef _RIVE_STATE_MACHINE_DOUBLE_BASE_HPP_
-#define _RIVE_STATE_MACHINE_DOUBLE_BASE_HPP_
-#include "animation/state_machine_input.hpp"
-#include "core/field_types/core_double_type.hpp"
-namespace rive
-{
- class StateMachineDoubleBase : public StateMachineInput
- {
- protected:
- typedef StateMachineInput Super;
-
- public:
- static const uint16_t typeKey = 56;
-
- /// Helper to quickly determine if a core object extends another without
- /// RTTI at runtime.
- bool isTypeOf(uint16_t typeKey) const override
- {
- switch (typeKey)
- {
- case StateMachineDoubleBase::typeKey:
- case StateMachineInputBase::typeKey:
- case StateMachineComponentBase::typeKey:
- return true;
- default:
- return false;
- }
- }
-
- uint16_t coreType() const override { return typeKey; }
-
- static const uint16_t valuePropertyKey = 140;
-
- private:
- float m_Value = 0.0f;
- public:
- inline float value() const { return m_Value; }
- void value(float value)
- {
- if (m_Value == value)
- {
- return;
- }
- m_Value = value;
- valueChanged();
- }
-
- bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
- {
- switch (propertyKey)
- {
- case valuePropertyKey:
- m_Value = CoreDoubleType::deserialize(reader);
- return true;
- }
- return StateMachineInput::deserialize(propertyKey, reader);
- }
-
- protected:
- virtual void valueChanged() {}
- };
-} // namespace rive
-
-#endif
\ No newline at end of file
--- /dev/null
+#ifndef _RIVE_STATE_MACHINE_NUMBER_BASE_HPP_
+#define _RIVE_STATE_MACHINE_NUMBER_BASE_HPP_
+#include "animation/state_machine_input.hpp"
+#include "core/field_types/core_double_type.hpp"
+namespace rive
+{
+ class StateMachineNumberBase : public StateMachineInput
+ {
+ protected:
+ typedef StateMachineInput Super;
+
+ public:
+ static const uint16_t typeKey = 56;
+
+ /// Helper to quickly determine if a core object extends another without
+ /// RTTI at runtime.
+ bool isTypeOf(uint16_t typeKey) const override
+ {
+ switch (typeKey)
+ {
+ case StateMachineNumberBase::typeKey:
+ case StateMachineInputBase::typeKey:
+ case StateMachineComponentBase::typeKey:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ uint16_t coreType() const override { return typeKey; }
+
+ static const uint16_t valuePropertyKey = 140;
+
+ private:
+ float m_Value = 0.0f;
+ public:
+ inline float value() const { return m_Value; }
+ void value(float value)
+ {
+ if (m_Value == value)
+ {
+ return;
+ }
+ m_Value = value;
+ valueChanged();
+ }
+
+ bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
+ {
+ switch (propertyKey)
+ {
+ case valuePropertyKey:
+ m_Value = CoreDoubleType::deserialize(reader);
+ return true;
+ }
+ return StateMachineInput::deserialize(propertyKey, reader);
+ }
+
+ protected:
+ virtual void valueChanged() {}
+ };
+} // namespace rive
+
+#endif
\ No newline at end of file
static const uint16_t stateToIdPropertyKey = 151;
static const uint16_t flagsPropertyKey = 152;
static const uint16_t durationPropertyKey = 158;
+ static const uint16_t exitTimePropertyKey = 160;
private:
int m_StateToId = -1;
int m_Flags = 0;
int m_Duration = 0;
+ int m_ExitTime = 0;
public:
inline int stateToId() const { return m_StateToId; }
void stateToId(int value)
durationChanged();
}
+ inline int exitTime() const { return m_ExitTime; }
+ void exitTime(int value)
+ {
+ if (m_ExitTime == value)
+ {
+ return;
+ }
+ m_ExitTime = value;
+ exitTimeChanged();
+ }
+
bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
{
switch (propertyKey)
case durationPropertyKey:
m_Duration = CoreUintType::deserialize(reader);
return true;
+ case exitTimePropertyKey:
+ m_ExitTime = CoreUintType::deserialize(reader);
+ return true;
}
return StateMachineLayerComponent::deserialize(propertyKey, reader);
}
virtual void stateToIdChanged() {}
virtual void flagsChanged() {}
virtual void durationChanged() {}
+ virtual void exitTimeChanged() {}
};
} // namespace rive
+++ /dev/null
-#ifndef _RIVE_TRANSITION_DOUBLE_CONDITION_BASE_HPP_
-#define _RIVE_TRANSITION_DOUBLE_CONDITION_BASE_HPP_
-#include "animation/transition_value_condition.hpp"
-#include "core/field_types/core_double_type.hpp"
-namespace rive
-{
- class TransitionDoubleConditionBase : public TransitionValueCondition
- {
- protected:
- typedef TransitionValueCondition Super;
-
- public:
- static const uint16_t typeKey = 70;
-
- /// Helper to quickly determine if a core object extends another without
- /// RTTI at runtime.
- bool isTypeOf(uint16_t typeKey) const override
- {
- switch (typeKey)
- {
- case TransitionDoubleConditionBase::typeKey:
- case TransitionValueConditionBase::typeKey:
- case TransitionConditionBase::typeKey:
- return true;
- default:
- return false;
- }
- }
-
- uint16_t coreType() const override { return typeKey; }
-
- static const uint16_t valuePropertyKey = 157;
-
- private:
- float m_Value = 0.0f;
- public:
- inline float value() const { return m_Value; }
- void value(float value)
- {
- if (m_Value == value)
- {
- return;
- }
- m_Value = value;
- valueChanged();
- }
-
- bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
- {
- switch (propertyKey)
- {
- case valuePropertyKey:
- m_Value = CoreDoubleType::deserialize(reader);
- return true;
- }
- return TransitionValueCondition::deserialize(propertyKey, reader);
- }
-
- protected:
- virtual void valueChanged() {}
- };
-} // namespace rive
-
-#endif
\ No newline at end of file
--- /dev/null
+#ifndef _RIVE_TRANSITION_NUMBER_CONDITION_BASE_HPP_
+#define _RIVE_TRANSITION_NUMBER_CONDITION_BASE_HPP_
+#include "animation/transition_value_condition.hpp"
+#include "core/field_types/core_double_type.hpp"
+namespace rive
+{
+ class TransitionNumberConditionBase : public TransitionValueCondition
+ {
+ protected:
+ typedef TransitionValueCondition Super;
+
+ public:
+ static const uint16_t typeKey = 70;
+
+ /// Helper to quickly determine if a core object extends another without
+ /// RTTI at runtime.
+ bool isTypeOf(uint16_t typeKey) const override
+ {
+ switch (typeKey)
+ {
+ case TransitionNumberConditionBase::typeKey:
+ case TransitionValueConditionBase::typeKey:
+ case TransitionConditionBase::typeKey:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ uint16_t coreType() const override { return typeKey; }
+
+ static const uint16_t valuePropertyKey = 157;
+
+ private:
+ float m_Value = 0.0f;
+ public:
+ inline float value() const { return m_Value; }
+ void value(float value)
+ {
+ if (m_Value == value)
+ {
+ return;
+ }
+ m_Value = value;
+ valueChanged();
+ }
+
+ bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
+ {
+ switch (propertyKey)
+ {
+ case valuePropertyKey:
+ m_Value = CoreDoubleType::deserialize(reader);
+ return true;
+ }
+ return TransitionValueCondition::deserialize(propertyKey, reader);
+ }
+
+ protected:
+ virtual void valueChanged() {}
+ };
+} // namespace rive
+
+#endif
\ No newline at end of file
#ifndef _RIVE_COMPONENT_BASE_HPP_
#define _RIVE_COMPONENT_BASE_HPP_
+#include <string>
#include "core.hpp"
#include "core/field_types/core_string_type.hpp"
#include "core/field_types/core_uint_type.hpp"
-#include <string>
namespace rive
{
class ComponentBase : public Core
std::string m_Name = "";
int m_ParentId = 0;
public:
- inline std::string name() const { return m_Name; }
+ inline const std::string& name() const { return m_Name; }
void name(std::string value)
{
if (m_Name == value)
#include "animation/state_machine.hpp"
#include "animation/state_machine_bool.hpp"
#include "animation/state_machine_component.hpp"
-#include "animation/state_machine_double.hpp"
#include "animation/state_machine_input.hpp"
#include "animation/state_machine_layer.hpp"
#include "animation/state_machine_layer_component.hpp"
+#include "animation/state_machine_number.hpp"
#include "animation/state_machine_trigger.hpp"
#include "animation/state_transition.hpp"
#include "animation/transition_bool_condition.hpp"
#include "animation/transition_condition.hpp"
-#include "animation/transition_double_condition.hpp"
+#include "animation/transition_number_condition.hpp"
#include "animation/transition_trigger_condition.hpp"
#include "animation/transition_value_condition.hpp"
#include "artboard.hpp"
return new AnimationState();
case KeyedObjectBase::typeKey:
return new KeyedObject();
+ case StateMachineNumberBase::typeKey:
+ return new StateMachineNumber();
case TransitionTriggerConditionBase::typeKey:
return new TransitionTriggerCondition();
case KeyedPropertyBase::typeKey:
return new KeyedProperty();
- case StateMachineDoubleBase::typeKey:
- return new StateMachineDouble();
case KeyFrameIdBase::typeKey:
return new KeyFrameId();
+ case TransitionNumberConditionBase::typeKey:
+ return new TransitionNumberCondition();
case AnyStateBase::typeKey:
return new AnyState();
case StateMachineLayerBase::typeKey:
return new Animation();
case CubicInterpolatorBase::typeKey:
return new CubicInterpolator();
- case TransitionDoubleConditionBase::typeKey:
- return new TransitionDoubleCondition();
case StateTransitionBase::typeKey:
return new StateTransition();
case KeyFrameDoubleBase::typeKey:
case StateTransitionBase::durationPropertyKey:
object->as<StateTransitionBase>()->duration(value);
break;
+ case StateTransitionBase::exitTimePropertyKey:
+ object->as<StateTransitionBase>()->exitTime(value);
+ break;
case LinearAnimationBase::fpsPropertyKey:
object->as<LinearAnimationBase>()->fps(value);
break;
{
switch (propertyKey)
{
- case StateMachineDoubleBase::valuePropertyKey:
- object->as<StateMachineDoubleBase>()->value(value);
+ case StateMachineNumberBase::valuePropertyKey:
+ object->as<StateMachineNumberBase>()->value(value);
+ break;
+ case TransitionNumberConditionBase::valuePropertyKey:
+ object->as<TransitionNumberConditionBase>()->value(value);
break;
case CubicInterpolatorBase::x1PropertyKey:
object->as<CubicInterpolatorBase>()->x1(value);
case CubicInterpolatorBase::y2PropertyKey:
object->as<CubicInterpolatorBase>()->y2(value);
break;
- case TransitionDoubleConditionBase::valuePropertyKey:
- object->as<TransitionDoubleConditionBase>()->value(value);
- break;
case KeyFrameDoubleBase::valuePropertyKey:
object->as<KeyFrameDoubleBase>()->value(value);
break;
return object->as<StateTransitionBase>()->flags();
case StateTransitionBase::durationPropertyKey:
return object->as<StateTransitionBase>()->duration();
+ case StateTransitionBase::exitTimePropertyKey:
+ return object->as<StateTransitionBase>()->exitTime();
case LinearAnimationBase::fpsPropertyKey:
return object->as<LinearAnimationBase>()->fps();
case LinearAnimationBase::durationPropertyKey:
{
switch (propertyKey)
{
- case StateMachineDoubleBase::valuePropertyKey:
- return object->as<StateMachineDoubleBase>()->value();
+ case StateMachineNumberBase::valuePropertyKey:
+ return object->as<StateMachineNumberBase>()->value();
+ case TransitionNumberConditionBase::valuePropertyKey:
+ return object->as<TransitionNumberConditionBase>()->value();
case CubicInterpolatorBase::x1PropertyKey:
return object->as<CubicInterpolatorBase>()->x1();
case CubicInterpolatorBase::y1PropertyKey:
return object->as<CubicInterpolatorBase>()->x2();
case CubicInterpolatorBase::y2PropertyKey:
return object->as<CubicInterpolatorBase>()->y2();
- case TransitionDoubleConditionBase::valuePropertyKey:
- return object->as<TransitionDoubleConditionBase>()->value();
case KeyFrameDoubleBase::valuePropertyKey:
return object->as<KeyFrameDoubleBase>()->value();
case LinearAnimationBase::speedPropertyKey:
case StateTransitionBase::stateToIdPropertyKey:
case StateTransitionBase::flagsPropertyKey:
case StateTransitionBase::durationPropertyKey:
+ case StateTransitionBase::exitTimePropertyKey:
case LinearAnimationBase::fpsPropertyKey:
case LinearAnimationBase::durationPropertyKey:
case LinearAnimationBase::loopValuePropertyKey:
case CubicWeightBase::outValuesPropertyKey:
case CubicWeightBase::outIndicesPropertyKey:
return CoreUintType::id;
- case StateMachineDoubleBase::valuePropertyKey:
+ case StateMachineNumberBase::valuePropertyKey:
+ case TransitionNumberConditionBase::valuePropertyKey:
case CubicInterpolatorBase::x1PropertyKey:
case CubicInterpolatorBase::y1PropertyKey:
case CubicInterpolatorBase::x2PropertyKey:
case CubicInterpolatorBase::y2PropertyKey:
- case TransitionDoubleConditionBase::valuePropertyKey:
case KeyFrameDoubleBase::valuePropertyKey:
case LinearAnimationBase::speedPropertyKey:
case LinearGradientBase::startXPropertyKey:
namespace rive
{
- class Core;
- class Artboard;
- class LinearAnimation;
- class StateMachine;
+ class Core;
+ class Artboard;
+ class LinearAnimation;
+ class StateMachine;
class ArtboardImporter : public ImportStackObject
{
private:
public:
ArtboardImporter(Artboard* artboard);
- void addComponent(Core* object);
- void addAnimation(LinearAnimation* animation);
- void addStateMachine(StateMachine* stateMachine);
+ void addComponent(Core* object);
+ void addAnimation(LinearAnimation* animation);
+ void addStateMachine(StateMachine* stateMachine);
StatusCode resolve() override;
+ const Artboard* artboard() const { return m_Artboard; }
+
+ bool readNullObject() override;
};
} // namespace rive
#endif
#ifndef _RIVE_IMPORT_STACK_HPP_
#define _RIVE_IMPORT_STACK_HPP_
#include "status_code.hpp"
+#include <algorithm>
#include <unordered_map>
+#include <vector>
+#include <algorithm>
namespace rive
{
public:
virtual ~ImportStackObject() {}
virtual StatusCode resolve() { return StatusCode::Ok; }
+ virtual bool readNullObject() { return false; }
};
class ImportStack
{
private:
std::unordered_map<uint16_t, ImportStackObject*> m_Latests;
+ std::vector<ImportStackObject*> m_LastAdded;
public:
template <typename T = ImportStackObject> T* latest(uint16_t coreType)
if (itr != m_Latests.end())
{
auto stackObject = itr->second;
+
+ // Remove it from latests.
+ auto itr = std::find(
+ m_LastAdded.begin(), m_LastAdded.end(), stackObject);
+ if (itr != m_LastAdded.end())
+ {
+ m_LastAdded.erase(itr);
+ }
+
StatusCode code = stackObject->resolve();
delete stackObject;
if (code != StatusCode::Ok)
else
{
m_Latests[coreType] = object;
+ m_LastAdded.push_back(object);
}
return StatusCode::Ok;
}
delete pair.second;
}
}
+
+ bool readNullObject()
+ {
+ for (auto itr = m_LastAdded.rbegin(); itr != m_LastAdded.rend();
+ itr++)
+ {
+ if ((*itr)->readNullObject())
+ {
+ return true;
+ }
+ }
+ return false;
+ }
};
} // namespace rive
#endif
\ No newline at end of file
KeyedPropertyImporter(LinearAnimation* animation,
KeyedProperty* keyedProperty);
void addKeyFrame(KeyFrame* keyFrame);
+ bool readNullObject() override;
};
} // namespace rive
#endif
--- /dev/null
+#ifndef _RIVE_LAYER_STATE_IMPORTER_HPP_
+#define _RIVE_LAYER_STATE_IMPORTER_HPP_
+
+#include "importers/import_stack.hpp"
+
+namespace rive
+{
+ class LayerState;
+ class StateTransition;
+
+ class LayerStateImporter : public ImportStackObject
+ {
+ private:
+ LayerState* m_State;
+
+ public:
+ LayerStateImporter(LayerState* state);
+ void addTransition(StateTransition* transition);
+ StatusCode resolve() override;
+ };
+} // namespace rive
+#endif
--- /dev/null
+#ifndef _RIVE_STATE_MACHINE_IMPORTER_HPP_
+#define _RIVE_STATE_MACHINE_IMPORTER_HPP_
+
+#include "importers/import_stack.hpp"
+
+namespace rive
+{
+ class StateMachineInput;
+ class StateMachineLayer;
+ class StateMachine;
+ class StateMachineImporter : public ImportStackObject
+ {
+ private:
+ StateMachine* m_StateMachine;
+
+ public:
+ StateMachineImporter(StateMachine* machine);
+ const StateMachine* stateMachine() const { return m_StateMachine; }
+ void addLayer(StateMachineLayer* layer);
+ void addInput(StateMachineInput* input);
+ StatusCode resolve() override;
+ bool readNullObject() override;
+ };
+} // namespace rive
+#endif
--- /dev/null
+#ifndef _RIVE_STATE_MACHINE_LAYER_IMPORTER_HPP_
+#define _RIVE_STATE_MACHINE_LAYER_IMPORTER_HPP_
+
+#include "importers/import_stack.hpp"
+
+namespace rive
+{
+ class StateMachineLayer;
+ class LayerState;
+ class ArtboardImporter;
+
+ class StateMachineLayerImporter : public ImportStackObject
+ {
+ private:
+ StateMachineLayer* m_Layer;
+ ArtboardImporter* m_ArtboardImporter;
+
+ public:
+ StateMachineLayerImporter(StateMachineLayer* layer,
+ ArtboardImporter* artboardImporter);
+ void addState(LayerState* state);
+ StatusCode resolve() override;
+ bool readNullObject() override;
+ };
+} // namespace rive
+#endif
--- /dev/null
+#ifndef _RIVE_TRANSITION_IMPORTER_HPP_
+#define _RIVE_TRANSITION_IMPORTER_HPP_
+
+#include "importers/import_stack.hpp"
+
+namespace rive
+{
+ class StateTransition;
+ class TransitionCondition;
+
+ class StateTransitionImporter : public ImportStackObject
+ {
+ private:
+ StateTransition* m_Transition;
+
+ public:
+ StateTransitionImporter(StateTransition* transition);
+ void addCondition(TransitionCondition* condition);
+ StatusCode resolve() override;
+ };
+} // namespace rive
+#endif
--- /dev/null
+#!/bin/sh
+
+set -e
+
+# GL3W requires CMake
+
+GL3W_REPO=https://github.com/skaslev/gl3w
+GL3W_STABLE_BRANCH=master
+
+if [ ! -d gl3w ]; then
+ echo "Cloning gl3w."
+ git clone $GL3W_REPO
+fi
+
+cd gl3w && git checkout $GL3W_STABLE_BRANCH && git fetch && git pull
+
+mkdir -p build
+cd build
+cmake ../
+make
\ No newline at end of file
--- /dev/null
+#!/bin/sh
+
+set -e
+
+IMGUI_REPO=https://github.com/ocornut/imgui
+IMGUI_STABLE_BRANCH=master
+
+if [ ! -d imgui ]; then
+ echo "Cloning ImGui."
+ git clone $IMGUI_REPO
+fi
+
+cd imgui && git checkout $IMGUI_STABLE_BRANCH && git fetch && git pull
--- /dev/null
+./make_skia.sh
+./make_glfw.sh
+./make_gl3w.sh
+./make_imgui.sh
\ No newline at end of file
includedirs {"../include", "../../../include", "../../renderer/include", "../../dependencies/glfw/include",
"../../dependencies/skia", "../../dependencies/skia/include/core",
"../../dependencies/skia/include/effects", "../../dependencies/skia/include/gpu",
- "../../dependencies/skia/include/config"}
+ "../../dependencies/skia/include/config", "../../dependencies/imgui", "../../dependencies",
+ "../../dependencies/gl3w/build/include"}
links {"Cocoa.framework", "IOKit.framework", "CoreVideo.framework", "rive", "skia", "rive_skia_renderer", "glfw3"}
libdirs {"../../../build/bin/%{cfg.buildcfg}", "../../dependencies/glfw_build/src",
"../../dependencies/skia/out/Static", "../../renderer/build/bin/%{cfg.buildcfg}"}
-files {"../src/**.cpp"}
+files {"../src/**.cpp", "../../dependencies/gl3w/build/src/gl3w.c",
+ "../../dependencies/imgui/backends/imgui_impl_glfw.cpp",
+ "../../dependencies/imgui/backends/imgui_impl_opengl3.cpp", "../../dependencies/imgui/imgui_widgets.cpp",
+ "../../dependencies/imgui/imgui.cpp", "../../dependencies/imgui/imgui_tables.cpp",
+ "../../dependencies/imgui/imgui_draw.cpp"}
buildoptions {"-Wall", "-fno-exceptions", "-fno-rtti"}
filter "configurations:release"
defines {"RELEASE"}
-defines { "NDEBUG" }
+defines {"NDEBUG"}
optimize "On"
-- Clean Function --
+
+// Makes ure gl3w is included before glfw3
+#include "GL/gl3w.h"
+
#define SK_GL
#include "GLFW/glfw3.h"
#include "gl/GrGLInterface.h"
#include "animation/linear_animation_instance.hpp"
+#include "animation/state_machine_instance.hpp"
+#include "animation/state_machine_input_instance.hpp"
+#include "animation/state_machine_number.hpp"
+#include "animation/state_machine_bool.hpp"
+#include "animation/state_machine_trigger.hpp"
#include "artboard.hpp"
#include "file.hpp"
#include "layout.hpp"
#include "math/aabb.hpp"
#include "skia_renderer.hpp"
+#include "imgui/backends/imgui_impl_glfw.h"
+#include "imgui/backends/imgui_impl_opengl3.h"
+
#include <cmath>
#include <stdio.h>
+std::string filename;
rive::File* currentFile = nullptr;
rive::Artboard* artboard = nullptr;
+rive::StateMachineInstance* stateMachineInstance = nullptr;
rive::LinearAnimationInstance* animationInstance = nullptr;
+uint8_t* fileBytes = nullptr;
+unsigned int fileBytesLength = 0;
-void glfwErrorCallback(int error, const char* description)
-{
- puts(description);
-}
+int animationIndex = 0;
+int stateMachineIndex = -1;
-void glfwDropCallback(GLFWwindow* window, int count, const char** paths)
+void initStateMachine(int index)
{
- // Just get the last dropped file for now...
- auto filename = paths[count - 1];
- FILE* fp = fopen(filename, "r");
- fseek(fp, 0, SEEK_END);
- auto length = ftell(fp);
- fseek(fp, 0, SEEK_SET);
- uint8_t* bytes = new uint8_t[length];
- if (fread(bytes, 1, length, fp) != length)
+ stateMachineIndex = index;
+ animationIndex = -1;
+ assert(fileBytes != nullptr);
+ auto reader = rive::BinaryReader(fileBytes, fileBytesLength);
+ rive::File* file = nullptr;
+ auto result = rive::File::import(reader, &file);
+ if (result != rive::ImportResult::success)
{
- delete[] bytes;
- fprintf(stderr, "failed to read all of %s\n", filename);
+ delete[] fileBytes;
+ fprintf(stderr, "failed to import file\n");
return;
}
- auto reader = rive::BinaryReader(bytes, length);
+ artboard = file->artboard();
+ artboard->advance(0.0f);
+
+ delete animationInstance;
+ delete stateMachineInstance;
+ delete currentFile;
+ animationInstance = nullptr;
+ stateMachineInstance = nullptr;
+
+ auto stateMachine = index >= 0 && index < artboard->stateMachineCount()
+ ? artboard->stateMachine(index)
+ : nullptr;
+ if (stateMachine != nullptr)
+ {
+ stateMachineInstance = new rive::StateMachineInstance(stateMachine);
+ }
+
+ currentFile = file;
+}
+
+void initAnimation(int index)
+{
+ animationIndex = index;
+ stateMachineIndex = -1;
+ assert(fileBytes != nullptr);
+ auto reader = rive::BinaryReader(fileBytes, fileBytesLength);
rive::File* file = nullptr;
auto result = rive::File::import(reader, &file);
if (result != rive::ImportResult::success)
{
- delete[] bytes;
- fprintf(stderr, "failed to import %s\n", filename);
+ delete[] fileBytes;
+ fprintf(stderr, "failed to import file\n");
return;
}
artboard = file->artboard();
artboard->advance(0.0f);
delete animationInstance;
+ delete stateMachineInstance;
delete currentFile;
+ animationInstance = nullptr;
+ stateMachineInstance = nullptr;
- auto animation = artboard->firstAnimation();
+ auto animation = index >= 0 && index < artboard->animationCount()
+ ? artboard->animation(index)
+ : nullptr;
if (animation != nullptr)
{
animationInstance = new rive::LinearAnimationInstance(animation);
}
- else
- {
- animationInstance = nullptr;
- }
currentFile = file;
- delete[] bytes;
+}
+
+void glfwErrorCallback(int error, const char* description)
+{
+ puts(description);
+}
+
+void glfwDropCallback(GLFWwindow* window, int count, const char** paths)
+{
+ // Just get the last dropped file for now...
+ filename = paths[count - 1];
+
+ FILE* fp = fopen(filename.c_str(), "r");
+ fseek(fp, 0, SEEK_END);
+ fileBytesLength = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+ delete[] fileBytes;
+ fileBytes = new uint8_t[fileBytesLength];
+ if (fread(fileBytes, 1, fileBytesLength, fp) != fileBytesLength)
+ {
+ delete[] fileBytes;
+ fprintf(stderr, "failed to read all of %s\n", filename.c_str());
+ return;
+ }
+ initAnimation(0);
}
int main()
}
glfwSetErrorCallback(glfwErrorCallback);
- glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
- glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
GLFWwindow* window = glfwCreateWindow(1280, 720, "Rive Viewer", NULL, NULL);
if (window == nullptr)
{
glfwSetDropCallback(window, glfwDropCallback);
glfwMakeContextCurrent(window);
+ if (gl3wInit() != 0)
+ {
+ fprintf(stderr, "Failed to make initialize gl3w.\n");
+ glfwTerminate();
+ return 1;
+ }
// Enable VSYNC.
glfwSwapInterval(1);
+ // Setup ImGui
+ ImGui::CreateContext();
+ ImGuiIO& io = ImGui::GetIO();
+ (void)io;
+
+ ImGui::StyleColorsDark();
+ ImGui_ImplGlfw_InitForOpenGL(window, true);
+ ImGui_ImplOpenGL3_Init("#version 150");
+ io.Fonts->AddFontDefault();
+
// Setup Skia
GrContextOptions options;
sk_sp<GrContext> context = GrContext::MakeGL(nullptr, options);
animationInstance->advance(elapsed);
animationInstance->apply(artboard);
}
+ else if (stateMachineInstance != nullptr)
+ {
+ stateMachineInstance->advance(elapsed);
+ stateMachineInstance->apply(artboard);
+ }
artboard->advance(elapsed);
rive::SkiaRenderer renderer(canvas);
context->flush();
+ ImGui_ImplOpenGL3_NewFrame();
+ ImGui_ImplGlfw_NewFrame();
+ ImGui::NewFrame();
+
+ if (artboard != nullptr)
+ {
+ ImGui::Begin(filename.c_str(), nullptr);
+ if (ImGui::ListBox(
+ "Animations",
+ &animationIndex,
+ [](void* data, int index, const char** name) {
+ const char* animationName =
+ artboard->animation(index)->name().c_str();
+ *name = animationName;
+ return true;
+ },
+ artboard,
+ artboard->animationCount(),
+ 4))
+ {
+ stateMachineIndex = -1;
+ initAnimation(animationIndex);
+ }
+ if (ImGui::ListBox(
+ "State Machines",
+ &stateMachineIndex,
+ [](void* data, int index, const char** name) {
+ const char* machineName =
+ artboard->stateMachine(index)->name().c_str();
+ *name = machineName;
+ return true;
+ },
+ artboard,
+ artboard->stateMachineCount(),
+ 4))
+ {
+ animationIndex = -1;
+ initStateMachine(stateMachineIndex);
+ }
+ if (stateMachineInstance != nullptr)
+ {
+
+ ImGui::Columns(2);
+ ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.6666);
+
+ for (int i = 0; i < stateMachineInstance->inputCount(); i++)
+ {
+ auto inputInstance = stateMachineInstance->input(i);
+
+ if (inputInstance->input()->is<rive::StateMachineNumber>())
+ {
+ // ImGui requires names as id's, use ## to hide the
+ // label but still give it an id.
+ char label[256];
+ snprintf(label, 256, "##%u", i);
+
+ auto number =
+ static_cast<rive::SMINumber*>(inputInstance);
+ float v = number->value();
+ ImGui::InputFloat(label, &v, 1.0f, 2.0f, "%.3f");
+ number->value(v);
+ ImGui::NextColumn();
+ }
+ else if (inputInstance->input()
+ ->is<rive::StateMachineTrigger>())
+ {
+ // ImGui requires names as id's, use ## to hide the
+ // label but still give it an id.
+ char label[256];
+ snprintf(label, 256, "Fire##%u", i);
+ if (ImGui::Button(label))
+ {
+ auto trigger =
+ static_cast<rive::SMITrigger*>(inputInstance);
+ trigger->fire();
+ }
+ ImGui::NextColumn();
+ }
+ else if (inputInstance->input()
+ ->is<rive::StateMachineBool>())
+ {
+ // ImGui requires names as id's, use ## to hide the
+ // label but still give it an id.
+ char label[256];
+ snprintf(label, 256, "##%u", i);
+ auto boolInput =
+ static_cast<rive::SMIBool*>(inputInstance);
+ bool value = boolInput->value();
+
+ ImGui::Checkbox(label, &value);
+ boolInput->value(value);
+ ImGui::NextColumn();
+ }
+ ImGui::Text("%s", inputInstance->input()->name().c_str());
+ ImGui::NextColumn();
+ }
+
+ ImGui::Columns(1);
+ }
+ ImGui::End();
+ }
+ else
+ {
+ ImGui::Text("Drop a .riv file to preview.");
+ }
+
+ ImGui::Render();
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+
glfwSwapBuffers(window);
glfwPollEvents();
}
delete currentFile;
+ delete[] fileBytes;
// Cleanup Skia.
delete surface;
context = nullptr;
+ ImGui_ImplGlfw_Shutdown();
+
// Cleanup GLFW.
glfwDestroyWindow(window);
glfwTerminate();
--- /dev/null
+#include "animation/animation_state.hpp"
+#include "animation/linear_animation.hpp"
+#include "core_context.hpp"
+#include "artboard.hpp"
+
+using namespace rive;
int mid = 0;
float closestSeconds = 0.0f;
int start = 0;
- auto numKeyFrames = m_KeyFrames.size();
- int end = (int)numKeyFrames - 1;
+ auto numKeyFrames = static_cast<int>(m_KeyFrames.size());
+ int end = numKeyFrames - 1;
while (start <= end)
{
mid = (start + end) >> 1;
}
importer->addKeyedProperty(this);
return Super::import(importStack);
-}
\ No newline at end of file
+}
#include "animation/layer_state.hpp"
-using namespace rive;
#include "animation/transition_bool_condition.hpp"
+#include "importers/import_stack.hpp"
+#include "importers/state_machine_layer_importer.hpp"
+#include "generated/animation/state_machine_layer_base.hpp"
+#include "animation/state_transition.hpp"
+
+using namespace rive;
using namespace rive;
+LayerState::~LayerState()
+{
+ for (auto transition : m_Transitions)
+ {
+ delete transition;
+ }
+}
+
StatusCode LayerState::onAddedDirty(CoreContext* context)
{
+ StatusCode code;
+ for (auto transition : m_Transitions)
+ {
+ if ((code = transition->onAddedDirty(context)) != StatusCode::Ok)
+ {
+ return code;
+ }
+ }
return StatusCode::Ok;
}
StatusCode LayerState::onAddedClean(CoreContext* context)
{
+ StatusCode code;
+ for (auto transition : m_Transitions)
+ {
+ if ((code = transition->onAddedClean(context)) != StatusCode::Ok)
+ {
+ return code;
+ }
+ }
return StatusCode::Ok;
+}
+
+StatusCode LayerState::import(ImportStack& importStack)
+{
+ auto layerImporter = importStack.latest<StateMachineLayerImporter>(
+ StateMachineLayerBase::typeKey);
+ if (layerImporter == nullptr)
+ {
+ return StatusCode::MissingObject;
+ }
+ layerImporter->addState(this);
+ return Super::import(importStack);
+}
+
+void LayerState::addTransition(StateTransition* transition)
+{
+ m_Transitions.push_back(transition);
}
\ No newline at end of file
m_KeyedObjects.push_back(object);
}
-void LinearAnimation::apply(Artboard* artboard, float time, float mix)
+void LinearAnimation::apply(Artboard* artboard, float time, float mix) const
{
for (auto object : m_KeyedObjects)
{
}
artboardImporter->addAnimation(this);
return Super::import(importStack);
+}
+
+float LinearAnimation::startSeconds() const
+{
+ return (enableWorkArea() ? workStart() : 0) / (float)fps();
+}
+float LinearAnimation::endSeconds() const
+{
+ return (enableWorkArea() ? workEnd() : duration()) / (float)fps();
+}
+float LinearAnimation::durationSeconds() const
+{
+ return endSeconds() - startSeconds();
}
\ No newline at end of file
#include "animation/linear_animation_instance.hpp"
+#include "animation/linear_animation.hpp"
#include "animation/loop.hpp"
#include <cmath>
using namespace rive;
-LinearAnimationInstance::LinearAnimationInstance(LinearAnimation* animation) :
+LinearAnimationInstance::LinearAnimationInstance(
+ const LinearAnimation* animation) :
m_Animation(animation),
- m_Time(animation->enableWorkArea() ? (float) animation->workStart() / animation->fps() : 0),
+ m_Time(animation->enableWorkArea()
+ ? (float)animation->workStart() / animation->fps()
+ : 0),
+ m_TotalTime(0.0f),
+ m_LastTotalTime(0.0f),
+ m_SpilledTime(0.0f),
m_Direction(1)
{
}
bool LinearAnimationInstance::advance(float elapsedSeconds)
{
- LinearAnimation& animation = *m_Animation;
+ const LinearAnimation& animation = *m_Animation;
m_Time += elapsedSeconds * animation.speed() * m_Direction;
+ m_LastTotalTime = m_TotalTime;
+ m_TotalTime += elapsedSeconds;
int fps = animation.fps();
bool keepGoing = true;
bool didLoop = false;
+ m_SpilledTime = 0.0f;
switch (animation.loop())
{
if (frames > end)
{
keepGoing = false;
+ m_SpilledTime = (frames - end) / fps;
frames = end;
m_Time = frames / fps;
didLoop = true;
case Loop::loop:
if (frames >= end)
{
+ m_SpilledTime = (frames - end) / fps;
frames = m_Time * fps;
frames = start + std::fmod(frames - start, range);
m_Time = frames / fps;
{
if (m_Direction == 1 && frames >= end)
{
+ m_SpilledTime = (frames - end) / fps;
m_Direction = -1;
frames = end + (end - frames);
m_Time = frames / fps;
}
else if (m_Direction == -1 && frames < start)
{
+ m_SpilledTime = (start - frames) / fps;
m_Direction = 1;
frames = start + (start - frames);
m_Time = frames / fps;
return;
}
m_Time = value;
+ // Make sure to keep last and total in relative lockstep so state machines
+ // can track change even when setting time.
+ auto diff = m_TotalTime - m_LastTotalTime;
+
+ int start = (m_Animation->enableWorkArea() ? m_Animation->workStart() : 0) *
+ m_Animation->fps();
+ m_TotalTime = value - start;
+ m_LastTotalTime = m_TotalTime - diff;
+
m_Direction = 1;
}
--- /dev/null
+#include "animation/state_machine.hpp"
+#include "artboard.hpp"
+#include "importers/artboard_importer.hpp"
+#include "animation/state_machine_layer.hpp"
+#include "animation/state_machine_input.hpp"
+
+using namespace rive;
+
+StateMachine::~StateMachine()
+{
+ for (auto object : m_Inputs)
+ {
+ delete object;
+ }
+ for (auto object : m_Layers)
+ {
+ delete object;
+ }
+}
+
+StatusCode StateMachine::onAddedDirty(CoreContext* context)
+{
+ StatusCode code;
+ for (auto object : m_Inputs)
+ {
+ if ((code = object->onAddedDirty(context)) != StatusCode::Ok)
+ {
+ return code;
+ }
+ }
+ for (auto object : m_Layers)
+ {
+ if ((code = object->onAddedDirty(context)) != StatusCode::Ok)
+ {
+ return code;
+ }
+ }
+ return StatusCode::Ok;
+}
+
+StatusCode StateMachine::onAddedClean(CoreContext* context)
+{
+ StatusCode code;
+ for (auto object : m_Inputs)
+ {
+ if ((code = object->onAddedClean(context)) != StatusCode::Ok)
+ {
+ return code;
+ }
+ }
+ for (auto object : m_Layers)
+ {
+ if ((code = object->onAddedClean(context)) != StatusCode::Ok)
+ {
+ return code;
+ }
+ }
+ return StatusCode::Ok;
+}
+
+StatusCode StateMachine::import(ImportStack& importStack)
+{
+ auto artboardImporter =
+ importStack.latest<ArtboardImporter>(ArtboardBase::typeKey);
+ if (artboardImporter == nullptr)
+ {
+ return StatusCode::MissingObject;
+ }
+ artboardImporter->addStateMachine(this);
+ return Super::import(importStack);
+}
+
+void StateMachine::addLayer(StateMachineLayer* layer)
+{
+ m_Layers.push_back(layer);
+}
+
+void StateMachine::addInput(StateMachineInput* input)
+{
+ m_Inputs.push_back(input);
+}
+
+const StateMachineInput* StateMachine::input(std::string name) const
+{
+ for (auto input : m_Inputs)
+ {
+ if (input->name() == name)
+ {
+ return input;
+ }
+ }
+ return nullptr;
+}
+
+const StateMachineInput* StateMachine::input(size_t index) const
+{
+ if (index >= 0 && index < m_Inputs.size())
+ {
+ return m_Inputs[index];
+ }
+ return nullptr;
+}
+
+const StateMachineLayer* StateMachine::layer(std::string name) const
+{
+ for (auto layer : m_Layers)
+ {
+ if (layer->name() == name)
+ {
+ return layer;
+ }
+ }
+ return nullptr;
+}
+
+const StateMachineLayer* StateMachine::layer(size_t index) const
+{
+
+ if (index >= 0 && index < m_Layers.size())
+ {
+ return m_Layers[index];
+ }
+ return nullptr;
+}
\ No newline at end of file
#include "animation/state_machine_input.hpp"
+#include "importers/import_stack.hpp"
+#include "importers/state_machine_importer.hpp"
+#include "generated/animation/state_machine_base.hpp"
using namespace rive;
StatusCode StateMachineInput::onAddedClean(CoreContext* context)
{
return StatusCode::Ok;
+}
+
+StatusCode StateMachineInput::import(ImportStack& importStack)
+{
+ auto stateMachineImporter =
+ importStack.latest<StateMachineImporter>(StateMachineBase::typeKey);
+ if (stateMachineImporter == nullptr)
+ {
+ return StatusCode::MissingObject;
+ }
+ stateMachineImporter->addInput(this);
+ return Super::import(importStack);
}
\ No newline at end of file
--- /dev/null
+#include "animation/state_machine_input_instance.hpp"
+#include "animation/state_machine_bool.hpp"
+#include "animation/state_machine_number.hpp"
+#include "animation/state_machine_trigger.hpp"
+#include "animation/state_machine_instance.hpp"
+
+using namespace rive;
+
+SMIInput::SMIInput(const StateMachineInput* input,
+ StateMachineInstance* machineInstance) :
+ m_MachineInstance(machineInstance), m_Input(input)
+{
+}
+
+uint16_t SMIInput::inputCoreType() const { return m_Input->coreType(); }
+
+const std::string& SMIInput::name() const { return m_Input->name(); }
+
+void SMIInput::valueChanged() { m_MachineInstance->markNeedsAdvance(); }
+
+// bool
+
+SMIBool::SMIBool(const StateMachineBool* input,
+ StateMachineInstance* machineInstance) :
+ SMIInput(input, machineInstance), m_Value(input->value())
+{
+}
+
+void SMIBool::value(bool newValue)
+{
+ if (m_Value == newValue)
+ {
+ return;
+ }
+ m_Value = newValue;
+ valueChanged();
+}
+
+// number
+SMINumber::SMINumber(const StateMachineNumber* input,
+ StateMachineInstance* machineInstance) :
+ SMIInput(input, machineInstance), m_Value(input->value())
+{
+}
+
+void SMINumber::value(float newValue)
+{
+ if (m_Value == newValue)
+ {
+ return;
+ }
+ m_Value = newValue;
+ valueChanged();
+}
+
+// trigger
+SMITrigger::SMITrigger(const StateMachineTrigger* input,
+ StateMachineInstance* machineInstance) :
+ SMIInput(input, machineInstance)
+{
+}
+
+void SMITrigger::fire()
+{
+ if (m_Fired)
+ {
+ return;
+ }
+ m_Fired = true;
+ valueChanged();
+}
--- /dev/null
+#include "animation/state_machine_instance.hpp"
+#include "animation/state_machine_input.hpp"
+#include "animation/state_machine_bool.hpp"
+#include "animation/state_machine_number.hpp"
+#include "animation/state_machine_trigger.hpp"
+#include "animation/state_machine_input_instance.hpp"
+#include "animation/state_machine.hpp"
+#include "animation/state_machine_layer.hpp"
+#include "animation/any_state.hpp"
+#include "animation/entry_state.hpp"
+#include "animation/state_transition.hpp"
+#include "animation/transition_condition.hpp"
+#include "animation/linear_animation_instance.hpp"
+#include "animation/animation_state.hpp"
+
+using namespace rive;
+
+namespace rive
+{
+ class StateMachineLayerInstance
+ {
+ private:
+ static const int maxIterations = 100;
+ const StateMachineLayer* m_Layer = nullptr;
+ const LayerState* m_CurrentState = nullptr;
+ const LayerState* m_StateFrom = nullptr;
+ const StateTransition* m_Transition = nullptr;
+
+ bool m_HoldAnimationFrom = false;
+ LinearAnimationInstance* m_AnimationInstance = nullptr;
+ LinearAnimationInstance* m_AnimationInstanceFrom = nullptr;
+ float m_Mix = 1.0f;
+
+ public:
+ void init(const StateMachineLayer* layer)
+ {
+ m_Layer = layer;
+ m_CurrentState = m_Layer->entryState();
+ }
+
+ bool advance(float seconds, SMIInput** inputs)
+ {
+ bool keepGoing = false;
+ if (m_AnimationInstance != nullptr)
+ {
+ keepGoing = m_AnimationInstance->advance(seconds);
+ }
+
+ if (m_Transition != nullptr && m_StateFrom != nullptr &&
+ m_Transition->duration() != 0)
+ {
+ m_Mix =
+ std::min(1.0f,
+ std::max(0.0f,
+ (m_Mix + seconds / m_Transition->mixTime(
+ m_StateFrom))));
+ }
+ else
+ {
+ m_Mix = 1.0f;
+ }
+
+ if (m_AnimationInstanceFrom != nullptr && m_Mix < 1.0f &&
+ !m_HoldAnimationFrom)
+ {
+ m_AnimationInstanceFrom->advance(seconds);
+ }
+
+ for (int i = 0; updateState(inputs); i++)
+ {
+ if (i == maxIterations)
+ {
+ fprintf(stderr, "StateMachine exceeded max iterations.\n");
+ return false;
+ }
+ }
+ return m_Mix != 1.0f || keepGoing;
+ }
+
+ bool updateState(SMIInput** inputs)
+ {
+ if (tryChangeState(m_Layer->anyState(), inputs))
+ {
+ return true;
+ }
+
+ return tryChangeState(m_CurrentState, inputs);
+ }
+
+ bool changeState(const LayerState* stateTo)
+ {
+ if (m_CurrentState == stateTo)
+ {
+ return false;
+ }
+ m_CurrentState = stateTo;
+ return true;
+ }
+
+ bool tryChangeState(const LayerState* stateFrom, SMIInput** inputs)
+ {
+ if (stateFrom == nullptr)
+ {
+ return false;
+ }
+ for (size_t i = 0, length = stateFrom->transitionCount();
+ i < length;
+ i++)
+ {
+ auto transition = stateFrom->transition(i);
+
+ if (transition->isDisabled())
+ {
+ continue;
+ }
+
+ bool valid = true;
+ for (size_t j = 0, length = transition->conditionCount();
+ j < length;
+ j++)
+ {
+ auto condition = transition->condition(j);
+ auto input = inputs[condition->inputId()];
+ if (!condition->evaluate(input))
+ {
+ valid = false;
+ break;
+ }
+ }
+
+ // If all the conditions evaluated to true, make sure the exit
+ // time (when set) is also valid.
+ if (valid && stateFrom->is<AnimationState>() &&
+ transition->enableExitTime())
+ {
+ auto fromAnimation =
+ stateFrom->as<AnimationState>()->animation();
+ assert(fromAnimation != nullptr);
+ auto lastTime = m_AnimationInstance->lastTotalTime();
+ auto time = m_AnimationInstance->totalTime();
+ auto exitTime =
+ transition->exitTimeSeconds(stateFrom, true);
+ if (exitTime < fromAnimation->durationSeconds())
+ {
+ // Get exit time relative to the loop lastTime was in.
+ exitTime +=
+ (int)(lastTime / fromAnimation->durationSeconds()) *
+ fromAnimation->durationSeconds();
+ }
+ if (time < exitTime)
+ {
+ valid = false;
+ }
+ }
+
+ // Try to change the state...
+ if (valid && changeState(transition->stateTo()))
+ {
+ // state actually has changed
+ m_Transition = transition;
+ m_StateFrom = stateFrom;
+
+ // If we had an exit time and wanted to pause on exit, make
+ // sure to hold the exit time.
+ if (transition->pauseOnExit() &&
+ transition->enableExitTime() &&
+ m_AnimationInstance != nullptr)
+ {
+ m_AnimationInstance->time(
+ transition->exitTimeSeconds(stateFrom, false));
+ }
+
+ if (m_Mix != 0.0f)
+ {
+ m_HoldAnimationFrom = transition->pauseOnExit();
+ delete m_AnimationInstanceFrom;
+ m_AnimationInstanceFrom = m_AnimationInstance;
+
+ // Don't let it get deleted as we've passed it on to the
+ // from.
+ m_AnimationInstance = nullptr;
+ }
+ else
+ {
+ delete m_AnimationInstance;
+ m_AnimationInstance = nullptr;
+ }
+ if (m_CurrentState->is<AnimationState>())
+ {
+ auto animationState =
+ m_CurrentState->as<AnimationState>();
+ auto spilledTime =
+ m_AnimationInstanceFrom == nullptr
+ ? 0
+ : m_AnimationInstanceFrom->spilledTime();
+ if (animationState->animation() != nullptr)
+ {
+ m_AnimationInstance = new LinearAnimationInstance(
+ animationState->animation());
+ m_AnimationInstance->advance(spilledTime);
+ }
+ m_Mix = 0.0f;
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void apply(Artboard* artboard) const
+ {
+ if (m_AnimationInstanceFrom != nullptr && m_Mix < 1.0f)
+ {
+ m_AnimationInstanceFrom->animation()->apply(
+ artboard, m_AnimationInstanceFrom->time(), 1.0 - m_Mix);
+ }
+ if (m_AnimationInstance != nullptr)
+ {
+ m_AnimationInstance->animation()->apply(
+ artboard, m_AnimationInstance->time(), m_Mix);
+ }
+ }
+ };
+} // namespace rive
+
+StateMachineInstance::StateMachineInstance(StateMachine* machine) :
+ m_Machine(machine)
+{
+ m_InputCount = machine->inputCount();
+ m_InputInstances = new SMIInput*[m_InputCount];
+ for (int i = 0; i < m_InputCount; i++)
+ {
+ auto input = machine->input(i);
+ if (input == nullptr)
+ {
+ m_InputInstances[i] = nullptr;
+ continue;
+ }
+ switch (input->coreType())
+ {
+ case StateMachineBool::typeKey:
+ m_InputInstances[i] =
+ new SMIBool(input->as<StateMachineBool>(), this);
+ break;
+ case StateMachineNumber::typeKey:
+ m_InputInstances[i] =
+ new SMINumber(input->as<StateMachineNumber>(), this);
+ break;
+ case StateMachineTrigger::typeKey:
+ m_InputInstances[i] =
+ new SMITrigger(input->as<StateMachineTrigger>(), this);
+ break;
+ default:
+ // Sanity check.
+ m_InputInstances[i] = nullptr;
+ break;
+ }
+ }
+
+ m_LayerCount = machine->layerCount();
+ m_Layers = new StateMachineLayerInstance[m_LayerCount];
+ for (int i = 0; i < m_LayerCount; i++)
+ {
+ m_Layers[i].init(machine->layer(i));
+ }
+}
+
+StateMachineInstance::~StateMachineInstance()
+{
+ for (int i = 0; i < m_InputCount; i++)
+ {
+ delete m_InputInstances[i];
+ }
+ delete[] m_InputInstances;
+ delete[] m_Layers;
+}
+
+bool StateMachineInstance::advance(float seconds)
+{
+ m_NeedsAdvance = false;
+ for (int i = 0; i < m_LayerCount; i++)
+ {
+ if (m_Layers[i].advance(seconds, m_InputInstances))
+ {
+ m_NeedsAdvance = true;
+ }
+ }
+
+ for (int i = 0; i < m_InputCount; i++)
+ {
+ m_InputInstances[i]->advanced();
+ }
+
+ return m_NeedsAdvance;
+}
+
+void StateMachineInstance::apply(Artboard* artboard) const
+{
+ for (int i = 0; i < m_LayerCount; i++)
+ {
+ m_Layers[i].apply(artboard);
+ }
+}
+
+void StateMachineInstance::markNeedsAdvance() { m_NeedsAdvance = true; }
+bool StateMachineInstance::needsAdvance() const { return m_NeedsAdvance; }
+
+SMIInput* StateMachineInstance::input(size_t index) const
+{
+ if (index < m_InputCount)
+ {
+ return m_InputInstances[index];
+ }
+ return nullptr;
+}
+
+SMIBool* StateMachineInstance::getBool(std::string name) const
+{
+ for (int i = 0; i < m_InputCount; i++)
+ {
+ auto input = m_InputInstances[i]->input();
+ if (input->is<StateMachineBool>() && input->name() == name)
+ {
+ return static_cast<SMIBool*>(m_InputInstances[i]);
+ }
+ }
+ return nullptr;
+}
+
+SMINumber* StateMachineInstance::getNumber(std::string name) const
+{
+ for (int i = 0; i < m_InputCount; i++)
+ {
+ auto input = m_InputInstances[i]->input();
+ if (input->is<StateMachineNumber>() && input->name() == name)
+ {
+ return static_cast<SMINumber*>(m_InputInstances[i]);
+ }
+ }
+ return nullptr;
+}
+SMITrigger* StateMachineInstance::getTrigger(std::string name) const
+{
+ for (int i = 0; i < m_InputCount; i++)
+ {
+ auto input = m_InputInstances[i]->input();
+ if (input->is<StateMachineTrigger>() && input->name() == name)
+ {
+ return static_cast<SMITrigger*>(m_InputInstances[i]);
+ }
+ }
+ return nullptr;
+}
\ No newline at end of file
#include "animation/state_machine_layer.hpp"
+#include "importers/import_stack.hpp"
+#include "importers/state_machine_importer.hpp"
+#include "generated/animation/state_machine_base.hpp"
+#include "animation/any_state.hpp"
+#include "animation/entry_state.hpp"
+#include "animation/exit_state.hpp"
using namespace rive;
+StateMachineLayer::~StateMachineLayer()
+{
+ for (auto state : m_States)
+ {
+ delete state;
+ }
+}
+
StatusCode StateMachineLayer::onAddedDirty(CoreContext* context)
{
+ StatusCode code;
+ for (auto state : m_States)
+ {
+ if ((code = state->onAddedDirty(context)) != StatusCode::Ok)
+ {
+ return code;
+ }
+ switch (state->coreType())
+ {
+ case AnyState::typeKey:
+ m_Any = state->as<AnyState>();
+ break;
+ case EntryState::typeKey:
+ m_Entry = state->as<EntryState>();
+ break;
+ case ExitState::typeKey:
+ m_Exit = state->as<ExitState>();
+ break;
+ }
+ }
+ if (m_Any == nullptr || m_Entry == nullptr || m_Exit == nullptr)
+ {
+ // The layer is corrupt, we must have all three of these states.
+ return StatusCode::InvalidObject;
+ }
+
return StatusCode::Ok;
}
StatusCode StateMachineLayer::onAddedClean(CoreContext* context)
{
+ StatusCode code;
+ for (auto state : m_States)
+ {
+ if ((code = state->onAddedClean(context)) != StatusCode::Ok)
+ {
+ return code;
+ }
+ }
+
return StatusCode::Ok;
-}
\ No newline at end of file
+}
+
+void StateMachineLayer::addState(LayerState* state)
+{
+ m_States.push_back(state);
+}
+
+StatusCode StateMachineLayer::import(ImportStack& importStack)
+{
+ auto stateMachineImporter =
+ importStack.latest<StateMachineImporter>(StateMachineBase::typeKey);
+ if (stateMachineImporter == nullptr)
+ {
+ return StatusCode::MissingObject;
+ }
+ stateMachineImporter->addLayer(this);
+ return Super::import(importStack);
+}
+++ /dev/null
-#include "animation/state_machine_trigger.hpp"
-
-using namespace rive;
-
-void StateMachineTrigger::fire() { m_Fired = true; }
-
-void StateMachineTrigger::reset() { m_Fired = false; }
\ No newline at end of file
#include "animation/state_transition.hpp"
+#include "importers/import_stack.hpp"
+#include "importers/layer_state_importer.hpp"
+#include "animation/layer_state.hpp"
+#include "animation/transition_condition.hpp"
+#include "animation/animation_state.hpp"
+#include "animation/linear_animation.hpp"
using namespace rive;
+StateTransition::~StateTransition()
+{
+ for (auto condition : m_Conditions)
+ {
+ delete condition;
+ }
+}
+
StatusCode StateTransition::onAddedDirty(CoreContext* context)
{
+ StatusCode code;
+ for (auto condition : m_Conditions)
+ {
+ if ((code = condition->onAddedDirty(context)) != StatusCode::Ok)
+ {
+ return code;
+ }
+ }
return StatusCode::Ok;
}
StatusCode StateTransition::onAddedClean(CoreContext* context)
{
+ StatusCode code;
+ for (auto condition : m_Conditions)
+ {
+ if ((code = condition->onAddedClean(context)) != StatusCode::Ok)
+ {
+ return code;
+ }
+ }
return StatusCode::Ok;
-}
\ No newline at end of file
+}
+
+StatusCode StateTransition::import(ImportStack& importStack)
+{
+ auto stateImporter =
+ importStack.latest<LayerStateImporter>(LayerState::typeKey);
+ if (stateImporter == nullptr)
+ {
+ return StatusCode::MissingObject;
+ }
+ stateImporter->addTransition(this);
+ return Super::import(importStack);
+}
+
+void StateTransition::addCondition(TransitionCondition* condition)
+{
+ m_Conditions.push_back(condition);
+}
+
+float StateTransition::mixTime(const LayerState* stateFrom) const
+{
+ if (duration() == 0)
+ {
+ return 0;
+ }
+ if ((transitionFlags() & StateTransitionFlags::DurationIsPercentage) ==
+ StateTransitionFlags::DurationIsPercentage)
+ {
+ float animationDuration = 0.0f;
+ if (stateFrom->is<AnimationState>())
+ {
+ auto animation = stateFrom->as<AnimationState>()->animation();
+ if (animation != nullptr)
+ {
+ animationDuration = animation->durationSeconds();
+ }
+ }
+ return duration() / 100.0f * animationDuration;
+ }
+ else
+ {
+ return duration() / 1000.0f;
+ }
+}
+
+float StateTransition::exitTimeSeconds(const LayerState* stateFrom,
+ bool relativeToWorkArea) const
+{
+ auto exitValue = exitTime();
+ if (exitValue == 0)
+ {
+ return 0;
+ }
+
+ float animationDuration = 0.0f;
+ float animationOrigin = 0.0f;
+ if (stateFrom->is<AnimationState>())
+ {
+ auto animation = stateFrom->as<AnimationState>()->animation();
+ animationDuration = animation->durationSeconds();
+ animationOrigin = relativeToWorkArea ? 0 : animation->startSeconds();
+ }
+
+ if ((transitionFlags() & StateTransitionFlags::ExitTimeIsPercentage) ==
+ StateTransitionFlags::ExitTimeIsPercentage)
+ {
+ return animationOrigin + exitValue / 100.0f * animationDuration;
+ }
+ else
+ {
+ return animationOrigin + exitValue / 1000.0f;
+ }
+}
--- /dev/null
+#include "animation/transition_bool_condition.hpp"
+#include "animation/state_machine_input_instance.hpp"
+#include "animation/state_machine_bool.hpp"
+#include "animation/transition_condition_op.hpp"
+
+using namespace rive;
+
+bool TransitionBoolCondition::validateInputType(
+ const StateMachineInput* input) const
+{
+ // A null input is valid as the StateMachine can attempt to limp along if we
+ // introduce new input types that old conditions are expected to handle in
+ // newer runtimes. The older runtimes will just evaluate them to true.
+ return input == nullptr || input->is<StateMachineBool>();
+}
+
+bool TransitionBoolCondition::evaluate(const SMIInput* inputInstance) const
+{
+ if (inputInstance == nullptr)
+ {
+ return true;
+ }
+ auto boolInput = reinterpret_cast<const SMIBool*>(inputInstance);
+
+ return (boolInput->value() && op() == TransitionConditionOp::equal) ||
+ (!boolInput->value() && op() == TransitionConditionOp::notEqual);
+}
\ No newline at end of file
#include "animation/transition_bool_condition.hpp"
+#include "animation/state_transition.hpp"
+#include "importers/state_transition_importer.hpp"
+#include "importers/state_machine_importer.hpp"
+#include "animation/state_machine.hpp"
using namespace rive;
StatusCode TransitionCondition::onAddedClean(CoreContext* context)
{
return StatusCode::Ok;
+}
+
+StatusCode TransitionCondition::import(ImportStack& importStack)
+{
+ auto stateMachineImporter =
+ importStack.latest<StateMachineImporter>(StateMachine::typeKey);
+ if (stateMachineImporter == nullptr)
+ {
+ return StatusCode::MissingObject;
+ }
+
+ // Make sure the inputId doesn't overflow the input buffer.
+ if (inputId() < 0 ||
+ inputId() >= stateMachineImporter->stateMachine()->inputCount())
+ {
+ return StatusCode::InvalidObject;
+ }
+ if (!validateInputType(
+ stateMachineImporter->stateMachine()->input((size_t)inputId())))
+ {
+ return StatusCode::InvalidObject;
+ }
+
+ auto transitionImporter =
+ importStack.latest<StateTransitionImporter>(StateTransition::typeKey);
+ if (transitionImporter == nullptr)
+ {
+ return StatusCode::MissingObject;
+ }
+ transitionImporter->addCondition(this);
+ return Super::import(importStack);
}
\ No newline at end of file
--- /dev/null
+#include "animation/transition_number_condition.hpp"
+#include "animation/state_machine_input_instance.hpp"
+#include "animation/state_machine_number.hpp"
+#include "animation/transition_condition_op.hpp"
+
+using namespace rive;
+
+bool TransitionNumberCondition::validateInputType(
+ const StateMachineInput* input) const
+{
+ // A null input is valid as the StateMachine can attempt to limp along if we
+ // introduce new input types that old conditions are expected to handle in
+ // newer runtimes. The older runtimes will just evaluate them to true.
+ return input == nullptr || input->is<StateMachineNumber>();
+}
+
+bool TransitionNumberCondition::evaluate(const SMIInput* inputInstance) const
+{
+ if (inputInstance == nullptr)
+ {
+ return true;
+ }
+ auto numberInput = reinterpret_cast<const SMINumber*>(inputInstance);
+
+ switch (op())
+ {
+ case TransitionConditionOp::equal:
+ return numberInput->value() == value();
+ case TransitionConditionOp::notEqual:
+ return numberInput->value() != value();
+ case TransitionConditionOp::lessThanOrEqual:
+ return numberInput->value() <= value();
+ case TransitionConditionOp::lessThan:
+ return numberInput->value() < value();
+ case TransitionConditionOp::greaterThanOrEqual:
+ return numberInput->value() >= value();
+ case TransitionConditionOp::greaterThan:
+ return numberInput->value() > value();
+ }
+}
\ No newline at end of file
--- /dev/null
+#include "animation/transition_trigger_condition.hpp"
+#include "animation/state_machine_input_instance.hpp"
+#include "animation/state_machine_trigger.hpp"
+#include "animation/transition_condition_op.hpp"
+
+using namespace rive;
+
+bool TransitionTriggerCondition::validateInputType(
+ const StateMachineInput* input) const
+{
+ // A null input is valid as the StateMachine can attempt to limp along if we
+ // introduce new input types that old conditions are expected to handle in
+ // newer runtimes. The older runtimes will just evaluate them to true.
+ return input == nullptr || input->is<StateMachineTrigger>();
+}
+
+bool TransitionTriggerCondition::evaluate(const SMIInput* inputInstance) const
+{
+ if (inputInstance == nullptr)
+ {
+ return true;
+ }
+ auto triggerInput = reinterpret_cast<const SMITrigger*>(inputInstance);
+
+ if (triggerInput->m_Fired)
+ {
+ return true;
+ }
+ return false;
+}
\ No newline at end of file
Core* Artboard::resolve(int id) const
{
- if (id < 0 || id >= m_Objects.size())
+ if (id < 0 || id >= static_cast<int>(m_Objects.size()))
{
return nullptr;
}
{
const int maxSteps = 100;
int step = 0;
- int count = (int)m_DependencyOrder.size();
+ auto count = m_DependencyOrder.size();
while (hasDirt(ComponentDirt::Components) && step < maxSteps)
{
// m_Dirt = m_Dirt & ~ComponentDirt::Components;
// Track dirt depth here so that if something else marks
// dirty, we restart.
- for (int i = 0; i < count; i++)
+ for (unsigned int i = 0; i < count; i++)
{
auto component = m_DependencyOrder[i];
m_DirtDepth = i;
AABB Artboard::bounds() const { return AABB(0.0f, 0.0f, width(), height()); }
-LinearAnimation* Artboard::firstAnimation()
+LinearAnimation* Artboard::firstAnimation() const
{
if (m_Animations.empty())
{
return m_Animations.front();
}
-LinearAnimation* Artboard::animation(std::string name)
+LinearAnimation* Artboard::animation(std::string name) const
{
for (auto animation : m_Animations)
{
return nullptr;
}
-LinearAnimation* Artboard::animation(size_t index)
+LinearAnimation* Artboard::animation(size_t index) const
{
- if (index < 0 || index >= m_Animations.size())
+ if (index >= m_Animations.size())
{
return nullptr;
}
return m_Animations[index];
}
-StateMachine* Artboard::firstStateMachine()
+StateMachine* Artboard::firstStateMachine() const
{
if (m_StateMachines.empty())
{
return m_StateMachines.front();
}
-StateMachine* Artboard::stateMachine(std::string name)
+StateMachine* Artboard::stateMachine(std::string name) const
{
for (auto machine : m_StateMachines)
{
return nullptr;
}
-StateMachine* Artboard::stateMachine(size_t index)
+StateMachine* Artboard::stateMachine(size_t index) const
{
- if (index < 0 || index >= m_StateMachines.size())
+ if (index >= m_StateMachines.size())
{
return nullptr;
}
return m_StateMachines[index];
-}
\ No newline at end of file
+}
#include "importers/keyed_object_importer.hpp"
#include "importers/keyed_property_importer.hpp"
#include "importers/linear_animation_importer.hpp"
+#include "importers/state_machine_importer.hpp"
+#include "importers/state_machine_layer_importer.hpp"
+#include "importers/layer_state_importer.hpp"
+#include "importers/state_transition_importer.hpp"
+#include "animation/any_state.hpp"
+#include "animation/entry_state.hpp"
+#include "animation/exit_state.hpp"
+#include "animation/animation_state.hpp"
// Default namespace for Rive Cpp code
using namespace rive;
}
if (object == nullptr)
{
- fprintf(stderr,
- "File contains an unknown object with coreType %llu, which "
- "this runtime doesn't understand.\n",
- coreObjectKey);
+ // fprintf(stderr,
+ // "File contains an unknown object with coreType %llu, which "
+ // "this runtime doesn't understand.\n",
+ // coreObjectKey);
return nullptr;
}
return object;
auto object = readRuntimeObject(reader, header);
if (object == nullptr)
{
- // See if there's an artboard on the stack, need to track the null
- // object as it'll still hold an id.
- auto importer =
- importStack.latest<ArtboardImporter>(Artboard::typeKey);
- if (importer == nullptr)
- {
- return ImportResult::malformed;
- }
- importer->addComponent(object);
+ importStack.readNullObject();
continue;
}
ImportStackObject* stackObject = nullptr;
importer->animation(), object->as<KeyedProperty>());
break;
}
+ case StateMachine::typeKey:
+ stackObject =
+ new StateMachineImporter(object->as<StateMachine>());
+ break;
+ case StateMachineLayer::typeKey:
+ {
+ auto artboardImporter =
+ importStack.latest<ArtboardImporter>(ArtboardBase::typeKey);
+ if (artboardImporter == nullptr)
+ {
+ return ImportResult::malformed;
+ }
+
+ stackObject = new StateMachineLayerImporter(
+ object->as<StateMachineLayer>(), artboardImporter);
+
+ break;
+ }
+ case EntryState::typeKey:
+ case ExitState::typeKey:
+ case AnyState::typeKey:
+ case AnimationState::typeKey:
+ stackObject = new LayerStateImporter(object->as<LayerState>());
+ stackType = LayerState::typeKey;
+ break;
+ case StateTransition::typeKey:
+ stackObject =
+ new StateTransitionImporter(object->as<StateTransition>());
+ break;
}
if (importStack.makeLatest(stackType, stackObject) != StatusCode::Ok)
{
}
return m_Artboards[0];
}
+
+Artboard* File::artboard(size_t index) const
+{
+ if (index >= m_Artboards.size())
+ {
+ return nullptr;
+ }
+ return m_Artboards[index];
+}
m_Artboard->addStateMachine(stateMachine);
}
-StatusCode ArtboardImporter::resolve() { return m_Artboard->initialize(); }
\ No newline at end of file
+StatusCode ArtboardImporter::resolve() { return m_Artboard->initialize(); }
+
+bool ArtboardImporter::readNullObject()
+{
+ addComponent(nullptr);
+ return true;
+}
\ No newline at end of file
{
keyFrame->computeSeconds(m_Animation->fps());
m_KeyedProperty->addKeyFrame(keyFrame);
+}
+
+bool KeyedPropertyImporter::readNullObject()
+{
+ // We don't need to add the null keyframe as nothing references them, but we
+ // do need to not allow the null to propagate up.
+ return true;
}
\ No newline at end of file
--- /dev/null
+#include "importers/layer_state_importer.hpp"
+#include "animation/state_transition.hpp"
+#include "animation/layer_state.hpp"
+
+using namespace rive;
+
+LayerStateImporter::LayerStateImporter(LayerState* state) : m_State(state) {}
+void LayerStateImporter::addTransition(StateTransition* transition)
+{
+ m_State->addTransition(transition);
+}
+
+StatusCode LayerStateImporter::resolve() { return StatusCode::Ok; }
\ No newline at end of file
--- /dev/null
+#include "importers/state_machine_importer.hpp"
+#include "animation/state_machine.hpp"
+
+using namespace rive;
+
+StateMachineImporter::StateMachineImporter(StateMachine* machine) :
+ m_StateMachine(machine)
+{
+}
+
+void StateMachineImporter::addLayer(StateMachineLayer* layer)
+{
+ m_StateMachine->addLayer(layer);
+}
+
+void StateMachineImporter::addInput(StateMachineInput* input)
+{
+ m_StateMachine->addInput(input);
+}
+
+bool StateMachineImporter::readNullObject()
+{
+ // Hard assumption that we won't add new layer types...
+ m_StateMachine->addInput(nullptr);
+ return true;
+}
+
+StatusCode StateMachineImporter::resolve() { return StatusCode::Ok; }
\ No newline at end of file
--- /dev/null
+#include "importers/state_machine_layer_importer.hpp"
+#include "importers/artboard_importer.hpp"
+#include "animation/state_machine_layer.hpp"
+#include "animation/animation_state.hpp"
+#include "animation/state_transition.hpp"
+#include "artboard.hpp"
+
+using namespace rive;
+StateMachineLayerImporter::StateMachineLayerImporter(
+ StateMachineLayer* layer, ArtboardImporter* artboardImporter) :
+ m_Layer(layer), m_ArtboardImporter(artboardImporter)
+{
+}
+void StateMachineLayerImporter::addState(LayerState* state)
+{
+ m_Layer->addState(state);
+}
+
+StatusCode StateMachineLayerImporter::resolve()
+{
+ auto artboard = m_ArtboardImporter->artboard();
+ for (auto state : m_Layer->m_States)
+ {
+ if (state->is<AnimationState>())
+ {
+ auto animationState = state->as<AnimationState>();
+
+ if (animationState->animationId() != -1)
+ {
+ animationState->m_Animation =
+ artboard->animation(animationState->animationId());
+ if (animationState->m_Animation == nullptr)
+ {
+ return StatusCode::MissingObject;
+ }
+ }
+ }
+ for (auto transition : state->m_Transitions)
+ {
+ if (transition->stateToId() < 0 ||
+ transition->stateToId() > m_Layer->m_States.size())
+ {
+ return StatusCode::InvalidObject;
+ }
+ transition->m_StateTo = m_Layer->m_States[transition->stateToId()];
+ }
+ }
+ return StatusCode::Ok;
+}
+
+bool StateMachineLayerImporter::readNullObject()
+{
+ // Add an 'empty' generic state that can be transitioned to/from but doesn't
+ // effectively do anything. This allows us to deal with unexpected new state
+ // types the runtime won't be able to understand. It'll still be able to
+ // make use of the state but it won't do anything visually.
+ addState(new LayerState());
+ return true;
+}
\ No newline at end of file
--- /dev/null
+#include "importers/state_transition_importer.hpp"
+#include "animation/transition_condition.hpp"
+#include "animation/state_transition.hpp"
+
+using namespace rive;
+
+StateTransitionImporter::StateTransitionImporter(StateTransition* transition) :
+ m_Transition(transition)
+{
+}
+void StateTransitionImporter::addCondition(TransitionCondition* condition)
+{
+ m_Transition->addCondition(condition);
+}
+
+StatusCode StateTransitionImporter::resolve() { return StatusCode::Ok; }
\ No newline at end of file
}
}
- for (int i = 1; i < length; i++)
+ for (size_t i = 1; i < length; i++)
{
auto vertex = vertices[i];
// // ascertaning it updates after both)
// m_Shape->pathChanged();
// }
-}
\ No newline at end of file
+}
void Polygon::resizeVertices(int newSize)
{
- auto currentSize = m_Vertices.size();
+ auto currentSize = static_cast<int>(m_Vertices.size());
if (newSize == currentSize)
{
{
if (hasDirt(value, ComponentDirt::Path))
{
- if (m_Vertices.size() != expectedSize())
+ if (static_cast<int>(m_Vertices.size()) != expectedSize())
{
resizeVertices(expectedSize());
}
buildPolygon();
}
Super::update(value);
-}
\ No newline at end of file
+}
delete[] bytes;
}
+TEST_CASE("artboards can be counted and accessed via index or name", "[file]")
+{
+ FILE* fp = fopen("../../test/assets/dependency_test.riv", "r");
+ REQUIRE(fp != nullptr);
+
+ fseek(fp, 0, SEEK_END);
+ auto length = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+ uint8_t* bytes = new uint8_t[length];
+ REQUIRE(fread(bytes, 1, length, fp) == length);
+ auto reader = rive::BinaryReader(bytes, length);
+ rive::File* file = nullptr;
+ auto result = rive::File::import(reader, &file);
+
+ REQUIRE(result == rive::ImportResult::success);
+ REQUIRE(file != nullptr);
+
+ // The default artboard can be accessed
+ REQUIRE(file->artboard() != nullptr);
+
+ // The artboards caqn be counted
+ REQUIRE(file->artboardCount() == 1);
+
+ // Artboards can be access by index
+ REQUIRE(file->artboard(0) != nullptr);
+
+ // Artboards can be accessed by name
+ REQUIRE(file->artboard("Blue") != nullptr);
+
+}
+
TEST_CASE("dependencies are as expected", "[file]")
{
// ┌────┐
--- /dev/null
+#include "catch.hpp"
+#include "core/binary_reader.hpp"
+#include "file.hpp"
+#include "animation/state_machine_bool.hpp"
+#include "animation/state_machine_layer.hpp"
+#include "animation/animation_state.hpp"
+#include "animation/entry_state.hpp"
+#include "animation/state_transition.hpp"
+#include "animation/state_machine_instance.hpp"
+#include "animation/state_machine_input_instance.hpp"
+#include <cstdio>
+
+TEST_CASE("file with state machine be read", "[file]")
+{
+ FILE* fp = fopen("../../test/assets/rocket.riv", "r");
+ REQUIRE(fp != nullptr);
+
+ fseek(fp, 0, SEEK_END);
+ auto length = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+ uint8_t* bytes = new uint8_t[length];
+ REQUIRE(fread(bytes, 1, length, fp) == length);
+ auto reader = rive::BinaryReader(bytes, length);
+ rive::File* file = nullptr;
+ auto result = rive::File::import(reader, &file);
+
+ REQUIRE(result == rive::ImportResult::success);
+ REQUIRE(file != nullptr);
+ auto artboard = file->artboard();
+ REQUIRE(artboard != nullptr);
+ REQUIRE(artboard->animationCount() == 3);
+ REQUIRE(artboard->stateMachineCount() == 1);
+
+ auto stateMachine = artboard->stateMachine("Button");
+ REQUIRE(stateMachine != nullptr);
+
+ REQUIRE(stateMachine->layerCount() == 1);
+ REQUIRE(stateMachine->inputCount() == 2);
+
+ auto hover = stateMachine->input("Hover");
+ REQUIRE(hover != nullptr);
+ REQUIRE(hover->is<rive::StateMachineBool>());
+
+ auto press = stateMachine->input("Press");
+ REQUIRE(press != nullptr);
+ REQUIRE(press->is<rive::StateMachineBool>());
+
+ auto layer = stateMachine->layer(0);
+ REQUIRE(layer->stateCount() == 6);
+
+ REQUIRE(layer->anyState() != nullptr);
+ REQUIRE(layer->entryState() != nullptr);
+ REQUIRE(layer->exitState() != nullptr);
+
+ int foundAnimationStates = 0;
+ for (int i = 0; i < layer->stateCount(); i++)
+ {
+ auto state = layer->state(i);
+ if (state->is<rive::AnimationState>())
+ {
+ foundAnimationStates++;
+ REQUIRE(state->as<rive::AnimationState>()->animation() != nullptr);
+ }
+ }
+
+ REQUIRE(foundAnimationStates == 3);
+
+ REQUIRE(layer->entryState()->transitionCount() == 1);
+ auto stateTo = layer->entryState()->transition(0)->stateTo();
+ REQUIRE(stateTo != nullptr);
+ REQUIRE(stateTo->is<rive::AnimationState>());
+ REQUIRE(stateTo->as<rive::AnimationState>()->animation() != nullptr);
+ REQUIRE(stateTo->as<rive::AnimationState>()->animation()->name() == "idle");
+
+ auto idleState = stateTo->as<rive::AnimationState>();
+ REQUIRE(idleState->transitionCount() == 2);
+ for (int i = 0; i < idleState->transitionCount(); i++)
+ {
+ auto transition = idleState->transition(i);
+ if (transition->stateTo()
+ ->as<rive::AnimationState>()
+ ->animation()
+ ->name() == "Roll_over")
+ {
+ // Check the condition
+ REQUIRE(transition->conditionCount() == 1);
+ }
+ }
+
+ rive::StateMachineInstance smi(artboard->stateMachine("Button"));
+ REQUIRE(smi.getBool("Hover")->name() == "Hover");
+ REQUIRE(smi.getBool("Press")->name() == "Press");
+ REQUIRE(smi.getBool("Hover") != nullptr);
+ REQUIRE(smi.getBool("Press") != nullptr);
+
+ delete file;
+ delete[] bytes;
+}
\ No newline at end of file