feat(CommandQueue)File asset loader (#9799) 77172ea62b

* added file asset loader

* tests for asset loader

* made asset loader rcp

* addressed pr comments

feat(CommandQueue) Added request Ids associated with jobs (#9796) 5aedcdc61f
* added request ids that are associated with callbacks for the command queue

* modified tests to check for request id

feat: TextInput - refactor shapes for runtime text input (#9836) 455374455c
* chore: updating runtime defs

* chore: refactor TextStyle into TextStylePaint

* chore: missed hpp files

* chore: missed cpp files

* chore: fix clone of text_style_paint

* feat: more text input at runtime

* chore: cleanup

* chore: add typed child iterator

* chore: missed files

* chore: running runtime input in editor

* feat: completing integration with editor

* chore: missed file

* chore: fix builds with text disabled

* chore: use child for test

* tests: adding more tests for text input

* chore: missed test file

* chore: more coverage for child iterator

* chore: using clipping on text input

* chore: test text input selected text

* chore: dry up iterator

Co-authored-by: Jonathon Copeland <jcopela4@gmail.com>
Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
This commit is contained in:
blakdragan7
2025-05-29 19:46:50 +00:00
parent c25a0d0f83
commit 47e0734849
73 changed files with 1818 additions and 272 deletions

View File

@@ -1 +1 @@
c62bdb256e8befe78d86e41b001545a9948d36c9
77172ea62bd8aa498352078e8e6c91ea92f875bd

View File

@@ -0,0 +1,41 @@
{
"name": "TextInput",
"key": {
"int": 569,
"string": "textinput"
},
"extends": "drawable.json",
"properties": {
"text": {
"type": "String",
"initialValue": "''",
"key": {
"int": 817,
"string": "text"
},
"bindable": true
},
"selectionRadius": {
"type": "double",
"initialValue": "5.0",
"animates": true,
"key": {
"int": 818,
"string": "selectionradius"
},
"description": "Selection radius applied to the selection geometry",
"bindable": true
},
"fieldHeight": {
"type": "double",
"initialValue": "98.0",
"key": {
"int": 819,
"string": "fieldheight"
},
"description": "An edit time property used to track the height of the field in the inspector",
"runtime": false,
"coop": false
}
}
}

View File

@@ -0,0 +1,8 @@
{
"name": "TextInputCursor",
"key": {
"int": 571,
"string": "textinputcursor"
},
"extends": "text/text_input_drawable.json"
}

View File

@@ -0,0 +1,9 @@
{
"name": "TextInputDrawable",
"key": {
"int": 570,
"string": "textinputdrawable"
},
"abstract": true,
"extends": "drawable.json"
}

View File

@@ -0,0 +1,8 @@
{
"name": "TextInputSelectedText",
"key": {
"int": 575,
"string": "textinputselectedtext"
},
"extends": "text/text_input_drawable.json"
}

View File

@@ -0,0 +1,8 @@
{
"name": "TextInputSelection",
"key": {
"int": 574,
"string": "textinputselection"
},
"extends": "text/text_input_drawable.json"
}

View File

@@ -0,0 +1,8 @@
{
"name": "TextInputText",
"key": {
"int": 572,
"string": "textinputtext"
},
"extends": "text/text_input_drawable.json"
}

View File

@@ -1,7 +1,7 @@
{
"name": "TextStyle",
"key": {
"int": 137,
"int": 573,
"string": "textstyle"
},
"extends": "container_component.json",

View File

@@ -0,0 +1,8 @@
{
"name": "TextStylePaint",
"key": {
"int": 137,
"string": "textstylepaint"
},
"extends": "text/text_style.json"
}

View File

@@ -18,6 +18,7 @@
#include "rive/event.hpp"
#include "rive/audio/audio_engine.hpp"
#include "rive/math/raw_path.hpp"
#include "rive/typed_children.hpp"
#include <queue>
#include <unordered_set>
@@ -191,6 +192,12 @@ public:
#endif
const std::vector<Core*>& objects() const { return m_Objects; }
template <typename T> TypedChildren<T> objects()
{
return TypedChildren<T>(
Span<Core*>(m_Objects.data(), m_Objects.size()));
}
const std::vector<NestedArtboard*> nestedArtboards() const
{
return m_NestedArtboards;

View File

@@ -14,6 +14,7 @@
#include <string>
#include <vector>
#include <unordered_map>
#include "file_asset_loader.hpp"
// Macros for defining and working with command buffer handles.
#if INTPTR_MAX == INT64_MAX
@@ -42,6 +43,8 @@ RIVE_DEFINE_HANDLE(ArtboardHandle);
RIVE_DEFINE_HANDLE(StateMachineHandle);
RIVE_DEFINE_HANDLE(DrawKey);
using RequestId = uint64_t;
// Function poimter that gets called back from the server thread.
using CommandServerCallback = std::function<void(CommandServer*)>;
using CommandServerDrawCallback = std::function<void(DrawKey, CommandServer*)>;
@@ -90,9 +93,10 @@ public:
: public CommandQueue::ListenerBase<FileListener, FileHandle>
{
public:
virtual void onFileDeleted(const FileHandle) {}
virtual void onFileDeleted(const FileHandle, RequestId) {}
virtual void onArtboardsListed(const FileHandle,
RequestId,
std::vector<std::string> artboardNames)
{}
};
@@ -101,10 +105,11 @@ public:
: public CommandQueue::ListenerBase<ArtboardListener, ArtboardHandle>
{
public:
virtual void onArtboardDeleted(const ArtboardHandle) {}
virtual void onArtboardDeleted(const ArtboardHandle, RequestId) {}
virtual void onStateMachinesListed(
const ArtboardHandle,
RequestId,
std::vector<std::string> artboardNames)
{}
};
@@ -114,42 +119,59 @@ public:
StateMachineHandle>
{
public:
virtual void onStateMachineDeleted(const StateMachineHandle) {}
virtual void onStateMachineDeleted(const StateMachineHandle, RequestId)
{}
};
CommandQueue();
~CommandQueue();
FileHandle loadFile(std::vector<uint8_t> rivBytes,
FileListener* listener = nullptr);
rcp<FileAssetLoader>,
FileListener* listener = nullptr,
RequestId* outId = nullptr);
void deleteFile(FileHandle);
FileHandle loadFile(std::vector<uint8_t> rivBytes,
FileListener* listener = nullptr,
RequestId* outId = nullptr)
{
return loadFile(std::move(rivBytes), nullptr, listener, outId);
}
RequestId deleteFile(FileHandle);
ArtboardHandle instantiateArtboardNamed(
FileHandle,
std::string name,
ArtboardListener* listener = nullptr);
ArtboardListener* listener = nullptr,
RequestId* outId = nullptr);
ArtboardHandle instantiateDefaultArtboard(
FileHandle fileHandle,
ArtboardListener* listener = nullptr)
ArtboardListener* listener = nullptr,
RequestId* outId = nullptr)
{
return instantiateArtboardNamed(fileHandle, "", listener);
return instantiateArtboardNamed(fileHandle, "", listener, outId);
}
void deleteArtboard(ArtboardHandle);
RequestId deleteArtboard(ArtboardHandle);
StateMachineHandle instantiateStateMachineNamed(
ArtboardHandle,
std::string name,
StateMachineListener* listener = nullptr);
StateMachineListener* listener = nullptr,
RequestId* outId = nullptr);
StateMachineHandle instantiateDefaultStateMachine(
ArtboardHandle artboardHandle,
StateMachineListener* listener = nullptr)
StateMachineListener* listener = nullptr,
RequestId* outId = nullptr)
{
return instantiateStateMachineNamed(artboardHandle, "", listener);
return instantiateStateMachineNamed(artboardHandle,
"",
listener,
outId);
}
void deleteStateMachine(StateMachineHandle);
RequestId deleteStateMachine(StateMachineHandle);
// Create unique draw key for draw.
DrawKey createDrawKey();
@@ -177,8 +199,8 @@ public:
void disconnect();
void requestArtboardNames(FileHandle);
void requestStateMachineNames(ArtboardHandle);
RequestId requestArtboardNames(FileHandle);
RequestId requestStateMachineNames(ArtboardHandle);
// Consume all messages received from the server.
void processMessages();
@@ -263,6 +285,7 @@ private:
uint64_t m_currentFileHandleIdx = 0;
uint64_t m_currentArtboardHandleIdx = 0;
uint64_t m_currentStateMachineHandleIdx = 0;
uint64_t m_currentRequestIdIdx = 0;
uint64_t m_currentDrawKeyIdx = 0;
std::mutex m_commandMutex;

View File

@@ -1,14 +1,22 @@
#ifndef _RIVE_CONTAINER_COMPONENT_HPP_
#define _RIVE_CONTAINER_COMPONENT_HPP_
#include "rive/generated/container_component_base.hpp"
#include "rive/typed_children.hpp"
#include <vector>
#include <functional>
namespace rive
{
class ContainerComponent : public ContainerComponentBase
{
public:
template <typename T> TypedChildren<T> children()
{
return TypedChildren<T>(
Span<Core*>((Core**)m_children.data(), m_children.size()));
}
const std::vector<Component*>& children() const { return m_children; }
virtual void addChild(Component* component);
bool collapse(bool value) override;
@@ -17,7 +25,8 @@ public:
bool forAll(std::function<bool(Component*)> predicate);
// Recursively descend onto all the children in the hierarchy tree.
// If predicate returns false, it won't recurse down a particular branch.
// If predicate returns false, it won't recurse down a particular
// branch.
void forEachChild(std::function<bool(Component*)> predicate);
private:

View File

@@ -51,7 +51,7 @@ public:
/// Minor version number supported by the runtime.
static const int minorVersion = 0;
File(Factory*, FileAssetLoader*);
File(Factory*, rcp<FileAssetLoader>);
public:
~File();
@@ -64,9 +64,17 @@ public:
/// cannot be found in-band.
/// @returns a pointer to the file, or null on failure.
static std::unique_ptr<File> import(Span<const uint8_t> data,
Factory*,
Factory* factory,
ImportResult* result = nullptr,
FileAssetLoader* assetLoader = nullptr);
FileAssetLoader* assetLoader = nullptr)
{
return import(data, factory, result, ref_rcp(assetLoader));
}
static std::unique_ptr<File> import(Span<const uint8_t> data,
Factory*,
ImportResult* result,
rcp<FileAssetLoader> assetLoader);
/// @returns the file's backboard. All files have exactly one backboard.
Backboard* backboard() const { return m_backboard; }
@@ -156,6 +164,13 @@ public:
ImportResult* result = nullptr);
#endif
#ifdef TESTING
FileAssetLoader* testing_getAssetLoader() const
{
return m_assetLoader.get();
}
#endif
private:
ImportResult read(BinaryReader&, const RuntimeHeader&);
@@ -191,7 +206,7 @@ private:
/// The helper used to load assets when they're not provided in-band
/// with the file.
FileAssetLoader* m_assetLoader;
rcp<FileAssetLoader> m_assetLoader;
rcp<ViewModelInstance> copyViewModelInstance(
ViewModelInstance* viewModelInstance,

View File

@@ -4,12 +4,13 @@
#include <cstdint>
#include <vector>
#include "rive/span.hpp"
#include "rive/refcnt.hpp"
namespace rive
{
class Factory;
class FileAsset;
class FileAssetLoader
class FileAssetLoader : public RefCnt<FileAssetLoader>
{
public:
virtual ~FileAssetLoader() {}

View File

@@ -219,6 +219,12 @@
#include "rive/solo.hpp"
#include "rive/text/text.hpp"
#include "rive/text/text_follow_path_modifier.hpp"
#include "rive/text/text_input.hpp"
#include "rive/text/text_input_cursor.hpp"
#include "rive/text/text_input_drawable.hpp"
#include "rive/text/text_input_selected_text.hpp"
#include "rive/text/text_input_selection.hpp"
#include "rive/text/text_input_text.hpp"
#include "rive/text/text_modifier.hpp"
#include "rive/text/text_modifier_group.hpp"
#include "rive/text/text_modifier_range.hpp"
@@ -226,6 +232,7 @@
#include "rive/text/text_style.hpp"
#include "rive/text/text_style_axis.hpp"
#include "rive/text/text_style_feature.hpp"
#include "rive/text/text_style_paint.hpp"
#include "rive/text/text_target_modifier.hpp"
#include "rive/text/text_value_run.hpp"
#include "rive/text/text_variation_modifier.hpp"
@@ -312,12 +319,18 @@ public:
return new DataEnumSystem();
case ViewModelPropertyViewModelBase::typeKey:
return new ViewModelPropertyViewModel();
case ViewModelInstanceBase::typeKey:
return new ViewModelInstance();
case ViewModelPropertyBooleanBase::typeKey:
return new ViewModelPropertyBoolean();
case DataEnumValueBase::typeKey:
return new DataEnumValue();
case ViewModelPropertyTriggerBase::typeKey:
return new ViewModelPropertyTrigger();
case ViewModelPropertyStringBase::typeKey:
return new ViewModelPropertyString();
case ViewModelPropertyColorBase::typeKey:
return new ViewModelPropertyColor();
case ViewModelPropertyBooleanBase::typeKey:
return new ViewModelPropertyBoolean();
case ViewModelInstanceBase::typeKey:
return new ViewModelInstance();
case ViewModelPropertyAssetImageBase::typeKey:
return new ViewModelPropertyAssetImage();
case ViewModelInstanceBooleanBase::typeKey:
@@ -330,18 +343,12 @@ public:
return new ViewModelInstanceTrigger();
case ViewModelInstanceSymbolListIndexBase::typeKey:
return new ViewModelInstanceSymbolListIndex();
case ViewModelPropertyStringBase::typeKey:
return new ViewModelPropertyString();
case ViewModelInstanceViewModelBase::typeKey:
return new ViewModelInstanceViewModel();
case ViewModelPropertyTriggerBase::typeKey:
return new ViewModelPropertyTrigger();
case ViewModelInstanceAssetBase::typeKey:
return new ViewModelInstanceAsset();
case ViewModelInstanceAssetImageBase::typeKey:
return new ViewModelInstanceAssetImage();
case DataEnumValueBase::typeKey:
return new DataEnumValue();
case DrawTargetBase::typeKey:
return new DrawTarget();
case CustomPropertyNumberBase::typeKey:
@@ -670,6 +677,10 @@ public:
return new TextModifierRange();
case TextFollowPathModifierBase::typeKey:
return new TextFollowPathModifier();
case TextInputCursorBase::typeKey:
return new TextInputCursor();
case TextInputTextBase::typeKey:
return new TextInputText();
case TextStyleFeatureBase::typeKey:
return new TextStyleFeature();
case TextVariationModifierBase::typeKey:
@@ -678,8 +689,16 @@ public:
return new TextModifierGroup();
case TextStyleBase::typeKey:
return new TextStyle();
case TextStylePaintBase::typeKey:
return new TextStylePaint();
case TextInputSelectedTextBase::typeKey:
return new TextInputSelectedText();
case TextInputBase::typeKey:
return new TextInput();
case TextStyleAxisBase::typeKey:
return new TextStyleAxis();
case TextInputSelectionBase::typeKey:
return new TextInputSelection();
case TextBase::typeKey:
return new Text();
case TextValueRunBase::typeKey:
@@ -1383,15 +1402,15 @@ public:
case ViewModelInstanceStringBase::propertyValuePropertyKey:
object->as<ViewModelInstanceStringBase>()->propertyValue(value);
break;
case ComponentBase::namePropertyKey:
object->as<ComponentBase>()->name(value);
break;
case DataEnumValueBase::keyPropertyKey:
object->as<DataEnumValueBase>()->key(value);
break;
case DataEnumValueBase::valuePropertyKey:
object->as<DataEnumValueBase>()->value(value);
break;
case ComponentBase::namePropertyKey:
object->as<ComponentBase>()->name(value);
break;
case AnimationBase::namePropertyKey:
object->as<AnimationBase>()->name(value);
break;
@@ -1419,6 +1438,9 @@ public:
case BindablePropertyStringBase::propertyValuePropertyKey:
object->as<BindablePropertyStringBase>()->propertyValue(value);
break;
case TextInputBase::textPropertyKey:
object->as<TextInputBase>()->text(value);
break;
case TextValueRunBase::textPropertyKey:
object->as<TextValueRunBase>()->text(value);
break;
@@ -2169,6 +2191,9 @@ public:
case TextStyleBase::letterSpacingPropertyKey:
object->as<TextStyleBase>()->letterSpacing(value);
break;
case TextInputBase::selectionRadiusPropertyKey:
object->as<TextInputBase>()->selectionRadius(value);
break;
case TextStyleAxisBase::axisValuePropertyKey:
object->as<TextStyleAxisBase>()->axisValue(value);
break;
@@ -2705,12 +2730,12 @@ public:
case ViewModelInstanceStringBase::propertyValuePropertyKey:
return object->as<ViewModelInstanceStringBase>()
->propertyValue();
case ComponentBase::namePropertyKey:
return object->as<ComponentBase>()->name();
case DataEnumValueBase::keyPropertyKey:
return object->as<DataEnumValueBase>()->key();
case DataEnumValueBase::valuePropertyKey:
return object->as<DataEnumValueBase>()->value();
case ComponentBase::namePropertyKey:
return object->as<ComponentBase>()->name();
case AnimationBase::namePropertyKey:
return object->as<AnimationBase>()->name();
case StateMachineComponentBase::namePropertyKey:
@@ -2731,6 +2756,8 @@ public:
case BindablePropertyStringBase::propertyValuePropertyKey:
return object->as<BindablePropertyStringBase>()
->propertyValue();
case TextInputBase::textPropertyKey:
return object->as<TextInputBase>()->text();
case TextValueRunBase::textPropertyKey:
return object->as<TextValueRunBase>()->text();
case CustomPropertyStringBase::propertyValuePropertyKey:
@@ -3250,6 +3277,8 @@ public:
return object->as<TextStyleBase>()->lineHeight();
case TextStyleBase::letterSpacingPropertyKey:
return object->as<TextStyleBase>()->letterSpacing();
case TextInputBase::selectionRadiusPropertyKey:
return object->as<TextInputBase>()->selectionRadius();
case TextStyleAxisBase::axisValuePropertyKey:
return object->as<TextStyleAxisBase>()->axisValue();
case TextBase::widthPropertyKey:
@@ -3482,9 +3511,9 @@ public:
case ViewModelComponentBase::namePropertyKey:
case DataEnumCustomBase::namePropertyKey:
case ViewModelInstanceStringBase::propertyValuePropertyKey:
case ComponentBase::namePropertyKey:
case DataEnumValueBase::keyPropertyKey:
case DataEnumValueBase::valuePropertyKey:
case ComponentBase::namePropertyKey:
case AnimationBase::namePropertyKey:
case StateMachineComponentBase::namePropertyKey:
case KeyFrameStringBase::valuePropertyKey:
@@ -3494,6 +3523,7 @@ public:
case DataConverterStringPadBase::textPropertyKey:
case DataConverterToStringBase::colorFormatPropertyKey:
case BindablePropertyStringBase::propertyValuePropertyKey:
case TextInputBase::textPropertyKey:
case TextValueRunBase::textPropertyKey:
case CustomPropertyStringBase::propertyValuePropertyKey:
case AssetBase::namePropertyKey:
@@ -3740,6 +3770,7 @@ public:
case TextStyleBase::fontSizePropertyKey:
case TextStyleBase::lineHeightPropertyKey:
case TextStyleBase::letterSpacingPropertyKey:
case TextInputBase::selectionRadiusPropertyKey:
case TextStyleAxisBase::axisValuePropertyKey:
case TextBase::widthPropertyKey:
case TextBase::heightPropertyKey:
@@ -4184,12 +4215,12 @@ public:
return object->is<DataEnumCustomBase>();
case ViewModelInstanceStringBase::propertyValuePropertyKey:
return object->is<ViewModelInstanceStringBase>();
case ComponentBase::namePropertyKey:
return object->is<ComponentBase>();
case DataEnumValueBase::keyPropertyKey:
return object->is<DataEnumValueBase>();
case DataEnumValueBase::valuePropertyKey:
return object->is<DataEnumValueBase>();
case ComponentBase::namePropertyKey:
return object->is<ComponentBase>();
case AnimationBase::namePropertyKey:
return object->is<AnimationBase>();
case StateMachineComponentBase::namePropertyKey:
@@ -4208,6 +4239,8 @@ public:
return object->is<DataConverterToStringBase>();
case BindablePropertyStringBase::propertyValuePropertyKey:
return object->is<BindablePropertyStringBase>();
case TextInputBase::textPropertyKey:
return object->is<TextInputBase>();
case TextValueRunBase::textPropertyKey:
return object->is<TextValueRunBase>();
case CustomPropertyStringBase::propertyValuePropertyKey:
@@ -4694,6 +4727,8 @@ public:
return object->is<TextStyleBase>();
case TextStyleBase::letterSpacingPropertyKey:
return object->is<TextStyleBase>();
case TextInputBase::selectionRadiusPropertyKey:
return object->is<TextInputBase>();
case TextStyleAxisBase::axisValuePropertyKey:
return object->is<TextStyleAxisBase>();
case TextBase::widthPropertyKey:

View File

@@ -0,0 +1,96 @@
#ifndef _RIVE_TEXT_INPUT_BASE_HPP_
#define _RIVE_TEXT_INPUT_BASE_HPP_
#include <string>
#include "rive/core/field_types/core_double_type.hpp"
#include "rive/core/field_types/core_string_type.hpp"
#include "rive/drawable.hpp"
namespace rive
{
class TextInputBase : public Drawable
{
protected:
typedef Drawable Super;
public:
static const uint16_t typeKey = 569;
/// Helper to quickly determine if a core object extends another without
/// RTTI at runtime.
bool isTypeOf(uint16_t typeKey) const override
{
switch (typeKey)
{
case TextInputBase::typeKey:
case DrawableBase::typeKey:
case NodeBase::typeKey:
case TransformComponentBase::typeKey:
case WorldTransformComponentBase::typeKey:
case ContainerComponentBase::typeKey:
case ComponentBase::typeKey:
return true;
default:
return false;
}
}
uint16_t coreType() const override { return typeKey; }
static const uint16_t textPropertyKey = 817;
static const uint16_t selectionRadiusPropertyKey = 818;
protected:
std::string m_Text = "";
float m_SelectionRadius = 5.0f;
public:
inline const std::string& text() const { return m_Text; }
void text(std::string value)
{
if (m_Text == value)
{
return;
}
m_Text = value;
textChanged();
}
inline float selectionRadius() const { return m_SelectionRadius; }
void selectionRadius(float value)
{
if (m_SelectionRadius == value)
{
return;
}
m_SelectionRadius = value;
selectionRadiusChanged();
}
Core* clone() const override;
void copy(const TextInputBase& object)
{
m_Text = object.m_Text;
m_SelectionRadius = object.m_SelectionRadius;
Drawable::copy(object);
}
bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
{
switch (propertyKey)
{
case textPropertyKey:
m_Text = CoreStringType::deserialize(reader);
return true;
case selectionRadiusPropertyKey:
m_SelectionRadius = CoreDoubleType::deserialize(reader);
return true;
}
return Drawable::deserialize(propertyKey, reader);
}
protected:
virtual void textChanged() {}
virtual void selectionRadiusChanged() {}
};
} // namespace rive
#endif

View File

@@ -0,0 +1,42 @@
#ifndef _RIVE_TEXT_INPUT_CURSOR_BASE_HPP_
#define _RIVE_TEXT_INPUT_CURSOR_BASE_HPP_
#include "rive/text/text_input_drawable.hpp"
namespace rive
{
class TextInputCursorBase : public TextInputDrawable
{
protected:
typedef TextInputDrawable Super;
public:
static const uint16_t typeKey = 571;
/// Helper to quickly determine if a core object extends another without
/// RTTI at runtime.
bool isTypeOf(uint16_t typeKey) const override
{
switch (typeKey)
{
case TextInputCursorBase::typeKey:
case TextInputDrawableBase::typeKey:
case DrawableBase::typeKey:
case NodeBase::typeKey:
case TransformComponentBase::typeKey:
case WorldTransformComponentBase::typeKey:
case ContainerComponentBase::typeKey:
case ComponentBase::typeKey:
return true;
default:
return false;
}
}
uint16_t coreType() const override { return typeKey; }
Core* clone() const override;
protected:
};
} // namespace rive
#endif

View File

@@ -0,0 +1,39 @@
#ifndef _RIVE_TEXT_INPUT_DRAWABLE_BASE_HPP_
#define _RIVE_TEXT_INPUT_DRAWABLE_BASE_HPP_
#include "rive/drawable.hpp"
namespace rive
{
class TextInputDrawableBase : public Drawable
{
protected:
typedef Drawable Super;
public:
static const uint16_t typeKey = 570;
/// Helper to quickly determine if a core object extends another without
/// RTTI at runtime.
bool isTypeOf(uint16_t typeKey) const override
{
switch (typeKey)
{
case TextInputDrawableBase::typeKey:
case DrawableBase::typeKey:
case NodeBase::typeKey:
case TransformComponentBase::typeKey:
case WorldTransformComponentBase::typeKey:
case ContainerComponentBase::typeKey:
case ComponentBase::typeKey:
return true;
default:
return false;
}
}
uint16_t coreType() const override { return typeKey; }
protected:
};
} // namespace rive
#endif

View File

@@ -0,0 +1,42 @@
#ifndef _RIVE_TEXT_INPUT_SELECTED_TEXT_BASE_HPP_
#define _RIVE_TEXT_INPUT_SELECTED_TEXT_BASE_HPP_
#include "rive/text/text_input_drawable.hpp"
namespace rive
{
class TextInputSelectedTextBase : public TextInputDrawable
{
protected:
typedef TextInputDrawable Super;
public:
static const uint16_t typeKey = 575;
/// Helper to quickly determine if a core object extends another without
/// RTTI at runtime.
bool isTypeOf(uint16_t typeKey) const override
{
switch (typeKey)
{
case TextInputSelectedTextBase::typeKey:
case TextInputDrawableBase::typeKey:
case DrawableBase::typeKey:
case NodeBase::typeKey:
case TransformComponentBase::typeKey:
case WorldTransformComponentBase::typeKey:
case ContainerComponentBase::typeKey:
case ComponentBase::typeKey:
return true;
default:
return false;
}
}
uint16_t coreType() const override { return typeKey; }
Core* clone() const override;
protected:
};
} // namespace rive
#endif

View File

@@ -0,0 +1,42 @@
#ifndef _RIVE_TEXT_INPUT_SELECTION_BASE_HPP_
#define _RIVE_TEXT_INPUT_SELECTION_BASE_HPP_
#include "rive/text/text_input_drawable.hpp"
namespace rive
{
class TextInputSelectionBase : public TextInputDrawable
{
protected:
typedef TextInputDrawable Super;
public:
static const uint16_t typeKey = 574;
/// Helper to quickly determine if a core object extends another without
/// RTTI at runtime.
bool isTypeOf(uint16_t typeKey) const override
{
switch (typeKey)
{
case TextInputSelectionBase::typeKey:
case TextInputDrawableBase::typeKey:
case DrawableBase::typeKey:
case NodeBase::typeKey:
case TransformComponentBase::typeKey:
case WorldTransformComponentBase::typeKey:
case ContainerComponentBase::typeKey:
case ComponentBase::typeKey:
return true;
default:
return false;
}
}
uint16_t coreType() const override { return typeKey; }
Core* clone() const override;
protected:
};
} // namespace rive
#endif

View File

@@ -0,0 +1,42 @@
#ifndef _RIVE_TEXT_INPUT_TEXT_BASE_HPP_
#define _RIVE_TEXT_INPUT_TEXT_BASE_HPP_
#include "rive/text/text_input_drawable.hpp"
namespace rive
{
class TextInputTextBase : public TextInputDrawable
{
protected:
typedef TextInputDrawable Super;
public:
static const uint16_t typeKey = 572;
/// Helper to quickly determine if a core object extends another without
/// RTTI at runtime.
bool isTypeOf(uint16_t typeKey) const override
{
switch (typeKey)
{
case TextInputTextBase::typeKey:
case TextInputDrawableBase::typeKey:
case DrawableBase::typeKey:
case NodeBase::typeKey:
case TransformComponentBase::typeKey:
case WorldTransformComponentBase::typeKey:
case ContainerComponentBase::typeKey:
case ComponentBase::typeKey:
return true;
default:
return false;
}
}
uint16_t coreType() const override { return typeKey; }
Core* clone() const override;
protected:
};
} // namespace rive
#endif

View File

@@ -11,7 +11,7 @@ protected:
typedef ContainerComponent Super;
public:
static const uint16_t typeKey = 137;
static const uint16_t typeKey = 573;
/// Helper to quickly determine if a core object extends another without
/// RTTI at runtime.

View File

@@ -0,0 +1,40 @@
#ifndef _RIVE_TEXT_STYLE_PAINT_BASE_HPP_
#define _RIVE_TEXT_STYLE_PAINT_BASE_HPP_
#include "rive/text/text_style.hpp"
namespace rive
{
class TextStylePaintBase : public TextStyle
{
protected:
typedef TextStyle Super;
public:
static const uint16_t typeKey = 137;
/// Helper to quickly determine if a core object extends another without
/// RTTI at runtime.
bool isTypeOf(uint16_t typeKey) const override
{
switch (typeKey)
{
case TextStylePaintBase::typeKey:
case TextStyleBase::typeKey:
case ContainerComponentBase::typeKey:
case ComponentBase::typeKey:
return true;
default:
return false;
}
}
uint16_t coreType() const override { return typeKey; }
Core* clone() const override;
protected:
};
} // namespace rive
#endif

View File

@@ -1,6 +1,7 @@
#ifndef _RIVE_FILE_ASSET_IMPORTER_HPP_
#define _RIVE_FILE_ASSET_IMPORTER_HPP_
#include "rive/refcnt.hpp"
#include "rive/importers/import_stack.hpp"
#include <unordered_map>
#include <vector>
@@ -16,13 +17,13 @@ class FileAssetImporter : public ImportStackObject
{
private:
FileAsset* m_FileAsset;
FileAssetLoader* m_FileAssetLoader;
rcp<FileAssetLoader> m_FileAssetLoader;
Factory* m_Factory;
// we will delete this when we go out of scope
std::unique_ptr<FileAssetContents> m_Content;
public:
FileAssetImporter(FileAsset*, FileAssetLoader*, Factory*);
FileAssetImporter(FileAsset*, rcp<FileAssetLoader>, Factory*);
void onFileAssetContents(std::unique_ptr<FileAssetContents> contents);
StatusCode resolve() override;
};

View File

@@ -45,6 +45,10 @@ protected:
void computedWorldYChanged() override {}
void computedWidthChanged() override {}
void computedHeightChanged() override {}
#ifdef WITH_RIVE_LAYOUT
void markLayoutNodeDirty();
#endif
};
} // namespace rive

View File

@@ -4,6 +4,8 @@
#pragma once
#include "rive/refcnt.hpp"
#include <cassert>
#include <deque>
@@ -63,6 +65,19 @@ public:
return *this;
}
template <typename T> PODStream& operator<<(rcp<T> obj)
{
return *this << obj.release();
}
template <typename T> PODStream& operator>>(rcp<T>& obj)
{
T* raw;
*this >> raw;
obj = rcp<T>(raw);
return *this;
}
private:
std::deque<char> m_byteStream;
};

View File

@@ -80,7 +80,7 @@ public:
/// Returns the bounds of the text object (helpful for aligning multiple
/// text objects/procredurally drawn shapes).
AABB bounds();
AABB bounds() const;
CursorVisualPosition cursorVisualPosition(CursorPosition position)
{
return position.visualPosition(m_shape);

View File

@@ -6,9 +6,12 @@
#include "rive/text_engine.hpp"
#include "rive/shapes/shape_paint_path.hpp"
#include "rive/simple_array.hpp"
#include "rive/text/glyph_lookup.hpp"
#include "rive/text/text_interface.hpp"
#include <unordered_map>
#include <vector>
#include "rive/text/glyph_lookup.hpp"
namespace rive
{
@@ -53,17 +56,21 @@ enum class LineIter : uint8_t
yOutOfBounds
};
class TextStyle;
class Text : public TextBase
class TextStylePaint;
class Text : public TextBase, public TextInterface
{
public:
// Implements TextInterface
void markShapeDirty() override;
void markPaintDirty() override;
void draw(Renderer* renderer) override;
Core* hitTest(HitInfo*, const Mat2D&) override;
void addRun(TextValueRun* run);
void addModifierGroup(TextModifierGroup* group);
void markShapeDirty(bool sendToLayout = true);
void markShapeDirty(bool sendToLayout);
void modifierShapeDirty();
void markPaintDirty();
void update(ComponentDirt value) override;
void onDirty(ComponentDirt value) override;
Mat2D m_transform;
@@ -82,7 +89,7 @@ public:
TextAlign align() const;
void overflow(TextOverflow value) { return overflowValue((uint32_t)value); }
void buildRenderStyles();
const TextStyle* styleFromShaperId(uint16_t id) const;
const TextStylePaint* styleFromShaperId(uint16_t id) const;
bool modifierRangesNeedShape() const;
AABB localBounds() const override;
void originXChanged() override;
@@ -115,10 +122,6 @@ public:
TextWrap wrap);
#endif
#ifdef WITH_RIVE_LAYOUT
void markLayoutNodeDirty();
#endif
bool haveModifiers() const
{
#ifdef WITH_RIVE_TEXT
@@ -157,7 +160,7 @@ private:
#ifdef WITH_RIVE_TEXT
void updateOriginWorldTransform();
std::vector<TextValueRun*> m_runs;
std::vector<TextStyle*> m_renderStyles;
std::vector<TextStylePaint*> m_renderStyles;
SimpleArray<Paragraph> m_shape;
SimpleArray<Paragraph> m_modifierShape;
SimpleArray<SimpleArray<GlyphLine>> m_lines;

View File

@@ -0,0 +1,50 @@
#ifndef _RIVE_TEXT_INPUT_HPP_
#define _RIVE_TEXT_INPUT_HPP_
#include "rive/generated/text/text_input_base.hpp"
#include "rive/text/raw_text_input.hpp"
#include "rive/text/text_interface.hpp"
namespace rive
{
class TextStyle;
class TextInput : public TextInputBase, public TextInterface
{
public:
void draw(Renderer* renderer) override;
Core* hitTest(HitInfo*, const Mat2D&) override;
#ifdef WITH_RIVE_TEXT
RawTextInput* rawTextInput() { return &m_rawTextInput; }
#endif
void markPaintDirty() override;
void markShapeDirty() override;
StatusCode onAddedClean(CoreContext* context) override;
AABB localBounds() const override;
void update(ComponentDirt value) override;
Vec2D measureLayout(float width,
LayoutMeasureMode widthMode,
float height,
LayoutMeasureMode heightMode) override;
void controlSize(Vec2D size,
LayoutScaleType widthScaleType,
LayoutScaleType heightScaleType,
LayoutDirection direction) override;
protected:
void textChanged() override;
void selectionRadiusChanged() override;
private:
AABB m_worldBounds;
TextStyle* m_textStyle = nullptr;
#ifdef WITH_RIVE_TEXT
RawTextInput m_rawTextInput;
#endif
};
} // namespace rive
#endif

View File

@@ -0,0 +1,15 @@
#ifndef _RIVE_TEXT_INPUT_CURSOR_HPP_
#define _RIVE_TEXT_INPUT_CURSOR_HPP_
#include "rive/generated/text/text_input_cursor_base.hpp"
#include <stdio.h>
namespace rive
{
class TextInputCursor : public TextInputCursorBase
{
public:
Core* hitTest(HitInfo*, const Mat2D&) override;
ShapePaintPath* localClockwisePath() override;
};
} // namespace rive
#endif

View File

@@ -0,0 +1,27 @@
#ifndef _RIVE_TEXT_INPUT_DRAWABLE_HPP_
#define _RIVE_TEXT_INPUT_DRAWABLE_HPP_
#include "rive/generated/text/text_input_drawable_base.hpp"
#include "rive/shapes/shape_paint_container.hpp"
namespace rive
{
class TextInput;
class TextInputDrawable : public TextInputDrawableBase,
public ShapePaintContainer
{
private:
Artboard* getArtboard() override { return artboard(); }
public:
ShapePaintPath* worldPath() override;
const Mat2D& shapeWorldTransform() const override;
ShapePaintPath* localPath() override { return localClockwisePath(); }
StatusCode onAddedClean(CoreContext* context) override;
TextInput* textInput() const;
Component* pathBuilder() override { return parent(); }
void draw(Renderer* renderer) override;
};
} // namespace rive
#endif

View File

@@ -0,0 +1,15 @@
#ifndef _RIVE_TEXT_INPUT_SELECTED_TEXT_HPP_
#define _RIVE_TEXT_INPUT_SELECTED_TEXT_HPP_
#include "rive/generated/text/text_input_selected_text_base.hpp"
#include <stdio.h>
namespace rive
{
class TextInputSelectedText : public TextInputSelectedTextBase
{
public:
Core* hitTest(HitInfo*, const Mat2D&) override;
ShapePaintPath* localClockwisePath() override;
};
} // namespace rive
#endif

View File

@@ -0,0 +1,15 @@
#ifndef _RIVE_TEXT_INPUT_SELECTION_HPP_
#define _RIVE_TEXT_INPUT_SELECTION_HPP_
#include "rive/generated/text/text_input_selection_base.hpp"
namespace rive
{
class TextInputSelection : public TextInputSelectionBase
{
public:
Core* hitTest(HitInfo*, const Mat2D&) override;
ShapePaintPath* localClockwisePath() override;
};
} // namespace rive
#endif

View File

@@ -0,0 +1,15 @@
#ifndef _RIVE_TEXT_INPUT_TEXT_HPP_
#define _RIVE_TEXT_INPUT_TEXT_HPP_
#include "rive/generated/text/text_input_text_base.hpp"
#include <stdio.h>
namespace rive
{
class TextInputText : public TextInputTextBase
{
public:
Core* hitTest(HitInfo*, const Mat2D&) override;
ShapePaintPath* localClockwisePath() override;
};
} // namespace rive
#endif

View File

@@ -0,0 +1,22 @@
#ifndef _RIVE_TEXT_INTERFACE_HPP_
#define _RIVE_TEXT_INTERFACE_HPP_
#include "rive/math/aabb.hpp"
#include "rive/math/vec2d.hpp"
#include "rive/math/mat2d.hpp"
namespace rive
{
class Core;
class TextInterface
{
public:
static TextInterface* from(Core* component);
virtual void markPaintDirty() = 0;
virtual void markShapeDirty() = 0;
virtual AABB localBounds() const = 0;
};
} // namespace rive
#endif

View File

@@ -1,12 +1,10 @@
#ifndef _RIVE_TEXT_STYLE_HPP_
#define _RIVE_TEXT_STYLE_HPP_
#include "rive/generated/text/text_style_base.hpp"
#include "rive/shapes/shape_paint_container.hpp"
#include "rive/shapes/shape_paint_path.hpp"
#include "rive/assets/file_asset_referencer.hpp"
#include "rive/assets/file_asset.hpp"
#include "rive/assets/font_asset.hpp"
#include <unordered_map>
#include "rive/text/text_interface.hpp"
namespace rive
{
@@ -20,13 +18,10 @@ class RenderPaint;
class TextVariationHelper;
class TextStyleAxis;
class TextStyleFeature;
class TextStyle : public TextStyleBase,
public ShapePaintContainer,
public FileAssetReferencer
{
private:
Artboard* getArtboard() override { return artboard(); }
class TextInterface;
class TextStyle : public TextStyleBase, public FileAssetReferencer
{
public:
TextStyle();
void buildDependencies() override;
@@ -37,19 +32,13 @@ public:
FontAsset* fontAsset() const { return (FontAsset*)m_fileAsset; }
bool addPath(const RawPath& rawPath, float opacity);
void rewindPath();
void draw(Renderer* renderer, const Mat2D& worldTransform);
Core* clone() const override;
void addVariation(TextStyleAxis* axis);
void addFeature(TextStyleFeature* feature);
void updateVariableFont();
StatusCode onAddedClean(CoreContext* context) override;
void onDirty(ComponentDirt dirt) override;
// Implemented for ShapePaintContainer.
const Mat2D& shapeWorldTransform() const override;
Component* pathBuilder() override;
bool validate(CoreContext* context) override;
protected:
void fontSizeChanged() override;
@@ -58,19 +47,13 @@ protected:
private:
std::unique_ptr<TextVariationHelper> m_variationHelper;
std::unordered_map<float, ShapePaintPath> m_opacityPaths;
rcp<Font> m_variableFont;
ShapePaintPath m_path;
bool m_hasContents = false;
std::vector<Font::Coord> m_coords;
std::vector<TextStyleAxis*> m_variations;
std::vector<rcp<RenderPaint>> m_paintPool;
std::vector<TextStyleFeature*> m_styleFeatures;
std::vector<Font::Feature> m_features;
public:
ShapePaintPath* localPath() override { return &m_path; }
ShapePaintPath* localClockwisePath() override { return &m_path; }
TextInterface* m_text;
};
} // namespace rive

View File

@@ -0,0 +1,34 @@
#ifndef _RIVE_TEXT_STYLE_PAINT_HPP_
#define _RIVE_TEXT_STYLE_PAINT_HPP_
#include "rive/generated/text/text_style_paint_base.hpp"
#include "rive/shapes/shape_paint_container.hpp"
#include "rive/shapes/shape_paint_path.hpp"
#include <unordered_map>
namespace rive
{
class TextStylePaint : public TextStylePaintBase, public ShapePaintContainer
{
public:
TextStylePaint();
bool addPath(const RawPath& rawPath, float opacity);
void rewindPath();
void draw(Renderer* renderer, const Mat2D& worldTransform);
// Implemented for ShapePaintContainer.
const Mat2D& shapeWorldTransform() const override;
Component* pathBuilder() override;
ShapePaintPath* localPath() override { return &m_path; }
ShapePaintPath* localClockwisePath() override { return &m_path; }
Core* clone() const override;
private:
Artboard* getArtboard() override { return artboard(); }
std::unordered_map<float, ShapePaintPath> m_opacityPaths;
std::vector<rcp<RenderPaint>> m_paintPool;
ShapePaintPath m_path;
bool m_hasContents = false;
};
} // namespace rive
#endif

View File

@@ -7,7 +7,7 @@
namespace rive
{
class TextStyle;
class TextStylePaint;
class Text;
class TextValueRun : public TextValueRunBase, public Hittable
{
@@ -16,7 +16,7 @@ class TextValueRun : public TextValueRunBase, public Hittable
public:
StatusCode onAddedClean(CoreContext* context) override;
StatusCode onAddedDirty(CoreContext* context) override;
TextStyle* style() { return m_style; }
TextStylePaint* style() { return m_style; }
Text* textComponent() const;
uint32_t length()
{
@@ -62,7 +62,7 @@ private:
AABB m_localBounds;
bool m_isHitTarget = false;
std::vector<AABB> m_glyphHitRects;
TextStyle* m_style = nullptr;
TextStylePaint* m_style = nullptr;
uint32_t m_length = -1;
bool canHitTest() const;
};

View File

@@ -0,0 +1,21 @@
#ifndef _RIVE_TEXT_VARIATION_HELPER_HPP_
#define _RIVE_TEXT_VARIATION_HELPER_HPP_
#include "rive/component.hpp"
namespace rive
{
class TextStyle;
class TextVariationHelper : public Component
{
public:
TextVariationHelper(TextStyle* style) : m_textStyle(style) {}
TextStyle* style() const { return m_textStyle; }
void buildDependencies() override;
void update(ComponentDirt value) override;
private:
TextStyle* m_textStyle;
};
} // namespace rive
#endif

View File

@@ -0,0 +1,84 @@
#ifndef _RIVE_TYPED_CHILDREN_HPP_
#define _RIVE_TYPED_CHILDREN_HPP_
#include "rive/core.hpp"
#include "rive/span.hpp"
namespace rive
{
class Core;
template <typename T> class TypedChild
{
public:
TypedChild(Core** child, Core** end) : m_child(child), m_end(end) {}
T* operator*() const { return (*m_child)->template as<T>(); }
TypedChild& operator++()
{
m_child++;
while (m_child != m_end && (*m_child) != nullptr &&
!(*m_child)->template is<T>())
{
m_child++;
}
return *this;
}
bool operator==(const TypedChild& o) const { return m_child == o.m_child; }
bool operator!=(const TypedChild& o) const { return m_child != o.m_child; }
private:
Core** m_child;
Core** m_end;
};
template <typename T> class TypedChildren
{
public:
TypedChildren(Span<Core*> children) : m_children(children) {}
TypedChild<T> begin() const
{
size_t size = m_children.size();
size_t index = 0;
while (index < size && !m_children[index]->template is<T>())
{
index++;
}
return TypedChild<T>(m_children.data() + index,
m_children.data() + size);
}
TypedChild<T> end() const
{
auto ptr = m_children.data() + m_children.size();
return TypedChild<T>(ptr, ptr);
}
T* first() const
{
auto start = begin();
if (start == end())
{
return nullptr;
}
return *start;
}
size_t size() const
{
size_t count = 0;
auto last = end();
for (auto itr = begin(); itr != last; ++itr)
{
count++;
}
return count;
}
private:
Span<Core*> m_children;
};
} // namespace rive
#endif

View File

@@ -36,8 +36,11 @@ CommandQueue::CommandQueue() {}
CommandQueue::~CommandQueue() {}
FileHandle CommandQueue::loadFile(std::vector<uint8_t> rivBytes,
FileListener* listener)
rcp<FileAssetLoader> loader,
FileListener* listener,
RequestId* outId)
{
auto requestId = ++m_currentRequestIdIdx;
auto handle = reinterpret_cast<FileHandle>(++m_currentFileHandleIdx);
if (listener)
@@ -47,26 +50,40 @@ FileHandle CommandQueue::loadFile(std::vector<uint8_t> rivBytes,
registerListener(handle, listener);
}
if (outId)
{
*outId = requestId;
}
AutoLockAndNotify lock(m_commandMutex, m_commandConditionVariable);
m_commandStream << Command::loadFile;
m_commandStream << handle;
m_commandStream << requestId;
m_commandStream << loader;
m_byteVectors << std::move(rivBytes);
return handle;
}
void CommandQueue::deleteFile(FileHandle fileHandle)
RequestId CommandQueue::deleteFile(FileHandle fileHandle)
{
auto requestId = ++m_currentRequestIdIdx;
AutoLockAndNotify lock(m_commandMutex, m_commandConditionVariable);
m_commandStream << Command::deleteFile;
m_commandStream << fileHandle;
m_commandStream << requestId;
return requestId;
}
ArtboardHandle CommandQueue::instantiateArtboardNamed(
FileHandle fileHandle,
std::string name,
ArtboardListener* listener)
ArtboardListener* listener,
RequestId* outId)
{
auto requestId = ++m_currentRequestIdIdx;
auto handle =
reinterpret_cast<ArtboardHandle>(++m_currentArtboardHandleIdx);
@@ -77,27 +94,40 @@ ArtboardHandle CommandQueue::instantiateArtboardNamed(
registerListener(handle, listener);
}
if (outId)
{
*outId = requestId;
}
AutoLockAndNotify lock(m_commandMutex, m_commandConditionVariable);
m_commandStream << Command::instantiateArtboard;
m_commandStream << handle;
m_commandStream << fileHandle;
m_commandStream << requestId;
m_names << std::move(name);
return handle;
}
void CommandQueue::deleteArtboard(ArtboardHandle artboardHandle)
RequestId CommandQueue::deleteArtboard(ArtboardHandle artboardHandle)
{
auto requestId = ++m_currentRequestIdIdx;
AutoLockAndNotify lock(m_commandMutex, m_commandConditionVariable);
m_commandStream << Command::deleteArtboard;
m_commandStream << artboardHandle;
m_commandStream << requestId;
return requestId;
}
StateMachineHandle CommandQueue::instantiateStateMachineNamed(
ArtboardHandle artboardHandle,
std::string name,
StateMachineListener* listener)
StateMachineListener* listener,
RequestId* outId)
{
auto requestId = ++m_currentRequestIdIdx;
auto handle =
reinterpret_cast<StateMachineHandle>(++m_currentStateMachineHandleIdx);
@@ -108,20 +138,32 @@ StateMachineHandle CommandQueue::instantiateStateMachineNamed(
registerListener(handle, listener);
}
if (outId)
{
*outId = requestId;
}
AutoLockAndNotify lock(m_commandMutex, m_commandConditionVariable);
m_commandStream << Command::instantiateStateMachine;
m_commandStream << handle;
m_commandStream << artboardHandle;
m_commandStream << requestId;
m_names << std::move(name);
return handle;
}
void CommandQueue::deleteStateMachine(StateMachineHandle stateMachineHandle)
RequestId CommandQueue::deleteStateMachine(
StateMachineHandle stateMachineHandle)
{
auto requestId = ++m_currentRequestIdIdx;
AutoLockAndNotify lock(m_commandMutex, m_commandConditionVariable);
m_commandStream << Command::deleteStateMachine;
m_commandStream << stateMachineHandle;
m_commandStream << requestId;
return requestId;
}
DrawKey CommandQueue::createDrawKey()
@@ -187,18 +229,28 @@ void CommandQueue::disconnect()
m_commandStream << Command::disconnect;
}
void CommandQueue::requestArtboardNames(FileHandle fileHandle)
RequestId CommandQueue::requestArtboardNames(FileHandle fileHandle)
{
auto requestId = ++m_currentRequestIdIdx;
AutoLockAndNotify lock(m_commandMutex, m_commandConditionVariable);
m_commandStream << Command::listArtboards;
m_commandStream << fileHandle;
m_commandStream << requestId;
return requestId;
}
void CommandQueue::requestStateMachineNames(ArtboardHandle artboardHandle)
RequestId CommandQueue::requestStateMachineNames(ArtboardHandle artboardHandle)
{
auto requestId = ++m_currentRequestIdIdx;
AutoLockAndNotify lock(m_commandMutex, m_commandConditionVariable);
m_commandStream << Command::listStateMachines;
m_commandStream << artboardHandle;
m_commandStream << requestId;
return requestId;
}
void CommandQueue::processMessages()
@@ -226,7 +278,9 @@ void CommandQueue::processMessages()
{
size_t numArtboards;
FileHandle handle;
RequestId requestId;
m_messageStream >> handle;
m_messageStream >> requestId;
m_messageStream >> numArtboards;
std::vector<std::string> artboardNames(numArtboards);
for (auto& name : artboardNames)
@@ -239,6 +293,7 @@ void CommandQueue::processMessages()
if (itr != m_fileListeners.end())
{
itr->second->onArtboardsListed(itr->first,
requestId,
std::move(artboardNames));
}
break;
@@ -247,7 +302,9 @@ void CommandQueue::processMessages()
{
size_t numStateMachines;
ArtboardHandle handle;
RequestId requestId;
m_messageStream >> handle;
m_messageStream >> requestId;
m_messageStream >> numStateMachines;
std::vector<std::string> stateMachineNames(numStateMachines);
for (auto& name : stateMachineNames)
@@ -261,6 +318,7 @@ void CommandQueue::processMessages()
{
itr->second->onStateMachinesListed(
itr->first,
requestId,
std::move(stateMachineNames));
}
@@ -269,12 +327,14 @@ void CommandQueue::processMessages()
case Message::fileDeleted:
{
FileHandle handle;
RequestId requestId;
m_messageStream >> handle;
m_messageStream >> requestId;
lock.unlock();
auto itr = m_fileListeners.find(handle);
if (itr != m_fileListeners.end())
{
itr->second->onFileDeleted(handle);
itr->second->onFileDeleted(handle, requestId);
m_fileListeners.erase(itr);
}
break;
@@ -282,12 +342,14 @@ void CommandQueue::processMessages()
case Message::artboardDeleted:
{
ArtboardHandle handle;
RequestId requestId;
m_messageStream >> handle;
m_messageStream >> requestId;
lock.unlock();
auto itr = m_artboardListeners.find(handle);
if (itr != m_artboardListeners.end())
{
itr->second->onArtboardDeleted(handle);
itr->second->onArtboardDeleted(handle, requestId);
m_artboardListeners.erase(itr);
}
break;
@@ -295,12 +357,14 @@ void CommandQueue::processMessages()
case Message::stateMachineDeleted:
{
StateMachineHandle handle;
RequestId requestId;
m_messageStream >> handle;
m_messageStream >> requestId;
lock.unlock();
auto itr = m_stateMachineListeners.find(handle);
if (itr != m_stateMachineListeners.end())
{
itr->second->onStateMachineDeleted(handle);
itr->second->onStateMachineDeleted(handle, requestId);
m_stateMachineListeners.erase(itr);
}
break;

View File

@@ -94,12 +94,19 @@ bool CommandServer::processCommands()
case CommandQueue::Command::loadFile:
{
FileHandle handle;
RequestId requestId;
std::vector<uint8_t> rivBytes;
rcp<FileAssetLoader> loader;
commandStream >> handle;
commandStream >> requestId;
commandStream >> loader;
m_commandQueue->m_byteVectors >> rivBytes;
lock.unlock();
std::unique_ptr<rive::File> file =
rive::File::import(rivBytes, m_factory);
rive::File::import(rivBytes,
m_factory,
nullptr,
std::move(loader));
if (file != nullptr)
{
m_files[handle] = std::move(file);
@@ -114,7 +121,9 @@ bool CommandServer::processCommands()
case CommandQueue::Command::deleteFile:
{
FileHandle handle;
RequestId requestId;
commandStream >> handle;
commandStream >> requestId;
lock.unlock();
m_files.erase(handle);
std::unique_lock<std::mutex> messageLock(
@@ -122,6 +131,7 @@ bool CommandServer::processCommands()
m_commandQueue->m_messageStream
<< CommandQueue::Message::fileDeleted;
m_commandQueue->m_messageStream << handle;
m_commandQueue->m_messageStream << requestId;
break;
}
@@ -129,9 +139,11 @@ bool CommandServer::processCommands()
{
ArtboardHandle handle;
FileHandle fileHandle;
RequestId requestId;
std::string name;
commandStream >> handle;
commandStream >> fileHandle;
commandStream >> requestId;
m_commandQueue->m_names >> name;
lock.unlock();
if (rive::File* file = getFile(fileHandle))
@@ -155,7 +167,9 @@ bool CommandServer::processCommands()
case CommandQueue::Command::deleteArtboard:
{
ArtboardHandle handle;
RequestId requestId;
commandStream >> handle;
commandStream >> requestId;
lock.unlock();
m_artboards.erase(handle);
std::unique_lock<std::mutex> messageLock(
@@ -163,6 +177,7 @@ bool CommandServer::processCommands()
m_commandQueue->m_messageStream
<< CommandQueue::Message::artboardDeleted;
m_commandQueue->m_messageStream << handle;
m_commandQueue->m_messageStream << requestId;
break;
}
@@ -170,9 +185,11 @@ bool CommandServer::processCommands()
{
StateMachineHandle handle;
ArtboardHandle artboardHandle;
RequestId requestId;
std::string name;
commandStream >> handle;
commandStream >> artboardHandle;
commandStream >> requestId;
m_commandQueue->m_names >> name;
lock.unlock();
if (rive::ArtboardInstance* artboard =
@@ -197,7 +214,9 @@ bool CommandServer::processCommands()
case CommandQueue::Command::deleteStateMachine:
{
StateMachineHandle handle;
RequestId requestId;
commandStream >> handle;
commandStream >> requestId;
lock.unlock();
m_stateMachines.erase(handle);
std::unique_lock<std::mutex> messageLock(
@@ -205,6 +224,7 @@ bool CommandServer::processCommands()
m_commandQueue->m_messageStream
<< CommandQueue::Message::stateMachineDeleted;
m_commandQueue->m_messageStream << handle;
m_commandQueue->m_messageStream << requestId;
break;
}
@@ -238,7 +258,9 @@ bool CommandServer::processCommands()
case CommandQueue::Command::listArtboards:
{
FileHandle handle;
RequestId requestId;
commandStream >> handle;
commandStream >> requestId;
lock.unlock();
auto file = getFile(handle);
if (file)
@@ -250,6 +272,7 @@ bool CommandServer::processCommands()
m_commandQueue->m_messageStream
<< CommandQueue::Message::artboardsListed;
m_commandQueue->m_messageStream << handle;
m_commandQueue->m_messageStream << requestId;
m_commandQueue->m_messageStream << artboards.size();
for (auto artboard : artboards)
{
@@ -263,7 +286,9 @@ bool CommandServer::processCommands()
case CommandQueue::Command::listStateMachines:
{
ArtboardHandle handle;
RequestId requestId;
commandStream >> handle;
commandStream >> requestId;
lock.unlock();
auto artboard = getArtboardInstance(handle);
if (artboard)
@@ -274,6 +299,7 @@ bool CommandServer::processCommands()
m_commandQueue->m_messageStream
<< CommandQueue::Message::stateMachinesListed;
m_commandQueue->m_messageStream << handle;
m_commandQueue->m_messageStream << requestId;
m_commandQueue->m_messageStream << numStateMachines;
for (int i = 0; i < numStateMachines; ++i)
{

View File

@@ -163,8 +163,8 @@ static Core* readRuntimeObject(BinaryReader& reader,
return object;
}
File::File(Factory* factory, FileAssetLoader* assetLoader) :
m_factory(factory), m_assetLoader(assetLoader)
File::File(Factory* factory, rcp<FileAssetLoader> assetLoader) :
m_factory(factory), m_assetLoader(std::move(assetLoader))
{
assert(factory);
}
@@ -214,7 +214,7 @@ File::~File()
std::unique_ptr<File> File::import(Span<const uint8_t> bytes,
Factory* factory,
ImportResult* result,
FileAssetLoader* assetLoader)
rcp<FileAssetLoader> assetLoader)
{
BinaryReader reader(bytes);
RuntimeHeader header;
@@ -241,7 +241,7 @@ std::unique_ptr<File> File::import(Span<const uint8_t> bytes,
}
return nullptr;
}
auto file = rivestd::make_unique<File>(factory, assetLoader);
auto file = rivestd::make_unique<File>(factory, std::move(assetLoader));
auto readResult = file->read(reader, header);
if (result)

View File

@@ -0,0 +1,11 @@
#include "rive/generated/text/text_input_base.hpp"
#include "rive/text/text_input.hpp"
using namespace rive;
Core* TextInputBase::clone() const
{
auto cloned = new TextInput();
cloned->copy(*this);
return cloned;
}

View File

@@ -0,0 +1,11 @@
#include "rive/generated/text/text_input_cursor_base.hpp"
#include "rive/text/text_input_cursor.hpp"
using namespace rive;
Core* TextInputCursorBase::clone() const
{
auto cloned = new TextInputCursor();
cloned->copy(*this);
return cloned;
}

View File

@@ -0,0 +1,11 @@
#include "rive/generated/text/text_input_selected_text_base.hpp"
#include "rive/text/text_input_selected_text.hpp"
using namespace rive;
Core* TextInputSelectedTextBase::clone() const
{
auto cloned = new TextInputSelectedText();
cloned->copy(*this);
return cloned;
}

View File

@@ -0,0 +1,11 @@
#include "rive/generated/text/text_input_selection_base.hpp"
#include "rive/text/text_input_selection.hpp"
using namespace rive;
Core* TextInputSelectionBase::clone() const
{
auto cloned = new TextInputSelection();
cloned->copy(*this);
return cloned;
}

View File

@@ -0,0 +1,11 @@
#include "rive/generated/text/text_input_text_base.hpp"
#include "rive/text/text_input_text.hpp"
using namespace rive;
Core* TextInputTextBase::clone() const
{
auto cloned = new TextInputText();
cloned->copy(*this);
return cloned;
}

View File

@@ -0,0 +1,12 @@
#include "rive/generated/text/text_style_paint_base.hpp"
#include "rive/text/text_style_paint.hpp"
#include "rive/text/text_variation_helper.hpp"
using namespace rive;
Core* TextStylePaintBase::clone() const
{
auto cloned = new TextStylePaint();
cloned->copy(*this);
return cloned;
}

View File

@@ -8,9 +8,11 @@
using namespace rive;
FileAssetImporter::FileAssetImporter(FileAsset* fileAsset,
FileAssetLoader* assetLoader,
rcp<FileAssetLoader> assetLoader,
Factory* factory) :
m_FileAsset(fileAsset), m_FileAssetLoader(assetLoader), m_Factory(factory)
m_FileAsset(fileAsset),
m_FileAssetLoader(std::move(assetLoader)),
m_Factory(factory)
{}
// if file asset contents are found when importing a rive file, store those for

View File

@@ -1,5 +1,6 @@
#include "rive/node.hpp"
#include "rive/world_transform_component.hpp"
#include "rive/layout_component.hpp"
using namespace rive;
@@ -25,4 +26,17 @@ Mat2D Node::localTransform()
m_LocalTransform = Mat2D();
}
return m_LocalTransform;
}
}
#ifdef WITH_RIVE_LAYOUT
void Node::markLayoutNodeDirty()
{
for (ContainerComponent* p = parent(); p != nullptr; p = p->parent())
{
if (p->is<LayoutComponent>())
{
p->as<LayoutComponent>()->markLayoutNodeDirty();
}
}
}
#endif

View File

@@ -3,7 +3,6 @@
#include "rive/shapes/paint/fill.hpp"
#include "rive/shapes/shape.hpp"
#include "rive/artboard.hpp"
#include "rive/factory.hpp"
#include "rive/core_context.hpp"

View File

@@ -6,7 +6,11 @@
#include "rive/foreground_layout_drawable.hpp"
#include "rive/shapes/paint/stroke.hpp"
#include "rive/shapes/shape.hpp"
#include "rive/text/text_style.hpp"
#include "rive/text/text_style_paint.hpp"
#include "rive/text/text_input_selected_text.hpp"
#include "rive/text/text_input_cursor.hpp"
#include "rive/text/text_input_selection.hpp"
#include "rive/text/text_input_text.hpp"
using namespace rive;
@@ -20,10 +24,18 @@ ShapePaintContainer* ShapePaintContainer::from(Component* component)
return component->as<LayoutComponent>();
case Shape::typeKey:
return component->as<Shape>();
case TextStyle::typeKey:
return component->as<TextStyle>();
case TextStylePaint::typeKey:
return component->as<TextStylePaint>();
case ForegroundLayoutDrawable::typeKey:
return component->as<ForegroundLayoutDrawable>();
case TextInputCursor::typeKey:
return component->as<TextInputCursor>();
case TextInputSelection::typeKey:
return component->as<TextInputSelection>();
case TextInputText::typeKey:
return component->as<TextInputText>();
case TextInputSelectedText::typeKey:
return component->as<TextInputSelectedText>();
}
return nullptr;
}

View File

@@ -384,7 +384,7 @@ void RawTextInput::buildTextPaths(Factory* factory)
}
}
AABB RawTextInput::bounds() { return m_shape.bounds(); }
AABB RawTextInput::bounds() const { return m_shape.bounds(); }
void RawTextInput::paragraphSpacing(float value)
{

View File

@@ -5,7 +5,7 @@ using namespace rive;
#include "rive/component_dirt.hpp"
#include "rive/math/rectangles_to_contour.hpp"
#include "rive/math/transform_components.hpp"
#include "rive/text/text_style.hpp"
#include "rive/text/text_style_paint.hpp"
#include "rive/text/text_value_run.hpp"
#include "rive/text/text_modifier_group.hpp"
#include "rive/shapes/paint/shape_paint.hpp"
@@ -59,7 +59,7 @@ TextSizing Text::effectiveSizing() const
void Text::clearRenderStyles()
{
for (TextStyle* style : m_renderStyles)
for (TextStylePaint* style : m_renderStyles)
{
style->rewindPath();
}
@@ -389,7 +389,7 @@ void Text::buildRenderStyles()
assert(run->styleId < m_runs.size());
TextValueRun* textValueRun = m_runs[run->styleId];
TextStyle* style = textValueRun->style();
TextStylePaint* style = textValueRun->style();
// TextValueRun::onAddedDirty botches loading if it cannot
// resolve a style, so we're confident we have a style here.
assert(style != nullptr);
@@ -495,7 +495,7 @@ skipLines:
}
}
const TextStyle* Text::styleFromShaperId(uint16_t id) const
const TextStylePaint* Text::styleFromShaperId(uint16_t id) const
{
assert(id < m_runs.size());
return m_runs[id]->style();
@@ -536,6 +536,8 @@ void Text::addModifierGroup(TextModifierGroup* group)
m_modifierGroups.push_back(group);
}
void Text::markShapeDirty() { markShapeDirty(true); }
void Text::markShapeDirty(bool sendToLayout)
{
addDirt(ComponentDirt::Path);
@@ -552,19 +554,6 @@ void Text::markShapeDirty(bool sendToLayout)
#endif
}
#ifdef WITH_RIVE_LAYOUT
void Text::markLayoutNodeDirty()
{
for (ContainerComponent* p = parent(); p != nullptr; p = p->parent())
{
if (p->is<LayoutComponent>())
{
p->as<LayoutComponent>()->markLayoutNodeDirty();
}
}
}
#endif
void Text::modifierShapeDirty() { addDirt(ComponentDirt::Path); }
void Text::markPaintDirty() { addDirt(ComponentDirt::Paint); }
@@ -717,7 +706,7 @@ void Text::onDirty(ComponentDirt value)
}
if (hasDirt(value, ComponentDirt::Path | ComponentDirt::Paint))
{
for (TextStyle* style : m_renderStyles)
for (TextStylePaint* style : m_renderStyles)
{
style->invalidateStrokeEffects();
}
@@ -808,7 +797,7 @@ void Text::update(ComponentDirt value)
{
// Note that buildRenderStyles does this too, which is why we can get
// away doing this in the else.
for (TextStyle* style : m_renderStyles)
for (TextStylePaint* style : m_renderStyles)
{
style->propagateOpacity(renderOpacity());
}
@@ -963,6 +952,7 @@ Core* Text::hitTest(HitInfo*, const Mat2D&) { return nullptr; }
void Text::addRun(TextValueRun* run) {}
void Text::addModifierGroup(TextModifierGroup* group) {}
void Text::markShapeDirty(bool sendToLayout) {}
void Text::markShapeDirty() {}
void Text::update(ComponentDirt value) {}
void Text::onDirty(ComponentDirt value) {}
void Text::alignValueChanged() {}
@@ -973,7 +963,10 @@ void Text::heightChanged() {}
void Text::markPaintDirty() {}
void Text::modifierShapeDirty() {}
bool Text::modifierRangesNeedShape() const { return false; }
const TextStyle* Text::styleFromShaperId(uint16_t id) const { return nullptr; }
const TextStylePaint* Text::styleFromShaperId(uint16_t id) const
{
return nullptr;
}
void Text::paragraphSpacingChanged() {}
AABB Text::localBounds() const { return AABB(); }
void Text::originValueChanged() {}

112
src/text/text_input.cpp Normal file
View File

@@ -0,0 +1,112 @@
#include "rive/text/text_input.hpp"
#include "rive/text/text_style.hpp"
#include "rive/text/text_input_drawable.hpp"
#include "rive/math/mat2d.hpp"
#include "rive/artboard.hpp"
#include "rive/factory.hpp"
using namespace rive;
void TextInput::draw(Renderer* renderer) {}
Core* TextInput::hitTest(HitInfo*, const Mat2D&) { return nullptr; }
void TextInput::textChanged()
{
#ifdef WITH_RIVE_TEXT
m_rawTextInput.text(m_Text);
#endif
}
void TextInput::selectionRadiusChanged() {}
void TextInput::markPaintDirty() { addDirt(ComponentDirt::Paint); }
void TextInput::markShapeDirty() { addDirt(ComponentDirt::TextShape); }
AABB TextInput::localBounds() const
{
#ifdef WITH_RIVE_TEXT
return m_rawTextInput.bounds();
#else
return AABB();
#endif
}
StatusCode TextInput::onAddedClean(CoreContext* context)
{
Super::onAddedClean(context);
m_textStyle = children<TextStyle>().first();
#ifdef WITH_RIVE_TEXT
if (m_textStyle != nullptr && m_textStyle->font() != nullptr)
{
m_rawTextInput.font(m_textStyle->font());
}
m_rawTextInput.text(m_Text);
#endif
return m_textStyle == nullptr ? StatusCode::MissingObject : StatusCode::Ok;
}
void TextInput::update(ComponentDirt value)
{
Super::update(value);
#ifdef WITH_RIVE_TEXT
if (hasDirt(value, ComponentDirt::Paint | ComponentDirt::TextShape))
{
Factory* factory = artboard()->factory();
RawTextInput::Flags changed = m_rawTextInput.update(factory);
if ((changed & RawTextInput::Flags::shapeDirty) != 0)
{
m_worldBounds =
worldTransform().mapBoundingBox(m_rawTextInput.bounds());
#ifdef WITH_RIVE_LAYOUT
if (m_rawTextInput.sizing() == TextSizing::autoHeight)
{
markLayoutNodeDirty();
}
#endif
}
if ((changed & RawTextInput::Flags::selectionDirty) != 0)
{
for (auto child : children<TextInputDrawable>())
{
child->invalidateStrokeEffects();
}
}
}
#endif
}
Vec2D TextInput::measureLayout(float width,
LayoutMeasureMode widthMode,
float height,
LayoutMeasureMode heightMode)
{
#ifdef WITH_RIVE_TEXT
AABB bounds =
m_rawTextInput.measure(widthMode == LayoutMeasureMode::undefined
? std::numeric_limits<float>::max()
: width,
heightMode == LayoutMeasureMode::undefined
? std::numeric_limits<float>::max()
: height);
return bounds.size();
#else
return Vec2D();
#endif
}
void TextInput::controlSize(Vec2D size,
LayoutScaleType widthScaleType,
LayoutScaleType heightScaleType,
LayoutDirection direction)
{
#ifdef WITH_RIVE_TEXT
m_rawTextInput.maxWidth(size.x);
m_rawTextInput.sizing(TextSizing::autoHeight);
addDirt(ComponentDirt::TextShape);
#endif
}

View File

@@ -0,0 +1,16 @@
#include "rive/text/text_input_cursor.hpp"
#include "rive/math/mat2d.hpp"
#include "rive/text/text_input.hpp"
using namespace rive;
Core* TextInputCursor::hitTest(HitInfo*, const Mat2D&) { return nullptr; }
ShapePaintPath* TextInputCursor::localClockwisePath()
{
#ifdef WITH_RIVE_TEXT
return textInput()->rawTextInput()->cursorPath();
#else
return nullptr;
#endif
}

View File

@@ -0,0 +1,64 @@
#include "rive/text/text_input_drawable.hpp"
#include "rive/text/text_input.hpp"
#include "rive/shapes/paint/shape_paint.hpp"
using namespace rive;
TextInput* TextInputDrawable::textInput() const
{
return parent()->as<TextInput>();
}
ShapePaintPath* TextInputDrawable::worldPath()
{
RIVE_UNREACHABLE();
return nullptr;
}
StatusCode TextInputDrawable::onAddedClean(CoreContext* context)
{
if (!parent()->is<TextInput>())
{
return StatusCode::InvalidObject;
}
return StatusCode::Ok;
}
const Mat2D& TextInputDrawable::shapeWorldTransform() const
{
return worldTransform();
}
void TextInputDrawable::draw(Renderer* renderer)
{
if (renderOpacity() == 0.0f)
{
return;
}
ClipResult clipResult = applyClip(renderer);
if (clipResult != ClipResult::emptyClip)
{
for (auto shapePaint : m_ShapePaints)
{
if (!shapePaint->isVisible())
{
continue;
}
auto shapePaintPath = shapePaint->pickPath(this);
if (shapePaintPath == nullptr)
{
continue;
}
shapePaint->draw(renderer,
shapePaintPath,
textInput()->worldTransform());
}
}
if (clipResult != ClipResult::noClip)
{
renderer->restore();
}
}

View File

@@ -0,0 +1,16 @@
#include "rive/text/text_input_selected_text.hpp"
#include "rive/math/mat2d.hpp"
#include "rive/text/text_input.hpp"
using namespace rive;
Core* TextInputSelectedText::hitTest(HitInfo*, const Mat2D&) { return nullptr; }
ShapePaintPath* TextInputSelectedText::localClockwisePath()
{
#ifdef WITH_RIVE_TEXT
return textInput()->rawTextInput()->selectedTextPath();
#else
return nullptr;
#endif
}

View File

@@ -0,0 +1,16 @@
#include "rive/text/text_input_selection.hpp"
#include "rive/math/mat2d.hpp"
#include "rive/text/text_input.hpp"
using namespace rive;
Core* TextInputSelection::hitTest(HitInfo*, const Mat2D&) { return nullptr; }
ShapePaintPath* TextInputSelection::localClockwisePath()
{
#ifdef WITH_RIVE_TEXT
return textInput()->rawTextInput()->selectionPath();
#else
return nullptr;
#endif
}

View File

@@ -0,0 +1,16 @@
#include "rive/text/text_input_text.hpp"
#include "rive/math/mat2d.hpp"
#include "rive/text/text_input.hpp"
using namespace rive;
Core* TextInputText::hitTest(HitInfo*, const Mat2D&) { return nullptr; }
ShapePaintPath* TextInputText::localClockwisePath()
{
#ifdef WITH_RIVE_TEXT
return textInput()->rawTextInput()->textPath();
#else
return nullptr;
#endif
}

View File

@@ -0,0 +1,23 @@
#include "rive/text/text_interface.hpp"
#include "rive/text/text_input.hpp"
#include "rive/text/text.hpp"
using namespace rive;
TextInterface* TextInterface::from(Core* component)
{
if (component == nullptr)
{
return nullptr;
}
switch (component->coreType())
{
case Text::typeKey:
return component->as<Text>();
break;
case TextInput::typeKey:
return component->as<TextInput>();
break;
}
return nullptr;
}

View File

@@ -4,7 +4,7 @@
#include "rive/text/text_shape_modifier.hpp"
#include "rive/text/text_modifier_range.hpp"
#include "rive/text/glyph_lookup.hpp"
#include "rive/text/text_style.hpp"
#include "rive/text/text_style_paint.hpp"
#include "rive/artboard.hpp"
#include <limits>
@@ -288,7 +288,7 @@ TextRun TextModifierGroup::modifyShape(const Text& text,
TextRun run,
float strength)
{
const TextStyle* style = text.styleFromShaperId(run.styleId);
const TextStylePaint* style = text.styleFromShaperId(run.styleId);
if (style == nullptr || style->font() == nullptr)
{
return run;

View File

@@ -1,43 +1,18 @@
#include "rive/text/text_style.hpp"
#include "rive/text/text_style_axis.hpp"
#include "rive/text/text_style_feature.hpp"
#include "rive/text/text_variation_helper.hpp"
#include "rive/renderer.hpp"
#include "rive/shapes/paint/feather.hpp"
#include "rive/shapes/paint/shape_paint.hpp"
#include "rive/backboard.hpp"
#include "rive/importers/backboard_importer.hpp"
#include "rive/text/text.hpp"
#include "rive/artboard.hpp"
#include "rive/factory.hpp"
using namespace rive;
namespace rive
{
class TextVariationHelper : public Component
{
public:
TextVariationHelper(TextStyle* style) : m_textStyle(style) {}
TextStyle* style() const { return m_textStyle; }
void buildDependencies() override
{
auto text = m_textStyle->parent();
text->artboard()->addDependent(this);
addDependent(text);
}
void update(ComponentDirt value) override
{
m_textStyle->updateVariableFont();
}
private:
TextStyle* m_textStyle;
};
} // namespace rive
// satisfy unique_ptr
TextStyle::TextStyle() : m_path(true, FillRule::clockwise) {}
TextStyle::TextStyle() = default;
void TextStyle::addVariation(TextStyleAxis* axis)
{
@@ -53,7 +28,7 @@ void TextStyle::onDirty(ComponentDirt dirt)
{
if ((dirt & ComponentDirt::TextShape) == ComponentDirt::TextShape)
{
parent()->as<Text>()->markShapeDirty();
m_text->markShapeDirty();
if (m_variationHelper != nullptr)
{
m_variationHelper->addDirt(ComponentDirt::TextShape);
@@ -63,6 +38,9 @@ void TextStyle::onDirty(ComponentDirt dirt)
StatusCode TextStyle::onAddedClean(CoreContext* context)
{
// We know this is good because we validated it during validate().
m_text = TextInterface::from(parent());
auto code = Super::onAddedClean(context);
if (code != StatusCode::Ok)
{
@@ -138,94 +116,6 @@ void TextStyle::buildDependencies()
Super::buildDependencies();
}
void TextStyle::rewindPath()
{
m_path.rewind();
m_hasContents = false;
m_opacityPaths.clear();
}
bool TextStyle::addPath(const RawPath& rawPath, float opacity)
{
bool hadContents = m_hasContents;
m_hasContents = true;
if (opacity > 0.0f)
{
// m_path contains everything, so inner feather bounds can work.
m_path.addPathClockwise(rawPath);
// Bucket by opacity
auto itr = m_opacityPaths.find(opacity);
ShapePaintPath* shapePaintPath = nullptr;
if (itr != m_opacityPaths.end())
{
ShapePaintPath& path = itr->second;
shapePaintPath = &path;
}
else
{
m_opacityPaths[opacity] = ShapePaintPath(true, FillRule::clockwise);
shapePaintPath = &m_opacityPaths.at(opacity);
}
shapePaintPath->addPathClockwise(rawPath);
}
return !hadContents;
}
void TextStyle::draw(Renderer* renderer, const Mat2D& worldTransform)
{
for (auto shapePaint : m_ShapePaints)
{
if (!shapePaint->shouldDraw())
{
continue;
}
shapePaint->blendMode(parent()->as<Text>()->blendMode());
// For blend modes to work, opaque paths render first
auto itr = m_opacityPaths.find(1.0f);
if (itr != m_opacityPaths.end())
{
ShapePaintPath& path = itr->second;
shapePaint->draw(renderer, &path, worldTransform, true);
}
if (m_paintPool.size() < m_opacityPaths.size())
{
m_paintPool.reserve(m_opacityPaths.size());
Factory* factory = artboard()->factory();
while (m_paintPool.size() < m_opacityPaths.size())
{
m_paintPool.emplace_back(factory->makeRenderPaint());
}
}
uint32_t paintIndex = 0;
for (itr = m_opacityPaths.begin(); itr != m_opacityPaths.end(); itr++)
{
// Don't render opaque paths twice
if (itr->first == 1.0f)
{
continue;
}
RenderPaint* renderPaint = m_paintPool[paintIndex++].get();
shapePaint->applyTo(renderPaint, itr->first);
if (auto feather = shapePaint->feather())
{
renderPaint->feather(feather->strength());
}
ShapePaintPath& path = itr->second;
shapePaint->draw(renderer,
&path,
worldTransform,
true,
renderPaint);
}
}
}
uint32_t TextStyle::assetId() { return this->fontAssetId(); }
void TextStyle::setAsset(FileAsset* asset)
@@ -246,14 +136,11 @@ StatusCode TextStyle::import(ImportStack& importStack)
return Super::import(importStack);
}
void TextStyle::fontSizeChanged() { parent()->as<Text>()->markShapeDirty(); }
void TextStyle::fontSizeChanged() { m_text->markShapeDirty(); }
void TextStyle::lineHeightChanged() { parent()->as<Text>()->markShapeDirty(); }
void TextStyle::lineHeightChanged() { m_text->markShapeDirty(); }
void TextStyle::letterSpacingChanged()
{
parent()->as<Text>()->markShapeDirty();
}
void TextStyle::letterSpacingChanged() { m_text->markShapeDirty(); }
Core* TextStyle::clone() const
{
@@ -266,9 +153,7 @@ Core* TextStyle::clone() const
return twin;
}
const Mat2D& TextStyle::shapeWorldTransform() const
bool TextStyle::validate(CoreContext* context)
{
return parent()->as<Text>()->shapeWorldTransform();
}
Component* TextStyle::pathBuilder() { return parent(); }
return TextInterface::from(context->resolve(parentId())) != nullptr;
}

View File

@@ -0,0 +1,117 @@
#include "rive/text/text_style_paint.hpp"
#include "rive/text/text.hpp"
#include "rive/text/text_variation_helper.hpp"
#include "rive/shapes/paint/shape_paint.hpp"
#include "rive/shapes/paint/feather.hpp"
#include "rive/artboard.hpp"
#include "rive/factory.hpp"
using namespace rive;
TextStylePaint::TextStylePaint() : m_path(true, FillRule::clockwise) {}
void TextStylePaint::rewindPath()
{
m_path.rewind();
m_hasContents = false;
m_opacityPaths.clear();
}
bool TextStylePaint::addPath(const RawPath& rawPath, float opacity)
{
bool hadContents = m_hasContents;
m_hasContents = true;
if (opacity > 0.0f)
{
// m_path contains everything, so inner feather bounds can work.
m_path.addPathClockwise(rawPath);
// Bucket by opacity
auto itr = m_opacityPaths.find(opacity);
ShapePaintPath* shapePaintPath = nullptr;
if (itr != m_opacityPaths.end())
{
ShapePaintPath& path = itr->second;
shapePaintPath = &path;
}
else
{
m_opacityPaths[opacity] = ShapePaintPath(true, FillRule::clockwise);
shapePaintPath = &m_opacityPaths.at(opacity);
}
shapePaintPath->addPathClockwise(rawPath);
}
return !hadContents;
}
void TextStylePaint::draw(Renderer* renderer, const Mat2D& worldTransform)
{
for (auto shapePaint : m_ShapePaints)
{
if (!shapePaint->shouldDraw())
{
continue;
}
shapePaint->blendMode(parent()->as<Text>()->blendMode());
// For blend modes to work, opaque paths render first
auto itr = m_opacityPaths.find(1.0f);
if (itr != m_opacityPaths.end())
{
ShapePaintPath& path = itr->second;
shapePaint->draw(renderer, &path, worldTransform, true);
}
if (m_paintPool.size() < m_opacityPaths.size())
{
m_paintPool.reserve(m_opacityPaths.size());
Factory* factory = artboard()->factory();
while (m_paintPool.size() < m_opacityPaths.size())
{
m_paintPool.emplace_back(factory->makeRenderPaint());
}
}
uint32_t paintIndex = 0;
for (itr = m_opacityPaths.begin(); itr != m_opacityPaths.end(); itr++)
{
// Don't render opaque paths twice
if (itr->first == 1.0f)
{
continue;
}
RenderPaint* renderPaint = m_paintPool[paintIndex++].get();
shapePaint->applyTo(renderPaint, itr->first);
if (auto feather = shapePaint->feather())
{
renderPaint->feather(feather->strength());
}
ShapePaintPath& path = itr->second;
shapePaint->draw(renderer,
&path,
worldTransform,
true,
renderPaint);
}
}
}
const Mat2D& TextStylePaint::shapeWorldTransform() const
{
return parent()->as<Text>()->shapeWorldTransform();
}
Component* TextStylePaint::pathBuilder() { return parent(); }
Core* TextStylePaint::clone() const
{
TextStylePaint* twin = TextStylePaintBase::clone()->as<TextStylePaint>();
if (m_fileAsset != nullptr)
{
twin->setAsset(m_fileAsset);
}
return twin;
}

View File

@@ -1,6 +1,6 @@
#include "rive/core_context.hpp"
#include "rive/text/text.hpp"
#include "rive/text/text_style.hpp"
#include "rive/text/text_style_paint.hpp"
#include "rive/text/text_value_run.hpp"
#include "rive/artboard.hpp"
#include "rive/hittest_command_path.hpp"
@@ -41,12 +41,12 @@ StatusCode TextValueRun::onAddedDirty(CoreContext* context)
return code;
}
auto coreObject = context->resolve(styleId());
if (coreObject == nullptr || !coreObject->is<TextStyle>())
if (coreObject == nullptr || !coreObject->is<TextStylePaint>())
{
return StatusCode::MissingObject;
}
m_style = static_cast<TextStyle*>(coreObject);
m_style = static_cast<TextStylePaint*>(coreObject);
return StatusCode::Ok;
}
@@ -54,9 +54,9 @@ StatusCode TextValueRun::onAddedDirty(CoreContext* context)
void TextValueRun::styleIdChanged()
{
auto coreObject = artboard()->resolve(styleId());
if (coreObject != nullptr && coreObject->is<TextStyle>())
if (coreObject != nullptr && coreObject->is<TextStylePaint>())
{
m_style = static_cast<TextStyle*>(coreObject);
m_style = static_cast<TextStylePaint*>(coreObject);
parent()->as<Text>()->markShapeDirty();
}
}

View File

@@ -0,0 +1,17 @@
#include "rive/text/text_variation_helper.hpp"
#include "rive/text/text_style.hpp"
#include "rive/artboard.hpp"
using namespace rive;
void TextVariationHelper::buildDependencies()
{
auto text = m_textStyle->parent();
text->artboard()->addDependent(this);
addDependent(text);
}
void TextVariationHelper::update(ComponentDirt value)
{
m_textStyle->updateVariableFont();
}

View File

@@ -61,6 +61,7 @@ class SimpleFileListener : public CommandQueue::FileListener
public:
virtual void onArtboardsListed(
const FileHandle fileHandle,
RequestId requestId,
std::vector<std::string> artboardNames) override
{
// we can guarantee that this is the only listener that will receive
@@ -76,6 +77,7 @@ class SimpleArtboardListener : public CommandQueue::ArtboardListener
public:
virtual void onStateMachinesListed(
const ArtboardHandle fileHandle,
RequestId requestId,
std::vector<std::string> stateMachineNames) override
{
// we can guarantee that this is the only listener that will receive
@@ -106,6 +108,16 @@ static void input_thread(rcp<CommandQueue> commandQueue)
commandQueue->disconnect();
}
class DefaultFileAssetLoader : public rive::FileAssetLoader
{
virtual bool loadContents(FileAsset& asset,
Span<const uint8_t> inBandBytes,
Factory* factory)
{
return false;
}
};
// this is a seperate thread for testing, it could just as easily be in
// input_thread or the main thread
static void draw_thread(rcp<CommandQueue> commandQueue)
@@ -114,8 +126,12 @@ static void draw_thread(rcp<CommandQueue> commandQueue)
SimpleArtboardListener aListener;
SimpleStateMachineListener stmListener;
FileHandle fileHandle =
commandQueue->loadFile(std::move(rivBytes), &fListener);
rcp<DefaultFileAssetLoader> defaultLoader =
make_rcp<DefaultFileAssetLoader>();
FileHandle fileHandle = commandQueue->loadFile(std::move(rivBytes),
defaultLoader.get(),
&fListener);
ArtboardHandle artboardHandle =
commandQueue->instantiateDefaultArtboard(fileHandle, &aListener);

Binary file not shown.

View File

@@ -0,0 +1,41 @@
#include "rive/file.hpp"
#include "rive/shapes/paint/shape_paint.hpp"
#include "rive_file_reader.hpp"
#include <catch.hpp>
#include <cstdio>
#include <cstring>
using namespace rive;
TEST_CASE("child typed iterators work", "[iterators]")
{
auto file = ReadRiveFile("assets/juice.riv");
auto artboard = file->artboardDefault();
// Expect only one node.
size_t count = 0;
for (auto child : artboard->children<Node>())
{
CHECK(child->name() == "root");
count++;
}
CHECK(count == 1);
size_t shapePaintCount = 0;
for (auto child : artboard->children<ShapePaint>())
{
shapePaintCount++;
CHECK(!child->isTranslucent());
}
CHECK(shapePaintCount == 1);
count = 0;
auto allShapePaints = artboard->objects<ShapePaint>();
for (auto itr = allShapePaints.begin(); itr != allShapePaints.end(); ++itr)
{
count++;
}
CHECK(allShapePaints.size() == 37);
CHECK(allShapePaints.size() == count);
}

View File

@@ -7,6 +7,7 @@
#include "rive/animation/state_machine_instance.hpp"
#include "rive/command_queue.hpp"
#include "rive/command_server.hpp"
#include "rive/file.hpp"
#include "common/render_context_null.hpp"
#include <fstream>
@@ -35,6 +36,35 @@ static void wait_for_server(CommandQueue* commandQueue)
cv.wait(lock);
}
class TestPODStream : public RefCnt<TestPODStream>
{
public:
static constexpr int MAG_NUMBER = 0x99;
int m_number = MAG_NUMBER;
};
TEST_CASE("POD Stream RCP", "[PODStream]")
{
PODStream stream;
rcp<TestPODStream> t1 = make_rcp<TestPODStream>();
TestPODStream* orig = t1.get();
stream << t1;
CHECK(t1.get() != nullptr);
CHECK(t1.get() == orig);
rcp<TestPODStream> t2;
stream >> t2;
CHECK(t2.get() == orig);
CHECK(t2->m_number == TestPODStream::MAG_NUMBER);
rcp<TestPODStream> t3;
stream << t3;
rcp<TestPODStream> t4;
stream >> t4;
CHECK(t4.get() == nullptr);
}
TEST_CASE("artboard management", "[CommandQueue]")
{
auto commandQueue = make_rcp<CommandQueue>();
@@ -486,13 +516,106 @@ TEST_CASE("disconnect", "[CommandQueue]")
CHECK(!server.processCommands());
}
static constexpr uint32_t MAGIC_NUMBER = 0x50;
class TestFileAssetLoader : public FileAssetLoader
{
public:
virtual bool loadContents(FileAsset& asset,
Span<const uint8_t> inBandBytes,
Factory* factory)
{
return false;
}
uint32_t m_magicNumber = MAGIC_NUMBER;
};
TEST_CASE("load file with asset loader", "[CommandQueue]")
{
auto commandQueue = make_rcp<CommandQueue>();
std::unique_ptr<gpu::RenderContext> nullContext =
RenderContextNULL::MakeContext();
CommandServer server(commandQueue, nullContext.get());
rcp<TestFileAssetLoader> loader = make_rcp<TestFileAssetLoader>();
std::ifstream stream("assets/entry.riv", std::ios::binary);
FileHandle fileHandle = commandQueue->loadFile(
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}),
loader);
CHECK(fileHandle != RIVE_NULL_HANDLE);
server.processCommands();
{
auto aal = server.getFile(fileHandle)->testing_getAssetLoader();
CHECK(aal == loader.get());
CHECK(static_cast<TestFileAssetLoader*>(aal)->m_magicNumber ==
MAGIC_NUMBER);
}
std::ifstream hstream("assets/entry.riv", std::ios::binary);
rcp<TestFileAssetLoader> heapLoader(new TestFileAssetLoader);
FileHandle heapFileHandle = commandQueue->loadFile(
std::vector<uint8_t>(std::istreambuf_iterator<char>(hstream), {}),
heapLoader);
server.processCommands();
{
auto aal = server.getFile(fileHandle)->testing_getAssetLoader();
CHECK(aal == loader.get());
CHECK(static_cast<TestFileAssetLoader*>(aal)->m_magicNumber ==
MAGIC_NUMBER);
}
{
auto aal = server.getFile(heapFileHandle)->testing_getAssetLoader();
CHECK(aal == heapLoader.get());
CHECK(static_cast<TestFileAssetLoader*>(aal)->m_magicNumber ==
MAGIC_NUMBER);
}
rcp<TestFileAssetLoader> nullLoader = nullptr;
std::ifstream nstream("assets/entry.riv", std::ios::binary);
FileHandle nullFileHandle = commandQueue->loadFile(
std::vector<uint8_t>(std::istreambuf_iterator<char>(nstream), {}),
nullLoader);
server.processCommands();
{
auto aal = server.getFile(fileHandle)->testing_getAssetLoader();
CHECK(aal == loader.get());
CHECK(static_cast<TestFileAssetLoader*>(aal)->m_magicNumber ==
MAGIC_NUMBER);
}
{
auto aal = server.getFile(heapFileHandle)->testing_getAssetLoader();
CHECK(aal == heapLoader.get());
CHECK(static_cast<TestFileAssetLoader*>(aal)->m_magicNumber ==
MAGIC_NUMBER);
}
{
auto aal = server.getFile(nullFileHandle)->testing_getAssetLoader();
CHECK(aal == nullLoader.get());
}
// How to test this better ??
commandQueue->disconnect();
}
class TestFileListener : public CommandQueue::FileListener
{
public:
virtual void onArtboardsListed(
const FileHandle handle,
RequestId requestId,
std::vector<std::string> artboardNames) override
{
CHECK(requestId == m_requestId);
CHECK(handle == m_handle);
CHECK(artboardNames.size() == m_artboardNames.size());
for (auto i = 0; i < artboardNames.size(); ++i)
@@ -503,6 +626,7 @@ public:
m_hasCallback = true;
}
RequestId m_requestId;
FileHandle m_handle;
std::vector<std::string> m_artboardNames;
bool m_hasCallback = false;
@@ -523,7 +647,7 @@ TEST_CASE("listArtboard", "[CommandQueue]")
fileListener.m_artboardNames = {"New Artboard", "New Artboard"};
fileListener.m_handle = goodFile;
commandQueue->requestArtboardNames(goodFile);
fileListener.m_requestId = commandQueue->requestArtboardNames(goodFile);
wait_for_server(commandQueue.get());
@@ -553,8 +677,10 @@ class TestArtboardListener : public CommandQueue::ArtboardListener
public:
virtual void onStateMachinesListed(
const ArtboardHandle handle,
RequestId requestId,
std::vector<std::string> stateMachineNames) override
{
CHECK(requestId == m_requestId);
CHECK(handle == m_handle);
CHECK(stateMachineNames.size() == m_stateMachineNames.size());
for (auto i = 0; i < stateMachineNames.size(); ++i)
@@ -565,6 +691,7 @@ public:
m_hasCallback = true;
}
RequestId m_requestId;
ArtboardHandle m_handle;
std::vector<std::string> m_stateMachineNames;
bool m_hasCallback = false;
@@ -588,7 +715,8 @@ TEST_CASE("listStateMachine", "[CommandQueue]")
artboardListener.m_stateMachineNames = {"State Machine 1"};
artboardListener.m_handle = artboardHandle;
commandQueue->requestStateMachineNames(artboardHandle);
artboardListener.m_requestId =
commandQueue->requestStateMachineNames(artboardHandle);
wait_for_server(commandQueue.get());
@@ -604,7 +732,8 @@ TEST_CASE("listStateMachine", "[CommandQueue]")
artboardListener.m_handle = badArtbaord;
artboardListener.m_hasCallback = false;
commandQueue->requestStateMachineNames(badArtbaord);
artboardListener.m_requestId =
commandQueue->requestStateMachineNames(badArtbaord);
wait_for_server(commandQueue.get());
@@ -619,12 +748,15 @@ TEST_CASE("listStateMachine", "[CommandQueue]")
class DeleteFileListener : public CommandQueue::FileListener
{
public:
virtual void onFileDeleted(const FileHandle handle) override
virtual void onFileDeleted(const FileHandle handle,
RequestId requestId) override
{
CHECK(requestId == m_requestId);
CHECK(handle == m_handle);
m_hasCallback = true;
}
RequestId m_requestId;
FileHandle m_handle;
bool m_hasCallback = false;
};
@@ -632,12 +764,15 @@ public:
class DeleteArtboardListener : public CommandQueue::ArtboardListener
{
public:
virtual void onArtboardDeleted(const ArtboardHandle handle) override
virtual void onArtboardDeleted(const ArtboardHandle handle,
RequestId requestId) override
{
CHECK(requestId == m_requestId);
CHECK(handle == m_handle);
m_hasCallback = true;
}
RequestId m_requestId;
ArtboardHandle m_handle;
bool m_hasCallback = false;
};
@@ -645,12 +780,15 @@ public:
class DeleteStateMachineListener : public CommandQueue::StateMachineListener
{
public:
virtual void onStateMachineDeleted(const StateMachineHandle handle) override
virtual void onStateMachineDeleted(const StateMachineHandle handle,
RequestId requestId) override
{
CHECK(requestId == m_requestId);
CHECK(handle == m_handle);
m_hasCallback = true;
}
RequestId m_requestId;
StateMachineHandle m_handle;
bool m_hasCallback = false;
};
@@ -694,9 +832,10 @@ TEST_CASE("listenerDeleteCallbacks", "[CommandQueue]")
CHECK(!artboardListener.m_hasCallback);
CHECK(!stateMachineListener.m_hasCallback);
commandQueue->deleteStateMachine(stateMachineHandle);
commandQueue->deleteArtboard(artboardHandle);
commandQueue->deleteFile(goodFile);
stateMachineListener.m_requestId =
commandQueue->deleteStateMachine(stateMachineHandle);
artboardListener.m_requestId = commandQueue->deleteArtboard(artboardHandle);
fileListener.m_requestId = commandQueue->deleteFile(goodFile);
wait_for_server(commandQueue.get());
commandQueue->processMessages();
@@ -834,3 +973,22 @@ TEST_CASE("listenerLifeTimes", "[CommandQueue]")
// artboardListener and stateMachineListener as they should gracefully
// remove themselves from the commandQeueue even though the ref here is gone
}
TEST_CASE("empty test for code cove", "[CommandQueue]")
{
CommandQueue::FileListener fileL;
CommandQueue::ArtboardListener artboardL;
CommandQueue::StateMachineListener statemachineL;
std::vector<std::string> emptyVector;
fileL.onFileDeleted(0, 0);
fileL.onArtboardsListed(0, 0, emptyVector);
artboardL.onArtboardDeleted(0, 0);
artboardL.onStateMachinesListed(0, 0, emptyVector);
statemachineL.onStateMachineDeleted(0, 0);
CHECK(true);
}

View File

@@ -0,0 +1,45 @@
#ifdef WITH_RIVE_TEXT
#include "rive/text/cursor.hpp"
#include "rive/text/font_hb.hpp"
#include "rive/text/text_input.hpp"
#include "rive/text/text_input_drawable.hpp"
#include "rive/text/text_input_text.hpp"
#include "rive/text/text_input_cursor.hpp"
#include "rive/text/text_input_selection.hpp"
#include "rive/text/text_input_selected_text.hpp"
#include "rive_testing.hpp"
#include "utils/no_op_factory.hpp"
#include "rive_file_reader.hpp"
#include "utils/serializing_factory.hpp"
using namespace rive;
TEST_CASE("file with text input loads correctly", "[text_input]")
{
auto file = ReadRiveFile("assets/text_input.riv");
CHECK(file != nullptr);
auto artboard = file->artboardNamed("Text Input - Multiline");
CHECK(artboard->objects<TextInput>().size() == 1);
auto textInput = artboard->objects<TextInput>().first();
CHECK(textInput != nullptr);
CHECK(textInput->children<TextInputDrawable>().size() == 4);
CHECK(textInput->children<TextInputText>().size() == 1);
CHECK(textInput->children<TextInputSelection>().size() == 1);
CHECK(textInput->children<TextInputCursor>().size() == 1);
CHECK(textInput->children<TextInputSelectedText>().size() == 1);
}
TEST_CASE("file with text input renders correctly", "[silver]")
{
SerializingFactory silver;
auto file = ReadRiveFile("assets/text_input.riv", &silver);
auto artboard = file->artboardNamed("Text Input - Multiline");
silver.frameSize(artboard->width(), artboard->height());
artboard->advance(0.0f);
auto renderer = silver.makeRenderer();
artboard->draw(renderer.get());
CHECK(silver.matches("text_input"));
}
#endif

Binary file not shown.