unit Myc.Fmx.AstVisualizer deprecated; interface uses System.SysUtils, System.Classes, System.UITypes, System.Types, System.Math.Vectors, System.Generics.Collections, System.Generics.Defaults, FMX.Types, FMX.Controls, FMX.Objects, FMX.Graphics, FMX.StdCtrls, Myc.Data.Value, Myc.Ast.Nodes, Myc.Ast.Scope, Myc.Ast.Types, Myc.Ast.Visitor, Myc.Fmx.AstEditor.Node, Myc.Fmx.AstEditor.Workspace; type // Visits the AST and creates visual TAuraNode representations on a TAuraWorkspace. TAstVisualizer = class(TAstVisitor, IAstVisualizer) private FCurrentPos: TPointF; FExprDepth: Integer; FWorkspace: TAuraWorkspace; FNodeVerticalGap: Single; FParentControl: TControl; // Helper to create and initialize a TAuraNode for a given AST node. function CreateNode(const Node: IAstNode): TAuraNode; function AddLabel(const ParentNode: TAuraNode; const Txt: String): TLabel; function AddTitleLabel(const ParentNode: TAuraNode; const Title: string): Single; // Adjusts the size of the parent node to fit its children. procedure AdjustParentNodeSize(const ParentNode: TAuraNode; const Children: TArray); // Generic processing logic for AST nodes with children. Returns the created TAuraNode. function ProcessStub(const ATitle: string; const ANode: IAstNode; const AChildren: TArray): TAuraNode; // Calls Accept on the node and returns the resulting TAuraNode. function CallAccept(const Node: IAstNode): TAuraNode; function GetCurrentPos: TPointF; function GetExprDepth: Integer; function GetParentControl: TControl; procedure SetCurrentPos(const Value: TPointF); procedure SetExprDepth(const Value: Integer); procedure SetParentControl(const Value: TControl); protected // Visitor implementations for each AST node type. function VisitConstant(const Node: IConstantNode): TDataValue; override; function VisitIdentifier(const Node: IIdentifierNode): TDataValue; override; function VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; override; function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; override; function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; override; function VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; override; function VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; override; function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; override; function VisitMacroExpansionNode(const Node: IMacroExpansionNode): TDataValue; override; function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; override; function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; override; function VisitAssignment(const Node: IAssignmentNode): TDataValue; override; function VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue; override; function VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue; override; function VisitUnquote(const Node: IUnquoteNode): TDataValue; override; function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue; override; function VisitIndexer(const Node: IIndexerNode): TDataValue; override; function VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; override; function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; override; function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; override; function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; override; function VisitRecurNode(const Node: IRecurNode): TDataValue; override; public constructor Create( AWorkspace: TAuraWorkspace; AParentControl: TControl; const AStartPosition: TPointF; ANodeVerticalGap: Single = 15; AExprDepth: Integer = 0 ); destructor Destroy; override; function Clone: IAstVisualizer; class function CreateVisitor(AWorkspace: TAuraWorkspace; const StartPosition: TPointF): IAstVisitor; property CurrentPos: TPointF read GetCurrentPos write SetCurrentPos; property ExprDepth: Integer read GetExprDepth write SetExprDepth; property ParentControl: TControl read GetParentControl write SetParentControl; end; implementation uses System.Math, System.StrUtils, FMX.Platform, FMX.Layouts, // For TLayout Myc.Data.Scalar, Myc.Ast.Binding, // For TBound...Node classes Myc.Fmx.AstEditor.Text; const cChildAreaTopPadding = 5; cNodeVerticalGap = 10; // Reduced gap { TAstVisualizer } constructor TAstVisualizer.Create( AWorkspace: TAuraWorkspace; AParentControl: TControl; const AStartPosition: TPointF; ANodeVerticalGap: Single = 15; AExprDepth: Integer = 0 ); begin inherited Create; FWorkspace := AWorkspace; FParentControl := AParentControl; FCurrentPos := AStartPosition; FNodeVerticalGap := ANodeVerticalGap; FExprDepth := AExprDepth; end; destructor TAstVisualizer.Destroy; begin inherited; end; function TAstVisualizer.AddLabel(const ParentNode: TAuraNode; const Txt: String): TLabel; begin Result := TLabel.Create(ParentNode); // Owner is the node itself Result.Parent := ParentNode; Result.Position.Point := TPoint.Create(cNodePadding, cNodePadding); Result.Margins.Left := cNodePadding; Result.Margins.Right := cNodePadding; Result.Margins.Top := cTitleTopPadding; Result.Margins.Bottom := cTitleBottomPadding; Result.WordWrap := false; Result.AutoSize := True; // Let label determine its own size initially (do NOT use Align, because it will override AutoSize) Result.HitTest := False; // Title label should not intercept clicks intended for the node Result.StyledSettings := Result.StyledSettings - [TStyledSetting.Style]; // Tell FMX not to manage Font.Style via styles Result.Text := Txt; Result.ApplyStyleLookup; end; class function TAstVisualizer.CreateVisitor(AWorkspace: TAuraWorkspace; const StartPosition: TPointF): IAstVisitor; begin Result := TAstVisualizer.Create(AWorkspace, AWorkspace, StartPosition); end; // Creates a TAuraNode, sets its parent and position. Title is added separately. function TAstVisualizer.CreateNode(const Node: IAstNode): TAuraNode; begin Result := TAuraNode.Create(FWorkspace); // Owner is always the workspace Result.Parent := FParentControl; // Set visual parent Result.Position.Point := FCurrentPos; // Set a default minimum size, will be adjusted later Result.Width := cMinNodeWidth; Result.Height := cMinNodeHeight; var cn: cardinal := $ea - (7 * FExprDepth); var c: cardinal := $ff000000 or (cn shl 16) or (cn shl 8) or cn; Result.BackgroundColor := c; // Store the original AST node reference if needed later // Result.TagObject := TObject(Node); end; // Creates and adds a TLabel for the title to the given ParentNode. // Returns the calculated height of the title area (label + margins). function TAstVisualizer.AddTitleLabel(const ParentNode: TAuraNode; const Title: string): Single; var titleLabel: TLabel; begin Result := 0; if Title.IsEmpty then Exit; titleLabel := AddLabel(ParentNode, Title); titleLabel.Font.Style := titleLabel.Font.Style + [TFontStyle.fsBold]; // Force immediate resize calculation for height measurement (usually not needed with Align=Top + AutoSize) // titleLabel.ApplyStyleLookup; titleLabel.Realign; Result := titleLabel.Height + titleLabel.Margins.Top + titleLabel.Margins.Bottom; end; // Adjusts the parent node's size after all children have been added, // considering both labels and child AuraNodes. procedure TAstVisualizer.AdjustParentNodeSize(const ParentNode: TAuraNode; const Children: TArray); var i: Integer; maxLabelWidth: Single; maxChildNodeWidth: Single; totalChildHeight: Single; childControl: TControl; isLabel: Boolean; requiredWidth: Single; begin maxLabelWidth := 0; maxChildNodeWidth := 0; totalChildHeight := cNodePadding; // Start with top padding // Iterate through FMX children of the ParentNode to calculate required size for i := 0 to ParentNode.ControlsCount - 1 do begin childControl := ParentNode.Controls[i]; isLabel := childControl is TLabel; // Calculate required width for this child (including its margins) requiredWidth := childControl.Width + childControl.Margins.Left + childControl.Margins.Right; if isLabel then begin maxLabelWidth := Max(maxLabelWidth, requiredWidth); end // Also consider TAuraNode children for width calculation, assuming they also have padding else if childControl is TAuraNode then begin // Child nodes are positioned starting at cNodePadding, so their width relative to parent border is Width + cNodePadding // We compare against the required width including parent padding maxChildNodeWidth := Max(maxChildNodeWidth, childControl.Width); end; // Height calculation based on the last child's bottom edge relative to parent's top totalChildHeight := Max(totalChildHeight, childControl.Position.Y + childControl.Height + childControl.Margins.Bottom); end; // Set parent size including padding // Ensure minimum size is met AND the widest label fits AND the widest child node fits ParentNode.Width := Max( cMinNodeWidth, // At least min width Max( maxLabelWidth, // At least widest label width + padding (already included in label width calc) maxChildNodeWidth + 2 * cNodePadding ) ); // At least widest child node width + padding // Adjust height based on the bottom-most control + bottom padding if ParentNode.ControlsCount > 0 then ParentNode.Height := Max(cMinNodeHeight, totalChildHeight + cNodePadding) else // Empty node ParentNode.Height := cMinNodeHeight; end; // Calls Accept on the node and returns the resulting TAuraNode. function TAstVisualizer.CallAccept(const Node: IAstNode): TAuraNode; var DataValue: TDataValue; begin if not Assigned(Node) then Exit(nil); // Handle nil input node gracefully DataValue := Node.Accept(Self); // Check if the result is actually a TAuraNode before casting if DataValue.Kind = TDataValueKind.vkGeneric then begin // It's recommended to add a type check here if possible, // although AsGeneric doesn't inherently support it. // Assuming the visitor correctly returns TAuraNode wrapped in TDataValue. Result := DataValue.AsGeneric; end else begin // The visitor returned something unexpected. // Decide how to handle this: raise an exception, return nil, log an error? // For now, return nil as the caller might handle it. Result := nil; // Consider: Log.d('TAstVisualizer.CallAccept', 'Expected TAuraNode, got ' + DataValue.Kind.ToString); // Consider: raise EMyAstError.Create('Visitor did not return a TAuraNode'); end; end; function TAstVisualizer.Clone: IAstVisualizer; begin Result := TAstVisualizer.Create(FWorkspace, FParentControl, FCurrentPos, FNodeVerticalGap, FExprDepth); end; function TAstVisualizer.GetCurrentPos: TPointF; begin Result := FCurrentPos; end; function TAstVisualizer.GetExprDepth: Integer; begin Result := FExprDepth; end; function TAstVisualizer.GetParentControl: TControl; begin Result := FParentControl; end; // Generic processing logic for AST nodes with children. Returns the created TAuraNode. function TAstVisualizer.ProcessStub(const ATitle: string; const ANode: IAstNode; const AChildren: TArray): TAuraNode; var auraNode: TAuraNode; savedParent: TControl; savedPos: TPointF; child: IAstNode; titleLabelHeight: Single; begin auraNode := CreateNode(ANode); // Create node without title auraNode.BeginUpdate; try // Add Title Label titleLabelHeight := AddTitleLabel(auraNode, ATitle); savedParent := FParentControl; savedPos := FCurrentPos; FParentControl := auraNode; // Start position for children inside the new node, below the title FCurrentPos := TPointF.Create(cNodePadding, titleLabelHeight + cChildAreaTopPadding); // Visit children for child in AChildren do begin if Assigned(child) then // Ensure child node exists {childNode :=} CallAccept(child); // Use CallAccept, store result // childNode is not explicitly used here for layout *after* the call, // but its creation and positioning happened *within* CallAccept. end; FParentControl := savedParent; // Restore parent FCurrentPos := savedPos; // Restore position for siblings AdjustParentNodeSize(auraNode, AChildren); finally auraNode.EndUpdate; end; // Move current position down for the next sibling node in the outer scope // Use the actual size of the created node FCurrentPos.Y := savedPos.Y + auraNode.Height + FNodeVerticalGap; Result := auraNode; // Return the created node end; procedure TAstVisualizer.SetCurrentPos(const Value: TPointF); begin FCurrentPos := Value; end; procedure TAstVisualizer.SetExprDepth(const Value: Integer); begin FExprDepth := Value; end; procedure TAstVisualizer.SetParentControl(const Value: TControl); begin FParentControl := Value; end; // --- Visitor Implementations --- function TAstVisualizer.VisitConstant(const Node: IConstantNode): TDataValue; begin var auraNode := TAuraConstantNode.CreateConstantNode(FWorkspace, Node, Self); // Move current position down for the next sibling node FCurrentPos.Y := FCurrentPos.Y + auraNode.Height + FNodeVerticalGap; Result := TDataValue.FromGeneric(auraNode); // Return the created node end; function TAstVisualizer.VisitIdentifier(const Node: IIdentifierNode): TDataValue; var auraNode: TAuraIdentifierNode; // Use specific type begin // Create the specific node type using its factory auraNode := TAuraIdentifierNode.CreateIdentifierNode(FWorkspace, Node, Self); // Move current position down for the next sibling node FCurrentPos.Y := FCurrentPos.Y + auraNode.Height + FNodeVerticalGap; Result := TDataValue.FromGeneric(auraNode); // Return as generic TAuraNode end; function TAstVisualizer.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; var auraNode: TAuraBinaryExpressionNode; // Use specific type savedPos: TPointF; begin // Store position before creating the node savedPos := FCurrentPos; // Create the specific node type using its factory auraNode := TAuraBinaryExpressionNode.CreateBinaryExpressionNode(FWorkspace, Node, Self); // Move current position down for the next sibling node // Use the position saved *before* creating this node, plus the node's final height FCurrentPos.Y := savedPos.Y + auraNode.Height + FNodeVerticalGap; Result := TDataValue.FromGeneric(auraNode); // Return as generic TAuraNode end; function TAstVisualizer.VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; var auraNode: TAuraNode; begin auraNode := ProcessStub('Unary Op: ' + Node.Operator.ToString, Node, [Node.Right]); Result := TDataValue.FromGeneric(auraNode); end; function TAstVisualizer.VisitIfExpression(const Node: IIfExpressionNode): TDataValue; var children: TArray; auraNode: TAuraNode; begin children := [Node.Condition, Node.ThenBranch]; if Assigned(Node.ElseBranch) then children := children + [Node.ElseBranch]; auraNode := ProcessStub('If', Node, children); Result := TDataValue.FromGeneric(auraNode); end; function TAstVisualizer.VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; var auraNode: TAuraNode; conditionNode: TAuraNode; qMarkLabel: TLabel; thenNode: TAuraNode; colonLabel: TLabel; elseNode: TAuraNode; savedParent: TControl; savedPos: TPointF; currentX: Single; // Track horizontal position maxHeightInRow: Single; // Track max height for row alignment yPos: Single; // Y position for elements in this row begin // Create the main node, frameless auraNode := CreateNode(Node); auraNode.BeginUpdate; inc(FExprDepth); try auraNode.Frameless := True; // Start position for children inside the node currentX := cNodePadding; yPos := cNodePadding; // Initial Y position maxHeightInRow := 0; // 1. Condition Node savedParent := FParentControl; savedPos := FCurrentPos; FParentControl := auraNode; FCurrentPos := TPointF.Create(currentX, yPos); conditionNode := CallAccept(Node.Condition); if Assigned(conditionNode) then begin currentX := conditionNode.Position.X + conditionNode.Width + conditionNode.Margins.Right + cNodePadding; maxHeightInRow := Max(maxHeightInRow, conditionNode.Height + conditionNode.Margins.Top + conditionNode.Margins.Bottom); end; // 2. Question Mark Label qMarkLabel := AddLabel(auraNode, '?'); qMarkLabel.Position.Point := TPointF.Create(currentX, yPos); currentX := qMarkLabel.Position.X + qMarkLabel.Width + qMarkLabel.Margins.Right + cNodePadding; maxHeightInRow := Max(maxHeightInRow, qMarkLabel.Height + qMarkLabel.Margins.Top + qMarkLabel.Margins.Bottom); // 3. Then Branch Node // FParentControl is already auraNode FCurrentPos := TPointF.Create(currentX, yPos); thenNode := CallAccept(Node.ThenBranch); if Assigned(thenNode) then begin currentX := thenNode.Position.X + thenNode.Width + thenNode.Margins.Right + cNodePadding; maxHeightInRow := Max(maxHeightInRow, thenNode.Height + thenNode.Margins.Top + thenNode.Margins.Bottom); end; // 4. Colon Label colonLabel := AddLabel(auraNode, ':'); colonLabel.Position.Point := TPointF.Create(currentX, yPos); currentX := colonLabel.Position.X + colonLabel.Width + colonLabel.Margins.Right + cNodePadding; maxHeightInRow := Max(maxHeightInRow, colonLabel.Height + colonLabel.Margins.Top + colonLabel.Margins.Bottom); // 5. Else Branch Node // FParentControl is already auraNode FCurrentPos := TPointF.Create(currentX, yPos); elseNode := CallAccept(Node.ElseBranch); if Assigned(elseNode) then begin currentX := elseNode.Position.X + elseNode.Width + elseNode.Margins.Right; maxHeightInRow := Max(maxHeightInRow, elseNode.Height + elseNode.Margins.Top + elseNode.Margins.Bottom); end; FParentControl := savedParent; FCurrentPos := savedPos; // --- Adjust parent size --- auraNode.Width := Max(cMinNodeWidth, currentX + cNodePadding); auraNode.Height := Max(cMinNodeHeight, maxHeightInRow + 2 * cNodePadding); // --- Optional Vertical Centering --- var centerOffsetY: Single := (auraNode.Height / 2); if Assigned(conditionNode) then conditionNode.Position.Y := centerOffsetY - (conditionNode.Height + conditionNode.Margins.Top + conditionNode.Margins.Bottom) / 2 + conditionNode.Margins.Top; if Assigned(qMarkLabel) then qMarkLabel.Position.Y := centerOffsetY - (qMarkLabel.Height + qMarkLabel.Margins.Top + qMarkLabel.Margins.Bottom) / 2 + qMarkLabel.Margins.Top; if Assigned(thenNode) then thenNode.Position.Y := centerOffsetY - (thenNode.Height + thenNode.Margins.Top + thenNode.Margins.Bottom) / 2 + thenNode.Margins.Top; if Assigned(colonLabel) then colonLabel.Position.Y := centerOffsetY - (colonLabel.Height + colonLabel.Margins.Top + colonLabel.Margins.Bottom) / 2 + colonLabel.Margins.Top; if Assigned(elseNode) then elseNode.Position.Y := centerOffsetY - (elseNode.Height + elseNode.Margins.Top + elseNode.Margins.Bottom) / 2 + elseNode.Margins.Top; // --- End Optional Vertical Centering --- finally dec(FExprDepth); auraNode.EndUpdate; end; // Move current position down for the next sibling node in the outer scope FCurrentPos.Y := savedPos.Y + auraNode.Height + FNodeVerticalGap; Result := TDataValue.FromGeneric(auraNode); end; function TAstVisualizer.VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; var auraNode: TAuraNode; savedParent: TControl; savedPos: TPointF; savedExprDepth: Integer; i: Integer; paramLabel: TLabel; lambdaLabel: TLabel; // Label for the lambda symbol titleParamHeight: Single; // Height required for the combined title/param line currentX: Single; // Horizontal position tracker for title/params maxHeightInTitleRow: Single; // Max height in the first row lambdaSymbol: string; bodyNode: TAuraNode; begin lambdaSymbol := WideChar($03BB); // Unicode for Greek Small Letter Lambda auraNode := CreateNode(Node); // Create standard framed node auraNode.BeginUpdate; try auraNode.Frameless := False; auraNode.BackgroundColor := $090000ff; // --- Create Title/Parameter Line --- currentX := cNodePadding; maxHeightInTitleRow := 0; var yPos := cNodePadding; // Y position for the first row // 1. Lambda Symbol Label (Title) lambdaLabel := AddLabel(auraNode, lambdaSymbol); lambdaLabel.Position.Point := TPointF.Create(currentX, yPos); lambdaLabel.Font.Style := lambdaLabel.Font.Style + [TFontStyle.fsBold]; // Make symbol bold currentX := lambdaLabel.Position.X + lambdaLabel.Width + lambdaLabel.Margins.Right + cNodePadding / 2; // Add small gap maxHeightInTitleRow := Max(maxHeightInTitleRow, lambdaLabel.Height + lambdaLabel.Margins.Top + lambdaLabel.Margins.Bottom); // 2. Add Parameter Labels (inline) if High(Node.Parameters) >= 0 then begin // Add parenthesis var openParen := AddLabel(auraNode, '('); openParen.Font.Style := openParen.Font.Style - [TFontStyle.fsBold]; // Normal style openParen.Position.Point := TPointF.Create(currentX, yPos); currentX := openParen.Position.X + openParen.Width + openParen.Margins.Right + cNodePadding / 2; maxHeightInTitleRow := Max(maxHeightInTitleRow, openParen.Height + openParen.Margins.Top + openParen.Margins.Bottom); for i := 0 to High(Node.Parameters) do begin paramLabel := AddLabel(auraNode, Node.Parameters[i].Name); paramLabel.Font.Style := paramLabel.Font.Style - [TFontStyle.fsBold]; // Normal style paramLabel.Position.Point := TPointF.Create(currentX, yPos); currentX := paramLabel.Position.X + paramLabel.Width + paramLabel.Margins.Right + IfThen(i < High(Node.Parameters), cNodePadding / 2, 0); // Gap or no gap maxHeightInTitleRow := Max(maxHeightInTitleRow, paramLabel.Height + paramLabel.Margins.Top + paramLabel.Margins.Bottom); // Add comma if not the last parameter if i < High(Node.Parameters) then begin var commaLabel := AddLabel(auraNode, ','); commaLabel.Font.Style := commaLabel.Font.Style - [TFontStyle.fsBold]; // Normal style commaLabel.Position.Point := TPointF.Create(currentX, yPos); currentX := commaLabel.Position.X + commaLabel.Width + commaLabel.Margins.Right + cNodePadding / 2; maxHeightInTitleRow := Max(maxHeightInTitleRow, commaLabel.Height + commaLabel.Margins.Top + commaLabel.Margins.Bottom); end; end; // Add closing parenthesis var closeParen := AddLabel(auraNode, ')'); closeParen.Font.Style := closeParen.Font.Style - [TFontStyle.fsBold]; // Normal style closeParen.Position.Point := TPointF.Create(currentX, yPos); maxHeightInTitleRow := Max(maxHeightInTitleRow, closeParen.Height + closeParen.Margins.Top + closeParen.Margins.Bottom); end else begin // No parameters, add empty () for clarity var emptyParens := AddLabel(auraNode, '()'); emptyParens.Font.Style := emptyParens.Font.Style - [TFontStyle.fsBold]; emptyParens.Position.Point := TPointF.Create(currentX, yPos); maxHeightInTitleRow := Max(maxHeightInTitleRow, emptyParens.Height + emptyParens.Margins.Top + emptyParens.Margins.Bottom); end; titleParamHeight := maxHeightInTitleRow; // Total height needed for the first row // --- Optional Vertical Centering for Title/Param Row --- var centerOffsetYTitleRow: Single := yPos + titleParamHeight / 2; for i := 0 to auraNode.ControlsCount - 1 do // Iterate controls created so far for the title row begin var ctrl := auraNode.Controls[i]; ctrl.Position.Y := centerOffsetYTitleRow - (ctrl.Height + ctrl.Margins.Top + ctrl.Margins.Bottom) / 2 + ctrl.Margins.Top; end; // --- End Centering --- // 3. Visit Body savedParent := FParentControl; savedPos := FCurrentPos; savedExprDepth := FExprDepth; try FParentControl := auraNode; // Body node is child of auraNode // Start position for the body, below the title/parameter line FCurrentPos := TPointF.Create(cNodePadding, yPos + titleParamHeight + cChildAreaTopPadding); // Start below the calculated height FExprDepth := 0; bodyNode := CallAccept(Node.Body); // Visit the body, get the node bodyNode.Frameless := false; finally FParentControl := savedParent; // Restore parent for siblings FCurrentPos := savedPos; // Restore position for siblings FExprDepth := savedExprDepth; end; AdjustParentNodeSize(auraNode, [Node.Body]); // Adjust size based on title, params, and body finally auraNode.EndUpdate; end; // Move current position down for the next sibling node FCurrentPos.Y := savedPos.Y + auraNode.Height + FNodeVerticalGap; // Use savedPos.Y Result := TDataValue.FromGeneric(auraNode); // Return the created node end; function TAstVisualizer.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; var auraNode: TAuraNode; savedParent: TControl; savedPos: TPointF; i: Integer; calleeNode: TAuraNode; // Use TAuraNode argNode: TAuraNode; // Use TAuraNode parenLabel, commaLabel: TLabel; // Layout variables yPos: Single; // Current vertical position for the row currentX: Single; // Horizontal position tracker maxHeightInRow: Single; // Max height for row alignment argStartIndex: Integer; // Index of first argument-related control begin auraNode := CreateNode(Node); // Create standard framed node auraNode.Frameless := False; // Function calls generally have a frame auraNode.BeginUpdate; inc(FExprDepth); try yPos := cNodePadding; // Start directly with padding currentX := cNodePadding; maxHeightInRow := 0; // --- Visit Callee --- savedParent := FParentControl; savedPos := FCurrentPos; FParentControl := auraNode; FCurrentPos := TPointF.Create(currentX, yPos); // Position for callee calleeNode := CallAccept(Node.Callee); // Create callee's visual node if Assigned(calleeNode) then begin // Position callee explicitly (redundant if FCurrentPos worked, but safe) calleeNode.Position.Point := TPointF.Create(currentX, yPos); currentX := calleeNode.Position.X + calleeNode.Width + calleeNode.Margins.Right + cNodePadding / 2; // Update X pos maxHeightInRow := Max(maxHeightInRow, calleeNode.Height + calleeNode.Margins.Top + calleeNode.Margins.Bottom); end else currentX := cNodePadding; // Start parens from left if no callee // --- Add Arguments (Horizontal Layout) --- // Add opening parenthesis parenLabel := AddLabel(auraNode, '('); parenLabel.Font.Style := parenLabel.Font.Style - [TFontStyle.fsBold]; // Normal style parenLabel.Position.Point := TPointF.Create(currentX, yPos); currentX := parenLabel.Position.X + parenLabel.Width + parenLabel.Margins.Right + cNodePadding / 2; maxHeightInRow := Max(maxHeightInRow, parenLabel.Height + parenLabel.Margins.Top + parenLabel.Margins.Bottom); for i := 0 to High(Node.Arguments) do begin // Set position for the argument node FCurrentPos := TPointF.Create(currentX, yPos); // FParentControl is already auraNode argNode := CallAccept(Node.Arguments[i]); // Create argument node if Assigned(argNode) then begin // Position it correctly (redundant, but safe) argNode.Position.Point := TPointF.Create(currentX, yPos); currentX := argNode.Position.X + argNode.Width + argNode.Margins.Right + IfThen(i < High(Node.Arguments), cNodePadding / 2, 0); // Gap or no gap after arg maxHeightInRow := Max(maxHeightInRow, argNode.Height + argNode.Margins.Top + argNode.Margins.Bottom); // Add comma if not the last argument if i < High(Node.Arguments) then begin commaLabel := AddLabel(auraNode, ','); commaLabel.Font.Style := commaLabel.Font.Style - [TFontStyle.fsBold]; // Normal style commaLabel.Position.Point := TPointF.Create(currentX, yPos); currentX := commaLabel.Position.X + commaLabel.Width + commaLabel.Margins.Right + cNodePadding / 2; maxHeightInRow := Max(maxHeightInRow, commaLabel.Height + commaLabel.Margins.Top + commaLabel.Margins.Bottom); end; end else // Should not happen begin currentX := currentX + cMinNodeWidth + cNodePadding; maxHeightInRow := Max(maxHeightInRow, cMinNodeHeight); end; end; // Add closing parenthesis parenLabel := AddLabel(auraNode, ')'); parenLabel.Font.Style := parenLabel.Font.Style - [TFontStyle.fsBold]; // Normal style parenLabel.Position.Point := TPointF.Create(currentX, yPos); currentX := parenLabel.Position.X + parenLabel.Width + parenLabel.Margins.Right; // Final X position maxHeightInRow := Max(maxHeightInRow, parenLabel.Height + parenLabel.Margins.Top + parenLabel.Margins.Bottom); // --- Vertical Centering for the single row --- var centerOffsetYRow: Single := yPos + maxHeightInRow / 2; argStartIndex := 0; // Start check from first control for i := argStartIndex to auraNode.ControlsCount - 1 do // Iterate ALL controls in the row begin var ctrl := auraNode.Controls[i]; // Adjust Y based on calculated center and control's own height/margins ctrl.Position.Y := centerOffsetYRow - (ctrl.Height + ctrl.Margins.Top + ctrl.Margins.Bottom) / 2 + ctrl.Margins.Top; end; // --- End Centering --- yPos := yPos + maxHeightInRow; // Update yPos based on row height for final size calculation FParentControl := savedParent; // Restore parent for siblings FCurrentPos := savedPos; // Restore position for siblings // --- Adjust Size --- auraNode.Width := Max(cMinNodeWidth, currentX + cNodePadding); // Width determined by final currentX auraNode.Height := Max(cMinNodeHeight, yPos + cNodePadding); // Height determined by final yPos finally dec(FExprDepth); auraNode.EndUpdate; end; // Move current position down for the next sibling node FCurrentPos.Y := savedPos.Y + auraNode.Height + FNodeVerticalGap; // Use savedPos.Y Result := TDataValue.FromGeneric(auraNode); // Return the created node end; function TAstVisualizer.VisitMacroExpansionNode(const Node: IMacroExpansionNode): TDataValue; var auraNode: TAuraNode; begin // Visualize the expanded body auraNode := ProcessStub('Macro Expansion', Node, [Node.ExpandedBody]); Result := TDataValue.FromGeneric(auraNode); end; function TAstVisualizer.VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; var auraNode: TAuraNode; savedParent: TControl; savedPos: TPointF; expr: IAstNode; prefixLabel: TLabel; prefixText: string; i: Integer; prefixWidth: Single; // Width needed only for the 'return ' label maxChildWidth: Single; // To calculate overall width yPos: Single; // Current vertical position for the row currentXOffset: Single; // Horizontal offset for the expression node returnLabel: TLabel; // Label for measuring 'return ' exprNode: TAuraNode; begin // Create the main node, frameless and without title auraNode := CreateNode(Node); auraNode.BeginUpdate; try auraNode.Frameless := True; savedParent := FParentControl; savedPos := FCurrentPos; // Store original position for siblings FParentControl := auraNode; // Start position for children inside the node yPos := cNodePadding; maxChildWidth := 0; // Max width including prefix + expression // --- Determine width needed for 'return ' prefix --- returnLabel := AddLabel(auraNode, 'return '); // Create temporarily to measure prefixWidth := returnLabel.Width + returnLabel.Margins.Left + returnLabel.Margins.Right + cNodePadding; // Width + gap returnLabel.Free; // Free temporary label // --- End prefix width calculation --- // --- Visit children --- for i := 0 to High(Node.Expressions) do begin expr := Node.Expressions[i]; prefixLabel := nil; // Initialize prefixLabel for this iteration // 1. Create Prefix Label ONLY for the last statement if i = High(Node.Expressions) then begin prefixText := 'return '; prefixLabel := AddLabel(auraNode, prefixText); prefixLabel.Position.Point := TPointF.Create(cNodePadding, yPos); // Place prefix label currentXOffset := cNodePadding + prefixWidth; // Start expression node after the 'return ' prefix space end else begin prefixText := ''; // No prefix for other statements currentXOffset := cNodePadding; // Start expression node directly at the padding edge end; // 2. Visit Expression Node // Set position for the expression node FCurrentPos := TPointF.Create(currentXOffset, yPos); exprNode := CallAccept(expr); // This creates the node/label for the expression // Get the created control and update max width/height if Assigned(exprNode) then begin var currentExprHeight := exprNode.Height + exprNode.Margins.Top + exprNode.Margins.Bottom; var currentPrefixHeight: Single := 0; if Assigned(prefixLabel) then currentPrefixHeight := prefixLabel.Height + prefixLabel.Margins.Top + prefixLabel.Margins.Bottom; var rowHeight := Max(currentExprHeight, currentPrefixHeight); // --- Optional Vertical Centering within the row --- if Assigned(prefixLabel) then prefixLabel.Position.Y := yPos + (rowHeight / 2) - currentPrefixHeight / 2 + prefixLabel.Margins.Top; exprNode.Position.Y := yPos + (rowHeight / 2) - currentExprHeight / 2 + exprNode.Margins.Top; // Position expr node correctly // --- End Vertical Centering --- // Update max width needed for the block node maxChildWidth := Max(maxChildWidth, exprNode.Position.X + exprNode.Width + exprNode.Margins.Right); // Update Y position for the next row yPos := yPos + rowHeight + FNodeVerticalGap; // Move down for the next statement end else // Only prefix label exists (shouldn't happen if expression is valid) begin if Assigned(prefixLabel) then begin yPos := yPos + prefixLabel.Height + prefixLabel.Margins.Top + prefixLabel.Margins.Bottom + FNodeVerticalGap; maxChildWidth := Max(maxChildWidth, prefixLabel.Position.X + prefixLabel.Width + prefixLabel.Margins.Right); end else begin yPos := yPos + cMinNodeHeight + FNodeVerticalGap; // Add minimal height if no control added maxChildWidth := Max(maxChildWidth, cMinNodeWidth); end; end; end; // --- Children Visited --- FParentControl := savedParent; // Restore parent for siblings FCurrentPos := savedPos; // Restore position for siblings // --- Adjust parent size --- auraNode.Width := Max(cMinNodeWidth, maxChildWidth + cNodePadding); // Total width + right padding if auraNode.ControlsCount > 0 then // Check if anything was added auraNode.Height := Max(cMinNodeHeight, yPos - FNodeVerticalGap + cNodePadding) // Use final yPos (minus last gap) + bottom padding else auraNode.Height := cMinNodeHeight; // Ensure minimal height if block is empty finally auraNode.EndUpdate; end; // Move current position down for the next sibling node in the outer scope FCurrentPos.Y := savedPos.Y + auraNode.Height + FNodeVerticalGap; // Use savedPos.Y Result := TDataValue.FromGeneric(auraNode); // Return the created node end; function TAstVisualizer.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; var auraNode: TAuraNode; varLabel: TLabel; nameLabel: TLabel; opLabel: TLabel; initNode: TAuraNode; // Use TAuraNode savedParent: TControl; savedPos: TPointF; currentX: Single; // Track horizontal position maxHeightInRow: Single; // Track max height for row alignment yPos: Single; // Y position for elements in this row begin // Create the main node, frameless auraNode := CreateNode(Node); auraNode.BeginUpdate; try auraNode.Frameless := true; // Start position for children inside the node currentX := cNodePadding; yPos := cNodePadding; // Initial Y position maxHeightInRow := 0; initNode := nil; // 1. "var" Label varLabel := AddLabel(auraNode, 'var'); varLabel.Position.Point := TPointF.Create(currentX, yPos); currentX := varLabel.Position.X + varLabel.Width + varLabel.Margins.Right + cNodePadding; maxHeightInRow := Max(maxHeightInRow, varLabel.Height + varLabel.Margins.Top + varLabel.Margins.Bottom); // 2. Identifier Label nameLabel := AddLabel(auraNode, Node.Identifier.Name); nameLabel.Position.Point := TPointF.Create(currentX, yPos); currentX := nameLabel.Position.X + nameLabel.Width + nameLabel.Margins.Right + cNodePadding; maxHeightInRow := Max(maxHeightInRow, nameLabel.Height + nameLabel.Margins.Top + nameLabel.Margins.Bottom); opLabel := nil; if Assigned(Node.Initializer) then begin // 3. Operator Label (only if initializer exists) opLabel := AddLabel(auraNode, ':='); opLabel.Position.Point := TPointF.Create(currentX, yPos); currentX := opLabel.Position.X + opLabel.Width + opLabel.Margins.Right + cNodePadding; maxHeightInRow := Max(maxHeightInRow, opLabel.Height + opLabel.Margins.Top + opLabel.Margins.Bottom); // 4. Initializer Expression Node (Visited Recursively) savedParent := FParentControl; savedPos := FCurrentPos; // Store original position for siblings FParentControl := auraNode; // Children are added to this node FCurrentPos := TPointF.Create(currentX, yPos); // Set position for the child node initNode := CallAccept(Node.Initializer); // Create the node/label if Assigned(initNode) then begin currentX := initNode.Position.X + initNode.Width + initNode.Margins.Right; maxHeightInRow := Max(maxHeightInRow, initNode.Height + initNode.Margins.Top + initNode.Margins.Bottom); end; FParentControl := savedParent; // Restore parent for siblings FCurrentPos := savedPos; // Restore position for siblings end; // --- Adjust parent size --- auraNode.Width := Max(cMinNodeWidth, currentX + cNodePadding); // Total width needed + right padding auraNode.Height := Max(cMinNodeHeight, maxHeightInRow + 2 * cNodePadding); // Max height + top/bottom padding // --- Optional Vertical Centering --- var centerOffsetY: Single := (auraNode.Height / 2); if Assigned(varLabel) then varLabel.Position.Y := centerOffsetY - (varLabel.Height + varLabel.Margins.Top + varLabel.Margins.Bottom) / 2 + varLabel.Margins.Top; if Assigned(nameLabel) then nameLabel.Position.Y := centerOffsetY - (nameLabel.Height + nameLabel.Margins.Top + nameLabel.Margins.Bottom) / 2 + nameLabel.Margins.Top; if Assigned(opLabel) then opLabel.Position.Y := centerOffsetY - (opLabel.Height + opLabel.Margins.Top + opLabel.Margins.Bottom) / 2 + opLabel.Margins.Top; if Assigned(initNode) then initNode.Position.Y := centerOffsetY - (initNode.Height + initNode.Margins.Top + initNode.Margins.Bottom) / 2 + initNode.Margins.Top; // --- End Optional Vertical Centering --- finally auraNode.EndUpdate; end; // Move current position down for the next sibling node in the outer scope FCurrentPos.Y := savedPos.Y + auraNode.Height + FNodeVerticalGap; // Use savedPos.Y Result := TDataValue.FromGeneric(auraNode); // Return the created node end; function TAstVisualizer.VisitAssignment(const Node: IAssignmentNode): TDataValue; var auraNode: TAuraNode; identifierLabel: TLabel; operatorLabel: TLabel; valueNode: TAuraNode; // Use TAuraNode savedParent: TControl; savedPos: TPointF; currentX: Single; // Track horizontal position maxHeightInRow: Single; // Track max height for row alignment labelYPos: Single; // Y position for labels in this row valueYPos: Single; // Y position for the value node in this row begin // Create the main node, frameless as it primarily serves as a container auraNode := CreateNode(Node); auraNode.BeginUpdate; try auraNode.Frameless := true; // Start position for children inside the node currentX := cNodePadding; labelYPos := cNodePadding; // Initial Y position for labels maxHeightInRow := 0; // 1. Identifier Label identifierLabel := AddLabel(auraNode, Node.Identifier.Name); identifierLabel.Position.Point := TPointF.Create(currentX, labelYPos); currentX := identifierLabel.Position.X + identifierLabel.Width + identifierLabel.Margins.Right + cNodePadding; // Update X pos after adding Margins.Right and gap maxHeightInRow := Max(maxHeightInRow, identifierLabel.Height + identifierLabel.Margins.Top + identifierLabel.Margins.Bottom); // 2. Operator Label operatorLabel := AddLabel(auraNode, ':='); operatorLabel.Position.Point := TPointF.Create(currentX, labelYPos); currentX := operatorLabel.Position.X + operatorLabel.Width + operatorLabel.Margins.Right + cNodePadding; // Update X pos maxHeightInRow := Max(maxHeightInRow, operatorLabel.Height + operatorLabel.Margins.Top + operatorLabel.Margins.Bottom); // 3. Expression Node (Visited Recursively) savedParent := FParentControl; savedPos := FCurrentPos; // Store original position for siblings FParentControl := auraNode; // Children are added to this node valueYPos := cNodePadding; // Align top of value node with top of labels FCurrentPos := TPointF.Create(currentX, valueYPos); // Set position for the child node valueNode := CallAccept(Node.Value); // This creates the node/label for the expression // Get the created control (it's the last one added to auraNode) if Assigned(valueNode) then begin // Update currentX based on the right edge of the value control currentX := valueNode.Position.X + valueNode.Width + valueNode.Margins.Right; // Added margin // Update maxHeightInRow considering the value control's height and potential margins maxHeightInRow := Max(maxHeightInRow, valueNode.Height + valueNode.Margins.Top + valueNode.Margins.Bottom); end; FParentControl := savedParent; // Restore parent for siblings FCurrentPos := savedPos; // Restore position for siblings // --- Adjust parent size --- auraNode.Width := Max(cMinNodeWidth, currentX + cNodePadding); // Total width needed + right padding auraNode.Height := Max(cMinNodeHeight, maxHeightInRow + 2 * cNodePadding); // Max height + top/bottom padding // --- Optional Vertical Centering --- var centerOffsetY: Single := (auraNode.Height / 2); if Assigned(identifierLabel) then identifierLabel.Position.Y := centerOffsetY - (identifierLabel.Height + identifierLabel.Margins.Top + identifierLabel.Margins.Bottom) / 2 + identifierLabel.Margins.Top; if Assigned(operatorLabel) then operatorLabel.Position.Y := centerOffsetY - (operatorLabel.Height + operatorLabel.Margins.Top + operatorLabel.Margins.Bottom) / 2 + operatorLabel.Margins.Top; if Assigned(valueNode) then valueNode.Position.Y := centerOffsetY - (valueNode.Height + valueNode.Margins.Top + valueNode.Margins.Bottom) / 2 + valueNode.Margins.Top; // --- End Optional Vertical Centering --- finally auraNode.EndUpdate; end; // Move current position down for the next sibling node in the outer scope FCurrentPos.Y := savedPos.Y + auraNode.Height + FNodeVerticalGap; // Use savedPos.Y Result := TDataValue.FromGeneric(auraNode); // Return the created node end; function TAstVisualizer.VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue; var i: Integer; paramStr: string; auraNode: TAuraNode; begin paramStr := ''; for i := 0 to High(Node.Parameters) do begin if i > 0 then paramStr := paramStr + ', '; paramStr := paramStr + Node.Parameters[i].Name; end; auraNode := ProcessStub('Macro Def: ' + Node.Name.Name + ' (' + paramStr + ')', Node, [Node.Body]); Result := TDataValue.FromGeneric(auraNode); end; function TAstVisualizer.VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue; var auraNode: TAuraNode; begin auraNode := ProcessStub('Quasiquote', Node, [Node.Expression]); Result := TDataValue.FromGeneric(auraNode); end; function TAstVisualizer.VisitUnquote(const Node: IUnquoteNode): TDataValue; var auraNode: TAuraNode; begin auraNode := ProcessStub('Unquote', Node, [Node.Expression]); Result := TDataValue.FromGeneric(auraNode); end; function TAstVisualizer.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue; var auraNode: TAuraNode; begin auraNode := ProcessStub('Unquote-Splicing', Node, [Node.Expression]); Result := TDataValue.FromGeneric(auraNode); end; function TAstVisualizer.VisitIndexer(const Node: IIndexerNode): TDataValue; var auraNode: TAuraNode; begin auraNode := ProcessStub('Indexer []', Node, [Node.Base, Node.Index]); Result := TDataValue.FromGeneric(auraNode); end; function TAstVisualizer.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; var auraNode: TAuraNode; begin auraNode := ProcessStub('Member Access: .' + Node.Member.Name, Node, [Node.Base]); Result := TDataValue.FromGeneric(auraNode); end; function TAstVisualizer.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; var auraNode: TAuraNode; labelHeight: Single; begin // Leaf node auraNode := CreateNode(Node); auraNode.BeginUpdate; try labelHeight := AddTitleLabel(auraNode, 'Create Series: ' + Node.Definition); // Adjust size based on label size if auraNode.ControlsCount > 0 then begin var Lbl := auraNode.Controls[0] as TLabel; auraNode.Width := Max(cMinNodeWidth, Lbl.Width + Lbl.Margins.Left + Lbl.Margins.Right); auraNode.Height := Max(cMinNodeHeight, labelHeight + cNodePadding); end else AdjustParentNodeSize(auraNode, []); finally auraNode.EndUpdate; end; FCurrentPos.Y := FCurrentPos.Y + auraNode.Height + FNodeVerticalGap; Result := TDataValue.FromGeneric(auraNode); // Return the created node end; function TAstVisualizer.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; var children: TArray; auraNode: TAuraNode; begin children := [Node.Value]; if Assigned(Node.Lookback) then children := children + [Node.Lookback]; auraNode := ProcessStub('Add Item to: ' + Node.Series.Name, Node, children); Result := TDataValue.FromGeneric(auraNode); end; function TAstVisualizer.VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; var auraNode: TAuraNode; labelHeight: Single; begin // Leaf node auraNode := CreateNode(Node); auraNode.BeginUpdate; try labelHeight := AddTitleLabel(auraNode, 'Length of: ' + Node.Series.Name); // Adjust size based on label size if auraNode.ControlsCount > 0 then begin var Lbl := auraNode.Controls[0] as TLabel; auraNode.Width := Max(cMinNodeWidth, Lbl.Width + Lbl.Margins.Left + Lbl.Margins.Right); auraNode.Height := Max(cMinNodeHeight, labelHeight + cNodePadding); end else AdjustParentNodeSize(auraNode, []); finally auraNode.EndUpdate; end; FCurrentPos.Y := FCurrentPos.Y + auraNode.Height + FNodeVerticalGap; Result := TDataValue.FromGeneric(auraNode); // Return the created node end; function TAstVisualizer.VisitRecurNode(const Node: IRecurNode): TDataValue; var auraNode: TAuraNode; savedParent: TControl; savedPos: TPointF; i: Integer; recurLabel: TLabel; argNode: TAuraNode; // Use TAuraNode parenLabel, commaLabel: TLabel; // Layout variables yPos: Single; // Current vertical position for the row currentX: Single; // Horizontal position tracker maxHeightInRow: Single; // Max height for row alignment argStartIndex: Integer; // Index of first argument-related control begin // Create the main node, frameless auraNode := CreateNode(Node); auraNode.BeginUpdate; inc(FExprDepth); try auraNode.Frameless := True; // Start position for children inside the node currentX := cNodePadding; yPos := cNodePadding; // Initial Y position maxHeightInRow := 0; // 1. "recur" Label recurLabel := AddLabel(auraNode, WideChar($03BB)); recurLabel.Font.Style := recurLabel.Font.Style + [TFontStyle.fsBold]; // Make keyword bold recurLabel.Position.Point := TPointF.Create(currentX, yPos); currentX := recurLabel.Position.X + recurLabel.Width + recurLabel.Margins.Right + cNodePadding / 2; // Update X pos maxHeightInRow := Max(maxHeightInRow, recurLabel.Height + recurLabel.Margins.Top + recurLabel.Margins.Bottom); // --- Add Arguments (Horizontal Layout) --- // Add opening parenthesis parenLabel := AddLabel(auraNode, '('); parenLabel.Font.Style := parenLabel.Font.Style - [TFontStyle.fsBold]; // Normal style parenLabel.Position.Point := TPointF.Create(currentX, yPos); currentX := parenLabel.Position.X + parenLabel.Width + parenLabel.Margins.Right + cNodePadding / 2; maxHeightInRow := Max(maxHeightInRow, parenLabel.Height + parenLabel.Margins.Top + parenLabel.Margins.Bottom); savedParent := FParentControl; savedPos := FCurrentPos; FParentControl := auraNode; // Arguments are children of auraNode for i := 0 to High(Node.Arguments) do begin // Set position for the argument node FCurrentPos := TPointF.Create(currentX, yPos); argNode := CallAccept(Node.Arguments[i]); // Create argument node if Assigned(argNode) then begin // Position it correctly (redundant, but safe) argNode.Position.Point := TPointF.Create(currentX, yPos); currentX := argNode.Position.X + argNode.Width + argNode.Margins.Right + IfThen(i < High(Node.Arguments), cNodePadding / 2, 0); // Gap or no gap after arg maxHeightInRow := Max(maxHeightInRow, argNode.Height + argNode.Margins.Top + argNode.Margins.Bottom); // Add comma if not the last argument if i < High(Node.Arguments) then begin commaLabel := AddLabel(auraNode, ','); commaLabel.Font.Style := commaLabel.Font.Style - [TFontStyle.fsBold]; // Normal style commaLabel.Position.Point := TPointF.Create(currentX, yPos); currentX := commaLabel.Position.X + commaLabel.Width + commaLabel.Margins.Right + cNodePadding / 2; maxHeightInRow := Max(maxHeightInRow, commaLabel.Height + commaLabel.Margins.Top + commaLabel.Margins.Bottom); end; end else // Should not happen begin currentX := currentX + cMinNodeWidth + cNodePadding; maxHeightInRow := Max(maxHeightInRow, cMinNodeHeight); end; end; // Add closing parenthesis parenLabel := AddLabel(auraNode, ')'); parenLabel.Font.Style := parenLabel.Font.Style - [TFontStyle.fsBold]; // Normal style parenLabel.Position.Point := TPointF.Create(currentX, yPos); currentX := parenLabel.Position.X + parenLabel.Width + parenLabel.Margins.Right; // Final X position maxHeightInRow := Max(maxHeightInRow, parenLabel.Height + parenLabel.Margins.Top + parenLabel.Margins.Bottom); // --- Vertical Centering for the single row --- var centerOffsetYRow: Single := yPos + maxHeightInRow / 2; argStartIndex := 0; // Start check from first control ("recur") for i := argStartIndex to auraNode.ControlsCount - 1 do // Iterate ALL controls in the row begin var ctrl := auraNode.Controls[i]; // Adjust Y based on calculated center and control's own height/margins ctrl.Position.Y := centerOffsetYRow - (ctrl.Height + ctrl.Margins.Top + ctrl.Margins.Bottom) / 2 + ctrl.Margins.Top; end; // --- End Centering --- yPos := yPos + maxHeightInRow; // Update yPos based on row height for final size calculation FParentControl := savedParent; // Restore parent for siblings FCurrentPos := savedPos; // Restore position for siblings // --- Adjust Size --- auraNode.Width := Max(cMinNodeWidth, currentX + cNodePadding); // Width determined by final currentX auraNode.Height := Max(cMinNodeHeight, yPos + cNodePadding); // Height determined by final yPos finally dec(FExprDepth); auraNode.EndUpdate; end; // Move current position down for the next sibling node FCurrentPos.Y := savedPos.Y + auraNode.Height + FNodeVerticalGap; // Use savedPos.Y Result := TDataValue.FromGeneric(auraNode); // Return the created node end; end.