using System.Runtime.CompilerServices; using ImGuiNET; using SharpDX.Direct3D11; using T3.Core.DataTypes.Vector; using T3.Core.Model; using T3.Core.Operator; using T3.Core.Operator.Interfaces; using T3.Core.Operator.Slots; using T3.Core.Resource; using T3.Core.Utils; using T3.Editor.Gui.Dialogs; using T3.Editor.Gui.OpUis; using T3.Editor.Gui.Input; using T3.Editor.Gui.Interaction.TransformGizmos; using T3.Editor.Gui.OutputUi; using T3.Editor.Gui.Styling; using T3.Editor.Gui.UiHelpers; using T3.Editor.Gui.Windows; using T3.Editor.Gui.Interaction; using T3.Editor.UiModel; using T3.Editor.UiModel.InputsAndTypes; using T3.Editor.UiModel.ProjectHandling; using T3.Editor.UiModel.Selection; using Color = T3.Core.DataTypes.Vector.Color; using Texture2D = T3.Core.DataTypes.Texture2D; using Vector2 = System.Numerics.Vector2; using T3.Editor.Gui.Interaction.Keyboard; using T3.Editor.Gui.Legacy.Interaction; using T3.Editor.Gui.Legacy.Interaction.Connections; using T3.Editor.Gui.MagGraph.Interaction; namespace T3.Editor.Gui.Legacy; /// /// Renders a graphic representation of a SymbolChild within the current GraphWindow. /// internal sealed class GraphNode { public GraphNode(ProjectView projectView, GraphView view, Legacy.Graph.ConnectionSorter sorter) { _projectView = projectView; _view = view; _sorter = sorter; lock (DraggedIds) { if (!DraggedIds.TryGetValue(_view, out _dragInfo)) { _dragInfo = new DraggedIdInfo(); DraggedIds[_view] = _dragInfo; } } } public void Draw(ImDrawListPtr drawList, float opacity, bool isSelected, SymbolUi.Child childUi, Instance instance, bool preventInteraction) { var symbolUi = instance.GetSymbolUi(); var nodeHasHiddenMatchingInputs = false; var visibleInputUis = FindVisibleInputUis(symbolUi, childUi, ref nodeHasHiddenMatchingInputs); var tempConnections = ConnectionMaker.GetTempConnectionsFor(_view); bool hasConnections = tempConnections.Count > 0; bool inActiveWindow = _view == ProjectView.Focused?.GraphView; var framesSinceLastUpdate = 100; foreach (var output in instance.Outputs) { framesSinceLastUpdate = Math.Min(framesSinceLastUpdate, output.DirtyFlag.FramesSinceLastUpdate); } OpUi.CustomUiResult customUiResult = OpUi.CustomUiResult.None; var newNodeSize = ComputeNodeSize(childUi, visibleInputUis, _sorter); AdjustGroupLayoutAfterResize(childUi, newNodeSize, instance.Parent.GetSymbolUi()); _usableScreenRect = _view.TransformRect(new ImRect(childUi.PosOnCanvas, childUi.PosOnCanvas + childUi.Size)); _selectableScreenRect = _usableScreenRect; if (UserSettings.Config.ShowThumbnails) PreparePreviewAndExpandSelectableArea(instance); _isVisible = ImGui.IsRectVisible(_selectableScreenRect.Min, _selectableScreenRect.Max); var isNodeHovered = false; ImGui.PushID(childUi.SymbolChild.Id.GetHashCode()); { if (_isVisible) { if (instance is IStatusProvider statusProvider) { var statusLevel = statusProvider.GetStatusLevel(); if (statusLevel != IStatusProvider.StatusLevel.Success && statusLevel != IStatusProvider.StatusLevel.Undefined) { ImGui.SetCursorScreenPos(_usableScreenRect.Min - new Vector2(10, 12) * T3Ui.UiScaleFactor); ImGui.InvisibleButton("#warning", new Vector2(15, 15)); var color = statusLevel switch { IStatusProvider.StatusLevel.Tip => UiColors.StatusActivated, IStatusProvider.StatusLevel.Notice => UiColors.StatusAttention, IStatusProvider.StatusLevel.Warning => UiColors.StatusWarning, IStatusProvider.StatusLevel.Error => UiColors.StatusError, _ => UiColors.StatusError }; var icon = statusLevel == IStatusProvider.StatusLevel.Tip ? Icon.Tip : Icon.Warning; Icons.DrawIconOnLastItem(icon, color); CustomComponents.TooltipForLastItem(color, statusLevel.ToString(), statusProvider.GetStatusMessage(), false); } } if (!string.IsNullOrEmpty(childUi.Comment)) { ImGui.SetCursorScreenPos(new Vector2(_usableScreenRect.Max.X, _usableScreenRect.Min.Y) - new Vector2(3, 12) * T3Ui.UiScaleFactor * T3Ui.UiScaleFactor); if (ImGui.InvisibleButton("#comment", new Vector2(15, 15))) { _projectView.NodeSelection.SetSelection(childUi, instance); _view.EditCommentDialog.ShowNextFrame(); } Icons.DrawIconOnLastItem(Icon.Comment, UiColors.ForegroundFull); CustomComponents.TooltipForLastItem( UiColors.Text, childUi.Comment, null, false); } // Resize indicator if (childUi.Style == SymbolUi.Child.Styles.Resizable) { ImGui.SetMouseCursor(ImGuiMouseCursor.ResizeNWSE); ImGui.SetCursorScreenPos(_usableScreenRect.Max - new Vector2(10, 10) * T3Ui.UiScaleFactor); ImGui.Button("##resize", new Vector2(10, 10) * T3Ui.UiScaleFactor); if (ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left)) { var delta = _view.InverseTransformDirection(ImGui.GetIO().MouseDelta); childUi.Size += delta; } ImGui.SetMouseCursor(ImGuiMouseCursor.Arrow); } // Rendering var typeColor = (childUi.SymbolChild.Symbol.OutputDefinitions.Count > 0 ? TypeUiRegistry.GetPropertiesForType(childUi.SymbolChild.Symbol.OutputDefinitions[0].ValueType).Color : UiColors.Gray).Fade(opacity); var backgroundColor = typeColor; // Background var isHighlighted = FrameStats.IsIdHovered(instance.SymbolChildId); if (framesSinceLastUpdate > 2) { var fadeFactor = MathUtils.RemapAndClamp(framesSinceLastUpdate, 0f, 60f, 0f, 1.0f); var mutedColor = ColorVariations.OperatorBackgroundIdle.Apply(backgroundColor).Fade(opacity); backgroundColor = Color.Mix(backgroundColor, mutedColor, fadeFactor); } var backgroundColorWithHover = isHighlighted ? ColorVariations.OperatorBackgroundHover.Apply(backgroundColor) : ColorVariations.OperatorBackground.Apply(backgroundColor); drawList.AddRectFilled(_usableScreenRect.Min, _usableScreenRect.Max, backgroundColorWithHover.Fade(opacity)); // Custom ui customUiResult = instance.DrawCustomUi(drawList, _selectableScreenRect, _view); // Size toggle if (customUiResult == OpUi.CustomUiResult.None && _view.Scale.X > 0.7f) { var pos = new Vector2(_usableScreenRect.Max.X - 15, _usableScreenRect.Min.Y + 2); var transparentWhite = new Color(1, 1, 1, 0.3f * opacity).Rgba; ImGui.SetCursorScreenPos(pos); ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); ImGui.PushStyleColor(ImGuiCol.Button, Color.Transparent.Rgba * opacity); ImGui.PushStyleColor(ImGuiCol.ButtonHovered, transparentWhite); ImGui.PushStyleColor(ImGuiCol.Text, transparentWhite); ImGui.PushFont(Icons.IconFont); if (childUi.Style == SymbolUi.Child.Styles.Default) { if (ImGui.Button(_unfoldLabel, new Vector2(16, 16))) { childUi.Style = SymbolUi.Child.Styles.Expanded; } } else if (childUi.Style != SymbolUi.Child.Styles.Default) { if (ImGui.Button(_foldLabel, new Vector2(16, 16))) { childUi.Style = SymbolUi.Child.Styles.Default; } } ImGui.PopFont(); ImGui.PopStyleVar(); ImGui.PopStyleColor(3); } // Disabled indicator if (childUi.SymbolChild.IsDisabled) { DrawUtils.DrawOverlayLine(drawList, opacity, Vector2.Zero, Vector2.One, _usableScreenRect.Min, _usableScreenRect.Max); DrawUtils.DrawOverlayLine(drawList, opacity, new Vector2(1,0), new Vector2(0,1), _usableScreenRect.Min, _usableScreenRect.Max); } // Bypass indicator if (childUi.SymbolChild.IsBypassed) { DrawUtils.DrawOverlayLine(drawList, opacity, new Vector2(0.05f,0.5f), new Vector2(0.4f,0.5f), _usableScreenRect.Min, _usableScreenRect.Max); DrawUtils.DrawOverlayLine(drawList, opacity, new Vector2(0.6f,0.5f), new Vector2(0.95f,0.5f), _usableScreenRect.Min, _usableScreenRect.Max); DrawUtils.DrawOverlayLine(drawList, opacity, new Vector2(0.35f,0.1f), new Vector2(0.65f,0.9f), _usableScreenRect.Min, _usableScreenRect.Max); DrawUtils.DrawOverlayLine(drawList, opacity, new Vector2(0.65f,0.1f), new Vector2(0.35f,0.9f), _usableScreenRect.Min, _usableScreenRect.Max); } // Interaction if (preventInteraction) { ImGui.SetCursorScreenPos( new Vector2(-5000,-5000)); } else { ImGui.SetCursorScreenPos(_selectableScreenRect.Min); } //-------------------------------------------------------------------------- var buttonSize = new Vector2(_selectableScreenRect.GetWidth().ClampMin(1), _selectableScreenRect.GetHeight().ClampMin(1)); ImGui.InvisibleButton("node", buttonSize); //-------------------------------------------------------------------------- if(!preventInteraction) _view.SelectableNodeMovement.Handle(childUi, instance); isNodeHovered = !preventInteraction && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByPopup) && !_view.SymbolBrowser.IsOpen && ImGui.IsWindowHovered(ImGuiHoveredFlags.AllowWhenBlockedByPopup); // Tooltip if (!isNodeHovered) { //FrameStats.Current.HoveredIds.Remove(childUi.SymbolChild.Id); } else if (UserSettings.Config.EditorHoverPreview && (customUiResult & OpUi.CustomUiResult.PreventTooltip) != OpUi.CustomUiResult.PreventTooltip) { if (UserSettings.Config.SmartGroupDragging) _view.SelectableNodeMovement.HighlightSnappedNeighbours(childUi); //ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); FrameStats.AddHoveredId(childUi.SymbolChild.Id); if (UserSettings.Config.HoverMode != UserSettings.GraphHoverModes.Disabled && !ImGui.IsMouseDragging(ImGuiMouseButton.Left) && !RenamingOperator.IsOpen) { ImGui.BeginTooltip(); { ImGui.SetNextWindowSizeConstraints(new Vector2(200, 200*9/16f), new Vector2(200, 200*9/16f)); ImGui.BeginChild("##innerTooltip"); { TransformGizmoHandling.SetDrawList(drawList); _imageCanvasForTooltips.Update(); _imageCanvasForTooltips.SetAsCurrent(); if (instance.Outputs.Count > 0) { var firstOutput = instance.Outputs[0]; IOutputUi outputUi = symbolUi.OutputUis[firstOutput.Id]; _evaluationContext.Reset(); _evaluationContext.RequestedResolution = new Int2(1280 / 2, 720 / 2); outputUi.DrawValue(firstOutput, _evaluationContext, "thumbnail", recompute: UserSettings.Config.HoverMode == UserSettings.GraphHoverModes.Live); } ImageOutputCanvas.Deactivate(); TransformGizmoHandling.RestoreDrawList(); } ImGui.EndChild(); } ImGui.EndTooltip(); } } // A work around to detect if node is below mouse while dragging end of new connection if (_selectableScreenRect.Contains(ImGui.GetMousePos())) { _hoveredNodeIdForConnectionTarget = childUi.Id; } var hovered = inActiveWindow && (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByPopup) || FrameStats.Current.HoveredIds.Contains(instance.SymbolChildId)); // A horrible work around to prevent exception because CompositionOp changed during drawing. // A better solution would defer setting the compositionOp to the beginning of next frame. var justOpenedChild = false; if (hovered && (ImGui.IsMouseDoubleClicked(0) || UserActions.OpenOperator.Triggered()) && !RenamingOperator.IsOpen && (customUiResult & OpUi.CustomUiResult.PreventOpenSubGraph) == 0) { if (ImGui.IsWindowFocused() || ImGui.IsWindowHovered(ImGuiHoveredFlags.AllowWhenBlockedByPopup)) { var blocked = false; if (UserSettings.Config.WarnBeforeLibEdit && instance.Symbol.Namespace.StartsWith("Lib.")) { if (UserSettings.Config.WarnBeforeLibEdit) { var count = Structure.CollectDependingSymbols(instance.Symbol).Count(); LibWarningDialog.DependencyCount = count; LibWarningDialog.HandledInstance = instance; _view.LibWarningDialog.ShowNextFrame(); blocked = true; } } if (!blocked) { _projectView.TrySetCompositionOpToChild(instance.SymbolChildId); /////////////////////////// ImGui.CloseCurrentPopup(); justOpenedChild = true; } } } if (!justOpenedChild) { ParameterPopUp.HandleOpenParameterPopUp(childUi, instance, customUiResult, _selectableScreenRect); } DrawPreview(drawList, opacity); // Outline shadow drawList.AddRect(_selectableScreenRect.Min, _selectableScreenRect.Max + Vector2.One, ColorVariations.OperatorOutline.Apply(typeColor), rounding: 0, ImDrawFlags.None); if (isHighlighted) { drawList.AddRect(_selectableScreenRect.Min - Vector2.One * 2, _selectableScreenRect.Max + Vector2.One * 3 , UiColors.ForegroundFull.Fade(0.6f), rounding: 0, ImDrawFlags.None); } // Animation indicator var indicatorCount = 0; if (instance.Symbol.Animator.IsInstanceAnimated(instance)) { DrawIndicator(drawList, UiColors.StatusAnimated, opacity, ref indicatorCount); } // Pinned indicator if (_projectView.NodeSelection.PinnedIds.Contains(instance.SymbolChildId)) { DrawIndicator(drawList, UiColors.Selection, opacity, ref indicatorCount); } // Snapshot indicator { if (childUi.EnabledForSnapshots) { DrawIndicator(drawList, UiColors.StatusAutomated, opacity, ref indicatorCount); } } // Hidden inputs indicator if (nodeHasHiddenMatchingInputs) { var blink = (float)(Math.Sin(ImGui.GetTime() * 10) / 2f + 0.8f); var colorForType = TypeUiRegistry.GetPropertiesForType(tempConnections[0].ConnectionType).Color; colorForType.Rgba.W *= blink; drawList.AddRectFilled( new Vector2(_usableScreenRect.Min.X, _usableScreenRect.Max.Y + 3), new Vector2(_usableScreenRect.Min.X + 10, _usableScreenRect.Max.Y + 5), colorForType); } // Label if (customUiResult == OpUi.CustomUiResult.None && _selectableScreenRect.GetHeight() > 8) { drawList.PushClipRect(_usableScreenRect.Min, _usableScreenRect.Max, true); var useSmallFont = _view.Scale.X < 1 * T3Ui.UiScaleFactor; var font = Fonts.FontBold; var isRenamed = !string.IsNullOrEmpty(childUi.SymbolChild.Name); var fade = MathUtils.SmootherStep(0.2f, 0.6f, _view.Scale.X); drawList.AddText(font, font.FontSize * ( useSmallFont ? _view.Scale.X : 1) , _usableScreenRect.Min + LabelPos, ColorVariations.OperatorLabel.Apply(typeColor).Fade(fade), isRenamed ? $"\"{childUi.SymbolChild.ReadableName}\"" : childUi.SymbolChild.ReadableName); drawList.PopClipRect(); } if (isSelected) { drawList.AddRect(_selectableScreenRect.Min - Vector2.One , _selectableScreenRect.Max + Vector2.One * 2, UiColors.BackgroundFull.Fade(opacity)); drawList.AddRect(_selectableScreenRect.Min , _selectableScreenRect.Max + Vector2.One * 1, UiColors.Selection.Fade(opacity)); } } } ImGui.PopID(); var connectionBorderArea = _selectableScreenRect; connectionBorderArea.Min.X -= 4; // Input Sockets... for (var inputIndex = 0; inputIndex < visibleInputUis.Count; inputIndex++) { var inputUi = visibleInputUis[inputIndex]; var inputDefinition = inputUi.InputDefinition; var usableSlotArea = GetUsableInputSlotSize(inputIndex, visibleInputUis.Count); ImGui.PushID(childUi.SymbolChild.Id.GetHashCode() + inputDefinition.GetHashCode()); ImGui.SetCursorScreenPos(usableSlotArea.Min); ImGui.InvisibleButton("input", usableSlotArea.GetSize()); DrawUtils.DebugItemRect("input-slot"); // Note: isItemHovered does not work when a connection is being dragged from another item var hovered = hasConnections ? usableSlotArea.Contains(ImGui.GetMousePos()) : ImGui.IsItemHovered(); hovered &= inActiveWindow; var isPotentialConnectionTarget = ConnectionMaker.IsMatchingInputType(_view, inputDefinition.DefaultValue.ValueType); var colorForType = ColorForInputType(inputDefinition).Fade(opacity); var connectedLines = _sorter.GetLinesToNodeInputSlot(childUi, inputDefinition.Id); // Render input Label if ((customUiResult & OpUi.CustomUiResult.PreventInputLabels) == 0) { var inputLabelOpacity = MathUtils.RemapAndClamp(_view.Scale.X, 0.75f, 1.5f, 0f, 1f) * opacity; var screenCursor = usableSlotArea.GetCenter() + new Vector2(14, -7); if (inputLabelOpacity > 0) { ImGui.PushFont(Fonts.FontSmall); var labelColor = ColorVariations.OperatorLabel.Apply(colorForType); labelColor.Rgba.W = inputLabelOpacity; var label = inputDefinition.Name; if (inputDefinition.IsMultiInput) { label = $" {label} [...]"; } var labelSize = ImGui.CalcTextSize(label); drawList.AddText(screenCursor, labelColor, label); screenCursor += new Vector2(labelSize.X + 8, 0); // Value ImGui.PushStyleColor(ImGuiCol.Text, labelColor.Rgba); //var inputSlot = instance.Inputs.Single(slot => inputDefinition.Id == slot.Id); var inputSlot = instance.GetInput(inputDefinition.Id); //var valueAsString = inputUi.GetSlotValue(inputSlot); var valueAsString = ValueUtils.GetValueString(inputSlot); // if (inputSlot is InputSlot f) // { // var xxx = f.TypedInputValue.Value; // } var valueColor = labelColor; valueColor.Rgba.W *= 0.6f; // Avoid clipping because it increases ImGui draw calls and is expensive var estimatedValueWidth = valueAsString.Length * 8; var needClipping = estimatedValueWidth > _usableScreenRect.Max.X - screenCursor.X; if (needClipping) { drawList.PushClipRect(_usableScreenRect.Min, _usableScreenRect.Max, true); } if(!string.IsNullOrEmpty(valueAsString)) drawList.AddText(screenCursor, valueColor, valueAsString); if (needClipping) drawList.PopClipRect(); ImGui.PopStyleColor(); ImGui.PopFont(); } } // Draw input slots if (inputDefinition.IsMultiInput) { var showGaps = isPotentialConnectionTarget; var socketCount = showGaps ? connectedLines.Count * 2 + 1 : connectedLines.Count; var socketHeight = (usableSlotArea.GetHeight() + 1) / socketCount; var targetPos = new Vector2(usableSlotArea.Max.X - 4, usableSlotArea.Min.Y + socketHeight * 0.5f); var topLeft = new Vector2(usableSlotArea.Min.X, usableSlotArea.Min.Y); var socketSize = new Vector2(usableSlotArea.GetWidth(), socketHeight - SlotGaps); var reactiveSlotColor = GetReactiveSlotColor(inputDefinition.DefaultValue.ValueType, colorForType, SocketDirections.Input); for (var socketIndex = 0; socketIndex < socketCount; socketIndex++) { var usableSocketArea = new ImRect(topLeft, topLeft + socketSize); var isSocketHovered = usableSocketArea.Contains(ImGui.GetMousePos()); ConnectionSnapEndHelper.RegisterAsPotentialTarget(_view, childUi, inputUi, socketIndex, usableSocketArea); bool isGap = false; if (showGaps) { isGap = (socketIndex & 1) == 0; } if (!isGap) { var line = showGaps ? connectedLines[socketIndex >> 1] : connectedLines[socketIndex]; if (_isVisible && socketHeight > 10) { ImGui.PushStyleVar(ImGuiStyleVar.Alpha, MathUtils.RemapAndClamp(socketHeight, 10, 20, 0, 0.5f).Clamp(0, 0.5f) * ImGui.GetStyle().Alpha); ImGui.PushFont(Fonts.FontSmall); //ImGui.SetCursorScreenPos(targetPos + new Vector2(0, -ImGui.GetFontSize()/2)); //ImGui.Value(socketIndex % 4 == 0 ? ">" : "", socketIndex); var sockedInputIndex = showGaps ? socketIndex / 2 : socketIndex; var isFourGroupStart = sockedInputIndex > 0 && sockedInputIndex % 4 == 0; var markerForFourAligned = isFourGroupStart ? " <" : ""; if(isFourGroupStart) drawList.AddRectFilled(topLeft + new Vector2(socketSize.X * 0.5f,-2), topLeft + new Vector2(_usableScreenRect.GetWidth()*0.25f,0), UiColors.ForegroundFull.Fade(0.1f)); drawList.AddText(targetPos + new Vector2(7, -ImGui.GetFontSize() / 2), new Color(MathUtils.RemapAndClamp(socketHeight, 10, 20, 0, 0.5f).Clamp(0, 0.5f)), $"{sockedInputIndex}" + markerForFourAligned); ImGui.PopFont(); ImGui.PopStyleVar(); } var isChildSelected = _projectView.NodeSelection.IsNodeSelected(childUi); line.TargetPosition = targetPos; line.TargetNodeArea = connectionBorderArea; line.IsSelected |= isChildSelected | isSocketHovered | isNodeHovered; line.FramesSinceLastUsage = framesSinceLastUpdate; line.IsAboutToBeReplaced = ConnectionSnapEndHelper.IsNextBestTarget(childUi, inputDefinition.Id, socketIndex); } DrawMultiInputSocket(drawList, childUi, inputDefinition, usableSocketArea, isSocketHovered, socketIndex, isGap, colorForType, reactiveSlotColor, instance); targetPos.Y += socketHeight; topLeft.Y += socketHeight; } if (_isVisible) { drawList.AddRectFilled(new Vector2(usableSlotArea.Max.X - 8, usableSlotArea.Min.Y), new Vector2(usableSlotArea.Max.X - 1, usableSlotArea.Min.Y + 2), reactiveSlotColor); drawList.AddRectFilled(new Vector2(usableSlotArea.Max.X - 8, usableSlotArea.Max.Y - 2), new Vector2(usableSlotArea.Max.X - 1, usableSlotArea.Max.Y), reactiveSlotColor); if (socketCount == 0) { drawList.AddRectFilled(new Vector2(usableSlotArea.Max.X - 2, usableSlotArea.Min.Y + 1), new Vector2(usableSlotArea.Max.X - 1, usableSlotArea.Max.Y-2), reactiveSlotColor); if (inputUi.Relevancy == Relevancy.Required) { var pos = usableSlotArea.GetCenter(); var size = _view.Scale.X * 3; drawList.AddCircleFilled( pos + new Vector2(-5, -(size *1f)), MathF.Min(3, size - 1), reactiveSlotColor ); drawList.AddCircleFilled( pos + new Vector2(-5, +(size * 1f)), MathF.Min(3, size - 1), reactiveSlotColor ); } } } } else { ConnectionSnapEndHelper.RegisterAsPotentialTarget(_view, childUi, inputUi, 0, usableSlotArea); //ConnectionMaker.ConnectionSnapEndHelper.IsNextBestTarget(targetUi, inputDef.Id,0) var isAboutToBeReconnected = ConnectionSnapEndHelper.IsNextBestTarget(childUi, inputDefinition.Id, 0); var isChildSelected = _projectView.NodeSelection.IsNodeSelected(childUi); foreach (var line in connectedLines) { line.TargetPosition = new Vector2(usableSlotArea.Max.X - 4, usableSlotArea.GetCenter().Y); line.TargetNodeArea = connectionBorderArea; line.IsSelected |= isChildSelected | hovered | isNodeHovered; line.IsAboutToBeReplaced = isAboutToBeReconnected; line.FramesSinceLastUsage = framesSinceLastUpdate; } if (_isVisible) { var isMissing = inputUi.Relevancy == Relevancy.Required && connectedLines.Count == 0; DrawInputSlot(drawList, childUi, inputDefinition, usableSlotArea, colorForType, hovered, isMissing, instance); } } ImGui.PopID(); } // Outputs sockets... var outputIndex = 0; //foreach(var output in instance.Outputs) var canvasScale = _view.Scale; foreach (var outputDef in childUi.SymbolChild.Symbol.OutputDefinitions) { var output = instance.Outputs[outputIndex]; var usableArea = GetUsableOutputSlotArea(canvasScale, childUi, outputIndex); if (!preventInteraction) { ImGui.SetCursorScreenPos(usableArea.Min); } ImGui.PushID(childUi.SymbolChild.Id.GetHashCode() + outputDef.Id.GetHashCode()); ImGui.InvisibleButton("output", usableArea.GetSize()); DrawUtils.DebugItemRect(); var valueType = outputDef.ValueType; var colorForType = TypeUiRegistry.GetPropertiesForType(valueType).Color.Fade(opacity); //Note: isItemHovered does not work when dragging is active var hovered = inActiveWindow && hasConnections ? usableArea.Contains(ImGui.GetMousePos()) : ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByPopup); // Update connection lines var dirtyFlagNumUpdatesWithinFrame = output.DirtyFlag.NumUpdatesWithinFrame; var isChildSelected = _projectView.NodeSelection.IsNodeSelected(childUi); foreach (var line in _sorter.GetLinesFromNodeOutput(childUi, outputDef.Id)) { line.SourcePosition = new Vector2(usableArea.Max.X, usableArea.GetCenter().Y); line.SourceNodeArea = _selectableScreenRect; line.IsSelected |= hovered; line.ColorForType = colorForType; line.UpdateCount = output.DirtyFlag.NumUpdatesWithinFrame; if (childUi.ConnectionStyleOverrides.ContainsKey(outputDef.Id)) { line.ColorForType.Rgba.W = 0.1f; } line.IsSelected |= isChildSelected || hovered | isNodeHovered; } { // Visualize update if (_isVisible) { // Draw update indicator var trigger = output.DirtyFlag.Trigger; if (trigger != DirtyFlagTrigger.None && usableArea.GetHeight() > 6) { var r = usableArea.GetWidth() / 4; var center = new Vector2(usableArea.Max.X + 2*r, usableArea.GetCenter().Y - 3*r); if (trigger == DirtyFlagTrigger.Always) { drawList.AddCircle(center, r, colorForType,3); } else if (trigger == DirtyFlagTrigger.Animated) { drawList.AddCircleFilled(center, r, colorForType, 3); } } DrawOutput(drawList, childUi, outputDef, usableArea, colorForType, hovered, instance); if (dirtyFlagNumUpdatesWithinFrame > 0) { var movement = (float)(ImGui.GetTime() * dirtyFlagNumUpdatesWithinFrame) % 1f * (usableArea.GetWidth() - 1); drawList.AddRectFilled(new Vector2(usableArea.Min.X + movement - 1, usableArea.Min.Y), new Vector2(usableArea.Min.X + movement + 1, usableArea.Max.Y), new Color(0.2f)); } } } outputIndex++; ImGui.PopID(); } } // // todo - move outta here // internal static OpUi.CustomUiResult DrawCustomUi(Instance instance, ImDrawListPtr drawList, ImRect selectableScreenRect, Vector2 canvasScale) // { // var type = instance.Type; // // var result = CustomChildUiRegistry.TryGetValue(instance.Symbol.Id, out var drawFunction) // // ? drawFunction(instance, drawList, selectableScreenRect, canvasScale) // // : OpUi.CustomUiResult.None; // // return result; // } private void DrawIndicator(ImDrawListPtr drawList, Color color, float opacity, ref int indicatorCount) { const int s = 10; var dx = (s + 1) * indicatorCount; var pMin = new Vector2(_usableScreenRect.Max.X - 2 - s - dx, (_usableScreenRect.Max.Y - 2 - s).Clamp(_usableScreenRect.Min.Y + 2, _usableScreenRect.Max.Y)); var pMax = new Vector2(_usableScreenRect.Max.X - 2 - dx, _usableScreenRect.Max.Y - 2); drawList.AddRectFilled(pMin, pMax, color.Fade(opacity)); drawList.AddRect(pMin-Vector2.One, pMax+Vector2.One, UiColors.WindowBackground.Fade(0.4f * opacity)); indicatorCount++; } private void AdjustGroupLayoutAfterResize(ISelectableCanvasObject childUi, Vector2 newNodeSize, SymbolUi parentUi) { if (childUi.Size == newNodeSize) return; var groupMembers = _view.SelectableNodeMovement.FindSnappedNeighbours(childUi); if (groupMembers.Count > 0) { var heightDelta = newNodeSize.Y - childUi.Size.Y; var offset = new Vector2(0, heightDelta); if (heightDelta > 0) { foreach (var neighbour in groupMembers) { if (neighbour == childUi) return; if (neighbour.PosOnCanvas.Y > childUi.PosOnCanvas.Y && Math.Abs(neighbour.PosOnCanvas.X - childUi.PosOnCanvas.X) < SelectableNodeMovement.Tolerance) { neighbour.PosOnCanvas += offset; } } } else if (heightDelta < 0) { foreach (var neighbour in groupMembers) { if (neighbour == childUi) return; if (neighbour.PosOnCanvas.Y > childUi.PosOnCanvas.Y && Math.Abs(neighbour.PosOnCanvas.X - childUi.PosOnCanvas.X) < SelectableNodeMovement.Tolerance) { neighbour.PosOnCanvas += offset; } } } } childUi.Size = newNodeSize; } // Find visible input slots. // TODO: this is a major performance hot spot and needs optimization private List FindVisibleInputUis(SymbolUi symbolUi, SymbolUi.Child childUi, ref bool nodeHasHiddenMatchingInputs) { var connectionsToNode = _sorter.GetLinesIntoNode(childUi); if (childUi.Style == SymbolUi.Child.Styles.Expanded) { return symbolUi.InputUis.Values.ToList(); } var tempConnections = ConnectionMaker.GetTempConnectionsFor(_view); var isNodeHoveredAsConnectionTarget = _hoveredNodeIdForConnectionTarget == childUi.Id && tempConnections.Count == 1 && tempConnections[0].TargetParentOrChildId == ConnectionMaker.NotConnectedId && tempConnections[0].SourceParentOrChildId != childUi.Id; _visibleInputs.Clear(); foreach (var inputUi in symbolUi.InputUis.Values) { bool inputIsConnectionTarget = false; for (int i = 0; i < connectionsToNode.Count; i++) { if (connectionsToNode[i].Connection.TargetSlotId == inputUi.Id) { inputIsConnectionTarget = true; break; } } if (inputUi.Relevancy != Relevancy.Optional || inputIsConnectionTarget) { _visibleInputs.Add(inputUi); } else if (ConnectionMaker.IsMatchingInputType(_view, inputUi.Type)) { if (isNodeHoveredAsConnectionTarget) { _visibleInputs.Add(inputUi); } else { nodeHasHiddenMatchingInputs = true; } } } return _visibleInputs; } private enum SocketDirections { Input, Output, } private Color GetReactiveSlotColor(Type type, Color colorForType, SocketDirections direction) { var style = direction == SocketDirections.Input ? ColorVariations.ConnectionLines : ColorVariations.OperatorBackground; if (ConnectionMaker.GetTempConnectionsFor(_view).Count > 0) { if (direction == SocketDirections.Input ? ConnectionMaker.IsMatchingInputType(_view, type) : ConnectionMaker.IsMatchingOutputType(_view, type)) { var blink = (float)(Math.Sin(ImGui.GetTime() * 10) / 2f + 0.8f); colorForType.Rgba.W *= blink; style = ColorVariations.Highlight; } else { style = ColorVariations.OperatorBackgroundIdle; } } return style.Apply(colorForType); } /// /// Set /// /// private void PreparePreviewAndExpandSelectableArea(Instance instance) { _previewTextureView = null; if (instance.Outputs.Count == 0) return; var firstOutput = instance.Outputs[0]; if (firstOutput is not Slot textureSlot) return; var texture = textureSlot.Value; if (texture == null || texture.IsDisposed) return; _previewTextureView = SrvManager.GetSrvForTexture(texture); var aspect = (float)texture.Description.Width / texture.Description.Height; var opWidth = _usableScreenRect.GetWidth(); var previewSize = new Vector2(opWidth, opWidth / aspect); if (previewSize.Y > opWidth) { previewSize *= opWidth / previewSize.Y; } var min = new Vector2(_usableScreenRect.Min.X, _usableScreenRect.Min.Y - previewSize.Y - 1); var max = new Vector2(_usableScreenRect.Min.X + previewSize.X, _usableScreenRect.Min.Y - 1); _selectableScreenRect.Add(min); _previewArea = new ImRect(min, max); } private static ImRect _previewArea; private static ShaderResourceView _previewTextureView; private void DrawPreview(ImDrawListPtr drawList, float opacity) { if (_previewTextureView == null) return; drawList.AddImage((IntPtr)_previewTextureView, _previewArea.Min, _previewArea.Max, Vector2.Zero, Vector2.One, Color.White.Fade(opacity)); } private Vector2 ComputeNodeSize(SymbolUi.Child childUi, List visibleInputUis, Legacy.Graph.ConnectionSorter connectionSorter) { if (childUi.Style == SymbolUi.Child.Styles.Resizable) { return childUi.Size; } var additionalMultiInputSlots = 0; foreach (var input in visibleInputUis) { if (!input.InputDefinition.IsMultiInput) continue; //TODO: this should be refactored, because it's very slow and is later repeated var connectedLines = connectionSorter.GetLinesToNodeInputSlot(childUi, input.Id); additionalMultiInputSlots += connectedLines.Count; } return new Vector2(SymbolUi.Child.DefaultOpSize.X, 23 + (visibleInputUis.Count + additionalMultiInputSlots) * 13); } private void DrawOutput(ImDrawListPtr drawList, SymbolUi.Child childUi, Symbol.OutputDefinition outputDef, ImRect usableArea, Color colorForType, bool hovered, Instance instance) { if (ConnectionMaker.IsOutputSlotCurrentConnectionSource(_view, childUi, outputDef)) { drawList.AddRectFilled(usableArea.Min, usableArea.Max, ColorVariations.Highlight.Apply(colorForType)); var isMouseReleasedWithoutDrag = ImGui.IsMouseReleased(ImGuiMouseButton.Left) && ImGui.GetMouseDragDelta(ImGuiMouseButton.Left).Length() < UserSettings.Config.ClickThreshold; if (isMouseReleasedWithoutDrag) { //Graph.Connections.GetLinesFromNodeOutput(childUi, outputDef.Id); // This method is complex, because it has to handle several edge cases and has potential to remove previous user data: // - We have to preserve the previous state. // - We have to make space -> Shift all connected operators towards the right. // - We have to convert all existing connections from the output into temporary connections. // - We have to insert a new temp connection line between output and symbol browser // // - If the user completes the symbol browser, it must complete the previous connections from the temp connections. // - If the user cancels the operation, the previous state has to be restored. This might be tricky ConnectionMaker.InitSymbolBrowserAtPosition(_view, _view.SymbolBrowser, childUi.PosOnCanvas + new Vector2(childUi.Size.X + SelectableNodeMovement.SnapPadding.X, 0)); } } else if (hovered) { if (ConnectionMaker.IsMatchingOutputType(_view, outputDef.ValueType)) { drawList.AddRectFilled(usableArea.Min, usableArea.Max, ColorVariations.OperatorBackgroundHover.Apply(colorForType)); if (ImGui.IsMouseReleased(0)) { ConnectionMaker.CompleteAtOutputSlot(_view, instance, childUi, outputDef); } } else { if (_isVisible) { drawList.AddRectFilled(usableArea.Min, usableArea.Max, ColorVariations.OperatorBackgroundHover.Apply(colorForType)); var output = instance.Outputs.Single(output2 => output2.Id == outputDef.Id); ImGui.BeginTooltip(); ImGui.TextUnformatted($".{outputDef.Name}"); ImGui.PushFont(Fonts.FontSmall); ImGui.TextColored(UiColors.Gray.Rgba, $"<{TypeNameRegistry.Entries[outputDef.ValueType]}>\n{output.DirtyFlag.NumUpdatesWithinFrame} Updates\n({output.DirtyFlag.Trigger})"); ImGui.PopFont(); ImGui.EndTooltip(); if(ImGui.IsMouseClicked(ImGuiMouseButton.Left)) { _dragInfo.DraggedOutputOpId = childUi.Id; _dragInfo.DraggedOutputDefId = outputDef.Id; } // Clicked else { if (ImGui.IsMouseReleased(ImGuiMouseButton.Left)) { _dragInfo.DraggedOutputOpId = Guid.Empty; _dragInfo.DraggedOutputDefId = Guid.Empty; if (ImGui.GetMouseDragDelta().Length() < UserSettings.Config.ClickThreshold) { ConnectionMaker.OpenSymbolBrowserAtOutput(_projectView, childUi, instance, output.Id); } } else if (ImGui.IsMouseReleased(ImGuiMouseButton.Right) && ImGui.GetIO().KeyCtrl) { _view.EditNodeOutputDialog.OpenForOutput(_projectView.CompositionInstance.Symbol, childUi, outputDef); } } } } } else if (_dragInfo.DraggedOutputOpId == childUi.Id && _dragInfo.DraggedOutputDefId == outputDef.Id) { if (ImGui.IsMouseDragging(ImGuiMouseButton.Left) && ImGui.GetMouseDragDelta().Length() > UserSettings.Config.ClickThreshold) { _dragInfo.DraggedOutputOpId = Guid.Empty; _dragInfo.DraggedOutputDefId = Guid.Empty; ConnectionMaker.StartFromOutputSlot(_view, _projectView.NodeSelection, childUi, outputDef); } } else { var color = GetReactiveSlotColor(outputDef.ValueType, colorForType, SocketDirections.Output); var pos = usableArea.Min; drawList.AddRectFilled( pos, usableArea.Max, color ); } } private ImRect GetUsableOutputSlotArea(Vector2 canvasScale, SymbolUi.Child targetUi, int outputIndex) { var thickness = (int)MathUtils.RemapAndClamp(canvasScale.X, 0.5f, 1.2f, (int)(UsableSlotThickness * 0.5f), UsableSlotThickness ) * T3Ui.UiScaleFactor ; var opRect = _usableScreenRect; var outputCount = targetUi.SymbolChild.Symbol.OutputDefinitions.Count; var outputHeight = outputCount == 0 ? opRect.GetHeight() : (opRect.GetHeight() - 1 + SlotGaps) / outputCount - SlotGaps; if (outputHeight <= 0) outputHeight = 1; return ImRect.RectWithSize( new Vector2( opRect.Max.X + 1, // - GraphNode._usableSlotThickness, opRect.Min.Y + (outputHeight + SlotGaps) * outputIndex + 1 ), new Vector2( thickness, outputHeight )); } /// /// Draws slot for non multi-input /// private void DrawInputSlot(ImDrawListPtr drawList, SymbolUi.Child targetUi, Symbol.InputDefinition inputDef, ImRect usableArea, Color colorForType, bool hovered, bool isMissing, Instance instance) { var parentSymbol = instance.Parent.Symbol; if (ConnectionMaker.IsInputSlotCurrentConnectionTarget(_view, targetUi, inputDef)) { } else if (ConnectionSnapEndHelper.IsNextBestTarget(targetUi, inputDef.Id, 0) || hovered) { if (ConnectionMaker.IsMatchingInputType(_view, inputDef.DefaultValue.ValueType)) { drawList.AddRectFilled(usableArea.Min, usableArea.Max, ColorVariations.OperatorBackgroundHover.Apply(colorForType)); if (ImGui.IsMouseReleased(0)) { ConnectionMaker.CompleteAtInputSlot(_view, instance, targetUi, inputDef); } } else { drawList.AddRectFilled( usableArea.Min, usableArea.Max, ColorVariations.OperatorBackgroundHover.Apply(colorForType) ); Symbol.Connection connection; Symbol.Child sourceOp = null; Symbol.Child.Output output = null; ImGui.BeginTooltip(); { DrawInputSources(targetUi, inputDef, instance); connection = parentSymbol.Connections.SingleOrDefault(c => c.TargetParentOrChildId == targetUi.Id && c.TargetSlotId == inputDef.Id); if (connection != null) { if (parentSymbol.Children.TryGetValue(connection.SourceParentOrChildId, out sourceOp)) { if (!sourceOp.Outputs.TryGetValue(connection.SourceSlotId, out output)) { parentSymbol.Connections.Remove(connection); } //output = sourceOp.Outputs[connection.SourceSlotId]; } } ImGui.TextUnformatted($".{inputDef.Name}"); CustomComponents.StylizedText($"<{TypeNameRegistry.Entries[inputDef.DefaultValue.ValueType]}>", Fonts.FontSmall, UiColors.Gray); if (isMissing) { FormInputs.AddVerticalSpace(5); ImGui.TextColored(UiColors.StatusAttention.Rgba, $"Requires input"); } } ImGui.EndTooltip(); if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) { var createCopy = ImGui.GetIO().KeyCtrl && connection != null; if (createCopy) { if (sourceOp != null) { Log.Debug("Cloning connection from source op..."); var sourceOpUi = parentSymbol.GetSymbolUi().ChildUis[sourceOp.Id]!; ConnectionMaker.StartFromOutputSlot(_view, _projectView.NodeSelection, sourceOpUi, output.OutputDefinition); } else if (connection.IsConnectedToSymbolInput) { Log.Debug("Cloning connection from input node..."); var inputDef2 = parentSymbol.InputDefinitions.Single(id => id.Id == connection.SourceSlotId); ConnectionMaker.StartFromInputNode(_view, inputDef2); } else { Log.Warning("This should not happen. Please contact customer support."); } } else { _dragInfo.DraggedInputOpId = targetUi.Id; _dragInfo.DraggedInputDefId = inputDef.Id; } } else { if (ImGui.IsMouseReleased(ImGuiMouseButton.Left)) { _dragInfo.DraggedInputOpId = Guid.Empty; _dragInfo.DraggedInputDefId = Guid.Empty; if (ImGui.GetMouseDragDelta().Length() < UserSettings.Config.ClickThreshold) { ConnectionMaker.StartFromInputSlot(_view, targetUi.SymbolChild.Parent, targetUi, inputDef); if (SymbolUiRegistry.TryGetSymbolUi(parentSymbol.Id, out var compositionUi)) { var freePosition = NodeGraphLayouting.FindPositionForNodeConnectedToInput(compositionUi, targetUi); ConnectionMaker.InitSymbolBrowserAtPosition(_view, _view.SymbolBrowser, freePosition); } } else if (ImGui.IsMouseReleased(ImGuiMouseButton.Right) && ImGui.GetIO().KeyCtrl) { ConnectionMaker.StartFromInputSlot(_view, parentSymbol, targetUi, inputDef); } } } } } else if (_dragInfo.DraggedInputOpId == targetUi.Id && _dragInfo.DraggedInputDefId == inputDef.Id) { if (ImGui.IsMouseDragging(ImGuiMouseButton.Left) && ImGui.GetMouseDragDelta().Length() > UserSettings.Config.ClickThreshold) { _dragInfo.DraggedInputOpId = Guid.Empty; _dragInfo.DraggedInputDefId = Guid.Empty; ConnectionMaker.StartFromInputSlot(_view, parentSymbol, targetUi, inputDef); } } else { var connectionColor = GetReactiveSlotColor(inputDef.DefaultValue.ValueType, colorForType, SocketDirections.Input); var pos = new Vector2( usableArea.Max.X - GraphNode.InputSlotThickness - InputSlotMargin, usableArea.Min.Y ); var size = new Vector2(GraphNode.InputSlotThickness, usableArea.GetHeight()); drawList.AddRectFilled( pos, pos + size, connectionColor ); if (isMissing) { drawList.AddCircleFilled( pos + new Vector2(-6, size.Y / 2), MathF.Min(3, size.Y - 1), connectionColor ); } } } private void DrawInputSources(SymbolUi.Child targetUi, Symbol.InputDefinition inputDef, Instance instance, int inputIndex = 0) { var sources = CollectSourcesForInput(instance.Parent, targetUi, inputDef, inputIndex); if (sources.Count <= 0) return; ImGui.PushFont(Fonts.FontSmall); foreach (var source in sources) { ImGui.TextColored(UiColors.Gray, source); } ImGui.PopFont(); } /// /// Crawl down the graph and collect a list of inputs that contributed to the given input /// private List CollectSourcesForInput(Instance compositionOp, SymbolUi.Child targetUi, Symbol.InputDefinition inputDef, int inputIndex) { var sources = new List(); var compositionUi = compositionOp.GetSymbolUi(); while (true) { Symbol.Connection connection = null; if (inputDef.IsMultiInput) { var connections = compositionOp.Symbol.Connections.Where(c => c.TargetParentOrChildId == targetUi.Id && c.TargetSlotId == inputDef.Id).ToList(); if (connections.Count > 0 && connections.Count > inputIndex) { connection = connections[inputIndex]; } } else { connection = compositionUi.Symbol.Connections.FirstOrDefault(c => c.TargetParentOrChildId == targetUi.Id && c.TargetSlotId == inputDef.Id ); } if (connection == null) break; if (connection.IsConnectedToSymbolInput) { var compInputDef = compositionUi.Symbol.InputDefinitions.SingleOrDefault(inp => inp.Id == connection.SourceSlotId); var input = compositionOp.Inputs.SingleOrDefault(inp => inp.Id == connection.SourceSlotId); sources.Insert(0, $". {compInputDef?.Name} " + ValueUtils.GetValueString(input?.Input.Value)); break; } var connectionSourceId = connection.SourceParentOrChildId; if (compositionUi.ChildUis.TryGetValue(connectionSourceId, out var connectionSourceUi) && compositionOp.Children.TryGetChildInstance(connectionSourceId, out var instance)) { var outputDef = connectionSourceUi.SymbolChild.Symbol.OutputDefinitions.SingleOrDefault(outp => outp.Id == connection.SourceSlotId); var output = instance.Outputs.SingleOrDefault(outp => outp.Id == connection.SourceSlotId); var outputName = (instance.Outputs.Count > 1 && outputDef?.Name != "Output" && outputDef?.Name != "Result") ? "." + outputDef?.Name : ""; sources.Insert(0, $"{connectionSourceUi?.SymbolChild.ReadableName} {outputName} " + GetValueString(output)); } if (connectionSourceUi?.SymbolChild.Symbol.InputDefinitions.Count > 0) { targetUi = connectionSourceUi; inputDef = connectionSourceUi?.SymbolChild.Symbol.InputDefinitions[0]; // FIXME: this should pick the first connected. } else { break; } } return sources; } private static string GetValueString(ISlot outputSlot) { return outputSlot switch { Slot f => $"{f.Value:0.00}", Slot i => $"{i.Value:G3}", Slot i => $"{i.Value:G3}", Slot b => $"{b.Value}", Slot v3 => $"{v3.Value:0.0}", Slot v2 => $"{v2.Value:0.0}", Slot s => s.Value.Truncate(), _ => "" }; } private void DrawMultiInputSocket(ImDrawListPtr drawList, SymbolUi.Child targetUi, Symbol.InputDefinition inputDef, ImRect usableArea, bool isInputHovered, int multiInputIndex, bool isGap, Color colorForType, Color reactiveSlotColor, Instance instance) { if (ConnectionMaker.IsInputSlotCurrentConnectionTarget(_view, targetUi, inputDef, multiInputIndex)) { } else if (ConnectionSnapEndHelper.IsNextBestTarget(targetUi, inputDef.Id, multiInputIndex) || isInputHovered) { if (ConnectionMaker.IsMatchingInputType(_view, inputDef.DefaultValue.ValueType)) { drawList.AddRectFilled(usableArea.Min, usableArea.Max, ColorVariations.OperatorBackgroundHover.Apply(colorForType)); if (ImGui.IsMouseReleased(0)) { ConnectionMaker.CompleteAtInputSlot(_view, instance, targetUi, inputDef, multiInputIndex, true); } } else { drawList.AddRectFilled( usableArea.Min, usableArea.Max, ColorVariations.OperatorBackgroundHover.Apply(colorForType) ); ImGui.BeginTooltip(); { DrawInputSources(targetUi, inputDef, instance, multiInputIndex); // var connectionSource = ""; // var connections = GraphCanvas.Current.CompositionOp.Symbol.Connections.Where(c => c.TargetParentOrChildId == targetUi.Id // && c.TargetSlotId == inputDef.Id).ToList(); // if (connections.Count > 0 && connections.Count > multiInputIndex) // { // var connection = connections[multiInputIndex]; // // // var sourceOp = // // GraphCanvas.Current.CompositionOp.Symbol.Children.SingleOrDefault(child => child.Id == connection.SourceParentOrChildId); // // if (sourceOp != null) // // { // // var output = sourceOp.Outputs[connection.SourceSlotId]; // // connectionSource = sourceOp.ReadableName + "." + output.OutputDefinition.Name; // // //connectionSource = sourceOp.ReadableName; // // } // } // if (!string.IsNullOrEmpty(connectionSource)) // { // ImGui.PushFont(Fonts.FontSmall); // ImGui.TextColored(Color.Gray, $"{connectionSource} -> "); // ImGui.PopFont(); // } ImGui.TextUnformatted($".{inputDef.Name}"); ImGui.PushFont(Fonts.FontSmall); ImGui.TextColored(UiColors.Gray, $"<{TypeNameRegistry.Entries[inputDef.DefaultValue.ValueType]}>"); ImGui.PopFont(); //ImGui.PopStyleVar(); } ImGui.EndTooltip(); //ImGui.SetTooltip($"-> .{inputDef.Name}[{multiInputIndex}] <{TypeNameRegistry.Entries[inputDef.DefaultValue.ValueType]}>"); if (ImGui.IsItemClicked(0)) { ConnectionMaker.StartFromInputSlot(_view, instance.Parent.Symbol, targetUi, inputDef, multiInputIndex); Log.Debug("started connection at MultiInputIndex:" + multiInputIndex); } } } else { //var pos = usableArea.Min + Vector2.UnitY * GraphNode._inputSlotMargin; var gapOffset = isGap ? new Vector2(2, 0) : Vector2.Zero; var pos = new Vector2(usableArea.Max.X - InputSlotMargin - InputSlotThickness, usableArea.Min.Y) - gapOffset; var size = new Vector2(InputSlotThickness, usableArea.GetHeight()) + gapOffset; drawList.AddRectFilled( pos, pos + size, reactiveSlotColor ); } } private ImRect GetUsableInputSlotSize(int inputIndex, int visibleSlotCount) { var areaForParams = new ImRect(new Vector2( _usableScreenRect.Min.X, _usableScreenRect.Min.Y + NodeTitleHeight), _usableScreenRect.Max); var inputHeight = visibleSlotCount == 0 ? areaForParams.GetHeight() : (areaForParams.GetHeight() + SlotGaps) / visibleSlotCount - SlotGaps; if (inputHeight <= 0) inputHeight = 1; return ImRect.RectWithSize( new Vector2( areaForParams.Min.X - UsableSlotThickness, Math.Min(_selectableScreenRect.Max.Y, areaForParams.Min.Y + (inputHeight + SlotGaps) * inputIndex) ), new Vector2( UsableSlotThickness, inputHeight )); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Color ColorForInputType(Symbol.InputDefinition inputDef) { return TypeUiRegistry.GetPropertiesForType(inputDef.DefaultValue.ValueType).Color; } #region style variables public static Vector2 LabelPos = new(4, 2); public static float UsableSlotThickness = 10; public static float InputSlotThickness = 3; public static float InputSlotMargin = 1; public static float SlotGaps = 2; public static float OutputSlotMargin = 1; #endregion private static readonly string _unfoldLabel = (char)Icon.ChevronLeft + "##size"; private static readonly string _foldLabel = (char)Icon.ChevronDown + "##size"; private static readonly List _visibleInputs = new(15); // A static variable to avoid GC allocations private static readonly EvaluationContext _evaluationContext = new(); private static readonly ImageOutputCanvas _imageCanvasForTooltips = new() { DisableDamping = true }; private static Guid _hoveredNodeIdForConnectionTarget; private readonly GraphView _view; private readonly ProjectView _projectView; private readonly Legacy.Graph.ConnectionSorter _sorter; private readonly DraggedIdInfo _dragInfo; private static readonly Dictionary DraggedIds = new(); private ImRect _usableScreenRect; private ImRect _selectableScreenRect; private bool _isVisible; private const float NodeTitleHeight = 22; private sealed class DraggedIdInfo() { public Guid DraggedOutputOpId; public Guid DraggedOutputDefId; public Guid DraggedInputOpId; public Guid DraggedInputDefId; } }