Files
BABA_YAGA/Packages/app.rive.rive-unity/Runtime/Components/Public/HitTesting/PanelRaycaster.cs
2026-05-19 17:39:03 +07:00

141 lines
6.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using Rive.Utils;
using UnityEngine;
namespace Rive.Components
{
/// <summary>
/// Utility class for performing hit testing (raycasting) on RivePanels to detect which widgets are under a given point.
/// This is used to handle input events and determine which widgets should receive pointer interactions.
/// </summary>
public class PanelRaycaster
{
/// <summary>
/// Populates the raycastResults list with the widgets in a RivePanel that are hit by the given normalized local point in the panel.
/// </summary>
/// <param name="rivePanel"> The RivePanel to check for hit widgets. </param>
/// <param name="normalizedPointInPanel"> The normalized local point in the panel to check for hit widgets. The coordinates are in the range [0,1] where (0,0) is the bottom-left corner and (1,1) is the top-right corner. </param>
/// <param name="eventCamera"> The camera used by the event system or canvas </param>
/// <param name="raycastResults"> The list to populate with hit widgets. </param>
public static void RaycastAll(IRivePanel rivePanel, Vector2 normalizedPointInPanel, List<IRiveWidget> raycastResults)
{
RectTransform panelRectTransform = rivePanel.WidgetContainer;
if (panelRectTransform == null)
{
DebugLogger.Instance.LogError("Panel RectTransform is null.");
return;
}
for (int i = rivePanel.Widgets.Count - 1; i >= 0; i--)
{
var widget = rivePanel.Widgets[i];
if (widget == null || !widget.Enabled || widget.RenderObject == null || widget.HitTestBehavior == HitTestBehavior.None)
continue;
Vector2 normalizedWidgetPoint;
bool isWithinWidgetBounds = TryGetNormalizedPointInWidget(rivePanel, normalizedPointInPanel, widget, out normalizedWidgetPoint);
if (ProcessHitTestBehavior(widget, normalizedWidgetPoint, raycastResults, isWithinWidgetBounds))
{
return;
}
}
}
/// <summary>
/// Processes the hit test behavior of a widget and adds it to the raycastResults list if it should be hit.
/// </summary>
/// <param name="widget"> The widget to process. </param>
/// <param name="normalizedPointInWidgetRect"> The normalized point in the widget's rect to check for a hit. The coordinates are in the range [0,1] where (0,0) is the bottom-left corner and (1,1) is the top-right corner. </param>
/// <param name="raycastResults"> The list to populate with hit widgets. </param>
/// <returns> True if should should block other widgets from being hit, false otherwise. </returns>
private static bool ProcessHitTestBehavior(IRiveWidget widget, Vector2 normalizedPointInWidgetRect, List<IRiveWidget> raycastResults, bool isWithinWidgetBounds)
{
switch (widget.HitTestBehavior)
{
case HitTestBehavior.Opaque:
raycastResults.Add(widget);
// Block other widgets from being hit if the pointer is within the widget
return isWithinWidgetBounds;
case HitTestBehavior.Translucent:
bool foundHit = widget.HitTest(normalizedPointInWidgetRect);
if (foundHit)
{
raycastResults.Add(widget);
return isWithinWidgetBounds;
}
break;
#pragma warning disable CS0618 // Transparent hit testing is deprecated but kept for backward compatibility
case HitTestBehavior.Transparent:
raycastResults.Add(widget);
// Continue checking other widgets
return false;
#pragma warning restore CS0618
case HitTestBehavior.None:
// Do not add to raycastResults
return false;
}
return false;
}
/// <summary>
/// Tries to get the normalized local point in the widget from the normalized local point in the panel.
/// </summary>
/// <param name="rivePanel"> The RivePanel that contains the widget. </param>
/// <param name="normalizedPointInPanel"> The normalized local point in the panel. The coordinates are in the range [0,1] where (0,0) is the bottom-left corner and (1,1) is the top-right corner.</param>
/// <param name="widget"> The widget to get the normalized local point in. </param>
/// <param name="normalizedWidgetPoint"> The normalized point in the widget's rect. </param>
/// <returns> True if the normalized local point is within the widget's bounds, false otherwise. </returns>
public static bool TryGetNormalizedPointInWidget(IRivePanel rivePanel, Vector2 normalizedPointInPanel, IRiveWidget widget, out Vector2 normalizedWidgetPoint)
{
normalizedWidgetPoint = Vector2.zero;
RectTransform panelRectTransform = rivePanel.WidgetContainer;
if (panelRectTransform == null)
{
DebugLogger.Instance.LogError("Panel RectTransform is null.");
return false;
}
var panelRect = panelRectTransform.rect;
if (panelRect.width <= 0f || panelRect.height <= 0f)
{
return false;
}
// We need to turn the normalized point (0..1) into a local point on the panel.
// Unitys RectTransform coordinates are based on the pivot, not always the center.
// So when the pivot changes, the rects xMin/yMin shift too. If we ignore that, input gets offset.
// Using xMin/yMin + (normalized * size) lets us rebuild the original local point again, so everything stays lined up.
Vector2 panelLocalPoint = new Vector2(
panelRect.xMin + (normalizedPointInPanel.x * panelRect.width),
panelRect.yMin + (normalizedPointInPanel.y * panelRect.height)
);
Vector3 worldPoint = panelRectTransform.TransformPoint(panelLocalPoint);
Vector3 widgetLocalPoint = widget.RectTransform.InverseTransformPoint(worldPoint);
normalizedWidgetPoint = new Vector2(
(widgetLocalPoint.x - widget.RectTransform.rect.xMin) / widget.RectTransform.rect.width,
(widgetLocalPoint.y - widget.RectTransform.rect.yMin) / widget.RectTransform.rect.height
);
if (widget.RectTransform.rect.Contains(widgetLocalPoint))
{
return true;
}
return false;
}
}
}