909 lines
31 KiB
C#
909 lines
31 KiB
C#
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
namespace EasyChart.UGUI
|
|
{
|
|
/// <summary>
|
|
/// Render mode for UGUIChartBridge.
|
|
/// </summary>
|
|
public enum ChartRenderMode
|
|
{
|
|
/// <summary>
|
|
/// Screen Space Overlay mode. Best quality, no RenderTexture.
|
|
/// Visible in Game view only.
|
|
/// </summary>
|
|
ScreenSpaceOverlay,
|
|
|
|
/// <summary>
|
|
/// World Space mode using RenderTexture.
|
|
/// Visible in both Scene and Game views.
|
|
/// May have slight quality differences due to RenderTexture.
|
|
/// </summary>
|
|
WorldSpace
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bridge component that positions a ChartElement (UI Toolkit) to overlay a UGUI RectTransform.
|
|
/// Supports both Screen Space Overlay mode (default, best quality) and World Space mode (visible in Scene view).
|
|
///
|
|
/// Screen Space Overlay: Renders directly without RenderTexture, best quality.
|
|
/// World Space: Uses RenderTexture, visible in Scene view but may have slight quality differences.
|
|
///
|
|
/// Requires Unity 2021.2+.
|
|
/// </summary>
|
|
[AddComponentMenu("EasyChart/UGUI Chart Bridge")]
|
|
[ExecuteAlways]
|
|
public class UGUIChartBridge : MonoBehaviour
|
|
{
|
|
[Header("Chart Configuration")]
|
|
[Tooltip("The ChartProfile to display")]
|
|
[SerializeField] private ChartProfile _profile;
|
|
|
|
[Tooltip("PanelSettings asset for UI Toolkit rendering. REQUIRED for proper font rendering.")]
|
|
[SerializeField] private PanelSettings _panelSettingsAsset;
|
|
|
|
[Header("Render Settings")]
|
|
[Tooltip("Render mode for the chart.\nScreen Space Overlay: Best quality, visible in Game view only.\nWorld Space: Visible in Scene view, uses RenderTexture.")]
|
|
[SerializeField] private ChartRenderMode _renderMode = ChartRenderMode.ScreenSpaceOverlay;
|
|
|
|
[Tooltip("If enabled, render through a RawImage/RenderTexture so the chart participates in normal UGUI masking and draw order. This fixes charts always appearing above UGUI, at the cost of using the RenderTexture path.")]
|
|
[SerializeField] private bool _respectUGUILayering = true;
|
|
|
|
[Header("Layer Settings")]
|
|
[Tooltip("Sort order for the UIDocument panel. Higher values render on top. (Screen Space Overlay only)")]
|
|
[SerializeField] private int _sortOrder = 100;
|
|
|
|
[Header("Debug")]
|
|
[Tooltip("If enabled, logs size/resolution changes (only when changed).")]
|
|
[SerializeField] private bool _logResolutionChanges = false;
|
|
|
|
[Header("Interaction")]
|
|
[Tooltip("If enabled, the runtime chart bridge behaves as a passive display and will not capture pointer interaction.")]
|
|
[SerializeField] private bool _disablePointerInteraction = false;
|
|
|
|
private UIDocument _uiDocument;
|
|
private ChartElement _chartElement;
|
|
private RectTransform _rectTransform;
|
|
private Canvas _parentCanvas;
|
|
private VisualElement _rootContainer;
|
|
private PanelSettings _runtimePanelSettings;
|
|
private bool _createdPanelSettings;
|
|
private bool _isInitialized;
|
|
|
|
private bool _loggedOnce;
|
|
|
|
private Rect _lastScreenRect;
|
|
|
|
// World Space mode fields
|
|
private RenderTexture _renderTexture;
|
|
private UnityEngine.UI.RawImage _rawImage;
|
|
private ChartRenderMode _lastRenderMode;
|
|
private const string DefaultPanelSettingsAssetPath = "Assets/EasyChart/UGUI/Prefabs/EasyChartPanelSettings.asset";
|
|
|
|
/// <summary>
|
|
/// The ChartProfile being displayed.
|
|
/// </summary>
|
|
public ChartProfile Profile
|
|
{
|
|
get => _profile;
|
|
set
|
|
{
|
|
if (_profile != value)
|
|
{
|
|
_profile = value;
|
|
UpdateChartProfile();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The underlying ChartElement instance.
|
|
/// </summary>
|
|
public ChartElement ChartElement => _chartElement;
|
|
|
|
/// <summary>
|
|
/// Sort order for the UIDocument panel.
|
|
/// </summary>
|
|
public int SortOrder
|
|
{
|
|
get => _sortOrder;
|
|
set
|
|
{
|
|
if (_sortOrder != value)
|
|
{
|
|
_sortOrder = value;
|
|
if (_uiDocument != null)
|
|
{
|
|
_uiDocument.sortingOrder = _sortOrder;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Render mode for the chart.
|
|
/// </summary>
|
|
public ChartRenderMode RenderMode
|
|
{
|
|
get => _renderMode;
|
|
set
|
|
{
|
|
if (_renderMode != value)
|
|
{
|
|
_renderMode = value;
|
|
if (_isInitialized)
|
|
{
|
|
// Need to reinitialize with new mode
|
|
CleanupUIDocument();
|
|
CleanupWorldSpace();
|
|
_isInitialized = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool RespectUGUILayering
|
|
{
|
|
get => _respectUGUILayering;
|
|
set
|
|
{
|
|
if (_respectUGUILayering != value)
|
|
{
|
|
_respectUGUILayering = value;
|
|
if (_isInitialized)
|
|
{
|
|
CleanupUIDocument();
|
|
CleanupWorldSpace();
|
|
_isInitialized = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool DisablePointerInteraction
|
|
{
|
|
get => _disablePointerInteraction;
|
|
set
|
|
{
|
|
if (_disablePointerInteraction == value)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_disablePointerInteraction = value;
|
|
ApplyPointerInteractionMode();
|
|
}
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
_rectTransform = GetComponent<RectTransform>();
|
|
_parentCanvas = GetComponentInParent<Canvas>();
|
|
TryResolvePanelSettingsAsset();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
_rectTransform = GetComponent<RectTransform>();
|
|
_parentCanvas = GetComponentInParent<Canvas>();
|
|
TryResolvePanelSettingsAsset();
|
|
|
|
_lastScreenRect = new Rect(float.NaN, float.NaN, float.NaN, float.NaN);
|
|
|
|
_loggedOnce = false;
|
|
|
|
// Delay initialization to next frame to avoid issues during activation
|
|
_isInitialized = false;
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
ReleaseRuntimeResources();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
CleanupUIDocument();
|
|
CleanupWorldSpace();
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
// Check if render mode changed
|
|
if (_lastRenderMode != _renderMode && _isInitialized)
|
|
{
|
|
CleanupUIDocument();
|
|
CleanupWorldSpace();
|
|
_isInitialized = false;
|
|
}
|
|
_lastRenderMode = _renderMode;
|
|
|
|
// Initialize on first LateUpdate to ensure everything is ready
|
|
EnsureInitialized();
|
|
|
|
if (_logResolutionChanges && !_loggedOnce)
|
|
{
|
|
_loggedOnce = true;
|
|
LogDebugSizes();
|
|
}
|
|
|
|
if (!UsesRenderTexturePipeline())
|
|
{
|
|
UpdatePosition();
|
|
}
|
|
else
|
|
{
|
|
UpdateWorldSpaceRenderTexture();
|
|
}
|
|
}
|
|
|
|
private void OnValidate()
|
|
{
|
|
TryResolvePanelSettingsAsset();
|
|
if (isActiveAndEnabled && _isInitialized)
|
|
{
|
|
if (_uiDocument != null)
|
|
{
|
|
_uiDocument.sortingOrder = _sortOrder;
|
|
}
|
|
UpdateChartProfile();
|
|
}
|
|
}
|
|
|
|
private bool UsesRenderTexturePipeline()
|
|
{
|
|
return _renderMode == ChartRenderMode.WorldSpace || _respectUGUILayering;
|
|
}
|
|
|
|
public void ReleaseRuntimeResources()
|
|
{
|
|
_isInitialized = false;
|
|
_lastScreenRect = new Rect(float.NaN, float.NaN, float.NaN, float.NaN);
|
|
|
|
DetachOverlayChartContent();
|
|
|
|
if (_uiDocument != null)
|
|
{
|
|
_uiDocument.visualTreeAsset = null;
|
|
|
|
if (_uiDocument.rootVisualElement != null)
|
|
{
|
|
_uiDocument.rootVisualElement.Clear();
|
|
}
|
|
|
|
if (_createdPanelSettings)
|
|
{
|
|
_uiDocument.panelSettings = null;
|
|
}
|
|
}
|
|
|
|
ReleaseRuntimePanelSettings();
|
|
|
|
if (_rawImage != null)
|
|
{
|
|
_rawImage.texture = null;
|
|
_rawImage.enabled = false;
|
|
_rawImage.raycastTarget = false;
|
|
}
|
|
|
|
ReleaseRenderTexture();
|
|
}
|
|
|
|
public void ForceRebuild()
|
|
{
|
|
ReleaseRuntimeResources();
|
|
EnsureInitialized();
|
|
Refresh();
|
|
}
|
|
|
|
private void EnsureInitialized()
|
|
{
|
|
if (_isInitialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_isInitialized = true;
|
|
if (UsesRenderTexturePipeline())
|
|
{
|
|
CreateWorldSpaceUI();
|
|
}
|
|
else
|
|
{
|
|
CreateUIDocument();
|
|
}
|
|
|
|
UpdateChartProfile();
|
|
}
|
|
|
|
private void CreateUIDocument()
|
|
{
|
|
// Check if UIDocument already exists on this GameObject
|
|
_uiDocument = GetComponent<UIDocument>();
|
|
|
|
if (_uiDocument == null)
|
|
{
|
|
_uiDocument = gameObject.AddComponent<UIDocument>();
|
|
}
|
|
|
|
// UGUIChartBridge owns the runtime tree. Binding a VisualTreeAsset here can
|
|
// interfere with chart rendering and pointer routing.
|
|
_uiDocument.visualTreeAsset = null;
|
|
|
|
TryResolvePanelSettingsAsset();
|
|
|
|
// Setup PanelSettings
|
|
if (_panelSettingsAsset != null)
|
|
{
|
|
// Reuse the authored asset directly in overlay mode.
|
|
// Cloning PanelSettings can lose Theme Style Sheet bindings in some Unity versions.
|
|
_runtimePanelSettings = _panelSettingsAsset;
|
|
_createdPanelSettings = false;
|
|
}
|
|
else
|
|
{
|
|
if (_uiDocument.panelSettings != null)
|
|
{
|
|
_runtimePanelSettings = _uiDocument.panelSettings;
|
|
_createdPanelSettings = false;
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("[UGUIChartBridge] Missing PanelSettings asset. Assign EasyChartPanelSettings.asset.");
|
|
_isInitialized = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
_uiDocument.panelSettings = _runtimePanelSettings;
|
|
_uiDocument.sortingOrder = _sortOrder;
|
|
|
|
// Wait for rootVisualElement to be available
|
|
if (_uiDocument.rootVisualElement == null)
|
|
{
|
|
// Will retry on next frame
|
|
_isInitialized = false;
|
|
return;
|
|
}
|
|
|
|
// The bridge fully owns this UIDocument tree. Clear any previous
|
|
// runtime content so overlay panels do not leave stale chart visuals behind.
|
|
_uiDocument.rootVisualElement.Clear();
|
|
|
|
// Create root container that will be positioned to match RectTransform
|
|
_rootContainer = new VisualElement();
|
|
_rootContainer.name = "ChartBridgeRoot";
|
|
_rootContainer.style.position = Position.Absolute;
|
|
_rootContainer.pickingMode = PickingMode.Ignore;
|
|
|
|
// Create ChartElement
|
|
_chartElement = new ChartElement();
|
|
// Let ChartElement use its profile dimensions; Scale affects display only
|
|
_chartElement.IgnoreProfileSize = false;
|
|
_chartElement.style.flexGrow = 1;
|
|
_chartElement.style.width = Length.Percent(100);
|
|
_chartElement.style.height = Length.Percent(100);
|
|
|
|
_rootContainer.Add(_chartElement);
|
|
_uiDocument.rootVisualElement.Add(_rootContainer);
|
|
ApplyPointerInteractionMode();
|
|
|
|
_lastScreenRect = new Rect(float.NaN, float.NaN, float.NaN, float.NaN);
|
|
}
|
|
|
|
private void DetachOverlayChartContent()
|
|
{
|
|
if (_chartElement != null)
|
|
{
|
|
_chartElement.RemoveFromHierarchy();
|
|
_chartElement = null;
|
|
}
|
|
|
|
if (_rootContainer != null)
|
|
{
|
|
_rootContainer.RemoveFromHierarchy();
|
|
_rootContainer = null;
|
|
}
|
|
|
|
if (_uiDocument != null && _uiDocument.rootVisualElement != null)
|
|
{
|
|
_uiDocument.rootVisualElement.Clear();
|
|
}
|
|
}
|
|
|
|
private void CleanupUIDocument()
|
|
{
|
|
DetachOverlayChartContent();
|
|
|
|
// Use delayed destruction to avoid issues during activation/deactivation
|
|
if (_uiDocument != null)
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (!Application.isPlaying)
|
|
{
|
|
EditorApplication.delayCall += () =>
|
|
{
|
|
if (_uiDocument != null)
|
|
DestroyImmediate(_uiDocument);
|
|
};
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
Destroy(_uiDocument);
|
|
}
|
|
_uiDocument = null;
|
|
}
|
|
|
|
if (_runtimePanelSettings != null && _createdPanelSettings)
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (!Application.isPlaying)
|
|
{
|
|
var ps = _runtimePanelSettings;
|
|
EditorApplication.delayCall += () =>
|
|
{
|
|
if (ps != null)
|
|
DestroyImmediate(ps);
|
|
};
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
Destroy(_runtimePanelSettings);
|
|
}
|
|
}
|
|
_runtimePanelSettings = null;
|
|
_createdPanelSettings = false;
|
|
}
|
|
|
|
private void ApplyPointerInteractionMode()
|
|
{
|
|
var pickingMode = _disablePointerInteraction ? PickingMode.Ignore : PickingMode.Position;
|
|
|
|
if (_uiDocument != null && _uiDocument.rootVisualElement != null)
|
|
{
|
|
_uiDocument.rootVisualElement.pickingMode = pickingMode;
|
|
_uiDocument.rootVisualElement.focusable = false;
|
|
}
|
|
|
|
if (_rootContainer != null)
|
|
{
|
|
_rootContainer.pickingMode = pickingMode;
|
|
_rootContainer.focusable = false;
|
|
}
|
|
|
|
if (_chartElement != null)
|
|
{
|
|
_chartElement.pickingMode = pickingMode;
|
|
_chartElement.focusable = false;
|
|
}
|
|
|
|
if (_rawImage != null)
|
|
{
|
|
_rawImage.raycastTarget = !_disablePointerInteraction;
|
|
}
|
|
}
|
|
|
|
private void TryResolvePanelSettingsAsset()
|
|
{
|
|
if (_panelSettingsAsset != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UIDocument existingDocument = GetComponent<UIDocument>();
|
|
if (existingDocument != null && existingDocument.panelSettings != null)
|
|
{
|
|
_panelSettingsAsset = existingDocument.panelSettings;
|
|
return;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
PanelSettings defaultAsset = AssetDatabase.LoadAssetAtPath<PanelSettings>(DefaultPanelSettingsAssetPath);
|
|
if (defaultAsset != null)
|
|
{
|
|
_panelSettingsAsset = defaultAsset;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
private void UpdatePosition()
|
|
{
|
|
if (_rectTransform == null || _rootContainer == null) return;
|
|
|
|
// Get screen rect of the RectTransform
|
|
Rect screenRect = GetScreenRect();
|
|
|
|
// Only update if changed
|
|
if (screenRect == _lastScreenRect) return;
|
|
_lastScreenRect = screenRect;
|
|
|
|
// Use profile dimensions for container size, screenRect for position
|
|
Vector2Int profileSize = GetPixelAdjustedSize();
|
|
|
|
if (_logResolutionChanges)
|
|
{
|
|
Vector2 sizeDelta = _rectTransform.sizeDelta;
|
|
Rect localRect = _rectTransform.rect;
|
|
Vector3 lossyScale = _rectTransform.lossyScale;
|
|
Debug.Log($"[UGUIChartBridge] ScreenRect: {Mathf.RoundToInt(screenRect.width)}x{Mathf.RoundToInt(screenRect.height)} | " +
|
|
$"ProfileSize: {profileSize.x}x{profileSize.y} | " +
|
|
$"Rect: {Mathf.RoundToInt(localRect.width)}x{Mathf.RoundToInt(localRect.height)} | " +
|
|
$"SizeDelta: {Mathf.RoundToInt(sizeDelta.x)}x{Mathf.RoundToInt(sizeDelta.y)} | " +
|
|
$"LossyScale: {lossyScale.x:F3},{lossyScale.y:F3}");
|
|
}
|
|
|
|
// Position the root container to match the RectTransform's screen position
|
|
// Container uses profile dimensions, CSS transform scale stretches to screen size
|
|
_rootContainer.style.left = screenRect.x;
|
|
_rootContainer.style.top = Screen.height - screenRect.y - screenRect.height; // UI Toolkit Y is from top
|
|
_rootContainer.style.width = profileSize.x;
|
|
_rootContainer.style.height = profileSize.y;
|
|
|
|
// Apply CSS transform scale to stretch profile-sized content to screen size
|
|
float scaleX = screenRect.width / profileSize.x;
|
|
float scaleY = screenRect.height / profileSize.y;
|
|
_rootContainer.style.transformOrigin = new TransformOrigin(0, 0);
|
|
_rootContainer.transform.scale = new Vector3(scaleX, scaleY, 1f);
|
|
}
|
|
|
|
private Rect GetScreenRect()
|
|
{
|
|
if (_rectTransform == null) return Rect.zero;
|
|
|
|
// Get the four corners of the RectTransform in world space
|
|
Vector3[] corners = new Vector3[4];
|
|
_rectTransform.GetWorldCorners(corners);
|
|
|
|
// Convert to screen space
|
|
Camera cam = null;
|
|
if (_parentCanvas != null)
|
|
{
|
|
if (_parentCanvas.renderMode == UnityEngine.RenderMode.ScreenSpaceCamera)
|
|
cam = _parentCanvas.worldCamera;
|
|
else if (_parentCanvas.renderMode == UnityEngine.RenderMode.WorldSpace)
|
|
cam = _parentCanvas.worldCamera ?? Camera.main;
|
|
}
|
|
|
|
Vector2 min = new Vector2(float.MaxValue, float.MaxValue);
|
|
Vector2 max = new Vector2(float.MinValue, float.MinValue);
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
Vector2 screenPoint;
|
|
if (cam != null)
|
|
screenPoint = RectTransformUtility.WorldToScreenPoint(cam, corners[i]);
|
|
else
|
|
screenPoint = RectTransformUtility.WorldToScreenPoint(null, corners[i]);
|
|
|
|
min = Vector2.Min(min, screenPoint);
|
|
max = Vector2.Max(max, screenPoint);
|
|
}
|
|
|
|
return new Rect(min.x, min.y, max.x - min.x, max.y - min.y);
|
|
}
|
|
|
|
private Vector2Int GetPixelAdjustedSize()
|
|
{
|
|
// Always use ChartProfile's fixed dimensions for rendering
|
|
// Scale is applied to the final display, not the render resolution
|
|
if (_profile != null && _profile.chartWidth > 0 && _profile.chartHeight > 0)
|
|
{
|
|
int pw = Mathf.Max(1, Mathf.RoundToInt(_profile.chartWidth));
|
|
int ph = Mathf.Max(1, Mathf.RoundToInt(_profile.chartHeight));
|
|
return new Vector2Int(pw, ph);
|
|
}
|
|
|
|
// Fallback to RectTransform size if profile dimensions are not set
|
|
if (_rectTransform == null) return Vector2Int.one;
|
|
|
|
int width = Mathf.Max(1, Mathf.RoundToInt(_rectTransform.rect.width));
|
|
int height = Mathf.Max(1, Mathf.RoundToInt(_rectTransform.rect.height));
|
|
return new Vector2Int(width, height);
|
|
}
|
|
|
|
private void UpdateChartProfile()
|
|
{
|
|
if (_chartElement == null) return;
|
|
_chartElement.Profile = _profile;
|
|
}
|
|
|
|
[ContextMenu("Log Debug Sizes")]
|
|
private void LogDebugSizes()
|
|
{
|
|
if (_rectTransform == null) _rectTransform = GetComponent<RectTransform>();
|
|
if (_parentCanvas == null) _parentCanvas = GetComponentInParent<Canvas>();
|
|
|
|
Rect screenRect = GetScreenRect();
|
|
Vector2Int pixelAdjusted = GetPixelAdjustedSize();
|
|
|
|
Vector2 sizeDelta = _rectTransform != null ? _rectTransform.sizeDelta : Vector2.zero;
|
|
Rect localRect = _rectTransform != null ? _rectTransform.rect : Rect.zero;
|
|
Vector3 lossyScale = _rectTransform != null ? _rectTransform.lossyScale : Vector3.one;
|
|
Vector2 anchorMin = _rectTransform != null ? _rectTransform.anchorMin : Vector2.zero;
|
|
Vector2 anchorMax = _rectTransform != null ? _rectTransform.anchorMax : Vector2.zero;
|
|
Vector2 pivot = _rectTransform != null ? _rectTransform.pivot : Vector2.zero;
|
|
|
|
string canvasInfo = _parentCanvas == null
|
|
? "Canvas: null"
|
|
: $"Canvas: {_parentCanvas.name} ({_parentCanvas.renderMode})";
|
|
|
|
Debug.Log(
|
|
$"[UGUIChartBridge] RenderMode={_renderMode} | {canvasInfo} | " +
|
|
$"ScreenRect={Mathf.RoundToInt(screenRect.width)}x{Mathf.RoundToInt(screenRect.height)} | " +
|
|
$"PixelAdjusted={pixelAdjusted.x}x{pixelAdjusted.y} | " +
|
|
$"Rect={Mathf.RoundToInt(localRect.width)}x{Mathf.RoundToInt(localRect.height)} | " +
|
|
$"SizeDelta={Mathf.RoundToInt(sizeDelta.x)}x{Mathf.RoundToInt(sizeDelta.y)} | " +
|
|
$"Anchors=({anchorMin.x:F3},{anchorMin.y:F3})-({anchorMax.x:F3},{anchorMax.y:F3}) | " +
|
|
$"Pivot=({pivot.x:F3},{pivot.y:F3}) | " +
|
|
$"LossyScale=({lossyScale.x:F3},{lossyScale.y:F3})");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Force refresh the chart display.
|
|
/// </summary>
|
|
public void Refresh()
|
|
{
|
|
EnsureInitialized();
|
|
UpdatePosition();
|
|
if (_chartElement != null)
|
|
{
|
|
ApplyPointerInteractionMode();
|
|
_chartElement.ForceRefreshProfile();
|
|
}
|
|
}
|
|
|
|
#region World Space Mode
|
|
|
|
private RenderTexture CreateRenderTexture(int width, int height)
|
|
{
|
|
var rt = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32);
|
|
rt.name = $"UGUIChartBridge_RT_{GetInstanceID()}";
|
|
rt.antiAliasing = 2;
|
|
rt.hideFlags = HideFlags.DontSave;
|
|
rt.Create();
|
|
return rt;
|
|
}
|
|
|
|
private void CreateWorldSpaceUI()
|
|
{
|
|
if (_rectTransform == null) return;
|
|
|
|
TryResolvePanelSettingsAsset();
|
|
if (_panelSettingsAsset == null)
|
|
{
|
|
Debug.LogWarning("[UGUIChartBridge] Missing PanelSettings asset. Assign EasyChartPanelSettings.asset.");
|
|
_isInitialized = false;
|
|
return;
|
|
}
|
|
|
|
// Create RenderTexture
|
|
Vector2Int size = GetPixelAdjustedSize();
|
|
int width = size.x;
|
|
int height = size.y;
|
|
|
|
_renderTexture = CreateRenderTexture(width, height);
|
|
|
|
// Create UIDocument for off-screen rendering
|
|
_uiDocument = GetComponent<UIDocument>();
|
|
if (_uiDocument == null)
|
|
{
|
|
_uiDocument = gameObject.AddComponent<UIDocument>();
|
|
}
|
|
|
|
_uiDocument.visualTreeAsset = null;
|
|
|
|
// Setup PanelSettings with RenderTexture target
|
|
// For World Space mode, we need to create a copy of the panel settings to set targetTexture
|
|
_runtimePanelSettings = ScriptableObject.Instantiate(_panelSettingsAsset);
|
|
_runtimePanelSettings.name = $"UGUIChartBridge_WorldSpace_PanelSettings_{GetInstanceID()}";
|
|
_runtimePanelSettings.hideFlags = HideFlags.DontSave;
|
|
_runtimePanelSettings.scaleMode = PanelScaleMode.ConstantPixelSize;
|
|
_createdPanelSettings = true;
|
|
|
|
// Set target texture for World Space rendering
|
|
_runtimePanelSettings.targetTexture = _renderTexture;
|
|
_runtimePanelSettings.clearColor = true;
|
|
|
|
_uiDocument.panelSettings = _runtimePanelSettings;
|
|
|
|
// Wait for rootVisualElement to be available
|
|
if (_uiDocument.rootVisualElement == null)
|
|
{
|
|
_isInitialized = false;
|
|
return;
|
|
}
|
|
|
|
// Create root container
|
|
_rootContainer = new VisualElement();
|
|
_rootContainer.name = "ChartBridgeRoot";
|
|
_rootContainer.style.position = Position.Absolute;
|
|
_rootContainer.style.left = 0;
|
|
_rootContainer.style.top = 0;
|
|
_rootContainer.style.width = width;
|
|
_rootContainer.style.height = height;
|
|
_rootContainer.pickingMode = PickingMode.Ignore;
|
|
|
|
// Create ChartElement
|
|
_chartElement = new ChartElement();
|
|
// Let ChartElement use its profile dimensions; Scale affects display only
|
|
_chartElement.IgnoreProfileSize = false;
|
|
_chartElement.style.flexGrow = 1;
|
|
_chartElement.style.width = Length.Percent(100);
|
|
_chartElement.style.height = Length.Percent(100);
|
|
|
|
_rootContainer.Add(_chartElement);
|
|
_uiDocument.rootVisualElement.Add(_rootContainer);
|
|
|
|
// Create RawImage to display the RenderTexture
|
|
_rawImage = GetComponent<UnityEngine.UI.RawImage>();
|
|
if (_rawImage == null)
|
|
{
|
|
_rawImage = gameObject.AddComponent<UnityEngine.UI.RawImage>();
|
|
}
|
|
else
|
|
{
|
|
_rawImage.enabled = true;
|
|
}
|
|
|
|
_rawImage.texture = _renderTexture;
|
|
_rawImage.raycastTarget = false;
|
|
_rawImage.color = Color.white;
|
|
ApplyPointerInteractionMode();
|
|
|
|
_lastScreenRect = new Rect(float.NaN, float.NaN, float.NaN, float.NaN);
|
|
}
|
|
|
|
private void UpdateWorldSpaceRenderTexture()
|
|
{
|
|
if (_rectTransform == null || _renderTexture == null) return;
|
|
|
|
// Check if size changed
|
|
Vector2Int size = GetPixelAdjustedSize();
|
|
int width = size.x;
|
|
int height = size.y;
|
|
|
|
if (_renderTexture.width != width || _renderTexture.height != height)
|
|
{
|
|
if (_logResolutionChanges)
|
|
{
|
|
Debug.Log($"[UGUIChartBridge] Recreate RenderTexture: {_renderTexture.width}x{_renderTexture.height} -> {width}x{height}");
|
|
}
|
|
|
|
// Recreate RenderTexture (more reliable than resizing the same instance for UI Toolkit panels)
|
|
var oldRt = _renderTexture;
|
|
_renderTexture = CreateRenderTexture(width, height);
|
|
|
|
if (_runtimePanelSettings != null)
|
|
{
|
|
_runtimePanelSettings.targetTexture = _renderTexture;
|
|
}
|
|
|
|
// Update root container size
|
|
if (_rootContainer != null)
|
|
{
|
|
_rootContainer.style.width = width;
|
|
_rootContainer.style.height = height;
|
|
}
|
|
|
|
// Update RawImage
|
|
if (_rawImage != null)
|
|
{
|
|
_rawImage.texture = _renderTexture;
|
|
}
|
|
|
|
if (oldRt != null)
|
|
{
|
|
oldRt.Release();
|
|
#if UNITY_EDITOR
|
|
if (!Application.isPlaying)
|
|
{
|
|
var rt = oldRt;
|
|
EditorApplication.delayCall += () =>
|
|
{
|
|
if (rt != null)
|
|
DestroyImmediate(rt);
|
|
};
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
Destroy(oldRt);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CleanupWorldSpace()
|
|
{
|
|
if (_rawImage != null)
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (!Application.isPlaying)
|
|
{
|
|
var img = _rawImage;
|
|
EditorApplication.delayCall += () =>
|
|
{
|
|
if (img != null)
|
|
DestroyImmediate(img);
|
|
};
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
Destroy(_rawImage);
|
|
}
|
|
_rawImage = null;
|
|
}
|
|
|
|
ReleaseRenderTexture();
|
|
}
|
|
|
|
private void ReleaseRuntimePanelSettings()
|
|
{
|
|
if (_runtimePanelSettings == null || !_createdPanelSettings)
|
|
{
|
|
_runtimePanelSettings = _createdPanelSettings ? null : _runtimePanelSettings;
|
|
_createdPanelSettings = false;
|
|
return;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
if (!Application.isPlaying)
|
|
{
|
|
var panelSettings = _runtimePanelSettings;
|
|
EditorApplication.delayCall += () =>
|
|
{
|
|
if (panelSettings != null)
|
|
{
|
|
DestroyImmediate(panelSettings);
|
|
}
|
|
};
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
Destroy(_runtimePanelSettings);
|
|
}
|
|
|
|
_runtimePanelSettings = null;
|
|
_createdPanelSettings = false;
|
|
}
|
|
|
|
private void ReleaseRenderTexture()
|
|
{
|
|
if (_renderTexture == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_renderTexture.Release();
|
|
#if UNITY_EDITOR
|
|
if (!Application.isPlaying)
|
|
{
|
|
var rt = _renderTexture;
|
|
EditorApplication.delayCall += () =>
|
|
{
|
|
if (rt != null)
|
|
{
|
|
DestroyImmediate(rt);
|
|
}
|
|
};
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
Destroy(_renderTexture);
|
|
}
|
|
|
|
_renderTexture = null;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|