From 230c4b51bf253e168c3d033e5d8c53deb295d573 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Tue, 16 Sep 2025 13:14:33 +0200 Subject: [PATCH] Ast control refactoring --- ASTPlayground/ASTPlayground.dpr | 5 +- ASTPlayground/ASTPlayground.dproj | 9 + ASTPlayground/MainForm.pas | 2 + ASTPlayground/Myc.Fmx.AstEditor.Node.pas | 303 ++++++ ASTPlayground/Myc.Fmx.AstEditor.Text.pas | 185 ++++ ASTPlayground/Myc.Fmx.AstEditor.Workspace.pas | 245 +++++ ASTPlayground/Myc.Fmx.AstEditor.pas | 902 +----------------- 7 files changed, 770 insertions(+), 881 deletions(-) create mode 100644 ASTPlayground/Myc.Fmx.AstEditor.Node.pas create mode 100644 ASTPlayground/Myc.Fmx.AstEditor.Text.pas create mode 100644 ASTPlayground/Myc.Fmx.AstEditor.Workspace.pas diff --git a/ASTPlayground/ASTPlayground.dpr b/ASTPlayground/ASTPlayground.dpr index 5e384b2..3adea48 100644 --- a/ASTPlayground/ASTPlayground.dpr +++ b/ASTPlayground/ASTPlayground.dpr @@ -11,7 +11,10 @@ uses Myc.Fmx.AstEditor in 'Myc.Fmx.AstEditor.pas', Myc.Ast.ViewModel in '..\Src\AST\Myc.Ast.ViewModel.pas', Myc.Data.Value in 'Myc.Data.Value.pas', - Myc.Ast.Debugger in '..\Src\AST\Myc.Ast.Debugger.pas'; + Myc.Ast.Debugger in '..\Src\AST\Myc.Ast.Debugger.pas', + Myc.Fmx.AstEditor.Node in 'Myc.Fmx.AstEditor.Node.pas', + Myc.Fmx.AstEditor.Workspace in 'Myc.Fmx.AstEditor.Workspace.pas', + Myc.Fmx.AstEditor.Text in 'Myc.Fmx.AstEditor.Text.pas'; {$R *.res} diff --git a/ASTPlayground/ASTPlayground.dproj b/ASTPlayground/ASTPlayground.dproj index 769b924..aced057 100644 --- a/ASTPlayground/ASTPlayground.dproj +++ b/ASTPlayground/ASTPlayground.dproj @@ -143,6 +143,9 @@ + + + Base @@ -198,6 +201,12 @@ true + + + ASTPlayground.rsm + true + + ASTPlayground.exe diff --git a/ASTPlayground/MainForm.pas b/ASTPlayground/MainForm.pas index f4c5415..a15df52 100644 --- a/ASTPlayground/MainForm.pas +++ b/ASTPlayground/MainForm.pas @@ -22,6 +22,8 @@ uses FMX.Memo, FMX.Controls.Presentation, Myc.Fmx.AstEditor, + Myc.Fmx.AstEditor.Node, + Myc.Fmx.AstEditor.Workspace, Myc.Data.Scalar, Myc.Data.Value, Myc.Ast.Nodes, diff --git a/ASTPlayground/Myc.Fmx.AstEditor.Node.pas b/ASTPlayground/Myc.Fmx.AstEditor.Node.pas new file mode 100644 index 0000000..d3c7848 --- /dev/null +++ b/ASTPlayground/Myc.Fmx.AstEditor.Node.pas @@ -0,0 +1,303 @@ +unit Myc.Fmx.AstEditor.Node; + +interface + +uses + System.SysUtils, + System.Classes, + System.UITypes, + System.Types, + System.Math.Vectors, + FMX.Types, + FMX.Controls, + FMX.Objects, + FMX.Graphics; + +type + // A movable panel with a custom-painted border and title. + TAuraNode = class(TStyledControl) + private + // Flag to indicate if the control is currently being dragged. + FIsDragging: Boolean; + // Stores the mouse coordinates at the beginning of the drag operation. + FDownPos: TPointF; + // Properties for the custom-drawn border. + FBorderColor: TAlphaColor; + FBorderWidth: Single; + FBorderRadius: Single; + // Properties for the title + FTitle: string; + FTitleFont: TFont; + FTitleFontColor: TAlphaColor; + // If true, the node is drawn without a border and with a transparent background. + FFrameless: Boolean; + + procedure SetBorderColor(const Value: TAlphaColor); + procedure SetBorderWidth(const Value: Single); + procedure SetBorderRadius(const Value: Single); + procedure SetTitle(const Value: string); + procedure SetTitleFont(const Value: TFont); + procedure SetTitleFontColor(const Value: TAlphaColor); + procedure TitleFontChanged(Sender: TObject); + procedure SetFrameless(const Value: Boolean); + protected + procedure Paint; override; + procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override; + procedure MouseMove(Shift: TShiftState; X, Y: Single); override; + procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + published + // Custom border properties + property BorderColor: TAlphaColor read FBorderColor write SetBorderColor; + property BorderWidth: Single read FBorderWidth write SetBorderWidth; + property BorderRadius: Single read FBorderRadius write SetBorderRadius; + + // Title properties + property Title: string read FTitle write SetTitle; + property TitleFont: TFont read FTitleFont write SetTitleFont; + property TitleFontColor: TAlphaColor read FTitleFontColor write SetTitleFontColor; + + // Behavior properties + property Frameless: Boolean read FFrameless write SetFrameless; + + // Standard control properties + property Align; + property Anchors; + property ClipChildren default True; + property Cursor default crDefault; + property DragMode default TDragMode.dmManual; + property Enabled; + property Height; + property HelpContext; + property HelpKeyword; + property HelpType; + property Hint; + property HitTest default True; + property Locked; + property Margins; + property Opacity; + property Padding; + property PopupMenu; + property Position; + property RotationAngle; + property RotationCenter; + property Scale; + property Size; + property StyleLookup; + property Visible; + property Width; + property OnClick; + property OnDblClick; + property OnMouseDown; + property OnMouseMove; + property OnMouseUp; + property OnMouseWheel; + property OnMouseEnter; + property OnMouseLeave; + end; + +implementation + +{ TAuraNode } + +constructor TAuraNode.Create(AOwner: TComponent); +begin + inherited Create(AOwner); + FIsDragging := False; + + // Default border settings + FBorderColor := TAlphaColors.Gray; + FBorderWidth := 1; + FBorderRadius := 9; // Sharp corners by default + + // Default title settings + FTitle := 'Node'; + FTitleFont := TFont.Create; + FTitleFont.Family := 'Segoe UI'; + FTitleFont.Size := 11; + FTitleFont.Style := [TFontStyle.fsBold]; + FTitleFont.OnChanged := TitleFontChanged; + FTitleFontColor := TAlphaColors.Black; + + // Default state for frameless mode + FFrameless := False; + + Width := 80; + Height := 45; + + // The panel must be able to receive mouse events. + // HitTest := True; + // Clip children to the panel's bounds. + ClipChildren := True; +end; + +destructor TAuraNode.Destroy; +begin + FTitleFont.Free; + inherited; +end; + +procedure TAuraNode.Paint; +var + rect, titleRect: TRectF; + effectiveBorderWidth: Single; +begin + inherited; // Allow styled painting to occur first (if any) + + if FFrameless then + begin + Canvas.Fill.Kind := TBrushKind.Solid; + Canvas.Fill.Color := $20C0C0C0; // Transparent Silver + Canvas.Stroke.Kind := TBrushKind.None; + + // In frameless mode, draw a transparent background and ignore the border. + Canvas.FillRect(TRectF.Create(0, 0, Width, Height), 0, 0, [], 1.0); + effectiveBorderWidth := 0; + end + else + begin + // Custom painting for the border + if (FBorderWidth > 0) and (FBorderColor <> TAlphaColors.Null) then + begin + rect := TRectF.Create(0, 0, Width, Height); + // Inflate inwards so the border is fully visible within the control's bounds + rect.Inflate(-FBorderWidth / 2, -FBorderWidth / 2); + + Canvas.Stroke.Kind := TBrushKind.Solid; + Canvas.Stroke.Color := FBorderColor; + Canvas.Stroke.Thickness := FBorderWidth; + + if FFrameless then + begin + Canvas.Fill.Kind := TBrushKind.Solid; + Canvas.Fill.Color := $20C0C0C0; // Transparent Silver + Canvas.FillRect(rect, FBorderRadius, FBorderRadius, AllCorners, 1.0, TCornerType.Round); + end + else + Canvas.DrawRect(rect, FBorderRadius, FBorderRadius, AllCorners, 1.0, TCornerType.Round); + end; + effectiveBorderWidth := FBorderWidth; + end; + + // Draw the title + if not FTitle.IsEmpty then + begin + // Define the rectangle for the title at the top of the control + titleRect := TRectF.Create(0, effectiveBorderWidth, Width, effectiveBorderWidth + 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(titleRect, FTitle, False, 1.0, [], TTextAlign.Center, TTextAlign.Center); + end; +end; + +procedure TAuraNode.SetBorderColor(const Value: TAlphaColor); +begin + if FBorderColor <> Value then + begin + FBorderColor := Value; + Repaint; + end; +end; + +procedure TAuraNode.SetBorderRadius(const Value: Single); +begin + if FBorderRadius <> Value then + begin + FBorderRadius := Value; + Repaint; + end; +end; + +procedure TAuraNode.SetBorderWidth(const Value: Single); +begin + if FBorderWidth <> Value then + begin + FBorderWidth := Value; + Repaint; + end; +end; + +procedure TAuraNode.SetFrameless(const Value: Boolean); +begin + if FFrameless <> Value then + begin + FFrameless := Value; + Repaint; + end; +end; + +procedure TAuraNode.SetTitle(const Value: string); +begin + if FTitle <> Value then + begin + FTitle := Value; + Repaint; + end; +end; + +procedure TAuraNode.SetTitleFont(const Value: TFont); +begin + FTitleFont.Assign(Value); +end; + +procedure TAuraNode.SetTitleFontColor(const Value: TAlphaColor); +begin + if FTitleFontColor <> Value then + begin + FTitleFontColor := Value; + Repaint; + end; +end; + +procedure TAuraNode.TitleFontChanged(Sender: TObject); +begin + Repaint; +end; + +procedure TAuraNode.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); +begin + inherited; + if Button = TMouseButton.mbLeft then + begin + if ObjectAtPoint(LocalToScreen(TPointF.Create(X, Y))) = Self as IControl then + begin + FIsDragging := True; + FDownPos := TPointF.Create(X, Y); + Capture; + end; + end; +end; + +procedure TAuraNode.MouseMove(Shift: TShiftState; X, Y: Single); +begin + inherited; + + if FIsDragging then + begin + var deltaX := X - FDownPos.X; + var deltaY := Y - FDownPos.Y; + + Position.X := Position.X + deltaX; + Position.Y := Position.Y + deltaY; + + if ParentControl <> nil then + ParentControl.Repaint; + end; +end; + +procedure TAuraNode.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); +begin + inherited; + if (Button = TMouseButton.mbLeft) and (FIsDragging) then + begin + ReleaseCapture; + FIsDragging := False; + end; +end; + +end. diff --git a/ASTPlayground/Myc.Fmx.AstEditor.Text.pas b/ASTPlayground/Myc.Fmx.AstEditor.Text.pas new file mode 100644 index 0000000..2be0a5d --- /dev/null +++ b/ASTPlayground/Myc.Fmx.AstEditor.Text.pas @@ -0,0 +1,185 @@ +unit Myc.Fmx.AstEditor.Text; + +interface + +uses + System.SysUtils, + Myc.Data.Value, + Myc.Data.Scalar, + Myc.Ast.Nodes; + +type + // This visitor converts an AST expression subtree into a single string. + TAstToTextVisitor = class(TInterfacedObject, IAstVisitor) + public + { IAstVisitor } + function VisitConstant(const Node: IConstantNode): TDataValue; + function VisitIdentifier(const Node: IIdentifierNode): TDataValue; + function VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; + function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; + function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; + function VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; + function VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; + function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; + function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; + function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; + function VisitAssignment(const Node: IAssignmentNode): TDataValue; + function VisitIndexer(const Node: IIndexerNode): TDataValue; + function VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; + function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; + function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; + function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; + end; + +implementation + +{ TAstToTextVisitor } + +function TAstToTextVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; +var + seriesStr, valueStr, lookbackStr: string; +begin + seriesStr := Node.Series.Accept(Self).AsText; + valueStr := Node.Value.Accept(Self).AsText; + lookbackStr := ''; + if Assigned(Node.Lookback) then + lookbackStr := ', ' + Node.Lookback.Accept(Self).AsText; + Result := Format('%s.add(%s%s)', [seriesStr, valueStr, lookbackStr]); +end; + +function TAstToTextVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue; +begin + Result := Node.Identifier.Name + ' := ' + Node.Value.Accept(Self).AsText; +end; + +function TAstToTextVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; +begin + var leftStr := Node.Left.Accept(Self).AsText; + var rightStr := Node.Right.Accept(Self).AsText; + Result := '(' + leftStr + ' ' + Node.Operator.ToString + ' ' + rightStr + ')'; +end; + +function TAstToTextVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; +begin + Result := '{...}'; +end; + +function TAstToTextVisitor.VisitConstant(const Node: IConstantNode): TDataValue; +begin + Result := Node.Value.ToString; +end; + +function TAstToTextVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; +begin + Result := 'new series(' + Node.Definition + ')'; +end; + +function TAstToTextVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; +var + i: Integer; + sb: TStringBuilder; + calleeStr: string; +begin + calleeStr := Node.Callee.Accept(Self).AsText; + + sb := TStringBuilder.Create; + try + sb.Append(calleeStr); + sb.Append('('); + for i := 0 to High(Node.Arguments) do + begin + sb.Append(Node.Arguments[i].Accept(Self).AsText); + if i < High(Node.Arguments) then + sb.Append(', '); + end; + sb.Append(')'); + Result := sb.ToString; + finally + sb.Free; + end; +end; + +function TAstToTextVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue; +begin + Result := Node.Name; +end; + +function TAstToTextVisitor.VisitIfExpression(const Node: IIfExpressionNode): TDataValue; +begin + Result := Node.Condition.Accept(Self).AsText; +end; + +function TAstToTextVisitor.VisitIndexer(const Node: IIndexerNode): TDataValue; +var + baseStr, indexStr: string; +begin + baseStr := Node.Base.Accept(Self).AsText; + indexStr := Node.Index.Accept(Self).AsText; + Result := Format('%s[%s]', [baseStr, indexStr]); +end; + +function TAstToTextVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; +var + i: Integer; + sb: TStringBuilder; +begin + sb := TStringBuilder.Create; + try + sb.Append(#$03BB + '('); + if Length(Node.Parameters) > 0 then + begin + for i := 0 to High(Node.Parameters) do + begin + sb.Append(Node.Parameters[i].Name); + if i < High(Node.Parameters) then + sb.Append(', '); + end; + end; + sb.Append(') => {...}'); + Result := sb.ToString; + finally + sb.Free; + end; +end; + +function TAstToTextVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; +var + baseStr: string; +begin + baseStr := Node.Base.Accept(Self).AsText; + Result := Format('%s.%s', [baseStr, Node.Member.Name]); +end; + +function TAstToTextVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; +var + seriesStr: string; +begin + seriesStr := Node.Series.Accept(Self).AsText; + Result := Format('length(%s)', [seriesStr]); +end; + +function TAstToTextVisitor.VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; +begin + var condStr := Node.Condition.Accept(Self).AsText; + var thenStr := Node.ThenBranch.Accept(Self).AsText; + var elseStr := Node.ElseBranch.Accept(Self).AsText; + Result := Format('(%s ? %s : %s)', [condStr, thenStr, elseStr]); +end; + +function TAstToTextVisitor.VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; +begin + Result := Node.Operator.ToString + ' ' + Node.Right.Accept(Self).AsText; +end; + +function TAstToTextVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; +var + initStr: string; +begin + if Assigned(Node.Initializer) then + initStr := ' := ' + Node.Initializer.Accept(Self).AsText + else + initStr := ''; + Result := 'var ' + Node.Identifier.Name + initStr; +end; + +end. diff --git a/ASTPlayground/Myc.Fmx.AstEditor.Workspace.pas b/ASTPlayground/Myc.Fmx.AstEditor.Workspace.pas new file mode 100644 index 0000000..0f89af0 --- /dev/null +++ b/ASTPlayground/Myc.Fmx.AstEditor.Workspace.pas @@ -0,0 +1,245 @@ +unit Myc.Fmx.AstEditor.Workspace; + +interface + +uses + System.SysUtils, + System.Classes, + System.Types, + System.UITypes, + System.Math.Vectors, + System.Generics.Collections, + FMX.Types, + FMX.Controls, + FMX.Graphics, + FMX.Objects, + Myc.Ast.Nodes; + +type + TPinConnection = record + OutputPin: TControl; + InputPin: TControl; + constructor Create(AOutputPin, AInputPin: TControl); + end; + + // Enum to select the visualization style. + TVisualizationMode = (vmDetailed, vmControlFlow); + + TAuraWorkspace = class(TStyledControl) + private + FConnections: TArray; + FPanning: TSizeF; + FIsPanning: Boolean; + FLastPanPos: TPointF; + FZoom: Single; + protected + procedure Paint; override; + procedure DoDeleteChildren; override; + procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override; + procedure MouseMove(Shift: TShiftState; X, Y: Single); override; + procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override; + procedure MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean); override; + procedure DblClick; override; + public + constructor Create(AOwner: TComponent); override; + procedure BuildTree(const Root: IAstNode; const Position: TPointF; AMode: TVisualizationMode); + function GetChildrenMatrix(var Matrix: TMatrix; var Simple: Boolean): Boolean; override; + function Zoom(Factor: Single): Boolean; + + published + property Align; + property Anchors; + property ClipChildren default True; + property Cursor default crDefault; + property DragMode default TDragMode.dmManual; + property Enabled; + property Height; + property HelpContext; + property HelpKeyword; + property HelpType; + property Hint; + property HitTest default True; + property Locked; + property Margins; + property Opacity; + property Padding; + property PopupMenu; + property Position; + property RotationAngle; + property RotationCenter; + property Scale; + property Size; + property StyleLookup; + property Visible; + property Width; + property OnClick; + property OnDblClick; + property OnMouseDown; + property OnMouseMove; + property OnMouseUp; + property OnMouseWheel; + property OnMouseEnter; + property OnMouseLeave; + end; + +implementation + +uses + System.Math, + FMX.Platform, + Myc.Fmx.AstEditor; + +{ TPinConnection } + +constructor TPinConnection.Create(AOutputPin, AInputPin: TControl); +begin + OutputPin := AOutputPin; + InputPin := AInputPin; +end; + +{ TAuraWorkspace } + +constructor TAuraWorkspace.Create(AOwner: TComponent); +begin + inherited; + FZoom := 1.0; +end; + +procedure TAuraWorkspace.BuildTree(const Root: IAstNode; const Position: TPointF; AMode: TVisualizationMode); +begin + var connections := TList.Create; + try + Root.Accept(TAstToAuraNodeVisitor.Create(Self, Self, Position, connections, nil, AMode)); + FConnections := FConnections + connections.ToArray; + finally + connections.Free; + end; +end; + +procedure TAuraWorkspace.DoDeleteChildren; +begin + FConnections := nil; + inherited; +end; + +function TAuraWorkspace.GetChildrenMatrix(var Matrix: TMatrix; var Simple: Boolean): Boolean; +begin + Matrix := TMatrix.CreateScaling(FZoom, FZoom) * TMatrix.CreateTranslation(FPanning.cx, FPanning.cy); + Simple := (FZoom = 1.0) and (FPanning.cx = 0) and (FPanning.cy = 0); + Result := true; +end; + +procedure TAuraWorkspace.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); +begin + inherited; + if (Button = TMouseButton.mbLeft) and (ObjectAtPoint(LocalToScreen(TPointF.Create(X, Y))) = Self as IControl) then + begin + FIsPanning := True; + FLastPanPos := TPointF.Create(X, Y); + Cursor := crHandPoint; + end; +end; + +procedure TAuraWorkspace.MouseMove(Shift: TShiftState; X, Y: Single); +var + delta: TPointF; +begin + inherited; + + if FIsPanning then + begin + delta := TPointF.Create(X - FLastPanPos.X, Y - FLastPanPos.Y); + FPanning.cx := FPanning.cx + delta.X; + FPanning.cy := FPanning.cy + delta.Y; + FLastPanPos := TPointF.Create(X, Y); + RecalcAbsolute; + RecalcUpdateRect; + Repaint; + end; +end; + +procedure TAuraWorkspace.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); +begin + inherited; + if (Button = TMouseButton.mbLeft) and (FIsPanning) then + begin + FIsPanning := False; + Cursor := crDefault; + end; +end; + +procedure TAuraWorkspace.MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean); +begin + inherited; + Handled := Zoom(IfThen(WheelDelta > 0, FZoom * 1.1, FZoom / 1.1)); +end; + +procedure TAuraWorkspace.DblClick; +begin + inherited; + Zoom(1.0); +end; + +procedure TAuraWorkspace.Paint; +var + connection: TPinConnection; + startPoint, endPoint, pinCenter: TPointF; + path: TPathData; + controlPoint1, controlPoint2: TPointF; + controlOffset: Single; +begin + inherited; + + Canvas.Stroke.Kind := TBrushKind.Solid; + Canvas.Stroke.Thickness := 2; + + for connection in FConnections do + begin + pinCenter := TPointF.Create(connection.OutputPin.Width / 2, connection.OutputPin.Height / 2); + var absoluteStart := connection.OutputPin.LocalToAbsolute(pinCenter); + + pinCenter := TPointF.Create(connection.InputPin.Width / 2, connection.InputPin.Height / 2); + var absoluteEnd := connection.InputPin.LocalToAbsolute(pinCenter); + + startPoint := AbsoluteToLocal(absoluteStart); + endPoint := AbsoluteToLocal(absoluteEnd); + + var str := connection.InputPin.TagString; + if Pos('data', str) = 0 then + Canvas.Stroke.Color := cExecPinColor + else + Canvas.Stroke.Color := cDataPinColor; + + path := TPathData.Create; + try + controlOffset := max(25, 0.5 * endPoint.Distance(startPoint)); + controlPoint1 := TPointF.Create(startPoint.X + controlOffset, startPoint.Y); + controlPoint2 := TPointF.Create(endPoint.X - controlOffset, endPoint.Y); + path.MoveTo(startPoint); + path.CurveTo(controlPoint1, controlPoint2, endPoint); + Canvas.DrawPath(path, 1.0); + finally + path.Free; + end; + end; +end; + +function TAuraWorkspace.Zoom(Factor: Single): Boolean; +var + MouseService: IFMXMouseService; +begin + Result := TPlatformServices.Current.SupportsPlatformService(IFMXMouseService, MouseService); + if Result then + begin + var MousePos := ScreenToLocal(MouseService.GetMousePos); + Factor := Max(0.2, Min(3.0, Factor)); + FPanning.cx := MousePos.X * (1 - Factor / FZoom) + FPanning.cx * (Factor / FZoom); + FPanning.cy := MousePos.Y * (1 - Factor / FZoom) + FPanning.cy * (Factor / FZoom); + FZoom := Factor; + RecalcAbsolute; + RecalcUpdateRect; + Repaint; + end; +end; + +end. diff --git a/ASTPlayground/Myc.Fmx.AstEditor.pas b/ASTPlayground/Myc.Fmx.AstEditor.pas index 2d87439..1b73f60 100644 --- a/ASTPlayground/Myc.Fmx.AstEditor.pas +++ b/ASTPlayground/Myc.Fmx.AstEditor.pas @@ -14,168 +14,20 @@ uses FMX.Objects, FMX.Graphics, Myc.Data.Value, - Myc.Ast.Nodes; + Myc.Ast.Nodes, + Myc.Fmx.AstEditor.Node, + Myc.Fmx.AstEditor.Workspace; + +const + // Pin Visuals + cPinSize = 10; + cDataPinColor = TAlphaColors.Dodgerblue; + cExecPinColor = TAlphaColors.Lightgreen; type - TPinConnection = record - OutputPin: TControl; - InputPin: TControl; - constructor Create(AOutputPin, AInputPin: TControl); - end; - TPinShape = (psCircle, psTriangle); TPinAlignment = (paLeft, paRight); - // New enum to select the visualization style. - TVisualizationMode = (vmDetailed, vmControlFlow); - - // A movable panel with a custom-painted border and title. - TAuraNode = class(TStyledControl) - private - // Flag to indicate if the control is currently being dragged. - FIsDragging: Boolean; - // Stores the mouse coordinates at the beginning of the drag operation. - FDownPos: TPointF; - // Properties for the custom-drawn border. - FBorderColor: TAlphaColor; - FBorderWidth: Single; - FBorderRadius: Single; - // Properties for the title - FTitle: string; - FTitleFont: TFont; - FTitleFontColor: TAlphaColor; - // If true, the node is drawn without a border and with a transparent background. - FFrameless: Boolean; - - procedure SetBorderColor(const Value: TAlphaColor); - procedure SetBorderWidth(const Value: Single); - procedure SetBorderRadius(const Value: Single); - procedure SetTitle(const Value: string); - procedure SetTitleFont(const Value: TFont); - procedure SetTitleFontColor(const Value: TAlphaColor); - procedure TitleFontChanged(Sender: TObject); - procedure SetFrameless(const Value: Boolean); - protected - procedure Paint; override; - procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override; - procedure MouseMove(Shift: TShiftState; X, Y: Single); override; - procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override; - public - constructor Create(AOwner: TComponent); override; - destructor Destroy; override; - published - // Custom border properties - property BorderColor: TAlphaColor read FBorderColor write SetBorderColor; - property BorderWidth: Single read FBorderWidth write SetBorderWidth; - property BorderRadius: Single read FBorderRadius write SetBorderRadius; - - // Title properties - property Title: string read FTitle write SetTitle; - property TitleFont: TFont read FTitleFont write SetTitleFont; - property TitleFontColor: TAlphaColor read FTitleFontColor write SetTitleFontColor; - - // Behavior properties - property Frameless: Boolean read FFrameless write SetFrameless; - - // Standard control properties - property Align; - property Anchors; - property ClipChildren default True; - property Cursor default crDefault; - property DragMode default TDragMode.dmManual; - property Enabled; - property Height; - property HelpContext; - property HelpKeyword; - property HelpType; - property Hint; - property HitTest default True; - property Locked; - property Margins; - property Opacity; - property Padding; - property PopupMenu; - property Position; - property RotationAngle; - property RotationCenter; - property Scale; - property Size; - property StyleLookup; - property Visible; - property Width; - property OnClick; - property OnDblClick; - property OnMouseDown; - property OnMouseMove; - property OnMouseUp; - property OnMouseWheel; - property OnMouseEnter; - property OnMouseLeave; - end; - - TAuraWorkspace = class(TStyledControl) - private - FConnections: TArray; - // If the mouse is dragging the background (i.e., no child control is being dragged), - // then FPanning should be dragged. - FPanning: TSizeF; - // Added for panning implementation. - FIsPanning: Boolean; - FLastPanPos: TPointF; - // Added for zooming implementation. - FZoom: Single; - protected - procedure Paint; override; - procedure DoDeleteChildren; override; - // Add a zoom factor that, along with FPanning, zooms the children via the mouse wheel. - procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override; - procedure MouseMove(Shift: TShiftState; X, Y: Single); override; - procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override; - procedure MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean); override; - procedure DblClick; override; - public - constructor Create(AOwner: TComponent); override; - procedure BuildTree(const Root: IAstNode; const Position: TPointF; AMode: TVisualizationMode); - function GetChildrenMatrix(var Matrix: TMatrix; var Simple: Boolean): Boolean; override; - function Zoom(Factor: Single): Boolean; - - published - // Standard control properties - property Align; - property Anchors; - property ClipChildren default True; - property Cursor default crDefault; - property DragMode default TDragMode.dmManual; - property Enabled; - property Height; - property HelpContext; - property HelpKeyword; - property HelpType; - property Hint; - property HitTest default True; - property Locked; - property Margins; - property Opacity; - property Padding; - property PopupMenu; - property Position; - property RotationAngle; - property RotationCenter; - property Scale; - property Size; - property StyleLookup; - property Visible; - property Width; - property OnClick; - property OnDblClick; - property OnMouseDown; - property OnMouseMove; - property OnMouseUp; - property OnMouseWheel; - property OnMouseEnter; - property OnMouseLeave; - end; - TAstToAuraNodeVisitor = class(TInterfacedObject, IAstVisitor) public type @@ -192,11 +44,6 @@ type private const - // Pin Visuals - cPinSize = 10; - cDataPinColor = TAlphaColors.Dodgerblue; - cExecPinColor = TAlphaColors.Lightgreen; - // Node Layout cHorizontalPadding = 25; // Padding inside the node title bar cPinOffsetY = 18; // Vertical distance between pins @@ -290,560 +137,8 @@ uses System.Math, System.StrUtils, FMX.Platform, - Myc.Data.Scalar; - -type - // This visitor converts an AST expression subtree into a single string. - TAstToTextVisitor = class(TInterfacedObject, IAstVisitor) - public - { IAstVisitor } - function VisitConstant(const Node: IConstantNode): TDataValue; - function VisitIdentifier(const Node: IIdentifierNode): TDataValue; - function VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; - function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; - function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; - function VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; - function VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; - function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; - function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; - function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; - function VisitAssignment(const Node: IAssignmentNode): TDataValue; - function VisitIndexer(const Node: IIndexerNode): TDataValue; - function VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; - function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; - function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; - function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; - end; - -{ TAstToTextVisitor } - -function TAstToTextVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; -var - seriesStr, valueStr, lookbackStr: string; -begin - seriesStr := Node.Series.Accept(Self).AsText; - valueStr := Node.Value.Accept(Self).AsText; - lookbackStr := ''; - if Assigned(Node.Lookback) then - lookbackStr := ', ' + Node.Lookback.Accept(Self).AsText; - Result := Format('%s.add(%s%s)', [seriesStr, valueStr, lookbackStr]); -end; - -function TAstToTextVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue; -begin - Result := Node.Identifier.Name + ' := ' + Node.Value.Accept(Self).AsText; -end; - -function TAstToTextVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; -begin - var leftStr := Node.Left.Accept(Self).AsText; - var rightStr := Node.Right.Accept(Self).AsText; - Result := '(' + leftStr + ' ' + Node.Operator.ToString + ' ' + rightStr + ')'; -end; - -function TAstToTextVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; -begin - Result := '{...}'; -end; - -function TAstToTextVisitor.VisitConstant(const Node: IConstantNode): TDataValue; -begin - Result := Node.Value.ToString; -end; - -function TAstToTextVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; -begin - Result := 'new series(' + Node.Definition + ')'; -end; - -function TAstToTextVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; -var - i: Integer; - sb: TStringBuilder; - calleeStr: string; -begin - calleeStr := Node.Callee.Accept(Self).AsText; - - sb := TStringBuilder.Create; - try - sb.Append(calleeStr); - sb.Append('('); - for i := 0 to High(Node.Arguments) do - begin - sb.Append(Node.Arguments[i].Accept(Self).AsText); - if i < High(Node.Arguments) then - sb.Append(', '); - end; - sb.Append(')'); - Result := sb.ToString; - finally - sb.Free; - end; -end; - -function TAstToTextVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue; -begin - Result := Node.Name; -end; - -function TAstToTextVisitor.VisitIfExpression(const Node: IIfExpressionNode): TDataValue; -begin - Result := Node.Condition.Accept(Self).AsText; -end; - -function TAstToTextVisitor.VisitIndexer(const Node: IIndexerNode): TDataValue; -var - baseStr, indexStr: string; -begin - baseStr := Node.Base.Accept(Self).AsText; - indexStr := Node.Index.Accept(Self).AsText; - Result := Format('%s[%s]', [baseStr, indexStr]); -end; - -function TAstToTextVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; -var - i: Integer; - sb: TStringBuilder; -begin - sb := TStringBuilder.Create; - try - sb.Append(#$03BB + '('); - if Length(Node.Parameters) > 0 then - begin - for i := 0 to High(Node.Parameters) do - begin - // Correctly append the parameter name from the node. - sb.Append(Node.Parameters[i].Name); - if i < High(Node.Parameters) then - sb.Append(', '); - end; - end; - sb.Append(') => {...}'); - Result := sb.ToString; - finally - sb.Free; - end; -end; - -function TAstToTextVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; -var - baseStr: string; -begin - baseStr := Node.Base.Accept(Self).AsText; - Result := Format('%s.%s', [baseStr, Node.Member.Name]); -end; - -function TAstToTextVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; -var - seriesStr: string; -begin - // Get the string representation of the series identifier by visiting the node. - seriesStr := Node.Series.Accept(Self).AsText; - Result := Format('length(%s)', [seriesStr]); -end; - -function TAstToTextVisitor.VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; -begin - var condStr := Node.Condition.Accept(Self).AsText; - var thenStr := Node.ThenBranch.Accept(Self).AsText; - var elseStr := Node.ElseBranch.Accept(Self).AsText; - Result := Format('(%s ? %s : %s)', [condStr, thenStr, elseStr]); -end; - -function TAstToTextVisitor.VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; -begin - Result := Node.Operator.ToString + ' ' + Node.Right.Accept(Self).AsText; -end; - -function TAstToTextVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; -var - initStr: string; -begin - if Assigned(Node.Initializer) then - initStr := ' := ' + Node.Initializer.Accept(Self).AsText - else - initStr := ''; - Result := 'var ' + Node.Identifier.Name + initStr; -end; - -{ TPinConnection } - -constructor TPinConnection.Create(AOutputPin, AInputPin: TControl); -begin - OutputPin := AOutputPin; - InputPin := AInputPin; -end; - -{ TAuraNode } - -constructor TAuraNode.Create(AOwner: TComponent); -begin - inherited Create(AOwner); - FIsDragging := False; - - // Default border settings - FBorderColor := TAlphaColors.Gray; - FBorderWidth := 1; - FBorderRadius := 9; // Sharp corners by default - - // Default title settings - FTitle := 'Node'; - FTitleFont := TFont.Create; - FTitleFont.Family := 'Segoe UI'; - FTitleFont.Size := 11; - FTitleFont.Style := [TFontStyle.fsBold]; - FTitleFont.OnChanged := TitleFontChanged; - FTitleFontColor := TAlphaColors.Black; - - // Default state for frameless mode - FFrameless := False; - - Width := 80; - Height := 45; - - // The panel must be able to receive mouse events. - // HitTest := True; - // Clip children to the panel's bounds. - ClipChildren := True; -end; - -destructor TAuraNode.Destroy; -begin - FTitleFont.Free; - inherited; -end; - -procedure TAuraNode.Paint; -var - rect, titleRect: TRectF; - effectiveBorderWidth: Single; -begin - inherited; // Allow styled painting to occur first (if any) - - if FFrameless then - begin - Canvas.Fill.Kind := TBrushKind.Solid; - Canvas.Fill.Color := $20C0C0C0; // Transparent Silver - Canvas.Stroke.Kind := TBrushKind.None; - - // In frameless mode, draw a transparent background and ignore the border. - Canvas.FillRect(TRectF.Create(0, 0, Width, Height), 0, 0, [], 1.0); - effectiveBorderWidth := 0; - end - else - begin - // Custom painting for the border - if (FBorderWidth > 0) and (FBorderColor <> TAlphaColors.Null) then - begin - rect := TRectF.Create(0, 0, Width, Height); - // Inflate inwards so the border is fully visible within the control's bounds - rect.Inflate(-FBorderWidth / 2, -FBorderWidth / 2); - - Canvas.Stroke.Kind := TBrushKind.Solid; - Canvas.Stroke.Color := FBorderColor; - Canvas.Stroke.Thickness := FBorderWidth; - - if FFrameless then - begin - Canvas.Fill.Kind := TBrushKind.Solid; - Canvas.Fill.Color := $20C0C0C0; // Transparent Silver - Canvas.FillRect(rect, FBorderRadius, FBorderRadius, AllCorners, 1.0, TCornerType.Round); - end - else - Canvas.DrawRect(rect, FBorderRadius, FBorderRadius, AllCorners, 1.0, TCornerType.Round); - end; - effectiveBorderWidth := FBorderWidth; - end; - - // Draw the title - if not FTitle.IsEmpty then - begin - // Define the rectangle for the title at the top of the control - titleRect := TRectF.Create(0, effectiveBorderWidth, Width, effectiveBorderWidth + 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(titleRect, FTitle, False, 1.0, [], TTextAlign.Center, TTextAlign.Center); - end; -end; - -procedure TAuraNode.SetBorderColor(const Value: TAlphaColor); -begin - if FBorderColor <> Value then - begin - FBorderColor := Value; - Repaint; - end; -end; - -procedure TAuraNode.SetBorderRadius(const Value: Single); -begin - if FBorderRadius <> Value then - begin - FBorderRadius := Value; - Repaint; - end; -end; - -procedure TAuraNode.SetBorderWidth(const Value: Single); -begin - if FBorderWidth <> Value then - begin - FBorderWidth := Value; - Repaint; - end; -end; - -procedure TAuraNode.SetFrameless(const Value: Boolean); -begin - if FFrameless <> Value then - begin - FFrameless := Value; - Repaint; - end; -end; - -procedure TAuraNode.SetTitle(const Value: string); -begin - if FTitle <> Value then - begin - FTitle := Value; - Repaint; - end; -end; - -procedure TAuraNode.SetTitleFont(const Value: TFont); -begin - FTitleFont.Assign(Value); -end; - -procedure TAuraNode.SetTitleFontColor(const Value: TAlphaColor); -begin - if FTitleFontColor <> Value then - begin - FTitleFontColor := Value; - Repaint; - end; -end; - -procedure TAuraNode.TitleFontChanged(Sender: TObject); -begin - Repaint; -end; - -procedure TAuraNode.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); -begin - inherited; - if Button = TMouseButton.mbLeft then - begin - if ObjectAtPoint(LocalToScreen(TPointF.Create(X, Y))) = Self as IControl then - begin - FIsDragging := True; - FDownPos := TPointF.Create(X, Y); - Capture; - end; - end; -end; - -procedure TAuraNode.MouseMove(Shift: TShiftState; X, Y: Single); -begin - inherited; - - if FIsDragging then - begin - var deltaX := X - FDownPos.X; - var deltaY := Y - FDownPos.Y; - - Position.X := Position.X + deltaX; - Position.Y := Position.Y + deltaY; - - if ParentControl <> nil then - ParentControl.Repaint; - end; -end; - -procedure TAuraNode.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); -begin - inherited; - if (Button = TMouseButton.mbLeft) and (FIsDragging) then - begin - ReleaseCapture; - FIsDragging := False; - end; -end; - -{ TAuraWorkspace } - -constructor TAuraWorkspace.Create(AOwner: TComponent); -begin - inherited; - // Initialize zoom factor. - FZoom := 1.0; -end; - -procedure TAuraWorkspace.BuildTree(const Root: IAstNode; const Position: TPointF; AMode: TVisualizationMode); -begin - var connections := TList.Create; - try - Root.Accept(TAstToAuraNodeVisitor.Create(Self, Self, Position, connections, nil, AMode)); - FConnections := FConnections + connections.ToArray; - finally - connections.Free; - end; -end; - -procedure TAuraWorkspace.DoDeleteChildren; -begin - FConnections := nil; - inherited; -end; - -function TAuraWorkspace.GetChildrenMatrix(var Matrix: TMatrix; var Simple: Boolean): Boolean; -begin - // Combine scaling (zoom) and translation (pan) into a single matrix. - // The order is important: scale first, then translate. - Matrix := TMatrix.CreateScaling(FZoom, FZoom) * TMatrix.CreateTranslation(FPanning.cx, FPanning.cy); - Simple := (FZoom = 1.0) and (FPanning.cx = 0) and (FPanning.cy = 0); - Result := true; -end; - -procedure TAuraWorkspace.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); -begin - inherited; - // Start panning if left button is pressed on the background - if (Button = TMouseButton.mbLeft) and (ObjectAtPoint(LocalToScreen(TPointF.Create(X, Y))) = Self as IControl) then - begin - FIsPanning := True; - FLastPanPos := TPointF.Create(X, Y); - Cursor := crHandPoint; - end; -end; - -procedure TAuraWorkspace.MouseMove(Shift: TShiftState; X, Y: Single); -var - delta: TPointF; -begin - inherited; - - if FIsPanning then - begin - // Calculate movement delta - delta := TPointF.Create(X - FLastPanPos.X, Y - FLastPanPos.Y); - - // Update panning offset - FPanning.cx := FPanning.cx + delta.X; - FPanning.cy := FPanning.cy + delta.Y; - - // Store current position for next move - FLastPanPos := TPointF.Create(X, Y); - - // Trigger repaint to apply the new transformation matrix - RecalcAbsolute; - RecalcUpdateRect; - Repaint; - end; -end; - -procedure TAuraWorkspace.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); -begin - inherited; - // Stop panning - if (Button = TMouseButton.mbLeft) and (FIsPanning) then - begin - FIsPanning := False; - Cursor := crDefault; - end; -end; - -procedure TAuraWorkspace.MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean); -begin - inherited; - Handled := Zoom(IfThen(WheelDelta > 0, FZoom * 1.1, FZoom / 1.1)); -end; - -procedure TAuraWorkspace.DblClick; -begin - inherited; - Zoom(1.0); -end; - -procedure TAuraWorkspace.Paint; -var - connection: TPinConnection; - startPoint, endPoint, pinCenter: TPointF; - path: TPathData; - controlPoint1, controlPoint2: TPointF; - controlOffset: Single; -begin - inherited; - - Canvas.Stroke.Kind := TBrushKind.Solid; - Canvas.Stroke.Thickness := 2; - - // Go through all stored connections and draw them - for connection in FConnections do - begin - // Get the absolute coordinates of the pin centers - pinCenter := TPointF.Create(connection.OutputPin.Width / 2, connection.OutputPin.Height / 2); - var absoluteStart := connection.OutputPin.LocalToAbsolute(pinCenter); - - pinCenter := TPointF.Create(connection.InputPin.Width / 2, connection.InputPin.Height / 2); - var absoluteEnd := connection.InputPin.LocalToAbsolute(pinCenter); - - // Convert them to the local coordinates of the workspace - startPoint := AbsoluteToLocal(absoluteStart); - endPoint := AbsoluteToLocal(absoluteEnd); - - 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. - path := TPathData.Create; - try - controlOffset := max(25, 0.5 * endPoint.Distance(startPoint)); - - controlPoint1 := TPointF.Create(startPoint.X + controlOffset, startPoint.Y); - controlPoint2 := TPointF.Create(endPoint.X - controlOffset, endPoint.Y); - - path.MoveTo(startPoint); - path.CurveTo(controlPoint1, controlPoint2, endPoint); - - Canvas.DrawPath(path, 1.0); - finally - path.Free; - end; - end; -end; - -function TAuraWorkspace.Zoom(Factor: Single): Boolean; -var - MouseService: IFMXMouseService; -begin - Result := TPlatformServices.Current.SupportsPlatformService(IFMXMouseService, MouseService); - if Result then - begin - var MousePos := ScreenToLocal(MouseService.GetMousePos); - - // Clamp zoom level to a reasonable range - Factor := Max(0.2, Min(3.0, Factor)); - - // Adjust panning to keep the point under the cursor stationary - FPanning.cx := MousePos.X * (1 - Factor / FZoom) + FPanning.cx * (Factor / FZoom); - FPanning.cy := MousePos.Y * (1 - Factor / FZoom) + FPanning.cy * (Factor / FZoom); - - FZoom := Factor; - - // Trigger repaint to apply the new transformation matrix - RecalcAbsolute; - RecalcUpdateRect; - Repaint; - end; -end; + Myc.Data.Scalar, + Myc.Fmx.AstEditor.Text; { TAstToAuraNodeVisitor } @@ -861,7 +156,7 @@ begin FWorkspace := AWorkspace; FParentControl := AParentControl; FCurrentPos := AStartPosition; - FSpacing := TPointF.Create(20, 10); // Horizontal indent, Vertical spacing + FSpacing := TPointF.Create(20, 10); FConnections := AConnections; FCurrentExec := TList.Create(ACurrentExec); FLastResult.LayoutNode := nil; @@ -902,12 +197,10 @@ begin leftPins := TList.Create; rightPins := TList.Create; try - // 1. Collect all pins and sort them by alignment for control in Node.Controls do begin if (Pos('exec.', control.TagString) = 1) or (Pos('data.', control.TagString) = 1) then begin - // Use the pin's X position to determine if it's on the left or right side. if Pos('.in', control.TagString) > 0 then begin leftPins.Add(control); @@ -928,13 +221,10 @@ begin if maxPins = 0 then exit; - // 2. Calculate and set new node height. - // It must accommodate the pins plus vertical padding. reqHeight := cVerticalPadding + (maxPins - 1) * cPinOffsetY + cVerticalPadding; Node.Height := Max(Node.Height, reqHeight); - Node.Height := Max(cDefaultNodeHeight, Node.Height); // Ensure minimum height + Node.Height := Max(cDefaultNodeHeight, Node.Height); - // 3. Position left pins. They are arranged centered on the node. if numLeftPins > 0 then begin totalHeight := (numLeftPins - 1) * cPinOffsetY; @@ -943,7 +233,6 @@ begin leftPins[i].Position.Y := startY + i * cPinOffsetY - (cPinSize / 2); end; - // 4. Position right pins. if numRightPins > 0 then begin totalHeight := (numRightPins - 1) * cPinOffsetY; @@ -968,20 +257,17 @@ 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; @@ -1000,12 +286,9 @@ function TAstToAuraNodeVisitor.CreateNodeControl(const Title, Details: string): begin Result := BuildNodeControl(Title, Details); Result.Position.Point := FCurrentPos; - - // Advance vertical position for the next node FCurrentPos.Y := FCurrentPos.Y + Result.Height + FSpacing.Y; - FLastResult.LayoutNode := Result; - FLastResult.OutputPin := nil; // Default: a new node has no specific data output pin + FLastResult.OutputPin := nil; end; function TAstToAuraNodeVisitor.CreateOutput(ParentNode: TAuraNode): TControl; @@ -1018,7 +301,6 @@ begin Result := CreatePin(ParentNode, psTriangle, paLeft, cExecPinColor, 'exec.in'); if connect then begin - // Connect all open exits to this new entry for var curr in FCurrentExec do FConnections.Add(TPinConnection.Create(curr, Result)); FCurrentExec.Clear; @@ -1031,7 +313,6 @@ begin if cap <> '' then cap := '.' + cap; Result := CreatePin(ParentNode, psTriangle, paRight, cExecPinColor, 'exec.out' + cap); - FCurrentExec.Add(Result); end; @@ -1045,17 +326,13 @@ function TAstToAuraNodeVisitor.CreatePin( var posX, posY: Single; begin - // Vertical position is now set later by FinalizeNodeLayout. - // Set a temporary Y position. posY := 0; - // Calculate horizontal position if Alignment = paLeft then posX := 0 else // paRight posX := ParentNode.Width - cPinSize; - // Create the shape case Shape of psCircle: begin @@ -1077,12 +354,9 @@ begin exit; end; - // Common properties 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 := Tag; end; @@ -1160,7 +434,6 @@ var begin if (FMode = vmControlFlow) and TryGetDescr(Node, details) then begin - // Keep the simple view for control flow mode assignmentNode := CreateNodeControl('Assignment', details); CreateEntry(assignmentNode); CreateExit(assignmentNode, ''); @@ -1168,17 +441,11 @@ begin end else begin - // Create the main container node for the assignment. assignmentNode := CreateNodeControl('Assignment', Node.Identifier.Name); - - // Create a child visitor to render the value expression inside the assignment node. - // We pass an empty array for ACurrentExec so the child's execution flow doesn't connect to anything externally. var childStartPos := TPointF.Create(FSpacing.X, assignmentNode.TitleFont.Size * 1.8 + FSpacing.Y); var childVisitor := TAstToAuraNodeVisitor.Create(FWorkspace, assignmentNode, childStartPos, FConnections, [], FMode, FResolvedIdentifierCache); Node.Value.Accept(childVisitor); - - // Resize the parent assignmentNode to fit the child node(s). var maxRight: Single := 0; var maxBottom: Single := 0; for control in assignmentNode.Controls do @@ -1191,15 +458,10 @@ begin end; assignmentNode.Width := Max(assignmentNode.Width, maxRight + FSpacing.X); assignmentNode.Height := Max(assignmentNode.Height, maxBottom + FSpacing.Y); - CreateEntry(assignmentNode); CreateExit(assignmentNode, ''); - // An assignment expression can be used in another expression, so it needs a data output. FLastResult.OutputPin := CreateOutput(assignmentNode); - - // Finalize the layout of the parent node to correctly position its own pins. FinalizeNodeLayout(assignmentNode); - FCurrentPos.Y := assignmentNode.Position.Y + assignmentNode.Height + FSpacing.Y; end; Result := TDataValue.Void; @@ -1211,7 +473,7 @@ begin if (FMode = vmControlFlow) and TryGetDescr(Node, exprStr) then begin var exprNode := CreateNodeControl('Expression', exprStr); - FLastResult.OutputPin := CreateOutput(exprNode); // Provide an output for potential connection + FLastResult.OutputPin := CreateOutput(exprNode); FinalizeNodeLayout(exprNode); end else @@ -1227,19 +489,15 @@ begin begin leftResult := InputResults[0]; rightResult := InputResults[1]; - binaryExprNode := BuildNodeControl(Node.Operator.ToString, ''); binaryExprNode.TitleFont.Size := 20; - - inputPin1 := CreateInput(binaryExprNode, ''); // Caption is in Hint + inputPin1 := CreateInput(binaryExprNode, ''); inputPin2 := CreateInput(binaryExprNode, ''); outPin := CreateOutput(binaryExprNode); - if Assigned(leftResult.OutputPin) then FConnections.Add(TPinConnection.Create(leftResult.OutputPin, inputPin1)); if Assigned(rightResult.OutputPin) then FConnections.Add(TPinConnection.Create(rightResult.OutputPin, inputPin2)); - FinalizeNodeLayout(binaryExprNode); Result.LayoutNode := binaryExprNode; Result.OutputPin := outPin; @@ -1256,11 +514,9 @@ var begin childLastResult.LayoutNode := nil; childLastResult.OutputPin := nil; - - // Use the helper to create and populate the container node. blockNode := VisitContainerNode( - '', // No title for blocks + '', '', '', '', @@ -1272,20 +528,13 @@ begin begin for expression in Node.Expressions do expression.Accept(Visitor); - - // Capture the full result of the last expression inside the block. childLastResult := (Visitor as TAstToAuraNodeVisitor).FLastResult; end ); - - // Blocks are drawn without title and border, with a transparent fill. blockNode.Frameless := true; - - // Update the state of the main visitor FCurrentPos.Y := blockNode.Position.Y + blockNode.Height + FSpacing.Y; - FLastResult.LayoutNode := blockNode; // The layout node is the container itself. - FLastResult.OutputPin := childLastResult.OutputPin; // The connection pin is from the internal node. - + FLastResult.LayoutNode := blockNode; + FLastResult.OutputPin := childLastResult.OutputPin; Result := TDataValue.Void; end; @@ -1293,7 +542,6 @@ function TAstToAuraNodeVisitor.VisitConstant(const Node: IConstantNode): TDataVa var constantNode: TAuraNode; begin - // In both modes, a constant is a simple node with an output. constantNode := CreateNodeControl('Constant', Node.Value.ToString); FLastResult.OutputPin := CreateOutput(constantNode); FinalizeNodeLayout(constantNode); @@ -1317,19 +565,14 @@ var childLastResult: TAuraNodeResult; begin const pinNodeHeight = cPinSize + cVerticalPadding; - - // Step 1: Create container node containerNode := BuildNodeControl(Title, Details); containerNode.Position.Point := FCurrentPos; - Result := containerNode; // Set result early - + Result := containerNode; childStartPos := TPointF.Create(FSpacing.X, FSpacing.Y + max(IfThen(InPin <> '', pinNodeHeight, 0), IfThen(Title <> '', cVerticalPadding, 0))); - var currExec := FCurrentExec.ToArray; if InPin <> '' then begin - // Create a small entry node to define the start of the execution flow inside the container. var entryNode := BuildNodeControl(InPin, ''); entryNode.Parent := containerNode; entryNode.Position.Point := TPointF.Create(0, 0); @@ -1349,21 +592,13 @@ begin FinalizeNodeLayout(entryNode); end; end; - - // Step 2: Create child visitor and run the closure. - // The child visitor starts its execution flow from the entry node's exit pin. childVisitor := TAstToAuraNodeVisitor .Create(FWorkspace, containerNode, childStartPos, FConnections, FCurrentExec.ToArray, FMode, FResolvedIdentifierCache); ChildVisitorProc(childVisitor); - - // Capture the result from the child visitor. childLastResult := (childVisitor as TAstToAuraNodeVisitor).FLastResult; - FCurrentExec.Clear; FCurrentExec.AddRange((childVisitor as TAstToAuraNodeVisitor).FCurrentExec); - - // Step 3: Adjust the size of the container node to fit children. maxRight := 0; maxBottom := 0; for control in containerNode.Controls do @@ -1374,32 +609,22 @@ begin 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); - if OutPin <> '' then begin var exitNode := BuildNodeControl(OutPin, ''); exitNode.Parent := containerNode; exitNode.Height := pinNodeHeight; var exitPin := CreateEntry(exitNode); - - // Create and connect a data pin if the container returns a value. if HasResult and Assigned(childLastResult.OutputPin) then begin var dataResultPin := CreateInput(exitNode, 'Result'); FConnections.Add(TPinConnection.Create(childLastResult.OutputPin, dataResultPin)); end; - FinalizeNodeLayout(exitNode); - - // Enlarge for the exit node containerNode.Height := Max(containerNode.Height, maxBottom + FSpacing.Y + exitNode.Height); - exitNode.Position.Point := TPointF.Create(containerNode.Width - exitNode.Width, containerNode.Height - exitNode.Height); - if not IsExec then begin FCurrentExec.Clear; @@ -1408,7 +633,6 @@ begin else CreateExit(containerNode, '').Position.Y := containerNode.AbsoluteToLocal(exitNode.LocalToAbsolute(exitPin.Position.Point)).Y; end; - FinalizeNodeLayout(containerNode); end; @@ -1416,8 +640,6 @@ function TAstToAuraNodeVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): var seriesNode: TAuraNode; begin - // This node creates a new series. It has no inputs, only a data output. - // The definition is a string literal, so it's part of the node's title. seriesNode := CreateNodeControl('Create Series', Node.Definition); FLastResult.OutputPin := CreateOutput(seriesNode); FinalizeNodeLayout(seriesNode); @@ -1433,7 +655,7 @@ begin begin var funcCallNode := CreateNodeControl('FunctionCall', details); CreateEntry(funcCallNode); - FLastResult.OutputPin := CreateOutput(funcCallNode); // Function calls can return values + FLastResult.OutputPin := CreateOutput(funcCallNode); CreateExit(funcCallNode, ''); FinalizeNodeLayout(funcCallNode); end @@ -1443,7 +665,6 @@ begin inputExpressions[0] := Node.Callee; for var i := 0 to High(Node.Arguments) do inputExpressions[i + 1] := Node.Arguments[i]; - VisitOperatorNode( inputExpressions, function(const InputResults: TAuraNodeResultList): TAuraNodeResult @@ -1455,23 +676,18 @@ begin outPin: TControl; begin funcCallNode := BuildNodeControl('FunctionCall', ''); - CreateEntry(funcCallNode); calleePin := CreateInput(funcCallNode, 'Callee'); - SetLength(argPin, Length(Node.Arguments)); for i := 0 to High(argPin) do argPin[i] := CreateInput(funcCallNode, 'Arg' + i.ToString); - CreateExit(funcCallNode, ''); outPin := CreateOutput(funcCallNode); - if Assigned(InputResults[0].OutputPin) then FConnections.Add(TPinConnection.Create(InputResults[0].OutputPin, calleePin)); for i := 0 to High(Node.Arguments) do if Assigned(InputResults[i + 1].OutputPin) then FConnections.Add(TPinConnection.Create(InputResults[i + 1].OutputPin, argPin[i])); - FinalizeNodeLayout(funcCallNode); Result.LayoutNode := funcCallNode; Result.OutputPin := outPin; @@ -1488,7 +704,6 @@ var begin if Node.IsResolved then begin - // Use the resolved address as the key. if FResolvedIdentifierCache.TryGetValue(Node.Address, existingResult) then begin FLastResult := existingResult; @@ -1504,7 +719,6 @@ begin end else begin - // Unresolved identifiers are not cached and drawn each time. identifierNode := CreateNodeControl('Identifier', Node.Name); FLastResult.LayoutNode := identifierNode; FLastResult.OutputPin := CreateOutput(identifierNode); @@ -1523,7 +737,6 @@ var thenEndY, elseEndY: Single; begin startPosition := FCurrentPos; - var condStr: String; if (FMode = vmControlFlow) and TryGetDescr(Node, condStr) then begin @@ -1532,7 +745,6 @@ begin end else begin - // Use helper for the condition part ifNode := VisitOperatorNode( [Node.Condition], @@ -1544,13 +756,9 @@ begin begin condResult := InputResults[0]; ifNode := BuildNodeControl('If', ''); - conditionInputPin := CreateInput(ifNode, 'Condition'); - if Assigned(condResult.OutputPin) then FConnections.Add(TPinConnection.Create(condResult.OutputPin, conditionInputPin)); - - // Do not finalize here, more pins will be added. Result.LayoutNode := ifNode; Result.OutputPin := nil; end @@ -1558,12 +766,9 @@ begin end; CreateEntry(ifNode); conditionEndY := FCurrentPos.Y; - - // Visit the 'Then' and 'Else' execution branches, placing them to the right of the 'If' node. branchStartX := ifNode.Position.X + ifNode.Width + FSpacing.X; execPathes := TList.Create; try - // Then branch FCurrentExec.Clear; CreateExit(ifNode, 'Then'); FCurrentPos.X := branchStartX; @@ -1572,8 +777,6 @@ begin thenEndY := FCurrentPos.Y; FLastResult.LayoutNode := ifNode; execPathes.AddRange(FCurrentExec); - - // Else branch (if it exists) elseEndY := thenEndY; FCurrentExec.Clear; CreateExit(ifNode, 'Else'); @@ -1585,24 +788,16 @@ begin elseEndY := FCurrentPos.Y; end; execPathes.AddRange(FCurrentExec); - - // Finalize layout now that all pins are added. FinalizeNodeLayout(ifNode); - - // Finalize visitor state. FCurrentPos.X := startPosition.X; FCurrentPos.Y := Max(conditionEndY, elseEndY); FLastResult.LayoutNode := ifNode; - // This node is for control-flow only and never produces a data output. FLastResult.OutputPin := nil; - - // After a branch, the execution flow has diverged. FCurrentExec.Clear; FCurrentExec.AddRange(execPathes); finally execPathes.Free; end; - Result := TDataValue.Void; end; @@ -1627,16 +822,13 @@ begin begin indexerNode := BuildNodeControl('[]', ''); indexerNode.TitleFont.Size := 20; - basePin := CreateInput(indexerNode, 'Base'); indexPin := CreateInput(indexerNode, 'Index'); outPin := CreateOutput(indexerNode); - if Assigned(InputResults[0].OutputPin) then FConnections.Add(TPinConnection.Create(InputResults[0].OutputPin, basePin)); if Assigned(InputResults[1].OutputPin) then FConnections.Add(TPinConnection.Create(InputResults[1].OutputPin, indexPin)); - FinalizeNodeLayout(indexerNode); Result.LayoutNode := indexerNode; Result.OutputPin := outPin; @@ -1652,7 +844,6 @@ var lambdaNode: TAuraNode; i: Integer; begin - // Prepare the parameter string for the title paramStr := '('; if Length(Node.Parameters) > 0 then begin @@ -1661,8 +852,6 @@ begin paramStr := paramStr + ', ' + Node.Parameters[i].Name; end; paramStr := paramStr + ')'; - - // Use the helper to create and populate the container node. lambdaNode := VisitContainerNode( #$03BB, @@ -1673,16 +862,10 @@ begin true, procedure(const Visitor: IAstVisitor) begin Node.Body.Accept(Visitor); end ); - - // Update the state of the main visitor FCurrentPos.Y := lambdaNode.Position.Y + lambdaNode.Height + FSpacing.Y; - FLastResult.LayoutNode := lambdaNode; FLastResult.OutputPin := CreateOutput(lambdaNode); - - // Re-finalize layout after adding the data output pin. FinalizeNodeLayout(lambdaNode); - Result := TDataValue.Void; end; @@ -1690,7 +873,6 @@ function TAstToAuraNodeVisitor.VisitMemberAccess(const Node: IMemberAccessNode): var details: String; begin - // Implements the visualization for member access (e.g., series.field). if (FMode = vmControlFlow) and TryGetDescr(Node, details) then begin var memberNode := CreateNodeControl('Expression', details); @@ -1707,13 +889,10 @@ begin basePin, outPin: TControl; begin memberAccessNode := BuildNodeControl('Get ' + Node.Member.Name, ''); - basePin := CreateInput(memberAccessNode, 'Base'); outPin := CreateOutput(memberAccessNode); - if Assigned(InputResults[0].OutputPin) then FConnections.Add(TPinConnection.Create(InputResults[0].OutputPin, basePin)); - FinalizeNodeLayout(memberAccessNode); Result.LayoutNode := memberAccessNode; Result.OutputPin := outPin; @@ -1739,26 +918,19 @@ begin startPosition := FCurrentPos; inputResults := TAuraNodeResultList.Create; try - // 1. Visit all input nodes and collect their visual representations. for inputNode in InputExpressions do begin inputNode.Accept(Self); inputResults.Add(FLastResult); end; endY := FCurrentPos.Y; - - // 2. Determine the layout bounds of the input nodes. maxChildWidth := 0; for var res in inputResults do if Assigned(res.LayoutNode) then maxChildWidth := Max(maxChildWidth, res.LayoutNode.Position.X + res.LayoutNode.Width); - - // 3. Let the closure create the parent node (which also finalizes its layout). createResult := CreateParentProc(inputResults); parentNode := createResult.LayoutNode; Result := parentNode; - - // 4. Now position the parent node to the right of the inputs. var parentY: Single; if Alignment = vaCenter then begin @@ -1771,8 +943,6 @@ begin parentY := startPosition.Y; end; parentNode.Position.Point := TPointF.Create(maxChildWidth + FSpacing.X, parentY); - - // 5. Finalize visitor state for the next operation. FCurrentPos.Y := Max(endY, parentNode.Position.Y + parentNode.Height + FSpacing.Y); FLastResult.LayoutNode := parentNode; FLastResult.OutputPin := createResult.OutputPin; @@ -1783,29 +953,19 @@ end; function TAstToAuraNodeVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; begin - // Use the standard helper for operator-style nodes. VisitOperatorNode( - [Node.Series], // The single input for this operator is the series identifier. + [Node.Series], function(const InputResults: TAuraNodeResultList): TAuraNodeResult var lengthNode: TAuraNode; seriesPin, outPin: TControl; begin - // Create the visual node for the 'length' operation. lengthNode := BuildNodeControl('Series Length', ''); - - // Create an input pin for the series and an output pin for the result. seriesPin := CreateInput(lengthNode, 'Series'); outPin := CreateOutput(lengthNode); - - // Connect the output of the visited series identifier to our input pin. if Assigned(InputResults[0].OutputPin) then FConnections.Add(TPinConnection.Create(InputResults[0].OutputPin, seriesPin)); - - // Finalize the node's layout to correctly position the pins. FinalizeNodeLayout(lengthNode); - - // The result of this visitation is the new node and its output pin. Result.LayoutNode := lengthNode; Result.OutputPin := outPin; end @@ -1833,19 +993,16 @@ begin begin ternaryNode := BuildNodeControl('?', ''); ternaryNode.TitleFont.Size := 20; - condPin := CreateInput(ternaryNode, 'Condition'); thenPin := CreateInput(ternaryNode, 'Then'); elsePin := CreateInput(ternaryNode, 'Else'); outPin := CreateOutput(ternaryNode); - if Assigned(InputResults[0].OutputPin) then FConnections.Add(TPinConnection.Create(InputResults[0].OutputPin, condPin)); if Assigned(InputResults[1].OutputPin) then FConnections.Add(TPinConnection.Create(InputResults[1].OutputPin, thenPin)); if Assigned(InputResults[2].OutputPin) then FConnections.Add(TPinConnection.Create(InputResults[2].OutputPin, elsePin)); - FinalizeNodeLayout(ternaryNode); Result.LayoutNode := ternaryNode; Result.OutputPin := outPin; @@ -1878,13 +1035,10 @@ begin rightResult := InputResults[0]; unaryExprNode := BuildNodeControl(Node.Operator.ToString, ''); unaryExprNode.TitleFont.Size := 20; - inputPin := CreateInput(unaryExprNode, ''); outPin := CreateOutput(unaryExprNode); - if Assigned(rightResult.OutputPin) then FConnections.Add(TPinConnection.Create(rightResult.OutputPin, inputPin)); - FinalizeNodeLayout(unaryExprNode); Result.LayoutNode := unaryExprNode; Result.OutputPin := outPin; @@ -1902,7 +1056,6 @@ var begin if (FMode = vmControlFlow) and TryGetDescr(Node, details) then begin - // Keep the simple view for control flow mode varDeclNode := CreateNodeControl('VarDecl', details); CreateEntry(varDeclNode); CreateExit(varDeclNode, ''); @@ -1912,16 +1065,11 @@ begin begin if Assigned(Node.Initializer) then begin - // Create the main container node for the declaration. varDeclNode := CreateNodeControl('VarDecl', Node.Identifier.Name); - - // Create a child visitor to render the initializer expression inside the node. var childStartPos := TPointF.Create(FSpacing.X, varDeclNode.TitleFont.Size * 1.8 + FSpacing.Y); var childVisitor := TAstToAuraNodeVisitor.Create(FWorkspace, varDeclNode, childStartPos, FConnections, [], FMode, FResolvedIdentifierCache); Node.Initializer.Accept(childVisitor); - - // Resize the parent node to fit the child node(s). var maxRight: Single := 0; var maxBottom: Single := 0; for control in varDeclNode.Controls do @@ -1934,20 +1082,14 @@ begin end; varDeclNode.Width := Max(varDeclNode.Width, maxRight + FSpacing.X); varDeclNode.Height := Max(varDeclNode.Height, maxBottom + FSpacing.Y); - CreateEntry(varDeclNode); CreateExit(varDeclNode, ''); - // The new variable can be used in other expressions, so it needs a data output. FLastResult.OutputPin := CreateOutput(varDeclNode); - - // Finalize the layout of the parent node to correctly position its own pins. FinalizeNodeLayout(varDeclNode); - FCurrentPos.Y := varDeclNode.Position.Y + varDeclNode.Height + FSpacing.Y; end else begin - // Case without initializer is a simple sequential node. varDeclNode := CreateNodeControl('VarDecl', Node.Identifier.Name); CreateEntry(varDeclNode); CreateExit(varDeclNode, '');