{
+ "disasexpl.associations": {
+ "**/*.c": "${workspaceFolder}/build/bin/assembly/${fileDirname}/${fileBasenameNoExtension}.S",
+ "**/*.cpp": "${workspaceFolder}/build/bin/assembly/${fileDirname}/${fileBasenameNoExtension}.S"
+ },
"files.associations": {
"type_traits": "cpp",
"string": "cpp",
# rive-cpp
-C++ runtime for Rive
+C++ runtime for [Rive](https://rive.app). Provides these runtime features:
+- Loading Artboards and their contents from **.riv** files.
+- Querying LinearAnimations and StateMachines from Artboards.
+- Making changes to Artboard hierarchy (fundamentally same guts used by LinearAnimations and StateMachines) and effienclty solving those changes via Artboard::advance.
+- Abstract Renderer for submitting high level vector path commands with retained path objects to optimize and minimize path re-computation (ultimately up to the concrete rendering implementation).
+- Example concrete renderer written in C++ with [Skia](https://skia.org/). Skia renderer code is in [skia/renderer/src/skia_renderer.cpp](skia/renderer/src/skia_renderer.cpp).
+
+## Build System
+We use [premake5](https://premake.github.io/). The Rive dev team primarily works on MacOS. There is some work done by the community to also support Windows and Linux. PRs welcomed for specific platforms you with to support! We encourage you to use premake as it's highly extensible and configurable for a variety of platforms.
## Build
In the ```rive``` directory, run ```build.sh``` to debug build and ```build.sh release``` for a release build.
## Testing
+Uses the [Catch2](https://github.com/catchorg/Catch2) testing framework.
+
+```
+cd dev
+./test.sh
+```
+
In the ```dev``` directory, run ```test.sh``` to compile and execute the tests.
-The tests live in ```rive/test```. To add new tests, create a new ```xxx_test.cpp``` file here. The test harness will automatically pick up the new file.
\ No newline at end of file
+The tests live in ```rive/test```. To add new tests, create a new ```xxx_test.cpp``` file here. The test harness will automatically pick up the new file.
+
+There's a VSCode command provided to ```run tests``` from the Tasks: Run Task command palette.
+
+## Memory Checks
+Note that if you're on MacOS you'll want to install valgrind, which is somewhat complicated these days. This is the easiest solution (please PR a better one when it becomes available).
+```
+brew tap LouisBrunner/valgrind
+brew install --HEAD LouisBrunner/valgrind/valgrind
+```
+You can now run the all the tests through valgrind by running ```test.sh memory```.
+
+## Disassembly Explorer
+If you want to examine the generated assembly code per cpp file, install [Disassembly Explorer](https://marketplace.visualstudio.com/items?itemName=dseight.disasexpl) in VSCode.
+
+A ```disassemble``` task is provided to compile and preview the generated assembly. You can reach it via the Tasks: Run Task command palette or you can bind it to a shortcut by editing your VSCode keybindings.json:
+```
+[
+ {
+ "key": "cmd+d",
+ "command": "workbench.action.tasks.runTask",
+ "args": "disassemble"
+ }
+]
+```
\ No newline at end of file
elif [ "$OPTION" = "clean" ]
then
echo Cleaning project ...
- premake5 gmake2 && make clean
+ premake5 gmake2 && make clean && make clean config=release
+
elif [ "$OPTION" = "release" ]
then
premake5 gmake2 && make config=release -j7
workspace "rive"
-configurations {"debug", "release"}
+ configurations {"debug", "release"}
project "rive"
-kind "StaticLib"
-language "C++"
-cppdialect "C++17"
-targetdir "bin/%{cfg.buildcfg}"
-objdir "obj/%{cfg.buildcfg}"
-includedirs {"../include"}
+ kind "StaticLib"
+ language "C++"
+ cppdialect "C++17"
+ targetdir "bin/%{cfg.buildcfg}"
+ objdir "obj/%{cfg.buildcfg}"
+ includedirs {"../include"}
-files {"../src/**.cpp"}
+ files {"../src/**.cpp"}
-buildoptions {"-Wall", "-fno-exceptions", "-fno-rtti"}
+ buildoptions {"-Wall", "-fno-exceptions", "-fno-rtti"}
-filter "configurations:debug"
-defines {"DEBUG"}
-symbols "On"
+ filter "system:windows"
+ defines {"_USE_MATH_DEFINES"}
-filter "configurations:release"
-defines {"RELEASE"}
-defines { "NDEBUG" }
-optimize "On"
+ filter "configurations:debug"
+ defines {"DEBUG"}
+ symbols "On"
+
+ filter "configurations:release"
+ defines {"RELEASE"}
+ defines {"NDEBUG"}
+ optimize "On"
-- Clean Function --
newaction {
},
"extends": "component.json",
"properties": {
- "index": {
- "type": "uint",
- "initialValue": "0",
- "key": {
- "int": 118,
- "string": "index"
- },
- "description": "The index used for weighting. Implicit at runtime. Required at edit time to allow undo ops to put them back in order.",
- "runtime": false
- },
"boneId": {
"type": "Id",
"typeRuntime": "uint",
},
"extends": "shapes/parametric_path.json",
"properties": {
- "cornerRadius": {
+ "linkCornerRadius": {
+ "type": "bool",
+ "initialValue": "true",
+ "key": {
+ "int": 164,
+ "string": "linkcornerradius"
+ },
+ "description": "Whether the TL corner radius defines all the radiuses"
+ },
+ "cornerRadiusTL": {
"type": "double",
"initialValue": "0",
+ "animates": true,
"key": {
"int": 31,
- "string": "cornerRadius"
+ "string": "cornerRadiusTL"
+ },
+ "description": "Top left radius of the corners of this rectangle"
+ },
+ "cornerRadiusTR": {
+ "type": "double",
+ "initialValue": "0",
+ "animates": true,
+ "key": {
+ "int": 161,
+ "string": "cornerRadiusTR"
+ },
+ "description": "Top right radius of the corners of this rectangle"
+ },
+ "cornerRadiusBL": {
+ "type": "double",
+ "initialValue": "0",
+ "animates": true,
+ "key": {
+ "int": 162,
+ "string": "cornerRadiusBL"
+ },
+ "description": "Bottom left radius of the corners of this rectangle"
+ },
+ "cornerRadiusBR": {
+ "type": "double",
+ "initialValue": "0",
+ "animates": true,
+ "key": {
+ "int": 163,
+ "string": "cornerRadiusBR"
},
- "description": "Radius of the corners of this rectangle"
+ "description": "Bottom right radius of the corners of this rectangle"
}
}
}
\ No newline at end of file
pushd test &>/dev/null
OPTION=$1
+UTILITY=
if [ "$OPTION" = "help" ]
then
echo Cleaning project ...
premake5 clean || exit 1
shift
+elif [ "$OPTION" = "memory" ]
+then
+ echo Will perform memory checks...
+ UTILITY='valgrind --leak-check=full'
+ shift
fi
premake5 gmake2 || exit 1
make -j7 || exit 1
for file in ./build/bin/debug/*; do
echo testing $file
- $file "$1"
+ $UTILITY $file "$1"
done
popd &>/dev/null
\ No newline at end of file
// to
float time() const { return m_Time; }
+ // Returns the direction that we are currently playing in
+ float direction() const { return m_Direction; }
+
+ // Update the direction of the animation instance, positive value for
+ // forwards Negative for backwards
+ void direction(int direction)
+ {
+ if (direction > 0)
+ {
+ m_Direction = 1;
+ }
+ else
+ {
+ m_Direction = -1;
+ }
+ }
+
// Sets the animation's point in time.
void time(float value);
class StateMachineNumber;
class StateMachineTrigger;
class TransitionTriggerCondition;
+ class StateMachineLayerInstance;
class SMIInput
{
friend class StateMachineInstance;
+ friend class StateMachineLayerInstance;
private:
StateMachineInstance* m_MachineInstance;
public:
virtual ~SMIInput() {}
const StateMachineInput* input() const { return m_Input; }
-
+
const std::string& name() const;
uint16_t inputCoreType() const;
};
#include <string>
#include <stddef.h>
+#include "animation/linear_animation_instance.hpp"
namespace rive
{
class StateMachine;
+ class LayerState;
class SMIInput;
class Artboard;
class SMIBool;
friend class SMIInput;
private:
- StateMachine* m_Machine;
+ const StateMachine* m_Machine;
bool m_NeedsAdvance = false;
size_t m_InputCount;
SMIInput** m_InputInstances;
- unsigned int m_LayerCount;
+ size_t m_LayerCount;
StateMachineLayerInstance* m_Layers;
void markNeedsAdvance();
public:
- StateMachineInstance(StateMachine* machine);
+ StateMachineInstance(const StateMachine* machine);
~StateMachineInstance();
// Advance the state machine by the specified time. Returns true if the
SMIBool* getBool(std::string name) const;
SMINumber* getNumber(std::string name) const;
SMITrigger* getTrigger(std::string name) const;
+
+ const size_t currentAnimationCount() const;
+ const LinearAnimationInstance* currentAnimationByIndex(size_t index) const;
+
+ // The number of state changes that occurred across all layers on the
+ // previous advance.
+ size_t stateChangedCount() const;
+
+ // Returns the state name for states that changed in layers on the
+ // previously called advance. If the index of out of range, it returns
+ // the empty string.
+ const LayerState* stateChangedByIndex(size_t index) const;
};
} // namespace rive
-#endif
\ No newline at end of file
+#endif
class ClippingShape;
class Artboard;
class DrawRules;
-
+
class Drawable : public DrawableBase
{
friend class Artboard;
{
return m_ClippingShapes;
}
+
+ const inline bool isHidden() const
+ {
+ // For now we have a single drawable flag, when we have more we can
+ // make an actual enum for this.
+ return (drawableFlags() & 0x1) == 0x1;
+ }
};
} // namespace rive
case ParametricPathBase::originYPropertyKey:
object->as<ParametricPathBase>()->originY(value);
break;
- case RectangleBase::cornerRadiusPropertyKey:
- object->as<RectangleBase>()->cornerRadius(value);
+ case RectangleBase::cornerRadiusTLPropertyKey:
+ object->as<RectangleBase>()->cornerRadiusTL(value);
+ break;
+ case RectangleBase::cornerRadiusTRPropertyKey:
+ object->as<RectangleBase>()->cornerRadiusTR(value);
+ break;
+ case RectangleBase::cornerRadiusBLPropertyKey:
+ object->as<RectangleBase>()->cornerRadiusBL(value);
+ break;
+ case RectangleBase::cornerRadiusBRPropertyKey:
+ object->as<RectangleBase>()->cornerRadiusBR(value);
break;
case CubicMirroredVertexBase::rotationPropertyKey:
object->as<CubicMirroredVertexBase>()->rotation(value);
case PointsPathBase::isClosedPropertyKey:
object->as<PointsPathBase>()->isClosed(value);
break;
+ case RectangleBase::linkCornerRadiusPropertyKey:
+ object->as<RectangleBase>()->linkCornerRadius(value);
+ break;
case ClippingShapeBase::isVisiblePropertyKey:
object->as<ClippingShapeBase>()->isVisible(value);
break;
return object->as<ParametricPathBase>()->originX();
case ParametricPathBase::originYPropertyKey:
return object->as<ParametricPathBase>()->originY();
- case RectangleBase::cornerRadiusPropertyKey:
- return object->as<RectangleBase>()->cornerRadius();
+ case RectangleBase::cornerRadiusTLPropertyKey:
+ return object->as<RectangleBase>()->cornerRadiusTL();
+ case RectangleBase::cornerRadiusTRPropertyKey:
+ return object->as<RectangleBase>()->cornerRadiusTR();
+ case RectangleBase::cornerRadiusBLPropertyKey:
+ return object->as<RectangleBase>()->cornerRadiusBL();
+ case RectangleBase::cornerRadiusBRPropertyKey:
+ return object->as<RectangleBase>()->cornerRadiusBR();
case CubicMirroredVertexBase::rotationPropertyKey:
return object->as<CubicMirroredVertexBase>()->rotation();
case CubicMirroredVertexBase::distancePropertyKey:
return object->as<StrokeBase>()->transformAffectsStroke();
case PointsPathBase::isClosedPropertyKey:
return object->as<PointsPathBase>()->isClosed();
+ case RectangleBase::linkCornerRadiusPropertyKey:
+ return object->as<RectangleBase>()->linkCornerRadius();
case ClippingShapeBase::isVisiblePropertyKey:
return object->as<ClippingShapeBase>()->isVisible();
}
case ParametricPathBase::heightPropertyKey:
case ParametricPathBase::originXPropertyKey:
case ParametricPathBase::originYPropertyKey:
- case RectangleBase::cornerRadiusPropertyKey:
+ case RectangleBase::cornerRadiusTLPropertyKey:
+ case RectangleBase::cornerRadiusTRPropertyKey:
+ case RectangleBase::cornerRadiusBLPropertyKey:
+ case RectangleBase::cornerRadiusBRPropertyKey:
case CubicMirroredVertexBase::rotationPropertyKey:
case CubicMirroredVertexBase::distancePropertyKey:
case PolygonBase::cornerRadiusPropertyKey:
case ShapePaintBase::isVisiblePropertyKey:
case StrokeBase::transformAffectsStrokePropertyKey:
case PointsPathBase::isClosedPropertyKey:
+ case RectangleBase::linkCornerRadiusPropertyKey:
case ClippingShapeBase::isVisiblePropertyKey:
return CoreBoolType::id;
default:
#ifndef _RIVE_RECTANGLE_BASE_HPP_
#define _RIVE_RECTANGLE_BASE_HPP_
+#include "core/field_types/core_bool_type.hpp"
#include "core/field_types/core_double_type.hpp"
#include "shapes/parametric_path.hpp"
namespace rive
uint16_t coreType() const override { return typeKey; }
- static const uint16_t cornerRadiusPropertyKey = 31;
+ static const uint16_t linkCornerRadiusPropertyKey = 164;
+ static const uint16_t cornerRadiusTLPropertyKey = 31;
+ static const uint16_t cornerRadiusTRPropertyKey = 161;
+ static const uint16_t cornerRadiusBLPropertyKey = 162;
+ static const uint16_t cornerRadiusBRPropertyKey = 163;
private:
- float m_CornerRadius = 0.0f;
+ bool m_LinkCornerRadius = true;
+ float m_CornerRadiusTL = 0.0f;
+ float m_CornerRadiusTR = 0.0f;
+ float m_CornerRadiusBL = 0.0f;
+ float m_CornerRadiusBR = 0.0f;
public:
- inline float cornerRadius() const { return m_CornerRadius; }
- void cornerRadius(float value)
+ inline bool linkCornerRadius() const { return m_LinkCornerRadius; }
+ void linkCornerRadius(bool value)
{
- if (m_CornerRadius == value)
+ if (m_LinkCornerRadius == value)
{
return;
}
- m_CornerRadius = value;
- cornerRadiusChanged();
+ m_LinkCornerRadius = value;
+ linkCornerRadiusChanged();
+ }
+
+ inline float cornerRadiusTL() const { return m_CornerRadiusTL; }
+ void cornerRadiusTL(float value)
+ {
+ if (m_CornerRadiusTL == value)
+ {
+ return;
+ }
+ m_CornerRadiusTL = value;
+ cornerRadiusTLChanged();
+ }
+
+ inline float cornerRadiusTR() const { return m_CornerRadiusTR; }
+ void cornerRadiusTR(float value)
+ {
+ if (m_CornerRadiusTR == value)
+ {
+ return;
+ }
+ m_CornerRadiusTR = value;
+ cornerRadiusTRChanged();
+ }
+
+ inline float cornerRadiusBL() const { return m_CornerRadiusBL; }
+ void cornerRadiusBL(float value)
+ {
+ if (m_CornerRadiusBL == value)
+ {
+ return;
+ }
+ m_CornerRadiusBL = value;
+ cornerRadiusBLChanged();
+ }
+
+ inline float cornerRadiusBR() const { return m_CornerRadiusBR; }
+ void cornerRadiusBR(float value)
+ {
+ if (m_CornerRadiusBR == value)
+ {
+ return;
+ }
+ m_CornerRadiusBR = value;
+ cornerRadiusBRChanged();
}
bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
{
switch (propertyKey)
{
- case cornerRadiusPropertyKey:
- m_CornerRadius = CoreDoubleType::deserialize(reader);
+ case linkCornerRadiusPropertyKey:
+ m_LinkCornerRadius = CoreBoolType::deserialize(reader);
+ return true;
+ case cornerRadiusTLPropertyKey:
+ m_CornerRadiusTL = CoreDoubleType::deserialize(reader);
+ return true;
+ case cornerRadiusTRPropertyKey:
+ m_CornerRadiusTR = CoreDoubleType::deserialize(reader);
+ return true;
+ case cornerRadiusBLPropertyKey:
+ m_CornerRadiusBL = CoreDoubleType::deserialize(reader);
+ return true;
+ case cornerRadiusBRPropertyKey:
+ m_CornerRadiusBR = CoreDoubleType::deserialize(reader);
return true;
}
return ParametricPath::deserialize(propertyKey, reader);
}
protected:
- virtual void cornerRadiusChanged() {}
+ virtual void linkCornerRadiusChanged() {}
+ virtual void cornerRadiusTLChanged() {}
+ virtual void cornerRadiusTRChanged() {}
+ virtual void cornerRadiusBLChanged() {}
+ virtual void cornerRadiusBRChanged() {}
};
} // namespace rive
{
class StateMachineLayer;
class LayerState;
- class ArtboardImporter;
+ class Artboard;
class StateMachineLayerImporter : public ImportStackObject
{
private:
StateMachineLayer* m_Layer;
- ArtboardImporter* m_ArtboardImporter;
+ const Artboard* m_Artboard;
public:
StateMachineLayerImporter(StateMachineLayer* layer,
- ArtboardImporter* artboardImporter);
+ const Artboard* artboard);
void addState(LayerState* state);
StatusCode resolve() override;
bool readNullObject() override;
{
private:
std::vector<GradientStop*> m_Stops;
- bool m_PaintsInWorldSpace;
Node* m_ShapePaintContainer = nullptr;
public:
}
void addStop(GradientStop* stop);
void update(ComponentDirt value) override;
- bool paintsInWorldSpace() const;
- void paintsInWorldSpace(bool value);
void markGradientDirty();
void markStopsDirty();
virtual PathSpace pathSpace() const = 0;
virtual void draw(Renderer* renderer, CommandPath* path) = 0;
+
+ /// Get the component that represents the ShapePaintMutator for this
+ /// ShapePaint. It'll be one of SolidColor, LinearGradient, or
+ /// RadialGradient.
+ Component* paint() const { return m_PaintMutator->component(); }
};
} // namespace rive
private:
float m_RenderOpacity = 1.0f;
RenderPaint* m_RenderPaint = nullptr;
+ /// The Component providing this ShapePaintMutator interface.
+ Component* m_Component = nullptr;
protected:
/// Hook up this paint mutator as the mutator for the shape paint
/// expected to be the parent.
- bool initPaintMutator(Component* parent);
+ bool initPaintMutator(Component* component);
virtual void renderOpacityChanged() = 0;
RenderPaint* renderPaint() const { return m_RenderPaint; }
public:
float renderOpacity() const { return m_RenderOpacity; }
void renderOpacity(float value);
+
+ Component* component() const { return m_Component; }
};
} // namespace rive
#endif
\ No newline at end of file
protected:
void widthChanged() override;
void heightChanged() override;
+ void originXChanged() override;
+ void originYChanged() override;
};
} // namespace rive
void update(ComponentDirt value) override;
protected:
- void cornerRadiusChanged() override;
+ void cornerRadiusTLChanged() override;
+ void cornerRadiusTRChanged() override;
+ void cornerRadiusBLChanged() override;
+ void cornerRadiusBRChanged() override;
};
} // namespace rive
sk_sp<SkImage> RiveFrameExtractor::getSnapshot()
{
+
+ // I see a canvas and I want to paint it black.
+ // (without this transparent background dont get cleared.)
+ SkPaint paint;
+ rasterCanvas->clear(SK_ColorBLACK);
+
// hmm "no deafault constructor exists bla bla... "
rive::SkiaRenderer renderer(rasterCanvas);
void SkiaRenderPath::reset() { m_Path.reset(); }
void SkiaRenderPath::addRenderPath(RenderPath* path, const Mat2D& transform)
{
- m_Path.addPath(
- reinterpret_cast<SkiaRenderPath*>(path)->m_Path,
- ToSkia::convert(transform));
+ m_Path.addPath(reinterpret_cast<SkiaRenderPath*>(path)->m_Path,
+ ToSkia::convert(transform));
}
void SkiaRenderPath::moveTo(float x, float y) { m_Path.moveTo(x, y); }
switch (animation.loop())
{
case Loop::oneShot:
- if (frames > end)
+ if (m_Direction == 1 && frames > end)
{
keepGoing = false;
m_SpilledTime = (frames - end) / fps;
m_Time = frames / fps;
didLoop = true;
}
+ else if (m_Direction == -1 && frames < start)
+ {
+ keepGoing = false;
+ m_SpilledTime = (start - frames) / fps;
+ frames = start;
+ m_Time = frames / fps;
+ didLoop = true;
+ }
break;
case Loop::loop:
- if (frames >= end)
+ if (m_Direction == 1 && frames >= end)
{
m_SpilledTime = (frames - end) / fps;
frames = m_Time * fps;
frames = start + std::fmod(frames - start, range);
+
+ m_Time = frames / fps;
+ didLoop = true;
+ }
+ else if (m_Direction == -1 && frames <= start)
+ {
+
+ m_SpilledTime = (start - frames) / fps;
+ frames = m_Time * fps;
+ frames = end - std::abs(std::fmod(start - frames, range));
m_Time = frames / fps;
didLoop = true;
}
m_TotalTime = value - start;
m_LastTotalTime = m_TotalTime - diff;
+ // leaving this RIGHT now. but is this required? it kinda messes up
+ // playing things backwards and seeking. what purpose does it solve?
m_Direction = 1;
}
#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;
LinearAnimationInstance* m_AnimationInstance = nullptr;
LinearAnimationInstance* m_AnimationInstanceFrom = nullptr;
float m_Mix = 1.0f;
+ bool m_stateChangedOnAdvance = false;
public:
void init(const StateMachineLayer* layer)
m_CurrentState = m_Layer->entryState();
}
- bool advance(float seconds, SMIInput** inputs)
+ bool advance(float seconds, SMIInput** inputs, size_t inputCount)
{
bool keepGoing = false;
+ m_stateChangedOnAdvance = false;
+
if (m_AnimationInstance != nullptr)
{
keepGoing = m_AnimationInstance->advance(seconds);
for (int i = 0; updateState(inputs); i++)
{
+ // Reset inputs between updates.
+ for (size_t i = 0; i < inputCount; i++)
+ {
+ inputs[i]->advanced();
+ }
+
if (i == maxIterations)
{
fprintf(stderr, "StateMachine exceeded max iterations.\n");
return false;
}
m_CurrentState = stateTo;
+ m_stateChangedOnAdvance = true;
return true;
}
artboard, m_AnimationInstance->time(), m_Mix);
}
}
+
+ bool stateChangedOnAdvance() const
+ {
+ return m_stateChangedOnAdvance;
+ }
+
+ const LayerState* currentState()
+ {
+ return m_CurrentState;
+ }
+
+ const LinearAnimationInstance* currentAnimation() const
+ {
+ return m_AnimationInstance;
+ }
};
} // namespace rive
-StateMachineInstance::StateMachineInstance(StateMachine* machine) :
+StateMachineInstance::StateMachineInstance(const StateMachine* machine) :
m_Machine(machine)
{
m_InputCount = machine->inputCount();
m_NeedsAdvance = false;
for (int i = 0; i < m_LayerCount; i++)
{
- if (m_Layers[i].advance(seconds, m_InputInstances))
+ if (m_Layers[i].advance(seconds, m_InputInstances, m_InputCount))
{
m_NeedsAdvance = true;
}
}
}
return nullptr;
+}
+
+size_t StateMachineInstance::stateChangedCount() const
+{
+ size_t count = 0;
+ for (int i = 0; i < m_LayerCount; i++)
+ {
+ if (m_Layers[i].stateChangedOnAdvance())
+ {
+ count++;
+ }
+ }
+ return count;
+}
+
+const LayerState* StateMachineInstance::stateChangedByIndex(size_t index) const
+{
+ size_t count = 0;
+ for (int i = 0; i < m_LayerCount; i++)
+ {
+ if (m_Layers[i].stateChangedOnAdvance())
+ {
+ if (count == index)
+ {
+ return m_Layers[i].currentState();
+ }
+ count++;
+ }
+ }
+ return nullptr;
+}
+
+const size_t StateMachineInstance::currentAnimationCount() const
+{
+ size_t count = 0;
+ for (int i = 0; i < m_LayerCount; i++)
+ {
+ if(m_Layers[i].currentAnimation() != nullptr)
+ {
+ count++;
+ }
+ }
+ return count;
+}
+
+const LinearAnimationInstance* StateMachineInstance::currentAnimationByIndex(size_t index) const
+{
+ size_t count = 0;
+ for (int i = 0; i < m_LayerCount; i++)
+ {
+ if (m_Layers[i].currentAnimation() != nullptr)
+ {
+ if (count == index)
+ {
+ return m_Layers[i].currentAnimation();
+ }
+ count++;
+ }
+ }
+ return nullptr;
}
\ No newline at end of file
for (auto drawable = m_FirstDrawable; drawable != nullptr;
drawable = drawable->prev)
{
+ if (drawable->isHidden())
+ {
+ continue;
+ }
drawable->draw(renderer);
}
#include "core/binary_reader.hpp"
#include "core/reader.h"
+#include <vector>
using namespace rive;
{
}
-bool BinaryReader::reachedEnd() const { return m_Position == m_End || didOverflow(); }
+bool BinaryReader::reachedEnd() const
+{
+ return m_Position == m_End || didOverflow();
+}
size_t BinaryReader::lengthInBytes() const { return m_Length; }
return std::string();
}
- char rawValue[length + 1];
+ std::vector<char> rawValue(length + 1);
auto readBytes = decode_string(length, m_Position, m_End, &rawValue[0]);
if (readBytes != length)
{
return std::string();
}
m_Position += readBytes;
- return std::string(rawValue);
+ return std::string(rawValue.data(), length);
}
double BinaryReader::readFloat64()
}
stackObject = new StateMachineLayerImporter(
- object->as<StateMachineLayer>(), artboardImporter);
+ object->as<StateMachineLayer>(),
+ artboardImporter->artboard());
break;
}
#include "artboard.hpp"
using namespace rive;
-StateMachineLayerImporter::StateMachineLayerImporter(
- StateMachineLayer* layer, ArtboardImporter* artboardImporter) :
- m_Layer(layer), m_ArtboardImporter(artboardImporter)
+StateMachineLayerImporter::StateMachineLayerImporter(StateMachineLayer* layer,
+ const Artboard* artboard) :
+ m_Layer(layer), m_Artboard(artboard)
{
}
void StateMachineLayerImporter::addState(LayerState* state)
StatusCode StateMachineLayerImporter::resolve()
{
- auto artboard = m_ArtboardImporter->artboard();
+
for (auto state : m_Layer->m_States)
{
if (state->is<AnimationState>())
if (animationState->animationId() != -1)
{
animationState->m_Animation =
- artboard->animation(animationState->animationId());
+ m_Artboard->animation(animationState->animationId());
if (animationState->m_Animation == nullptr)
{
return StatusCode::MissingObject;
m_RenderPath->fillRule((FillRule)fillRule());
for (auto shape : m_Shapes)
{
- m_RenderPath->addPath(shape->pathComposer()->worldPath(), identity);
+ if (!shape->isHidden())
+ {
+ m_RenderPath->addPath(shape->pathComposer()->worldPath(),
+ identity);
+ }
}
}
}
\ No newline at end of file
m_Points.emplace_back(Vec2D(x, y));
}
-void MetricsPath::close()
-{
- if (m_Parts.back().type == PathPart::line)
- {
- // We auto close the last part if it's a cubic, if it's not then make
- // sure to add the final part in so we can compute its length too.
- m_Parts.push_back(PathPart(0, m_Points.size()));
- m_Points.emplace_back(m_Points[0]);
- }
-}
+void MetricsPath::close() {}
static void computeHull(const Vec2D& from,
const Vec2D& fromOut,
// transform is changing (path may not have been reset but got added with
// another transform).
m_TransformedPoints.resize(m_Points.size());
- for (int i = 0, l = m_Points.size(); i < l; i++)
+ for (size_t i = 0, l = m_Points.size(); i < l; i++)
{
Vec2D::transform(m_TransformedPoints[i], m_Points[i], transform);
}
#include "shapes/paint/color.hpp"
#include "shapes/paint/gradient_stop.hpp"
#include "shapes/shape_paint_container.hpp"
+#include "shapes/paint/shape_paint.hpp"
#include <algorithm>
using namespace rive;
return code;
}
- if (!initPaintMutator(parent()))
+ if (!initPaintMutator(this))
{
return StatusCode::MissingObject;
}
return StatusCode::Ok;
}
-bool LinearGradient::paintsInWorldSpace() const { return m_PaintsInWorldSpace; }
-void LinearGradient::paintsInWorldSpace(bool value)
-{
- if (m_PaintsInWorldSpace == value)
- {
- return;
- }
- m_PaintsInWorldSpace = value;
- addDirt(ComponentDirt::Paint);
-}
-
void LinearGradient::buildDependencies()
{
auto p = parent();
bool worldTransformed = hasDirt(value, ComponentDirt::WorldTransform);
+ bool paintsInWorldSpace =
+ parent()->as<ShapePaint>()->pathSpace() == PathSpace::World;
// We rebuild the gradient if the gradient is dirty or we paint in world
// space and the world space transform has changed, or the local transform
// has changed. Local transform changes when a stop moves in local space.
hasDirt(value,
ComponentDirt::Paint | ComponentDirt::RenderOpacity |
ComponentDirt::Transform) ||
- (m_PaintsInWorldSpace && worldTransformed);
-
+ (paintsInWorldSpace && worldTransformed);
if (rebuildGradient)
{
auto paint = renderPaint();
// Check if we need to update the world space gradient (if there's no
// shape container, presumably it's the artboard and we're already in
// world).
- if (m_PaintsInWorldSpace && m_ShapePaintContainer != nullptr)
+ if (paintsInWorldSpace && m_ShapePaintContainer != nullptr)
{
// Get the start and end of the gradient in world coordinates (world
// transform of the shape).
using namespace rive;
-bool ShapePaintMutator::initPaintMutator(Component* parent)
+bool ShapePaintMutator::initPaintMutator(Component* component)
{
+ auto parent = component->parent();
+ m_Component = component;
if (parent->is<ShapePaint>())
{
- // Set this object as the mutator for the shape paint and get a
- // reference to the paint we'll be mutating.
+ // Set this object as the mutator for the shape paint and get a
+ // reference to the paint we'll be mutating.
m_RenderPaint = parent->as<ShapePaint>()->initRenderPaint(this);
return true;
}
{
return code;
}
- if (!initPaintMutator(parent()))
+ if (!initPaintMutator(this))
{
return StatusCode::MissingObject;
}
using namespace rive;
void ParametricPath::widthChanged() { markPathDirty(); }
-void ParametricPath::heightChanged() { markPathDirty(); }
\ No newline at end of file
+void ParametricPath::heightChanged() { markPathDirty(); }
+void ParametricPath::originXChanged() { markPathDirty(); }
+void ParametricPath::originYChanged() { markPathDirty(); }
\ No newline at end of file
{
commandPath.cubicTo(outX, outY, startInX, startInY, startX, startY);
}
+ else
+ {
+ commandPath.lineTo(startX, startY);
+ }
commandPath.close();
}
}
addVertex(&m_Vertex4);
}
-void Rectangle::cornerRadiusChanged() { markPathDirty(); }
+void Rectangle::cornerRadiusTLChanged() { markPathDirty(); }
+void Rectangle::cornerRadiusTRChanged() { markPathDirty(); }
+void Rectangle::cornerRadiusBLChanged() { markPathDirty(); }
+void Rectangle::cornerRadiusBRChanged() { markPathDirty(); }
void Rectangle::update(ComponentDirt value)
{
if (hasDirt(value, ComponentDirt::Path))
{
- auto radius = cornerRadius();
+ auto radius = cornerRadiusTL();
+ auto link = linkCornerRadius();
auto ox = -originX() * width();
auto oy = -originY() * height();
m_Vertex2.x(ox + width());
m_Vertex2.y(oy);
- m_Vertex2.radius(radius);
+ m_Vertex2.radius(link ? radius : cornerRadiusTR());
m_Vertex3.x(ox + width());
m_Vertex3.y(oy + height());
- m_Vertex3.radius(radius);
+ m_Vertex3.radius(link ? radius : cornerRadiusBR());
m_Vertex4.x(ox);
m_Vertex4.y(oy + height());
- m_Vertex4.radius(radius);
+ m_Vertex4.radius(link ? radius : cornerRadiusBL());
}
Super::update(value);
-// The onlu purpose of this file is to DEFINE the catch config so it can include main()
+// The only purpose of this file is to DEFINE the catch config so it can include
+// main()
-#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
+#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this
+ // in one cpp file
#include "catch.hpp"
\ No newline at end of file
rectangle->y(0.0f);
rectangle->width(100.0f);
rectangle->height(200.0f);
- rectangle->cornerRadius(0.0f);
+ rectangle->cornerRadiusTL(0.0f);
artboard->addObject(artboard);
artboard->addObject(shape);
auto path =
reinterpret_cast<rive::NoOpRenderPath*>(rectangle->commandPath());
- REQUIRE(path->commands.size() == 6);
+ REQUIRE(path->commands.size() == 7);
REQUIRE(path->commands[0].command == rive::NoOpPathCommandType::Reset);
REQUIRE(path->commands[1].command == rive::NoOpPathCommandType::MoveTo);
REQUIRE(path->commands[2].command == rive::NoOpPathCommandType::LineTo);
REQUIRE(path->commands[3].command == rive::NoOpPathCommandType::LineTo);
REQUIRE(path->commands[4].command == rive::NoOpPathCommandType::LineTo);
- REQUIRE(path->commands[5].command == rive::NoOpPathCommandType::Close);
+ REQUIRE(path->commands[5].command == rive::NoOpPathCommandType::LineTo);
+ REQUIRE(path->commands[6].command == rive::NoOpPathCommandType::Close);
delete artboard;
}
rectangle->y(0.0f);
rectangle->width(100.0f);
rectangle->height(200.0f);
- rectangle->cornerRadius(20.0f);
+ rectangle->cornerRadiusTL(20.0f);
+ rectangle->linkCornerRadius(true);
artboard->addObject(artboard);
artboard->addObject(shape);
// close
- REQUIRE(path->commands.size() == 10);
+ REQUIRE(path->commands.size() == 11);
// Init
REQUIRE(path->commands[0].command == rive::NoOpPathCommandType::Reset);
REQUIRE(path->commands[7].command == rive::NoOpPathCommandType::LineTo);
REQUIRE(path->commands[8].command == rive::NoOpPathCommandType::CubicTo);
- REQUIRE(path->commands[9].command == rive::NoOpPathCommandType::Close);
+ REQUIRE(path->commands[9].command == rive::NoOpPathCommandType::LineTo);
+
+ REQUIRE(path->commands[10].command == rive::NoOpPathCommandType::Close);
delete artboard;
}
REQUIRE(smi.getBool("Press")->name() == "Press");
REQUIRE(smi.getBool("Hover") != nullptr);
REQUIRE(smi.getBool("Press") != nullptr);
+ REQUIRE(smi.stateChangedCount() == 0);
+ REQUIRE(smi.currentAnimationCount() == 0);
delete file;
delete[] bytes;