#nullable enable using System.Diagnostics.CodeAnalysis; using T3.Core.Operator; using T3.Core.Operator.Slots; using T3.Editor.Gui.Interaction.Snapping; using T3.Editor.Gui.OpUis; using T3.Editor.Gui.OutputUi; using T3.Editor.Gui.UiHelpers; using T3.Editor.UiModel; using T3.Editor.UiModel.InputsAndTypes; using T3.Editor.UiModel.Selection; // ReSharper disable UseCollectionExpression // ReSharper disable UseWithExpressionToCopyStruct namespace T3.Editor.Gui.MagGraph.Model; internal sealed class MagGraphItem : ISelectableCanvasObject, IValueSnapAttractor { public enum Variants { Operator, Input, Output, Placeholder, Obsolete, } internal int LastUpdateCycle; public Guid Id { get; init; } public Variants Variant; public Type PrimaryType = typeof(float); public required ISelectableCanvasObject Selectable; public SymbolUi.Child? ChildUi; // matches Selected for operators public Vector2 PosOnCanvas { get => Selectable.PosOnCanvas; set => Selectable.PosOnCanvas = value; } public Vector2 DampedPosOnCanvas; public Vector2 Size { get; set; } public bool IsCollapsedAway => ChildUi != null && ChildUi.IsHiddenInCollapsedSection; public OpUiBinding? OpUiBinding; public ImRect Area => ImRect.RectWithSize(PosOnCanvas, Size); public ImRect VerticalStackArea; public override string ToString() => ReadableName; public SymbolUi? SymbolUi; public Symbol.Child? SymbolChild; public IReadOnlyList? InstancePath; public Instance? Instance { get { if (InstancePath == null || SymbolChild == null) return null; SymbolChild.TryGetOrCreateInstance(InstancePath, out var instance, out _, true); return instance; } } public string ReadableName { get { return Variant switch { Variants.Operator => SymbolChild == null ? "???" : SymbolChild.HasCustomName ? "\"" + SymbolChild.ReadableName + "\"" : SymbolChild.ReadableName, Variants.Input => Selectable is IInputUi inputUi ? inputUi.InputDefinition.Name : "???", Variants.Output => Selectable is IOutputUi outputUi ? outputUi.OutputDefinition.Name : "???", _ => "???" }; } } // public bool IsSelected(NodeSelection nodeSelection) // { // return nodeSelection.Selection.Any(c => c.Id == Id); // } public void ResetConnections(int updateCycle) { InputLines = Array.Empty(); OutputLines = Array.Empty(); LastUpdateCycle = updateCycle; } public InputLine[] InputLines = Array.Empty(); public OutputLine[] OutputLines = Array.Empty(); public bool HasHiddenOutputs; public enum InputLineStates { Connected, TempConnection, NotConnected, } public struct InputLine { public Type Type; public Guid Id; public ISlot Input; public IInputUi InputUi; public int VisibleIndex; public MagGraphConnection? ConnectionIn; public int MultiInputIndex; public InputLineStates ConnectionState; } public struct OutputLine { public Guid Id; public ISlot Output; public IOutputUi? OutputUi; public int VisibleIndex; public int OutputIndex; public List ConnectionsOut; public Vector2 GetRightAnchorPosition(MagGraphItem item) { return item.DampedPosOnCanvas + new Vector2(GridSize.X + GridSize.Y * (0.5f * VisibleIndex)); } } public struct InputAnchorPoint { public Vector2 PositionOnCanvas; public Directions Direction; public Type ConnectionType; public int SnappedConnectionHash; public Guid SlotId; public InputLine InputLine; } public struct OutputAnchorPoint { public Vector2 PositionOnCanvas; public Directions Direction; public Type ConnectionType; public int SnappedConnectionHash; public Guid SlotId; public int OutputLineIndex; } public enum Directions { Horizontal, Vertical, } public const float Width = 140; public const float WidthHalf = Width / 2; public const float LineHeight = 35; public static readonly Vector2 GridSize = new(Width, LineHeight); public ImRect Bounds => ImRect.RectWithSize(PosOnCanvas, Size); public static ImRect GetItemsBounds(IEnumerable items) { ImRect extend = default; var index = 0; // Can't use list because enumerable... foreach (var item in items) { if (index == 0) { extend = item.Area; } else { extend.Add(item.Area); } index++; } return extend; } /// /// input anchor taken if /// - connected /// /// output anchor is taken if... /// - /// public void GetOutputAnchorAtIndex(int index, ref OutputAnchorPoint point) { if (index == 0) { point.PositionOnCanvas = new Vector2(WidthHalf, Size.Y) + DampedPosOnCanvas; point.Direction = Directions.Vertical; point.ConnectionType = OutputLines[0].Output.ValueType; point.SnappedConnectionHash = GetSnappedConnectionHash(OutputLines[0].ConnectionsOut); point.SlotId = OutputLines[0].Output.Id; point.OutputLineIndex = 0; return; } var lineIndex = index - 1; point.PositionOnCanvas = new Vector2(Width, (0.5f + OutputLines[lineIndex].VisibleIndex) * LineHeight) + DampedPosOnCanvas; point.Direction = Directions.Horizontal; point.ConnectionType = OutputLines[lineIndex].Output.ValueType; point.SnappedConnectionHash = GetSnappedConnectionHash(OutputLines[lineIndex].ConnectionsOut); point.SlotId = OutputLines[lineIndex].Output.Id; point.OutputLineIndex = lineIndex; } public int GetOutputAnchorCount() => OutputLines.Length == 0 ? 0 : OutputLines.Length + 1; /// /// Get input anchors with current position and orientation for drawing and snapping /// /// /// Using an Enumerable interface here is bad, because it creates a lot of allocations. /// In the long term, this should be cached. /// public void GetInputAnchorAtIndex(int index, ref InputAnchorPoint anchorPoint) { if (index == 0) { anchorPoint.PositionOnCanvas = new Vector2(WidthHalf, 0) + DampedPosOnCanvas; anchorPoint.Direction = Directions.Vertical; anchorPoint.ConnectionType = InputLines[0].Type; anchorPoint.SnappedConnectionHash = InputLines[0].ConnectionIn?.ConnectionHash ?? FreeAnchor; anchorPoint.SlotId = InputLines[0].Id; anchorPoint.InputLine = InputLines[0]; return; } var lineIndex = index - 1; anchorPoint.PositionOnCanvas = new Vector2(0, (0.5f + InputLines[lineIndex].VisibleIndex) * LineHeight) + DampedPosOnCanvas; anchorPoint.Direction = Directions.Horizontal; anchorPoint.ConnectionType = InputLines[lineIndex].Type; anchorPoint.SnappedConnectionHash = InputLines[lineIndex].ConnectionIn?.ConnectionHash ?? FreeAnchor; anchorPoint.SlotId = InputLines[lineIndex].Id; anchorPoint.InputLine = InputLines[lineIndex]; //TODO avoid copy } public int GetInputAnchorCount() => InputLines.Length == 0 ? 0 : InputLines.Length + 1; /** Assume as free (I.e. not connected) unless an connection is snapped, then return this connection as hash. */ private static int GetSnappedConnectionHash(List snapGraphConnections) { foreach (var sc in snapGraphConnections) { if (!sc.IsSnapped) continue; return sc.ConnectionHash; } return FreeAnchor; } public bool TryGetPrimaryInConnection([NotNullWhen(true)]out MagGraphConnection? connection) { connection = null; if (InputLines.Length == 0) return false; connection= InputLines[0].ConnectionIn; return connection != null; } public bool TryGetPrimaryOutConnections(out List connections) { if (OutputLines.Length == 0) { connections = []; return false; } connections= OutputLines[0].ConnectionsOut; return connections.Count > 0; } public const int FreeAnchor = -1; void IValueSnapAttractor.CheckForSnap(ref SnapResult snapResult) { if (snapResult.Orientation == SnapResult.Orientations.Horizontal) { snapResult.TryToImproveWithAnchorValue(DampedPosOnCanvas.X); //snapResult.TryToImproveWithAnchorValue(DampedPosOnCanvas.X + Size.X); } else if (snapResult.Orientation == SnapResult.Orientations.Vertical) { snapResult.TryToImproveWithAnchorValue(DampedPosOnCanvas.Y); //snapResult.TryToImproveWithAnchorValue(DampedPosOnCanvas.Y + Size.Y); } } // // public void ForOutputAnchors(Action call) // { // if (OutputLines.Length == 0) // return; // // call(new AnchorPoint // { // PositionOnCanvas = new Vector2(WidthHalf, Size.Y) + PosOnCanvas, // Direction = Directions.Vertical, // ConnectionType = OutputLines[0].Output.ValueType, // ConnectionHash = OutputLines[0].Output.HasInputConnections // ? OutputLines[0].Output.GetConnection(0).GetHashCode() // : 0, // SlotId = OutputLines[0].Output.Id, // }); // // foreach (var il in OutputLines) // { // call(new AnchorPoint // { // PositionOnCanvas = new Vector2(Width, (0.5f + il.VisibleIndex) * LineHeight) + PosOnCanvas, // Direction = Directions.Horizontal, // ConnectionType = il.Output.ValueType, // ConnectionHash = il.Output.HasInputConnections // ? OutputLines[0].Output.GetConnection(0).GetHashCode() // : 0, // SlotId = il.Output.Id, // }); // } // } public void Select(NodeSelection nodeSelection) { // TODO: Avoid this by not using magGraphItem as selectable if (Variant == MagGraphItem.Variants.Operator && Instance?.Parent != null && SymbolUiRegistry.TryGetSymbolUi(Instance.Parent.Symbol.Id, out var parentSymbolUi) && parentSymbolUi.ChildUis.TryGetValue(Instance.SymbolChildId, out var childUi)) { nodeSelection.SetSelection(childUi, Instance); } else { //nodeSelection.Clear(); nodeSelection.SetSelection(Selectable); } } public void AddToSelection(NodeSelection nodeSelection) { // TODO: Avoid this by not using magGraphItem as selectable if (Variant == MagGraphItem.Variants.Operator && Instance?.Parent != null && SymbolUiRegistry.TryGetSymbolUi(Instance.Parent.Symbol.Id, out var parentSymbolUi) && parentSymbolUi.ChildUis.TryGetValue(Instance.SymbolChildId, out var childUi)) { nodeSelection.AddSelection(childUi, Instance); } else { nodeSelection.AddSelection(this); } } }