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

163 lines
7.2 KiB
C#

#nullable enable
using ImGuiNET;
using T3.Core.DataTypes.Vector;
using T3.Core.Operator;
using T3.Editor.Gui.MagGraph.Model;
using T3.Editor.Gui.MagGraph.States;
using T3.Editor.UiModel.Commands.Graph;
using T3.Editor.UiModel.InputsAndTypes;
using T3.Editor.UiModel.ProjectHandling;
using Vector2 = System.Numerics.Vector2;
namespace T3.Editor.Gui.MagGraph.Interaction;
/// <summary>
/// Handles snapping to input and output connections
/// </summary>
internal static class InputSnapper
{
public static void Update(GraphUiContext context)
{
BestInputMatch = _bestInputMatchForCurrentFrame;
_bestInputMatchForCurrentFrame = new InputMatch();
if (context.StateMachine.CurrentState != GraphStates.HoldingConnectionEnd)
return;
if (BestInputMatch.Item != null)
{
// TODO: Make beautiful
ImGui.GetWindowDrawList().AddCircle(context.View.TransformPosition(BestInputMatch.PosOnScreen), 20, Color.Red);
}
}
public static void RegisterAsPotentialTargetInput(MagGraphItem item, Vector2 posOnScreen, Guid slotId,
InputSnapTypes inputSnapType = InputSnapTypes.Normal, int multiInputIndex = 0)
{
var mousePos = ImGui.GetMousePos();
var distance = Vector2.Distance(posOnScreen, mousePos);
if ( distance < _bestInputMatchForCurrentFrame.Distance)
{
_bestInputMatchForCurrentFrame = new InputMatch(item, slotId, posOnScreen, inputSnapType, multiInputIndex, distance);
}
}
public static bool TryToReconnect(GraphUiContext context)
{
if (BestInputMatch.Item == null)
return false;
if (context.TempConnections.Count == 0)
return false;
if (context.MacroCommand == null)
{
context.StartMacroCommand("Create connection");
}
// Only multi-inputs can take more than one dragged connection...
var targetIsMultiInput = false;
foreach (var line in BestInputMatch.Item.InputLines)
{
if (line.Id != BestInputMatch.SlotId)
continue;
targetIsMultiInput = line.InputUi?.InputDefinition.IsMultiInput ?? false;
break;
}
var connectionCount = targetIsMultiInput ? context.TempConnections.Count : 1;
var targetParentOrChildId = BestInputMatch.Item.Variant == MagGraphItem.Variants.Output ? Guid.Empty : BestInputMatch.Item.Id;
var existingConnection = context.CompositionInstance.Symbol.Connections.FirstOrDefault(c => c.TargetParentOrChildId == targetParentOrChildId
&& c.TargetSlotId == BestInputMatch.SlotId);
if (existingConnection != null && (BestInputMatch.InputSnapType == InputSnapTypes.Normal || BestInputMatch.InputSnapType == InputSnapTypes.ReplaceMultiInput))
{
context.MacroCommand?.AddAndExecCommand(new DeleteConnectionCommand(context.CompositionInstance.Symbol, existingConnection, BestInputMatch.MultiInputIndex));
}
var multiInputIndex = BestInputMatch.MultiInputIndex;
var adjustedMultiInputIndex = multiInputIndex + (BestInputMatch.InputSnapType == InputSnapTypes.InsertAfterMultiInput ? 1 : 0);
var addedCount = 0;
for (var index = 0; index < connectionCount; index++)
{
var tempConnection = context.TempConnections[index];
var sourceParentOrChildId = tempConnection.SourceItem.Variant == MagGraphItem.Variants.Input ? Guid.Empty : tempConnection.SourceItem.Id;
var connectionToAdd = new Symbol.Connection(sourceParentOrChildId,
tempConnection.SourceOutput.Id,
targetParentOrChildId,
BestInputMatch.SlotId);
if (Structure.CheckForCycle(context.CompositionInstance.Symbol, connectionToAdd))
{
Log.Debug("This action is not allowed. This connection would create a cycle.");
continue;
}
context.MacroCommand!.AddAndExecCommand(new AddConnectionCommand(context.CompositionInstance.Symbol,
connectionToAdd,
adjustedMultiInputIndex + addedCount));
addedCount++;
}
if (addedCount == 0)
return false;
// Push down other items
{
var lines = BestInputMatch.Item.InputLines;
var inputLineIndex = 0;
while ( inputLineIndex < lines.Length && lines[inputLineIndex].Id != BestInputMatch.SlotId)
{
inputLineIndex++;
}
var wasInputLineWasConnected = lines[inputLineIndex].ConnectionIn != null;
var hasHeightChanged = (BestInputMatch.InputSnapType == InputSnapTypes.InsertAfterMultiInput ||
BestInputMatch.InputSnapType == InputSnapTypes.InsertBeforeMultiInput)
|| (BestInputMatch.InputSnapType == InputSnapTypes.Normal
&& !wasInputLineWasConnected
&& BestInputMatch.Item.Variant == MagGraphItem.Variants.Operator
&& lines[inputLineIndex].InputUi.Relevancy == Relevancy.Optional
&& inputLineIndex > 0);
// Every connection beyond the first adds another input line...
var rowsToInsert = hasHeightChanged ? addedCount : addedCount - 1;
if (rowsToInsert > 0 && inputLineIndex < lines.Length)
{
var insertionIndex = inputLineIndex + adjustedMultiInputIndex;
var collectSnappedItems = MagItemMovement.CollectSnappedItems(BestInputMatch.Item);
collectSnappedItems.Remove(BestInputMatch.Item);
MagItemMovement.MoveSnappedItemsVertically(context,
collectSnappedItems,
BestInputMatch.Item.PosOnCanvas.Y + MagGraphItem.GridSize.Y * (insertionIndex - 0.5f),
MagGraphItem.GridSize.Y * rowsToInsert);
}
}
return true;
}
public enum InputSnapTypes
{
Normal,
InsertBeforeMultiInput,
ReplaceMultiInput,
InsertAfterMultiInput,
}
public sealed record InputMatch(
MagGraphItem? Item = null,
Guid SlotId = default,
Vector2 PosOnScreen = default,
InputSnapTypes InputSnapType = InputSnapTypes.Normal,
int MultiInputIndex = 0,
float Distance = OutputSnapper.SnapThreshold);
private static InputMatch _bestInputMatchForCurrentFrame = new();
public static InputMatch BestInputMatch = new();
}