chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,984 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using T3.Core.Operator;
|
||||
using T3.Core.Operator.Slots;
|
||||
using T3.Editor.Gui.MagGraph.Interaction;
|
||||
using T3.Editor.Gui.Interaction;
|
||||
using T3.Editor.Gui.MagGraph.States;
|
||||
using T3.Editor.Gui.OutputUi;
|
||||
using T3.Editor.Gui.UiHelpers;
|
||||
using T3.Editor.UiModel;
|
||||
using T3.Editor.UiModel.InputsAndTypes;
|
||||
|
||||
// ReSharper disable MergeIntoPattern
|
||||
|
||||
// ReSharper disable ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
|
||||
// ReSharper disable ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
|
||||
// ReSharper disable SuggestBaseTypeForParameter
|
||||
|
||||
namespace T3.Editor.Gui.MagGraph.Model;
|
||||
|
||||
/// <summary>
|
||||
/// Holds an intermediate view model that is updated if required. This view model
|
||||
/// builds referenceable items, view elements, and connections. This makes it much easier to traverse the
|
||||
/// graph without dictionary lookups. The layout also precomputes the visibility of input links, which simplifies
|
||||
/// the layout and rendering of connection lines (one of the most complicated parts of the legacy layout).
|
||||
/// Generate considerations:
|
||||
/// - The layout model is a temporary data that is completely generated from the t3-ui data.
|
||||
/// - Its computation is expensive and should only be done if required.
|
||||
/// - This might be especially complicated if the state cannot be stored in the layout model, because it
|
||||
/// relies on a temp. property like expanding a parameter type group.
|
||||
///
|
||||
/// It basically separates the following steps:
|
||||
/// - generating the ui-model data (esp. elements, their inputs and connection slot indices)
|
||||
/// - Updating the layouts
|
||||
///
|
||||
/// </summary>
|
||||
internal sealed class MagGraphLayout
|
||||
{
|
||||
public readonly Dictionary<Guid, MagGraphItem> Items = new(127);
|
||||
public readonly List<MagGraphConnection> MagConnections = new(127);
|
||||
public readonly Dictionary<Guid, MagGraphSection> Sections = new(63);
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Sections"/> ordered outermost-first, so nested sections draw on top of the
|
||||
/// sections that contain them. Rebuilt on structural changes; iterate this when drawing.
|
||||
/// </summary>
|
||||
public readonly List<MagGraphSection> SectionsInDrawOrder = new(63);
|
||||
|
||||
public void ComputeLayout(GraphUiContext context, bool forceUpdate = false)
|
||||
{
|
||||
var compositionOp = context.CompositionInstance;
|
||||
|
||||
if (!SymbolUiRegistry.TryGetSymbolUi(compositionOp.Symbol.Id, out var parentSymbolUi))
|
||||
return;
|
||||
|
||||
if (StructureFlaggedAsChanged)
|
||||
{
|
||||
// Only bump the display-cache version here: this also runs when merely checking out a
|
||||
// composition, which must not mark it as modified (read-only symbols would prompt to
|
||||
// save as copy). Actual edits flag the symbol through their commands.
|
||||
parentSymbolUi.BumpVersionCounter();
|
||||
}
|
||||
|
||||
if (forceUpdate || FrameStats.Last.UndoRedoTriggered || StructureFlaggedAsChanged ||
|
||||
HasCompositionDataChanged(compositionOp.Symbol, ref _compositionModelHash))
|
||||
RefreshDataStructure(context, parentSymbolUi);
|
||||
|
||||
// TODO: This only needs to be done, on structural changes or when items have been moved
|
||||
UpdateConnectionLayout();
|
||||
ComputeVerticalStackBoundaries(context.View);
|
||||
}
|
||||
|
||||
public void FlagStructureAsChanged()
|
||||
{
|
||||
StructureFlaggedAsChanged = true;
|
||||
}
|
||||
|
||||
private int _structureUpdateCycle;
|
||||
|
||||
private void RefreshDataStructure(GraphUiContext context, SymbolUi parentSymbolUi)
|
||||
{
|
||||
var composition = context.CompositionInstance;
|
||||
|
||||
_structureUpdateCycle++;
|
||||
CollectItemReferences(composition, parentSymbolUi);
|
||||
CollectedSections(parentSymbolUi);
|
||||
SectionTree.UpdateOwnershipFromGeometry(parentSymbolUi);
|
||||
SectionTree.UpdateCollapsedVisibility(parentSymbolUi);
|
||||
RebuildSectionDrawOrder(parentSymbolUi);
|
||||
UpdateConnectionSources(composition);
|
||||
UpdateVisibleItemLines(context);
|
||||
CollectConnectionReferences(composition);
|
||||
StructureFlaggedAsChanged = false;
|
||||
}
|
||||
|
||||
private void CollectedSections(SymbolUi compositionSymbolUi)
|
||||
{
|
||||
var addedCount = 0;
|
||||
var updatedCount = 0;
|
||||
|
||||
foreach (var (sectionId, section) in compositionSymbolUi.Sections)
|
||||
{
|
||||
if (Sections.TryGetValue(sectionId, out var opItem))
|
||||
{
|
||||
updatedCount++;
|
||||
opItem.Section = section; // instance can change through undo or reload
|
||||
opItem.LastUpdateCycle = _structureUpdateCycle;
|
||||
}
|
||||
else
|
||||
{
|
||||
opItem = new MagGraphSection
|
||||
{
|
||||
Id = section.Id,
|
||||
Section = section,
|
||||
DampedPosOnCanvas = section.PosOnCanvas,
|
||||
DampedSize = section.Size,
|
||||
LastUpdateCycle = _structureUpdateCycle,
|
||||
};
|
||||
|
||||
Sections[sectionId] = opItem;
|
||||
addedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
var hasObsoleteSections = Sections.Count > updatedCount + addedCount;
|
||||
if (!hasObsoleteSections) return;
|
||||
|
||||
foreach (var a in Sections.Values)
|
||||
{
|
||||
if (a.LastUpdateCycle >= _structureUpdateCycle)
|
||||
continue;
|
||||
|
||||
Sections.Remove(a.Id);
|
||||
a.IsRemoved = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Orders sections outermost-first by nesting depth so inner sections draw on top of their
|
||||
/// containers. Runs after ownership is resolved, so <see cref="Section.ParentSectionId"/> is current.
|
||||
/// </summary>
|
||||
private void RebuildSectionDrawOrder(SymbolUi compositionSymbolUi)
|
||||
{
|
||||
SectionsInDrawOrder.Clear();
|
||||
foreach (var magSection in Sections.Values)
|
||||
{
|
||||
magSection.NestingDepth = GetSectionDepth(compositionSymbolUi, magSection.Section);
|
||||
SectionsInDrawOrder.Add(magSection);
|
||||
}
|
||||
|
||||
SectionsInDrawOrder.Sort(static (a, b) => a.NestingDepth.CompareTo(b.NestingDepth));
|
||||
}
|
||||
|
||||
private static int GetSectionDepth(SymbolUi compositionSymbolUi, Section section)
|
||||
{
|
||||
var depth = 0;
|
||||
var parentId = section.ParentSectionId;
|
||||
var remainingSteps = compositionSymbolUi.Sections.Count; // guards against parent-reference cycles
|
||||
while (parentId != Guid.Empty && remainingSteps-- > 0)
|
||||
{
|
||||
if (!compositionSymbolUi.Sections.TryGetValue(parentId, out var parent))
|
||||
break;
|
||||
|
||||
depth++;
|
||||
parentId = parent.ParentSectionId;
|
||||
}
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// This method is extremely slow for large compositions...
|
||||
/// </remarks>
|
||||
private void CollectItemReferences(Instance compositionOp, SymbolUi compositionSymbolUi)
|
||||
{
|
||||
//Items.Clear();
|
||||
|
||||
var addedItemCount = 0;
|
||||
var updatedItemCount = 0;
|
||||
|
||||
foreach (var (childId, childInstance) in compositionOp.Children)
|
||||
{
|
||||
if (!compositionSymbolUi.ChildUis.TryGetValue(childId, out var childUi))
|
||||
continue;
|
||||
|
||||
if (!SymbolUiRegistry.TryGetSymbolUi(childInstance.Symbol.Id, out var symbolUi))
|
||||
continue;
|
||||
|
||||
if (Items.TryGetValue(childId, out var opItem))
|
||||
{
|
||||
opItem.ResetConnections(_structureUpdateCycle);
|
||||
updatedItemCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
opItem = new MagGraphItem
|
||||
{
|
||||
// Variant = MagGraphItem.Variants.Operator,
|
||||
Id = childId,
|
||||
// Instance = childInstance,
|
||||
Selectable = childUi,
|
||||
SymbolChild = childInstance.SymbolChild,
|
||||
ChildUi = childUi,
|
||||
DampedPosOnCanvas = childUi.PosOnCanvas,
|
||||
InstancePath = childInstance.InstancePath,
|
||||
// SymbolUi = symbolUi,
|
||||
// SymbolChild = childUi.SymbolChild,
|
||||
// ChildUi = childUi,
|
||||
// Size = MagGraphItem.GridSize,
|
||||
// LastUpdateCycle = _structureUpdateCycle,
|
||||
// DampedPosOnCanvas = childUi.PosOnCanvas,
|
||||
};
|
||||
Items[childId] = opItem;
|
||||
addedItemCount++;
|
||||
}
|
||||
|
||||
opItem.Variant = MagGraphItem.Variants.Operator;
|
||||
//opItem.Id = childId;
|
||||
opItem.InstancePath = childInstance.InstancePath;
|
||||
opItem.Selectable = childUi;
|
||||
opItem.SymbolUi = symbolUi;
|
||||
opItem.SymbolChild = childUi.SymbolChild;
|
||||
opItem.ChildUi = childUi;
|
||||
opItem.Size = MagGraphItem.GridSize;
|
||||
opItem.LastUpdateCycle = _structureUpdateCycle;
|
||||
}
|
||||
|
||||
foreach (var input in compositionOp.Inputs)
|
||||
{
|
||||
var inputUi = compositionSymbolUi.InputUis[input.Id];
|
||||
if (Items.TryGetValue(input.Id, out var inputOp))
|
||||
{
|
||||
inputOp.ResetConnections(_structureUpdateCycle);
|
||||
// Refresh references so renames (same id, new definition) are picked up
|
||||
inputOp.Selectable = inputUi;
|
||||
inputOp.SymbolChild = compositionOp.SymbolChild;
|
||||
inputOp.InstancePath = compositionOp.InstancePath;
|
||||
updatedItemCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
Items[input.Id] = new MagGraphItem
|
||||
{
|
||||
Variant = MagGraphItem.Variants.Input,
|
||||
Id = input.Id,
|
||||
SymbolChild = compositionOp.SymbolChild,
|
||||
InstancePath = compositionOp.InstancePath,
|
||||
Selectable = inputUi,
|
||||
Size = MagGraphItem.GridSize,
|
||||
DampedPosOnCanvas = inputUi.PosOnCanvas,
|
||||
LastUpdateCycle = _structureUpdateCycle,
|
||||
};
|
||||
addedItemCount++;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var output in compositionOp.Outputs)
|
||||
{
|
||||
var outputUi = compositionSymbolUi.OutputUis[output.Id];
|
||||
if (Items.TryGetValue(output.Id, out var outputOp))
|
||||
{
|
||||
outputOp.ResetConnections(_structureUpdateCycle);
|
||||
// Refresh references so renames (same id, new definition) are picked up
|
||||
outputOp.Selectable = outputUi;
|
||||
outputOp.SymbolChild = compositionOp.SymbolChild;
|
||||
outputOp.InstancePath = compositionOp.InstancePath;
|
||||
updatedItemCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
Items[output.Id] = new MagGraphItem
|
||||
{
|
||||
Variant = MagGraphItem.Variants.Output,
|
||||
Id = output.Id,
|
||||
SymbolChild = compositionOp.SymbolChild,
|
||||
InstancePath = compositionOp.InstancePath,
|
||||
Selectable = outputUi,
|
||||
Size = MagGraphItem.GridSize,
|
||||
DampedPosOnCanvas = outputUi.PosOnCanvas,
|
||||
};
|
||||
addedItemCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (Items.TryGetValue(PlaceholderCreation.PlaceHolderId, out var placeholderOp))
|
||||
{
|
||||
placeholderOp.ResetConnections(_structureUpdateCycle);
|
||||
updatedItemCount++;
|
||||
}
|
||||
|
||||
var hasObsoleteItems = Items.Count > updatedItemCount + addedItemCount;
|
||||
if (hasObsoleteItems)
|
||||
{
|
||||
foreach (var item in Items.Values)
|
||||
{
|
||||
if (item.LastUpdateCycle >= _structureUpdateCycle)
|
||||
continue;
|
||||
|
||||
Items.Remove(item.Id);
|
||||
item.Variant = MagGraphItem.Variants.Obsolete;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly HashSet<int> _connectedOutputs = new(100);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetConnectionSourceHash(Symbol.Connection c)
|
||||
{
|
||||
var hash = c.SourceSlotId.GetHashCode();
|
||||
hash = hash * 31 + c.SourceParentOrChildId.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetHashCodeForOutputSlot(ISlot output)
|
||||
{
|
||||
var hash = output.Id.GetHashCode();
|
||||
hash = hash * 31 + output.Parent.SymbolChildId.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sadly there is no obvious easy method to store if an output has a connection.
|
||||
/// So we collect connected outputs in a hashset.
|
||||
/// </summary>
|
||||
private void UpdateConnectionSources(Instance composition)
|
||||
{
|
||||
_connectedOutputs.Clear();
|
||||
|
||||
foreach (var c in composition.Symbol.Connections)
|
||||
{
|
||||
if (c.IsConnectedToSymbolInput)
|
||||
continue;
|
||||
|
||||
_connectedOutputs.Add(GetConnectionSourceHash(c));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVisibleItemLines(GraphUiContext context)
|
||||
{
|
||||
var inputLines = new List<MagGraphItem.InputLine>(8);
|
||||
var outputLines = new List<MagGraphItem.OutputLine>(4);
|
||||
|
||||
foreach (var item in Items.Values)
|
||||
{
|
||||
|
||||
inputLines.Clear();
|
||||
outputLines.Clear();
|
||||
|
||||
// if (item.IsCollapsedAway)
|
||||
// continue;
|
||||
|
||||
var visibleIndex = 0;
|
||||
|
||||
switch (item.Variant)
|
||||
{
|
||||
// Collect inputs
|
||||
case MagGraphItem.Variants.Operator:
|
||||
{
|
||||
visibleIndex = CollectVisibleLines(context, item, inputLines, outputLines, _connectedOutputs);
|
||||
break;
|
||||
}
|
||||
case MagGraphItem.Variants.Input:
|
||||
{
|
||||
Debug.Assert(item.Selectable is IInputUi);
|
||||
var parentInstance = item.Instance;
|
||||
|
||||
Debug.Assert(parentInstance != null);
|
||||
var parentInput = parentInstance.Inputs.FirstOrDefault(i => i.Id == item.Id);
|
||||
|
||||
outputLines.Add(new MagGraphItem.OutputLine
|
||||
{
|
||||
Output = parentInput!, // This looks confusing but is correct
|
||||
Id = parentInput!.Id,
|
||||
OutputUi = null,
|
||||
OutputIndex = 0,
|
||||
VisibleIndex = 0,
|
||||
ConnectionsOut = [],
|
||||
});
|
||||
|
||||
item.PrimaryType = parentInput.ValueType;
|
||||
break;
|
||||
}
|
||||
case MagGraphItem.Variants.Output:
|
||||
{
|
||||
Debug.Assert(item.Selectable is IOutputUi);
|
||||
|
||||
var parentInstance = item.Instance;
|
||||
Debug.Assert(parentInstance != null);
|
||||
|
||||
var parentOutput = parentInstance.Outputs.FirstOrDefault(o => o.Id == item.Id);
|
||||
Debug.Assert(parentOutput != null);
|
||||
|
||||
inputLines.Add(new MagGraphItem.InputLine
|
||||
{
|
||||
Type = parentOutput.ValueType,
|
||||
Id = parentOutput.Id,
|
||||
Input = parentOutput,
|
||||
MultiInputIndex = 0,
|
||||
VisibleIndex = 0,
|
||||
});
|
||||
|
||||
item.PrimaryType = parentOutput.ValueType;
|
||||
break;
|
||||
}
|
||||
case MagGraphItem.Variants.Placeholder:
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
item.InputLines = inputLines.ToArray();
|
||||
item.OutputLines = outputLines.ToArray();
|
||||
|
||||
//var count = Math.Max(1, item.InputLines.Count + item.OutputLines.Count -2);
|
||||
item.Size = new Vector2(MagGraphItem.Width, MagGraphItem.LineHeight * (Math.Max(1, visibleIndex)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aggregate visible inputs and outputs into a consistent stack
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is accessible because for some use-cases we need to compute the height of inserted items.
|
||||
/// </remarks>
|
||||
internal static int CollectVisibleLines(GraphUiContext context, MagGraphItem item, List<MagGraphItem.InputLine> inputLines,
|
||||
List<MagGraphItem.OutputLine> outputLines,
|
||||
HashSet<int>? connectedOutputs = null)
|
||||
{
|
||||
var instance = item.Instance;
|
||||
Debug.Assert(instance != null && item.SymbolUi != null);
|
||||
int visibleIndex = 0;
|
||||
item.HasHiddenOutputs = false;
|
||||
|
||||
for (var inputLineIndex = 0; inputLineIndex < instance.Inputs.Count; inputLineIndex++)
|
||||
{
|
||||
var input = instance.Inputs[inputLineIndex];
|
||||
if (!item.SymbolUi.InputUis.TryGetValue(input.Id, out var inputUi))
|
||||
{
|
||||
Log.Warning("Can't find input? " + input.Id);
|
||||
continue;
|
||||
}
|
||||
|
||||
var isRelevant = inputUi.Relevancy is Relevancy.Relevant or Relevancy.Required;
|
||||
//var isMatchingType = false; //input.ValueType == typeof(float);//ConnectionTargetType;
|
||||
|
||||
var isPrimaryInput = inputLineIndex == 0;
|
||||
|
||||
if (input.IsMultiInput && input is IMultiInputSlot)
|
||||
{
|
||||
var shouldInputBeVisible = isRelevant || isPrimaryInput || input.HasInputConnections;
|
||||
|
||||
var connectionsToInput = context
|
||||
.CompositionInstance
|
||||
.Symbol
|
||||
.Connections
|
||||
.FindAll(c => c.TargetParentOrChildId == item.Id
|
||||
&& c.TargetSlotId == input.Id);
|
||||
|
||||
var multiConIndex = 0;
|
||||
var virtualConnectionCount = 0; // including disconnected
|
||||
var tempConnectionCount = 0; // count additional to connectionsToInput.count
|
||||
for (var virtualSubIndex = 0; virtualSubIndex < connectionsToInput.Count + tempConnectionCount + 1; virtualSubIndex++)
|
||||
{
|
||||
if (IsDisconnectedVisibleMultiInputLine(context, item.Id, input.Id, virtualConnectionCount))
|
||||
{
|
||||
inputLines.Add(new MagGraphItem.InputLine
|
||||
{
|
||||
Id = input.Id,
|
||||
Type = input.ValueType,
|
||||
Input = input,
|
||||
InputUi = inputUi,
|
||||
VisibleIndex = visibleIndex,
|
||||
MultiInputIndex = multiConIndex,
|
||||
ConnectionState = MagGraphItem.InputLineStates.TempConnection,
|
||||
});
|
||||
visibleIndex++;
|
||||
virtualConnectionCount++;
|
||||
tempConnectionCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (shouldInputBeVisible && multiConIndex < connectionsToInput.Count)
|
||||
{
|
||||
inputLines.Add(new MagGraphItem.InputLine
|
||||
{
|
||||
Id = input.Id,
|
||||
Type = input.ValueType,
|
||||
Input = input,
|
||||
InputUi = inputUi,
|
||||
VisibleIndex = visibleIndex,
|
||||
MultiInputIndex = multiConIndex,
|
||||
ConnectionState = MagGraphItem.InputLineStates.Connected,
|
||||
});
|
||||
virtualConnectionCount++;
|
||||
visibleIndex++;
|
||||
multiConIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// Show input even it not connected
|
||||
if (shouldInputBeVisible && virtualConnectionCount == 0)
|
||||
{
|
||||
inputLines.Add(new MagGraphItem.InputLine
|
||||
{
|
||||
Id = input.Id,
|
||||
Type = input.ValueType,
|
||||
Input = input,
|
||||
InputUi = inputUi,
|
||||
VisibleIndex = visibleIndex,
|
||||
MultiInputIndex = multiConIndex,
|
||||
ConnectionState = MagGraphItem.InputLineStates.NotConnected,
|
||||
});
|
||||
visibleIndex++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var hasInputConnections = input.HasInputConnections;
|
||||
if (isRelevant || isPrimaryInput || hasInputConnections)
|
||||
{
|
||||
inputLines.Add(new MagGraphItem.InputLine
|
||||
{
|
||||
Id = input.Id,
|
||||
Type = input.ValueType,
|
||||
Input = input,
|
||||
InputUi = inputUi,
|
||||
VisibleIndex = visibleIndex,
|
||||
ConnectionState = hasInputConnections
|
||||
? MagGraphItem.InputLineStates.Connected
|
||||
: MagGraphItem.InputLineStates.NotConnected,
|
||||
});
|
||||
visibleIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var hasTempConnection = context.DisconnectedInputHashes.Count > 0
|
||||
&& context.DisconnectedInputHashes.Contains(MagGraphConnection.GetItemInputHash(item.Id, input.Id, 0));
|
||||
if (hasTempConnection)
|
||||
{
|
||||
inputLines.Add(new MagGraphItem.InputLine
|
||||
{
|
||||
Id = input.Id,
|
||||
Type = input.ValueType,
|
||||
Input = input,
|
||||
InputUi = inputUi,
|
||||
VisibleIndex = visibleIndex,
|
||||
ConnectionState = MagGraphItem.InputLineStates.TempConnection,
|
||||
});
|
||||
visibleIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hasNoInputs = visibleIndex == 0;
|
||||
|
||||
// Collect outputs
|
||||
for (var outputIndex = 0; outputIndex < instance.Outputs.Count; outputIndex++)
|
||||
{
|
||||
var output = instance.Outputs[outputIndex];
|
||||
if (!item.SymbolUi.OutputUis.TryGetValue(output.Id, out var outputUi))
|
||||
{
|
||||
Log.Warning("Can't find outputUi:" + output.Id);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Should non connected secondary outputs be visible?
|
||||
int outputHash2 = GetHashCodeForOutputSlot(output);
|
||||
var isConnected = connectedOutputs != null && connectedOutputs.Contains(outputHash2);
|
||||
if (outputIndex > 0 && !isConnected)
|
||||
{
|
||||
item.HasHiddenOutputs = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
outputLines.Add(new MagGraphItem.OutputLine
|
||||
{
|
||||
Id = outputUi.Id,
|
||||
Output = output,
|
||||
OutputUi = outputUi,
|
||||
OutputIndex = outputIndex,
|
||||
VisibleIndex = outputIndex == 0 ? 0 : visibleIndex,
|
||||
ConnectionsOut = [],
|
||||
});
|
||||
if (outputIndex == 0)
|
||||
{
|
||||
item.PrimaryType = output.ValueType;
|
||||
if (hasNoInputs)
|
||||
visibleIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
visibleIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// Fix height
|
||||
|
||||
return visibleIndex;
|
||||
}
|
||||
|
||||
private static bool IsDisconnectedVisibleMultiInputLine(GraphUiContext context, Guid itemId, Guid inputId,
|
||||
int multiInputIndex)
|
||||
{
|
||||
var itemInputHash = MagGraphConnection.GetItemInputHash(itemId, inputId, multiInputIndex);
|
||||
var isDisconnectedVisibleMultiInputLine =
|
||||
context.DisconnectedInputHashes.Count > 0
|
||||
&& context.DisconnectedInputHashes.Contains(itemInputHash);
|
||||
return isDisconnectedVisibleMultiInputLine;
|
||||
}
|
||||
|
||||
private void CollectConnectionReferences(Instance composition)
|
||||
{
|
||||
MagConnections.Clear();
|
||||
MagConnections.Capacity = composition.Symbol.Connections.Count;
|
||||
|
||||
Symbol.Connection c; // Avoid repetitive closure capture
|
||||
for (var cIndex = 0; cIndex < composition.Symbol.Connections.Count; cIndex++)
|
||||
{
|
||||
c = composition.Symbol.Connections[cIndex];
|
||||
|
||||
if (c.IsConnectedToSymbolInput)
|
||||
{
|
||||
if (!Items.TryGetValue(c.TargetParentOrChildId, out var targetFromInputItem)
|
||||
|| !Items.TryGetValue(c.SourceSlotId, out var symbolInputItem))
|
||||
continue;
|
||||
|
||||
Debug.Assert(targetFromInputItem.Instance != null);
|
||||
|
||||
var symbolInput = composition.Inputs.FirstOrDefault(i => i.Id == c.SourceSlotId);
|
||||
var targetInput = targetFromInputItem.Instance.Inputs.FirstOrDefault(i => i.Input.InputDefinition.Id == c.TargetSlotId);
|
||||
Debug.Assert(targetInput != null);
|
||||
|
||||
FindVisibleIndex(targetFromInputItem, targetInput, out var targetInputIndex, out var targetMultiInputIndex);
|
||||
|
||||
var connectionFromSymbolInput = new MagGraphConnection
|
||||
{
|
||||
Style = MagGraphConnection.ConnectionStyles.Unknown,
|
||||
SourceItem = symbolInputItem,
|
||||
SourceOutput = symbolInput,
|
||||
TargetItem = targetFromInputItem,
|
||||
//TargetInput = targetInput,
|
||||
InputLineIndex = targetInputIndex,
|
||||
OutputLineIndex = 0,
|
||||
ConnectionHash = c.GetHashCode(),
|
||||
MultiInputIndex = targetMultiInputIndex,
|
||||
VisibleOutputIndex = 0,
|
||||
};
|
||||
|
||||
symbolInputItem.OutputLines[0].ConnectionsOut.Add(connectionFromSymbolInput);
|
||||
targetFromInputItem.InputLines[targetInputIndex].ConnectionIn = connectionFromSymbolInput;
|
||||
MagConnections.Add(connectionFromSymbolInput);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c.IsConnectedToSymbolOutput)
|
||||
{
|
||||
if (!Items.TryGetValue(c.SourceParentOrChildId, out var sourceItem2)
|
||||
|| !Items.TryGetValue(c.TargetSlotId, out var symbolOutputItem))
|
||||
{
|
||||
Log.Warning("Inconsistent output connection " + c);
|
||||
continue;
|
||||
}
|
||||
|
||||
Debug.Assert(sourceItem2.Instance != null);
|
||||
|
||||
//var symbolOutput = composition.Outputs.FirstOrDefault((o => o.Id == c.TargetSlotId));
|
||||
var sourceOutput = sourceItem2.Instance.Outputs.FirstOrDefault(o => o.Id == c.SourceSlotId);
|
||||
|
||||
Debug.Assert(sourceOutput != null);
|
||||
|
||||
// Find the position within OutputLines (not OutputLine.OutputIndex, which indexes
|
||||
// instance.Outputs and diverges once hidden outputs are skipped)
|
||||
int outputLineIndex2;
|
||||
for (outputLineIndex2 = 0; outputLineIndex2 < sourceItem2.OutputLines.Length; outputLineIndex2++)
|
||||
{
|
||||
if (sourceItem2.OutputLines[outputLineIndex2].Output == sourceOutput)
|
||||
break;
|
||||
}
|
||||
|
||||
if (outputLineIndex2 >= sourceItem2.OutputLines.Length)
|
||||
{
|
||||
Log.Warning($"Can't find output line for connection to symbol output in {sourceItem2}");
|
||||
outputLineIndex2 = sourceItem2.OutputLines.Length - 1;
|
||||
}
|
||||
|
||||
var visibleOutputIndex = sourceItem2.OutputLines[outputLineIndex2].VisibleIndex;
|
||||
|
||||
var connectionToSymbolOutput = new MagGraphConnection
|
||||
{
|
||||
Style = MagGraphConnection.ConnectionStyles.Unknown,
|
||||
SourceItem = sourceItem2,
|
||||
SourceOutput = sourceOutput,
|
||||
TargetItem = symbolOutputItem,
|
||||
//TargetInput = targetInput,
|
||||
InputLineIndex = 0,
|
||||
OutputLineIndex = outputLineIndex2,
|
||||
ConnectionHash = c.GetHashCode(),
|
||||
MultiInputIndex = 0,
|
||||
VisibleOutputIndex = visibleOutputIndex,
|
||||
};
|
||||
|
||||
sourceItem2.OutputLines[outputLineIndex2].ConnectionsOut.Add(connectionToSymbolOutput);
|
||||
symbolOutputItem.InputLines[0].ConnectionIn = connectionToSymbolOutput;
|
||||
MagConnections.Add(connectionToSymbolOutput);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip connection to symbol inputs and outputs for now
|
||||
if (c.IsConnectedToSymbolOutput
|
||||
|| !Items.TryGetValue(c.SourceParentOrChildId, out var sourceItem)
|
||||
|| !Items.TryGetValue(c.TargetParentOrChildId, out var targetItem)
|
||||
)
|
||||
{
|
||||
//Log.Warning("Can't find items for connection line");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sourceItem.IsCollapsedAway && targetItem.IsCollapsedAway)
|
||||
continue;
|
||||
|
||||
// Connections between nodes
|
||||
if (sourceItem.Instance == null || targetItem.Instance == null)
|
||||
continue;
|
||||
|
||||
var output = sourceItem.Variant == MagGraphItem.Variants.Input
|
||||
? sourceItem.Instance.Inputs.FirstOrDefault(o => o.Id == c.SourceSlotId)
|
||||
: sourceItem.Instance.Outputs.FirstOrDefault(o => o.Id == c.SourceSlotId);
|
||||
|
||||
var input = targetItem.Instance.Inputs.FirstOrDefault(i => i.Input.InputDefinition.Id == c.TargetSlotId);
|
||||
|
||||
if (output == null || input == null)
|
||||
{
|
||||
Log.Warning("Unable to find connected items?");
|
||||
continue;
|
||||
}
|
||||
|
||||
FindVisibleIndex(targetItem, input, out var inputLineIndex, out var multiInputIndex2);
|
||||
|
||||
int outputLineIndex;
|
||||
for (outputLineIndex = 0; outputLineIndex < sourceItem.OutputLines.Length; outputLineIndex++)
|
||||
{
|
||||
if (sourceItem.OutputLines[outputLineIndex].Output == output)
|
||||
break;
|
||||
}
|
||||
|
||||
if (outputLineIndex >= sourceItem.OutputLines.Length)
|
||||
{
|
||||
Log.Warning($"OutputIndex {outputLineIndex} exceeds number of output lines {sourceItem.OutputLines.Length} in {sourceItem}");
|
||||
outputLineIndex = sourceItem.OutputLines.Length - 1;
|
||||
}
|
||||
|
||||
var snapGraphConnection = new MagGraphConnection
|
||||
{
|
||||
Style = MagGraphConnection.ConnectionStyles.Unknown,
|
||||
SourceItem = sourceItem,
|
||||
SourceOutput = output,
|
||||
TargetItem = targetItem,
|
||||
InputLineIndex = inputLineIndex,
|
||||
OutputLineIndex = outputLineIndex,
|
||||
ConnectionHash = c.GetHashCode(),
|
||||
MultiInputIndex = multiInputIndex2,
|
||||
VisibleOutputIndex = sourceItem.OutputLines[outputLineIndex].VisibleIndex,
|
||||
};
|
||||
|
||||
var valid = inputLineIndex < targetItem.InputLines.Length
|
||||
&& outputLineIndex < sourceItem.OutputLines.Length;
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
Log.Warning($"Input line index {inputLineIndex} ouf of range?");
|
||||
continue;
|
||||
}
|
||||
|
||||
targetItem.InputLines[inputLineIndex].ConnectionIn = snapGraphConnection;
|
||||
sourceItem.OutputLines[outputLineIndex].ConnectionsOut.Add(snapGraphConnection);
|
||||
MagConnections.Add(snapGraphConnection);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method needs to be rethought and cleaned up.
|
||||
/// MultiInputIndex will always return max count?
|
||||
/// </summary>
|
||||
private static void FindVisibleIndex(MagGraphItem targetItem, IInputSlot input, out int visibleInputIndex, out int multiInputIndex)
|
||||
{
|
||||
// Find connected index
|
||||
multiInputIndex = 0;
|
||||
for (visibleInputIndex = 0; visibleInputIndex < targetItem.InputLines.Length; visibleInputIndex++)
|
||||
{
|
||||
// Count other inputs
|
||||
var targetItemInputLine = targetItem.InputLines[visibleInputIndex];
|
||||
if (targetItemInputLine.Id != input.Id)
|
||||
continue;
|
||||
|
||||
// Skip temp connections
|
||||
if (targetItemInputLine.ConnectionState == MagGraphItem.InputLineStates.TempConnection)
|
||||
continue;
|
||||
|
||||
// Skip already connected multi-inputs slots...
|
||||
// (This assumes ConnectionIn to be nullified before using this)
|
||||
if (targetItemInputLine.ConnectionIn != null && visibleInputIndex < targetItem.InputLines.Length)
|
||||
{
|
||||
multiInputIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is a match
|
||||
return;
|
||||
}
|
||||
Log.Warning("Input line not found?");
|
||||
}
|
||||
|
||||
// Reuse list to avoid allocations
|
||||
private static readonly List<MagGraphItem> _listStackedItems = new(32);
|
||||
|
||||
/// <summary>
|
||||
/// This improves the layout of arc connections inputs into multiple stacked ops so they
|
||||
/// avoid overlap.
|
||||
/// </summary>
|
||||
private void ComputeVerticalStackBoundaries(ScalableCanvas canvas)
|
||||
{
|
||||
MagGraphItem? previousItem = null;
|
||||
|
||||
_listStackedItems.Clear();
|
||||
foreach (var item in Items.Values.OrderBy(i => MathF.Round(i.PosOnCanvas.X)).ThenBy(i => i.PosOnCanvas.Y))
|
||||
{
|
||||
item.VerticalStackArea = item.Area;
|
||||
|
||||
if (previousItem == null)
|
||||
{
|
||||
_listStackedItems.Clear();
|
||||
_listStackedItems.Add(item);
|
||||
previousItem = item;
|
||||
continue;
|
||||
}
|
||||
|
||||
// is stacked?
|
||||
if (!(Math.Abs(item.PosOnCanvas.X - previousItem.PosOnCanvas.X) < 10f)
|
||||
|| !(Math.Abs(item.PosOnCanvas.Y - previousItem.Area.Max.Y) < 180f))
|
||||
{
|
||||
ApplyStackToItems();
|
||||
}
|
||||
|
||||
_listStackedItems.Add(item);
|
||||
previousItem = item;
|
||||
}
|
||||
|
||||
ApplyStackToItems();
|
||||
|
||||
return;
|
||||
|
||||
void ApplyStackToItems()
|
||||
{
|
||||
if (_listStackedItems.Count > 1)
|
||||
{
|
||||
var stackArea = new ImRect(_listStackedItems[0].PosOnCanvas,
|
||||
_listStackedItems[^1].Area.Max);
|
||||
foreach (var x in _listStackedItems)
|
||||
{
|
||||
x.VerticalStackArea = stackArea;
|
||||
}
|
||||
|
||||
// Draw Debug
|
||||
// var aOnScreen = canvas.TransformRect(stackArea);
|
||||
// ImGui.GetWindowDrawList().AddRect(aOnScreen.Min, aOnScreen.Max, Color.Green);
|
||||
}
|
||||
|
||||
_listStackedItems.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the layout of the connections (e.g. if they are snapped, of flowing vertically or horizontally)
|
||||
/// </summary>
|
||||
private void UpdateConnectionLayout()
|
||||
{
|
||||
foreach (var sc in MagConnections)
|
||||
{
|
||||
var sourceMin = sc.SourceItem.PosOnCanvas;
|
||||
var sourceMax = sourceMin + sc.SourceItem.Size;
|
||||
|
||||
var targetMin = sc.TargetItem.PosOnCanvas;
|
||||
|
||||
// Snapped horizontally
|
||||
if (
|
||||
MathF.Abs(sourceMax.X - targetMin.X) < 1
|
||||
&& MathF.Abs((sourceMin.Y + sc.VisibleOutputIndex * MagGraphItem.GridSize.Y)
|
||||
- (targetMin.Y + sc.InputLineIndex * MagGraphItem.GridSize.Y)) < 1)
|
||||
{
|
||||
sc.Style = sc.InputLineIndex == 0
|
||||
? MagGraphConnection.ConnectionStyles.MainOutToMainInSnappedHorizontal
|
||||
: MagGraphConnection.ConnectionStyles.MainOutToInputSnappedHorizontal;
|
||||
|
||||
var p = new Vector2(sourceMax.X, sourceMin.Y + (+sc.VisibleOutputIndex + 0.5f) * MagGraphItem.GridSize.Y);
|
||||
sc.SourcePos = p;
|
||||
sc.TargetPos = p;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sc.InputLineIndex == 0
|
||||
&& sc.OutputLineIndex == 0
|
||||
&& MathF.Abs(sourceMin.X - targetMin.X) < 1
|
||||
&& MathF.Abs(sourceMax.Y - targetMin.Y) < 1)
|
||||
{
|
||||
sc.Style = MagGraphConnection.ConnectionStyles.MainOutToMainInSnappedVertical;
|
||||
var p = new Vector2(sourceMin.X + MagGraphItem.GridSize.X / 2, targetMin.Y);
|
||||
sc.SourcePos = p;
|
||||
sc.TargetPos = p;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sc.OutputLineIndex == 0
|
||||
&& sc.InputLineIndex > 0
|
||||
&& MathF.Abs(sourceMax.X - targetMin.X) < 1
|
||||
&& MathF.Abs(sourceMin.Y - (targetMin.Y + sc.VisibleOutputIndex * MagGraphItem.GridSize.Y)) < 1
|
||||
)
|
||||
{
|
||||
//sc.Style = MagGraphConnection.ConnectionStyles.MainOutToInputSnappedHorizontal;
|
||||
sc.Style = MagGraphConnection.ConnectionStyles.BottomToLeft;
|
||||
var p = new Vector2(sourceMax.X, targetMin.Y + (0.5f + sc.InputLineIndex) * MagGraphItem.GridSize.Y);
|
||||
sc.SourcePos = new Vector2((sourceMin.X + sourceMax.X) / 2, sourceMax.Y);
|
||||
sc.TargetPos = p;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sc.OutputLineIndex > 0
|
||||
&& sc.InputLineIndex == 0
|
||||
&& MathF.Abs(sourceMax.X - targetMin.Y) < 1
|
||||
&& MathF.Abs(sourceMax.Y + (1 + sc.SourceItem.OutputLines.Length + sc.VisibleOutputIndex) * MagGraphItem.GridSize.Y) < 1)
|
||||
{
|
||||
sc.Style = MagGraphConnection.ConnectionStyles.AdditionalOutToMainInputSnappedVertical;
|
||||
var p = new Vector2(sourceMax.X, targetMin.Y + 0.5f * MagGraphItem.GridSize.Y);
|
||||
sc.SourcePos = p;
|
||||
sc.TargetPos = p;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sc.OutputLineIndex == 0
|
||||
&& sc.InputLineIndex == 0
|
||||
&& sourceMax.Y < targetMin.Y
|
||||
//&& MathF.Abs(sourceMin.X - targetMin.X) < MagGraphItem.GridSize.X / 2
|
||||
&& sourceMin.X > targetMin.X - MagGraphItem.GridSize.X / 2
|
||||
)
|
||||
{
|
||||
sc.SourcePos = new Vector2(sourceMin.X + MagGraphItem.GridSize.X / 2, sourceMax.Y);
|
||||
sc.TargetPos = new Vector2(targetMin.X + MagGraphItem.GridSize.X / 2, targetMin.Y);
|
||||
sc.Style = MagGraphConnection.ConnectionStyles.BottomToTop;
|
||||
continue;
|
||||
}
|
||||
|
||||
sc.SourcePos = new Vector2(sourceMax.X, sourceMin.Y + (sc.VisibleOutputIndex + 0.5f) * MagGraphItem.GridSize.Y);
|
||||
sc.TargetPos = new Vector2(targetMin.X, targetMin.Y + (sc.InputLineIndex + 0.5f) * MagGraphItem.GridSize.Y);
|
||||
|
||||
sc.Style = MagGraphConnection.ConnectionStyles.RightToLeft;
|
||||
|
||||
//TODO: Snapped from output
|
||||
//TODO: Snapped to input
|
||||
//TODO: Snapped vertically
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We rely on manually flagging structure changes, because
|
||||
/// computing a hash of a composition is not easy because the item order of children can change...
|
||||
/// </summary>
|
||||
private static bool HasCompositionDataChanged(Symbol composition, ref int originalHash)
|
||||
{
|
||||
var newHash = composition.Id.GetHashCode();
|
||||
|
||||
if (newHash == originalHash)
|
||||
return false;
|
||||
|
||||
originalHash = newHash;
|
||||
return true;
|
||||
}
|
||||
|
||||
private int _compositionModelHash;
|
||||
private bool StructureFlaggedAsChanged { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user