mirror of
https://github.com/rive-app/rive-cpp.git
synced 2026-01-18 21:21:17 +01:00
sort hit shapes when draw order changes and stop propagation on hit s…
sort hit shapes when draw order changes and stop propagation on hit success Diffs= 8bca56dca sort hit shapes when draw order changes and stop propagation on hit s… (#6624) Co-authored-by: hernan <hernan@rive.app>
This commit is contained in:
@@ -1 +1 @@
|
||||
9d605a1feb39dcad526ac9dda6b53b547921c58d
|
||||
8bca56dcaffd0f563a91f628b0ed432eca71acb5
|
||||
|
||||
@@ -123,6 +123,17 @@
|
||||
"description": "List of selected animations",
|
||||
"runtime": false,
|
||||
"coop": false
|
||||
},
|
||||
"viewModelId": {
|
||||
"type": "Id",
|
||||
"typeRuntime": "uint",
|
||||
"initialValue": "Core.missingId",
|
||||
"initialValueRuntime": "-1",
|
||||
"key": {
|
||||
"int": 434,
|
||||
"string": "viewmodelid"
|
||||
},
|
||||
"description": "The view model attached to this artboard data context."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
#define _RIVE_NESTED_STATE_MACHINE_HPP_
|
||||
#include "rive/animation/state_machine_instance.hpp"
|
||||
#include "rive/generated/animation/nested_state_machine_base.hpp"
|
||||
#include "rive/hit_result.hpp"
|
||||
#include "rive/math/vec2d.hpp"
|
||||
#include <memory>
|
||||
|
||||
@@ -23,9 +24,10 @@ public:
|
||||
void initializeAnimation(ArtboardInstance*) override;
|
||||
StateMachineInstance* stateMachineInstance();
|
||||
|
||||
void pointerMove(Vec2D position);
|
||||
void pointerDown(Vec2D position);
|
||||
void pointerUp(Vec2D position);
|
||||
HitResult pointerMove(Vec2D position);
|
||||
HitResult pointerDown(Vec2D position);
|
||||
HitResult pointerUp(Vec2D position);
|
||||
HitResult pointerExit(Vec2D position);
|
||||
|
||||
void addNestedInput(NestedInput* input);
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <vector>
|
||||
#include "rive/animation/linear_animation_instance.hpp"
|
||||
#include "rive/core/field_types/core_callback_type.hpp"
|
||||
#include "rive/hit_result.hpp"
|
||||
#include "rive/listener_type.hpp"
|
||||
#include "rive/scene.hpp"
|
||||
|
||||
@@ -20,7 +21,7 @@ class SMINumber;
|
||||
class SMITrigger;
|
||||
class Shape;
|
||||
class StateMachineLayerInstance;
|
||||
class HitShape;
|
||||
class HitComponent;
|
||||
class NestedArtboard;
|
||||
class Event;
|
||||
class KeyedProperty;
|
||||
@@ -41,23 +42,24 @@ class StateMachineInstance : public Scene
|
||||
{
|
||||
friend class SMIInput;
|
||||
friend class KeyedProperty;
|
||||
friend class HitComponent;
|
||||
|
||||
private:
|
||||
void markNeedsAdvance();
|
||||
|
||||
/// Provide a hitListener if you want to process a down or an up for the pointer position
|
||||
/// too.
|
||||
void updateListeners(Vec2D position, ListenerType hitListener);
|
||||
HitResult updateListeners(Vec2D position, ListenerType hitListener);
|
||||
|
||||
template <typename SMType, typename InstType>
|
||||
InstType* getNamedInput(const std::string& name) const;
|
||||
void notifyEventListeners(std::vector<EventReport> events, NestedArtboard* source);
|
||||
void sortHitComponents();
|
||||
|
||||
public:
|
||||
StateMachineInstance(const StateMachine* machine, ArtboardInstance* instance);
|
||||
StateMachineInstance(StateMachineInstance const&) = delete;
|
||||
~StateMachineInstance() override;
|
||||
|
||||
void markNeedsAdvance();
|
||||
// Advance the state machine by the specified time. Returns true if the
|
||||
// state machine will continue to animate after this advance.
|
||||
bool advance(float seconds);
|
||||
@@ -88,9 +90,10 @@ public:
|
||||
|
||||
bool advanceAndApply(float secs) override;
|
||||
std::string name() const override;
|
||||
void pointerMove(Vec2D position) override;
|
||||
void pointerDown(Vec2D position) override;
|
||||
void pointerUp(Vec2D position) override;
|
||||
HitResult pointerMove(Vec2D position) override;
|
||||
HitResult pointerDown(Vec2D position) override;
|
||||
HitResult pointerUp(Vec2D position) override;
|
||||
HitResult pointerExit(Vec2D position) override;
|
||||
|
||||
float durationSeconds() const override { return -1; }
|
||||
Loop loop() const override { return Loop::oneShot; }
|
||||
@@ -125,8 +128,7 @@ private:
|
||||
std::vector<SMIInput*> m_inputInstances; // we own each pointer
|
||||
size_t m_layerCount;
|
||||
StateMachineLayerInstance* m_layers;
|
||||
std::vector<std::unique_ptr<HitShape>> m_hitShapes;
|
||||
std::vector<NestedArtboard*> m_hitNestedArtboards;
|
||||
std::vector<std::unique_ptr<HitComponent>> m_hitComponents;
|
||||
StateMachineInstance* m_parentStateMachineInstance = nullptr;
|
||||
NestedArtboard* m_parentNestedArtboard = nullptr;
|
||||
};
|
||||
|
||||
@@ -49,6 +49,7 @@ private:
|
||||
std::vector<NestedArtboard*> m_NestedArtboards;
|
||||
std::vector<Joystick*> m_Joysticks;
|
||||
bool m_JoysticksApplyBeforeUpdate = true;
|
||||
bool m_HasChangedDrawOrderInLastUpdate = false;
|
||||
|
||||
unsigned int m_DirtDepth = 0;
|
||||
rcp<RenderPath> m_BackgroundPath;
|
||||
@@ -100,6 +101,8 @@ public:
|
||||
void onDirty(ComponentDirt dirt) override;
|
||||
|
||||
bool advance(double elapsedSeconds);
|
||||
bool hasChangedDrawOrderInLastUpdate() { return m_HasChangedDrawOrderInLastUpdate; };
|
||||
Drawable* firstDrawable() { return m_FirstDrawable; };
|
||||
|
||||
enum class DrawOption
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "rive/hit_info.hpp"
|
||||
#include "rive/renderer.hpp"
|
||||
#include "rive/clip_result.hpp"
|
||||
#include "rive/drawable_flag.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace rive
|
||||
@@ -15,6 +16,7 @@ class DrawRules;
|
||||
class Drawable : public DrawableBase
|
||||
{
|
||||
friend class Artboard;
|
||||
friend class StateMachineInstance;
|
||||
|
||||
private:
|
||||
std::vector<ClippingShape*> m_ClippingShapes;
|
||||
@@ -34,9 +36,15 @@ public:
|
||||
|
||||
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 || hasDirt(ComponentDirt::Collapsed);
|
||||
return (static_cast<DrawableFlag>(drawableFlags()) & DrawableFlag::Hidden) ==
|
||||
DrawableFlag::Hidden ||
|
||||
hasDirt(ComponentDirt::Collapsed);
|
||||
}
|
||||
|
||||
inline bool isTargetOpaque() const
|
||||
{
|
||||
return (static_cast<DrawableFlag>(drawableFlags()) & DrawableFlag::Opaque) ==
|
||||
DrawableFlag::Opaque;
|
||||
}
|
||||
};
|
||||
} // namespace rive
|
||||
|
||||
26
include/rive/drawable_flag.hpp
Normal file
26
include/rive/drawable_flag.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef _RIVE_DRAWABLE_FLAGS_HPP_
|
||||
#define _RIVE_DRAWABLE_FLAGS_HPP_
|
||||
|
||||
#include "rive/enum_bitset.hpp"
|
||||
|
||||
namespace rive
|
||||
{
|
||||
enum class DrawableFlag : unsigned short
|
||||
{
|
||||
None = 0,
|
||||
|
||||
/// Whether the component should be drawn
|
||||
Hidden = 1 << 0,
|
||||
|
||||
/// Editor only
|
||||
Locked = 1 << 1,
|
||||
|
||||
/// Editor only
|
||||
Disconnected = 1 << 2,
|
||||
|
||||
/// Whether this Component lets hit events pass through to components behind it
|
||||
Opaque = 1 << 3,
|
||||
};
|
||||
RIVE_MAKE_ENUM_BITSET(DrawableFlag)
|
||||
} // namespace rive
|
||||
#endif
|
||||
13
include/rive/hit_result.hpp
Normal file
13
include/rive/hit_result.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef _RIVE_HIT_RESULT_HPP_
|
||||
#define _RIVE_HIT_RESULT_HPP_
|
||||
|
||||
namespace rive
|
||||
{
|
||||
enum class HitResult : uint8_t
|
||||
{
|
||||
none,
|
||||
hit,
|
||||
hitOpaque,
|
||||
};
|
||||
} // namespace rive
|
||||
#endif
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "rive/math/vec2d.hpp"
|
||||
#include "rive/animation/keyed_callback_reporter.hpp"
|
||||
#include "rive/core/field_types/core_callback_type.hpp"
|
||||
#include "rive/hit_result.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace rive
|
||||
@@ -46,9 +47,10 @@ public:
|
||||
|
||||
void draw(Renderer*);
|
||||
|
||||
virtual void pointerDown(Vec2D);
|
||||
virtual void pointerMove(Vec2D);
|
||||
virtual void pointerUp(Vec2D);
|
||||
virtual HitResult pointerDown(Vec2D);
|
||||
virtual HitResult pointerMove(Vec2D);
|
||||
virtual HitResult pointerUp(Vec2D);
|
||||
virtual HitResult pointerExit(Vec2D);
|
||||
|
||||
virtual size_t inputCount() const;
|
||||
virtual SMIInput* input(size_t index) const;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "rive/animation/nested_number.hpp"
|
||||
#include "rive/animation/nested_state_machine.hpp"
|
||||
#include "rive/animation/state_machine_instance.hpp"
|
||||
#include "rive/hit_result.hpp"
|
||||
|
||||
using namespace rive;
|
||||
|
||||
@@ -37,28 +38,40 @@ StateMachineInstance* NestedStateMachine::stateMachineInstance()
|
||||
return m_StateMachineInstance.get();
|
||||
}
|
||||
|
||||
void NestedStateMachine::pointerMove(Vec2D position)
|
||||
HitResult NestedStateMachine::pointerMove(Vec2D position)
|
||||
{
|
||||
if (m_StateMachineInstance != nullptr)
|
||||
{
|
||||
m_StateMachineInstance->pointerMove(position);
|
||||
return m_StateMachineInstance->pointerMove(position);
|
||||
}
|
||||
return HitResult::none;
|
||||
}
|
||||
|
||||
void NestedStateMachine::pointerDown(Vec2D position)
|
||||
HitResult NestedStateMachine::pointerDown(Vec2D position)
|
||||
{
|
||||
if (m_StateMachineInstance != nullptr)
|
||||
{
|
||||
m_StateMachineInstance->pointerDown(position);
|
||||
return m_StateMachineInstance->pointerDown(position);
|
||||
}
|
||||
return HitResult::none;
|
||||
}
|
||||
|
||||
void NestedStateMachine::pointerUp(Vec2D position)
|
||||
HitResult NestedStateMachine::pointerUp(Vec2D position)
|
||||
{
|
||||
if (m_StateMachineInstance != nullptr)
|
||||
{
|
||||
m_StateMachineInstance->pointerUp(position);
|
||||
return m_StateMachineInstance->pointerUp(position);
|
||||
}
|
||||
return HitResult::none;
|
||||
}
|
||||
|
||||
HitResult NestedStateMachine::pointerExit(Vec2D position)
|
||||
{
|
||||
if (m_StateMachineInstance != nullptr)
|
||||
{
|
||||
return m_StateMachineInstance->pointerExit(position);
|
||||
}
|
||||
return HitResult::none;
|
||||
}
|
||||
|
||||
void NestedStateMachine::addNestedInput(NestedInput* input) { m_nestedInputs.push_back(input); }
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "rive/animation/state_transition.hpp"
|
||||
#include "rive/animation/transition_condition.hpp"
|
||||
#include "rive/animation/state_machine_fire_event.hpp"
|
||||
#include "rive/hit_result.hpp"
|
||||
#include "rive/math/aabb.hpp"
|
||||
#include "rive/math/hit_test.hpp"
|
||||
#include "rive/nested_animation.hpp"
|
||||
@@ -318,49 +319,48 @@ private:
|
||||
float m_holdTime = 0.0f;
|
||||
};
|
||||
|
||||
class HitComponent
|
||||
{
|
||||
public:
|
||||
Component* component() const { return m_component; }
|
||||
HitComponent(Component* component, StateMachineInstance* stateMachineInstance) :
|
||||
m_component(component), m_stateMachineInstance(stateMachineInstance)
|
||||
{}
|
||||
virtual ~HitComponent(){};
|
||||
virtual HitResult processEvent(Vec2D position, ListenerType hitType, bool canHit) = 0;
|
||||
|
||||
protected:
|
||||
Component* m_component;
|
||||
StateMachineInstance* m_stateMachineInstance;
|
||||
};
|
||||
|
||||
/// Representation of a Shape from the Artboard Instance and all the listeners it
|
||||
/// triggers. Allows tracking hover and performing hit detection only once on
|
||||
/// shapes that trigger multiple listeners.
|
||||
class HitShape
|
||||
class HitShape : public HitComponent
|
||||
{
|
||||
public:
|
||||
Shape* shape() const { return m_shape; }
|
||||
HitShape(Shape* shape) : m_shape(shape) {}
|
||||
HitShape(Component* shape, StateMachineInstance* stateMachineInstance) :
|
||||
HitComponent(shape, stateMachineInstance)
|
||||
{}
|
||||
~HitShape() {}
|
||||
bool isHovered = false;
|
||||
float hitRadius = 2;
|
||||
std::vector<const StateMachineListener*> listeners;
|
||||
|
||||
private:
|
||||
Shape* m_shape;
|
||||
};
|
||||
} // namespace rive
|
||||
|
||||
void StateMachineInstance::updateListeners(Vec2D position, ListenerType hitType)
|
||||
{
|
||||
if (m_artboardInstance->frameOrigin())
|
||||
HitResult processEvent(Vec2D position, ListenerType hitType, bool canHit) override
|
||||
{
|
||||
position -= Vec2D(m_artboardInstance->originX() * m_artboardInstance->width(),
|
||||
m_artboardInstance->originY() * m_artboardInstance->height());
|
||||
}
|
||||
auto shape = m_component->as<Shape>();
|
||||
auto hitArea = AABB(position.x - hitRadius,
|
||||
position.y - hitRadius,
|
||||
position.x + hitRadius,
|
||||
position.y + hitRadius)
|
||||
.round();
|
||||
bool isOver = canHit ? shape->hitTest(hitArea) : false;
|
||||
bool hoverChange = isHovered != isOver;
|
||||
isHovered = isOver;
|
||||
|
||||
const float hitRadius = 2;
|
||||
auto hitArea = AABB(position.x - hitRadius,
|
||||
position.y - hitRadius,
|
||||
position.x + hitRadius,
|
||||
position.y + hitRadius)
|
||||
.round();
|
||||
|
||||
for (const auto& hitShape : m_hitShapes)
|
||||
{
|
||||
|
||||
// TODO: quick reject.
|
||||
|
||||
bool isOver = hitShape->shape()->hitTest(hitArea);
|
||||
|
||||
bool hoverChange = hitShape->isHovered != isOver;
|
||||
hitShape->isHovered = isOver;
|
||||
|
||||
// iterate all listeners associated with this hit shape
|
||||
for (auto listener : hitShape->listeners)
|
||||
// // iterate all listeners associated with this hit shape
|
||||
for (auto listener : listeners)
|
||||
{
|
||||
// Always update hover states regardless of which specific listener type
|
||||
// we're trying to trigger.
|
||||
@@ -368,38 +368,45 @@ void StateMachineInstance::updateListeners(Vec2D position, ListenerType hitType)
|
||||
{
|
||||
if (isOver && listener->listenerType() == ListenerType::enter)
|
||||
{
|
||||
listener->performChanges(this, position);
|
||||
markNeedsAdvance();
|
||||
listener->performChanges(m_stateMachineInstance, position);
|
||||
m_stateMachineInstance->markNeedsAdvance();
|
||||
}
|
||||
else if (!isOver && listener->listenerType() == ListenerType::exit)
|
||||
{
|
||||
listener->performChanges(this, position);
|
||||
markNeedsAdvance();
|
||||
listener->performChanges(m_stateMachineInstance, position);
|
||||
m_stateMachineInstance->markNeedsAdvance();
|
||||
}
|
||||
}
|
||||
if (isOver && hitType == listener->listenerType())
|
||||
{
|
||||
listener->performChanges(this, position);
|
||||
markNeedsAdvance();
|
||||
listener->performChanges(m_stateMachineInstance, position);
|
||||
m_stateMachineInstance->markNeedsAdvance();
|
||||
}
|
||||
}
|
||||
return isOver ? shape->isTargetOpaque() ? HitResult::hitOpaque : HitResult::hit
|
||||
: HitResult::none;
|
||||
}
|
||||
|
||||
// TODO: store a hittable abstraction for HitShape and NestedArtboard that
|
||||
// can be sorted by drawOrder so they can be iterated in one loop and early
|
||||
// out if any hit stops propagation (also require the ability to mark a hit
|
||||
// as able to stop propagation)
|
||||
for (auto nestedArtboard : m_hitNestedArtboards)
|
||||
};
|
||||
class HitNestedArtboard : public HitComponent
|
||||
{
|
||||
public:
|
||||
HitNestedArtboard(Component* nestedArtboard, StateMachineInstance* stateMachineInstance) :
|
||||
HitComponent(nestedArtboard, stateMachineInstance)
|
||||
{}
|
||||
~HitNestedArtboard() {}
|
||||
HitResult processEvent(Vec2D position, ListenerType hitType, bool canHit) override
|
||||
{
|
||||
auto nestedArtboard = m_component->as<NestedArtboard>();
|
||||
HitResult hitResult = HitResult::none;
|
||||
if (nestedArtboard->isCollapsed())
|
||||
{
|
||||
continue;
|
||||
return hitResult;
|
||||
}
|
||||
Vec2D nestedPosition;
|
||||
if (!nestedArtboard->worldToLocal(position, &nestedPosition))
|
||||
{
|
||||
// Mounted artboard isn't ready or has a 0 scale transform.
|
||||
continue;
|
||||
return hitResult;
|
||||
}
|
||||
|
||||
for (auto nestedAnimation : nestedArtboard->nestedAnimations())
|
||||
@@ -407,38 +414,90 @@ void StateMachineInstance::updateListeners(Vec2D position, ListenerType hitType)
|
||||
if (nestedAnimation->is<NestedStateMachine>())
|
||||
{
|
||||
auto nestedStateMachine = nestedAnimation->as<NestedStateMachine>();
|
||||
switch (hitType)
|
||||
if (canHit)
|
||||
{
|
||||
case ListenerType::down:
|
||||
nestedStateMachine->pointerDown(nestedPosition);
|
||||
break;
|
||||
case ListenerType::up:
|
||||
nestedStateMachine->pointerUp(nestedPosition);
|
||||
break;
|
||||
case ListenerType::move:
|
||||
nestedStateMachine->pointerMove(nestedPosition);
|
||||
break;
|
||||
case ListenerType::enter:
|
||||
case ListenerType::exit:
|
||||
case ListenerType::event:
|
||||
break;
|
||||
switch (hitType)
|
||||
{
|
||||
case ListenerType::down:
|
||||
hitResult = nestedStateMachine->pointerDown(nestedPosition);
|
||||
break;
|
||||
case ListenerType::up:
|
||||
hitResult = nestedStateMachine->pointerUp(nestedPosition);
|
||||
break;
|
||||
case ListenerType::move:
|
||||
hitResult = nestedStateMachine->pointerMove(nestedPosition);
|
||||
break;
|
||||
case ListenerType::enter:
|
||||
case ListenerType::exit:
|
||||
case ListenerType::event:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (hitType)
|
||||
{
|
||||
case ListenerType::down:
|
||||
case ListenerType::up:
|
||||
case ListenerType::move:
|
||||
nestedStateMachine->pointerExit(nestedPosition);
|
||||
break;
|
||||
case ListenerType::enter:
|
||||
case ListenerType::exit:
|
||||
case ListenerType::event:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return hitResult;
|
||||
}
|
||||
};
|
||||
} // namespace rive
|
||||
|
||||
HitResult StateMachineInstance::updateListeners(Vec2D position, ListenerType hitType)
|
||||
{
|
||||
if (m_artboardInstance->frameOrigin())
|
||||
{
|
||||
position -= Vec2D(m_artboardInstance->originX() * m_artboardInstance->width(),
|
||||
m_artboardInstance->originY() * m_artboardInstance->height());
|
||||
}
|
||||
|
||||
bool hitSomething = false;
|
||||
bool hitOpaque = false;
|
||||
for (const auto& hitShape : m_hitComponents)
|
||||
{
|
||||
|
||||
// TODO: quick reject.
|
||||
|
||||
HitResult hitResult = hitShape->processEvent(position, hitType, !hitOpaque);
|
||||
if (hitResult != HitResult::none)
|
||||
{
|
||||
hitSomething = true;
|
||||
if (hitResult == HitResult::hitOpaque)
|
||||
{
|
||||
hitOpaque = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hitSomething ? hitOpaque ? HitResult::hitOpaque : HitResult::hit : HitResult::none;
|
||||
}
|
||||
|
||||
void StateMachineInstance::pointerMove(Vec2D position)
|
||||
HitResult StateMachineInstance::pointerMove(Vec2D position)
|
||||
{
|
||||
updateListeners(position, ListenerType::move);
|
||||
return updateListeners(position, ListenerType::move);
|
||||
}
|
||||
void StateMachineInstance::pointerDown(Vec2D position)
|
||||
HitResult StateMachineInstance::pointerDown(Vec2D position)
|
||||
{
|
||||
updateListeners(position, ListenerType::down);
|
||||
return updateListeners(position, ListenerType::down);
|
||||
}
|
||||
void StateMachineInstance::pointerUp(Vec2D position)
|
||||
HitResult StateMachineInstance::pointerUp(Vec2D position)
|
||||
{
|
||||
updateListeners(position, ListenerType::up);
|
||||
return updateListeners(position, ListenerType::up);
|
||||
}
|
||||
HitResult StateMachineInstance::pointerExit(Vec2D position)
|
||||
{
|
||||
return updateListeners(position, ListenerType::exit);
|
||||
}
|
||||
|
||||
StateMachineInstance::StateMachineInstance(const StateMachine* machine,
|
||||
@@ -497,9 +556,9 @@ StateMachineInstance::StateMachineInstance(const StateMachine* machine,
|
||||
auto shape = m_artboardInstance->resolve(id);
|
||||
if (shape != nullptr && shape->is<Shape>())
|
||||
{
|
||||
auto hs = rivestd::make_unique<HitShape>(shape->as<Shape>());
|
||||
auto hs = rivestd::make_unique<HitShape>(shape->as<Component>(), this);
|
||||
hitShapeLookup[id] = hitShape = hs.get();
|
||||
m_hitShapes.push_back(std::move(hs));
|
||||
m_hitComponents.push_back(std::move(hs));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -519,7 +578,11 @@ StateMachineInstance::StateMachineInstance(const StateMachine* machine,
|
||||
{
|
||||
if (nestedArtboard->hasNestedStateMachines())
|
||||
{
|
||||
m_hitNestedArtboards.push_back(nestedArtboard);
|
||||
|
||||
auto hn =
|
||||
rivestd::make_unique<HitNestedArtboard>(nestedArtboard->as<Component>(), this);
|
||||
m_hitComponents.push_back(std::move(hn));
|
||||
|
||||
for (auto animation : nestedArtboard->nestedAnimations())
|
||||
{
|
||||
if (animation->is<NestedStateMachine>())
|
||||
@@ -534,6 +597,7 @@ StateMachineInstance::StateMachineInstance(const StateMachine* machine,
|
||||
}
|
||||
}
|
||||
}
|
||||
sortHitComponents();
|
||||
}
|
||||
|
||||
StateMachineInstance::~StateMachineInstance()
|
||||
@@ -545,8 +609,47 @@ StateMachineInstance::~StateMachineInstance()
|
||||
delete[] m_layers;
|
||||
}
|
||||
|
||||
void StateMachineInstance::sortHitComponents()
|
||||
{
|
||||
Drawable* last = m_artboardInstance->firstDrawable();
|
||||
if (last)
|
||||
{
|
||||
// walk to the end, so we can visit in reverse-order
|
||||
while (last->prev)
|
||||
{
|
||||
last = last->prev;
|
||||
}
|
||||
}
|
||||
auto hitShapesCount = m_hitComponents.size();
|
||||
auto currentSortedIndex = 0;
|
||||
for (auto drawable = last; drawable; drawable = drawable->next)
|
||||
{
|
||||
for (size_t i = currentSortedIndex; i < hitShapesCount; i++)
|
||||
{
|
||||
if (m_hitComponents[i]->component() == drawable)
|
||||
{
|
||||
if (currentSortedIndex != i)
|
||||
{
|
||||
std::iter_swap(m_hitComponents.begin() + currentSortedIndex,
|
||||
m_hitComponents.begin() + i);
|
||||
}
|
||||
currentSortedIndex++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (currentSortedIndex == hitShapesCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool StateMachineInstance::advance(float seconds)
|
||||
{
|
||||
if (m_artboardInstance->hasChangedDrawOrderInLastUpdate())
|
||||
{
|
||||
sortHitComponents();
|
||||
}
|
||||
this->notifyEventListeners(m_reportedEvents, nullptr);
|
||||
m_reportedEvents.clear();
|
||||
m_needsAdvance = false;
|
||||
|
||||
@@ -272,6 +272,7 @@ StatusCode Artboard::initialize()
|
||||
|
||||
void Artboard::sortDrawOrder()
|
||||
{
|
||||
m_HasChangedDrawOrderInLastUpdate = true;
|
||||
for (auto target : m_DrawTargets)
|
||||
{
|
||||
target->first = target->last = nullptr;
|
||||
@@ -486,6 +487,7 @@ bool Artboard::updateComponents()
|
||||
|
||||
bool Artboard::advance(double elapsedSeconds)
|
||||
{
|
||||
m_HasChangedDrawOrderInLastUpdate = false;
|
||||
if (m_JoysticksApplyBeforeUpdate)
|
||||
{
|
||||
for (auto joystick : m_Joysticks)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "rive/artboard.hpp"
|
||||
#include "rive/hit_result.hpp"
|
||||
#include "rive/scene.hpp"
|
||||
#include "rive/generated/core_registry.hpp"
|
||||
using namespace rive;
|
||||
@@ -14,9 +15,10 @@ float Scene::height() const { return m_artboardInstance->height(); }
|
||||
|
||||
void Scene::draw(Renderer* renderer) { m_artboardInstance->draw(renderer); }
|
||||
|
||||
void Scene::pointerDown(Vec2D) {}
|
||||
void Scene::pointerMove(Vec2D) {}
|
||||
void Scene::pointerUp(Vec2D) {}
|
||||
HitResult Scene::pointerDown(Vec2D) { return HitResult::none; }
|
||||
HitResult Scene::pointerMove(Vec2D) { return HitResult::none; }
|
||||
HitResult Scene::pointerUp(Vec2D) { return HitResult::none; }
|
||||
HitResult Scene::pointerExit(Vec2D) { return HitResult::none; }
|
||||
|
||||
size_t Scene::inputCount() const { return 0; }
|
||||
SMIInput* Scene::input(size_t index) const { return nullptr; }
|
||||
|
||||
BIN
test/assets/opaque_hit_test.riv
Normal file
BIN
test/assets/opaque_hit_test.riv
Normal file
Binary file not shown.
@@ -4,6 +4,11 @@
|
||||
|
||||
#include <rive/math/aabb.hpp>
|
||||
#include <rive/math/hit_test.hpp>
|
||||
#include <rive/nested_artboard.hpp>
|
||||
#include <rive/animation/state_machine_instance.hpp>
|
||||
#include <rive/animation/state_machine_input_instance.hpp>
|
||||
#include <rive/animation/nested_state_machine.hpp>
|
||||
#include "rive_file_reader.hpp"
|
||||
|
||||
#include <catch.hpp>
|
||||
#include <cstdio>
|
||||
@@ -57,3 +62,121 @@ TEST_CASE("hittest-mesh", "[hittest]")
|
||||
};
|
||||
REQUIRE(HitTester::testMesh(area, make_span(verts, 3), make_span(indices, 3)));
|
||||
}
|
||||
|
||||
TEST_CASE("hit test on opaque target", "[hittest]")
|
||||
{
|
||||
// This artboard has two rects of size 200 x 200, "red-activate" at [0, 0, 200, 200]
|
||||
// and "green-activate" at [0, 100, 200, 300]
|
||||
// "red-activate" is above "green-activate" in drawing order
|
||||
// Both targets are set as opaque for its listeners
|
||||
// "red-activate" sets "toGreen" to false
|
||||
// "green-activate" sets "toGreen" to true
|
||||
// There is also a "gray-activate" above the other 2 that is not opaque so events should
|
||||
// traverse through the other targets
|
||||
auto file = ReadRiveFile("../../test/assets/opaque_hit_test.riv");
|
||||
|
||||
auto artboard = file->artboard("main");
|
||||
auto artboardInstance = artboard->instance();
|
||||
auto stateMachine = artboard->stateMachine("main-state-machine");
|
||||
|
||||
REQUIRE(artboardInstance != nullptr);
|
||||
REQUIRE(artboardInstance->stateMachineCount() == 1);
|
||||
|
||||
REQUIRE(stateMachine != nullptr);
|
||||
|
||||
rive::StateMachineInstance* stateMachineInstance =
|
||||
new rive::StateMachineInstance(stateMachine, artboardInstance.get());
|
||||
|
||||
stateMachineInstance->advance(0.0f);
|
||||
artboardInstance->advance(0.0f);
|
||||
REQUIRE(stateMachineInstance->needsAdvance() == true);
|
||||
stateMachineInstance->advance(0.0f);
|
||||
|
||||
auto toGreenToggle = stateMachineInstance->getBool("toGreen");
|
||||
REQUIRE(toGreenToggle != nullptr);
|
||||
auto grayToggle = stateMachineInstance->getBool("grayToggle");
|
||||
REQUIRE(grayToggle != nullptr);
|
||||
|
||||
stateMachineInstance->pointerDown(rive::Vec2D(100.0f, 50.0f));
|
||||
// "gray-activate" is clicked
|
||||
REQUIRE(grayToggle->value() == true);
|
||||
// Pointer only over "red-activate"
|
||||
REQUIRE(toGreenToggle->value() == false);
|
||||
|
||||
stateMachineInstance->pointerDown(rive::Vec2D(100.0f, 250.0f));
|
||||
// "gray-activate" is clicked
|
||||
REQUIRE(grayToggle->value() == false);
|
||||
// Pointer over "green-activate"
|
||||
REQUIRE(toGreenToggle->value() == true);
|
||||
|
||||
stateMachineInstance->pointerDown(rive::Vec2D(100.0f, 110.0f));
|
||||
// "gray-activate" is clicked
|
||||
REQUIRE(grayToggle->value() == true);
|
||||
// Pointer over "red-activate" and "green-activate", but "red-activate" is opaque and above
|
||||
// so green activate does not trigger
|
||||
REQUIRE(toGreenToggle->value() == false);
|
||||
delete stateMachineInstance;
|
||||
}
|
||||
|
||||
TEST_CASE("hit test on opaque nested artboard", "[hittest]")
|
||||
{
|
||||
// This artboard (300x300) has a main rect at [0, 0, 300, 300]
|
||||
// this rect has a listener that toggles "second-gray-toggle"
|
||||
// and a nested artboard at [0, 0, 150, 150]
|
||||
// the nested artboard and the rect have opaque targets
|
||||
auto file = ReadRiveFile("../../test/assets/opaque_hit_test.riv");
|
||||
|
||||
auto artboard = file->artboard("second");
|
||||
auto artboardInstance = artboard->instance();
|
||||
auto stateMachine = artboard->stateMachine("second-state-machine");
|
||||
|
||||
REQUIRE(artboardInstance != nullptr);
|
||||
REQUIRE(artboardInstance->stateMachineCount() == 1);
|
||||
|
||||
REQUIRE(stateMachine != nullptr);
|
||||
|
||||
rive::StateMachineInstance* stateMachineInstance =
|
||||
new rive::StateMachineInstance(stateMachine, artboardInstance.get());
|
||||
|
||||
auto nestedArtboard =
|
||||
stateMachineInstance->artboard()->find<rive::NestedArtboard>("second-nested");
|
||||
REQUIRE(nestedArtboard != nullptr);
|
||||
auto nestedArtboardStateMachine =
|
||||
nestedArtboard->nestedAnimations()[0]->as<NestedStateMachine>();
|
||||
REQUIRE(nestedArtboardStateMachine != nullptr);
|
||||
auto nestedArtboardStateMachineInstance = nestedArtboardStateMachine->stateMachineInstance();
|
||||
|
||||
auto secondNestedBoolTarget = nestedArtboardStateMachineInstance->getBool("bool-target");
|
||||
REQUIRE(secondNestedBoolTarget != nullptr);
|
||||
|
||||
artboardInstance->advance(0.0f);
|
||||
stateMachineInstance->advanceAndApply(0.0f);
|
||||
|
||||
REQUIRE(secondNestedBoolTarget->value() == false);
|
||||
|
||||
auto secondGrayToggle = stateMachineInstance->getBool("second-gray-toggle");
|
||||
REQUIRE(secondGrayToggle != nullptr);
|
||||
|
||||
stateMachineInstance->pointerDown(rive::Vec2D(100.0f, 250.0f));
|
||||
// toggle changes value because it is not under an opaque nested artboard
|
||||
REQUIRE(secondGrayToggle->value() == true);
|
||||
|
||||
stateMachineInstance->pointerDown(rive::Vec2D(100.0f, 50.0f));
|
||||
// toggle does not change because it is under an opaque nested artboard
|
||||
REQUIRE(secondGrayToggle->value() == true);
|
||||
|
||||
// nested toggle changes because it's on top of shape
|
||||
REQUIRE(secondNestedBoolTarget->value() == true);
|
||||
|
||||
// A timeline switches draw order and the nested artboard is now below the rect
|
||||
stateMachineInstance->advanceAndApply(1.0f);
|
||||
stateMachineInstance->advance(0.0f);
|
||||
|
||||
stateMachineInstance->pointerDown(rive::Vec2D(100.0f, 50.0f));
|
||||
// So now the pointer down is captured by the rect
|
||||
REQUIRE(secondGrayToggle->value() == false);
|
||||
|
||||
// nested toggle does not change because it's below shape
|
||||
REQUIRE(secondNestedBoolTarget->value() == true);
|
||||
delete stateMachineInstance;
|
||||
}
|
||||
Reference in New Issue
Block a user