Files
tooll3--t3/Editor/Gui/MagGraph/Ui/MagGraphCanvas.DrawNode.cs
2026-07-13 13:13:17 +08:00

1424 lines
67 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using ImGuiNET;
using System.Diagnostics;
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.Help;
using T3.Editor.Gui.MagGraph.Interaction;
using T3.Editor.Gui.MagGraph.Model;
using T3.Editor.Gui.MagGraph.States;
using T3.Editor.Gui.OpUis;
using T3.Editor.Gui.Styling;
using T3.Editor.Gui.UiHelpers;
using T3.Editor.Gui.UiHelpers.Thumbnails;
using T3.Editor.Skills.Training;
using T3.Editor.UiModel.Helpers;
using T3.Editor.UiModel.InputsAndTypes;
using Texture2D = T3.Core.DataTypes.Texture2D;
namespace T3.Editor.Gui.MagGraph.Ui;
internal sealed partial class MagGraphView
{
private void DrawNode(MagGraphItem item, ImDrawListPtr drawList, GraphUiContext context)
{
if (item.Variant == MagGraphItem.Variants.Placeholder || item.Instance == null)
return;
if (!IsRectVisible(item.Area))
return;
if (item.ChildUi != null && item.ChildUi.IsHiddenInCollapsedSection)
return;
var idleFadeFactor = 1f;
var idleFactor = 0f;
if (item.Variant == MagGraphItem.Variants.Operator && item.Instance != null)
{
var framesSinceLastUpdate = 100;
for (var index = 0; index < item.Instance.Outputs.Count; index++)
{
var output = item.Instance.Outputs[index];
framesSinceLastUpdate = Math.Min(framesSinceLastUpdate, output.DirtyFlag.FramesSinceLastUpdate);
}
idleFadeFactor = MathUtils.RemapAndClamp(framesSinceLastUpdate, 0f, 60f, 1f, 0.6f);
idleFactor = MathUtils.RemapAndClamp(framesSinceLastUpdate, 0f, 60f, 0f, 1f);
}
var smallFontScaleFactor = CanvasScale.Clamp(0.5f, 2);
var typeUiProperties = TypeUiRegistry.GetPropertiesForType(item.PrimaryType);
var typeColor = typeUiProperties.Color.Fade(_context.GraphOpacity);
var labelColor = ColorVariations.OperatorLabel.Apply(typeColor);
var pMin = TransformPosition(item.DampedPosOnCanvas);
var pMax = TransformPosition(item.DampedPosOnCanvas + item.Size);
var pMinVisible = pMin;
var pMaxVisible = pMax;
// Adjust size when snapped
var snappedBorders = Borders.None;
{
for (var index = 0; index < 1 && index < item.InputLines.Length; index++)
{
ref var il = ref item.InputLines[index];
var c = il.ConnectionIn;
if (c != null)
{
if (c.IsSnapped)
{
switch (c.Style)
{
case MagGraphConnection.ConnectionStyles.MainOutToMainInSnappedVertical:
snappedBorders |= Borders.Up;
break;
case MagGraphConnection.ConnectionStyles.MainOutToMainInSnappedHorizontal:
case MagGraphConnection.ConnectionStyles.MainOutToInputSnappedHorizontal:
snappedBorders |= Borders.Left;
break;
}
}
}
}
for (var index = 0; index < 1 && index < item.OutputLines.Length; index++)
{
ref var ol = ref item.OutputLines[index];
foreach (var c in ol.ConnectionsOut)
{
if (c.IsSnapped && c.SourceItem == item)
{
switch (c.Style)
{
case MagGraphConnection.ConnectionStyles.MainOutToMainInSnappedVertical:
snappedBorders |= Borders.Down;
break;
case MagGraphConnection.ConnectionStyles.MainOutToMainInSnappedHorizontal:
case MagGraphConnection.ConnectionStyles.MainOutToInputSnappedHorizontal:
snappedBorders |= Borders.Right;
break;
}
}
}
}
// There is probably a better method than this...
const int snapPadding = 2;
if ((snappedBorders & Borders.Down) == 0) pMaxVisible.Y -= (int)(snapPadding * 2 * CanvasScale);
if ((snappedBorders & Borders.Right) == 0) pMaxVisible.X -= (int)(snapPadding * CanvasScale);
}
// Background and Outline
var borders = (int)snappedBorders % 16;
var imDrawFlags = _borderRoundings[borders];
drawList.AddRectFilled(pMinVisible,
pMaxVisible,
Color.Mix(
ColorVariations.OperatorBackground.Apply(typeColor),
ColorVariations.OperatorBackgroundIdle.Apply(typeColor),
idleFactor), CanvasScale < 0.5f ? 0 : 5 * CanvasScale,
imDrawFlags);
// Snapped borders
if ((snappedBorders & Borders.Down) != 0)
{
drawList.AddRectFilled(new Vector2(pMinVisible.X, pMaxVisible.Y),
pMaxVisible - new Vector2(0, 2),
ColorVariations.OperatorOutline.Apply(typeColor));
}
if ((snappedBorders & Borders.Right) != 0)
{
drawList.AddRectFilled(new Vector2(pMaxVisible.X - 2, pMinVisible.Y),
pMaxVisible,
ColorVariations.OperatorOutline.Apply(typeColor));
}
var isSelected = _context.Selector.IsSelected(item);
if (isSelected)
{
drawList.AddRect(pMinVisible, pMaxVisible, UiColors.ForegroundFull.Fade(_context.GraphOpacity),
CanvasScale < 0.5 ? 0 : 5 * CanvasScale,
imDrawFlags);
}
var isHighlighted = item.Variant == MagGraphItem.Variants.Operator &&
FrameStats.IsIdHovered(item.Instance.SymbolChildId) && !IsHovered;
if (isHighlighted)
{
drawList.AddRect(pMinVisible, pMaxVisible, UiColors.ForegroundFull.Fade(Blink),
CanvasScale < 0.5 ? 0 : 5 * CanvasScale,
imDrawFlags);
}
OpUi.CustomUiResult customUiResult = OpUi.CustomUiResult.None;
if (Scale.X > 0.3f)
{
// Custom Ui
if (item.Variant == MagGraphItem.Variants.Operator)
{
var additionalRightPadding = item.Instance.Outputs.Count> 1 ? 6 * CanvasScale:0;
customUiResult = item.Instance.DrawCustomUi(drawList,
new ImRect(pMinVisible + Vector2.One,
pMaxVisible - Vector2.One - new Vector2(additionalRightPadding,0)), this, ref item.OpUiBinding);
if ((customUiResult & OpUi.CustomUiResult.IsActive) != 0)
{
context.ItemWithActiveCustomUi = item;
}
}
}
// ImGUI element for selection
ImGui.SetCursorScreenPos(pMin);
ImGui.PushID(item.Id.GetHashCode());
// ImGui 1.91 asserts on zero-sized InvisibleButton. Guard against
// nodes that haven't been laid out yet or are fully collapsed.
var buttonSize = pMax - pMin;
if (buttonSize.X < 1f) buttonSize.X = 1f;
if (buttonSize.Y < 1f) buttonSize.Y = 1f;
ImGui.InvisibleButton("##op", buttonSize);
var isItemHovered = ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByPopup
| ImGuiHoveredFlags.AllowWhenBlockedByActiveItem);
if (_context.StateMachine.CurrentState == GraphStates.Default
&& isItemHovered
&& (customUiResult & OpUi.CustomUiResult.IsActive) == 0)
{
_context.ActiveItem = item;
// Feed the Help window so it follows the hovered operator without any dwell delay.
if (item.Variant == MagGraphItem.Variants.Operator && item.SymbolChild != null)
HoveredHelpTarget.SetOperator(item.SymbolChild.Symbol.Id);
}
// if ((customUiResult & OpUi.CustomUiResult.IsActive) != 0)
// {
// //context.StateMachine.SetState(GraphStates.Default, context);
// }
{
ParameterPopUp.HandleOpenParameterPopUp(item.ChildUi, item.Instance, customUiResult, new ImRect(pMinVisible, pMaxVisible));
}
ImGui.PopID();
// Todo: We eventually need to handle right clicking to select and open context menu when dragging with right mouse button.
// var wasDraggingRight = ImGui.GetMouseDragDelta(ImGuiMouseButton.Right).Length() > UserSettings.Config.ClickThreshold;
// if (ImGui.IsMouseReleased(ImGuiMouseButton.Right)
// && !wasDraggingRight
// && ImGui.IsItemHovered()
// && !_nodeSelection.IsNodeSelected(item))
// {
// item.Select(_nodeSelection);
// }
if ((customUiResult == OpUi.CustomUiResult.None
|| (customUiResult&OpUi.CustomUiResult.AllowThumbnail) != 0
) && CanvasScale > 0.2f)
{
// Draw Texture thumbnail
var hasPreview = TryDrawTexturePreview(item, pMinVisible, pMaxVisible, drawList, typeColor);
if ((customUiResult & OpUi.CustomUiResult.AllowThumbnail) == 0)
{
// Label...
var name = item.ReadableName;
if (item.Variant == MagGraphItem.Variants.Output)
{
var height = pMaxVisible.Y - pMinVisible.Y;
var width = pMaxVisible.X - pMinVisible.X;
_inputIndicatorPoints[0] = pMinVisible;
_inputIndicatorPoints[1] = pMinVisible + new Vector2(0.9f * width, 0);
_inputIndicatorPoints[2] = pMinVisible + new Vector2(0.9f * width + height / 4, height / 2);
_inputIndicatorPoints[3] = pMinVisible + new Vector2(0.9f * width, height);
_inputIndicatorPoints[4] = pMinVisible + new Vector2(0, height);
drawList.AddConvexPolyFilled(ref _inputIndicatorPoints[0], 5, ColorVariations.Highlight.Apply(typeColor).Fade(0.5f));
}
else if (item.Variant == MagGraphItem.Variants.Input)
{
var t = pMaxVisible.Y - pMinVisible.Y;
var framesSinceLastUpdate = (item.OutputLines.Length > 0)
? (float)(item.OutputLines[0].Output.DirtyFlag.FramesSinceLastUpdate)
: 1000;
var fade = framesSinceLastUpdate.RemapAndClamp(0, 120, 1f, 0.4f);
if (framesSinceLastUpdate == 0)
fade -= Blink * 0.2f;
_inputIndicatorPoints[0] = pMinVisible;
_inputIndicatorPoints[1] = pMinVisible + new Vector2(0.2f, 0) * t;
_inputIndicatorPoints[2] = pMinVisible + new Vector2(0.5f, 0.5f) * t;
_inputIndicatorPoints[3] = pMinVisible + new Vector2(0.2f, 1f) * t;
_inputIndicatorPoints[4] = pMinVisible + new Vector2(0.0f, 1f) * t;
drawList.AddConvexPolyFilled(ref _inputIndicatorPoints[0], 5, ColorVariations.Highlight.Apply(typeColor).Fade(fade));
name = " " + name;
}
ImGui.PushFont(Fonts.FontNormal);
var requiredLabelSize = ImGui.CalcTextSize(name.AddSpacesForImGuiOutput()).X;
ImGui.PopFont();
var paddingForPreview = (hasPreview ? MagGraphItem.LineHeight + 15 : 0) ;
var availableLabelWidth = (MagGraphItem.Width - paddingForPreview) * 0.9f;
var downScaleFactor = MathF.Min(1/T3Ui.UiScaleFactor, availableLabelWidth / requiredLabelSize );
var fontSize = Fonts.FontNormal.FontSize * (downScaleFactor * CanvasScale).Clamp(0.1f , 1);
var visibleLineHeight = Math.Min((pMaxVisible.Y - pMinVisible.Y), MagGraphItem.LineHeight * CanvasScale);
var yCenter = pMin.Y + visibleLineHeight / 2 - fontSize / 2;
var labelPos = new Vector2(pMin.X + 8 * CanvasScale, yCenter);
labelPos = new Vector2(MathF.Round(labelPos.X), MathF.Round(labelPos.Y));
drawList.AddText(Fonts.FontNormal,
fontSize,
labelPos,
labelColor.Fade(CanvasScale.RemapAndClamp(0.3f, 0.7f, 0, 1)),
name.AddSpacesForImGuiOutput());
}
}
var hasHiddenMatchingTypes = false;
// Indicate hidden matching inputs...
if (_context.DraggedPrimaryOutputType != null
&& item.Variant == MagGraphItem.Variants.Operator
&& _context.StateMachine.CurrentState == GraphStates.DragConnectionEnd
&& !context.ItemMovement.IsItemDragged(item)
&& _context.ActiveSourceItem != null)
{
Debug.Assert(item.Instance != null); // should be true to operator variant
//hasHiddenMatchingTypes = true;
for (var inputIndex = 0; inputIndex < item.Instance.Inputs.Count; inputIndex++)
{
var inputSlot = item.Instance.Inputs[inputIndex];
if (inputSlot.ValueType == _context.DraggedPrimaryOutputType
&& !inputSlot.HasInputConnections)
{
hasHiddenMatchingTypes = true;
break;
}
}
if (hasHiddenMatchingTypes && item != _context.ActiveItem)
{
var indicatorPos = new Vector2(pMinVisible.X + 5 * CanvasScale, pMaxVisible.Y - 5 * CanvasScale);
var isSnappedToAnInput = InputSnapper.BestInputMatch.Item != null;
if (!isItemHovered || isSnappedToAnInput)
{
drawList.AddCircle(indicatorPos, 3, UiColors.ForegroundFull.Fade(Blink));
}
else
{
if (_hoveredForInputPickingId != item.Id)
{
_hoverPickingProgress = 0;
_hoveredForInputPickingId = item.Id;
}
// Small animation from indicator to cursor pos
{
var animDuration = 0.2f;
_hoverPickingProgress = (_hoverPickingProgress + 1 / (ImGui.GetIO().Framerate * animDuration)).Clamp(0, 1);
var smoothedProgress = MathF.Pow(_hoverPickingProgress, 4);
var centerLerp = MathUtils.SmootherStep(0, 1, _hoverPickingProgress);
var center = Vector2.Lerp(indicatorPos, ImGui.GetMousePos(), centerLerp);
var radius = 3 + smoothedProgress * 3;
var circleColor = UiColors.ForegroundFull.Fade(1 - _hoverPickingProgress * 0.5f);
CircleInBoxHelper.DrawClippedCircle(pMinVisible, pMaxVisible, center, radius, circleColor, drawList);
}
// Draw selection outline...
drawList.AddRect(pMinVisible, pMaxVisible, UiColors.ForegroundFull.Fade(_hoverPickingProgress * 0.4f), CanvasScale < 0.5 ? 0 : 5 * CanvasScale, imDrawFlags);
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(8, 8));
ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 3);
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(3, 4));
ImGui.BeginTooltip();
var childUi = item.SymbolUi;
if (childUi != null)
{
if (!TypeNameRegistry.Entries.TryGetValue(_context.DraggedPrimaryOutputType, out var typeName))
typeName = _context.DraggedPrimaryOutputType.Name;
ImGui.PushFont(Fonts.FontSmall);
ImGui.TextColored(UiColors.TextMuted, typeName + " inputs");
ImGui.PopFont();
var inputIndex = 0;
foreach (var inputUi in childUi.InputUis.Values)
{
if (inputIndex <= item.Instance!.Inputs.Count)
{
var input = item.Instance!.Inputs[inputIndex];
if (inputUi.Type == context.DraggedPrimaryOutputType)
{
var isConnected = input.HasInputConnections;
var prefix = isConnected ? "× " : " ";
ImGui.Selectable(prefix + inputUi.InputDefinition.Name);
}
}
inputIndex++;
}
}
ImGui.EndTooltip();
ImGui.PopStyleVar(3);
}
}
}
// 8.5 -> 7.9ms
//return;
// Missing primary input indicator
if (item.InputLines.Length > 0)
{
var inputLine = item.InputLines[0];
// ReSharper disable once ConstantConditionalAccessQualifier
var isMissing = inputLine.InputUi?.Relevancy == Relevancy.Required && inputLine.ConnectionIn == null;
if (isMissing)
{
DrawMissingInputIndicator(drawList, item, pMin, inputLine);
}
}
var borderColor = ColorVariations.OperatorOutline.Apply(typeColor);
IReadOnlyList<ISlot> multiInputSlots = null;
if (CanvasScale > 0.25f)
{
// Input labels...
if ((customUiResult & OpUi.CustomUiResult.PreventInputLabels) == 0)
{
int inputIndex;
var itemWidth = pMax.X - pMin.X;
var labelFontSize = Fonts.FontSmall.FontSize / T3Ui.UiScaleFactor * Fonts.FontSmall.Scale * smallFontScaleFactor;
for (inputIndex = 0; inputIndex < item.InputLines.Length; inputIndex++)
{
var inputLine = item.InputLines[inputIndex];
// Draw multi-input region
if (item.Variant == MagGraphItem.Variants.Operator)
{
var inputSlot = item.Instance.GetInput(inputLine.Input.Id);
var isMultiInput = inputLine.InputUi.InputDefinition.IsMultiInput;
// Start new multiInputGroup
if (isMultiInput && inputLine.MultiInputIndex == 0)
{
multiInputSlots = inputSlot.TryGetAsMultiInput(out var multiInput)
? multiInput.GetCollectedInputs()
: null;
var slotCount = multiInputSlots?.Count ?? 0;
if (slotCount == 0)
slotCount = 1;
var y2 = pMin.Y + GridSizeOnScreen.Y * (inputIndex);
var rMin = new Vector2(pMin.X + (isSelected ? 1 : 0), y2 + 0.2f * GridSizeOnScreen.Y);
var size = new Vector2(pMin.X + itemWidth * 0.04f, y2 + (slotCount - 0.2f) * GridSizeOnScreen.Y) - rMin;
drawList.AddQuadFilled(rMin,
rMin + new Vector2(size.X, size.X),
rMin + new Vector2(size.X, size.Y - size.X),
rMin + new Vector2(0, size.Y),
borderColor.Fade(0.7f)
);
}
if (!isMultiInput)
{
multiInputSlots = null;
}
}
// Skip label for first primary input
if (inputIndex == 0)
continue;
var isMissing = inputLine.InputUi.Relevancy == Relevancy.Required && inputLine.ConnectionIn == null;
if (isMissing)
{
DrawMissingInputIndicator(drawList, item, pMin + new Vector2(0, GridSizeOnScreen.Y * inputIndex), inputLine);
}
var yCenter = pMin.Y + GridSizeOnScreen.Y * (inputIndex + 0.5f) - labelFontSize / 2 - 2;
var labelPos = new Vector2(pMin.X + 8 * CanvasScale, yCenter);
var label = inputLine.InputUi.InputDefinition.Name ?? "?";
if (inputLine.MultiInputIndex > 0)
{
label = " +" + inputLine.MultiInputIndex;
// Draw 16 byte padding borders
if (inputLine.MultiInputIndex % 4 == 0)
{
var y2 = pMin.Y + GridSizeOnScreen.Y * (inputIndex);
drawList.AddRectFilled(new Vector2(pMin.X + itemWidth * 0.04f, y2),
new Vector2(pMin.X + itemWidth * 0.20f, y2 + 1),
borderColor.Fade(0.8f)
);
}
}
drawList.AddText(Fonts.FontSmall,
labelFontSize,
labelPos,
labelColor.Fade(0.7f * CanvasScale.RemapAndClamp(0.3f,0.7f, 0,1) ),
label
);
// Draw Value if possible
if (CanvasScale > 0.4f)
{
string valueAsString;
if (multiInputSlots != null && inputLine.MultiInputIndex < multiInputSlots.Count)
{
var multiInputSlot = multiInputSlots[inputLine.MultiInputIndex];
valueAsString = ValueUtils.GetValueString(multiInputSlot);
}
else
{
valueAsString = ValueUtils.GetValueString(item.Instance.GetInput(inputLine.Input.Id));
}
if (!string.IsNullOrWhiteSpace(valueAsString))
{
ImGui.PushStyleColor(ImGuiCol.Text, labelColor.Rgba);
var valueColor = labelColor;
valueColor.Rgba.W *= 0.6f;
ImGui.PushFont(Fonts.FontSmall);
var labelSize = ImGui.CalcTextSize(valueAsString) * smallFontScaleFactor;
ImGui.PopFont();
var valuePos = new Vector2(pMin.X + (item.Size.X - 5) * CanvasScale - labelSize.X, labelPos.Y);
if (!string.IsNullOrEmpty(valueAsString))
{
drawList.AddText(Fonts.FontSmall,
labelFontSize,
valuePos,
labelColor.Fade(0.5f),
valueAsString
);
}
ImGui.PopStyleColor();
}
}
}
// Draw output labels...
for (var outputIndex = 1; outputIndex < item.OutputLines.Length; outputIndex++)
{
var outputLine = item.OutputLines[outputIndex];
if (outputLine.OutputUi == null)
continue;
ImGui.PushFont(Fonts.FontSmall);
var outputDefinitionName = outputLine.OutputUi.OutputDefinition.Name.AddSpacesForImGuiOutput();
var outputLabelSize = ImGui.CalcTextSize(outputDefinitionName);
ImGui.PopFont();
var fontSizeRatio = labelFontSize / Fonts.FontSmall.FontSize;
var scaledLabelWidth = outputLabelSize.X * fontSizeRatio;
var visibleLineHeight = GridSizeOnScreen.Y;
var yCenter = pMin.Y + visibleLineHeight * outputLine.VisibleIndex + visibleLineHeight / 2 - labelFontSize / 2;
drawList.AddText(Fonts.FontSmall,
labelFontSize,
new Vector2(pMin.X + MagGraphItem.Width * CanvasScale - scaledLabelWidth - 8 * CanvasScale, yCenter),
labelColor.Fade(0.7f * CanvasScale.RemapAndClamp(0.3f, 0.7f, 0, 1)),
outputDefinitionName);
}
}
}
if (item.Variant == MagGraphItem.Variants.Operator)
{
// Animation indicator
var indicatorCount = 0;
if (item.Instance.Parent.Symbol.Animator.IsInstanceAnimated(item.Instance))
{
DrawIndicator(drawList, UiColors.StatusAnimated, idleFadeFactor, pMin, pMax, CanvasScale, ref indicatorCount, "is animated");
}
// Pinned indicator
if (context.Selector.PinnedIds.Contains(item.Instance.SymbolChildId))
{
DrawIndicator(drawList, UiColors.Selection, idleFadeFactor, pMin, pMax, CanvasScale, ref indicatorCount, "is pinned");
}
// Snapshot indicator — suppressed during SkillQuest play mode because every
// solution-bearing op is snapshot-enabled by design; the indicator would just
// be visual noise and reveal which ops the user is "supposed" to touch.
{
if (item.ChildUi.EnabledForSnapshots && !SkillTraining.IsInPlayMode)
{
DrawIndicator(drawList, UiColors.StatusControlled, idleFadeFactor, pMin, pMax, CanvasScale, ref indicatorCount, "enabled for snapshot");
}
}
// Disabled indicator
if (item.SymbolChild.IsDisabled)
{
DrawUtils.DrawOverlayLine(drawList, idleFadeFactor, Vector2.Zero, Vector2.One, pMinVisible, pMaxVisible);
DrawUtils.DrawOverlayLine(drawList, idleFadeFactor, new Vector2(1, 0), new Vector2(0, 1), pMinVisible, pMaxVisible);
}
// Bypass indicator
if (item.SymbolChild.IsBypassed)
{
DrawUtils.DrawOverlayLine(drawList, idleFadeFactor, new Vector2(0.05f, 0.5f), new Vector2(0.4f, 0.5f), pMinVisible, pMaxVisible);
DrawUtils.DrawOverlayLine(drawList, idleFadeFactor, new Vector2(0.6f, 0.5f), new Vector2(0.95f, 0.5f), pMinVisible, pMaxVisible);
DrawUtils.DrawOverlayLine(drawList, idleFadeFactor, new Vector2(0.35f, 0.1f), new Vector2(0.65f, 0.9f), pMinVisible, pMaxVisible);
DrawUtils.DrawOverlayLine(drawList, idleFadeFactor, new Vector2(0.65f, 0.1f), new Vector2(0.35f, 0.9f), pMinVisible, pMaxVisible);
}
if (!string.IsNullOrEmpty(item.ChildUi.Comment))
{
ImGui.SetCursorScreenPos(new Vector2(pMax.X, pMin.Y) + Vector2.Round(new Vector2(-5, -1) * (T3Ui.UiScaleFactor + CanvasScale)));
if (ImGui.InvisibleButton("#comment", new Vector2(15, 15)))
{
context.Selector.SetSelection(item.ChildUi, item.Instance);
context.EditCommentDialog.ShowNextFrame();
}
Icons.DrawIconOnLastItem(Icon.Comment, UiColors.ForegroundFull);
CustomComponents.TooltipForLastItem(UiColors.Text, item.ChildUi.Comment, null, false);
}
// Non-Lib indicator
var isNonLib = !SymbolAnalysis.TryGetOperatorType(item.Instance.Symbol, out var operatorType);
if (isNonLib)
{
DrawIndicator(drawList, UiColors.StatusActivated, idleFadeFactor, pMin, pMax, CanvasScale, ref indicatorCount, "is a custom symbol");
}
}
// Hide additional UI elements when custom ui-op is hovered with control
if (isItemHovered && ImGui.GetIO().KeyCtrl && customUiResult != OpUi.CustomUiResult.None || context.ItemWithActiveCustomUi != null)
{
return;
}
if (CanvasScale < 0.5f)
return;
// Draw free input sockets...
MagGraphItem.InputAnchorPoint inputAnchor = default;
var anchorWidth = 1.5f * 2;
var anchorHeight = 2f * 2;
var inputAnchorCount = item.GetInputAnchorCount();
for (var inputIndex = 0; inputIndex < inputAnchorCount; inputIndex++)
{
item.GetInputAnchorAtIndex(inputIndex, ref inputAnchor);
var isMultiInput = inputAnchor.InputLine.InputUi != null && inputAnchor.InputLine.InputUi.InputDefinition.IsMultiInput;
//var isAlreadyUsed = inputAnchor.SnappedConnectionHash != MagGraphItem.FreeAnchor;
var type2UiProperties = TypeUiRegistry.GetPropertiesForType(inputAnchor.ConnectionType);
var center = TransformPosition(inputAnchor.PositionOnCanvas);
var isInputHovered =
// Only when the canvas is the window under the cursor — otherwise the anchor
// "hovers" through a window covering the graph and shows its tooltip on top.
IsHovered &&
Vector2.Distance(ImGui.GetMousePos(), center) < 7 * CanvasScale &&
context.StateMachine.CurrentState == GraphStates.Default;
var fillColor = isInputHovered
? ColorVariations.Highlight.Apply(type2UiProperties.Color)
: ColorVariations.OperatorOutline.Apply(type2UiProperties.Color);
var anchorOutlineColor = ColorVariations.OperatorOutline.Apply(type2UiProperties.Color);
if (inputAnchor.Direction == MagGraphItem.Directions.Vertical)
{
var pp = new Vector2(center.X, pMinVisible.Y);
if (isInputHovered)
{
//var showTriangleAnchor = true;
var hoverOutputFactor = isInputHovered ? 1.3f : 1;
drawList.AddCircleFilled(center, (3 + 2 * hoverOutputFactor) * CanvasScale - 1, fillColor, 12);
drawList.AddCircle(center, (3 + 2 * hoverOutputFactor) * CanvasScale, anchorOutlineColor, 12);
//showTriangleAnchor = false;
var e = MathF.Round(2 * CanvasScale);
drawList.AddRectFilled(center + new Vector2(-e, 0),
center + new Vector2(e + 1, 1),
anchorOutlineColor);
drawList.AddRectFilled(center + new Vector2(0, -e),
center + new Vector2(1, e + 1),
anchorOutlineColor);
// This will later be used for by DefaultState to create a connection
var hasSnappedConnection = inputAnchor.InputLine.ConnectionIn is { IsSnapped: true };
if (hasSnappedConnection)
{
// Snapped connections are handled by outputs
}
else
{
_context.ActiveItem = item;
_context.ActiveInputDirection = inputAnchor.Direction;
_context.ActiveTargetInputId = inputAnchor.SlotId;
_context.ActiveTargetItem = item;
// Show tooltip with output name and type
if (item.Variant == MagGraphItem.Variants.Operator)
{
var inputLine = inputAnchor.InputLine;
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(5, 5));
ImGui.BeginTooltip();
ImGui.TextUnformatted(inputLine.InputUi.InputDefinition.Name);
var type = inputLine.InputUi.InputDefinition.ValueType;
var typeName = type.Name;
if (typeName == "Single")
typeName = "Float";
var uiProperties = TypeUiRegistry.GetPropertiesForType(type);
ImGui.PushStyleColor(ImGuiCol.Text, uiProperties.Color.Rgba);
ImGui.TextUnformatted(typeName);
ImGui.PopStyleColor();
CustomComponents.HelpText("Click or drag to add");
ImGui.EndTooltip();
ImGui.PopStyleVar();
}
}
}
else
{
drawList.AddTriangleFilled(pp + new Vector2(-anchorWidth, 0) * CanvasScale,
pp + new Vector2(anchorWidth, 0) * CanvasScale,
pp + new Vector2(0, anchorHeight) * CanvasScale,
fillColor);
}
}
else
{
var showTriangleAnchor = true;
var hasSnappedConnection = inputAnchor.InputLine.ConnectionIn is { IsSnapped: true };
if (!hasSnappedConnection && isInputHovered)
{
var hoverOutputFactor = isInputHovered ? 1.3f : 1;
drawList.AddCircleFilled(center, (1 + 2 * hoverOutputFactor) * CanvasScale - 1, fillColor, 12);
drawList.AddCircle(center, (1 + 2 * hoverOutputFactor) * CanvasScale, anchorOutlineColor, 12);
showTriangleAnchor = false;
var e = MathF.Round(2 * CanvasScale);
drawList.AddRectFilled(center + new Vector2(-e, 0),
center + new Vector2(e + 1, 1),
anchorOutlineColor);
drawList.AddRectFilled(center + new Vector2(0, -e),
center + new Vector2(1, e + 1),
anchorOutlineColor);
// This will later be used for by DefaultState to create a connection
//var contextActiveSourceOutputId = inputAnchor.SlotId;
// TODO: Only do this if not snapped. Otherwise output is preferred.
_context.ActiveTargetInputId = inputAnchor.SlotId;
_context.ActiveInputDirection = MagGraphItem.Directions.Horizontal;
_context.ActiveTargetItem = item;
_context.ActiveItem = item;
// Show tooltip with output name and type
if (item.Variant == MagGraphItem.Variants.Operator)
{
var inputLine = inputAnchor.InputLine;
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(5, 5));
ImGui.BeginTooltip();
ImGui.TextUnformatted(inputLine.InputUi.InputDefinition.Name);
var type = inputLine.InputUi.InputDefinition.ValueType;
var typeName = type.Name;
if (typeName == "Single")
typeName = "Float";
var uiProperties = TypeUiRegistry.GetPropertiesForType(type);
ImGui.PushStyleColor(ImGuiCol.Text, uiProperties.Color.Rgba);
ImGui.TextUnformatted(typeName);
ImGui.PopStyleColor();
CustomComponents.HelpText("Click or drag to add");
ImGui.EndTooltip();
ImGui.PopStyleVar();
}
}
var isPotentialConnectionEndDropTarget = _context.StateMachine.CurrentState == GraphStates.DragConnectionEnd
&& _context.DraggedPrimaryOutputType == inputAnchor.ConnectionType;
if (isMultiInput)
{
var isConnected = inputAnchor.InputLine.ConnectionIn != null;
if (isPotentialConnectionEndDropTarget)
{
fillColor = ColorVariations.Highlight.Apply(type2UiProperties.Color);
DrawMultiInputIndicator(item, inputAnchor.SlotId, inputAnchor.InputLine.MultiInputIndex, drawList, inputAnchor.PositionOnCanvas,
fillColor,
InputSnapper.InputSnapTypes.ReplaceMultiInput);
if (isConnected)
{
if (inputAnchor.InputLine.MultiInputIndex == 0)
DrawMultiInputIndicator(item, inputAnchor.SlotId, inputAnchor.InputLine.MultiInputIndex, drawList, inputAnchor.PositionOnCanvas,
fillColor, InputSnapper.InputSnapTypes.InsertBeforeMultiInput);
DrawMultiInputIndicator(item, inputAnchor.SlotId, inputAnchor.InputLine.MultiInputIndex, drawList, inputAnchor.PositionOnCanvas,
fillColor, InputSnapper.InputSnapTypes.InsertAfterMultiInput);
}
showTriangleAnchor = false;
}
}
else
{
// Register for input snapping...
if (isPotentialConnectionEndDropTarget && item != _context.ActiveItem)
{
fillColor = ColorVariations.Highlight.Apply(type2UiProperties.Color).Fade(Blink);
var mousePos = ImGui.GetMousePos();
var isHovered = new ImRect(pMin + new Vector2(4 * CanvasScale,0), pMax).Contains(mousePos);
var preventInsideSnapping = isHovered && hasHiddenMatchingTypes;
if (!preventInsideSnapping)
{
InputSnapper.RegisterAsPotentialTargetInput(item, center, inputAnchor.SlotId);
}
}
else if (inputAnchor.InputLine.ConnectionIn != null)
{
fillColor = fillColor = ColorVariations.OperatorLabel.Apply(type2UiProperties.Color).Fade(0.1f);
}
}
if (showTriangleAnchor)
{
var pp = new Vector2(pMinVisible.X - 1, center.Y);
drawList.AddTriangleFilled(pp + new Vector2(1, 0) + new Vector2(-0, -anchorWidth) * CanvasScale,
pp + new Vector2(1, 0) + new Vector2(anchorHeight, 0) * CanvasScale,
pp + new Vector2(1, 0) + new Vector2(0, anchorWidth) * CanvasScale,
fillColor);
}
}
}
// Draw output sockets...
var count = item.GetOutputAnchorCount();
MagGraphItem.OutputAnchorPoint outputAnchor = default;
for (var index = 0; index < count; index++)
{
item.GetOutputAnchorAtIndex(index, ref outputAnchor);
var type2UiProperties = TypeUiRegistry.GetPropertiesForType(outputAnchor.ConnectionType);
var posOnCanvas = TransformPosition(outputAnchor.PositionOnCanvas);
Vector2 center;
var isPotentialConnectionStartDropTarget = false;
// ...below
if (outputAnchor.Direction == MagGraphItem.Directions.Vertical)
{
center = new Vector2(posOnCanvas.X, pMaxVisible.Y);
center += new Vector2(0, -1);
}
// ... on the right
else
{
var hasOutputSnappedConnection = false;
foreach (var c in item.OutputLines[outputAnchor.OutputLineIndex].ConnectionsOut)
{
if (c.IsSnapped && c.Style is MagGraphConnection.ConnectionStyles.MainOutToMainInSnappedHorizontal
or MagGraphConnection.ConnectionStyles.MainOutToInputSnappedHorizontal)
{
hasOutputSnappedConnection = true;
break;
}
}
// Register for output snapping...
isPotentialConnectionStartDropTarget = _context.StateMachine.CurrentState == GraphStates.DragConnectionBeginning
&& _context.DraggedPrimaryOutputType == outputAnchor.ConnectionType
&& !hasOutputSnappedConnection;
//&& outputAnchor.SnappedConnectionHash == MagGraphItem.FreeAnchor;
if (isPotentialConnectionStartDropTarget)
{
OutputSnapper.RegisterAsPotentialTargetOutput(context, item, outputAnchor);
}
center = new Vector2(pMaxVisible.X, posOnCanvas.Y);
center += new Vector2(-2, 0);
}
var hasSnappedConnection = false;
foreach (var c in item.OutputLines[outputAnchor.OutputLineIndex].ConnectionsOut)
{
if (!c.IsSnapped)
continue;
if (outputAnchor.Direction == MagGraphItem.Directions.Vertical &&
c.Style == MagGraphConnection.ConnectionStyles.MainOutToMainInSnappedVertical
|| outputAnchor.Direction == MagGraphItem.Directions.Horizontal &&
c.Style == MagGraphConnection.ConnectionStyles.MainOutToMainInSnappedHorizontal)
{
hasSnappedConnection = true;
break;
}
}
var isOutputHovered =
// Suppress the hover when another window covers the graph at the cursor.
IsHovered
&& Vector2.Distance(ImGui.GetMousePos(), center) < 7 * CanvasScale
&& context.StateMachine.CurrentState == GraphStates.Default;
var anchorOutlineColor = ColorVariations.OperatorOutline.Apply(type2UiProperties.Color);
var fillColor = ColorVariations.OperatorBackground.Apply(type2UiProperties.Color);
if (isOutputHovered)
{
fillColor = ColorVariations.Highlight.Apply(type2UiProperties.Color);
}
else if (isPotentialConnectionStartDropTarget)
{
fillColor = ColorVariations.ConnectionLines.Apply(type2UiProperties.Color).Fade(Blink);
}
var hoverOutputFactor = isOutputHovered ? 1.3f : 1;
if (!hasSnappedConnection
|| isItemHovered
|| isOutputHovered)
{
var radius = MathF.Min((2 + 2 * hoverOutputFactor) , 10) * CanvasScale;
drawList.AddCircleFilled(center, radius - 1, fillColor, 12);
drawList.AddCircle(center, radius, anchorOutlineColor, 12);
}
if (isOutputHovered)
{
var e = MathF.Round(2 * CanvasScale);
drawList.AddRectFilled(center + new Vector2(-e, 0),
center + new Vector2(e + 1, 1),
anchorOutlineColor);
drawList.AddRectFilled(center + new Vector2(0, -e),
center + new Vector2(1, e + 1),
anchorOutlineColor);
// This will later be used for by DefaultState to create a connection
var contextActiveSourceOutputId = outputAnchor.SlotId;
_context.ActiveSourceItem = item;
_context.ActiveSourceOutputId = contextActiveSourceOutputId;
_context.ActiveOutputDirection = outputAnchor.Direction;
// Show tooltip with output name and type
if (item.Variant == MagGraphItem.Variants.Operator)
{
var outputLine = item.OutputLines.FirstOrDefault(o => o.Id == contextActiveSourceOutputId);
if (outputLine.Id == contextActiveSourceOutputId)
{
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(5, 5));
ImGui.BeginTooltip();
ImGui.TextUnformatted(outputLine.OutputUi.OutputDefinition.Name);
var type = outputLine.OutputUi.OutputDefinition.ValueType;
var typeName = type.Name;
if (typeName == "Single")
typeName = "Float";
var uiProperties = TypeUiRegistry.GetPropertiesForType(type);
ImGui.PushStyleColor(ImGuiCol.Text, uiProperties.Color.Rgba);
ImGui.TextUnformatted(typeName);
ImGui.PopStyleColor();
if (outputLine.Output.DirtyFlag.Trigger != DirtyFlagTrigger.None)
{
if (outputLine.Output.DirtyFlag.Trigger != DirtyFlagTrigger.Animated)
{
ImGui.TextUnformatted("(Cache invalidating)");
}
else if (outputLine.Output.DirtyFlag.Trigger != DirtyFlagTrigger.Always)
{
ImGui.TextUnformatted("(always evaluated)");
}
}
ToolTipContentDrawer.DrawForOutput(outputLine.Output, out var xxx);
ImGui.EndTooltip();
ImGui.PopStyleVar();
}
}
}
}
// Draw additional output indicator
if (item.Variant == MagGraphItem.Variants.Operator)
{
if (item.HasHiddenOutputs)
{
if (_context.StateMachine.CurrentState == GraphStates.PickOutput && _context.ActiveItem == item)
{
OutputPicking.DrawAdditionOutputSelector(context);
}
else
{
var padding = 0.1f;
var p = pMaxVisible - new Vector2(GridSizeOnScreen.Y * padding, GridSizeOnScreen.Y * padding);
var area = new ImRect(p + new Vector2(-0.4f, -0.5f) * CanvasScale * 7,
p + new Vector2(0.4f, 0.5f) * CanvasScale * 7);
// Don't react to the toggle (tooltip or click) when another window covers the graph.
var isToggleHovered = IsHovered && area.Contains(ImGui.GetMousePos());
//var opacity = hoverProgress.RemapAndClamp(0, 1, 0.1f, 0.7f);
drawList.AddTriangleFilled(
p + new Vector2(-0.4f, -0.5f) * CanvasScale * 4,
p + new Vector2(0.4f, 0) * CanvasScale * 4,
p + new Vector2(-0.4f, 0.5f) * CanvasScale * 4,
UiColors.ForegroundFull.Fade(isToggleHovered ? 1 : 0.5f)
);
if (isToggleHovered)
{
ImGui.SetNextWindowPos(p - new Vector2(15, 15));
if (CustomComponents.BeginTooltip())
{
CustomComponents.HintLabel("Show more outputs...");
foreach (var o in item.SymbolUi!.OutputUis.Values)
{
var isVisible = false;
foreach (var visibleOutput in item.OutputLines)
{
if (visibleOutput.Id != o.Id) continue;
isVisible = true;
break;
}
if (isVisible)
continue;
ImGui.TextUnformatted(o.OutputDefinition.Name);
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, UiColors.TextMuted.Rgba);
ImGui.TextUnformatted($" <{o.OutputDefinition.ValueType.Name}>");
ImGui.PopStyleColor();
}
CustomComponents.EndTooltip();
}
if (ImGui.IsMouseClicked(ImGuiMouseButton.Left)
|| ImGui.IsMouseClicked(ImGuiMouseButton.Right))
{
ImGui.OpenPopup("pickOutput");
_context.StateMachine.SetState(GraphStates.PickOutput, context);
_context.ActiveItem = item;
}
}
}
}
}
if (item.Variant == MagGraphItem.Variants.Operator)
{
var instance = item.Instance;
// SkillQuest feedback takes precedence over the op's own IStatusProvider so
// training hints can override status from operators that already self-report.
var statusLevel = IStatusProvider.StatusLevel.Undefined;
string? statusMessage = null;
var badgeAlpha = 1f;
var feedback = SkillTraining.ActiveLevelFeedback;
if (feedback != null && feedback.TryGetOpStatus(instance.SymbolChildId, out var feedbackLevel, out var feedbackMessage))
{
statusLevel = feedbackLevel;
statusMessage = feedbackMessage;
badgeAlpha = feedback.GetOpHintAlpha(instance.SymbolChildId, feedbackLevel);
}
else if (instance is IStatusProvider statusProvider)
{
statusLevel = statusProvider.GetStatusLevel();
statusMessage = statusProvider.GetStatusMessage();
}
if (statusLevel != IStatusProvider.StatusLevel.Success
&& statusLevel != IStatusProvider.StatusLevel.Undefined
&& badgeAlpha > 0f)
{
ImGui.PushID(item.Id.GetHashCode());
ImGui.SetCursorScreenPos(pMinVisible + new Vector2(8, -7));
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
}).Fade(badgeAlpha);
var icon = statusLevel == IStatusProvider.StatusLevel.Tip ? Icon.Tip : Icon.Warning;
Icons.DrawIconOnLastItem(icon, color);
if (ImGui.IsItemHovered())
{
CustomComponents.TooltipForLastItem(color, statusLevel.ToString(), statusMessage, false);
}
ImGui.PopID();
}
if (UserSettings.Config.ShowOperatorStats)
{
var s = item.Size.Y * 0.15f * CanvasScale;
ImGui.SetCursorScreenPos(pMinVisible + new Vector2(s, pMaxVisible.Y - pMinVisible.Y - s));
ImGui.InvisibleButton("#status", new Vector2(s, s));
Icons.DrawIconOnLastItem(Icon.HelpOutline, Color.White with {A = 0.1f});
if (ImGui.BeginItemTooltip())
{
DrawInstanceStatus(instance);
ImGui.EndTooltip();
}
}
}
return;
static void DrawInstanceStatus(Instance instance)
{
var status = instance.Status;
var id = Symbol.Child.HashCodeOf(instance.InstancePath);
ImGui.BeginDisabled();
DrawFlags(status, id);
/* instance.Children.GetExistingCounts(out var cound, out var existingCount, out var childFlags, out var nestedCount, out var nestedExistingCount, out var nestedFlags);
ImGui.TextUnformatted($"My Children: {existingCount}/{cound}, Nested Children: {nestedExistingCount}/{nestedCount}");
DrawFlags(nestedFlags, id + 1);
*/
ImGui.EndDisabled();
return;
static void DrawFlags(Instance.InstanceStatus status, int id)
{
var isInitialized = (status & Instance.InstanceStatus.Initialized) == Instance.InstanceStatus.Initialized;
ImGui.Checkbox($"Initialized ##initflag{id}" , ref isInitialized);
var isActive = (status & Instance.InstanceStatus.Active) == Instance.InstanceStatus.Active;
ImGui.Checkbox($"Active ##activeflag{id}", ref isActive);
var isBypassed = (status & Instance.InstanceStatus.Bypassed) == Instance.InstanceStatus.Bypassed;
ImGui.Checkbox($"Bypassed ##bypassflag{id}", ref isBypassed);
var isConnectedInternally = (status & Instance.InstanceStatus.ConnectedInternally) == Instance.InstanceStatus.ConnectedInternally;
ImGui.Checkbox($"ConnectedInternally ##connectedflag{id}", ref isConnectedInternally);
var isDisposed = (status & Instance.InstanceStatus.Disposed) == Instance.InstanceStatus.Disposed;
ImGui.Checkbox($"Disposed ##disposedflag{id}", ref isDisposed);
var isResourceFoldersDirty = (status & Instance.InstanceStatus.ResourceFoldersDirty) == Instance.InstanceStatus.ResourceFoldersDirty;
ImGui.Checkbox($"ResourceFoldersDirty ##resourcedirtyflag{id}", ref isResourceFoldersDirty);
}
}
}
private void DrawMissingInputIndicator(ImDrawListPtr drawList, MagGraphItem item, Vector2 pMin, MagGraphItem.InputLine inputLine)
{
var s = GridSizeOnScreen.Y;
var c = pMin + new Vector2(-s * 0.2f, s * 0.45f);
var s2 = s * 0.4f;
drawList.AddTriangleFilled(c + new Vector2(0, -0.2f) * s2,
c + new Vector2(-0.2f, 0.2f) * s2,
c + new Vector2(0.2f, 0.2f) * s2,
//+ new Vector2(8, 9) * CanvasScale
UiColors.StatusAttention);
ImGui.SetCursorScreenPos(c - Vector2.One * s2 / 2);
ImGui.PushID(item.Id.GetHashCode());
// Multi-input rows share the same input definition, so the index is needed for a unique ID
ImGui.PushID(inputLine.InputUi.InputDefinition.Id.GetHashCode() + inputLine.MultiInputIndex);
ImGui.InvisibleButton("##missingInput", new Vector2(s2, s2));
if (ImGui.IsItemHovered())
{
CustomComponents.TooltipForLastItem("Requires " + inputLine.InputUi.InputDefinition.Name);
}
ImGui.PopID();
ImGui.PopID();
}
private void DrawMultiInputIndicator(MagGraphItem item, Guid slotId, int multiInputIndex, ImDrawListPtr drawList, Vector2 inputPosOnCanvas, Color color,
InputSnapper.InputSnapTypes snapType)
{
var verticalOffset = snapType switch
{
InputSnapper.InputSnapTypes.Normal => 0,
InputSnapper.InputSnapTypes.InsertBeforeMultiInput => -1f,
InputSnapper.InputSnapTypes.ReplaceMultiInput => 0,
InputSnapper.InputSnapTypes.InsertAfterMultiInput => +1f,
_ => 0f
};
var padding = MagGraphItem.GridSize.Y * 0.15f;
var snapPosOnCanvas = inputPosOnCanvas
+ new Vector2(padding, MagGraphItem.LineHeight * 0.5f * verticalOffset);
snapPosOnCanvas.Y = snapPosOnCanvas.Y.Clamp(item.PosOnCanvas.Y + padding, item.PosOnCanvas.Y + item.Size.Y - padding);
if (snapType != InputSnapper.InputSnapTypes.ReplaceMultiInput)
drawList.AddCircleFilled(TransformPosition(snapPosOnCanvas), 2 * CanvasScale, color.Fade(0.5f + 0.5f * Blink), 8);
var pOnScreen = TransformPosition(snapPosOnCanvas);
InputSnapper.RegisterAsPotentialTargetInput(item, pOnScreen, slotId, snapType, multiInputIndex);
}
private void DrawIndicator(ImDrawListPtr drawList, Color color, float opacity, Vector2 areaMin, Vector2 areaMax, float canvasScale,
ref int indicatorCount, string tooltip=null)
{
const int s = 4;
var dx = (s + 1) * indicatorCount;
var pMin = new Vector2(s + dx,
s) * canvasScale + areaMin;
var pMax = pMin + new Vector2(4, 3) * canvasScale;
drawList.AddRectFilled(pMin, pMax, color.Fade(opacity));
drawList.AddRect(pMin - Vector2.One,
pMax + Vector2.One,
UiColors.WindowBackground.Fade(0.4f * opacity));
if (!string.IsNullOrEmpty(tooltip)&&canvasScale>2.0f)
{
var mousePos = ImGui.GetMousePos();
var area= new ImRect(pMin, pMax);
if (IsHovered && area.Contains(mousePos))
{
ImGui.BeginTooltip();
ImGui.TextUnformatted(tooltip);
ImGui.EndTooltip();
}
}
indicatorCount++;
}
private bool TryDrawTexturePreview(MagGraphItem item, Vector2 itemMin, Vector2 itemMax, ImDrawListPtr drawList, Color typeColor)
{
if (item.Variant != MagGraphItem.Variants.Operator)
return false;
var instance = item.Instance;
if (instance == null || instance.Outputs.Count == 0)
return false;
var firstOutput = instance.Outputs[0];
if (firstOutput is not Slot<Texture2D> textureSlot)
return false;
ShaderResourceView previewTextureView;
float aspect = 1;
var texture = textureSlot.Value;
var uvMin = Vector2.Zero;
var uvMax = Vector2.One;
if (texture != null && !texture.IsDisposed)
{
previewTextureView = SrvManager.GetSrvForTexture(texture);
aspect = (float)texture.Description.Width / texture.Description.Height;
}
else
{
var thumbnail = ThumbnailManager.GetThumbnail(instance.Symbol.Id, instance.Symbol.SymbolPackage, ThumbnailManager.Categories.PackageMeta);
if(!thumbnail.IsReady)
return false;
previewTextureView = ThumbnailManager.AtlasSrv;
uvMin = thumbnail.UvMin;
uvMax = thumbnail.UvMax;
aspect = 4 / 3.0f;
}
var unitScreenHeight = (MagGraphItem.GridSize.Y - 5) * CanvasScale;
var previewSize = new Vector2(unitScreenHeight * aspect, unitScreenHeight);
var maxAspect = 1.6f;
if (previewSize.X > unitScreenHeight * maxAspect)
{
previewSize *= unitScreenHeight / (previewSize.X) * maxAspect;
}
var min = new Vector2(itemMax.X - previewSize.X - 2 * CanvasScale,
itemMin.Y
+ (unitScreenHeight - previewSize.Y) / 2
+ 1 * CanvasScale);
if (previewTextureView == null)
return false;
drawList.AddImage((IntPtr)previewTextureView, min,
min + previewSize,
uvMin,
uvMax,
Color.White);
if (CanvasScale > 0.5f)
{
drawList.AddRect(min - Vector2.One * 0.5f * CanvasScale,
min + previewSize + Vector2.One * 0.5f * CanvasScale,
ColorVariations.ConnectionLines.Apply(typeColor),
2 * CanvasScale,
ImDrawFlags.RoundCornersAll,
1 * CanvasScale);
}
return true;
}
private static Guid _hoveredForInputPickingId;
private static float _hoverPickingProgress;
private static readonly Vector2[] _inputIndicatorPoints = new Vector2[5];
}
internal static class CircleInBoxHelper
{
public static void DrawClippedCircle(Vector2 pMin, Vector2 pMax, Vector2 center, float radius, Color color, ImDrawListPtr drawList)
{
if (!new ImRect(pMin, pMax).Contains(center))
return;
// Bottom right quadrant (0 to π/2)
{
var dx = pMax.X - center.X;
var start = dx < 0 ? MathF.PI / 2
: dx < radius ? MathF.Acos(dx / radius) : 0;
var dy = pMax.Y - center.Y;
var end = dy < 0 ? 0
: dy < radius ? MathF.PI / 2 - MathF.Acos(dy / radius) : MathF.PI / 2;
if (start < end)
{
drawList.PathArcTo(center, radius, start, end);
}
else
{
drawList.PathLineTo(new Vector2(pMax.X, center.Y));
drawList.PathLineTo(pMax);
}
}
// Bottom left quadrant (π/2 to π)
{
var dy = pMax.Y - center.Y;
var start = dy < 0
? MathF.PI / 2
: dy < radius
? MathF.PI / 2 + MathF.Acos(dy / radius)
: MathF.PI / 1.95f;
var dx = center.X - pMin.X;
var end = dx < 0
? MathF.PI
: dx < radius
? MathF.PI - MathF.Acos(dx / radius)
: MathF.PI * 0.95f;
if (start < end)
{
drawList.PathArcTo(center, radius, start, end);
}
else
{
drawList.PathLineTo(new Vector2(pMin.X, pMax.Y));
}
}
// Top left quadrant (π to 3π/2)
{
var dx = center.X - pMin.X;
var start = dx < 0 ? MathF.PI
: dx < radius ? MathF.PI + MathF.Acos(dx / radius) : MathF.PI;
var dy = center.Y - pMin.Y;
var end = dy < 0
? MathF.PI * 1.1f
: dy < radius
? 3 * MathF.PI / 2 - MathF.Acos(dy / radius)
: 2.95f * MathF.PI / 2;
if (start < end)
{
drawList.PathArcTo(center, radius, start, end);
}
else
{
drawList.PathLineTo(pMin);
}
}
// Top right quadrant (3π/2 to 2π)
{
var dy = center.Y - pMin.Y;
var start = dy < 0
? 3 * MathF.PI / 2
: dy < radius
? 3 * MathF.PI / 2 + MathF.Acos(dy / radius)
: 3 * MathF.PI / 2;
var dx = pMax.X - center.X;
var end = dx < 0
? 2 * MathF.PI
: dx < radius
? 2 * MathF.PI - MathF.Acos(dx / radius)
: 2 * MathF.PI;
if (start < end)
{
drawList.PathArcTo(center, radius, start, end);
}
else
{
drawList.PathLineTo(new Vector2(pMax.X, pMin.Y));
drawList.PathLineTo(new Vector2(pMax.X, center.Y));
}
}
//drawList.PathStroke(color, ImDrawFlags.Closed , 2);
drawList.PathFillConvex(color);
}
}