diff --git a/ASTPlayground/DraggablePanel.pas b/ASTPlayground/DraggablePanel.pas
index 91c1047..99013fd 100644
--- a/ASTPlayground/DraggablePanel.pas
+++ b/ASTPlayground/DraggablePanel.pas
@@ -14,10 +14,7 @@ uses
FMX.Objects;
type
- ///
- /// A panel that can contain other controls and can be moved by dragging its background.
- /// It custom-paints its own border and title.
- ///
+ // A movable panel with a custom-painted border and title.
TAuraNode = class(TStyledControl)
private
// Flag to indicate if the control is currently being dragged.
@@ -190,45 +187,45 @@ end;
procedure TAuraNode.Paint;
var
- LRect, LTitleRect: TRectF;
+ rect, titleRect: TRectF;
begin
inherited; // Allow styled painting to occur first (if any)
// Custom painting for the border
if (FBorderWidth > 0) and (FBorderColor <> TAlphaColors.Null) then
begin
- LRect := TRectF.Create(0, 0, Width, Height);
+ rect := TRectF.Create(0, 0, Width, Height);
// Inflate inwards so the border is fully visible within the control's bounds
- LRect.Inflate(-FBorderWidth / 2, -FBorderWidth / 2);
+ rect.Inflate(-FBorderWidth / 2, -FBorderWidth / 2);
Canvas.Stroke.Kind := TBrushKind.Solid;
Canvas.Stroke.Color := FBorderColor;
Canvas.Stroke.Thickness := FBorderWidth;
// Use DrawRoundRect to support both sharp and rounded corners
- Canvas.DrawRect(LRect, FBorderRadius, FBorderRadius, AllCorners, 1.0, TCornerType.Round);
+ Canvas.DrawRect(rect, FBorderRadius, FBorderRadius, AllCorners, 1.0, TCornerType.Round);
end;
// Draw the title
if not FTitle.IsEmpty then
begin
// Define the rectangle for the title at the top of the control
- LTitleRect := TRectF.Create(0, FBorderWidth, Width, FBorderWidth + FTitleFont.Size * 1.8);
+ titleRect := TRectF.Create(0, FBorderWidth, Width, FBorderWidth + FTitleFont.Size * 1.8);
Canvas.Font.Assign(FTitleFont);
Canvas.Fill.Color := FTitleFontColor;
// Draw the text centered horizontally and vertically within the title rectangle
- Canvas.FillText(LTitleRect, FTitle, False, 1.0, [], TTextAlign.Center, TTextAlign.Center);
+ Canvas.FillText(titleRect, FTitle, False, 1.0, [], TTextAlign.Center, TTextAlign.Center);
end;
end;
procedure TAuraNode.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
inherited;
- if (Button = TMouseButton.mbLeft) then
+ if Button = TMouseButton.mbLeft then
begin
- if (ObjectAtPoint(LocalToScreen(TPointF.Create(X, Y))) = Self as IControl) then
+ if ObjectAtPoint(LocalToScreen(TPointF.Create(X, Y))) = Self as IControl then
begin
FIsDragging := True;
FDownPos := TPointF.Create(X, Y);
diff --git a/ASTPlayground/Myc.Ast.Visualizer.pas b/ASTPlayground/Myc.Ast.Visualizer.pas
index 98498ba..843ac04 100644
--- a/ASTPlayground/Myc.Ast.Visualizer.pas
+++ b/ASTPlayground/Myc.Ast.Visualizer.pas
@@ -29,6 +29,13 @@ type
TAuraWorkspace = class;
TAstToAuraNodeVisitor = class(TInterfacedObject, IAstVisitor)
+ public
+ type
+ TChildVisitorProc = reference to procedure(const Visitor: IAstVisitor);
+ TAuraNodeList = TList;
+ TCreateParentNodeProc = reference to function(const InputNodes: TAuraNodeList): TAuraNode;
+ TVerticalAlignment = (vaCenter, vaTop);
+ private
const
// Pin Visuals
cPinSize = 10;
@@ -51,22 +58,31 @@ type
FLastNode: TAuraNode;
FConnections: TList;
FCurrentExec: TControl;
- function CreateNodeControl(const ATitle, ADetails: string): TAuraNode;
+
+ function VisitContainerNode(const Title, Details: string; const ChildVisitorProc: TChildVisitorProc): TAuraNode;
+ function VisitOperatorNode(
+ const InputExpressions: TArray;
+ const CreateParentProc: TCreateParentNodeProc;
+ Alignment: TVerticalAlignment = vaCenter
+ ): TAuraNode;
+
+ function BuildNodeControl(const Title, Details: string): TAuraNode;
+ function CreateNodeControl(const Title, Details: string): TAuraNode;
function CreatePin(
- AParentNode: TAuraNode;
- AShape: TPinShape;
- AAlignment: TPinAlignment;
- AColor: TAlphaColor;
- const ATag: string;
- AOffsetY: Single = 0
+ ParentNode: TAuraNode;
+ Shape: TPinShape;
+ Alignment: TPinAlignment;
+ Color: TAlphaColor;
+ const Tag: string;
+ OffsetY: Single = 0
): TShape;
- function CreateInput(AParentNode: TAuraNode; const ACaption: string; AOffsetY: Single = 0): TControl;
- function CreateOutput(AParentNode: TAuraNode; AOffsetY: Single = 0): TControl;
- function CreateEntry(AParentNode: TAuraNode; AOffsetY: Single = 0): TControl;
- function CreateExit(AParentNode: TAuraNode; const ACaption: string; AOffsetY: Single = 0): TControl;
+ function CreateInput(ParentNode: TAuraNode; const Caption: string; OffsetY: Single = 0): TControl;
+ function CreateOutput(ParentNode: TAuraNode; OffsetY: Single = 0): TControl;
+ function CreateEntry(ParentNode: TAuraNode; OffsetY: Single = 0): TControl;
+ function CreateExit(ParentNode: TAuraNode; const Caption: string; OffsetY: Single = 0): TControl;
- function FindPinByTag(AParentNode: TAuraNode; const ATag: string): TControl;
+ function FindPinByTag(ParentNode: TAuraNode; const Tag: string): TControl;
function ConnectData(InputPin: TControl; OutputNode: TAuraNode): TControl;
public
constructor Create(
@@ -167,6 +183,34 @@ begin
FConnections := AConnections;
end;
+function TAstToAuraNodeVisitor.BuildNodeControl(const Title, Details: string): TAuraNode;
+var
+ fullTitle: string;
+ textWidth: Single;
+ newWidth: Single;
+ measureCanvas: TCanvas;
+begin
+ Result := TAuraNode.Create(FParentControl);
+ Result.Parent := FParentControl;
+
+ // Build the full title string first
+ fullTitle := Title;
+ if not Details.IsEmpty then
+ fullTitle := fullTitle + ': ' + Details;
+ Result.Title := fullTitle;
+
+ // Adjust the width so that the text is completely visible.
+ measureCanvas := TCanvasManager.MeasureCanvas;
+ if Assigned(measureCanvas) then
+ begin
+ measureCanvas.Font.Assign(Result.TitleFont);
+ textWidth := measureCanvas.TextWidth(Result.Title);
+ newWidth := textWidth + (2 * cHorizontalPadding);
+ // Ensure the new width is not smaller than the default width of the node.
+ Result.Width := Max(Result.Width, newWidth);
+ end;
+end;
+
function TAstToAuraNodeVisitor.ConnectData(InputPin: TControl; OutputNode: TAuraNode): TControl;
begin
Result := FindPinByTag(OutputNode, 'data.out');
@@ -174,367 +218,291 @@ begin
FConnections.Add(TPinConnection.Create(Result, InputPin));
end;
-function TAstToAuraNodeVisitor.CreateInput(AParentNode: TAuraNode; const ACaption: string; AOffsetY: Single = 0): TControl;
+function TAstToAuraNodeVisitor.CreateInput(ParentNode: TAuraNode; const Caption: string; OffsetY: Single = 0): TControl;
begin
- var cap := ACaption;
+ var cap := Caption;
if cap <> '' then
cap := '.' + cap;
- Result := CreatePin(AParentNode, psCircle, paLeft, cDataPinColor, 'data.in' + cap, AOffsetY);
- Result.Hint := ACaption;
- Result.ShowHint := ACaption <> '';
+ Result := CreatePin(ParentNode, psCircle, paLeft, cDataPinColor, 'data.in' + cap, OffsetY);
+ Result.Hint := Caption;
+ Result.ShowHint := Caption <> '';
end;
-function TAstToAuraNodeVisitor.CreateOutput(AParentNode: TAuraNode; AOffsetY: Single = 0): TControl;
+function TAstToAuraNodeVisitor.CreateNodeControl(const Title, Details: string): TAuraNode;
begin
- Result := CreatePin(AParentNode, psCircle, paRight, cDataPinColor, 'data.out', AOffsetY);
-end;
-
-function TAstToAuraNodeVisitor.CreateEntry(AParentNode: TAuraNode; AOffsetY: Single = 0): TControl;
-begin
- Result := CreatePin(AParentNode, psTriangle, paLeft, cExecPinColor, 'exec.in', AOffsetY);
- if FCurrentExec <> nil then
- FConnections.Add(TPinConnection.Create(FCurrentExec, Result));
-end;
-
-function TAstToAuraNodeVisitor.CreateExit(AParentNode: TAuraNode; const ACaption: string; AOffsetY: Single = 0): TControl;
-begin
- var cap := ACaption;
- if cap <> '' then
- cap := '.' + cap;
- Result := CreatePin(AParentNode, psTriangle, paRight, cExecPinColor, 'exec.out' + cap, AOffsetY);
-
- FCurrentExec := Result;
-end;
-
-function TAstToAuraNodeVisitor.CreateNodeControl(const ATitle, ADetails: string): TAuraNode;
-var
- LFullTitle: string;
- LTextWidth: Single;
- LNewWidth: Single;
- LMeasureCanvas: TCanvas;
-begin
- Result := TAuraNode.Create(FParentControl);
- Result.Parent := FParentControl;
-
- // Build the full title string first
- LFullTitle := ATitle;
- if not ADetails.IsEmpty then
- LFullTitle := LFullTitle + ': ' + ADetails;
- Result.Title := LFullTitle;
-
+ Result := BuildNodeControl(Title, Details);
Result.Position.Point := FCurrentPos;
- // Adjust the width so that the text and pins are completely visible.
- LMeasureCanvas := TCanvasManager.MeasureCanvas;
- if Assigned(LMeasureCanvas) then
- begin
- LMeasureCanvas.Font.Assign(Result.TitleFont);
- LTextWidth := LMeasureCanvas.TextWidth(Result.Title);
- LNewWidth := LTextWidth + (2 * cHorizontalPadding);
- // Ensure the new width is not smaller than the default width of the node.
- Result.Width := Max(Result.Width, LNewWidth);
- end;
-
// Advance vertical position for the next node
FCurrentPos.Y := FCurrentPos.Y + Result.Height + FSpacing.Y;
FLastNode := Result;
end;
+function TAstToAuraNodeVisitor.CreateOutput(ParentNode: TAuraNode; OffsetY: Single = 0): TControl;
+begin
+ Result := CreatePin(ParentNode, psCircle, paRight, cDataPinColor, 'data.out', OffsetY);
+end;
+
+function TAstToAuraNodeVisitor.CreateEntry(ParentNode: TAuraNode; OffsetY: Single = 0): TControl;
+begin
+ Result := CreatePin(ParentNode, psTriangle, paLeft, cExecPinColor, 'exec.in', OffsetY);
+ if FCurrentExec <> nil then
+ FConnections.Add(TPinConnection.Create(FCurrentExec, Result));
+end;
+
+function TAstToAuraNodeVisitor.CreateExit(ParentNode: TAuraNode; const Caption: string; OffsetY: Single = 0): TControl;
+begin
+ var cap := Caption;
+ if cap <> '' then
+ cap := '.' + cap;
+ Result := CreatePin(ParentNode, psTriangle, paRight, cExecPinColor, 'exec.out' + cap, OffsetY);
+
+ FCurrentExec := Result;
+end;
+
function TAstToAuraNodeVisitor.CreatePin(
- AParentNode: TAuraNode;
- AShape: TPinShape;
- AAlignment: TPinAlignment;
- AColor: TAlphaColor;
- const ATag: string;
- AOffsetY: Single = 0
+ ParentNode: TAuraNode;
+ Shape: TPinShape;
+ Alignment: TPinAlignment;
+ Color: TAlphaColor;
+ const Tag: string;
+ OffsetY: Single = 0
): TShape;
var
- LPosX, LPosY: Single;
+ posX, posY: Single;
begin
// Calculate vertical position (centered + offset)
- LPosY := (AParentNode.Height / 2) - (cPinSize / 2) + AOffsetY;
+ posY := (ParentNode.Height / 2) - (cPinSize / 2) + OffsetY;
// Calculate horizontal position
- if (AAlignment = paLeft) then
- LPosX := 0
+ if Alignment = paLeft then
+ posX := 0
else // paRight
- LPosX := AParentNode.Width - cPinSize;
+ posX := ParentNode.Width - cPinSize;
// Create the shape
- case AShape of
+ case Shape of
psCircle:
begin
- var LCircle := TEllipse.Create(AParentNode);
- LCircle.Fill.Color := AColor;
- LCircle.Stroke.Kind := TBrushKind.None;
- Result := LCircle;
+ var circle := TEllipse.Create(ParentNode);
+ circle.Fill.Color := Color;
+ circle.Stroke.Kind := TBrushKind.None;
+ Result := circle;
end;
psTriangle:
begin
- var LTriangle := TPath.Create(AParentNode);
- LTriangle.Data.Data := Format('M 0 0 L %d %d L 0 %d Z', [cPinSize, cPinSize div 2, cPinSize]);
- LTriangle.Fill.Color := AColor;
- LTriangle.Stroke.Kind := TBrushKind.None;
- Result := LTriangle;
+ var triangle := TPath.Create(ParentNode);
+ triangle.Data.Data := Format('M 0 0 L %d %d L 0 %d Z', [cPinSize, cPinSize div 2, cPinSize]);
+ triangle.Fill.Color := Color;
+ triangle.Stroke.Kind := TBrushKind.None;
+ Result := triangle;
end;
else
Result := nil;
- Exit;
+ exit;
end;
// Common properties
- Result.Parent := AParentNode;
- Result.SetBounds(LPosX, LPosY, cPinSize, cPinSize);
+ Result.Parent := ParentNode;
+ Result.SetBounds(posX, posY, cPinSize, cPinSize);
Result.HitTest := true;
// Use TagString to identify the control as a pin and describe its function.
- Result.TagString := ATag;
+ Result.TagString := Tag;
end;
-function TAstToAuraNodeVisitor.FindPinByTag(AParentNode: TAuraNode; const ATag: string): TControl;
+function TAstToAuraNodeVisitor.FindPinByTag(ParentNode: TAuraNode; const Tag: string): TControl;
var
- LControl: TControl;
+ control: TControl;
begin
Result := nil;
- if not Assigned(AParentNode) then
- Exit;
+ if not Assigned(ParentNode) then
+ exit;
- for LControl in AParentNode.Controls do
+ for control in ParentNode.Controls do
begin
- if (LControl.TagString = ATag) then
+ if control.TagString = Tag then
begin
- Result := LControl;
- Exit;
+ Result := control;
+ exit;
end;
end;
end;
function TAstToAuraNodeVisitor.VisitAssignment(const Node: IAssignmentNode): TAstValue;
-var
- LAssignmentNode, LValueNode: TAuraNode;
- LInputPin: TControl;
- LStartPosition: TPointF;
begin
- LStartPosition := FCurrentPos;
+ VisitOperatorNode(
+ [Node.Value],
+ function(const InputNodes: TAuraNodeList): TAuraNode
+ var
+ assignmentNode, valueNode: TAuraNode;
+ inputPin: TControl;
+ begin
+ valueNode := InputNodes[0];
+ assignmentNode := BuildNodeControl('Assignment', Node.Identifier.Name);
- // Visit the value expression first
- Node.Value.Accept(Self);
- LValueNode := FLastNode;
-
- // Reposition for the assignment node
- FCurrentPos.X := LValueNode.Position.X + LValueNode.Width + FSpacing.X;
- FCurrentPos.Y := LValueNode.Position.Y;
-
- // Create the assignment node and its pins
- LAssignmentNode := CreateNodeControl('Assignment', Node.Identifier.Name);
- CreateEntry(LAssignmentNode, 0);
- CreateExit(LAssignmentNode, '', 0);
- LInputPin := CreateInput(LAssignmentNode, Node.Identifier.Name, 0);
-
- // Connect the value to the assignment
- ConnectData(LInputPin, LValueNode);
-
- // Finalize visitor state
- FCurrentPos.X := LStartPosition.X;
- FCurrentPos.Y := Max(LValueNode.Position.Y + LValueNode.Height, LAssignmentNode.Position.Y + LAssignmentNode.Height) + FSpacing.Y;
- FLastNode := LAssignmentNode;
+ CreateEntry(assignmentNode, 0);
+ CreateExit(assignmentNode, '', 0);
+ inputPin := CreateInput(assignmentNode, Node.Identifier.Name, 0);
+ ConnectData(inputPin, valueNode);
+ Result := assignmentNode;
+ end,
+ vaTop
+ );
Result := TAstValue.Void;
end;
function TAstToAuraNodeVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TAstValue;
-var
- LBinaryExprNode, LLeftNode, LRightNode: TAuraNode;
- LInputPin1, LInputPin2: TControl;
- LStartPosition: TPointF;
- LRightEndY, LMaxChildWidth: Single;
begin
- LStartPosition := FCurrentPos;
+ VisitOperatorNode(
+ [Node.Left, Node.Right],
+ function(const InputNodes: TAuraNodeList): TAuraNode
+ var
+ binaryExprNode, leftNode, rightNode: TAuraNode;
+ inputPin1, inputPin2: TControl;
+ begin
+ leftNode := InputNodes[0];
+ rightNode := InputNodes[1];
- // First, visit the child nodes to create their visual representation
- // Left branch
- Node.Left.Accept(Self);
- LLeftNode := FLastNode;
+ binaryExprNode := BuildNodeControl(Node.Operator.ToString, '');
+ binaryExprNode.TitleFont.Size := 20;
- // Right branch starts below the left branch
- Node.Right.Accept(Self);
- LRightNode := FLastNode;
- LRightEndY := FCurrentPos.Y;
+ inputPin1 := CreateInput(binaryExprNode, '', -8); // Caption is in Hint
+ inputPin2 := CreateInput(binaryExprNode, '', 8);
+ CreateOutput(binaryExprNode, 0);
- // Now, create the node for the binary operation itself
- LMaxChildWidth := Max(LLeftNode.Position.X + LLeftNode.Width, LRightNode.Position.X + LRightNode.Width);
- FCurrentPos.X := LMaxChildWidth + FSpacing.X;
-
- // Vertically center the binary expression node between its children
- var LTotalChildHeight := LRightEndY - LStartPosition.Y;
- var LNodeCenterY := LStartPosition.Y + (LTotalChildHeight / 2);
- FCurrentPos.Y := LNodeCenterY - (cDefaultNodeHeight / 2);
-
- LBinaryExprNode := CreateNodeControl(Node.Operator.ToString, '');
- LBinaryExprNode.TitleFont.Size := 20;
-
- // Create pins for the binary expression node
- LInputPin1 := CreateInput(LBinaryExprNode, LLeftNode.Name, -8);
- LInputPin2 := CreateInput(LBinaryExprNode, LRightNode.Name, 8);
- CreateOutput(LBinaryExprNode, 0);
-
- // Connect the output of the left child to the first input of the binary node
- ConnectData(LInputPin1, LLeftNode);
-
- // Connect the output of the right child to the second input of the binary node
- ConnectData(LInputPin2, LRightNode);
-
- // Finalize visitor state for the next node at this level
- FCurrentPos.X := LStartPosition.X;
- FCurrentPos.Y := Max(LRightEndY, LBinaryExprNode.Position.Y + LBinaryExprNode.Height + FSpacing.Y);
- FLastNode := LBinaryExprNode;
+ ConnectData(inputPin1, leftNode);
+ ConnectData(inputPin2, rightNode);
+ Result := binaryExprNode;
+ end
+ );
Result := TAstValue.Void;
end;
function TAstToAuraNodeVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue;
var
- Expression: IExpressionNode;
- LBlockNode: TAuraNode;
- LChildVisitor: IAstVisitor;
- LChildStartPos: TPointF;
- LMaxRight: Single;
- LMaxBottom: Single;
- LControl: TControl;
+ blockNode: TAuraNode;
begin
- // A block node visually encloses all its child nodes.
- // Create the container node for the block. Its size will be adjusted later.
- LBlockNode := TAuraNode.Create(FParentControl);
- LBlockNode.Parent := FParentControl;
- LBlockNode.Title := 'Block';
- LBlockNode.Position.Point := FCurrentPos;
+ // Use the helper to create and populate the container node.
+ blockNode :=
+ VisitContainerNode(
+ 'Block',
+ '',
+ procedure(const Visitor: IAstVisitor)
+ var
+ expression: IExpressionNode;
+ begin
+ for expression in Node.Expressions do
+ expression.Accept(Visitor);
+ end
+ );
- // Create a new visitor for the child nodes. The children will be parented
- // to LBlockNode and positioned relative to it, starting below the title.
- LChildStartPos := TPointF.Create(FSpacing.X, FSpacing.Y + LBlockNode.TitleFont.Size * 1.8);
- LChildVisitor := TAstToAuraNodeVisitor.Create(FWorkspace, LBlockNode, LChildStartPos, FConnections);
-
- // Visit all expressions within the block using the new visitor.
- for Expression in Node.Expressions do
- begin
- Expression.Accept(LChildVisitor);
- end;
-
- // Adjust the size of the block node to encompass all its children.
- LMaxRight := 0;
- LMaxBottom := 0;
- for LControl in LBlockNode.Controls do
- begin
- if (LControl is TAuraNode) then
- begin
- LMaxRight := Max(LMaxRight, LControl.Position.X + LControl.Width);
- LMaxBottom := Max(LMaxBottom, LControl.Position.Y + LControl.Height);
- end;
- end;
-
- // Set the final size with some internal padding.
- LBlockNode.Width := Max(LBlockNode.Width, LMaxRight + FSpacing.X);
- LBlockNode.Height := Max(LBlockNode.Height, LMaxBottom + FSpacing.Y);
-
- // Update the current Y position of the main visitor to be below the now correctly sized block node.
- FCurrentPos.Y := LBlockNode.Position.Y + LBlockNode.Height + FSpacing.Y;
- FLastNode := LBlockNode;
+ // Update the state of the main visitor
+ FCurrentPos.Y := blockNode.Position.Y + blockNode.Height + FSpacing.Y;
+ FLastNode := blockNode;
Result := TAstValue.Void;
end;
function TAstToAuraNodeVisitor.VisitConstant(const Node: IConstantNode): TAstValue;
var
- LConstantNode: TAuraNode;
+ constantNode: TAuraNode;
begin
// A constant has a data output pin.
// Data outputs are round and on the right side of the box.
- LConstantNode := CreateNodeControl('Constant', Node.Value.ToString);
- CreateOutput(LConstantNode, 0);
+ constantNode := CreateNodeControl('Constant', Node.Value.ToString);
+ CreateOutput(constantNode, 0);
Result := TAstValue.Void;
end;
+function TAstToAuraNodeVisitor.VisitContainerNode(const Title, Details: string; const ChildVisitorProc: TChildVisitorProc): TAuraNode;
+var
+ containerNode: TAuraNode;
+ childVisitor: IAstVisitor;
+ childStartPos: TPointF;
+ maxRight: Single;
+ maxBottom: Single;
+ control: TControl;
+begin
+ // Step 1: Create container node
+ containerNode := BuildNodeControl(Title, Details);
+ containerNode.Position.Point := FCurrentPos;
+ Result := containerNode; // Set result early
+
+ // Step 2: Create child visitor and run the closure.
+ childStartPos := TPointF.Create(FSpacing.X, FSpacing.Y + containerNode.TitleFont.Size * 1.8);
+ childVisitor := TAstToAuraNodeVisitor.Create(FWorkspace, containerNode, childStartPos, FConnections);
+ ChildVisitorProc(childVisitor);
+
+ // Step 3: Adjust the size of the container node to fit children.
+ maxRight := 0;
+ maxBottom := 0;
+ for control in containerNode.Controls do
+ begin
+ if control is TAuraNode then
+ begin
+ maxRight := Max(maxRight, control.Position.X + control.Width);
+ maxBottom := Max(maxBottom, control.Position.Y + control.Height);
+ end;
+ end;
+ // The width must be at least the title width, and large enough for children.
+ containerNode.Width := Max(containerNode.Width, maxRight + FSpacing.X);
+ containerNode.Height := Max(containerNode.Height, maxBottom + FSpacing.Y);
+end;
+
function TAstToAuraNodeVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TAstValue;
var
- LFuncCallNode: TAuraNode;
- LInputResultNodes: TList;
- LInputNode: IExpressionNode;
- LStartPosition: TPointF;
- LEndY, LMaxChildWidth: Single;
- i: Integer;
- LCalleePin: TControl;
- LArgPin: array of TControl;
- LCurrentInputOffsetY, LCurrentOutputOffsetY: Single;
+ inputExpressions: TArray;
begin
- LStartPosition := FCurrentPos;
- LInputResultNodes := TList.Create;
- try
- // 1. Visit all input nodes first (Callee + Arguments) and collect their visual representations.
- Node.Callee.Accept(Self);
- LInputResultNodes.Add(FLastNode);
+ SetLength(inputExpressions, 1 + Node.Arguments.Count);
+ inputExpressions[0] := Node.Callee;
+ for var i := 0 to Node.Arguments.Count - 1 do
+ inputExpressions[i + 1] := Node.Arguments[i];
- for LInputNode in Node.Arguments do
+ VisitOperatorNode(
+ inputExpressions,
+ function(const InputNodes: TAuraNodeList): TAuraNode
+ var
+ funcCallNode: TAuraNode;
+ calleePin: TControl;
+ argPin: array of TControl;
+ currentInputOffsetY, currentOutputOffsetY: Single;
+ i: Integer;
begin
- LInputNode.Accept(Self);
- LInputResultNodes.Add(FLastNode);
- end;
- LEndY := FCurrentPos.Y;
+ funcCallNode := BuildNodeControl('FunctionCall', '');
- // 2. Determine the layout bounds of the input nodes.
- LMaxChildWidth := 0;
- for var LNode in LInputResultNodes do
- LMaxChildWidth := Max(LMaxChildWidth, LNode.Position.X + LNode.Width);
+ var numInputPins := 2 + Node.Arguments.Count;
+ var numOutputPins := 2;
+ var maxPins := Max(numInputPins, numOutputPins);
+ funcCallNode.Height := cVerticalPadding * 2 + (maxPins - 1) * cPinOffsetY;
- // 3. Position and create the 'FunctionCall' node to the right of the inputs.
- FCurrentPos.X := LMaxChildWidth + FSpacing.X;
+ currentInputOffsetY := -(numInputPins - 1) / 2.0 * cPinOffsetY;
+ CreateEntry(funcCallNode, currentInputOffsetY);
+ currentInputOffsetY := currentInputOffsetY + cPinOffsetY;
+ calleePin := CreateInput(funcCallNode, 'Callee', currentInputOffsetY);
+ currentInputOffsetY := currentInputOffsetY + cPinOffsetY;
+ SetLength(argPin, Node.Arguments.Count);
+ for i := 0 to Node.Arguments.Count - 1 do
+ begin
+ argPin[i] := CreateInput(funcCallNode, 'Arg' + i.ToString, currentInputOffsetY);
+ currentInputOffsetY := currentInputOffsetY + cPinOffsetY;
+ end;
- // Calculate dynamic height based on the number of pins
- var LNumInputPins := 2 + Node.Arguments.Count;
- var LNumOutputPins := 2;
- var LMaxPins := Max(LNumInputPins, LNumOutputPins);
- var LNodeHeight := cVerticalPadding + (LMaxPins - 1) * cPinOffsetY;
+ currentOutputOffsetY := -(numOutputPins - 1) / 2.0 * cPinOffsetY;
+ CreateExit(funcCallNode, '', currentOutputOffsetY);
+ currentOutputOffsetY := currentOutputOffsetY + cPinOffsetY;
+ CreateOutput(funcCallNode, currentOutputOffsetY);
- var LTotalInputHeight := LEndY - LStartPosition.Y;
- var LNodeCenterY := LStartPosition.Y + (LTotalInputHeight / 2);
- FCurrentPos.Y := LNodeCenterY - (LNodeHeight / 2);
+ ConnectData(calleePin, InputNodes[0]);
+ for i := 0 to Node.Arguments.Count - 1 do
+ ConnectData(argPin[i], InputNodes[i + 1]);
- LFuncCallNode := CreateNodeControl('FunctionCall', '');
- LFuncCallNode.Height := LNodeHeight;
-
- // 4. Create all necessary pins on the 'FunctionCall' node with proper spacing.
- // Left side (Inputs)
- LCurrentInputOffsetY := -(LNumInputPins - 1) / 2.0 * cPinOffsetY;
- CreateEntry(LFuncCallNode, LCurrentInputOffsetY);
- LCurrentInputOffsetY := LCurrentInputOffsetY + cPinOffsetY;
- LCalleePin := CreateInput(LFuncCallNode, LInputResultNodes[0].Name, LCurrentInputOffsetY);
- LCurrentInputOffsetY := LCurrentInputOffsetY + cPinOffsetY;
- SetLength(LArgPin, Node.Arguments.Count);
- for i := 0 to Node.Arguments.Count - 1 do
- begin
- LArgPin[i] := CreateInput(LFuncCallNode, LInputResultNodes[i + 1].Name + i.ToString, LCurrentInputOffsetY);
- LCurrentInputOffsetY := LCurrentInputOffsetY + cPinOffsetY;
- end;
-
- // Right side (Outputs)
- LCurrentOutputOffsetY := -(LNumOutputPins - 1) / 2.0 * cPinOffsetY;
- CreateExit(LFuncCallNode, '', LCurrentOutputOffsetY);
- LCurrentOutputOffsetY := LCurrentOutputOffsetY + cPinOffsetY;
- CreateOutput(LFuncCallNode, LCurrentOutputOffsetY);
-
- // 5. Connect the inputs.
- ConnectData(LCalleePin, LInputResultNodes[0]);
- for i := 0 to Node.Arguments.Count - 1 do
- ConnectData(LArgPin[i], LInputResultNodes[i + 1]);
-
- finally
- LInputResultNodes.Free;
- end;
-
- // 6. Finalize visitor state.
- FCurrentPos.X := LStartPosition.X;
- FCurrentPos.Y := Max(LEndY, LFuncCallNode.Position.Y + LFuncCallNode.Height + FSpacing.Y);
- FLastNode := LFuncCallNode;
+ Result := funcCallNode;
+ end
+ );
Result := TAstValue.Void;
end;
@@ -549,123 +517,148 @@ end;
function TAstToAuraNodeVisitor.VisitIfExpression(const Node: IIfExpressionNode): TAstValue;
var
- LIfNode, LConditionNode, LThenNode, LElseNode: TAuraNode;
- LConditionInputPin, LThenOutputPin, LElseOutputPin: TControl;
- LStartPosition: TPointF;
- LConditionEndY, LThenEndY, LElseEndY, LMaxChildWidth: Single;
+ ifNode: TAuraNode;
+ conditionInputPin, thenOutputPin, elseOutputPin: TControl;
+ startPosition: TPointF;
+ thenEndY, elseEndY: Single;
begin
- LStartPosition := FCurrentPos;
+ startPosition := FCurrentPos;
- // Place the input node for the condition to the left of the 'If' node.
- Node.Condition.Accept(Self);
- LConditionNode := FLastNode;
- LConditionEndY := FCurrentPos.Y;
+ // Use helper for the condition part
+ VisitOperatorNode(
+ [Node.Condition],
+ function(const InputNodes: TAuraNodeList): TAuraNode
+ var
+ condNode: TAuraNode;
+ begin
+ condNode := InputNodes[0];
- // Position and create the 'If' node to the right of the condition's subtree.
- LMaxChildWidth := LConditionNode.Position.X + LConditionNode.Width;
- FCurrentPos.X := LMaxChildWidth + FSpacing.X;
- FCurrentPos.Y := LStartPosition.Y + (LConditionNode.Position.Y + LConditionNode.Height / 2) - LStartPosition.Y - (cIfNodeHeight / 2);
+ ifNode := BuildNodeControl('If', '');
+ ifNode.Height := cIfNodeHeight;
+ CreateEntry(ifNode, -12);
+ conditionInputPin := CreateInput(ifNode, 'Condition', 12);
+ thenOutputPin := CreateExit(ifNode, 'Then', -12);
+ if Assigned(Node.ElseBranch) then
+ elseOutputPin := CreateExit(ifNode, 'Else', 12)
+ else
+ elseOutputPin := nil;
- LIfNode := CreateNodeControl('If', '');
- LIfNode.Height := cIfNodeHeight;
- CreateEntry(LIfNode, -12);
- LConditionInputPin := CreateInput(LIfNode, 'Condition', 12);
- LThenOutputPin := CreateExit(LIfNode, 'Then', -12);
- if Assigned(Node.ElseBranch) then
- LElseOutputPin := CreateExit(LIfNode, 'Else', 12)
- else
- LElseOutputPin := nil;
-
- // Connect the condition's output to the 'If' node's data input.
- ConnectData(LConditionInputPin, LConditionNode);
+ ConnectData(conditionInputPin, condNode);
+ Result := ifNode;
+ end
+ );
+ var conditionEndY := FCurrentPos.Y;
// Visit the 'Then' and 'Else' execution branches, placing them to the right of the 'If' node.
- var LBranchStartX := LIfNode.Position.X + LIfNode.Width + FSpacing.X;
+ var branchStartX := ifNode.Position.X + ifNode.Width + FSpacing.X;
// Then branch
- FCurrentPos.X := LBranchStartX;
- FCurrentPos.Y := LStartPosition.Y;
+ FCurrentPos.X := branchStartX;
+ FCurrentPos.Y := startPosition.Y;
Node.ThenBranch.Accept(Self);
- LThenNode := FLastNode;
- LThenEndY := FCurrentPos.Y;
- // ConnectData(FindPinByTag(LThenNode, 'Pin.Exec.In'), LIfNode, 'Pin.Exec.Out.Then');
+ thenEndY := FCurrentPos.Y;
// Else branch (if it exists)
- LElseEndY := LThenEndY;
+ elseEndY := thenEndY;
if Assigned(Node.ElseBranch) then
begin
- FCurrentPos.X := LBranchStartX;
- FCurrentPos.Y := LThenEndY;
+ FCurrentPos.X := branchStartX;
+ FCurrentPos.Y := thenEndY;
Node.ElseBranch.Accept(Self);
- LElseNode := FLastNode;
- LElseEndY := FCurrentPos.Y;
- // ConnectData(FindPinByTag(LElseNode, 'Pin.Exec.In'), LIfNode, 'Pin.Exec.Out.Else');
+ elseEndY := FCurrentPos.Y;
end;
// Finalize visitor state.
- FCurrentPos.X := LStartPosition.X;
- FCurrentPos.Y := Max(LConditionEndY, LElseEndY);
- FLastNode := LIfNode;
+ FCurrentPos.X := startPosition.X;
+ FCurrentPos.Y := Max(conditionEndY, elseEndY);
+ FLastNode := ifNode;
Result := TAstValue.Void;
end;
function TAstToAuraNodeVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue;
var
- ParamStr: String;
- LLambdaNode: TAuraNode;
- LChildVisitor: IAstVisitor;
- LChildStartPos: TPointF;
- LMaxRight, LMaxBottom: Single;
- LControl: TControl;
+ paramStr: String;
+ lambdaNode: TAuraNode;
i: Integer;
begin
// Prepare the parameter string for the title
- ParamStr := '(';
+ paramStr := '(';
if Length(Node.Parameters) > 0 then
begin
- ParamStr := ParamStr + Node.Parameters[0].Name;
+ paramStr := paramStr + Node.Parameters[0].Name;
for i := 1 to High(Node.Parameters) do
- ParamStr := ParamStr + ', ' + Node.Parameters[i].Name;
+ paramStr := paramStr + ', ' + Node.Parameters[i].Name;
end;
- ParamStr := ParamStr + ')';
+ paramStr := paramStr + ')';
- // Create the container node for the lambda. Its size will be adjusted later.
- LLambdaNode := CreateNodeControl('Lambda', ParamStr);
+ // Use the helper to create and populate the container node.
+ lambdaNode := VisitContainerNode('Lambda', paramStr, procedure(const Visitor: IAstVisitor) begin Node.Body.Accept(Visitor); end);
- // Create a new visitor for the child nodes (the lambda body).
- // The children will be parented to LLambdaNode and positioned relative to it.
- LChildStartPos := TPointF.Create(FSpacing.X, FSpacing.Y + LLambdaNode.TitleFont.Size * 1.8);
- LChildVisitor := TAstToAuraNodeVisitor.Create(FWorkspace, LLambdaNode, LChildStartPos, FConnections);
+ CreateOutput(lambdaNode, 0);
- // Visit the body expression using the new visitor.
- Node.Body.Accept(LChildVisitor);
-
- // Adjust the size of the lambda node to encompass its body.
- LMaxRight := 0;
- LMaxBottom := 0;
- for LControl in LLambdaNode.Controls do
- begin
- if (LControl is TAuraNode) then
- begin
- LMaxRight := Max(LMaxRight, LControl.Position.X + LControl.Width);
- LMaxBottom := Max(LMaxBottom, LControl.Position.Y + LControl.Height);
- end;
- end;
-
- // Set the final size with some internal padding.
- LLambdaNode.Width := Max(LLambdaNode.Width, LMaxRight + FSpacing.X);
- LLambdaNode.Height := Max(LLambdaNode.Height, LMaxBottom + FSpacing.Y);
-
- // Add a data output pin for the resulting closure.
- CreateOutput(LLambdaNode, 0);
-
- // Update the current Y position of the main visitor to be below the now correctly sized lambda node.
- FCurrentPos.Y := LLambdaNode.Position.Y + LLambdaNode.Height + FSpacing.Y;
+ // Update the state of the main visitor
+ FCurrentPos.Y := lambdaNode.Position.Y + lambdaNode.Height + FSpacing.Y;
+ FLastNode := lambdaNode;
Result := TAstValue.Void;
end;
+function TAstToAuraNodeVisitor.VisitOperatorNode(
+ const InputExpressions: TArray;
+ const CreateParentProc: TCreateParentNodeProc;
+ Alignment: TVerticalAlignment
+): TAuraNode;
+var
+ inputResultNodes: TAuraNodeList;
+ inputNode: IExpressionNode;
+ startPosition: TPointF;
+ endY, maxChildWidth: Single;
+ parentNode: TAuraNode;
+begin
+ startPosition := FCurrentPos;
+ inputResultNodes := TAuraNodeList.Create;
+ try
+ // 1. Visit all input nodes and collect their visual representations.
+ for inputNode in InputExpressions do
+ begin
+ inputNode.Accept(Self);
+ inputResultNodes.Add(FLastNode);
+ end;
+ endY := FCurrentPos.Y;
+
+ // 2. Determine the layout bounds of the input nodes.
+ maxChildWidth := 0;
+ for var node in inputResultNodes do
+ maxChildWidth := Max(maxChildWidth, node.Position.X + node.Width);
+
+ // 3. Let the closure create the parent node (without positioning).
+ parentNode := CreateParentProc(inputResultNodes);
+ Result := parentNode;
+
+ // 4. Now position the parent node to the right of the inputs.
+ var parentY: Single;
+ if Alignment = vaCenter then
+ begin
+ var totalInputHeight := endY - startPosition.Y;
+ var nodeCenterY := startPosition.Y + (totalInputHeight / 2);
+ parentY := nodeCenterY - (parentNode.Height / 2);
+ end
+ else // vaTop
+ begin
+ parentY := startPosition.Y;
+ end;
+ parentNode.Position.Point := TPointF.Create(maxChildWidth + FSpacing.X, parentY);
+
+ // 5. Finalize visitor state for the next operation.
+ FCurrentPos.X := startPosition.X;
+ FCurrentPos.Y := Max(endY, parentNode.Position.Y + parentNode.Height + FSpacing.Y);
+ FLastNode := parentNode;
+ finally
+ inputResultNodes.Free;
+ end;
+end;
+
function TAstToAuraNodeVisitor.VisitUnaryExpression(const Node: IUnaryExpressionNode): TAstValue;
begin
CreateNodeControl('UnaryExpr', Node.Operator.ToString);
@@ -679,49 +672,35 @@ begin
end;
function TAstToAuraNodeVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue;
-var
- LVarDeclNode, LInitializerNode: TAuraNode;
- LStartPosition: TPointF;
- LInitializerEndY, LVarDeclNodeEndY: Single;
- LInputPin: TControl;
begin
- LStartPosition := FCurrentPos;
- LInitializerEndY := LStartPosition.Y;
- LInitializerNode := nil;
-
if Assigned(Node.Initializer) then
begin
- Node.Initializer.Accept(Self);
+ VisitOperatorNode(
+ [Node.Initializer],
+ function(const InputNodes: TAuraNodeList): TAuraNode
+ var
+ varDeclNode, initializerNode: TAuraNode;
+ inputPin: TControl;
+ begin
+ initializerNode := InputNodes[0];
+ varDeclNode := BuildNodeControl('VarDecl', Node.Identifier.Name);
- LInitializerNode := FLastNode;
- LInitializerEndY := FCurrentPos.Y;
+ CreateEntry(varDeclNode, -8);
+ CreateExit(varDeclNode, '', -8);
+ inputPin := CreateInput(varDeclNode, 'Value', 8);
+ ConnectData(inputPin, initializerNode);
- // Position the VarDecl node dynamically to the right of the initializer's visual tree.
- FCurrentPos.X := LInitializerNode.Position.X + LInitializerNode.Width + FSpacing.X;
- FCurrentPos.Y := LStartPosition.Y;
- end;
-
- LVarDeclNode := CreateNodeControl('VarDecl', Node.Identifier.Name);
- LVarDeclNodeEndY := FCurrentPos.Y;
-
- CreateEntry(LVarDeclNode, -8);
- CreateExit(LVarDeclNode, '', -8);
-
- if Assigned(Node.Initializer) then
+ Result := varDeclNode;
+ end
+ );
+ end
+ else
begin
- LInputPin := CreateInput(LVarDeclNode, LInitializerNode.Name, 8);
- ConnectData(LInputPin, LInitializerNode);
-
- // Vertically center the VarDecl node relative to the initializer's visual tree.
- var LInitializerHeight := LInitializerEndY - LStartPosition.Y;
- LVarDeclNode.Position.Y := LStartPosition.Y + (LInitializerHeight / 2) - (LVarDeclNode.Height / 2);
- LVarDeclNodeEndY := LVarDeclNode.Position.Y + LVarDeclNode.Height + FSpacing.Y;
-
- FCurrentPos.Y := Max(LInitializerEndY, LVarDeclNodeEndY);
- FCurrentPos.X := LStartPosition.X;
+ // Case without initializer is a simple sequential node.
+ var varDeclNode := CreateNodeControl('VarDecl', Node.Identifier.Name);
+ CreateEntry(varDeclNode, 0);
+ CreateExit(varDeclNode, '', 0);
end;
- FLastNode := LVarDeclNode;
-
Result := TAstValue.Void;
end;
@@ -729,12 +708,12 @@ end;
procedure TAuraWorkspace.BuildTree(const Root: IAstNode; const Position: TPointF);
begin
- var Connections := TList.Create;
+ var connections := TList.Create;
try
- Root.Accept(TAstToAuraNodeVisitor.Create(Self, Self, Position, Connections));
- FConnections := FConnections + Connections.ToArray;
+ Root.Accept(TAstToAuraNodeVisitor.Create(Self, Self, Position, connections));
+ FConnections := FConnections + connections.ToArray;
finally
- Connections.Free;
+ connections.Free;
end;
end;
@@ -746,11 +725,11 @@ end;
procedure TAuraWorkspace.Paint;
var
- LConnection: TPinConnection;
- LStartPoint, LEndPoint, LPinCenter: TPointF;
- LPath: TPathData;
- LControlPoint1, LControlPoint2: TPointF;
- LDeltaX, LControlOffset: Single;
+ connection: TPinConnection;
+ startPoint, endPoint, pinCenter: TPointF;
+ path: TPathData;
+ controlPoint1, controlPoint2: TPointF;
+ deltaX, controlOffset: Single;
begin
inherited;
@@ -758,40 +737,40 @@ begin
Canvas.Stroke.Thickness := 2;
// Gehe durch alle gespeicherten Verbindungen und zeichne sie
- for LConnection in FConnections do
+ for connection in FConnections do
begin
// Hole die absoluten Koordinaten der Pin-Mittelpunkte
- LPinCenter := TPointF.Create(LConnection.OutputPin.Width / 2, LConnection.OutputPin.Height / 2);
- var LAbsoluteStart := LConnection.OutputPin.LocalToAbsolute(LPinCenter);
+ pinCenter := TPointF.Create(connection.OutputPin.Width / 2, connection.OutputPin.Height / 2);
+ var absoluteStart := connection.OutputPin.LocalToAbsolute(pinCenter);
- LPinCenter := TPointF.Create(LConnection.InputPin.Width / 2, LConnection.InputPin.Height / 2);
- var LAbsoluteEnd := LConnection.InputPin.LocalToAbsolute(LPinCenter);
+ pinCenter := TPointF.Create(connection.InputPin.Width / 2, connection.InputPin.Height / 2);
+ var absoluteEnd := connection.InputPin.LocalToAbsolute(pinCenter);
// Rechne sie in die lokalen Koordinaten der PaintBox um
- LStartPoint := AbsoluteToLocal(LAbsoluteStart);
- LEndPoint := AbsoluteToLocal(LAbsoluteEnd);
+ startPoint := AbsoluteToLocal(absoluteStart);
+ endPoint := AbsoluteToLocal(absoluteEnd);
- var str := LConnection.InputPin.TagString;
+ var str := connection.InputPin.TagString;
if Pos('data', str) = 0 then
Canvas.Stroke.Color := TAstToAuraNodeVisitor.cExecPinColor
else
Canvas.Stroke.Color := TAstToAuraNodeVisitor.cDataPinColor;
// Draw a Bezier curve with horizontal tangents at start and end points.
- LPath := TPathData.Create;
+ path := TPathData.Create;
try
- LDeltaX := LEndPoint.X - LStartPoint.X;
- LControlOffset := Abs(LDeltaX) / 2;
+ deltaX := endPoint.X - startPoint.X;
+ controlOffset := max(30, abs(deltaX) / 2);
- LControlPoint1 := TPointF.Create(LStartPoint.X + LControlOffset, LStartPoint.Y);
- LControlPoint2 := TPointF.Create(LEndPoint.X - LControlOffset, LEndPoint.Y);
+ controlPoint1 := TPointF.Create(startPoint.X + controlOffset, startPoint.Y);
+ controlPoint2 := TPointF.Create(endPoint.X - controlOffset, endPoint.Y);
- LPath.MoveTo(LStartPoint);
- LPath.CurveTo(LControlPoint1, LControlPoint2, LEndPoint);
+ path.MoveTo(startPoint);
+ path.CurveTo(controlPoint1, controlPoint2, endPoint);
- Canvas.DrawPath(LPath, 1.0);
+ Canvas.DrawPath(path, 1.0);
finally
- LPath.Free;
+ path.Free;
end;
end;
end;