325 lines
14 KiB
C#
325 lines
14 KiB
C#
using System.Diagnostics;
|
||
using ImGuiNET;
|
||
using T3.Core.Model;
|
||
using T3.Core.Operator;
|
||
using T3.Core.Operator.Slots;
|
||
using T3.Editor.Gui.MagGraph.Model;
|
||
using T3.Editor.Gui.MagGraph.States;
|
||
using T3.Editor.Gui.Styling;
|
||
using T3.Editor.Gui.Styling.Markdown;
|
||
using T3.Editor.Gui.UiHelpers;
|
||
using T3.Editor.UiModel.Commands.Graph;
|
||
using T3.Editor.UiModel.InputsAndTypes;
|
||
using T3.Editor.UiModel.ProjectHandling;
|
||
|
||
namespace T3.Editor.Gui.MagGraph.Interaction;
|
||
|
||
/// <summary>
|
||
/// Things related to picking a hidden input after dropping a node or connection onto an item
|
||
/// </summary>
|
||
internal static class InputPicking
|
||
{
|
||
internal static bool TryInitializeInputSelectionPickerForDraggedItem(GraphUiContext context)
|
||
{
|
||
if (context.ActiveSourceItem == null)
|
||
return false;
|
||
|
||
foreach (var otherItem in context.Layout.Items.Values)
|
||
{
|
||
if (otherItem == context.ActiveSourceItem)
|
||
continue;
|
||
|
||
if (!otherItem.Area.Contains(context.PeekAnchorInCanvas))
|
||
continue;
|
||
|
||
context.ItemForInputSelection = otherItem;
|
||
context.ShouldAttemptToSnapToInput = true;
|
||
context.PeekAnchorInCanvas = new Vector2(context.ActiveSourceItem.Area.Max.X - MagGraphItem.GridSize.Y * 0.25f,
|
||
context.ActiveSourceItem.Area.Min.Y + MagGraphItem.GridSize.Y * 0.5f);
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
internal static bool TryInitializeAtPosition(GraphUiContext context, Vector2 peekPosInCanvas)
|
||
{
|
||
if (context.ActiveSourceItem == null)
|
||
return false;
|
||
|
||
foreach (var otherItem in context.Layout.Items.Values)
|
||
{
|
||
if (otherItem == context.ActiveSourceItem)
|
||
continue;
|
||
|
||
if (!otherItem.Area.Contains(peekPosInCanvas))
|
||
continue;
|
||
|
||
context.ItemForInputSelection = otherItem;
|
||
context.PeekAnchorInCanvas = peekPosInCanvas;
|
||
context.ShouldAttemptToSnapToInput = false;
|
||
context.StartOrContinueMacroCommand("Connect operators");
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// After snapping picking an hidden input field for connection, this
|
||
/// method can be called to...
|
||
/// - move the dragged items to the snapped position
|
||
/// - move all other relevant snapped items down
|
||
/// - create the connection
|
||
/// </summary>
|
||
private static void TryConnectHiddenInput(GraphUiContext context, IInputUi targetInputUi)
|
||
{
|
||
var composition = context.CompositionInstance;
|
||
|
||
Debug.Assert(context.ActiveSourceItem != null && context.ItemForInputSelection != null);
|
||
Debug.Assert(context.MacroCommand != null);
|
||
Debug.Assert(context.ItemForInputSelection.Variant == MagGraphItem.Variants.Operator); // This will bite us later...
|
||
|
||
if (context.ActiveSourceItem.OutputLines.Length == 0)
|
||
{
|
||
Log.Warning("no visible output to connect?");
|
||
return;
|
||
}
|
||
|
||
var inputConnectionCount = context.CompositionInstance.Symbol.Connections.Count(c => c.TargetParentOrChildId == context.ItemForInputSelection.Id
|
||
&& c.TargetSlotId == targetInputUi.Id);
|
||
|
||
var addedCount = 0;
|
||
if (context.TempConnections.Count > 1 && targetInputUi.InputDefinition.IsMultiInput)
|
||
{
|
||
// Connect all dragged connections to the picked multi-input...
|
||
foreach (var tempConnection in context.TempConnections)
|
||
{
|
||
if (tempConnection.SourceItem == null || tempConnection.SourceOutput == null)
|
||
continue;
|
||
|
||
var connectionToAdd = new Symbol.Connection(tempConnection.SourceParentOrChildId,
|
||
tempConnection.SourceOutput.Id,
|
||
context.ItemForInputSelection.Id,
|
||
targetInputUi.Id);
|
||
|
||
if (Structure.CheckForCycle(composition.Symbol, connectionToAdd))
|
||
{
|
||
Log.Debug("This action is not allowed. This connection would create a cycle.");
|
||
continue;
|
||
}
|
||
|
||
context.MacroCommand.AddAndExecCommand(new AddConnectionCommand(composition.Symbol,
|
||
connectionToAdd,
|
||
inputConnectionCount + addedCount));
|
||
addedCount++;
|
||
}
|
||
|
||
if (addedCount == 0)
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
var sourceParentOrChildId = context.ActiveSourceItem.Variant == MagGraphItem.Variants.Input ? Guid.Empty : context.ActiveSourceItem.Id;
|
||
var connectionToAdd = new Symbol.Connection(sourceParentOrChildId,
|
||
context.ActiveSourceOutputId,
|
||
context.ItemForInputSelection.Id,
|
||
targetInputUi.Id);
|
||
|
||
if (Structure.CheckForCycle(composition.Symbol, connectionToAdd))
|
||
{
|
||
Log.Debug("This action is not allowed. This connection would create a cycle.");
|
||
return;
|
||
}
|
||
|
||
context.MacroCommand.AddAndExecCommand(new AddConnectionCommand(composition.Symbol,
|
||
connectionToAdd,
|
||
inputConnectionCount));
|
||
addedCount = 1;
|
||
}
|
||
|
||
// Find insertion index
|
||
var visibleInputLines = context.ItemForInputSelection.InputLines;
|
||
var allInputSlots = context.ItemForInputSelection.Instance!.Inputs;
|
||
var selectedInputId = targetInputUi.InputDefinition.Id;
|
||
|
||
var insertionLineIndex = GetInsertionLineIndex(allInputSlots, visibleInputLines, selectedInputId, out var shouldPushDown);
|
||
|
||
if (shouldPushDown)
|
||
MagItemMovement.MoveSnappedItemsVertically(context,
|
||
MagItemMovement.CollectSnappedItems(context.ItemForInputSelection),
|
||
context.ItemForInputSelection.PosOnCanvas.Y + MagGraphItem.GridSize.Y * (insertionLineIndex - 0.5f),
|
||
MagGraphItem.GridSize.Y * addedCount);
|
||
|
||
context.ItemMovement.Reset();
|
||
context.Layout.FlagStructureAsChanged();
|
||
}
|
||
|
||
public static int GetInsertionLineIndex(IReadOnlyList<IInputSlot> allInputSlots, MagGraphItem.InputLine[] visibleInputLines,
|
||
Guid selectedInputId, out bool shouldPushDown)
|
||
{
|
||
var insertionLineIndex = 0;
|
||
var isSlotVisible = false;
|
||
var isMultiInput = false;
|
||
var isConnected = false;
|
||
foreach (var slot in allInputSlots)
|
||
{
|
||
var slotId = slot.Id;
|
||
isSlotVisible = insertionLineIndex < visibleInputLines.Length
|
||
&& slotId == visibleInputLines[insertionLineIndex].Input.Id;
|
||
|
||
if (isSlotVisible)
|
||
{
|
||
var visibleInputLine = visibleInputLines[insertionLineIndex];
|
||
isMultiInput = visibleInputLine.InputUi.InputDefinition.IsMultiInput;
|
||
|
||
// Go through end of visible input group (could be multiple lines for multiInputs)
|
||
while (insertionLineIndex < visibleInputLines.Length && visibleInputLines[insertionLineIndex].Input.Id == slotId)
|
||
{
|
||
isConnected = visibleInputLines[insertionLineIndex].ConnectionIn != null;
|
||
insertionLineIndex++;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
isMultiInput = false;
|
||
isConnected = false;
|
||
}
|
||
|
||
if (slotId == selectedInputId)
|
||
break;
|
||
}
|
||
|
||
shouldPushDown = !isSlotVisible || (isMultiInput && isConnected);
|
||
|
||
if (isSlotVisible && !isConnected)
|
||
insertionLineIndex--;
|
||
|
||
return insertionLineIndex;
|
||
}
|
||
|
||
internal static void DrawHiddenInputSelector(GraphUiContext context)
|
||
{
|
||
if (context.ItemForInputSelection == null || context.DraggedPrimaryOutputType == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (context.StateMachine.CurrentState != GraphStates.PickInput)
|
||
{
|
||
return;
|
||
}
|
||
|
||
var screenPos = context.View.TransformPosition(context.PeekAnchorInCanvas);
|
||
|
||
ImGui.SetNextWindowPos(screenPos);
|
||
|
||
ImGui.PushStyleVar(ImGuiStyleVar.ChildRounding, 5);
|
||
ImGui.PushStyleVar(ImGuiStyleVar.PopupBorderSize, 0);
|
||
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.One * 4);
|
||
ImGui.PushStyleVar(ImGuiStyleVar.ButtonTextAlign, new Vector2(0,0));
|
||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0,0));
|
||
//ImGui.PushStyleColor(ImGuiCol.ScrollbarBg, Color.Transparent.Rgba);
|
||
var lastSize = WindowContentExtend.GetLastAndReset();
|
||
// + ImGui.GetStyle().WindowPadding * 2
|
||
// + new Vector2(10,3);
|
||
|
||
//lastSize.Y = lastSize.Y.Clamp(0f,300f);
|
||
|
||
|
||
ImGui.PushStyleColor(ImGuiCol.ChildBg, UiColors.BackgroundFull.Fade(0.7f).Rgba);
|
||
ImGui.PushStyleColor(ImGuiCol.Button, UiColors.BackgroundFull.Fade(0.0f).Rgba);
|
||
|
||
//ImGui.PushStyleColor(ImGuiCol.PopupBg, UiColors.BackgroundFull.Fade(0.6f).Rgba);
|
||
if (ImGui.BeginChild("Popup",
|
||
lastSize,
|
||
ImGuiChildFlags.Borders | ImGuiChildFlags.AlwaysUseWindowPadding,
|
||
ImGuiWindowFlags.NoResize
|
||
| ImGuiWindowFlags.NoScrollbar
|
||
))
|
||
{
|
||
var visibleInputCount = 0;
|
||
|
||
var childUi = context.ItemForInputSelection.SymbolUi;
|
||
if (childUi != null)
|
||
{
|
||
if (context.DraggedPrimaryOutputType == null || !TypeNameRegistry.Entries.TryGetValue(context.DraggedPrimaryOutputType, out var typeName))
|
||
{
|
||
typeName = context.DraggedPrimaryOutputType.Name;
|
||
}
|
||
|
||
ImGui.PushFont(Fonts.FontSmall);
|
||
ImGui.TextColored(UiColors.TextMuted, typeName + " inputs");
|
||
ImGui.PopFont();
|
||
|
||
// With multiple dragged connections only multi-inputs can take them all...
|
||
var onlyMultiInputs = context.TempConnections.Count > 1;
|
||
|
||
var inputIndex = 0;
|
||
foreach (var inputUi in childUi.InputUis.Values)
|
||
{
|
||
var input = context.ItemForInputSelection.Instance!.Inputs[inputIndex];
|
||
if (inputUi.Type == context.DraggedPrimaryOutputType
|
||
&& (!onlyMultiInputs || inputUi.InputDefinition.IsMultiInput))
|
||
{
|
||
var isConnected = input.HasInputConnections;
|
||
var prefix = isConnected ? "× " : " ";
|
||
var inputDefinitionName = prefix + inputUi.InputDefinition.Name;
|
||
var labelSize = ImGui.CalcTextSize(inputDefinitionName);
|
||
var width = MathF.Max(lastSize.X -4, labelSize.X +30);
|
||
var buttonSize = new Vector2(width, ImGui.GetFrameHeight());
|
||
|
||
visibleInputCount++;
|
||
|
||
if (ImGui.Button(inputDefinitionName, buttonSize))
|
||
{
|
||
TryConnectHiddenInput(context, inputUi);
|
||
context.CompleteMacroCommand();
|
||
|
||
Reset(context);
|
||
context.StateMachine.SetState(GraphStates.Default, context);
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(inputUi.Description))
|
||
{
|
||
var description = inputUi.Description;
|
||
CustomComponents.TooltipForLastItem(() => MarkdownTooltip.Draw(description));
|
||
}
|
||
|
||
WindowContentExtend.ExtendToLastItem();
|
||
}
|
||
|
||
inputIndex++;
|
||
}
|
||
|
||
|
||
}
|
||
|
||
// Cancel by clicking outside
|
||
var isPopupHovered = ImRect.RectWithSize(ImGui.GetWindowPos(), ImGui.GetWindowSize())
|
||
.Contains(ImGui.GetMousePos());
|
||
|
||
if (visibleInputCount ==0 || !isPopupHovered && ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
||
{
|
||
context.StateMachine.SetState(GraphStates.Default, context);
|
||
}
|
||
|
||
}
|
||
|
||
ImGui.EndChild();
|
||
ImGui.PopStyleVar(5);
|
||
ImGui.PopStyleColor(2);
|
||
}
|
||
|
||
public static void Reset(GraphUiContext context)
|
||
{
|
||
context.TempConnections.Clear();
|
||
context.ActiveSourceItem = null;
|
||
context.DraggedPrimaryOutputType = null;
|
||
}
|
||
|
||
public static void Init(GraphUiContext context)
|
||
{
|
||
WindowContentExtend.GetLastAndReset();
|
||
}
|
||
} |