Editor/Debugger/EffectTreeNode.cs
#if DEBUG
using System.IO;
using Sandbox.Reactivity.Internals;
namespace Sandbox.Reactivity.Editor.Debugger;
internal class EffectTreeNode : TreeNode<Effect>, IDisposable
{
private const float FadeDurationSeconds = 0.5f;
private readonly Effect _effect;
private readonly TimeSince _timeSinceCreated = DebuggerWidget.HighlightNewEffects ? 0 : double.MaxValue;
private TimeSince _timeSinceLastRun = double.MaxValue;
public EffectTreeNode(Effect effect)
: base(effect)
{
EditorEvent.Unregister(this);
_effect = effect;
effect.OnChildEffectCreated += OnChildEffectCreated;
effect.OnRerun += OnEffectRerun;
effect.OnDisposed += Dispose;
}
// hack since treeview items are only updated every 100 ms, enables/disables the EditorEvent.Frame method below
private bool IsAnimating
{
set
{
if (value == field)
{
return;
}
if (value)
{
EditorEvent.Register(this);
}
else
{
EditorEvent.Unregister(this);
}
field = value;
}
}
public void Dispose()
{
_effect.OnChildEffectCreated -= OnChildEffectCreated;
_effect.OnRerun -= OnEffectRerun;
_effect.OnDisposed -= Dispose;
IsAnimating = false;
if (Parent != null)
{
Parent.RemoveItem(this);
}
else if (TreeView != null)
{
// calling RemoveItem internally calls ResolveObject when removing it from its internal list which ends up
// trying to remove the effect from the tree instead of this node. setting Value to null will make
// ResolveObject default to the node itself, making it correctly remove from the tree
Value = null!;
TreeView.RemoveItem(this);
Value = _effect;
}
}
public override bool OnContextMenu()
{
var menu = new ContextMenu(TreeView);
menu.AddOption(new Option
{
Text = "Find containing object in scene",
Icon = "search",
Enabled = _effect.Parent is Component { IsValid: true } or GameObject { IsValid: true },
Triggered = () =>
{
switch (_effect.Parent)
{
case Component component:
EditorUtility.FindInScene(component);
break;
case GameObject go:
EditorUtility.FindInScene(go);
break;
}
},
});
menu.OpenAtCursor();
return false;
}
private void OnChildEffectCreated(Effect effect)
{
var node = new EffectTreeNode(effect);
AddItem(node);
TreeView?.Open(this, true);
}
private void OnEffectRerun()
{
if (!DebuggerWidget.HighlightEffectReruns)
{
return;
}
_timeSinceLastRun = 0;
Dirty();
}
// ReSharper disable once UnusedMember.Local
[EditorEvent.Frame]
private void OnAnimate()
{
RebuildOnDirty();
}
public override void OnSelectionChanged(bool selected)
{
if (selected)
{
EditorUtility.InspectorObject = _effect;
}
else if (EditorUtility.InspectorObject == _effect)
{
EditorUtility.InspectorObject = null;
}
}
public override void OnPaint(VirtualWidget item)
{
Paint.ClearPen();
Paint.ClearBrush();
Paint.SetDefaultFont();
var rect = item.Rect;
var fullRect = rect with
{
Left = 0,
Right = TreeView?.Width ?? 0f,
};
var isEven = item.Row % 2 == 0;
var textColor = Theme.TextControl;
var backgroundColor = Color.Transparent;
if (item.Selected)
{
backgroundColor = Theme.SelectedBackground;
}
else if (item.Hovered)
{
backgroundColor = Theme.SelectedBackground.WithAlpha(0.25f);
}
else if (isEven)
{
backgroundColor = Theme.SurfaceLightBackground.WithAlpha(0.1f);
}
if (_timeSinceCreated < FadeDurationSeconds)
{
var fraction = FadeDurationSeconds - _timeSinceCreated;
IsAnimating = true;
textColor = textColor.LerpTo(Color.Green, fraction);
backgroundColor = backgroundColor.LerpTo(Color.Green.WithAlpha(0.6f), fraction);
}
else if (_timeSinceLastRun < FadeDurationSeconds)
{
var fraction = FadeDurationSeconds - _timeSinceLastRun;
IsAnimating = true;
textColor = textColor.LerpTo(Color.Orange, fraction);
backgroundColor = backgroundColor.LerpTo(Color.Orange.WithAlpha(0.6f), fraction);
}
else
{
IsAnimating = false;
}
Paint.SetBrush(backgroundColor);
Paint.DrawRect(fullRect);
Paint.Pen = textColor.WithAlpha(0.6f);
rect.Left += 4;
rect.Left += Paint.DrawIcon(rect, _effect.Icon ?? "link", 16, TextFlag.LeftCenter).Width + 4;
Paint.Pen = textColor;
rect.Left += Paint.DrawText(rect, _effect.Name ?? "Effect", TextFlag.LeftCenter).Width + 4;
if (_effect.Location is { } location && DebuggerWidget.ShowSourcePath)
{
rect.Right -= 4;
var filename = Path.GetFileName(location);
var text = Paint.GetElidedText(filename, rect.Width, ElideMode.Left, TextFlag.RightCenter);
Paint.Pen = textColor.WithAlpha(0.4f);
Paint.DrawText(rect, text, TextFlag.RightCenter);
}
}
}
#endif