blob: 85dd9b886a3e8e6f6209f04f067f73d95dc8de7e [file] [edit]
#ifndef _RIVE_SCRIPTED_TRANSITION_HPP_
#define _RIVE_SCRIPTED_TRANSITION_HPP_
#include "rive/generated/scripted/scripted_transition_base.hpp"
#include "rive/data_bind/data_bind_list_item_consumer.hpp"
#include "rive/refcnt.hpp"
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
namespace rive
{
class Component;
class NestedArtboard;
class ArtboardInstance;
class StateMachineInstance;
class File;
class ViewModelInstanceListItem;
/// A ScriptedTransition renders exactly one active child (Solo-style), chosen
/// by activeComponentId, but when the active child changes it hands the
/// outgoing (from) and incoming (to) children to a Transition-protocol script
/// which composites them over time (crossfade / slide / wipe / ...).
///
/// The switchable set is a COMBINED solo set: the design-time NestedArtboard
/// children (indices 0..D-1), followed by artboards instanced from a bound
/// ViewModel list via `listSource` (indices D..D+N-1). List artboards are
/// host-owned (not core objects, so they have no id) and are instanced lazily
/// on first selection. `activeComponentId` data-bound to a number selects by
/// this combined index.
///
/// While a transition is in flight both sources are composited by the script;
/// design children are excluded from the normal draw loop via the Hidden flag,
/// and list instances (which are never in the loop) are drawn by this
/// component.
class ScriptedTransition : public ScriptedTransitionBase,
public DataBindListItemConsumer
{
public:
~ScriptedTransition() override;
ScriptProtocol scriptProtocol() override
{
return ScriptProtocol::transition;
}
void activeComponentIdChanged() override;
void listSourceChanged() override;
StatusCode onAddedClean(CoreContext* context) override;
bool collapse(bool value) override;
bool advanceComponent(float elapsedSeconds,
AdvanceFlags flags = AdvanceFlags::Animate |
AdvanceFlags::NewFrame) override;
void draw(Renderer* renderer) override;
bool willDraw() override;
void update(ComponentDirt value) override;
Core* clone() const override;
// File is threaded in by File import (see file.cpp) so list artboards can
// be resolved from the file's artboard table.
void file(File* value) { m_file = value; }
File* file() const { return m_file; }
// DataBindListItemConsumer: receives the bound ViewModel list (via
// listSource) and (lazily) instances an artboard per item.
void updateList(std::vector<rcp<ViewModelInstanceListItem>>* list) override;
/// The design-time child resolved from activeComponentId (or null).
Component* activeComponent();
// Combined-index selectors (design children then list instances).
void updateByIndex(size_t index);
void updateByName(const std::string& name);
int getActiveChildIndex();
std::string getActiveChildName();
#ifdef WITH_RIVE_LAYOUT
void recollectOwningLayout();
#endif
private:
// A renderable source in the combined solo set: either a design-time
// NestedArtboard child, or a list-instanced ArtboardInstance. Exactly one
// of the two pointers is set (both null == empty/no selection).
struct TransitionSource
{
NestedArtboard* design = nullptr;
ArtboardInstance* instance = nullptr;
bool empty() const { return design == nullptr && instance == nullptr; }
bool operator==(const TransitionSource& o) const
{
return design == o.design && instance == o.instance;
}
bool operator!=(const TransitionSource& o) const
{
return !(*this == o);
}
};
void propagateCollapse(bool collapse);
void startTransition(const TransitionSource& from,
const TransitionSource& to);
void completeTransition();
void callChanged(const TransitionSource& from, const TransitionSource& to);
bool scriptReady() const;
bool scriptManagesTo() const;
#ifdef WITH_RIVE_SCRIPTING
// Resolve a source into the artboard the script draws and the world
// transform that places it. Design children carry their own transform;
// list instances are placed at this container's.
ScriptBackend::TransitionChildRef childRef(
const TransitionSource& source) const;
#endif
// Combined solo set + unified selection.
int designChildCount();
NestedArtboard* designChildAt(int index);
// Combined index (design children 0..D-1, then list instances D..) of a
// source, or -1 if not found. Used to derive the transition direction.
int sourceIndex(const TransitionSource& source);
TransitionSource intendedSource();
void applySelection();
void selectActiveSource(const TransitionSource& next);
// list instancing (lazy)
ArtboardInstance* ensureListInstance(int index);
Artboard* findListArtboard(const rcp<ViewModelInstanceListItem>& item);
void disposeListItem(const rcp<ViewModelInstanceListItem>& item);
void clearListInstances();
// Drop any selection/transition reference to a list instance that is about
// to be destroyed, so m_current/m_from/m_to never dangle.
void forgetInstance(ArtboardInstance* instance);
void advanceLiveInstance(const TransitionSource& source,
float elapsedSeconds,
AdvanceFlags flags,
bool& keepGoing);
// The child currently exposed as active (design or list).
TransitionSource m_current;
// The in-flight transition pair.
TransitionSource m_from;
TransitionSource m_to;
bool m_transitioning = false;
// Whether the script composites the incoming (to) source this transition.
bool m_manageTo = true;
// >= 0 => active is that list instance; -1 => active is a design child.
int m_activeListIndex = -1;
File* m_file = nullptr;
std::vector<rcp<ViewModelInstanceListItem>> m_listItems;
std::unordered_map<rcp<ViewModelInstanceListItem>,
std::unique_ptr<ArtboardInstance>>
m_listInstances;
// Keyed by the instance pointer for O(1) advance lookup. Destroyed before
// the owning ArtboardInstance (see disposeListItem / clearListInstances).
std::unordered_map<ArtboardInstance*, std::unique_ptr<StateMachineInstance>>
m_listStateMachines;
// Lazily-filled fast index -> instance (nullptr until ensureListInstance).
std::vector<ArtboardInstance*> m_listInstancesByIndex;
// Cache: viewModelId -> source artboard in the file.
std::unordered_map<uint32_t, Artboard*> m_artboardsMap;
};
} // namespace rive
#endif