Compare commits
2 Commits
e79457a952
...
acdaecf8de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acdaecf8de | ||
|
|
f604f58068 |
@@ -42,6 +42,9 @@ namespace Nodify.Calculator
|
|||||||
|
|
||||||
// Dynamic ForEach node: adapt Current Item output based on list type
|
// Dynamic ForEach node: adapt Current Item output based on list type
|
||||||
if (!IsLoading) HandleForEachNodeConnected(c);
|
if (!IsLoading) HandleForEachNodeConnected(c);
|
||||||
|
|
||||||
|
// Dynamic Knot node: adapt shape/color to match connected wire
|
||||||
|
if (!IsLoading) HandleKnotNodeConnected(c);
|
||||||
})
|
})
|
||||||
.WhenRemoved(c =>
|
.WhenRemoved(c =>
|
||||||
{
|
{
|
||||||
@@ -74,6 +77,9 @@ namespace Nodify.Calculator
|
|||||||
|
|
||||||
// Dynamic ForEach node: reset Current Item output on disconnect
|
// Dynamic ForEach node: reset Current Item output on disconnect
|
||||||
HandleForEachNodeDisconnected(c);
|
HandleForEachNodeDisconnected(c);
|
||||||
|
|
||||||
|
// Dynamic Knot node: reset on disconnect
|
||||||
|
HandleKnotNodeDisconnected(c);
|
||||||
});
|
});
|
||||||
|
|
||||||
Operations.WhenAdded(x =>
|
Operations.WhenAdded(x =>
|
||||||
@@ -137,12 +143,37 @@ namespace Nodify.Calculator
|
|||||||
public PendingConnectionViewModel PendingConnection { get; set; } = new PendingConnectionViewModel();
|
public PendingConnectionViewModel PendingConnection { get; set; } = new PendingConnectionViewModel();
|
||||||
public OperationsMenuViewModel OperationsMenu { get; set; }
|
public OperationsMenuViewModel OperationsMenu { get; set; }
|
||||||
|
|
||||||
|
public void CreateKnotNodeAt(Point location)
|
||||||
|
{
|
||||||
|
var knot = new KnotOperationViewModel { Location = location };
|
||||||
|
Operations.Add(knot);
|
||||||
|
}
|
||||||
|
|
||||||
public INodifyCommand StartConnectionCommand { get; }
|
public INodifyCommand StartConnectionCommand { get; }
|
||||||
public INodifyCommand CreateConnectionCommand { get; }
|
public INodifyCommand CreateConnectionCommand { get; }
|
||||||
public INodifyCommand DisconnectConnectorCommand { get; }
|
public INodifyCommand DisconnectConnectorCommand { get; }
|
||||||
public INodifyCommand DeleteSelectionCommand { get; }
|
public INodifyCommand DeleteSelectionCommand { get; }
|
||||||
public INodifyCommand GroupSelectionCommand { get; }
|
public INodifyCommand GroupSelectionCommand { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A knot connector allows any shape only when its knot node has no existing connections (unadapted).
|
||||||
|
/// Once adapted (input connected), shape must match.
|
||||||
|
/// </summary>
|
||||||
|
private bool IsKnotConnectorAllowed(ConnectorViewModel source, ConnectorViewModel target)
|
||||||
|
{
|
||||||
|
ConnectorViewModel knotConn = source.IsKnotConnector ? source : target.IsKnotConnector ? target : null;
|
||||||
|
if (knotConn == null) return false;
|
||||||
|
|
||||||
|
var knotOp = knotConn.Operation as KnotOperationViewModel;
|
||||||
|
if (knotOp == null) return false;
|
||||||
|
|
||||||
|
// If knot has no connections yet, allow any shape
|
||||||
|
bool hasAnyConnection = Connections.Any(con =>
|
||||||
|
con.Input.Operation == knotOp || con.Output.Operation == knotOp);
|
||||||
|
|
||||||
|
return !hasAnyConnection;
|
||||||
|
}
|
||||||
|
|
||||||
private void DisconnectConnector(ConnectorViewModel connector)
|
private void DisconnectConnector(ConnectorViewModel connector)
|
||||||
{
|
{
|
||||||
var connections = Connections.Where(c => c.Input == connector || c.Output == connector).ToList();
|
var connections = Connections.Where(c => c.Input == connector || c.Output == connector).ToList();
|
||||||
@@ -152,6 +183,7 @@ namespace Nodify.Calculator
|
|||||||
internal bool CanCreateConnection(ConnectorViewModel source, ConnectorViewModel? target)
|
internal bool CanCreateConnection(ConnectorViewModel source, ConnectorViewModel? target)
|
||||||
=> target == null || (source != target &&
|
=> target == null || (source != target &&
|
||||||
(source.Shape == target.Shape ||
|
(source.Shape == target.Shape ||
|
||||||
|
IsKnotConnectorAllowed(source, target) ||
|
||||||
((source.IsCopyConnector || target.IsCopyConnector) && source.Shape != ConnectorShape.Triangle && target.Shape != ConnectorShape.Triangle)) &&
|
((source.IsCopyConnector || target.IsCopyConnector) && source.Shape != ConnectorShape.Triangle && target.Shape != ConnectorShape.Triangle)) &&
|
||||||
!source.IsConnected &&
|
!source.IsConnected &&
|
||||||
!target.IsConnected &&
|
!target.IsConnected &&
|
||||||
@@ -714,6 +746,68 @@ namespace Nodify.Calculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleKnotNodeConnected(ConnectionViewModel c)
|
||||||
|
{
|
||||||
|
// Determine if a knot node's INPUT connector is being connected
|
||||||
|
// c.Input is the receiving connector, c.Output is the source connector
|
||||||
|
KnotOperationViewModel knotOp = null;
|
||||||
|
ConnectorViewModel sourceConn = null;
|
||||||
|
|
||||||
|
// Connection into the knot's input: c.Input belongs to knot, c.Output is the source
|
||||||
|
if (c.Input.IsKnotConnector && c.Input.IsInput && c.Input.Operation is KnotOperationViewModel k1)
|
||||||
|
{
|
||||||
|
knotOp = k1;
|
||||||
|
sourceConn = c.Output;
|
||||||
|
}
|
||||||
|
// Connection from knot's output into something: c.Output belongs to knot, c.Input is the target
|
||||||
|
// If knot is unadapted and output connects first, adapt from the target
|
||||||
|
else if (c.Output.IsKnotConnector && !c.Output.IsInput && c.Output.Operation is KnotOperationViewModel k2)
|
||||||
|
{
|
||||||
|
knotOp = k2;
|
||||||
|
sourceConn = c.Input;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (knotOp == null || sourceConn == null) return;
|
||||||
|
|
||||||
|
// Adapt ALL connectors (both input and output) to match the connected wire's shape/color/type
|
||||||
|
foreach (var conn in knotOp.Input.Concat(knotOp.Output))
|
||||||
|
{
|
||||||
|
conn.Shape = sourceConn.Shape;
|
||||||
|
conn.ConnectorColor = sourceConn.RawColor;
|
||||||
|
conn.DataType = sourceConn.DataType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleKnotNodeDisconnected(ConnectionViewModel c)
|
||||||
|
{
|
||||||
|
ConnectorViewModel knotConn = null;
|
||||||
|
|
||||||
|
if (c.Input.IsKnotConnector)
|
||||||
|
knotConn = c.Input;
|
||||||
|
else if (c.Output.IsKnotConnector)
|
||||||
|
knotConn = c.Output;
|
||||||
|
|
||||||
|
if (knotConn == null) return;
|
||||||
|
|
||||||
|
var knotOp = knotConn.Operation as KnotOperationViewModel;
|
||||||
|
if (knotOp == null) return;
|
||||||
|
|
||||||
|
// Check if any connection still exists on the knot
|
||||||
|
var stillConnected = Connections.Any(con =>
|
||||||
|
(con.Input.IsKnotConnector && con.Input.Operation == knotOp) ||
|
||||||
|
(con.Output.IsKnotConnector && con.Output.Operation == knotOp));
|
||||||
|
|
||||||
|
if (stillConnected) return;
|
||||||
|
|
||||||
|
// Reset to default
|
||||||
|
foreach (var conn in knotOp.Input.Concat(knotOp.Output))
|
||||||
|
{
|
||||||
|
conn.Shape = ConnectorShape.Circle;
|
||||||
|
conn.ConnectorColor = System.Drawing.Color.DodgerBlue;
|
||||||
|
conn.DataType = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void HandleForEachNodeDisconnected(ConnectionViewModel c)
|
private void HandleForEachNodeDisconnected(ConnectionViewModel c)
|
||||||
{
|
{
|
||||||
ConnectorViewModel forEachListInput = null;
|
ConnectorViewModel forEachListInput = null;
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ namespace Nodify.Calculator
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
_color = value;
|
_color = value;
|
||||||
|
OnPropertyChanged(nameof(Color));
|
||||||
|
OnPropertyChanged(nameof(RawColor));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +123,16 @@ namespace Nodify.Calculator
|
|||||||
set => SetProperty(ref _isCopyConnector, value);
|
set => SetProperty(ref _isCopyConnector, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool _isKnotConnector;
|
||||||
|
/// <summary>
|
||||||
|
/// When true, this connector belongs to a Knot/Reroute node and accepts any shape for connections.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsKnotConnector
|
||||||
|
{
|
||||||
|
get => _isKnotConnector;
|
||||||
|
set => SetProperty(ref _isKnotConnector, value);
|
||||||
|
}
|
||||||
|
|
||||||
private bool _isTakeListConnector;
|
private bool _isTakeListConnector;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When true, this connector belongs to a TAKE node and only accepts list/array types.
|
/// When true, this connector belongs to a TAKE node and only accepts list/array types.
|
||||||
|
|||||||
@@ -646,6 +646,20 @@
|
|||||||
</nodify:Node>
|
</nodify:Node>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate DataType="{x:Type local:KnotOperationViewModel}">
|
||||||
|
<nodify:Node Input="{Binding Input}"
|
||||||
|
Output="{Binding Output}"
|
||||||
|
Background="#FF2A2A2A"
|
||||||
|
BorderBrush="#FF666666"
|
||||||
|
BorderThickness="1"
|
||||||
|
Padding="0"
|
||||||
|
ToolTip="Knot (Reroute) — Ctrl+Click to create">
|
||||||
|
<Ellipse Width="8" Height="8"
|
||||||
|
Fill="#FFAAAAAA"
|
||||||
|
Margin="2" />
|
||||||
|
</nodify:Node>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
<DataTemplate DataType="{x:Type local:OperationViewModel}">
|
<DataTemplate DataType="{x:Type local:OperationViewModel}">
|
||||||
<nodify:Node Content="{Binding Title}"
|
<nodify:Node Content="{Binding Title}"
|
||||||
Input="{Binding Input}"
|
Input="{Binding Input}"
|
||||||
|
|||||||
@@ -34,6 +34,26 @@ namespace Nodify.Calculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class KnotNodeHandler : InputElementState<NodifyEditor>
|
||||||
|
{
|
||||||
|
private static InputGesture CreateKnotGesture { get; } = new Interactivity.MouseGesture(MouseAction.LeftClick, ModifierKeys.Control);
|
||||||
|
|
||||||
|
public KnotNodeHandler(NodifyEditor element) : base(element)
|
||||||
|
{
|
||||||
|
ProcessHandledEvents = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseUp(MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (CreateKnotGesture.Matches(e.Source, e) && Element.DataContext is CalculatorViewModel calculator)
|
||||||
|
{
|
||||||
|
var location = Element.MouseLocation;
|
||||||
|
calculator.CreateKnotNodeAt(location);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public partial class EditorView : UserControl
|
public partial class EditorView : UserControl
|
||||||
{
|
{
|
||||||
public EditorView()
|
public EditorView()
|
||||||
@@ -44,6 +64,7 @@ namespace Nodify.Calculator
|
|||||||
static EditorView()
|
static EditorView()
|
||||||
{
|
{
|
||||||
InputProcessor.Shared<NodifyEditor>.RegisterHandlerFactory(editor => new OperationsMenuHandler(editor));
|
InputProcessor.Shared<NodifyEditor>.RegisterHandlerFactory(editor => new OperationsMenuHandler(editor));
|
||||||
|
InputProcessor.Shared<NodifyEditor>.RegisterHandlerFactory(editor => new KnotNodeHandler(editor));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDropNode(object sender, DragEventArgs e)
|
private void OnDropNode(object sender, DragEventArgs e)
|
||||||
|
|||||||
@@ -428,6 +428,20 @@ namespace Nodify.Calculator
|
|||||||
OnLogMe?.Invoke($"Auth node resolved. Base URL: {_authBaseUrl}, Auth Type: {_authType}");
|
OnLogMe?.Invoke($"Auth node resolved. Base URL: {_authBaseUrl}, Auth Type: {_authType}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Knot/Reroute node: passthrough - forward input data to output
|
||||||
|
if (op is KnotOperationViewModel)
|
||||||
|
{
|
||||||
|
var knotInputCon = connections.Where(c => c.Input.Operation == op && c.Input.Shape != ConnectorShape.Triangle).FirstOrDefault();
|
||||||
|
if (knotInputCon != null)
|
||||||
|
{
|
||||||
|
var srcNodeId = knotInputCon.Output?.Operation?.NodeId;
|
||||||
|
if (srcNodeId != null && outputs.TryGetValue(srcNodeId, out var srcVal))
|
||||||
|
{
|
||||||
|
outputs[op.NodeId] = srcVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (op is SystemOperationViewModel debugSysOp && debugSysOp.SystemOperationType == SystemOperations.DEBUG)
|
if (op is SystemOperationViewModel debugSysOp && debugSysOp.SystemOperationType == SystemOperations.DEBUG)
|
||||||
{
|
{
|
||||||
// Find the data input connection (non-triangle)
|
// Find the data input connection (non-triangle)
|
||||||
|
|||||||
15
Examples/Nodify.Calculator/KnotOperationViewModel.cs
Normal file
15
Examples/Nodify.Calculator/KnotOperationViewModel.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public class KnotOperationViewModel : SystemOperationViewModel
|
||||||
|
{
|
||||||
|
public KnotOperationViewModel()
|
||||||
|
{
|
||||||
|
Title = "⬥";
|
||||||
|
SystemOperationType = SystemOperations.KNOT;
|
||||||
|
|
||||||
|
// Single flow-through input and output (will adapt shape when connected)
|
||||||
|
Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Circle, IsKnotConnector = true });
|
||||||
|
Output.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Circle, IsKnotConnector = true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,8 @@ namespace Nodify.Calculator
|
|||||||
DEBUG,
|
DEBUG,
|
||||||
NEW_OBJECT,
|
NEW_OBJECT,
|
||||||
FOREACH,
|
FOREACH,
|
||||||
ASSERT
|
ASSERT,
|
||||||
|
KNOT
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SystemOperationViewModel : OperationViewModel
|
public class SystemOperationViewModel : OperationViewModel
|
||||||
|
|||||||
1
push command.txt
Normal file
1
push command.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
git push -u origin master
|
||||||
Reference in New Issue
Block a user