#if UNITY_6000_7_OR_NEWER
using System.Collections.Generic;
using System.Linq;
using Unity.Pipeline.Commands;
using UnityEngine;
using UnityEngine.UIElements;
using Object = UnityEngine.Object;
namespace Unity.Pipeline.Runtime.Commands
{
///
/// Captures a UI Toolkit from a live runtime panel to a PNG and
/// returns it base64-encoded (and writes it to disk). Runtime-only so it is available in a
/// development Player as well as the editor's Play mode.
///
/// A runtime panel is hosted by either a or a .
/// The panel is identified by name (the asset name or the host
/// GameObject name) rather than an asset path, because a Player has no AssetDatabase.
///
public static class CaptureRuntimeElementCommand
{
[CliCommand("capture_runtime_element",
"Capture a UI Toolkit VisualElement (by selector) from a live runtime panel (UIDocument or PanelRenderer) to a PNG; returns path + base64.",
MainThreadRequired = true, RuntimeOnly = true)]
public static CaptureElementResponse CaptureRuntimeElement(
[CliArg("panel", "Name of the target panel: matches the PanelSettings asset name or the host GameObject name (UIDocument or PanelRenderer). Optional when exactly one panel exists.")] string panel = "",
[CliArg("selector", "Element selector: '#name', '.class', a type name (e.g. Button), descendant (space) / child ('>') chains, optional pseudo-states (:checked,:hover,:focus,:active,:enabled,:disabled,:not(...)).", Required = true)] string selector = "",
[CliArg("output", "Output PNG path (absolute, or relative to Application.persistentDataPath). Defaults to a timestamped file under Application.persistentDataPath.")] string output = "")
{
if (string.IsNullOrWhiteSpace(selector))
return CaptureElementResponse.Fail("A 'selector' is required.");
var hosts = GatherHosts();
if (hosts.Count == 0)
return CaptureElementResponse.Fail("No live runtime UI panels found (UIDocument or PanelRenderer). Show the UI first.");
List matched;
string resolvedName;
if (string.IsNullOrWhiteSpace(panel))
{
if (hosts.Count > 1)
{
var names = string.Join(", ", hosts.Select(h => h.DisplayName).Distinct());
return CaptureElementResponse.Fail(
$"Multiple runtime panels exist; specify --panel as one of: {names}.");
}
matched = hosts;
resolvedName = hosts[0].DisplayName;
}
else
{
matched = hosts.Where(h => h.PanelSettingsName == panel || h.GameObjectName == panel).ToList();
if (matched.Count == 0)
{
var names = string.Join(", ", hosts.Select(h => h.DisplayName).Distinct());
return CaptureElementResponse.Fail(
$"No live runtime panel named '{panel}'. Available: {names}.");
}
resolvedName = panel;
}
// Lower sorting order is drawn first (further back); query in that order so the topmost
// match is found last only if earlier panels miss. Roots that can't be resolved are skipped.
var roots = matched
.OrderBy(h => h.SortingOrder)
.Select(h => h.GetRoot())
.Where(r => r != null)
.ToList();
if (roots.Count == 0)
return CaptureElementResponse.Fail(
$"Panel '{resolvedName}' has no initialized root VisualElement yet. Show the UI first.");
var element = VisualElementCaptureSupport.ResolveElement(roots, selector, out _);
if (element == null)
return CaptureElementResponse.Fail(
$"No element matched selector '{selector}' in panel '{resolvedName}'.");
var dir = Application.persistentDataPath;
return VisualElementCaptureSupport.CaptureAndRespond(
element, selector, $"panel:{resolvedName}", output,
relativeBaseDir: dir, defaultDir: dir, prefix: "element");
}
/// A live runtime UI host (UIDocument or PanelRenderer) and how to reach its root.
class Host
{
public string PanelSettingsName;
public string GameObjectName;
public float SortingOrder;
public System.Func GetRoot;
public string DisplayName => PanelSettingsName ?? GameObjectName;
}
static List GatherHosts()
{
var hosts = new List();
foreach (var doc in PipelineUtils.FindObjectsByType())
{
var d = doc;
hosts.Add(new Host
{
PanelSettingsName = d.panelSettings != null ? d.panelSettings.name : null,
GameObjectName = d.gameObject.name,
SortingOrder = d.sortingOrder,
GetRoot = () => d.rootVisualElement
});
}
// PanelRenderer is a 6000.5+ UI Toolkit API; on older editors only UIDocument panels exist.
foreach (var renderer in PipelineUtils.FindObjectsByType())
{
var r = renderer;
hosts.Add(new Host
{
PanelSettingsName = r.panelSettings != null ? r.panelSettings.name : null,
GameObjectName = r.gameObject.name,
SortingOrder = r.sortingOrder,
GetRoot = () => GetPanelRendererRoot(r)
});
}
return hosts;
}
// PanelRenderer.rootVisualElement is internal; the public route to its root is a UI-reload
// callback, which fires synchronously when the panel is already initialized. Register, capture
// the root, and immediately unregister. PanelRenderer is a 6000.5+ API.
static VisualElement GetPanelRendererRoot(PanelRenderer renderer)
{
VisualElement captured = null;
PanelRenderer.VersionedUIReloadCallback callback = (pr, root, version) => captured = root;
renderer.RegisterUIReloadCallback(callback);
renderer.UnregisterUIReloadCallback(callback);
return captured;
}
}
}
#endif