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, '');