using System.Collections.Generic;
using Rive.Utils;
using UnityEngine;
namespace Rive.Components.Utilities
{
///
/// The is responsible for managing the loading Rive file, artboard, state machine. It handles the core logic for loading and unloading artboards from Rive Files.
///
internal class ArtboardLoadHelper
{
public enum LoadErrorType
{
InvalidArguments = 0,
ArtboardNotFound = 1,
StateMachineNotFound = 2,
}
public readonly struct LoadErrorEventData
{
public LoadErrorType ErrorType { get; }
public string Message { get; }
public LoadErrorEventData(LoadErrorType errorType, string message = null)
{
ErrorType = errorType;
Message = message;
}
}
internal struct DataBindingLoadInfo
{
public RiveWidget.DataBindingMode BindingMode { get; }
public string InstanceName { get; }
public DataBindingLoadInfo(RiveWidget.DataBindingMode bindingMode, string instanceName)
{
BindingMode = bindingMode;
InstanceName = instanceName;
}
}
internal struct LoadResult
{
public bool Success { get; }
public LoadErrorEventData ErrorData { get; }
public LoadResult(bool success, LoadErrorEventData errorData = default)
{
Success = success;
ErrorData = errorData;
}
}
private Artboard m_artboard;
private StateMachine m_stateMachine;
private File m_file;
private ArtboardRenderObject m_renderObject;
private float originalArtboardWidth;
private float originalArtboardHeight;
private bool m_isLoaded = false;
private List m_reportedEvents = new List();
public Artboard Artboard => m_artboard;
public StateMachine StateMachine => m_stateMachine;
public File File { get => m_file; }
public ArtboardRenderObject RenderObject => m_renderObject;
public float OriginalArtboardWidth => originalArtboardWidth;
public float OriginalArtboardHeight => originalArtboardHeight;
public bool IsLoaded
{
get => m_isLoaded;
private set => m_isLoaded = value;
}
public delegate void RiveEventDelegate(ReportedEvent report);
public delegate void RiveLoadErrorDelegate(LoadErrorEventData eventData);
public delegate void RiveLoadCompleteDelegate();
public delegate void RiveRenderStateChangeDelegate();
public event RiveEventDelegate OnRiveEventReported;
public LoadResult Load(File file, Fit fit, Alignment alignment, string artboardName, string stateMachineName, float scaleFactor, DataBindingLoadInfo bindingInfo)
{
CleanUpBeforeLoad();
if (file == null)
{
IsLoaded = false;
return new LoadResult(false, new LoadErrorEventData(LoadErrorType.InvalidArguments, "File is null"));
}
m_file = file;
m_artboard = string.IsNullOrEmpty(artboardName) ? m_file.Artboard(0) : m_file.Artboard(artboardName);
if (m_artboard == null)
{
IsLoaded = false;
return new LoadResult(false, new LoadErrorEventData(LoadErrorType.ArtboardNotFound, $"Artboard {artboardName} not found in file"));
}
originalArtboardWidth = m_artboard.Width;
originalArtboardHeight = m_artboard.Height;
m_stateMachine = string.IsNullOrEmpty(stateMachineName) ? m_artboard.StateMachine(0) : m_artboard.StateMachine(stateMachineName);
if (m_stateMachine == null)
{
IsLoaded = false;
return new LoadResult(false, new LoadErrorEventData(LoadErrorType.StateMachineNotFound, $"State machine {stateMachineName} not found in artboard {artboardName}"));
}
var viewModelInstance = GetVmInstanceToApply(bindingInfo.BindingMode, m_artboard, bindingInfo.InstanceName);
if (viewModelInstance != null)
{
m_stateMachine.BindViewModelInstance(viewModelInstance);
}
m_renderObject = CreateRenderObject(m_artboard, alignment, fit, scaleFactor);
IsLoaded = true;
return new LoadResult(true);
}
private ViewModelInstance GetVmInstanceToApply(RiveWidget.DataBindingMode bindingMode, Artboard artboard, string instanceName = null)
{
ViewModelInstance vmInstance = null;
switch (bindingMode)
{
case RiveWidget.DataBindingMode.Manual:
break;
case RiveWidget.DataBindingMode.AutoBindDefault:
vmInstance = artboard.DefaultViewModel?.CreateDefaultInstance();
break;
case RiveWidget.DataBindingMode.AutoBindSelected:
vmInstance = artboard.DefaultViewModel?.CreateInstanceByName(instanceName);
break;
default:
break;
}
return vmInstance;
}
public void Tick(float deltaTime, RiveWidget.EventPoolingMode poolingMode, float speed)
{
if (m_stateMachine == null)
{
return;
}
m_reportedEvents.Clear();
m_stateMachine.ReportedEvents(m_reportedEvents);
for (int i = 0; i < m_reportedEvents.Count; i++)
{
var evt = m_reportedEvents[i];
OnRiveEventReported?.Invoke(evt);
// If pooling is enabled, auto-dispose the event
if (poolingMode == RiveWidget.EventPoolingMode.Enabled)
{
evt.Dispose();
}
}
m_stateMachine.Advance(deltaTime * speed);
// Legacy callback propagation behavior
if (RiveWidget.PropertyCallbackApproach == RiveWidget.DataBindingPropertyCallbackApproach.Propagation)
{
m_stateMachine.ViewModelInstance?.HandleCallbacks();
}
}
private ArtboardRenderObject CreateRenderObject(Artboard artboard, Alignment alignment, Fit fit, float scaleFactor)
{
ArtboardRenderObject existingRenderObject = m_renderObject as ArtboardRenderObject;
if (existingRenderObject != null)
{
existingRenderObject.Init(artboard, alignment, fit, scaleFactor);
return existingRenderObject;
}
return new ArtboardRenderObject(artboard, alignment, fit, scaleFactor);
}
private void CleanUpBeforeLoad()
{
m_stateMachine?.Dispose();
m_stateMachine = null;
m_artboard?.Dispose();
m_artboard = null;
// File ownership/lifecycle is managed by the caller (e.g., RiveWidget), so do not dispose it here.
m_file = null;
}
///
/// Calculates the effective scale factor based on the scaling mode and provided parameters.
///
/// The scaling mode to use.
/// The scale factor to apply.
/// The original size of the artboard.
/// The frame rect where the artboard will be displayed.
/// The reference DPI to use for scaling.
/// The fallback DPI to use if the current screen DPI is not available.
/// The screen DPI to use for scaling. If not provided, Screen.dpi will be used.
public static float CalculateEffectiveScaleFactor(
LayoutScalingMode scalingMode,
float scaleFactor,
Vector2 originalArtboardSize,
Rect frameRect,
float referenceDPI,
float fallbackDPI = 96f,
float screenDPI = -1f
)
{
float originalWidth = originalArtboardSize.x;
float originalHeight = originalArtboardSize.y;
switch (scalingMode)
{
case LayoutScalingMode.ConstantPixelSize:
return scaleFactor;
case LayoutScalingMode.ReferenceArtboardSize:
{
if (originalWidth <= 0 || originalHeight <= 0)
{
return 1.0f;
}
float widthScale = frameRect.width / originalWidth;
float heightScale = frameRect.height / originalHeight;
// Using the height scale gives us a match with the Rive Editor
float resolutionScale = heightScale;
return scaleFactor * resolutionScale;
}
case LayoutScalingMode.ConstantPhysicalSize:
{
float dpi = screenDPI > 0f ? screenDPI : Screen.dpi;
if (dpi <= 0f)
{
dpi = fallbackDPI;
}
float devicePixelRatio = dpi / referenceDPI;
return scaleFactor * devicePixelRatio;
}
default:
return 1.0f;
}
}
///
/// Calculates the new artboard dimensions based on the frame rect and effective scale.
///
/// Returns true if resize was successful, false if invalid values were encountered.
public static bool CalculateArtboardDimensionsForLayout(
Rect frameRect,
float effectiveScaleFactor,
out float newWidth,
out float newHeight
)
{
newWidth = 0f;
newHeight = 0f;
// Guard against invalid scale
if (effectiveScaleFactor <= 0 || float.IsNaN(effectiveScaleFactor) || float.IsInfinity(effectiveScaleFactor))
{
DebugLogger.Instance.LogWarning($"Invalid effective scale: {effectiveScaleFactor}");
return false;
}
newWidth = frameRect.width / effectiveScaleFactor;
newHeight = frameRect.height / effectiveScaleFactor;
// Guard against invalid dimensions
if (float.IsNaN(newWidth) || float.IsInfinity(newWidth) ||
float.IsNaN(newHeight) || float.IsInfinity(newHeight))
{
DebugLogger.Instance.LogWarning($"Invalid artboard dimensions calculated. Width: {newWidth}, Height: {newHeight}");
return false;
}
return true;
}
public void Dispose()
{
CleanUpBeforeLoad();
m_renderObject = null;
}
}
}