Compare commits

..

2 Commits

Author SHA1 Message Date
Ankitkumar Satapara
acdaecf8de Added knot nodes to for channing the lines to the proper format
Some checks failed
Build / build (push) Has been cancelled
2026-04-20 20:31:35 +05:30
Ankitkumar Satapara
f604f58068 implemente the knot node 2026-04-20 20:16:53 +05:30
8 changed files with 173 additions and 1 deletions

View File

@@ -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;

View File

@@ -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.

View File

@@ -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}"

View File

@@ -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)

View File

@@ -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)

View 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 });
}
}
}

View File

@@ -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
View File

@@ -0,0 +1 @@
git push -u origin master