项目文件夹

文件
2026-07-13 13:15:05 +08:00

1424 行
67 KiB
C#

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
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);
}
}