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; /// /// Things related to picking a hidden input after dropping a node or connection onto an item /// 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; } /// /// 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 /// 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 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(); } }