Files
2026-07-13 13:13:17 +08:00

303 lines
12 KiB
C#

using ImGuiNET;
using T3.Core.DataTypes.Vector;
using T3.Core.Operator;
using T3.Core.Utils;
using T3.Editor.Gui.Legacy.Interaction;
using T3.Editor.Gui.Legacy.Interaction.Connections;
using T3.Editor.Gui.Styling;
using T3.Editor.Gui.UiHelpers;
using T3.Editor.UiModel;
using T3.Editor.UiModel.ProjectHandling;
// ReSharper disable LoopCanBeConvertedToQuery
namespace T3.Editor.Gui.Legacy;
/// <summary>Rendering a node graph</summary>
/// <remarks>
/// Rendering the graph is complicated because:
/// - Connection has no real model to store computations
/// - Connections are defined by Guid references to Symbol-Definitions
/// - Computing connection end point position involves...
/// - ...many states of the graph nodes
/// - ...connections under construction
/// - ...potentially hidden connections
/// - ...layout of connections into multi input slots
///
/// This implementation first collects the information required to drawing the input sockets
/// and connection links over several passes in which the information about visible connection-lines
/// is collected into a list of ConnectionLineUi instances. These passes are...
///
/// 1. Initializes lists of ConnectionLineUis
/// 2. Fill the lists of which nodes are connected to which lines
/// 3. Draw nodes and their sockets and set positions for connection lines
/// 4. Draw inputs
/// 5. Draw outputs
/// 6. Draw connection lines
///</remarks>
internal sealed partial class Graph
{
private readonly ProjectView _components;
private readonly GraphView _view;
private readonly Func<SymbolBrowser> _getSymbolBrowser;
public Graph(ProjectView components, GraphView view, Func<SymbolBrowser> getSymbolBrowser)
{
_components = components;
_view = view;
_getSymbolBrowser = getSymbolBrowser;
_connectionSorter = new Graph.ConnectionSorter(this, view);
}
public void DrawGraph(ImDrawListPtr drawList, bool preventInteraction, Instance composition, float graphOpacity)
{
var canvas = _components.GraphView.Canvas;
var needsReinit = false;
var graphSymbol = composition.Symbol;
var tempConnections = ConnectionMaker.GetTempConnectionsFor(_components.GraphView);
// _symbolUi = SymbolUiRegistry.Entries[graphSymbol.Id];
// _childUis = _symbolUi.ChildUis;
// _inputUisById = _symbolUi.InputUis;
// _outputUisById = _symbolUi.OutputUis;
if (tempConnections.Count > 0
|| AllConnections.Count != tempConnections.Count + graphSymbol.Connections.Count)
{
_lastCheckSum = 0;
needsReinit = true;
}
// Checksum
if (!needsReinit)
{
var checkSum = 0;
for (var index = 0; index < graphSymbol.Connections.Count; index++)
{
var c = graphSymbol.Connections[index];
checkSum = checkSum * 31 + c.GetHashCode() * (index+1);
}
foreach (var c in tempConnections)
{
checkSum = checkSum * 31+ c.GetHashCode();
}
if (checkSum != _lastCheckSum)
{
needsReinit = true;
_lastCheckSum = checkSum;
}
}
//needsReinit = true;
var compositionUi = composition.GetSymbolUi();
if (needsReinit)
{
AllConnections.Clear();
AllConnections.AddRange(graphSymbol.Connections);
AllConnections.AddRange(tempConnections);
// 1. Initializes lists of ConnectionLineUis
_connectionSorter.Init();
// 2. Collect which nodes are connected to which lines
var symbolBrowser = _getSymbolBrowser();
foreach (var c in AllConnections)
{
_connectionSorter.CreateAndSortLineUi(c, compositionUi, symbolBrowser);
}
}
else
{
foreach (var c in _connectionSorter.Lines)
{
c.IsSelected = false;
}
}
//drawList.ChannelsSplit(2);
drawList.ChannelsSetCurrent((int)Channels.Operators);
compositionUi = composition.GetSymbolUi();
// 3. Draw Nodes and their sockets and set positions for connection lines
foreach (var instance in _components.CompositionInstance.Children.Values)
{
if (instance == null)
continue;
if (!compositionUi.ChildUis.TryGetValue(instance.SymbolChildId, out var childUi))
{
Log.Warning($"ChildUi not found for {instance.SymbolChildId}");
continue;
}
var isSelected = _components.NodeSelection.IsNodeSelected(childUi);
// todo - remove nodes that are not in the graph anymore?
if (!_graphNodes.TryGetValue(childUi, out var node))
{
node = new GraphNode(_components, _view, _connectionSorter);
_graphNodes[childUi] = node;
}
node.Draw(drawList, graphOpacity, isSelected, childUi, instance, preventInteraction);
}
// 4. Draw Inputs Nodes
for (var index = 0; index < graphSymbol.InputDefinitions.Count; index++)
{
var inputDef = graphSymbol.InputDefinitions[index];
var inputUi = compositionUi.InputUis[inputDef.Id];
var isSelectedOrHovered = InputNode.Draw(_components, _view, drawList, inputDef, inputUi, index);
var sourcePos = new Vector2(
InputNode._lastScreenRect.Max.X + GraphNode.UsableSlotThickness,
InputNode._lastScreenRect.GetCenter().Y
);
foreach (var line in _connectionSorter.GetLinesFromInputNodes(inputUi, inputDef.Id))
{
line.SourcePosition = sourcePos;
line.IsSelected |= isSelectedOrHovered;
}
}
// 5. Draw Output Nodes
foreach (var (outputId, outputNode) in compositionUi.OutputUis)
{
var outputDef = graphSymbol.OutputDefinitions.Find(od => od.Id == outputId);
OutputNode.Draw(_view, _components, drawList, outputDef, outputNode);
var targetPos = new Vector2(OutputNode.LastScreenRect.Min.X ,
OutputNode.LastScreenRect.GetCenter().Y);
foreach (var line in _connectionSorter.GetLinesToOutputNodes(outputNode, outputId))
{
line.TargetPosition = targetPos;
}
}
// 6. Draw ConnectionLines
foreach (var line in _connectionSorter.Lines)
{
line.Draw(canvas.Scale, drawList);
}
// 7. Draw Sections
drawList.ChannelsSetCurrent((int)Channels.Sections);
foreach (var section in compositionUi.Sections.Values)
{
//var posOnScreen = GraphCanvas.Current.TransformPosition(section.Position);
//drawList.AddRectFilled( posOnScreen, posOnScreen + new Vector2(300,300), Color.Green);
// todo - remove sections that are not in the graph anymore?
if(!_sectionElements.TryGetValue(section, out var sectionElement))
{
sectionElement = new SectionElement(_components, section);
_sectionElements[section] = sectionElement;
}
sectionElement.Draw(drawList, _view);
}
drawList.ChannelsMerge();
}
internal void RenameSection(Section section)
{
if (!_sectionElements.TryGetValue(section, out var sectionElement))
{
sectionElement = new SectionElement(_components, section);
_sectionElements[section] = sectionElement;
}
sectionElement.StartRenaming();
}
internal sealed class ConnectionLineUi
{
public readonly Symbol.Connection Connection;
public Vector2 TargetPosition;
public Vector2 SourcePosition;
public Color ColorForType;
public bool IsSelected;
public int UpdateCount;
public int FramesSinceLastUsage;
public ImRect SourceNodeArea;
public ImRect TargetNodeArea;
public bool IsAboutToBeReplaced;
private Graph _graph;
internal ConnectionLineUi(Symbol.Connection connection, Graph graph)
{
Connection = connection;
_graph = graph;
}
internal void Draw(Vector2 canvasScale, ImDrawListPtr drawList)
{
var color = IsSelected
? ColorVariations.Highlight.Apply(ColorForType)
: ColorVariations.ConnectionLines.Apply(ColorForType);
if (IsAboutToBeReplaced)
color = Color.Mix(color, UiColors.StatusAttention, (float)Math.Sin(ImGui.GetTime() * 15) / 2 + 0.5f);
if (!IsSelected)
color = color.Fade(0.6f);
var usageFactor = Math.Max(0, 1 - FramesSinceLastUsage / 50f);
var thickness = ((1 - 1 / (UpdateCount + 1f)) * 3 + 1) * 0.5f * (usageFactor * 2 + 1);
if (UserSettings.Config.UseArcConnections)
{
var isHovering = LegacyConnectionDrawer.Draw(canvasScale, new ImRect(SourcePosition, SourcePosition + new Vector2(10, 10)),
SourcePosition,
TargetNodeArea,
TargetPosition,
color,
thickness,
out var hoverPositionOnLine);
const float minDistanceToTargetSocket = 10;
if (isHovering && Vector2.Distance(hoverPositionOnLine, TargetPosition) > minDistanceToTargetSocket
&& Vector2.Distance(hoverPositionOnLine, SourcePosition) > minDistanceToTargetSocket)
{
ConnectionSplitHelper.RegisterAsPotentialSplit(Connection, ColorForType, hoverPositionOnLine);
}
}
else
{
var tangentLength = MathUtils.RemapAndClamp(Vector2.Distance(SourcePosition, TargetPosition),
30, 300,
5, 200);
drawList.AddBezierCubic(
SourcePosition,
SourcePosition + new Vector2(tangentLength, 0),
TargetPosition + new Vector2(-tangentLength, 0),
TargetPosition,
color,
thickness,
num_segments: 20);
}
}
}
private enum Channels
{
Sections = 0,
Operators = 1,
}
private int _lastCheckSum;
private readonly Graph.ConnectionSorter _connectionSorter;
// Try to avoid allocations
private readonly List<Symbol.Connection> AllConnections = new(100);
private readonly Dictionary<SymbolUi.Child, GraphNode> _graphNodes = new();
private readonly Dictionary<Section, SectionElement> _sectionElements = new();
}