Ast editor custom draw
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -32,7 +32,8 @@ uses
|
|||||||
Myc.Fmx.AstEditor.Layout in '..\Src\AST\Myc.Fmx.AstEditor.Layout.pas',
|
Myc.Fmx.AstEditor.Layout in '..\Src\AST\Myc.Fmx.AstEditor.Layout.pas',
|
||||||
Myc.Fmx.AstEditor.Node in '..\Src\AST\Myc.Fmx.AstEditor.Node.pas',
|
Myc.Fmx.AstEditor.Node in '..\Src\AST\Myc.Fmx.AstEditor.Node.pas',
|
||||||
Myc.Fmx.AstEditor in '..\Src\AST\Myc.Fmx.AstEditor.pas',
|
Myc.Fmx.AstEditor in '..\Src\AST\Myc.Fmx.AstEditor.pas',
|
||||||
Myc.Ast.Refactoring.Remove in '..\Src\AST\Myc.Ast.Refactoring.Remove.pas';
|
Myc.Ast.Refactoring.Remove in '..\Src\AST\Myc.Ast.Refactoring.Remove.pas',
|
||||||
|
Myc.Fmx.AstEditor.Render in '..\Src\AST\Myc.Fmx.AstEditor.Render.pas';
|
||||||
|
|
||||||
{$R *.res}
|
{$R *.res}
|
||||||
|
|
||||||
|
|||||||
@@ -163,6 +163,7 @@
|
|||||||
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Node.pas"/>
|
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Node.pas"/>
|
||||||
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.pas"/>
|
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.pas"/>
|
||||||
<DCCReference Include="..\Src\AST\Myc.Ast.Refactoring.Remove.pas"/>
|
<DCCReference Include="..\Src\AST\Myc.Ast.Refactoring.Remove.pas"/>
|
||||||
|
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Render.pas"/>
|
||||||
<BuildConfiguration Include="Base">
|
<BuildConfiguration Include="Base">
|
||||||
<Key>Base</Key>
|
<Key>Base</Key>
|
||||||
</BuildConfiguration>
|
</BuildConfiguration>
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ type
|
|||||||
function FormatAddress(const Addr: TResolvedAddress): string;
|
function FormatAddress(const Addr: TResolvedAddress): string;
|
||||||
|
|
||||||
protected
|
protected
|
||||||
// Override abstract PROCEDURES from TAstVisitor
|
|
||||||
function VisitConstant(const Node: IConstantNode): TVoid; override;
|
function VisitConstant(const Node: IConstantNode): TVoid; override;
|
||||||
function VisitIdentifier(const Node: IIdentifierNode): TVoid; override;
|
function VisitIdentifier(const Node: IIdentifierNode): TVoid; override;
|
||||||
function VisitKeyword(const Node: IKeywordNode): TVoid; override;
|
function VisitKeyword(const Node: IKeywordNode): TVoid; override;
|
||||||
|
|||||||
@@ -9,20 +9,18 @@ uses
|
|||||||
System.UITypes,
|
System.UITypes,
|
||||||
System.Generics.Collections,
|
System.Generics.Collections,
|
||||||
FMX.Types,
|
FMX.Types,
|
||||||
FMX.Controls,
|
|
||||||
FMX.StdCtrls,
|
|
||||||
FMX.Graphics,
|
FMX.Graphics,
|
||||||
Myc.Data.Value,
|
Myc.Data.Value,
|
||||||
Myc.Data.Keyword,
|
Myc.Data.Keyword,
|
||||||
Myc.Ast,
|
Myc.Ast,
|
||||||
Myc.Ast.Nodes,
|
Myc.Ast.Nodes,
|
||||||
Myc.Ast.Identities,
|
Myc.Ast.Identities,
|
||||||
Myc.Fmx.AstEditor.Layout,
|
Myc.Fmx.AstEditor.Render, // The new Virtual Engine
|
||||||
Myc.Fmx.AstEditor.Node;
|
Myc.Fmx.AstEditor.Node; // The View Node
|
||||||
|
|
||||||
type
|
type
|
||||||
// --- Abstract List Base ---
|
// --- Abstract List Base ---
|
||||||
// Now implements IReorderable to support drag & drop reordering
|
// Implements IReorderable to support drag & drop reordering within the virtual tree
|
||||||
TNodeListHandler<T: IAstNode> = class(TBaseNodeHandler<INodeList<T>>, IReorderable)
|
TNodeListHandler<T: IAstNode> = class(TBaseNodeHandler<INodeList<T>>, IReorderable)
|
||||||
protected
|
protected
|
||||||
FChildNodes: TList<TAstViewNode>;
|
FChildNodes: TList<TAstViewNode>;
|
||||||
@@ -35,7 +33,7 @@ type
|
|||||||
function ReconstructAst(OwnerNode: TAstViewNode): IAstNode; override; abstract;
|
function ReconstructAst(OwnerNode: TAstViewNode): IAstNode; override; abstract;
|
||||||
|
|
||||||
// IReorderable Implementation
|
// IReorderable Implementation
|
||||||
function CanDrag(Child: TControl): Boolean;
|
function CanDrag(Child: TVisualNode): Boolean;
|
||||||
procedure MoveChild(SourceIndex, TargetIndex: Integer);
|
procedure MoveChild(SourceIndex, TargetIndex: Integer);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -75,7 +73,7 @@ type
|
|||||||
|
|
||||||
TRecordFieldHandler = class(TBaseNodeHandler<IRecordFieldNode>)
|
TRecordFieldHandler = class(TBaseNodeHandler<IRecordFieldNode>)
|
||||||
private
|
private
|
||||||
FKeyLabel: TLabel;
|
FKeyLabel: TTextNode;
|
||||||
FValueNode: TAstViewNode;
|
FValueNode: TAstViewNode;
|
||||||
public
|
public
|
||||||
procedure BuildUI(OwnerNode: TAstViewNode); override;
|
procedure BuildUI(OwnerNode: TAstViewNode); override;
|
||||||
@@ -322,47 +320,45 @@ begin
|
|||||||
|
|
||||||
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth);
|
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth);
|
||||||
|
|
||||||
OwnerNode.BeginUpdate;
|
// No BeginUpdate/EndUpdate needed for TVisualNode, just property setting
|
||||||
try
|
OwnerNode.Frameless := True;
|
||||||
OwnerNode.Frameless := True;
|
OwnerNode.Orientation := GetOrientation;
|
||||||
OwnerNode.Orientation := GetOrientation;
|
OwnerNode.Alignment := GetAlignment;
|
||||||
OwnerNode.Alignment := GetAlignment;
|
|
||||||
|
|
||||||
if openTok <> '' then
|
if openTok <> '' then
|
||||||
OwnerNode.AddLabel(OwnerNode, openTok);
|
OwnerNode.AddLabel(OwnerNode, openTok);
|
||||||
|
|
||||||
for i := 0 to FNode.Count - 1 do
|
for i := 0 to FNode.Count - 1 do
|
||||||
|
begin
|
||||||
|
childView := visu.CallAccept(FNode[i]);
|
||||||
|
FChildNodes.Add(childView);
|
||||||
|
|
||||||
|
if (i < FNode.Count - 1) and (sep <> '') and (sep <> ' ') then
|
||||||
|
OwnerNode.AddLabel(OwnerNode, sep);
|
||||||
|
end;
|
||||||
|
|
||||||
|
if closeTok <> '' then
|
||||||
|
OwnerNode.AddLabel(OwnerNode, closeTok);
|
||||||
|
|
||||||
|
if FNode.Count = 0 then
|
||||||
|
begin
|
||||||
|
if (openTok = '') and (closeTok = '') then
|
||||||
begin
|
begin
|
||||||
childView := visu.CallAccept(FNode[i]);
|
var lbl := OwnerNode.AddLabel(OwnerNode, 'ø');
|
||||||
FChildNodes.Add(childView);
|
lbl.Opacity := 0.5;
|
||||||
|
|
||||||
if (i < FNode.Count - 1) and (sep <> '') and (sep <> ' ') then
|
|
||||||
OwnerNode.AddLabel(OwnerNode, sep);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if closeTok <> '' then
|
|
||||||
OwnerNode.AddLabel(OwnerNode, closeTok);
|
|
||||||
|
|
||||||
if FNode.Count = 0 then
|
|
||||||
begin
|
|
||||||
if (openTok = '') and (closeTok = '') then
|
|
||||||
begin
|
|
||||||
var lbl := OwnerNode.AddLabel(OwnerNode, 'ø');
|
|
||||||
lbl.Opacity := 0.5;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
finally
|
|
||||||
OwnerNode.EndUpdate;
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// IReorderable Implementation
|
// IReorderable Implementation
|
||||||
|
|
||||||
function TNodeListHandler<T>.CanDrag(Child: TControl): Boolean;
|
function TNodeListHandler<T>.CanDrag(Child: TVisualNode): Boolean;
|
||||||
begin
|
begin
|
||||||
// Allow dragging only if the child is one of our managed ViewNodes
|
// Only allow dragging if the child is one of our managed ViewNodes
|
||||||
Result := (Child is TAstViewNode) and FChildNodes.Contains(TAstViewNode(Child));
|
if Child is TAstViewNode then
|
||||||
|
Result := FChildNodes.Contains(TAstViewNode(Child))
|
||||||
|
else
|
||||||
|
Result := False;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TNodeListHandler<T>.MoveChild(SourceIndex, TargetIndex: Integer);
|
procedure TNodeListHandler<T>.MoveChild(SourceIndex, TargetIndex: Integer);
|
||||||
@@ -385,8 +381,7 @@ begin
|
|||||||
FChildNodes.Delete(SourceIndex);
|
FChildNodes.Delete(SourceIndex);
|
||||||
FChildNodes.Insert(TargetIndex, item);
|
FChildNodes.Insert(TargetIndex, item);
|
||||||
|
|
||||||
// The visual order update will happen upon the next ReconstructAst -> Compile -> BuildUI cycle
|
// The visual order update happens via the controller's compilation cycle
|
||||||
// triggered by the controller.
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TParameterListHandler }
|
{ TParameterListHandler }
|
||||||
@@ -482,26 +477,20 @@ var
|
|||||||
visu: IAstVisualizer;
|
visu: IAstVisualizer;
|
||||||
begin
|
begin
|
||||||
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth);
|
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth);
|
||||||
OwnerNode.BeginUpdate;
|
|
||||||
try
|
|
||||||
OwnerNode.Frameless := True;
|
|
||||||
OwnerNode.Orientation := loHorizontal;
|
|
||||||
OwnerNode.Alignment := laCenter;
|
|
||||||
|
|
||||||
FKeyLabel := OwnerNode.AddLabel(OwnerNode, ':' + FNode.Key.Value.Name);
|
OwnerNode.Frameless := True;
|
||||||
FKeyLabel.Font.Style := FKeyLabel.Font.Style + [TFontStyle.fsBold];
|
OwnerNode.Orientation := loHorizontal;
|
||||||
FKeyLabel.StyledSettings := FKeyLabel.StyledSettings - [TStyledSetting.FontColor];
|
OwnerNode.Alignment := laCenter;
|
||||||
FKeyLabel.FontColor := TAlphaColors.Mediumvioletred;
|
|
||||||
|
|
||||||
FValueNode := visu.CallAccept(FNode.Value);
|
FKeyLabel := OwnerNode.AddLabel(OwnerNode, ':' + FNode.Key.Value.Name);
|
||||||
finally
|
FKeyLabel.FontSettings.Font.Style := [TFontStyle.fsBold];
|
||||||
OwnerNode.EndUpdate;
|
FKeyLabel.Color := TAlphaColors.Mediumvioletred;
|
||||||
end;
|
|
||||||
|
FValueNode := visu.CallAccept(FNode.Value);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TRecordFieldHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TRecordFieldHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
// Updated to use new Factory Overload to preserve Identity
|
|
||||||
Result := TAst.RecordField(FNode.Identity, FNode.Key, FValueNode.CreateAst);
|
Result := TAst.RecordField(FNode.Identity, FNode.Key, FValueNode.CreateAst);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -511,33 +500,25 @@ procedure TConstantNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
|||||||
var
|
var
|
||||||
valueStr: string;
|
valueStr: string;
|
||||||
isConcise: Boolean;
|
isConcise: Boolean;
|
||||||
titleLabel: TLabel;
|
titleLabel: TTextNode;
|
||||||
valueLabel: TLabel;
|
|
||||||
begin
|
begin
|
||||||
OwnerNode.BeginUpdate;
|
valueStr := FNode.Value.ToString;
|
||||||
try
|
isConcise := ((FNode.Value.Kind = TDataValueKind.vkScalar) or (Pos(sLineBreak, valueStr) = 0)) and (Length(valueStr) < 40);
|
||||||
valueStr := FNode.Value.ToString;
|
|
||||||
isConcise := ((FNode.Value.Kind = TDataValueKind.vkScalar) or (Pos(sLineBreak, valueStr) = 0)) and (Length(valueStr) < 40);
|
|
||||||
|
|
||||||
if isConcise then
|
if isConcise then
|
||||||
begin
|
begin
|
||||||
OwnerNode.Frameless := True;
|
OwnerNode.Frameless := True;
|
||||||
OwnerNode.AddLabel(OwnerNode, valueStr);
|
OwnerNode.AddLabel(OwnerNode, valueStr);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
OwnerNode.Frameless := False;
|
OwnerNode.Frameless := False;
|
||||||
OwnerNode.Orientation := loVertical;
|
OwnerNode.Orientation := loVertical;
|
||||||
|
|
||||||
titleLabel := OwnerNode.AddLabel(OwnerNode, 'Const');
|
titleLabel := OwnerNode.AddLabel(OwnerNode, 'Const');
|
||||||
titleLabel.Font.Style := titleLabel.Font.Style + [TFontStyle.fsBold];
|
titleLabel.FontSettings.Font.Style := [TFontStyle.fsBold];
|
||||||
|
|
||||||
valueLabel := OwnerNode.AddLabel(OwnerNode, valueStr);
|
OwnerNode.AddLabel(OwnerNode, valueStr);
|
||||||
valueLabel.AutoSize := False;
|
|
||||||
valueLabel.WordWrap := True;
|
|
||||||
end;
|
|
||||||
finally
|
|
||||||
OwnerNode.EndUpdate;
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -550,13 +531,8 @@ end;
|
|||||||
|
|
||||||
procedure TIdentifierNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
procedure TIdentifierNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
||||||
begin
|
begin
|
||||||
OwnerNode.BeginUpdate;
|
OwnerNode.Frameless := True;
|
||||||
try
|
OwnerNode.AddLabel(OwnerNode, FNode.Name);
|
||||||
OwnerNode.Frameless := True;
|
|
||||||
OwnerNode.AddLabel(OwnerNode, FNode.Name);
|
|
||||||
finally
|
|
||||||
OwnerNode.EndUpdate;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TIdentifierNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TIdentifierNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -568,17 +544,11 @@ end;
|
|||||||
|
|
||||||
procedure TKeywordNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
procedure TKeywordNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
||||||
var
|
var
|
||||||
lbl: TLabel;
|
lbl: TTextNode;
|
||||||
begin
|
begin
|
||||||
OwnerNode.BeginUpdate;
|
OwnerNode.Frameless := True;
|
||||||
try
|
lbl := OwnerNode.AddLabel(OwnerNode, ':' + FNode.Value.Name);
|
||||||
OwnerNode.Frameless := True;
|
lbl.Color := TAlphaColors.Mediumvioletred;
|
||||||
lbl := OwnerNode.AddLabel(OwnerNode, ':' + FNode.Value.Name);
|
|
||||||
lbl.StyledSettings := lbl.StyledSettings - [TStyledSetting.FontColor];
|
|
||||||
lbl.FontColor := TAlphaColors.Mediumvioletred;
|
|
||||||
finally
|
|
||||||
OwnerNode.EndUpdate;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TKeywordNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TKeywordNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -590,19 +560,14 @@ end;
|
|||||||
|
|
||||||
procedure TNopNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
procedure TNopNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
||||||
var
|
var
|
||||||
lbl: TLabel;
|
lbl: TTextNode;
|
||||||
begin
|
begin
|
||||||
OwnerNode.BeginUpdate;
|
OwnerNode.Frameless := True;
|
||||||
try
|
OwnerNode.Alignment := TLayoutAlignment.laCenter;
|
||||||
OwnerNode.Frameless := True;
|
OwnerNode.Orientation := TLayoutOrientation.loHorizontal;
|
||||||
OwnerNode.Alignment := TLayoutAlignment.laCenter;
|
lbl := OwnerNode.AddLabel(OwnerNode, '...');
|
||||||
OwnerNode.Orientation := TLayoutOrientation.loHorizontal;
|
lbl.FontSettings.Font.Style := [TFontStyle.fsBold];
|
||||||
lbl := OwnerNode.AddLabel(OwnerNode, '...');
|
lbl.Margins := TMarginRect.Create(8, 4, 8, 4);
|
||||||
lbl.Font.Style := lbl.Font.Style + [TFontStyle.fsBold];
|
|
||||||
lbl.Margins.Rect := TRectF.Create(8, 4, 8, 4);
|
|
||||||
finally
|
|
||||||
OwnerNode.EndUpdate;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TNopNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TNopNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -615,23 +580,19 @@ end;
|
|||||||
procedure TBlockExpressionNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
procedure TBlockExpressionNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
||||||
var
|
var
|
||||||
visu: IAstVisualizer;
|
visu: IAstVisualizer;
|
||||||
titleLabel: TLabel;
|
titleLabel: TTextNode;
|
||||||
begin
|
begin
|
||||||
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
||||||
OwnerNode.BeginUpdate;
|
|
||||||
try
|
|
||||||
OwnerNode.Frameless := False;
|
|
||||||
OwnerNode.Orientation := loVertical;
|
|
||||||
OwnerNode.Alignment := laFlush;
|
|
||||||
|
|
||||||
titleLabel := OwnerNode.AddLabel(OwnerNode, 'do');
|
OwnerNode.Frameless := False;
|
||||||
titleLabel.Font.Style := titleLabel.Font.Style + [TFontStyle.fsBold];
|
OwnerNode.Orientation := loVertical;
|
||||||
|
OwnerNode.Alignment := laFlush;
|
||||||
|
|
||||||
FExpressionsNode := visu.CallAccept(FNode.Expressions);
|
titleLabel := OwnerNode.AddLabel(OwnerNode, 'do');
|
||||||
FExpressionsNode.Frameless := True;
|
titleLabel.FontSettings.Font.Style := [TFontStyle.fsBold];
|
||||||
finally
|
|
||||||
OwnerNode.EndUpdate;
|
FExpressionsNode := visu.CallAccept(FNode.Expressions);
|
||||||
end;
|
FExpressionsNode.Frameless := True;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TBlockExpressionNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TBlockExpressionNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -643,19 +604,14 @@ end;
|
|||||||
|
|
||||||
procedure TIfExpressionNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
procedure TIfExpressionNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
||||||
begin
|
begin
|
||||||
OwnerNode.BeginUpdate;
|
OwnerNode.Frameless := False;
|
||||||
try
|
OwnerNode.Orientation := loVertical;
|
||||||
OwnerNode.Frameless := False;
|
OwnerNode.Alignment := laFlush;
|
||||||
OwnerNode.Orientation := loVertical;
|
|
||||||
OwnerNode.Alignment := laFlush;
|
|
||||||
|
|
||||||
FConditionNode := OwnerNode.AddExpr(OwnerNode, 'if', FNode.Condition);
|
FConditionNode := OwnerNode.AddExpr(OwnerNode, 'if', FNode.Condition);
|
||||||
FThenNode := OwnerNode.AddExpr(OwnerNode, 'then', FNode.ThenBranch);
|
FThenNode := OwnerNode.AddExpr(OwnerNode, 'then', FNode.ThenBranch);
|
||||||
if Assigned(FNode.ElseBranch) then
|
if Assigned(FNode.ElseBranch) then
|
||||||
FElseNode := OwnerNode.AddExpr(OwnerNode, 'else', FNode.ElseBranch);
|
FElseNode := OwnerNode.AddExpr(OwnerNode, 'else', FNode.ElseBranch);
|
||||||
finally
|
|
||||||
OwnerNode.EndUpdate;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TIfExpressionNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TIfExpressionNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -677,18 +633,14 @@ var
|
|||||||
visu: IAstVisualizer;
|
visu: IAstVisualizer;
|
||||||
begin
|
begin
|
||||||
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
||||||
OwnerNode.BeginUpdate;
|
|
||||||
try
|
OwnerNode.Frameless := True;
|
||||||
OwnerNode.Frameless := True;
|
OwnerNode.Orientation := loHorizontal;
|
||||||
OwnerNode.Orientation := loHorizontal;
|
FConditionNode := visu.CallAccept(FNode.Condition);
|
||||||
FConditionNode := visu.CallAccept(FNode.Condition);
|
OwnerNode.AddLabel(OwnerNode, '?');
|
||||||
OwnerNode.AddLabel(OwnerNode, '?');
|
FThenNode := visu.CallAccept(FNode.ThenBranch);
|
||||||
FThenNode := visu.CallAccept(FNode.ThenBranch);
|
OwnerNode.AddLabel(OwnerNode, ':');
|
||||||
OwnerNode.AddLabel(OwnerNode, ':');
|
FElseNode := visu.CallAccept(FNode.ElseBranch);
|
||||||
FElseNode := visu.CallAccept(FNode.ElseBranch);
|
|
||||||
finally
|
|
||||||
OwnerNode.EndUpdate;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTernaryExpressionNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TTernaryExpressionNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -701,31 +653,27 @@ end;
|
|||||||
procedure TLambdaExpressionNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
procedure TLambdaExpressionNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
||||||
var
|
var
|
||||||
visu: IAstVisualizer;
|
visu: IAstVisualizer;
|
||||||
titleContainer: TAutoFitControl;
|
titleContainer: TAutoFitLayout;
|
||||||
lbl: TLabel;
|
lbl: TTextNode;
|
||||||
begin
|
begin
|
||||||
visu := OwnerNode.Visualizer.Clone(OwnerNode, 0);
|
visu := OwnerNode.Visualizer.Clone(OwnerNode, 0);
|
||||||
OwnerNode.BeginUpdate;
|
|
||||||
try
|
|
||||||
OwnerNode.Frameless := False;
|
|
||||||
OwnerNode.Orientation := loVertical;
|
|
||||||
OwnerNode.Alignment := laFlush;
|
|
||||||
OwnerNode.BackgroundColor := $090000ff;
|
|
||||||
|
|
||||||
titleContainer := OwnerNode.AddContainer(OwnerNode, loHorizontal, laCenter);
|
OwnerNode.Frameless := False;
|
||||||
lbl := OwnerNode.AddLabel(titleContainer, 'fn');
|
OwnerNode.Orientation := loVertical;
|
||||||
lbl.Font.Style := lbl.Font.Style + [TFontStyle.fsBold];
|
OwnerNode.Alignment := laFlush;
|
||||||
|
OwnerNode.BackgroundColor := $090000ff;
|
||||||
|
|
||||||
// Parameters: Delegate to List Handler
|
titleContainer := OwnerNode.AddContainer(OwnerNode, loHorizontal, laCenter);
|
||||||
var paramVisu := visu.Clone(titleContainer, 0);
|
lbl := OwnerNode.AddLabel(titleContainer, 'fn');
|
||||||
FParamsNode := paramVisu.CallAccept(FNode.Parameters);
|
lbl.FontSettings.Font.Style := [TFontStyle.fsBold];
|
||||||
|
|
||||||
// Body
|
// Parameters: Delegate to List Handler
|
||||||
FBodyNode := visu.CallAccept(FNode.Body);
|
var paramVisu := visu.Clone(titleContainer, 0);
|
||||||
FBodyNode.Frameless := False;
|
FParamsNode := paramVisu.CallAccept(FNode.Parameters);
|
||||||
finally
|
|
||||||
OwnerNode.EndUpdate;
|
// Body
|
||||||
end;
|
FBodyNode := visu.CallAccept(FNode.Body);
|
||||||
|
FBodyNode.Frameless := False;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TLambdaExpressionNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TLambdaExpressionNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -749,23 +697,19 @@ end;
|
|||||||
procedure TFunctionCallNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
procedure TFunctionCallNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
||||||
var
|
var
|
||||||
visu: IAstVisualizer;
|
visu: IAstVisualizer;
|
||||||
callLabel: TLabel;
|
callLabel: TTextNode;
|
||||||
begin
|
begin
|
||||||
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
||||||
OwnerNode.BeginUpdate;
|
|
||||||
try
|
|
||||||
OwnerNode.Frameless := True;
|
|
||||||
OwnerNode.Orientation := loHorizontal;
|
|
||||||
|
|
||||||
callLabel := OwnerNode.AddLabel(OwnerNode, 'call ');
|
OwnerNode.Frameless := True;
|
||||||
callLabel.Font.Style := callLabel.Font.Style + [TFontStyle.fsBold];
|
OwnerNode.Orientation := loHorizontal;
|
||||||
|
|
||||||
FCalleeNode := visu.CallAccept(FNode.Callee);
|
callLabel := OwnerNode.AddLabel(OwnerNode, 'call ');
|
||||||
|
callLabel.FontSettings.Font.Style := [TFontStyle.fsBold];
|
||||||
|
|
||||||
FArgumentsNode := visu.CallAccept(FNode.Arguments);
|
FCalleeNode := visu.CallAccept(FNode.Callee);
|
||||||
finally
|
|
||||||
OwnerNode.EndUpdate;
|
FArgumentsNode := visu.CallAccept(FNode.Arguments);
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TFunctionCallNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TFunctionCallNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -787,19 +731,15 @@ end;
|
|||||||
procedure TMacroExpansionNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
procedure TMacroExpansionNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
||||||
var
|
var
|
||||||
visu: IAstVisualizer;
|
visu: IAstVisualizer;
|
||||||
titleLabel: TLabel;
|
titleLabel: TTextNode;
|
||||||
begin
|
begin
|
||||||
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
||||||
OwnerNode.BeginUpdate;
|
|
||||||
try
|
OwnerNode.Frameless := False;
|
||||||
OwnerNode.Frameless := False;
|
OwnerNode.Orientation := loVertical;
|
||||||
OwnerNode.Orientation := loVertical;
|
titleLabel := OwnerNode.AddLabel(OwnerNode, 'Macro Expansion');
|
||||||
titleLabel := OwnerNode.AddLabel(OwnerNode, 'Macro Expansion');
|
titleLabel.FontSettings.Font.Style := [TFontStyle.fsBold];
|
||||||
titleLabel.Font.Style := titleLabel.Font.Style + [TFontStyle.fsBold];
|
FExpandedBodyNode := visu.CallAccept(FNode.ExpandedBody);
|
||||||
FExpandedBodyNode := visu.CallAccept(FNode.ExpandedBody);
|
|
||||||
finally
|
|
||||||
OwnerNode.EndUpdate;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMacroExpansionNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TMacroExpansionNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -812,21 +752,17 @@ end;
|
|||||||
procedure TRecurNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
procedure TRecurNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
||||||
var
|
var
|
||||||
visu: IAstVisualizer;
|
visu: IAstVisualizer;
|
||||||
titleLabel: TLabel;
|
titleLabel: TTextNode;
|
||||||
begin
|
begin
|
||||||
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
||||||
OwnerNode.BeginUpdate;
|
|
||||||
try
|
|
||||||
OwnerNode.Frameless := True;
|
|
||||||
OwnerNode.Orientation := loHorizontal;
|
|
||||||
|
|
||||||
titleLabel := OwnerNode.AddLabel(OwnerNode, 'recur');
|
OwnerNode.Frameless := True;
|
||||||
titleLabel.Font.Style := titleLabel.Font.Style + [TFontStyle.fsBold];
|
OwnerNode.Orientation := loHorizontal;
|
||||||
|
|
||||||
FArgumentsNode := visu.CallAccept(FNode.Arguments);
|
titleLabel := OwnerNode.AddLabel(OwnerNode, 'recur');
|
||||||
finally
|
titleLabel.FontSettings.Font.Style := [TFontStyle.fsBold];
|
||||||
OwnerNode.EndUpdate;
|
|
||||||
end;
|
FArgumentsNode := visu.CallAccept(FNode.Arguments);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TRecurNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TRecurNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -839,29 +775,25 @@ end;
|
|||||||
procedure TVariableDeclarationNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
procedure TVariableDeclarationNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
||||||
var
|
var
|
||||||
visu: IAstVisualizer;
|
visu: IAstVisualizer;
|
||||||
varLabel: TLabel;
|
varLabel: TTextNode;
|
||||||
begin
|
begin
|
||||||
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
||||||
OwnerNode.BeginUpdate;
|
|
||||||
try
|
|
||||||
OwnerNode.Frameless := True;
|
|
||||||
OwnerNode.Orientation := loHorizontal;
|
|
||||||
|
|
||||||
varLabel := OwnerNode.AddLabel(OwnerNode, 'var');
|
OwnerNode.Frameless := True;
|
||||||
|
OwnerNode.Orientation := loHorizontal;
|
||||||
|
|
||||||
varLabel.Font.Style := varLabel.Font.Style + [TFontStyle.fsBold];
|
varLabel := OwnerNode.AddLabel(OwnerNode, 'var');
|
||||||
FTargetNode := visu.CallAccept(FNode.Target);
|
|
||||||
|
|
||||||
if Assigned(FNode.Initializer) then
|
varLabel.FontSettings.Font.Style := [TFontStyle.fsBold];
|
||||||
begin
|
FTargetNode := visu.CallAccept(FNode.Target);
|
||||||
OwnerNode.AddLabel(OwnerNode, ':=');
|
|
||||||
FInitializerNode := visu.CallAccept(FNode.Initializer);
|
if Assigned(FNode.Initializer) then
|
||||||
end
|
begin
|
||||||
else
|
OwnerNode.AddLabel(OwnerNode, ':=');
|
||||||
FInitializerNode := nil;
|
FInitializerNode := visu.CallAccept(FNode.Initializer);
|
||||||
finally
|
end
|
||||||
OwnerNode.EndUpdate;
|
else
|
||||||
end;
|
FInitializerNode := nil;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TVariableDeclarationNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TVariableDeclarationNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -882,16 +814,12 @@ var
|
|||||||
visu: IAstVisualizer;
|
visu: IAstVisualizer;
|
||||||
begin
|
begin
|
||||||
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
||||||
OwnerNode.BeginUpdate;
|
|
||||||
try
|
OwnerNode.Frameless := True;
|
||||||
OwnerNode.Frameless := True;
|
OwnerNode.Orientation := loHorizontal;
|
||||||
OwnerNode.Orientation := loHorizontal;
|
FTargetNode := visu.CallAccept(FNode.Target);
|
||||||
FTargetNode := visu.CallAccept(FNode.Target);
|
OwnerNode.AddLabel(OwnerNode, ':=');
|
||||||
OwnerNode.AddLabel(OwnerNode, ':=');
|
FValueNode := visu.CallAccept(FNode.Value);
|
||||||
FValueNode := visu.CallAccept(FNode.Value);
|
|
||||||
finally
|
|
||||||
OwnerNode.EndUpdate;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAssignmentNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TAssignmentNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -904,27 +832,23 @@ end;
|
|||||||
procedure TMacroDefinitionNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
procedure TMacroDefinitionNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
||||||
var
|
var
|
||||||
visu: IAstVisualizer;
|
visu: IAstVisualizer;
|
||||||
titleLabel: TLabel;
|
titleLabel: TTextNode;
|
||||||
titleContainer: TAutoFitControl;
|
titleContainer: TAutoFitLayout;
|
||||||
begin
|
begin
|
||||||
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
||||||
OwnerNode.BeginUpdate;
|
|
||||||
try
|
|
||||||
OwnerNode.Frameless := False;
|
|
||||||
OwnerNode.Orientation := loVertical;
|
|
||||||
|
|
||||||
titleContainer := OwnerNode.AddContainer(OwnerNode, loHorizontal, laCenter);
|
OwnerNode.Frameless := False;
|
||||||
titleLabel := OwnerNode.AddLabel(titleContainer, 'Macro Def: ' + FNode.Name.Name);
|
OwnerNode.Orientation := loVertical;
|
||||||
titleLabel.Font.Style := titleLabel.Font.Style + [TFontStyle.fsBold];
|
|
||||||
|
|
||||||
// Parameters List
|
titleContainer := OwnerNode.AddContainer(OwnerNode, loHorizontal, laCenter);
|
||||||
var paramVisu := visu.Clone(titleContainer, visu.ExprDepth);
|
titleLabel := OwnerNode.AddLabel(titleContainer, 'Macro Def: ' + FNode.Name.Name);
|
||||||
FParamsNode := paramVisu.CallAccept(FNode.Parameters);
|
titleLabel.FontSettings.Font.Style := [TFontStyle.fsBold];
|
||||||
|
|
||||||
FBodyNode := visu.CallAccept(FNode.Body);
|
// Parameters List
|
||||||
finally
|
var paramVisu := visu.Clone(titleContainer, visu.ExprDepth);
|
||||||
OwnerNode.EndUpdate;
|
FParamsNode := paramVisu.CallAccept(FNode.Parameters);
|
||||||
end;
|
|
||||||
|
FBodyNode := visu.CallAccept(FNode.Body);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMacroDefinitionNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TMacroDefinitionNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -937,19 +861,15 @@ end;
|
|||||||
procedure TQuasiquoteNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
procedure TQuasiquoteNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
||||||
var
|
var
|
||||||
visu: IAstVisualizer;
|
visu: IAstVisualizer;
|
||||||
titleLabel: TLabel;
|
titleLabel: TTextNode;
|
||||||
begin
|
begin
|
||||||
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
||||||
OwnerNode.BeginUpdate;
|
|
||||||
try
|
OwnerNode.Frameless := False;
|
||||||
OwnerNode.Frameless := False;
|
OwnerNode.Orientation := loVertical;
|
||||||
OwnerNode.Orientation := loVertical;
|
titleLabel := OwnerNode.AddLabel(OwnerNode, 'Quasiquote');
|
||||||
titleLabel := OwnerNode.AddLabel(OwnerNode, 'Quasiquote');
|
titleLabel.FontSettings.Font.Style := [TFontStyle.fsBold];
|
||||||
titleLabel.Font.Style := titleLabel.Font.Style + [TFontStyle.fsBold];
|
FExpressionNode := visu.CallAccept(FNode.Expression);
|
||||||
FExpressionNode := visu.CallAccept(FNode.Expression);
|
|
||||||
finally
|
|
||||||
OwnerNode.EndUpdate;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TQuasiquoteNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TQuasiquoteNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -962,26 +882,25 @@ end;
|
|||||||
procedure TUnquoteNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
procedure TUnquoteNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
||||||
var
|
var
|
||||||
visu: IAstVisualizer;
|
visu: IAstVisualizer;
|
||||||
leftBracket, rightBracket: TLabel;
|
leftBracket, rightBracket: TTextNode;
|
||||||
begin
|
begin
|
||||||
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
||||||
OwnerNode.BeginUpdate;
|
|
||||||
try
|
|
||||||
OwnerNode.Frameless := True;
|
|
||||||
OwnerNode.Orientation := loHorizontal;
|
|
||||||
|
|
||||||
leftBracket := OwnerNode.AddLabel(OwnerNode, '<');
|
OwnerNode.Frameless := True;
|
||||||
leftBracket.Padding.Right := 0;
|
OwnerNode.Orientation := loHorizontal;
|
||||||
leftBracket.Margins.Right := 0;
|
|
||||||
|
|
||||||
FExpressionNode := visu.CallAccept(FNode.Expression);
|
leftBracket := OwnerNode.AddLabel(OwnerNode, '<');
|
||||||
|
// We cannot assign to fields of record properties directly.
|
||||||
|
// Replace the entire margin record.
|
||||||
|
// Default from AddLabel is (cNodePadding, cTitleTopPadding, cNodePadding, cTitleBottomPadding)
|
||||||
|
// We want Right Margin = 0.
|
||||||
|
leftBracket.Margins := TMarginRect.Create(cNodePadding, cTitleTopPadding, 0, cTitleBottomPadding);
|
||||||
|
|
||||||
rightBracket := OwnerNode.AddLabel(OwnerNode, '>');
|
FExpressionNode := visu.CallAccept(FNode.Expression);
|
||||||
rightBracket.Padding.Left := 0;
|
|
||||||
rightBracket.Margins.Left := 0;
|
rightBracket := OwnerNode.AddLabel(OwnerNode, '>');
|
||||||
finally
|
// We want Left Margin = 0.
|
||||||
OwnerNode.EndUpdate;
|
rightBracket.Margins := TMarginRect.Create(0, cTitleTopPadding, cNodePadding, cTitleBottomPadding);
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TUnquoteNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TUnquoteNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -994,19 +913,15 @@ end;
|
|||||||
procedure TUnquoteSplicingNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
procedure TUnquoteSplicingNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
||||||
var
|
var
|
||||||
visu: IAstVisualizer;
|
visu: IAstVisualizer;
|
||||||
titleLabel: TLabel;
|
titleLabel: TTextNode;
|
||||||
begin
|
begin
|
||||||
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
||||||
OwnerNode.BeginUpdate;
|
|
||||||
try
|
OwnerNode.Frameless := False;
|
||||||
OwnerNode.Frameless := False;
|
OwnerNode.Orientation := loVertical;
|
||||||
OwnerNode.Orientation := loVertical;
|
titleLabel := OwnerNode.AddLabel(OwnerNode, 'Unquote-Splicing');
|
||||||
titleLabel := OwnerNode.AddLabel(OwnerNode, 'Unquote-Splicing');
|
titleLabel.FontSettings.Font.Style := [TFontStyle.fsBold];
|
||||||
titleLabel.Font.Style := titleLabel.Font.Style + [TFontStyle.fsBold];
|
FExpressionNode := visu.CallAccept(FNode.Expression);
|
||||||
FExpressionNode := visu.CallAccept(FNode.Expression);
|
|
||||||
finally
|
|
||||||
OwnerNode.EndUpdate;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TUnquoteSplicingNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TUnquoteSplicingNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -1021,17 +936,13 @@ var
|
|||||||
visu: IAstVisualizer;
|
visu: IAstVisualizer;
|
||||||
begin
|
begin
|
||||||
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
||||||
OwnerNode.BeginUpdate;
|
|
||||||
try
|
OwnerNode.Frameless := True;
|
||||||
OwnerNode.Frameless := True;
|
OwnerNode.Orientation := loHorizontal;
|
||||||
OwnerNode.Orientation := loHorizontal;
|
FBaseNode := visu.CallAccept(FNode.Base);
|
||||||
FBaseNode := visu.CallAccept(FNode.Base);
|
OwnerNode.AddLabel(OwnerNode, '[');
|
||||||
OwnerNode.AddLabel(OwnerNode, '[');
|
FIndexNode := visu.CallAccept(FNode.Index);
|
||||||
FIndexNode := visu.CallAccept(FNode.Index);
|
OwnerNode.AddLabel(OwnerNode, ']');
|
||||||
OwnerNode.AddLabel(OwnerNode, ']');
|
|
||||||
finally
|
|
||||||
OwnerNode.EndUpdate;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TIndexerNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TIndexerNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -1046,21 +957,16 @@ var
|
|||||||
visu: IAstVisualizer;
|
visu: IAstVisualizer;
|
||||||
begin
|
begin
|
||||||
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
||||||
OwnerNode.BeginUpdate;
|
|
||||||
try
|
OwnerNode.Frameless := True;
|
||||||
OwnerNode.Frameless := True;
|
OwnerNode.Orientation := loHorizontal;
|
||||||
OwnerNode.Orientation := loHorizontal;
|
FBaseNode := visu.CallAccept(FNode.Base);
|
||||||
FBaseNode := visu.CallAccept(FNode.Base);
|
OwnerNode.AddLabel(OwnerNode, '.');
|
||||||
OwnerNode.AddLabel(OwnerNode, '.');
|
OwnerNode.AddLabel(OwnerNode, FNode.Member.Value.Name);
|
||||||
OwnerNode.AddLabel(OwnerNode, FNode.Member.Value.Name);
|
|
||||||
finally
|
|
||||||
OwnerNode.EndUpdate;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMemberAccessNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TMemberAccessNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
// Reuse Identifier, Reuse Member Keyword (via TAst.Keyword with Identity)
|
|
||||||
Result := TAst.MemberAccess(FNode.Identity, FBaseNode.CreateAst, TAst.Keyword(FNode.Member.Identity.AsKeyword), FNode.StaticType);
|
Result := TAst.MemberAccess(FNode.Identity, FBaseNode.CreateAst, TAst.Keyword(FNode.Member.Identity.AsKeyword), FNode.StaticType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -1071,17 +977,13 @@ var
|
|||||||
visu: IAstVisualizer;
|
visu: IAstVisualizer;
|
||||||
begin
|
begin
|
||||||
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
||||||
OwnerNode.BeginUpdate;
|
|
||||||
try
|
|
||||||
OwnerNode.Frameless := False;
|
|
||||||
OwnerNode.Orientation := loVertical;
|
|
||||||
OwnerNode.Alignment := laFlush;
|
|
||||||
|
|
||||||
// Delegate to RecordFieldList
|
OwnerNode.Frameless := False;
|
||||||
FFieldsNode := visu.CallAccept(FNode.Fields);
|
OwnerNode.Orientation := loVertical;
|
||||||
finally
|
OwnerNode.Alignment := laFlush;
|
||||||
OwnerNode.EndUpdate;
|
|
||||||
end;
|
// Delegate to RecordFieldList
|
||||||
|
FFieldsNode := visu.CallAccept(FNode.Fields);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TRecordLiteralNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TRecordLiteralNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -1100,17 +1002,12 @@ end;
|
|||||||
|
|
||||||
procedure TCreateSeriesNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
procedure TCreateSeriesNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
||||||
var
|
var
|
||||||
titleLabel: TLabel;
|
titleLabel: TTextNode;
|
||||||
begin
|
begin
|
||||||
OwnerNode.BeginUpdate;
|
OwnerNode.Frameless := False;
|
||||||
try
|
OwnerNode.Orientation := loVertical;
|
||||||
OwnerNode.Frameless := False;
|
titleLabel := OwnerNode.AddLabel(OwnerNode, 'Create Series: ' + FNode.Definition);
|
||||||
OwnerNode.Orientation := loVertical;
|
titleLabel.FontSettings.Font.Style := [TFontStyle.fsBold];
|
||||||
titleLabel := OwnerNode.AddLabel(OwnerNode, 'Create Series: ' + FNode.Definition);
|
|
||||||
titleLabel.Font.Style := titleLabel.Font.Style + [TFontStyle.fsBold];
|
|
||||||
finally
|
|
||||||
OwnerNode.EndUpdate;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCreateSeriesNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TCreateSeriesNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -1123,25 +1020,21 @@ end;
|
|||||||
procedure TAddSeriesItemNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
procedure TAddSeriesItemNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
||||||
var
|
var
|
||||||
visu: IAstVisualizer;
|
visu: IAstVisualizer;
|
||||||
titleLabel: TLabel;
|
titleLabel: TTextNode;
|
||||||
begin
|
begin
|
||||||
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth + 1);
|
||||||
OwnerNode.BeginUpdate;
|
|
||||||
try
|
|
||||||
OwnerNode.Frameless := False;
|
|
||||||
OwnerNode.Orientation := loVertical;
|
|
||||||
|
|
||||||
titleLabel := OwnerNode.AddLabel(OwnerNode, 'Add Item to: ' + FNode.Series.Name);
|
OwnerNode.Frameless := False;
|
||||||
titleLabel.Font.Style := titleLabel.Font.Style + [TFontStyle.fsBold];
|
OwnerNode.Orientation := loVertical;
|
||||||
|
|
||||||
FValueNode := visu.CallAccept(FNode.Value);
|
titleLabel := OwnerNode.AddLabel(OwnerNode, 'Add Item to: ' + FNode.Series.Name);
|
||||||
if Assigned(FNode.Lookback) then
|
titleLabel.FontSettings.Font.Style := [TFontStyle.fsBold];
|
||||||
FLookbackNode := visu.CallAccept(FNode.Lookback)
|
|
||||||
else
|
FValueNode := visu.CallAccept(FNode.Value);
|
||||||
FLookbackNode := nil;
|
if Assigned(FNode.Lookback) then
|
||||||
finally
|
FLookbackNode := visu.CallAccept(FNode.Lookback)
|
||||||
OwnerNode.EndUpdate;
|
else
|
||||||
end;
|
FLookbackNode := nil;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAddSeriesItemNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TAddSeriesItemNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
@@ -1167,17 +1060,12 @@ end;
|
|||||||
|
|
||||||
procedure TSeriesLengthNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
procedure TSeriesLengthNodeHandler.BuildUI(OwnerNode: TAstViewNode);
|
||||||
var
|
var
|
||||||
titleLabel: TLabel;
|
titleLabel: TTextNode;
|
||||||
begin
|
begin
|
||||||
OwnerNode.BeginUpdate;
|
OwnerNode.Frameless := False;
|
||||||
try
|
OwnerNode.Orientation := loVertical;
|
||||||
OwnerNode.Frameless := False;
|
titleLabel := OwnerNode.AddLabel(OwnerNode, 'Length of: ' + FNode.Series.Name);
|
||||||
OwnerNode.Orientation := loVertical;
|
titleLabel.FontSettings.Font.Style := [TFontStyle.fsBold];
|
||||||
titleLabel := OwnerNode.AddLabel(OwnerNode, 'Length of: ' + FNode.Series.Name);
|
|
||||||
titleLabel.Font.Style := titleLabel.Font.Style + [TFontStyle.fsBold];
|
|
||||||
finally
|
|
||||||
OwnerNode.EndUpdate;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TSeriesLengthNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
function TSeriesLengthNodeHandler.ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
|
|||||||
+174
-260
@@ -12,13 +12,11 @@ uses
|
|||||||
System.Generics.Defaults,
|
System.Generics.Defaults,
|
||||||
System.Generics.Collections,
|
System.Generics.Collections,
|
||||||
FMX.Types,
|
FMX.Types,
|
||||||
FMX.Controls,
|
|
||||||
FMX.Objects,
|
|
||||||
FMX.Graphics,
|
FMX.Graphics,
|
||||||
FMX.StdCtrls,
|
FMX.TextLayout,
|
||||||
Myc.Ast.Nodes,
|
Myc.Ast.Nodes,
|
||||||
Myc.Fmx.AstEditor.Workspace,
|
Myc.Fmx.AstEditor.Render,
|
||||||
Myc.Fmx.AstEditor.Layout;
|
Myc.Fmx.AstEditor.Workspace;
|
||||||
|
|
||||||
const
|
const
|
||||||
cNodePadding = 10;
|
cNodePadding = 10;
|
||||||
@@ -26,7 +24,7 @@ const
|
|||||||
cTitleBottomPadding = 4;
|
cTitleBottomPadding = 4;
|
||||||
cMinNodeWidth = 10;
|
cMinNodeWidth = 10;
|
||||||
cMinNodeHeight = 10;
|
cMinNodeHeight = 10;
|
||||||
cDragGutterWidth = 20; // Width of the trigger zone for dragging
|
cDragGutterWidth = 20;
|
||||||
|
|
||||||
type
|
type
|
||||||
TAstViewNode = class;
|
TAstViewNode = class;
|
||||||
@@ -40,26 +38,26 @@ type
|
|||||||
|
|
||||||
IReorderable = interface
|
IReorderable = interface
|
||||||
['{9FA7C504-D327-4E00-A5FE-DD2876886212}']
|
['{9FA7C504-D327-4E00-A5FE-DD2876886212}']
|
||||||
function CanDrag(Child: TControl): Boolean;
|
function CanDrag(Child: TVisualNode): Boolean;
|
||||||
procedure MoveChild(SourceIndex, TargetIndex: Integer);
|
procedure MoveChild(SourceIndex, TargetIndex: Integer);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
IAstVisualizer = interface
|
IAstVisualizer = interface
|
||||||
{$region 'private'}
|
{$region 'private'}
|
||||||
function GetExprDepth: Integer;
|
function GetExprDepth: Integer;
|
||||||
function GetParentControl: TControl;
|
function GetParentNode: TVisualNode;
|
||||||
function GetWorkspace: TWorkspace;
|
function GetWorkspace: TWorkspace;
|
||||||
{$endregion}
|
{$endregion}
|
||||||
|
|
||||||
function Clone(ParentControl: TControl; ExprDepth: Integer): IAstVisualizer;
|
function Clone(ParentNode: TVisualNode; ExprDepth: Integer): IAstVisualizer;
|
||||||
function CallAccept(const Node: IAstNode): TAstViewNode;
|
function CallAccept(const Node: IAstNode): TAstViewNode;
|
||||||
|
|
||||||
property ExprDepth: Integer read GetExprDepth;
|
property ExprDepth: Integer read GetExprDepth;
|
||||||
property ParentControl: TControl read GetParentControl;
|
property ParentNode: TVisualNode read GetParentNode;
|
||||||
property Workspace: TWorkspace read GetWorkspace;
|
property Workspace: TWorkspace read GetWorkspace;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TAstViewNode = class(TAutoFitControl)
|
TAstViewNode = class(TAutoFitLayout)
|
||||||
private
|
private
|
||||||
FBackgroundColor: TAlphaColor;
|
FBackgroundColor: TAlphaColor;
|
||||||
FBorderColor: TAlphaColor;
|
FBorderColor: TAlphaColor;
|
||||||
@@ -73,6 +71,7 @@ type
|
|||||||
FTypeInfoText: string;
|
FTypeInfoText: string;
|
||||||
|
|
||||||
FIsFocused: Boolean;
|
FIsFocused: Boolean;
|
||||||
|
FIsHovered: Boolean;
|
||||||
|
|
||||||
procedure SetBackgroundColor(const Value: TAlphaColor);
|
procedure SetBackgroundColor(const Value: TAlphaColor);
|
||||||
procedure SetBorderColor(const Value: TAlphaColor);
|
procedure SetBorderColor(const Value: TAlphaColor);
|
||||||
@@ -83,28 +82,27 @@ type
|
|||||||
|
|
||||||
procedure SetErrorMessage(const Value: string);
|
procedure SetErrorMessage(const Value: string);
|
||||||
procedure SetTypeInfoText(const Value: string);
|
procedure SetTypeInfoText(const Value: string);
|
||||||
procedure UpdateVisualState;
|
|
||||||
procedure SetIsFocused(const Value: Boolean);
|
procedure SetIsFocused(const Value: Boolean);
|
||||||
|
procedure InvalidateVisuals;
|
||||||
|
|
||||||
protected
|
protected
|
||||||
procedure Paint; override;
|
procedure DoPaint(Canvas: TCanvas; const Offset: TPointF); override;
|
||||||
procedure SetupNode;
|
procedure SetupNode;
|
||||||
|
|
||||||
procedure DoMouseEnter; override;
|
|
||||||
procedure DoMouseLeave; 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
|
public
|
||||||
constructor Create(const AVisualizer: IAstVisualizer; const AHandler: IAstViewNodeHandler); reintroduce;
|
constructor Create(const AVisualizer: IAstVisualizer; const AHandler: IAstViewNodeHandler); reintroduce;
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
|
|
||||||
procedure AfterConstruction; override;
|
procedure AfterConstruction; override;
|
||||||
|
|
||||||
function AddLabel(Parent: TControl; const Txt: String): TLabel;
|
procedure MouseEnter; override;
|
||||||
function AddContainer(Parent: TControl; Orientation: TLayoutOrientation; Alignment: TLayoutAlignment): TAutoFitControl;
|
procedure MouseLeave; override;
|
||||||
function AddExpr(Parent: TControl; const Title: String; const Node: IAstNode): TAstViewNode;
|
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
|
||||||
|
procedure MouseMove(Shift: TShiftState; X, Y: Single); override;
|
||||||
|
|
||||||
|
function AddLabel(Parent: TVisualNode; const Txt: String): TTextNode;
|
||||||
|
function AddContainer(Parent: TVisualNode; Orientation: TLayoutOrientation; Alignment: TLayoutAlignment): TAutoFitLayout;
|
||||||
|
function AddExpr(Parent: TVisualNode; const Title: String; const Node: IAstNode): TAstViewNode;
|
||||||
|
|
||||||
function CreateAst: IAstNode;
|
function CreateAst: IAstNode;
|
||||||
|
|
||||||
@@ -116,42 +114,11 @@ type
|
|||||||
property TypeInfoText: string read FTypeInfoText write SetTypeInfoText;
|
property TypeInfoText: string read FTypeInfoText write SetTypeInfoText;
|
||||||
property IsFocused: Boolean read FIsFocused write SetIsFocused;
|
property IsFocused: Boolean read FIsFocused write SetIsFocused;
|
||||||
|
|
||||||
published
|
|
||||||
property BorderColor: TAlphaColor read FBorderColor write SetBorderColor;
|
property BorderColor: TAlphaColor read FBorderColor write SetBorderColor;
|
||||||
property BorderWidth: Single read FBorderWidth write SetBorderWidth;
|
property BorderWidth: Single read FBorderWidth write SetBorderWidth;
|
||||||
property BorderRadius: Single read FBorderRadius write SetBorderRadius;
|
property BorderRadius: Single read FBorderRadius write SetBorderRadius;
|
||||||
property BackgroundColor: TAlphaColor read FBackgroundColor write SetBackgroundColor;
|
property BackgroundColor: TAlphaColor read FBackgroundColor write SetBackgroundColor;
|
||||||
property Frameless: Boolean read FFrameless write SetFrameless;
|
property Frameless: Boolean read FFrameless write SetFrameless;
|
||||||
|
|
||||||
property Align;
|
|
||||||
property Anchors;
|
|
||||||
property ClipChildren default True;
|
|
||||||
property Cursor default crDefault;
|
|
||||||
property DragMode default TDragMode.dmManual;
|
|
||||||
property Enabled;
|
|
||||||
property Height;
|
|
||||||
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 Visible;
|
|
||||||
property Width;
|
|
||||||
property OnClick;
|
|
||||||
property OnDblClick;
|
|
||||||
property OnMouseDown;
|
|
||||||
property OnMouseMove;
|
|
||||||
property OnMouseUp;
|
|
||||||
property OnMouseWheel;
|
|
||||||
property OnMouseEnter;
|
|
||||||
property OnMouseLeave;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TBaseNodeHandler<T: IAstNode> = class(TInterfacedObject, IAstViewNodeHandler)
|
TBaseNodeHandler<T: IAstNode> = class(TInterfacedObject, IAstViewNodeHandler)
|
||||||
@@ -173,9 +140,10 @@ uses
|
|||||||
|
|
||||||
constructor TAstViewNode.Create(const AVisualizer: IAstVisualizer; const AHandler: IAstViewNodeHandler);
|
constructor TAstViewNode.Create(const AVisualizer: IAstVisualizer; const AHandler: IAstViewNodeHandler);
|
||||||
begin
|
begin
|
||||||
inherited Create(AVisualizer.Workspace);
|
inherited Create(AVisualizer.Workspace as IVisualHost);
|
||||||
|
|
||||||
Parent := AVisualizer.ParentControl;
|
if Assigned(AVisualizer.ParentNode) then
|
||||||
|
AVisualizer.ParentNode.AddChild(Self);
|
||||||
|
|
||||||
FVisualizer := AVisualizer.Clone(Self, AVisualizer.ExprDepth);
|
FVisualizer := AVisualizer.Clone(Self, AVisualizer.ExprDepth);
|
||||||
FHandler := AHandler;
|
FHandler := AHandler;
|
||||||
@@ -187,25 +155,18 @@ begin
|
|||||||
|
|
||||||
FFrameless := False;
|
FFrameless := False;
|
||||||
FIsFocused := False;
|
FIsFocused := False;
|
||||||
|
FIsHovered := False;
|
||||||
|
|
||||||
Width := cMinNodeWidth;
|
Size := TSizeF.Create(cMinNodeWidth, cMinNodeHeight);
|
||||||
Height := cMinNodeHeight;
|
|
||||||
|
|
||||||
HitTest := True;
|
HitTestEnabled := True;
|
||||||
ClipChildren := True;
|
|
||||||
|
|
||||||
Margins.Left := 4;
|
Margins := TMarginRect.Create(4, 4, 4, 4);
|
||||||
Margins.Top := 4;
|
Padding := TMarginRect.Create(3, 3, 3, 3);
|
||||||
Margins.Right := 4;
|
|
||||||
Margins.Bottom := 4;
|
|
||||||
Padding.Left := 3;
|
|
||||||
Padding.Top := 3;
|
|
||||||
Padding.Right := 3;
|
|
||||||
Padding.Bottom := 3;
|
|
||||||
|
|
||||||
var cn: Cardinal := $ea - (7 * FVisualizer.ExprDepth);
|
var cn: Cardinal := $ea - (7 * FVisualizer.ExprDepth);
|
||||||
var c: Cardinal := $ff000000 or (cn shl 16) or (cn shl 8) or cn;
|
var c: Cardinal := $ff000000 or (cn shl 16) or (cn shl 8) or cn;
|
||||||
BackgroundColor := c;
|
FBackgroundColor := c;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TAstViewNode.Destroy;
|
destructor TAstViewNode.Destroy;
|
||||||
@@ -214,103 +175,18 @@ begin
|
|||||||
inherited;
|
inherited;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TAstViewNode.DoMouseEnter;
|
|
||||||
begin
|
|
||||||
inherited;
|
|
||||||
Repaint;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstViewNode.DoMouseLeave;
|
|
||||||
begin
|
|
||||||
inherited;
|
|
||||||
Repaint;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstViewNode.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
|
||||||
const
|
|
||||||
cHeaderHeight = 30; // Defined height for header drag area
|
|
||||||
begin
|
|
||||||
// Check for Drag Start.
|
|
||||||
// Condition: Left Click AND we are part of a container (list)
|
|
||||||
if (Button = TMouseButton.mbLeft) and (Parent is TAutoFitControl) then
|
|
||||||
begin
|
|
||||||
// Hit-Test: Either Gutter (Left) OR Header area (Top)
|
|
||||||
if (X < cDragGutterWidth) or (Y < cHeaderHeight) then
|
|
||||||
begin
|
|
||||||
if Assigned(FVisualizer.Workspace) then
|
|
||||||
begin
|
|
||||||
FVisualizer.Workspace.BeginDragNode(Self);
|
|
||||||
Exit;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
inherited;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstViewNode.MouseMove(Shift: TShiftState; X, Y: Single);
|
|
||||||
const
|
|
||||||
cHeaderHeight = 30;
|
|
||||||
begin
|
|
||||||
// Cursor Logic (Hover Feedback)
|
|
||||||
if (Parent is TAutoFitControl) and ((X < cDragGutterWidth) or (Y < cHeaderHeight)) then
|
|
||||||
Cursor := crSizeAll
|
|
||||||
else
|
|
||||||
Cursor := crDefault;
|
|
||||||
|
|
||||||
inherited;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstViewNode.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
|
||||||
begin
|
|
||||||
inherited;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TAstViewNode.AddContainer(Parent: TControl; Orientation: TLayoutOrientation; Alignment: TLayoutAlignment): TAutoFitControl;
|
|
||||||
begin
|
|
||||||
Result := TAutoFitControl.Create(Self);
|
|
||||||
Result.Parent := Parent;
|
|
||||||
Result.Orientation := Orientation;
|
|
||||||
Result.Alignment := Alignment;
|
|
||||||
Result.HitTest := False;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TAstViewNode.AddExpr(Parent: TControl; const Title: String; const Node: IAstNode): TAstViewNode;
|
|
||||||
begin
|
|
||||||
var cont := AddContainer(Parent, loHorizontal, laCenter);
|
|
||||||
|
|
||||||
var lbl := AddLabel(cont, Title);
|
|
||||||
lbl.Font.Style := lbl.Font.Style + [TFontStyle.fsBold];
|
|
||||||
|
|
||||||
Result := FVisualizer.Clone(cont, FVisualizer.ExprDepth + 1).CallAccept(Node);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TAstViewNode.AddLabel(Parent: TControl; const Txt: String): TLabel;
|
|
||||||
begin
|
|
||||||
Result := TLabel.Create(Self);
|
|
||||||
Result.Parent := Parent;
|
|
||||||
|
|
||||||
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;
|
|
||||||
Result.HitTest := False;
|
|
||||||
|
|
||||||
Result.StyledSettings := Result.StyledSettings - [TStyledSetting.Style];
|
|
||||||
|
|
||||||
Result.Text := Txt;
|
|
||||||
Result.ApplyStyleLookup;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstViewNode.AfterConstruction;
|
procedure TAstViewNode.AfterConstruction;
|
||||||
begin
|
begin
|
||||||
inherited;
|
inherited;
|
||||||
SetupNode;
|
SetupNode;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TAstViewNode.SetupNode;
|
||||||
|
begin
|
||||||
|
if Assigned(FHandler) then
|
||||||
|
FHandler.BuildUI(Self);
|
||||||
|
end;
|
||||||
|
|
||||||
function TAstViewNode.CreateAst: IAstNode;
|
function TAstViewNode.CreateAst: IAstNode;
|
||||||
begin
|
begin
|
||||||
if Assigned(FHandler) then
|
if Assigned(FHandler) then
|
||||||
@@ -327,154 +203,142 @@ begin
|
|||||||
Result := nil;
|
Result := nil;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TAstViewNode.SetErrorMessage(const Value: string);
|
function TAstViewNode.AddContainer(Parent: TVisualNode; Orientation: TLayoutOrientation; Alignment: TLayoutAlignment): TAutoFitLayout;
|
||||||
begin
|
begin
|
||||||
if FErrorMessage <> Value then
|
Result := TAutoFitLayout.Create(Host);
|
||||||
begin
|
Parent.AddChild(Result);
|
||||||
FErrorMessage := Value;
|
Result.Orientation := Orientation;
|
||||||
UpdateVisualState;
|
Result.Alignment := Alignment;
|
||||||
Repaint;
|
Result.HitTestEnabled := False;
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TAstViewNode.SetTypeInfoText(const Value: string);
|
function TAstViewNode.AddExpr(Parent: TVisualNode; const Title: String; const Node: IAstNode): TAstViewNode;
|
||||||
begin
|
begin
|
||||||
if FTypeInfoText <> Value then
|
var cont := AddContainer(Parent, loHorizontal, laCenter);
|
||||||
begin
|
|
||||||
FTypeInfoText := Value;
|
var lbl := AddLabel(cont, Title);
|
||||||
UpdateVisualState;
|
lbl.FontSettings.Font.Style := [TFontStyle.fsBold];
|
||||||
end;
|
|
||||||
|
Result := FVisualizer.Clone(cont, FVisualizer.ExprDepth + 1).CallAccept(Node);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TAstViewNode.SetIsFocused(const Value: Boolean);
|
function TAstViewNode.AddLabel(Parent: TVisualNode; const Txt: String): TTextNode;
|
||||||
begin
|
begin
|
||||||
if FIsFocused <> Value then
|
Result := TTextNode.Create(Host);
|
||||||
begin
|
Parent.AddChild(Result);
|
||||||
FIsFocused := Value;
|
|
||||||
Repaint;
|
Result.Margins := TMarginRect.Create(cNodePadding, cTitleTopPadding, cNodePadding, cTitleBottomPadding);
|
||||||
end;
|
Result.Padding := TMarginRect.Create(0, 0, 0, 0);
|
||||||
|
|
||||||
|
Result.Text := Txt;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TAstViewNode.UpdateVisualState;
|
procedure TAstViewNode.DoPaint(Canvas: TCanvas; const Offset: TPointF);
|
||||||
begin
|
|
||||||
if FErrorMessage <> '' then
|
|
||||||
begin
|
|
||||||
Hint := 'Error: ' + FErrorMessage;
|
|
||||||
ShowHint := True;
|
|
||||||
end
|
|
||||||
else if FTypeInfoText <> '' then
|
|
||||||
begin
|
|
||||||
Hint := 'Type: ' + FTypeInfoText;
|
|
||||||
ShowHint := True;
|
|
||||||
end
|
|
||||||
else
|
|
||||||
begin
|
|
||||||
Hint := '';
|
|
||||||
ShowHint := False;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstViewNode.Paint;
|
|
||||||
var
|
var
|
||||||
rect: TRectF;
|
Rect: TRectF;
|
||||||
effBorderColor: TAlphaColor;
|
EffBorderColor: TAlphaColor;
|
||||||
effBorderWidth: Single;
|
EffBorderWidth: Single;
|
||||||
effDash: TStrokeDash;
|
EffDash: TStrokeDash;
|
||||||
isHovering: Boolean;
|
ShouldIgnoreHover: Boolean;
|
||||||
shouldIgnoreHover: Boolean;
|
|
||||||
begin
|
begin
|
||||||
inherited;
|
Rect := TRectF.Create(Offset, Size.Width, Size.Height);
|
||||||
|
|
||||||
shouldIgnoreHover := False;
|
ShouldIgnoreHover := False;
|
||||||
if Assigned(Node) then
|
if Assigned(Node) then
|
||||||
begin
|
begin
|
||||||
case Node.Kind of
|
case Node.Kind of
|
||||||
akBlockExpression, akParameterList, akArgumentList, akExpressionList, akRecordFieldList: shouldIgnoreHover := True;
|
akBlockExpression, akParameterList, akArgumentList, akExpressionList, akRecordFieldList: ShouldIgnoreHover := True;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
isHovering := IsMouseOver and (not shouldIgnoreHover);
|
|
||||||
|
|
||||||
// 1. Determine Base Style
|
|
||||||
if FFrameless then
|
if FFrameless then
|
||||||
begin
|
begin
|
||||||
effBorderColor := FBorderColor;
|
EffBorderColor := FBorderColor;
|
||||||
effBorderWidth := FBorderWidth;
|
EffBorderWidth := FBorderWidth;
|
||||||
effDash := TStrokeDash.Dot;
|
EffDash := TStrokeDash.Dot;
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
effBorderColor := FBorderColor;
|
EffBorderColor := FBorderColor;
|
||||||
effBorderWidth := FBorderWidth;
|
EffBorderWidth := FBorderWidth;
|
||||||
effDash := TStrokeDash.Solid;
|
EffDash := TStrokeDash.Solid;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// 2. Hover Overlay
|
if FIsHovered and (not ShouldIgnoreHover) then
|
||||||
if isHovering then
|
|
||||||
begin
|
begin
|
||||||
effBorderColor := TAlphaColors.White;
|
EffBorderColor := TAlphaColors.White;
|
||||||
effBorderWidth := Max(2.0, FBorderWidth + 1.0);
|
EffBorderWidth := Max(2.0, FBorderWidth + 1.0);
|
||||||
effDash := TStrokeDash.Solid;
|
EffDash := TStrokeDash.Solid;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// 3. Error Overlay (High Priority for Color)
|
|
||||||
if FErrorMessage <> '' then
|
if FErrorMessage <> '' then
|
||||||
begin
|
begin
|
||||||
effBorderColor := TAlphaColors.Red;
|
EffBorderColor := TAlphaColors.Red;
|
||||||
effBorderWidth := 2.0;
|
EffBorderWidth := 2.0;
|
||||||
effDash := TStrokeDash.Solid;
|
EffDash := TStrokeDash.Solid;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// 4. Focus Overlay (Highest Priority for Visibility/Color)
|
|
||||||
// Note: If Error is present, Focus will override color to Blue to indicate "Active Cursor".
|
|
||||||
// Error text is available in Hint.
|
|
||||||
if FIsFocused then
|
if FIsFocused then
|
||||||
begin
|
begin
|
||||||
effBorderColor := TAlphaColors.Dodgerblue;
|
EffBorderColor := TAlphaColors.Dodgerblue;
|
||||||
effBorderWidth := 2.0;
|
EffBorderWidth := 2.0;
|
||||||
effDash := TStrokeDash.Solid;
|
EffDash := TStrokeDash.Solid;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Canvas.Fill.Kind := TBrushKind.Solid;
|
Canvas.Fill.Kind := TBrushKind.Solid;
|
||||||
Canvas.Fill.Color := FBackgroundColor;
|
Canvas.Fill.Color := FBackgroundColor;
|
||||||
|
|
||||||
if FFrameless and (FErrorMessage = '') and (not isHovering) and (not FIsFocused) then
|
if FFrameless and (FErrorMessage = '') and (not FIsHovered) and (not FIsFocused) then
|
||||||
begin
|
begin
|
||||||
Canvas.FillRect(TRectF.Create(0, 0, Width, Height), 0, 0, [], 1.0);
|
Canvas.FillRect(Rect, 0, 0, AllCorners, 1.0);
|
||||||
|
|
||||||
if FBorderWidth > 0 then
|
if FBorderWidth > 0 then
|
||||||
begin
|
begin
|
||||||
Canvas.Stroke.Kind := TBrushKind.Solid;
|
Canvas.Stroke.Kind := TBrushKind.Solid;
|
||||||
Canvas.Stroke.Color := effBorderColor;
|
Canvas.Stroke.Color := EffBorderColor;
|
||||||
Canvas.Stroke.Thickness := effBorderWidth;
|
Canvas.Stroke.Thickness := EffBorderWidth;
|
||||||
Canvas.Stroke.Dash := effDash;
|
Canvas.Stroke.Dash := EffDash;
|
||||||
Canvas.DrawRect(TRectF.Create(0, 0, Width, Height), 0, 0, [], 1.0);
|
Canvas.DrawRect(Rect, 0, 0, AllCorners, 1.0);
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
rect := TRectF.Create(0, 0, Width, Height);
|
var DrawRect := Rect;
|
||||||
if (effBorderWidth > 0) then
|
if EffBorderWidth > 0 then
|
||||||
rect.Inflate(-effBorderWidth / 2, -effBorderWidth / 2);
|
DrawRect.Inflate(-EffBorderWidth / 2, -EffBorderWidth / 2);
|
||||||
|
|
||||||
Canvas.FillRect(rect, FBorderRadius, FBorderRadius, AllCorners, 1.0, TCornerType.Round);
|
Canvas.FillRect(DrawRect, FBorderRadius, FBorderRadius, AllCorners, 1.0, TCornerType.Round);
|
||||||
|
|
||||||
if (effBorderWidth > 0) and (effBorderColor <> TAlphaColors.Null) then
|
if (EffBorderWidth > 0) and (EffBorderColor <> TAlphaColors.Null) then
|
||||||
begin
|
begin
|
||||||
Canvas.Stroke.Kind := TBrushKind.Solid;
|
Canvas.Stroke.Kind := TBrushKind.Solid;
|
||||||
Canvas.Stroke.Color := effBorderColor;
|
Canvas.Stroke.Color := EffBorderColor;
|
||||||
Canvas.Stroke.Thickness := effBorderWidth;
|
Canvas.Stroke.Thickness := EffBorderWidth;
|
||||||
Canvas.Stroke.Dash := effDash;
|
Canvas.Stroke.Dash := EffDash;
|
||||||
Canvas.DrawRect(rect, FBorderRadius, FBorderRadius, AllCorners, 1.0, TCornerType.Round);
|
Canvas.DrawRect(DrawRect, FBorderRadius, FBorderRadius, AllCorners, 1.0, TCornerType.Round);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if isHovering and (Parent is TAutoFitControl) then
|
if FIsHovered and (Parent is TAutoFitLayout) then
|
||||||
begin
|
begin
|
||||||
Canvas.Fill.Color := TAlphaColors.DimGray;
|
Canvas.Fill.Color := TAlphaColors.DimGray;
|
||||||
Canvas.FillEllipse(TRectF.Create(4, 6, 8, 10), 1.0);
|
Canvas.FillEllipse(TRectF.Create(Offset.X + 4, Offset.Y + 6, Offset.X + 12, Offset.Y + 14), 1.0);
|
||||||
Canvas.FillEllipse(TRectF.Create(4, 12, 8, 16), 1.0);
|
Canvas.FillEllipse(TRectF.Create(Offset.X + 4, Offset.Y + 16, Offset.X + 12, Offset.Y + 24), 1.0);
|
||||||
Canvas.FillEllipse(TRectF.Create(4, 18, 8, 22), 1.0);
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAstViewNode.InvalidateVisuals;
|
||||||
|
begin
|
||||||
|
if Assigned(Host) then
|
||||||
|
Host.InvalidateRect(AbsoluteRect);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAstViewNode.SetBackgroundColor(const Value: TAlphaColor);
|
||||||
|
begin
|
||||||
|
if FBackgroundColor <> Value then
|
||||||
|
begin
|
||||||
|
FBackgroundColor := Value;
|
||||||
|
InvalidateVisuals;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -483,7 +347,7 @@ begin
|
|||||||
if FBorderColor <> Value then
|
if FBorderColor <> Value then
|
||||||
begin
|
begin
|
||||||
FBorderColor := Value;
|
FBorderColor := Value;
|
||||||
Repaint;
|
InvalidateVisuals;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -492,7 +356,7 @@ begin
|
|||||||
if FBorderRadius <> Value then
|
if FBorderRadius <> Value then
|
||||||
begin
|
begin
|
||||||
FBorderRadius := Value;
|
FBorderRadius := Value;
|
||||||
Repaint;
|
InvalidateVisuals;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -501,7 +365,17 @@ begin
|
|||||||
if FBorderWidth <> Value then
|
if FBorderWidth <> Value then
|
||||||
begin
|
begin
|
||||||
FBorderWidth := Value;
|
FBorderWidth := Value;
|
||||||
Repaint;
|
RequestLayout;
|
||||||
|
InvalidateVisuals;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAstViewNode.SetErrorMessage(const Value: string);
|
||||||
|
begin
|
||||||
|
if FErrorMessage <> Value then
|
||||||
|
begin
|
||||||
|
FErrorMessage := Value;
|
||||||
|
InvalidateVisuals;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -510,20 +384,60 @@ begin
|
|||||||
if FFrameless <> Value then
|
if FFrameless <> Value then
|
||||||
begin
|
begin
|
||||||
FFrameless := Value;
|
FFrameless := Value;
|
||||||
Repaint;
|
InvalidateVisuals;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TAstViewNode.SetBackgroundColor(const Value: TAlphaColor);
|
procedure TAstViewNode.SetIsFocused(const Value: Boolean);
|
||||||
begin
|
begin
|
||||||
FBackgroundColor := Value;
|
if FIsFocused <> Value then
|
||||||
Repaint;
|
begin
|
||||||
|
FIsFocused := Value;
|
||||||
|
InvalidateVisuals;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TAstViewNode.SetupNode;
|
procedure TAstViewNode.SetTypeInfoText(const Value: string);
|
||||||
begin
|
begin
|
||||||
if Assigned(FHandler) then
|
if FTypeInfoText <> Value then
|
||||||
FHandler.BuildUI(Self);
|
begin
|
||||||
|
FTypeInfoText := Value;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAstViewNode.MouseEnter;
|
||||||
|
begin
|
||||||
|
FIsHovered := True;
|
||||||
|
InvalidateVisuals;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAstViewNode.MouseLeave;
|
||||||
|
begin
|
||||||
|
FIsHovered := False;
|
||||||
|
InvalidateVisuals;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAstViewNode.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
||||||
|
const
|
||||||
|
cHeaderHeight = 30;
|
||||||
|
begin
|
||||||
|
if (Button = TMouseButton.mbLeft) and (Parent is TAutoFitLayout) then
|
||||||
|
begin
|
||||||
|
if (X < cDragGutterWidth) or (Y < cHeaderHeight) then
|
||||||
|
begin
|
||||||
|
if Assigned(FVisualizer.Workspace) then
|
||||||
|
begin
|
||||||
|
FVisualizer.Workspace.BeginDragNode(Self);
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
inherited;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAstViewNode.MouseMove(Shift: TShiftState; X, Y: Single);
|
||||||
|
begin
|
||||||
|
inherited;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TBaseNodeHandler<T> }
|
{ TBaseNodeHandler<T> }
|
||||||
|
|||||||
@@ -0,0 +1,626 @@
|
|||||||
|
unit Myc.Fmx.AstEditor.Render;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.Types,
|
||||||
|
System.UITypes,
|
||||||
|
System.Classes,
|
||||||
|
System.SysUtils,
|
||||||
|
System.Math,
|
||||||
|
System.Generics.Collections,
|
||||||
|
FMX.Types,
|
||||||
|
FMX.Graphics,
|
||||||
|
FMX.TextLayout;
|
||||||
|
|
||||||
|
type
|
||||||
|
TVisualNode = class;
|
||||||
|
|
||||||
|
// Interface for the host (e.g. TWorkspace) to request repaints/layouts
|
||||||
|
IVisualHost = interface
|
||||||
|
['{A9E2BD60-281B-42A2-9DB5-3100E8EF1519}']
|
||||||
|
procedure InvalidateRect(const R: TRectF);
|
||||||
|
procedure RequestLayout;
|
||||||
|
function GetCanvas: TCanvas;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Helper structure for margins and padding
|
||||||
|
TMarginRect = record
|
||||||
|
Left, Top, Right, Bottom: Single;
|
||||||
|
function Width: Single; inline;
|
||||||
|
function Height: Single; inline;
|
||||||
|
class function Create(L, T, R, B: Single): TMarginRect; static; inline;
|
||||||
|
class operator Initialize(out Dest: TMarginRect);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// The base class for all virtual UI elements (Flyweight)
|
||||||
|
TVisualNode = class
|
||||||
|
private
|
||||||
|
FHost: IVisualHost;
|
||||||
|
FParent: TVisualNode;
|
||||||
|
FChildren: TList<TVisualNode>;
|
||||||
|
|
||||||
|
// Data stored as Position and Size (Layout Model)
|
||||||
|
// Direct field access is preferred within the unit
|
||||||
|
FPosition: TPointF;
|
||||||
|
FSize: TSizeF;
|
||||||
|
|
||||||
|
FPadding: TMarginRect;
|
||||||
|
FMargins: TMarginRect;
|
||||||
|
FVisible: Boolean;
|
||||||
|
FHitTest: Boolean;
|
||||||
|
FTag: NativeInt;
|
||||||
|
FOpacity: Single;
|
||||||
|
|
||||||
|
procedure SetParent(const Value: TVisualNode);
|
||||||
|
|
||||||
|
protected
|
||||||
|
procedure SetHost(const Value: IVisualHost); virtual;
|
||||||
|
// Called to paint the node-specific content (background, etc.)
|
||||||
|
procedure DoPaint(Canvas: TCanvas; const Offset: TPointF); virtual;
|
||||||
|
public
|
||||||
|
constructor Create(AHost: IVisualHost = nil); virtual;
|
||||||
|
destructor Destroy; override;
|
||||||
|
|
||||||
|
// Layout system
|
||||||
|
procedure RequestLayout;
|
||||||
|
procedure RecalcLayout; virtual;
|
||||||
|
|
||||||
|
// Geometry
|
||||||
|
function GetAbsolutePosition: TPointF;
|
||||||
|
function LocalToAbsolute(const P: TPointF): TPointF;
|
||||||
|
function AbsoluteToLocal(const P: TPointF): TPointF;
|
||||||
|
function AbsoluteRect: TRectF;
|
||||||
|
|
||||||
|
function HitTest(const P: TPointF): TVisualNode; virtual;
|
||||||
|
|
||||||
|
// Rendering pipeline
|
||||||
|
// Offset is the absolute position of the parent content area
|
||||||
|
procedure Paint(Canvas: TCanvas; const Offset: TPointF);
|
||||||
|
|
||||||
|
// Child Management
|
||||||
|
procedure AddChild(Child: TVisualNode);
|
||||||
|
procedure RemoveChild(Child: TVisualNode);
|
||||||
|
procedure DeleteChildren;
|
||||||
|
function GetChildCount: Integer; inline;
|
||||||
|
function GetChild(Index: Integer): TVisualNode; inline;
|
||||||
|
|
||||||
|
// Interaction stubs (called by the host)
|
||||||
|
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); virtual;
|
||||||
|
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); virtual;
|
||||||
|
procedure MouseMove(Shift: TShiftState; X, Y: Single); virtual;
|
||||||
|
procedure MouseEnter; virtual;
|
||||||
|
procedure MouseLeave; virtual;
|
||||||
|
|
||||||
|
property Host: IVisualHost read FHost;
|
||||||
|
property Parent: TVisualNode read FParent;
|
||||||
|
|
||||||
|
// Direct field access via properties
|
||||||
|
property Position: TPointF read FPosition write FPosition;
|
||||||
|
property Size: TSizeF read FSize write FSize;
|
||||||
|
|
||||||
|
property Padding: TMarginRect read FPadding write FPadding;
|
||||||
|
property Margins: TMarginRect read FMargins write FMargins;
|
||||||
|
|
||||||
|
property Visible: Boolean read FVisible write FVisible;
|
||||||
|
property HitTestEnabled: Boolean read FHitTest write FHitTest;
|
||||||
|
property Opacity: Single read FOpacity write FOpacity;
|
||||||
|
property Tag: NativeInt read FTag write FTag;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Lightweight replacement for TLabel
|
||||||
|
TTextNode = class(TVisualNode)
|
||||||
|
private
|
||||||
|
FText: string;
|
||||||
|
FLayout: TTextLayout;
|
||||||
|
FFontSettings: TTextSettings; // Stores font properties
|
||||||
|
FColor: TAlphaColor;
|
||||||
|
FIsLayoutDirty: Boolean;
|
||||||
|
procedure SetText(const Value: string);
|
||||||
|
procedure SetColor(const Value: TAlphaColor);
|
||||||
|
protected
|
||||||
|
procedure DoPaint(Canvas: TCanvas; const Offset: TPointF); override;
|
||||||
|
procedure UpdateTextLayout(Canvas: TCanvas);
|
||||||
|
public
|
||||||
|
constructor Create(AHost: IVisualHost = nil); override;
|
||||||
|
destructor Destroy; override;
|
||||||
|
procedure RecalcLayout; override;
|
||||||
|
|
||||||
|
property Text: string read FText write SetText;
|
||||||
|
property Color: TAlphaColor read FColor write SetColor;
|
||||||
|
property FontSettings: TTextSettings read FFontSettings;
|
||||||
|
end;
|
||||||
|
|
||||||
|
TLayoutOrientation = (loVertical, loHorizontal);
|
||||||
|
TLayoutAlignment = (laCenter, laFlush);
|
||||||
|
|
||||||
|
// The Layout Manager
|
||||||
|
TAutoFitLayout = class(TVisualNode)
|
||||||
|
private
|
||||||
|
FOrientation: TLayoutOrientation;
|
||||||
|
FAlignment: TLayoutAlignment;
|
||||||
|
procedure SetOrientation(const Value: TLayoutOrientation);
|
||||||
|
procedure SetAlignment(const Value: TLayoutAlignment);
|
||||||
|
protected
|
||||||
|
// TAutoFitLayout is a container, draws no background by default, but propagates Paint to children
|
||||||
|
procedure DoPaint(Canvas: TCanvas; const Offset: TPointF); override;
|
||||||
|
public
|
||||||
|
constructor Create(AHost: IVisualHost = nil); override;
|
||||||
|
procedure RecalcLayout; override;
|
||||||
|
|
||||||
|
// Calculates the insertion index for drag & drop based on coordinates
|
||||||
|
function GetInsertionIndex(const LocalP: TPointF): Integer;
|
||||||
|
|
||||||
|
property Orientation: TLayoutOrientation read FOrientation write SetOrientation;
|
||||||
|
property Alignment: TLayoutAlignment read FAlignment write SetAlignment;
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
{ TMarginRect }
|
||||||
|
|
||||||
|
class function TMarginRect.Create(L, T, R, B: Single): TMarginRect;
|
||||||
|
begin
|
||||||
|
Result.Left := L;
|
||||||
|
Result.Top := T;
|
||||||
|
Result.Right := R;
|
||||||
|
Result.Bottom := B;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class operator TMarginRect.Initialize(out Dest: TMarginRect);
|
||||||
|
begin
|
||||||
|
Dest.Left := 0;
|
||||||
|
Dest.Top := 0;
|
||||||
|
Dest.Right := 0;
|
||||||
|
Dest.Bottom := 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMarginRect.Height: Single;
|
||||||
|
begin
|
||||||
|
Result := Top + Bottom;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMarginRect.Width: Single;
|
||||||
|
begin
|
||||||
|
Result := Left + Right;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TVisualNode }
|
||||||
|
|
||||||
|
constructor TVisualNode.Create(AHost: IVisualHost);
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FHost := AHost;
|
||||||
|
FChildren := TList<TVisualNode>.Create;
|
||||||
|
FVisible := True;
|
||||||
|
FHitTest := True;
|
||||||
|
FOpacity := 1.0;
|
||||||
|
FPosition := TPointF.Create(0, 0);
|
||||||
|
FSize := TSizeF.Create(10, 10);
|
||||||
|
end;
|
||||||
|
|
||||||
|
destructor TVisualNode.Destroy;
|
||||||
|
begin
|
||||||
|
// Recursive release
|
||||||
|
for var i := 0 to FChildren.Count - 1 do
|
||||||
|
FChildren[i].Free;
|
||||||
|
FChildren.Free;
|
||||||
|
inherited;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TVisualNode.SetParent(const Value: TVisualNode);
|
||||||
|
begin
|
||||||
|
if FParent <> Value then
|
||||||
|
begin
|
||||||
|
FParent := Value;
|
||||||
|
// Propagate host if parent has one
|
||||||
|
if (FHost = nil) and Assigned(FParent) then
|
||||||
|
SetHost(FParent.Host);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TVisualNode.SetHost(const Value: IVisualHost);
|
||||||
|
begin
|
||||||
|
FHost := Value;
|
||||||
|
for var Child in FChildren do
|
||||||
|
Child.SetHost(Value);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TVisualNode.AddChild(Child: TVisualNode);
|
||||||
|
begin
|
||||||
|
if Child.Parent <> nil then
|
||||||
|
Child.Parent.RemoveChild(Child);
|
||||||
|
|
||||||
|
FChildren.Add(Child);
|
||||||
|
Child.SetParent(Self);
|
||||||
|
RequestLayout;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TVisualNode.RemoveChild(Child: TVisualNode);
|
||||||
|
begin
|
||||||
|
FChildren.Remove(Child);
|
||||||
|
Child.FParent := nil;
|
||||||
|
RequestLayout;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TVisualNode.DeleteChildren;
|
||||||
|
begin
|
||||||
|
for var i := 0 to FChildren.Count - 1 do
|
||||||
|
FChildren[i].Free;
|
||||||
|
FChildren.Clear;
|
||||||
|
RequestLayout;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TVisualNode.GetChild(Index: Integer): TVisualNode;
|
||||||
|
begin
|
||||||
|
Result := FChildren[Index];
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TVisualNode.GetChildCount: Integer;
|
||||||
|
begin
|
||||||
|
Result := FChildren.Count;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TVisualNode.RequestLayout;
|
||||||
|
begin
|
||||||
|
if Assigned(FParent) then
|
||||||
|
FParent.RequestLayout
|
||||||
|
else if Assigned(FHost) then
|
||||||
|
FHost.RequestLayout;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TVisualNode.RecalcLayout;
|
||||||
|
begin
|
||||||
|
// Base implementation: Does nothing.
|
||||||
|
// Subclasses (Layouts) must implement calculation here.
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TVisualNode.GetAbsolutePosition: TPointF;
|
||||||
|
var
|
||||||
|
Curr: TVisualNode;
|
||||||
|
begin
|
||||||
|
Result := FPosition;
|
||||||
|
Curr := FParent;
|
||||||
|
while Assigned(Curr) do
|
||||||
|
begin
|
||||||
|
// Add parent position + padding (since we are in the content area)
|
||||||
|
Result := Result + Curr.FPosition + TPointF.Create(Curr.Padding.Left, Curr.Padding.Top);
|
||||||
|
Curr := Curr.Parent;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TVisualNode.LocalToAbsolute(const P: TPointF): TPointF;
|
||||||
|
begin
|
||||||
|
Result := GetAbsolutePosition + P;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TVisualNode.AbsoluteToLocal(const P: TPointF): TPointF;
|
||||||
|
begin
|
||||||
|
Result := P - GetAbsolutePosition;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TVisualNode.AbsoluteRect: TRectF;
|
||||||
|
begin
|
||||||
|
Result := TRectF.Create(GetAbsolutePosition, FSize.Width, FSize.Height);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TVisualNode.Paint(Canvas: TCanvas; const Offset: TPointF);
|
||||||
|
var
|
||||||
|
AbsPos: TPointF;
|
||||||
|
ChildOffset: TPointF;
|
||||||
|
Child: TVisualNode;
|
||||||
|
begin
|
||||||
|
if not FVisible then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
// Absolute position of this node
|
||||||
|
AbsPos := Offset + FPosition;
|
||||||
|
|
||||||
|
// 1. Background / Own content
|
||||||
|
DoPaint(Canvas, AbsPos);
|
||||||
|
|
||||||
|
// 2. Draw children
|
||||||
|
if FChildren.Count > 0 then
|
||||||
|
begin
|
||||||
|
// The 0,0 point for children is shifted by padding
|
||||||
|
ChildOffset := AbsPos + TPointF.Create(FPadding.Left, FPadding.Top);
|
||||||
|
for Child in FChildren do
|
||||||
|
Child.Paint(Canvas, ChildOffset);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TVisualNode.DoPaint(Canvas: TCanvas; const Offset: TPointF);
|
||||||
|
begin
|
||||||
|
// Stub
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TVisualNode.HitTest(const P: TPointF): TVisualNode;
|
||||||
|
var
|
||||||
|
i: Integer;
|
||||||
|
Child: TVisualNode;
|
||||||
|
LocalP: TPointF;
|
||||||
|
ContentOrigin: TPointF;
|
||||||
|
begin
|
||||||
|
Result := nil;
|
||||||
|
if not FVisible or not FHitTest then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
// P is relative to Position of this node. Check if P is inside rect.
|
||||||
|
if not TRectF.Create(0, 0, FSize.Width, FSize.Height).Contains(P) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
ContentOrigin := TPointF.Create(FPadding.Left, FPadding.Top);
|
||||||
|
|
||||||
|
// Check children in reverse Z-order (topmost first)
|
||||||
|
for i := FChildren.Count - 1 downto 0 do
|
||||||
|
begin
|
||||||
|
Child := FChildren[i];
|
||||||
|
// Transformation into child coordinates
|
||||||
|
LocalP := P - ContentOrigin - Child.FPosition;
|
||||||
|
|
||||||
|
Result := Child.HitTest(LocalP);
|
||||||
|
if Result <> nil then
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// If no child hit, it's us
|
||||||
|
Result := Self;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TVisualNode.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
||||||
|
begin
|
||||||
|
end;
|
||||||
|
procedure TVisualNode.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
||||||
|
begin
|
||||||
|
end;
|
||||||
|
procedure TVisualNode.MouseMove(Shift: TShiftState; X, Y: Single);
|
||||||
|
begin
|
||||||
|
end;
|
||||||
|
procedure TVisualNode.MouseEnter;
|
||||||
|
begin
|
||||||
|
end;
|
||||||
|
procedure TVisualNode.MouseLeave;
|
||||||
|
begin
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TTextNode }
|
||||||
|
|
||||||
|
constructor TTextNode.Create(AHost: IVisualHost);
|
||||||
|
begin
|
||||||
|
inherited;
|
||||||
|
FHitTest := False; // Text is usually passive
|
||||||
|
FIsLayoutDirty := True;
|
||||||
|
FFontSettings := TTextSettings.Create(nil);
|
||||||
|
FColor := TAlphaColors.Black;
|
||||||
|
end;
|
||||||
|
|
||||||
|
destructor TTextNode.Destroy;
|
||||||
|
begin
|
||||||
|
FreeAndNil(FLayout);
|
||||||
|
FreeAndNil(FFontSettings);
|
||||||
|
inherited;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTextNode.SetText(const Value: string);
|
||||||
|
begin
|
||||||
|
if FText <> Value then
|
||||||
|
begin
|
||||||
|
FText := Value;
|
||||||
|
FIsLayoutDirty := True;
|
||||||
|
RequestLayout;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTextNode.SetColor(const Value: TAlphaColor);
|
||||||
|
begin
|
||||||
|
if FColor <> Value then
|
||||||
|
begin
|
||||||
|
FColor := Value;
|
||||||
|
if Assigned(FHost) then
|
||||||
|
FHost.InvalidateRect(AbsoluteRect);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTextNode.UpdateTextLayout(Canvas: TCanvas);
|
||||||
|
begin
|
||||||
|
if FIsLayoutDirty or (FLayout = nil) then
|
||||||
|
begin
|
||||||
|
if FLayout = nil then
|
||||||
|
FLayout := TTextLayoutManager.DefaultTextLayout.Create(Canvas);
|
||||||
|
|
||||||
|
FLayout.BeginUpdate;
|
||||||
|
try
|
||||||
|
FLayout.Text := FText;
|
||||||
|
FLayout.Font.Assign(FFontSettings.Font);
|
||||||
|
FLayout.Color := FColor;
|
||||||
|
FLayout.WordWrap := False;
|
||||||
|
finally
|
||||||
|
FLayout.EndUpdate;
|
||||||
|
end;
|
||||||
|
FIsLayoutDirty := False;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTextNode.RecalcLayout;
|
||||||
|
begin
|
||||||
|
if Assigned(FHost) then
|
||||||
|
begin
|
||||||
|
UpdateTextLayout(FHost.GetCanvas);
|
||||||
|
|
||||||
|
FSize.Width := FLayout.TextRect.Width + FPadding.Width;
|
||||||
|
FSize.Height := FLayout.TextRect.Height + FPadding.Height;
|
||||||
|
|
||||||
|
// Safety net for empty strings
|
||||||
|
if FSize.Width < 1 then
|
||||||
|
FSize.Width := 5;
|
||||||
|
if FSize.Height < 1 then
|
||||||
|
FSize.Height := 10;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTextNode.DoPaint(Canvas: TCanvas; const Offset: TPointF);
|
||||||
|
begin
|
||||||
|
UpdateTextLayout(Canvas);
|
||||||
|
var P := Offset + TPointF.Create(FPadding.Left, FPadding.Top);
|
||||||
|
|
||||||
|
// Set position and color on layout, then render
|
||||||
|
FLayout.TopLeft := P;
|
||||||
|
FLayout.Color := FColor;
|
||||||
|
FLayout.RenderLayout(Canvas);
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TAutoFitLayout }
|
||||||
|
|
||||||
|
constructor TAutoFitLayout.Create(AHost: IVisualHost);
|
||||||
|
begin
|
||||||
|
inherited;
|
||||||
|
FOrientation := loHorizontal;
|
||||||
|
FAlignment := laCenter;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAutoFitLayout.SetOrientation(const Value: TLayoutOrientation);
|
||||||
|
begin
|
||||||
|
if FOrientation <> Value then
|
||||||
|
begin
|
||||||
|
FOrientation := Value;
|
||||||
|
RequestLayout;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAutoFitLayout.SetAlignment(const Value: TLayoutAlignment);
|
||||||
|
begin
|
||||||
|
if FAlignment <> Value then
|
||||||
|
begin
|
||||||
|
FAlignment := Value;
|
||||||
|
RequestLayout;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAutoFitLayout.DoPaint(Canvas: TCanvas; const Offset: TPointF);
|
||||||
|
begin
|
||||||
|
// TAutoFitLayout is a container, draws no background by default.
|
||||||
|
inherited DoPaint(Canvas, Offset);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAutoFitLayout.RecalcLayout;
|
||||||
|
var
|
||||||
|
Child: TVisualNode;
|
||||||
|
CurrentX, CurrentY: Single;
|
||||||
|
ReqW, ReqH: Single;
|
||||||
|
begin
|
||||||
|
// 1. Recursion: Calculate children
|
||||||
|
for Child in FChildren do
|
||||||
|
begin
|
||||||
|
if Child.Visible then
|
||||||
|
Child.RecalcLayout;
|
||||||
|
end;
|
||||||
|
|
||||||
|
CurrentX := 0;
|
||||||
|
CurrentY := 0;
|
||||||
|
ReqW := 0;
|
||||||
|
ReqH := 0;
|
||||||
|
|
||||||
|
// 2. Stacking Logic
|
||||||
|
if FOrientation = loVertical then
|
||||||
|
begin
|
||||||
|
for Child in FChildren do
|
||||||
|
begin
|
||||||
|
if not Child.Visible then
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Set Y-Position
|
||||||
|
Child.FPosition := TPointF.Create(Child.Margins.Left, CurrentY + Child.Margins.Top);
|
||||||
|
|
||||||
|
var ChildTotalH := Child.Size.Height + Child.Margins.Top + Child.Margins.Bottom;
|
||||||
|
var ChildTotalW := Child.Size.Width + Child.Margins.Left + Child.Margins.Right;
|
||||||
|
|
||||||
|
CurrentY := CurrentY + ChildTotalH;
|
||||||
|
ReqW := Max(ReqW, ChildTotalW);
|
||||||
|
end;
|
||||||
|
ReqH := CurrentY;
|
||||||
|
|
||||||
|
// 3. Alignment Logic (Cross-Axis)
|
||||||
|
if FAlignment = laCenter then
|
||||||
|
begin
|
||||||
|
for Child in FChildren do
|
||||||
|
begin
|
||||||
|
if not Child.Visible then
|
||||||
|
continue;
|
||||||
|
var ChildTotalW := Child.Size.Width + Child.Margins.Left + Child.Margins.Right;
|
||||||
|
var OffsetX := (ReqW - ChildTotalW) * 0.5;
|
||||||
|
Child.FPosition := TPointF.Create(Child.Margins.Left + OffsetX, Child.FPosition.Y);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
else // Horizontal
|
||||||
|
begin
|
||||||
|
for Child in FChildren do
|
||||||
|
begin
|
||||||
|
if not Child.Visible then
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Set X-Position
|
||||||
|
Child.FPosition := TPointF.Create(CurrentX + Child.Margins.Left, Child.Margins.Top);
|
||||||
|
|
||||||
|
var ChildTotalW := Child.Size.Width + Child.Margins.Left + Child.Margins.Right;
|
||||||
|
var ChildTotalH := Child.Size.Height + Child.Margins.Top + Child.Margins.Bottom;
|
||||||
|
|
||||||
|
CurrentX := CurrentX + ChildTotalW;
|
||||||
|
ReqH := Max(ReqH, ChildTotalH);
|
||||||
|
end;
|
||||||
|
ReqW := CurrentX;
|
||||||
|
|
||||||
|
// 3. Alignment Logic (Cross-Axis)
|
||||||
|
if FAlignment = laCenter then
|
||||||
|
begin
|
||||||
|
for Child in FChildren do
|
||||||
|
begin
|
||||||
|
if not Child.Visible then
|
||||||
|
continue;
|
||||||
|
var ChildTotalH := Child.Size.Height + Child.Margins.Top + Child.Margins.Bottom;
|
||||||
|
var OffsetY := (ReqH - ChildTotalH) * 0.5;
|
||||||
|
Child.FPosition := TPointF.Create(Child.FPosition.X, Child.Margins.Top + OffsetY);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// 4. Set own size
|
||||||
|
FSize.Width := ReqW + FPadding.Width;
|
||||||
|
FSize.Height := ReqH + FPadding.Height;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAutoFitLayout.GetInsertionIndex(const LocalP: TPointF): Integer;
|
||||||
|
var
|
||||||
|
i: Integer;
|
||||||
|
Child: TVisualNode;
|
||||||
|
Center: Single;
|
||||||
|
ContentP: TPointF;
|
||||||
|
begin
|
||||||
|
// Assumption: Append to end as default
|
||||||
|
Result := FChildren.Count;
|
||||||
|
|
||||||
|
// Convert coords to content area
|
||||||
|
ContentP := LocalP - TPointF.Create(FPadding.Left, FPadding.Top);
|
||||||
|
|
||||||
|
for i := 0 to FChildren.Count - 1 do
|
||||||
|
begin
|
||||||
|
Child := FChildren[i];
|
||||||
|
if not Child.Visible then
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if FOrientation = loVertical then
|
||||||
|
begin
|
||||||
|
Center := Child.FPosition.Y + (Child.Size.Height * 0.5);
|
||||||
|
if ContentP.Y < Center then
|
||||||
|
exit(i);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
Center := Child.FPosition.X + (Child.Size.Width * 0.5);
|
||||||
|
if ContentP.X < Center then
|
||||||
|
exit(i);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
@@ -9,65 +9,79 @@ uses
|
|||||||
Myc.Data.Value,
|
Myc.Data.Value,
|
||||||
Myc.Ast.Nodes,
|
Myc.Ast.Nodes,
|
||||||
Myc.Ast.Visitor,
|
Myc.Ast.Visitor,
|
||||||
Myc.Fmx.AstEditor.Workspace,
|
Myc.Fmx.AstEditor.Render, // Virtual Engine
|
||||||
Myc.Fmx.AstEditor.Node,
|
Myc.Fmx.AstEditor.Node, // View Nodes
|
||||||
Myc.Fmx.AstEditor.Handlers;
|
Myc.Fmx.AstEditor.Handlers, // Handlers
|
||||||
|
Myc.Fmx.AstEditor.Workspace; // Host
|
||||||
|
|
||||||
type
|
type
|
||||||
// Inherits from the generic TAstVisitor<TAstViewNode>
|
// Implementation of the Factory/Visitor for the Virtual Tree
|
||||||
TAstVisualizer = class(TAstVisitor<TAstViewNode>, IAstVisualizer)
|
TAstVisualizer = class(TAstVisitor<TAstViewNode>, IAstVisualizer)
|
||||||
private
|
private
|
||||||
FExprDepth: Integer;
|
FExprDepth: Integer;
|
||||||
FParentControl: TControl;
|
FParentNode: TVisualNode; // Changed from TControl
|
||||||
FWorkspace: TWorkspace; // Changed from TAstWorkspace to TWorkspace
|
FWorkspace: TWorkspace;
|
||||||
|
|
||||||
function CallAccept(const Node: IAstNode): TAstViewNode;
|
function CallAccept(const Node: IAstNode): TAstViewNode;
|
||||||
|
|
||||||
|
// IAstVisualizer Implementation
|
||||||
function GetExprDepth: Integer;
|
function GetExprDepth: Integer;
|
||||||
function GetParentControl: TControl;
|
function GetParentNode: TVisualNode;
|
||||||
function GetWorkspace: TWorkspace; // Changed signature
|
function GetWorkspace: TWorkspace;
|
||||||
procedure SetExprDepth(const Value: Integer);
|
|
||||||
procedure SetParentControl(const Value: TControl);
|
|
||||||
protected
|
protected
|
||||||
// Visitor implementations for each AST node type.
|
// --- Visitor Implementations ---
|
||||||
function VisitConstant(const Node: IConstantNode): TAstViewNode; override;
|
function VisitConstant(const Node: IConstantNode): TAstViewNode; override;
|
||||||
function VisitIdentifier(const Node: IIdentifierNode): TAstViewNode; override;
|
function VisitIdentifier(const Node: IIdentifierNode): TAstViewNode; override;
|
||||||
function VisitKeyword(const Node: IKeywordNode): TAstViewNode; override;
|
function VisitKeyword(const Node: IKeywordNode): TAstViewNode; override;
|
||||||
|
|
||||||
// --- List Visitors (NEW) ---
|
// Lists
|
||||||
function VisitParameterList(const Node: IParameterList): TAstViewNode; override;
|
function VisitParameterList(const Node: IParameterList): TAstViewNode; override;
|
||||||
function VisitArgumentList(const Node: IArgumentList): TAstViewNode; override;
|
function VisitArgumentList(const Node: IArgumentList): TAstViewNode; override;
|
||||||
function VisitExpressionList(const Node: IExpressionList): TAstViewNode; override;
|
function VisitExpressionList(const Node: IExpressionList): TAstViewNode; override;
|
||||||
function VisitRecordFieldList(const Node: IRecordFieldList): TAstViewNode; override;
|
function VisitRecordFieldList(const Node: IRecordFieldList): TAstViewNode; override;
|
||||||
function VisitRecordField(const Node: IRecordFieldNode): TAstViewNode; override;
|
function VisitRecordField(const Node: IRecordFieldNode): TAstViewNode; override;
|
||||||
|
|
||||||
|
// Control Flow
|
||||||
function VisitIfExpression(const Node: IIfExpressionNode): TAstViewNode; override;
|
function VisitIfExpression(const Node: IIfExpressionNode): TAstViewNode; override;
|
||||||
function VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstViewNode; override;
|
function VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstViewNode; override;
|
||||||
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstViewNode; override;
|
|
||||||
function VisitFunctionCall(const Node: IFunctionCallNode): TAstViewNode; override;
|
|
||||||
function VisitMacroExpansionNode(const Node: IMacroExpansionNode): TAstViewNode; override;
|
|
||||||
function VisitBlockExpression(const Node: IBlockExpressionNode): TAstViewNode; override;
|
function VisitBlockExpression(const Node: IBlockExpressionNode): TAstViewNode; override;
|
||||||
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstViewNode; override;
|
|
||||||
function VisitAssignment(const Node: IAssignmentNode): TAstViewNode; override;
|
|
||||||
function VisitMacroDefinition(const Node: IMacroDefinitionNode): TAstViewNode; override;
|
|
||||||
function VisitQuasiquote(const Node: IQuasiquoteNode): TAstViewNode; override;
|
|
||||||
function VisitUnquote(const Node: IUnquoteNode): TAstViewNode; override;
|
|
||||||
function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TAstViewNode; override;
|
|
||||||
function VisitIndexer(const Node: IIndexerNode): TAstViewNode; override;
|
|
||||||
function VisitMemberAccess(const Node: IMemberAccessNode): TAstViewNode; override;
|
|
||||||
function VisitRecordLiteral(const Node: IRecordLiteralNode): TAstViewNode; override;
|
|
||||||
function VisitCreateSeries(const Node: ICreateSeriesNode): TAstViewNode; override;
|
|
||||||
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstViewNode; override;
|
|
||||||
function VisitSeriesLength(const Node: ISeriesLengthNode): TAstViewNode; override;
|
|
||||||
function VisitRecurNode(const Node: IRecurNode): TAstViewNode; override;
|
function VisitRecurNode(const Node: IRecurNode): TAstViewNode; override;
|
||||||
function VisitNop(const Node: INopNode): TAstViewNode; override;
|
function VisitNop(const Node: INopNode): TAstViewNode; override;
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstViewNode; override;
|
||||||
|
function VisitFunctionCall(const Node: IFunctionCallNode): TAstViewNode; override;
|
||||||
|
function VisitMacroExpansionNode(const Node: IMacroExpansionNode): TAstViewNode; override;
|
||||||
|
|
||||||
|
// Declarations
|
||||||
|
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstViewNode; override;
|
||||||
|
function VisitAssignment(const Node: IAssignmentNode): TAstViewNode; override;
|
||||||
|
function VisitMacroDefinition(const Node: IMacroDefinitionNode): TAstViewNode; override;
|
||||||
|
|
||||||
|
// Metaprogramming
|
||||||
|
function VisitQuasiquote(const Node: IQuasiquoteNode): TAstViewNode; override;
|
||||||
|
function VisitUnquote(const Node: IUnquoteNode): TAstViewNode; override;
|
||||||
|
function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TAstViewNode; override;
|
||||||
|
|
||||||
|
// Data Access
|
||||||
|
function VisitIndexer(const Node: IIndexerNode): TAstViewNode; override;
|
||||||
|
function VisitMemberAccess(const Node: IMemberAccessNode): TAstViewNode; override;
|
||||||
|
function VisitRecordLiteral(const Node: IRecordLiteralNode): TAstViewNode; override;
|
||||||
|
|
||||||
|
// Series
|
||||||
|
function VisitCreateSeries(const Node: ICreateSeriesNode): TAstViewNode; override;
|
||||||
|
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstViewNode; override;
|
||||||
|
function VisitSeriesLength(const Node: ISeriesLengthNode): TAstViewNode; override;
|
||||||
|
|
||||||
public
|
public
|
||||||
constructor Create(AWorkspace: TWorkspace; AParentControl: TControl; AExprDepth: Integer);
|
constructor Create(AWorkspace: TWorkspace; AParentNode: TVisualNode; AExprDepth: Integer);
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
|
|
||||||
function Clone(ParentControl: TControl; ExprDepth: Integer): IAstVisualizer;
|
function Clone(ParentNode: TVisualNode; ExprDepth: Integer): IAstVisualizer;
|
||||||
|
|
||||||
property ExprDepth: Integer read GetExprDepth write SetExprDepth;
|
property ExprDepth: Integer read GetExprDepth;
|
||||||
property ParentControl: TControl read GetParentControl write SetParentControl;
|
property ParentNode: TVisualNode read GetParentNode;
|
||||||
property Workspace: TWorkspace read GetWorkspace;
|
property Workspace: TWorkspace read GetWorkspace;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -75,11 +89,11 @@ implementation
|
|||||||
|
|
||||||
{ TAstVisualizer }
|
{ TAstVisualizer }
|
||||||
|
|
||||||
constructor TAstVisualizer.Create(AWorkspace: TWorkspace; AParentControl: TControl; AExprDepth: Integer);
|
constructor TAstVisualizer.Create(AWorkspace: TWorkspace; AParentNode: TVisualNode; AExprDepth: Integer);
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
FWorkspace := AWorkspace;
|
FWorkspace := AWorkspace;
|
||||||
FParentControl := AParentControl;
|
FParentNode := AParentNode;
|
||||||
FExprDepth := AExprDepth;
|
FExprDepth := AExprDepth;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -95,21 +109,18 @@ begin
|
|||||||
if not Assigned(Node) then
|
if not Assigned(Node) then
|
||||||
exit(nil);
|
exit(nil);
|
||||||
|
|
||||||
|
// Dynamic dispatch via Visitor pattern
|
||||||
dataValue := Node.Accept(Self);
|
dataValue := Node.Accept(Self);
|
||||||
|
|
||||||
if dataValue.Kind = TDataValueKind.vkGeneric then
|
if dataValue.Kind = TDataValueKind.vkGeneric then
|
||||||
begin
|
Result := dataValue.AsGeneric<TAstViewNode>
|
||||||
Result := dataValue.AsGeneric<TAstViewNode>;
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
begin
|
|
||||||
Result := nil;
|
Result := nil;
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstVisualizer.Clone(ParentControl: TControl; ExprDepth: Integer): IAstVisualizer;
|
function TAstVisualizer.Clone(ParentNode: TVisualNode; ExprDepth: Integer): IAstVisualizer;
|
||||||
begin
|
begin
|
||||||
Result := TAstVisualizer.Create(FWorkspace, ParentControl, ExprDepth);
|
Result := TAstVisualizer.Create(FWorkspace, ParentNode, ExprDepth);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstVisualizer.GetExprDepth: Integer;
|
function TAstVisualizer.GetExprDepth: Integer;
|
||||||
@@ -117,9 +128,9 @@ begin
|
|||||||
Result := FExprDepth;
|
Result := FExprDepth;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstVisualizer.GetParentControl: TControl;
|
function TAstVisualizer.GetParentNode: TVisualNode;
|
||||||
begin
|
begin
|
||||||
Result := FParentControl;
|
Result := FParentNode;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstVisualizer.GetWorkspace: TWorkspace;
|
function TAstVisualizer.GetWorkspace: TWorkspace;
|
||||||
@@ -127,17 +138,7 @@ begin
|
|||||||
Result := FWorkspace;
|
Result := FWorkspace;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TAstVisualizer.SetExprDepth(const Value: Integer);
|
// --- Visitor Implementation Helpers ---
|
||||||
begin
|
|
||||||
FExprDepth := Value;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstVisualizer.SetParentControl(const Value: TControl);
|
|
||||||
begin
|
|
||||||
FParentControl := Value;
|
|
||||||
end;
|
|
||||||
|
|
||||||
// --- Visitor Implementations ---
|
|
||||||
|
|
||||||
function TAstVisualizer.VisitConstant(const Node: IConstantNode): TAstViewNode;
|
function TAstVisualizer.VisitConstant(const Node: IConstantNode): TAstViewNode;
|
||||||
begin
|
begin
|
||||||
@@ -154,7 +155,7 @@ begin
|
|||||||
Result := TAstViewNode.Create(Self, TKeywordNodeHandler.Create(Node));
|
Result := TAstViewNode.Create(Self, TKeywordNodeHandler.Create(Node));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// --- List Visitors ---
|
// Lists
|
||||||
|
|
||||||
function TAstVisualizer.VisitParameterList(const Node: IParameterList): TAstViewNode;
|
function TAstVisualizer.VisitParameterList(const Node: IParameterList): TAstViewNode;
|
||||||
begin
|
begin
|
||||||
@@ -181,7 +182,7 @@ begin
|
|||||||
Result := TAstViewNode.Create(Self, TRecordFieldHandler.Create(Node));
|
Result := TAstViewNode.Create(Self, TRecordFieldHandler.Create(Node));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// --- Node Visitors ---
|
// Control Flow
|
||||||
|
|
||||||
function TAstVisualizer.VisitBlockExpression(const Node: IBlockExpressionNode): TAstViewNode;
|
function TAstVisualizer.VisitBlockExpression(const Node: IBlockExpressionNode): TAstViewNode;
|
||||||
begin
|
begin
|
||||||
@@ -198,6 +199,18 @@ begin
|
|||||||
Result := TAstViewNode.Create(Self, TTernaryExpressionNodeHandler.Create(Node));
|
Result := TAstViewNode.Create(Self, TTernaryExpressionNodeHandler.Create(Node));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TAstVisualizer.VisitRecurNode(const Node: IRecurNode): TAstViewNode;
|
||||||
|
begin
|
||||||
|
Result := TAstViewNode.Create(Self, TRecurNodeHandler.Create(Node));
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstVisualizer.VisitNop(const Node: INopNode): TAstViewNode;
|
||||||
|
begin
|
||||||
|
Result := TAstViewNode.Create(Self, TNopNodeHandler.Create(Node));
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
|
||||||
function TAstVisualizer.VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstViewNode;
|
function TAstVisualizer.VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstViewNode;
|
||||||
begin
|
begin
|
||||||
Result := TAstViewNode.Create(Self, TLambdaExpressionNodeHandler.Create(Node));
|
Result := TAstViewNode.Create(Self, TLambdaExpressionNodeHandler.Create(Node));
|
||||||
@@ -213,6 +226,8 @@ begin
|
|||||||
Result := TAstViewNode.Create(Self, TMacroExpansionNodeHandler.Create(Node));
|
Result := TAstViewNode.Create(Self, TMacroExpansionNodeHandler.Create(Node));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// Declarations
|
||||||
|
|
||||||
function TAstVisualizer.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstViewNode;
|
function TAstVisualizer.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstViewNode;
|
||||||
begin
|
begin
|
||||||
Result := TAstViewNode.Create(Self, TVariableDeclarationNodeHandler.Create(Node));
|
Result := TAstViewNode.Create(Self, TVariableDeclarationNodeHandler.Create(Node));
|
||||||
@@ -228,6 +243,8 @@ begin
|
|||||||
Result := TAstViewNode.Create(Self, TMacroDefinitionNodeHandler.Create(Node));
|
Result := TAstViewNode.Create(Self, TMacroDefinitionNodeHandler.Create(Node));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// Metaprogramming
|
||||||
|
|
||||||
function TAstVisualizer.VisitQuasiquote(const Node: IQuasiquoteNode): TAstViewNode;
|
function TAstVisualizer.VisitQuasiquote(const Node: IQuasiquoteNode): TAstViewNode;
|
||||||
begin
|
begin
|
||||||
Result := TAstViewNode.Create(Self, TQuasiquoteNodeHandler.Create(Node));
|
Result := TAstViewNode.Create(Self, TQuasiquoteNodeHandler.Create(Node));
|
||||||
@@ -243,6 +260,8 @@ begin
|
|||||||
Result := TAstViewNode.Create(Self, TUnquoteSplicingNodeHandler.Create(Node));
|
Result := TAstViewNode.Create(Self, TUnquoteSplicingNodeHandler.Create(Node));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// Data Access
|
||||||
|
|
||||||
function TAstVisualizer.VisitIndexer(const Node: IIndexerNode): TAstViewNode;
|
function TAstVisualizer.VisitIndexer(const Node: IIndexerNode): TAstViewNode;
|
||||||
begin
|
begin
|
||||||
Result := TAstViewNode.Create(Self, TIndexerNodeHandler.Create(Node));
|
Result := TAstViewNode.Create(Self, TIndexerNodeHandler.Create(Node));
|
||||||
@@ -258,6 +277,8 @@ begin
|
|||||||
Result := TAstViewNode.Create(Self, TRecordLiteralNodeHandler.Create(Node));
|
Result := TAstViewNode.Create(Self, TRecordLiteralNodeHandler.Create(Node));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// Series
|
||||||
|
|
||||||
function TAstVisualizer.VisitCreateSeries(const Node: ICreateSeriesNode): TAstViewNode;
|
function TAstVisualizer.VisitCreateSeries(const Node: ICreateSeriesNode): TAstViewNode;
|
||||||
begin
|
begin
|
||||||
Result := TAstViewNode.Create(Self, TCreateSeriesNodeHandler.Create(Node));
|
Result := TAstViewNode.Create(Self, TCreateSeriesNodeHandler.Create(Node));
|
||||||
@@ -273,14 +294,4 @@ begin
|
|||||||
Result := TAstViewNode.Create(Self, TSeriesLengthNodeHandler.Create(Node));
|
Result := TAstViewNode.Create(Self, TSeriesLengthNodeHandler.Create(Node));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstVisualizer.VisitRecurNode(const Node: IRecurNode): TAstViewNode;
|
|
||||||
begin
|
|
||||||
Result := TAstViewNode.Create(Self, TRecurNodeHandler.Create(Node));
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TAstVisualizer.VisitNop(const Node: INopNode): TAstViewNode;
|
|
||||||
begin
|
|
||||||
Result := TAstViewNode.Create(Self, TNopNodeHandler.Create(Node));
|
|
||||||
end;
|
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
@@ -14,146 +14,84 @@ uses
|
|||||||
FMX.Objects,
|
FMX.Objects,
|
||||||
FMX.Graphics,
|
FMX.Graphics,
|
||||||
FMX.Forms,
|
FMX.Forms,
|
||||||
Myc.Fmx.AstEditor.Layout;
|
Myc.Fmx.AstEditor.Render;
|
||||||
|
|
||||||
type
|
type
|
||||||
TOnPaintConnectionsEvent = procedure(ASender: TObject; ACanvas: TCanvas) of object;
|
TOnPaintConnectionsEvent = procedure(ASender: TObject; ACanvas: TCanvas) of object;
|
||||||
TOnDragDropEvent = procedure(ASource, ATarget: TControl; AIndex: Integer) of object;
|
TOnDragDropEvent = procedure(ASource, ATarget: TVisualNode; AIndex: Integer) of object;
|
||||||
|
|
||||||
TAstWorld = class(TControl)
|
TWorkspace = class(TControl, IVisualHost)
|
||||||
private
|
|
||||||
FOnPaintConnections: TOnPaintConnectionsEvent;
|
|
||||||
// VISUAL: Drop Marker State
|
|
||||||
FShowDropMarker: Boolean;
|
|
||||||
FDropP1, FDropP2: TPointF;
|
|
||||||
protected
|
|
||||||
procedure Paint; override;
|
|
||||||
procedure AfterPaint; override;
|
|
||||||
public
|
|
||||||
constructor Create(AOwner: TComponent); override;
|
|
||||||
procedure InvalidateConnections;
|
|
||||||
property OnPaintConnections: TOnPaintConnectionsEvent read FOnPaintConnections write FOnPaintConnections;
|
|
||||||
end;
|
|
||||||
|
|
||||||
TWorkspace = class(TControl)
|
|
||||||
private
|
private
|
||||||
const
|
const
|
||||||
MinZoom = 0.1;
|
MinZoom = 0.1;
|
||||||
MaxZoom = 5.0;
|
MaxZoom = 5.0;
|
||||||
ZoomStep = 0.1;
|
ZoomStep = 0.1;
|
||||||
private
|
private
|
||||||
FRootLayer: TAstWorld;
|
FRootNode: TVisualNode;
|
||||||
|
|
||||||
|
FContentOffset: TPointF;
|
||||||
|
FZoom: Single;
|
||||||
|
|
||||||
FIsPanning: Boolean;
|
FIsPanning: Boolean;
|
||||||
FLastMousePos: TPointF;
|
FLastMousePos: TPointF;
|
||||||
|
FMouseOverNode: TVisualNode;
|
||||||
|
FCapturedNode: TVisualNode;
|
||||||
|
|
||||||
FIsDraggingNode: Boolean;
|
FIsDraggingNode: Boolean;
|
||||||
FDragSource: TControl;
|
FDragSource: TVisualNode;
|
||||||
FDragVisual: TControl;
|
FDragVisualBitmap: TBitmap;
|
||||||
|
FDragVisualPos: TPointF;
|
||||||
FDragOffset: TPointF;
|
FDragOffset: TPointF;
|
||||||
|
|
||||||
FCurrentDropTarget: TControl;
|
FCurrentDropTarget: TVisualNode;
|
||||||
FCurrentDropIndex: Integer;
|
FCurrentDropIndex: Integer;
|
||||||
|
FShowDropMarker: Boolean;
|
||||||
|
FDropP1, FDropP2: TPointF;
|
||||||
|
|
||||||
|
FOnPaintConnections: TOnPaintConnectionsEvent;
|
||||||
FOnNodeDragDrop: TOnDragDropEvent;
|
FOnNodeDragDrop: TOnDragDropEvent;
|
||||||
|
|
||||||
function GetZoom: Single;
|
// IVisualHost
|
||||||
procedure SetZoom(const Value: Single);
|
procedure InvalidateRect(const R: TRectF);
|
||||||
function GetContentOffset: TPointF;
|
function GetCanvas: TCanvas;
|
||||||
procedure SetContentOffset(const Value: TPointF);
|
|
||||||
function GetOnPaintConnections: TOnPaintConnectionsEvent;
|
|
||||||
procedure SetOnPaintConnections(const Value: TOnPaintConnectionsEvent);
|
|
||||||
|
|
||||||
|
procedure SetZoom(const Value: Single);
|
||||||
|
procedure SetContentOffset(const Value: TPointF);
|
||||||
|
procedure SetRootNode(const Value: TVisualNode);
|
||||||
|
|
||||||
|
procedure UpdateDropMarker(const WorldPos: TPointF);
|
||||||
procedure StopDrag;
|
procedure StopDrag;
|
||||||
procedure UpdateDropMarker(const ScreenPos: TPointF);
|
|
||||||
protected
|
protected
|
||||||
procedure Loaded; override;
|
procedure Paint; override;
|
||||||
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
|
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
|
||||||
procedure MouseMove(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 MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
|
||||||
procedure MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean); override;
|
procedure MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean); override;
|
||||||
|
procedure Resize; override;
|
||||||
public
|
public
|
||||||
constructor Create(AOwner: TComponent); override;
|
constructor Create(AOwner: TComponent); override;
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
|
|
||||||
|
procedure BeginDragNode(ASource: TVisualNode);
|
||||||
|
procedure InvalidateConnections;
|
||||||
|
|
||||||
|
// Moved to public for Editor access
|
||||||
function ScreenToWorld(const APoint: TPointF): TPointF;
|
function ScreenToWorld(const APoint: TPointF): TPointF;
|
||||||
procedure BeginDragNode(ASource: TControl);
|
procedure RequestLayout;
|
||||||
|
|
||||||
property World: TAstWorld read FRootLayer;
|
property RootNode: TVisualNode read FRootNode write SetRootNode;
|
||||||
property Zoom: Single read GetZoom write SetZoom;
|
property Zoom: Single read FZoom write SetZoom;
|
||||||
property ContentOffset: TPointF read GetContentOffset write SetContentOffset;
|
property ContentOffset: TPointF read FContentOffset write SetContentOffset;
|
||||||
property OnPaintConnections: TOnPaintConnectionsEvent read GetOnPaintConnections write SetOnPaintConnections;
|
|
||||||
|
|
||||||
|
property OnPaintConnections: TOnPaintConnectionsEvent read FOnPaintConnections write FOnPaintConnections;
|
||||||
property OnNodeDragDrop: TOnDragDropEvent read FOnNodeDragDrop write FOnNodeDragDrop;
|
property OnNodeDragDrop: TOnDragDropEvent read FOnNodeDragDrop write FOnNodeDragDrop;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
{ TAstWorld }
|
uses
|
||||||
|
Myc.Fmx.AstEditor.Node;
|
||||||
constructor TAstWorld.Create(AOwner: TComponent);
|
|
||||||
begin
|
|
||||||
inherited;
|
|
||||||
HitTest := False;
|
|
||||||
Locked := True;
|
|
||||||
Stored := False;
|
|
||||||
ClipChildren := False;
|
|
||||||
Width := 100;
|
|
||||||
Height := 100;
|
|
||||||
FShowDropMarker := False;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstWorld.Paint;
|
|
||||||
begin
|
|
||||||
inherited;
|
|
||||||
if (csDesigning in ComponentState) then
|
|
||||||
DrawDesignBorder;
|
|
||||||
|
|
||||||
// Connections should be BEHIND the nodes, so Paint is the correct place.
|
|
||||||
if Assigned(FOnPaintConnections) then
|
|
||||||
FOnPaintConnections(Self, Canvas);
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstWorld.AfterPaint;
|
|
||||||
var
|
|
||||||
ScaleFactor: Single;
|
|
||||||
Radius: Single;
|
|
||||||
Thickness: Single;
|
|
||||||
begin
|
|
||||||
inherited;
|
|
||||||
|
|
||||||
// --- DRAW DROP MARKER (IN FRONT OF NODES) ---
|
|
||||||
if FShowDropMarker then
|
|
||||||
begin
|
|
||||||
// Calculate visual sizes inversely proportional to zoom
|
|
||||||
if Scale.X > 0 then
|
|
||||||
ScaleFactor := 1 / Scale.X
|
|
||||||
else
|
|
||||||
ScaleFactor := 1;
|
|
||||||
|
|
||||||
Thickness := 3 * ScaleFactor;
|
|
||||||
Radius := 4 * ScaleFactor;
|
|
||||||
|
|
||||||
Canvas.Stroke.Kind := TBrushKind.Solid;
|
|
||||||
Canvas.Stroke.Color := TAlphaColors.Red;
|
|
||||||
Canvas.Stroke.Thickness := Thickness;
|
|
||||||
Canvas.Stroke.Cap := TStrokeCap.Round;
|
|
||||||
|
|
||||||
// Draw Line
|
|
||||||
Canvas.DrawLine(FDropP1, FDropP2, 1.0);
|
|
||||||
|
|
||||||
// Draw Circles at ends
|
|
||||||
Canvas.Fill.Kind := TBrushKind.Solid;
|
|
||||||
Canvas.Fill.Color := TAlphaColors.Red;
|
|
||||||
|
|
||||||
Canvas.FillEllipse(TRectF.Create(FDropP1.X - Radius, FDropP1.Y - Radius, FDropP1.X + Radius, FDropP1.Y + Radius), 1.0);
|
|
||||||
Canvas.FillEllipse(TRectF.Create(FDropP2.X - Radius, FDropP2.Y - Radius, FDropP2.X + Radius, FDropP2.Y + Radius), 1.0);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstWorld.InvalidateConnections;
|
|
||||||
begin
|
|
||||||
Repaint;
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ TWorkspace }
|
{ TWorkspace }
|
||||||
|
|
||||||
@@ -162,260 +100,234 @@ begin
|
|||||||
inherited Create(AOwner);
|
inherited Create(AOwner);
|
||||||
ClipChildren := True;
|
ClipChildren := True;
|
||||||
HitTest := True;
|
HitTest := True;
|
||||||
CanFocus := True; // Enable Keyboard Input
|
CanFocus := True;
|
||||||
AutoCapture := True;
|
AutoCapture := True;
|
||||||
|
|
||||||
FRootLayer := TAstWorld.Create(Self);
|
FZoom := 1.0;
|
||||||
FRootLayer.Parent := Self;
|
FContentOffset := TPointF.Zero;
|
||||||
FRootLayer.SetBounds(0, 0, 100, 100);
|
FRootNode := TVisualNode.Create(Self);
|
||||||
FRootLayer.Scale.Point := TPointF.Create(1, 1);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TWorkspace.Destroy;
|
destructor TWorkspace.Destroy;
|
||||||
begin
|
begin
|
||||||
|
FreeAndNil(FRootNode);
|
||||||
|
FreeAndNil(FDragVisualBitmap);
|
||||||
inherited;
|
inherited;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWorkspace.Loaded;
|
function TWorkspace.GetCanvas: TCanvas;
|
||||||
|
begin
|
||||||
|
Result := Canvas;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWorkspace.InvalidateRect(const R: TRectF);
|
||||||
|
begin
|
||||||
|
Repaint;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWorkspace.RequestLayout;
|
||||||
|
begin
|
||||||
|
if Assigned(FRootNode) then
|
||||||
|
begin
|
||||||
|
FRootNode.RecalcLayout;
|
||||||
|
Repaint;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWorkspace.SetRootNode(const Value: TVisualNode);
|
||||||
|
begin
|
||||||
|
if FRootNode <> Value then
|
||||||
|
begin
|
||||||
|
if Assigned(FRootNode) then
|
||||||
|
FRootNode.Free;
|
||||||
|
FRootNode := Value;
|
||||||
|
RequestLayout;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWorkspace.SetZoom(const Value: Single);
|
||||||
|
begin
|
||||||
|
if FZoom <> Value then
|
||||||
|
begin
|
||||||
|
FZoom := EnsureRange(Value, MinZoom, MaxZoom);
|
||||||
|
Repaint;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWorkspace.SetContentOffset(const Value: TPointF);
|
||||||
|
begin
|
||||||
|
if FContentOffset <> Value then
|
||||||
|
begin
|
||||||
|
FContentOffset := Value;
|
||||||
|
Repaint;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWorkspace.Paint;
|
||||||
|
var
|
||||||
|
State: TCanvasSaveState;
|
||||||
|
DragRect: TRectF;
|
||||||
begin
|
begin
|
||||||
inherited;
|
inherited;
|
||||||
if FRootLayer <> nil then
|
|
||||||
FRootLayer.BringToFront;
|
if (csDesigning in ComponentState) then
|
||||||
|
begin
|
||||||
|
Canvas.Stroke.Kind := TBrushKind.Solid;
|
||||||
|
Canvas.Stroke.Color := TAlphaColors.Gray;
|
||||||
|
Canvas.Stroke.Dash := TStrokeDash.Dash;
|
||||||
|
Canvas.DrawRect(LocalRect, 0, 0, AllCorners, 1.0);
|
||||||
|
end;
|
||||||
|
|
||||||
|
State := Canvas.SaveState;
|
||||||
|
try
|
||||||
|
Canvas.IntersectClipRect(LocalRect);
|
||||||
|
|
||||||
|
Canvas.MultiplyMatrix(TMatrix.CreateScaling(FZoom, FZoom));
|
||||||
|
Canvas.MultiplyMatrix(TMatrix.CreateTranslation(FContentOffset.X, FContentOffset.Y));
|
||||||
|
|
||||||
|
if Assigned(FOnPaintConnections) then
|
||||||
|
FOnPaintConnections(Self, Canvas);
|
||||||
|
|
||||||
|
if Assigned(FRootNode) then
|
||||||
|
FRootNode.Paint(Canvas, TPointF.Zero);
|
||||||
|
|
||||||
|
if FShowDropMarker then
|
||||||
|
begin
|
||||||
|
Canvas.Stroke.Kind := TBrushKind.Solid;
|
||||||
|
Canvas.Stroke.Color := TAlphaColors.Red;
|
||||||
|
Canvas.Stroke.Thickness := 3 / FZoom;
|
||||||
|
Canvas.Stroke.Cap := TStrokeCap.Round;
|
||||||
|
|
||||||
|
Canvas.DrawLine(FDropP1, FDropP2, 1.0);
|
||||||
|
|
||||||
|
Canvas.Fill.Color := TAlphaColors.Red;
|
||||||
|
var R: Single := 4 / FZoom;
|
||||||
|
Canvas.FillEllipse(TRectF.Create(FDropP1.X - R, FDropP1.Y - R, FDropP1.X + R, FDropP1.Y + R), 1.0);
|
||||||
|
Canvas.FillEllipse(TRectF.Create(FDropP2.X - R, FDropP2.Y - R, FDropP2.X + R, FDropP2.Y + R), 1.0);
|
||||||
|
end;
|
||||||
|
|
||||||
|
if FIsDraggingNode and Assigned(FDragVisualBitmap) then
|
||||||
|
begin
|
||||||
|
DragRect := TRectF.Create(FDragVisualPos, FDragVisualBitmap.Width, FDragVisualBitmap.Height);
|
||||||
|
Canvas.DrawBitmap(FDragVisualBitmap, TRectF.Create(0, 0, FDragVisualBitmap.Width, FDragVisualBitmap.Height), DragRect, 0.7);
|
||||||
|
end;
|
||||||
|
|
||||||
|
finally
|
||||||
|
Canvas.RestoreState(State);
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TWorkspace.ScreenToWorld(const APoint: TPointF): TPointF;
|
function TWorkspace.ScreenToWorld(const APoint: TPointF): TPointF;
|
||||||
begin
|
|
||||||
Result := ScreenToLocal(APoint);
|
|
||||||
Result := FRootLayer.AbsoluteToLocal(LocalToAbsolute(Result));
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TWorkspace.BeginDragNode(ASource: TControl);
|
|
||||||
var
|
var
|
||||||
Snapshot: TBitmap;
|
LocalP: TPointF;
|
||||||
DragImg: TImage;
|
|
||||||
MouseWorld: TPointF;
|
|
||||||
AbsPos, WorldPos: TPointF;
|
|
||||||
begin
|
begin
|
||||||
if FIsDraggingNode or FIsPanning then
|
LocalP := ScreenToLocal(APoint);
|
||||||
Exit;
|
Result := (LocalP - FContentOffset) / FZoom;
|
||||||
|
|
||||||
FIsDraggingNode := True;
|
|
||||||
FDragSource := ASource;
|
|
||||||
Root.Captured := Self;
|
|
||||||
|
|
||||||
Snapshot := ASource.MakeScreenshot;
|
|
||||||
|
|
||||||
DragImg := TImage.Create(nil);
|
|
||||||
DragImg.Parent := FRootLayer;
|
|
||||||
DragImg.Bitmap := Snapshot;
|
|
||||||
DragImg.WrapMode := TImageWrapMode.Stretch;
|
|
||||||
DragImg.HitTest := False;
|
|
||||||
DragImg.Opacity := 0.7;
|
|
||||||
DragImg.BringToFront;
|
|
||||||
|
|
||||||
AbsPos := ASource.LocalToAbsolute(TPointF.Zero);
|
|
||||||
WorldPos := FRootLayer.AbsoluteToLocal(AbsPos);
|
|
||||||
|
|
||||||
DragImg.SetBounds(WorldPos.X, WorldPos.Y, ASource.Width, ASource.Height);
|
|
||||||
FDragVisual := DragImg;
|
|
||||||
|
|
||||||
MouseWorld := ScreenToWorld(Screen.MousePos);
|
|
||||||
FDragOffset := MouseWorld - WorldPos;
|
|
||||||
|
|
||||||
ASource.Opacity := 0.4;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWorkspace.UpdateDropMarker(const ScreenPos: TPointF);
|
procedure TWorkspace.Resize;
|
||||||
var
|
|
||||||
Obj: IControl;
|
|
||||||
TargetCtrl: TControl;
|
|
||||||
TargetLocalPos: TPointF;
|
|
||||||
IsHorizontal: Boolean;
|
|
||||||
InsertAfter: Boolean;
|
|
||||||
PTL, PBR: TPointF;
|
|
||||||
P1, P2: TPointF;
|
|
||||||
SourceParent: TControl;
|
|
||||||
TargetContainer: TAutoFitControl;
|
|
||||||
begin
|
begin
|
||||||
FRootLayer.FShowDropMarker := False;
|
inherited;
|
||||||
FCurrentDropTarget := nil;
|
Repaint;
|
||||||
FCurrentDropIndex := -1;
|
|
||||||
|
|
||||||
if FDragSource = nil then
|
|
||||||
Exit;
|
|
||||||
|
|
||||||
if not (FDragSource.Parent is TControl) then
|
|
||||||
Exit;
|
|
||||||
SourceParent := TControl(FDragSource.Parent);
|
|
||||||
|
|
||||||
// 1. Find Drop Target
|
|
||||||
Obj := ObjectAtPoint(ScreenPos);
|
|
||||||
if (Obj = nil) or not (Obj.GetObject is TControl) then
|
|
||||||
Exit;
|
|
||||||
|
|
||||||
TargetCtrl := TControl(Obj.GetObject);
|
|
||||||
|
|
||||||
// 2. Walk up hierarchy
|
|
||||||
while (TargetCtrl <> nil) and (TargetCtrl.Parent <> SourceParent) do
|
|
||||||
begin
|
|
||||||
TargetCtrl := TargetCtrl.ParentControl;
|
|
||||||
if TargetCtrl = FRootLayer then
|
|
||||||
Exit;
|
|
||||||
end;
|
|
||||||
|
|
||||||
if TargetCtrl = nil then
|
|
||||||
Exit;
|
|
||||||
if TargetCtrl = FDragSource then
|
|
||||||
Exit;
|
|
||||||
|
|
||||||
FCurrentDropTarget := TargetCtrl;
|
|
||||||
|
|
||||||
// 3. Determine Geometry using TAutoFitControl info
|
|
||||||
TargetLocalPos := TargetCtrl.AbsoluteToLocal(ScreenPos);
|
|
||||||
|
|
||||||
IsHorizontal := True; // Default fallback
|
|
||||||
|
|
||||||
if SourceParent is TAutoFitControl then
|
|
||||||
begin
|
|
||||||
TargetContainer := TAutoFitControl(SourceParent);
|
|
||||||
|
|
||||||
if TargetContainer.Orientation = TLayoutOrientation.loHorizontal then
|
|
||||||
IsHorizontal := True
|
|
||||||
else
|
|
||||||
IsHorizontal := False;
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Split logic
|
|
||||||
if IsHorizontal then
|
|
||||||
InsertAfter := TargetLocalPos.X > (TargetCtrl.Width / 2)
|
|
||||||
else
|
|
||||||
InsertAfter := TargetLocalPos.Y > (TargetCtrl.Height / 2);
|
|
||||||
|
|
||||||
// 4. Calculate Index
|
|
||||||
if InsertAfter then
|
|
||||||
FCurrentDropIndex := TargetCtrl.Index + 1
|
|
||||||
else
|
|
||||||
FCurrentDropIndex := TargetCtrl.Index;
|
|
||||||
|
|
||||||
// 5. Calculate Marker Visuals
|
|
||||||
var AbsTL := TargetCtrl.LocalToAbsolute(TPointF.Zero);
|
|
||||||
var AbsBR := TargetCtrl.LocalToAbsolute(TPointF.Create(TargetCtrl.Width, TargetCtrl.Height));
|
|
||||||
|
|
||||||
PTL := FRootLayer.AbsoluteToLocal(AbsTL);
|
|
||||||
PBR := FRootLayer.AbsoluteToLocal(AbsBR);
|
|
||||||
|
|
||||||
if IsHorizontal then
|
|
||||||
begin
|
|
||||||
// Vertical Line
|
|
||||||
var XPos := PTL.X;
|
|
||||||
if InsertAfter then
|
|
||||||
XPos := PBR.X;
|
|
||||||
|
|
||||||
if InsertAfter then
|
|
||||||
XPos := XPos + 2
|
|
||||||
else
|
|
||||||
XPos := XPos - 2;
|
|
||||||
|
|
||||||
P1 := TPointF.Create(XPos, PTL.Y);
|
|
||||||
P2 := TPointF.Create(XPos, PBR.Y);
|
|
||||||
end
|
|
||||||
else
|
|
||||||
begin
|
|
||||||
// Horizontal Line
|
|
||||||
var YPos := PTL.Y;
|
|
||||||
if InsertAfter then
|
|
||||||
YPos := PBR.Y;
|
|
||||||
|
|
||||||
if InsertAfter then
|
|
||||||
YPos := YPos + 2
|
|
||||||
else
|
|
||||||
YPos := YPos - 2;
|
|
||||||
|
|
||||||
P1 := TPointF.Create(PTL.X, YPos);
|
|
||||||
P2 := TPointF.Create(PBR.X, YPos);
|
|
||||||
end;
|
|
||||||
|
|
||||||
FRootLayer.FDropP1 := P1;
|
|
||||||
FRootLayer.FDropP2 := P2;
|
|
||||||
FRootLayer.FShowDropMarker := True;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TWorkspace.StopDrag;
|
|
||||||
begin
|
|
||||||
if not FIsDraggingNode then
|
|
||||||
Exit;
|
|
||||||
|
|
||||||
if Assigned(FDragVisual) then
|
|
||||||
begin
|
|
||||||
FDragVisual.Parent := nil;
|
|
||||||
FDragVisual.Free;
|
|
||||||
FDragVisual := nil;
|
|
||||||
end;
|
|
||||||
|
|
||||||
if Assigned(FDragSource) then
|
|
||||||
FDragSource.Opacity := 1.0;
|
|
||||||
|
|
||||||
FRootLayer.FShowDropMarker := False;
|
|
||||||
FRootLayer.Repaint;
|
|
||||||
|
|
||||||
if Assigned(FOnNodeDragDrop) and Assigned(FCurrentDropTarget) and (FCurrentDropIndex >= 0) then
|
|
||||||
FOnNodeDragDrop(FDragSource, FCurrentDropTarget, FCurrentDropIndex);
|
|
||||||
|
|
||||||
FIsDraggingNode := False;
|
|
||||||
FDragSource := nil;
|
|
||||||
FCurrentDropTarget := nil;
|
|
||||||
Root.Captured := nil;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWorkspace.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
procedure TWorkspace.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
||||||
|
var
|
||||||
|
WorldP: TPointF;
|
||||||
|
Hit: TVisualNode;
|
||||||
begin
|
begin
|
||||||
inherited;
|
inherited;
|
||||||
// Capture Keyboard Focus
|
|
||||||
if CanFocus then
|
if CanFocus then
|
||||||
SetFocus;
|
SetFocus;
|
||||||
|
|
||||||
if not FIsDraggingNode and (Button in [TMouseButton.mbLeft, TMouseButton.mbMiddle]) then
|
WorldP := ScreenToWorld(LocalToScreen(TPointF.Create(X, Y)));
|
||||||
|
|
||||||
|
if (Button in [TMouseButton.mbLeft, TMouseButton.mbMiddle]) and (ssCtrl in Shift) then
|
||||||
begin
|
begin
|
||||||
FIsPanning := True;
|
FIsPanning := True;
|
||||||
FLastMousePos := TPointF.Create(X, Y);
|
FLastMousePos := TPointF.Create(X, Y);
|
||||||
Root.Captured := Self;
|
Root.Captured := Self;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if Assigned(FRootNode) then
|
||||||
|
begin
|
||||||
|
Hit := FRootNode.HitTest(WorldP);
|
||||||
|
|
||||||
|
if Assigned(Hit) then
|
||||||
|
begin
|
||||||
|
FCapturedNode := Hit;
|
||||||
|
var NodeLocal := Hit.AbsoluteToLocal(WorldP);
|
||||||
|
Hit.MouseDown(Button, Shift, NodeLocal.X, NodeLocal.Y);
|
||||||
|
end
|
||||||
|
else if Button = TMouseButton.mbLeft then
|
||||||
|
begin
|
||||||
|
FIsPanning := True;
|
||||||
|
FLastMousePos := TPointF.Create(X, Y);
|
||||||
|
Root.Captured := Self;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWorkspace.MouseMove(Shift: TShiftState; X, Y: Single);
|
procedure TWorkspace.MouseMove(Shift: TShiftState; X, Y: Single);
|
||||||
var
|
var
|
||||||
LCurrentPos: TPointF;
|
WorldP: TPointF;
|
||||||
LDelta: TPointF;
|
LocalP: TPointF;
|
||||||
LWorldPos: TPointF;
|
Hit: TVisualNode;
|
||||||
|
Delta: TPointF;
|
||||||
begin
|
begin
|
||||||
inherited;
|
inherited;
|
||||||
|
LocalP := TPointF.Create(X, Y);
|
||||||
|
WorldP := ScreenToWorld(LocalToScreen(LocalP));
|
||||||
|
|
||||||
if FIsDraggingNode then
|
if FIsDraggingNode then
|
||||||
begin
|
begin
|
||||||
if Assigned(FDragVisual) then
|
FDragVisualPos := WorldP - FDragOffset;
|
||||||
begin
|
UpdateDropMarker(WorldP);
|
||||||
LWorldPos := ScreenToWorld(Screen.MousePos);
|
Repaint;
|
||||||
FDragVisual.Position.Point := LWorldPos - FDragOffset;
|
|
||||||
end;
|
|
||||||
|
|
||||||
UpdateDropMarker(Screen.MousePos);
|
|
||||||
|
|
||||||
FRootLayer.Repaint;
|
|
||||||
end
|
end
|
||||||
else if FIsPanning then
|
else if FIsPanning then
|
||||||
begin
|
begin
|
||||||
LCurrentPos := TPointF.Create(X, Y);
|
Delta := LocalP - FLastMousePos;
|
||||||
LDelta := LCurrentPos - FLastMousePos;
|
FContentOffset := FContentOffset + Delta;
|
||||||
|
FLastMousePos := LocalP;
|
||||||
|
Repaint;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
if Assigned(FCapturedNode) then
|
||||||
|
begin
|
||||||
|
var NodeLocal := FCapturedNode.AbsoluteToLocal(WorldP);
|
||||||
|
FCapturedNode.MouseMove(Shift, NodeLocal.X, NodeLocal.Y);
|
||||||
|
end
|
||||||
|
else if Assigned(FRootNode) then
|
||||||
|
begin
|
||||||
|
Hit := FRootNode.HitTest(WorldP);
|
||||||
|
if Hit <> FMouseOverNode then
|
||||||
|
begin
|
||||||
|
if Assigned(FMouseOverNode) then
|
||||||
|
FMouseOverNode.MouseLeave;
|
||||||
|
FMouseOverNode := Hit;
|
||||||
|
if Assigned(FMouseOverNode) then
|
||||||
|
FMouseOverNode.MouseEnter;
|
||||||
|
end;
|
||||||
|
|
||||||
FRootLayer.Position.Point := FRootLayer.Position.Point + LDelta;
|
if Assigned(FMouseOverNode) then
|
||||||
|
begin
|
||||||
FLastMousePos := LCurrentPos;
|
var NodeLocal := FMouseOverNode.AbsoluteToLocal(WorldP);
|
||||||
|
FMouseOverNode.MouseMove(Shift, NodeLocal.X, NodeLocal.Y);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWorkspace.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
procedure TWorkspace.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
||||||
|
var
|
||||||
|
WorldP: TPointF;
|
||||||
begin
|
begin
|
||||||
inherited;
|
inherited;
|
||||||
|
WorldP := ScreenToWorld(LocalToScreen(TPointF.Create(X, Y)));
|
||||||
|
|
||||||
if FIsDraggingNode then
|
if FIsDraggingNode then
|
||||||
begin
|
begin
|
||||||
StopDrag;
|
StopDrag;
|
||||||
@@ -424,68 +336,192 @@ begin
|
|||||||
begin
|
begin
|
||||||
FIsPanning := False;
|
FIsPanning := False;
|
||||||
Root.Captured := nil;
|
Root.Captured := nil;
|
||||||
|
end
|
||||||
|
else if Assigned(FCapturedNode) then
|
||||||
|
begin
|
||||||
|
var NodeLocal := FCapturedNode.AbsoluteToLocal(WorldP);
|
||||||
|
FCapturedNode.MouseUp(Button, Shift, NodeLocal.X, NodeLocal.Y);
|
||||||
|
FCapturedNode := nil;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWorkspace.MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean);
|
procedure TWorkspace.MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean);
|
||||||
var
|
var
|
||||||
LOldZoom, LNewZoom: Single;
|
OldZoom: Single;
|
||||||
LScaleRatio: Single;
|
LocalP, OffsetLocal: TPointF;
|
||||||
LMousePosLocal: TPointF;
|
|
||||||
LOffset: TPointF;
|
|
||||||
begin
|
begin
|
||||||
LOldZoom := Zoom;
|
OldZoom := FZoom;
|
||||||
|
|
||||||
if WheelDelta > 0 then
|
if WheelDelta > 0 then
|
||||||
LNewZoom := LOldZoom + ZoomStep
|
SetZoom(FZoom + ZoomStep)
|
||||||
else
|
else
|
||||||
LNewZoom := LOldZoom - ZoomStep;
|
SetZoom(FZoom - ZoomStep);
|
||||||
|
|
||||||
LNewZoom := EnsureRange(LNewZoom, MinZoom, MaxZoom);
|
if not SameValue(OldZoom, FZoom, TEpsilon.Position) then
|
||||||
|
|
||||||
if not SameValue(LNewZoom, LOldZoom, TEpsilon.Vector) then
|
|
||||||
begin
|
begin
|
||||||
LMousePosLocal := ScreenToLocal(Screen.MousePos);
|
LocalP := ScreenToLocal(Screen.MousePos);
|
||||||
LOffset := LMousePosLocal - FRootLayer.Position.Point;
|
OffsetLocal := LocalP - FContentOffset;
|
||||||
LScaleRatio := LNewZoom / LOldZoom;
|
FContentOffset := LocalP - (OffsetLocal * (FZoom / OldZoom));
|
||||||
|
Repaint;
|
||||||
FRootLayer.Position.Point := LMousePosLocal - (LOffset * LScaleRatio);
|
|
||||||
SetZoom(LNewZoom);
|
|
||||||
end;
|
end;
|
||||||
Handled := True;
|
Handled := True;
|
||||||
|
|
||||||
if not Handled then
|
|
||||||
inherited;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TWorkspace.GetZoom: Single;
|
procedure TWorkspace.BeginDragNode(ASource: TVisualNode);
|
||||||
|
var
|
||||||
|
Bitmap: TBitmap;
|
||||||
|
Scale: Single;
|
||||||
begin
|
begin
|
||||||
Result := FRootLayer.Scale.X;
|
if FIsDraggingNode or FIsPanning then
|
||||||
|
Exit;
|
||||||
|
if ASource = nil then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
FIsDraggingNode := True;
|
||||||
|
FDragSource := ASource;
|
||||||
|
Root.Captured := Self;
|
||||||
|
|
||||||
|
Scale := Scene.GetSceneScale;
|
||||||
|
Bitmap := TBitmap.Create(Round(ASource.Size.Width * Scale), Round(ASource.Size.Height * Scale));
|
||||||
|
Bitmap.BitmapScale := Scale;
|
||||||
|
|
||||||
|
if Bitmap.Canvas.BeginScene then
|
||||||
|
try
|
||||||
|
Bitmap.Canvas.Clear(0);
|
||||||
|
ASource.Paint(Bitmap.Canvas, TPointF.Zero);
|
||||||
|
finally
|
||||||
|
Bitmap.Canvas.EndScene;
|
||||||
|
end;
|
||||||
|
|
||||||
|
FDragVisualBitmap := Bitmap;
|
||||||
|
|
||||||
|
var MouseWorld := ScreenToWorld(Screen.MousePos);
|
||||||
|
var NodeWorld := ASource.GetAbsolutePosition;
|
||||||
|
|
||||||
|
FDragOffset := MouseWorld - NodeWorld;
|
||||||
|
FDragVisualPos := NodeWorld;
|
||||||
|
|
||||||
|
ASource.Opacity := 0.4;
|
||||||
|
ASource.RequestLayout;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWorkspace.SetZoom(const Value: Single);
|
procedure TWorkspace.UpdateDropMarker(const WorldPos: TPointF);
|
||||||
|
var
|
||||||
|
Hit: TVisualNode;
|
||||||
|
TargetContainer: TAutoFitLayout;
|
||||||
|
IsHorizontal: Boolean;
|
||||||
|
InsertIndex: Integer;
|
||||||
|
Child: TVisualNode;
|
||||||
|
P1, P2: TPointF;
|
||||||
|
AbsP: TPointF;
|
||||||
begin
|
begin
|
||||||
FRootLayer.Scale.Point := TPointF.Create(Value, Value);
|
FShowDropMarker := False;
|
||||||
|
FCurrentDropTarget := nil;
|
||||||
|
FCurrentDropIndex := -1;
|
||||||
|
|
||||||
|
if FDragSource = nil then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
Hit := FRootNode.HitTest(WorldPos);
|
||||||
|
if Hit = nil then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
if (Hit is TAutoFitLayout) then
|
||||||
|
TargetContainer := TAutoFitLayout(Hit)
|
||||||
|
else if (Hit.Parent is TAutoFitLayout) then
|
||||||
|
TargetContainer := TAutoFitLayout(Hit.Parent)
|
||||||
|
else
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
if TargetContainer = FDragSource then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
FCurrentDropTarget := TargetContainer;
|
||||||
|
IsHorizontal := TargetContainer.Orientation = loHorizontal;
|
||||||
|
|
||||||
|
var TargetLocal := TargetContainer.AbsoluteToLocal(WorldPos);
|
||||||
|
InsertIndex := TargetContainer.GetInsertionIndex(TargetLocal);
|
||||||
|
FCurrentDropIndex := InsertIndex;
|
||||||
|
|
||||||
|
if InsertIndex < TargetContainer.GetChildCount then
|
||||||
|
begin
|
||||||
|
Child := TargetContainer.GetChild(InsertIndex);
|
||||||
|
AbsP := Child.GetAbsolutePosition;
|
||||||
|
if IsHorizontal then
|
||||||
|
begin
|
||||||
|
P1 := AbsP;
|
||||||
|
P2 := AbsP + TPointF.Create(0, Child.Size.Height);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
P1 := AbsP;
|
||||||
|
P2 := AbsP + TPointF.Create(Child.Size.Width, 0);
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
else if TargetContainer.GetChildCount > 0 then
|
||||||
|
begin
|
||||||
|
Child := TargetContainer.GetChild(TargetContainer.GetChildCount - 1);
|
||||||
|
AbsP := Child.GetAbsolutePosition;
|
||||||
|
if IsHorizontal then
|
||||||
|
begin
|
||||||
|
P1 := AbsP + TPointF.Create(Child.Size.Width + Child.Margins.Right, 0);
|
||||||
|
P2 := P1 + TPointF.Create(0, Child.Size.Height);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
P1 := AbsP + TPointF.Create(0, Child.Size.Height + Child.Margins.Bottom);
|
||||||
|
P2 := P1 + TPointF.Create(Child.Size.Width, 0);
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
AbsP := TargetContainer.GetAbsolutePosition + TPointF.Create(TargetContainer.Padding.Left, TargetContainer.Padding.Top);
|
||||||
|
if IsHorizontal then
|
||||||
|
begin
|
||||||
|
P1 := AbsP;
|
||||||
|
P2 := AbsP + TPointF.Create(0, 20);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
P1 := AbsP;
|
||||||
|
P2 := AbsP + TPointF.Create(20, 0);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
FDropP1 := P1;
|
||||||
|
FDropP2 := P2;
|
||||||
|
FShowDropMarker := True;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TWorkspace.GetContentOffset: TPointF;
|
procedure TWorkspace.StopDrag;
|
||||||
begin
|
begin
|
||||||
Result := FRootLayer.Position.Point;
|
if not FIsDraggingNode then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
FreeAndNil(FDragVisualBitmap);
|
||||||
|
|
||||||
|
if Assigned(FDragSource) then
|
||||||
|
begin
|
||||||
|
FDragSource.Opacity := 1.0;
|
||||||
|
|
||||||
|
if Assigned(FOnNodeDragDrop) and Assigned(FCurrentDropTarget) and (FCurrentDropIndex >= 0) then
|
||||||
|
begin
|
||||||
|
FOnNodeDragDrop(FDragSource, FCurrentDropTarget, FCurrentDropIndex);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
FShowDropMarker := False;
|
||||||
|
FIsDraggingNode := False;
|
||||||
|
FDragSource := nil;
|
||||||
|
FCurrentDropTarget := nil;
|
||||||
|
Root.Captured := nil;
|
||||||
|
|
||||||
|
Repaint;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWorkspace.SetContentOffset(const Value: TPointF);
|
procedure TWorkspace.InvalidateConnections;
|
||||||
begin
|
begin
|
||||||
FRootLayer.Position.Point := Value;
|
Repaint;
|
||||||
end;
|
|
||||||
|
|
||||||
function TWorkspace.GetOnPaintConnections: TOnPaintConnectionsEvent;
|
|
||||||
begin
|
|
||||||
Result := FRootLayer.OnPaintConnections;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TWorkspace.SetOnPaintConnections(const Value: TOnPaintConnectionsEvent);
|
|
||||||
begin
|
|
||||||
FRootLayer.OnPaintConnections := Value;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
+139
-425
@@ -21,9 +21,10 @@ uses
|
|||||||
Myc.Ast.Scope,
|
Myc.Ast.Scope,
|
||||||
Myc.Ast.Environment,
|
Myc.Ast.Environment,
|
||||||
Myc.Ast.Refactoring.Remove,
|
Myc.Ast.Refactoring.Remove,
|
||||||
|
Myc.Fmx.AstEditor.Render,
|
||||||
Myc.Fmx.AstEditor.Node,
|
Myc.Fmx.AstEditor.Node,
|
||||||
Myc.Fmx.AstEditor.Layout,
|
Myc.Fmx.AstEditor.Workspace,
|
||||||
Myc.Fmx.AstEditor.Workspace;
|
Myc.Fmx.AstEditor.Visualizer;
|
||||||
|
|
||||||
type
|
type
|
||||||
TAstEditor = class(TControl)
|
TAstEditor = class(TControl)
|
||||||
@@ -49,37 +50,29 @@ type
|
|||||||
FRootViewNode: TAstViewNode;
|
FRootViewNode: TAstViewNode;
|
||||||
FFocusedNode: TAstViewNode;
|
FFocusedNode: TAstViewNode;
|
||||||
|
|
||||||
// State Tracking
|
|
||||||
FCurrentAst: IAstNode;
|
FCurrentAst: IAstNode;
|
||||||
|
|
||||||
// Undo/Redo Stacks
|
|
||||||
FUndoStack: TUndoStack;
|
FUndoStack: TUndoStack;
|
||||||
FRedoStack: TUndoStack;
|
FRedoStack: TUndoStack;
|
||||||
FIsUndoing: Boolean;
|
FIsUndoing: Boolean;
|
||||||
|
|
||||||
// Events
|
|
||||||
FOnPaintConnections: TOnPaintConnectionsEvent;
|
FOnPaintConnections: TOnPaintConnectionsEvent;
|
||||||
FOnWorkspaceMouseDown: TMouseEvent;
|
FOnWorkspaceMouseDown: TMouseEvent;
|
||||||
|
|
||||||
procedure CollectNodes(Parent: TControl);
|
procedure CollectNodes(Parent: TVisualNode);
|
||||||
procedure ClearVisuals;
|
procedure ClearVisuals;
|
||||||
procedure ApplyErrors(const Log: ICompilerLog);
|
procedure ApplyErrors(const Log: ICompilerLog);
|
||||||
procedure ApplyTypes(const TypedAst: IAstNode);
|
procedure ApplyTypes(const TypedAst: IAstNode);
|
||||||
procedure RootResized(Sender: TObject);
|
|
||||||
|
|
||||||
// Internal Event Wiring
|
procedure HandleNodeDragDrop(ASource, ATarget: TVisualNode; AIndex: Integer);
|
||||||
procedure HandleNodeDragDrop(ASource, ATarget: TControl; AIndex: Integer);
|
|
||||||
procedure HandleKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState);
|
procedure HandleKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState);
|
||||||
procedure HandleWorkspacePaintConnections(ASender: TObject; ACanvas: TCanvas);
|
procedure HandleWorkspacePaintConnections(ASender: TObject; ACanvas: TCanvas);
|
||||||
procedure HandleWorkspaceMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
procedure HandleWorkspaceMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
||||||
procedure HandleNodeMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
|
||||||
|
|
||||||
procedure CommitSnapshot;
|
procedure CommitSnapshot;
|
||||||
|
|
||||||
// --- Navigation Helper ---
|
|
||||||
procedure EnsureVisible(Node: TAstViewNode);
|
procedure EnsureVisible(Node: TAstViewNode);
|
||||||
function GetParentNode(StartNode: TAstViewNode): TAstViewNode;
|
|
||||||
function GetFirstChild(StartNode: TAstViewNode): TAstViewNode;
|
|
||||||
function GetNextVerticalNeighbor(StartNode: TAstViewNode): TAstViewNode;
|
function GetNextVerticalNeighbor(StartNode: TAstViewNode): TAstViewNode;
|
||||||
function GetPrevVerticalNeighbor(StartNode: TAstViewNode): TAstViewNode;
|
function GetPrevVerticalNeighbor(StartNode: TAstViewNode): TAstViewNode;
|
||||||
function GetNextLinearNode(StartNode: TAstViewNode): TAstViewNode;
|
function GetNextLinearNode(StartNode: TAstViewNode): TAstViewNode;
|
||||||
@@ -88,18 +81,16 @@ type
|
|||||||
procedure SetOnPaintConnections(const Value: TOnPaintConnectionsEvent);
|
procedure SetOnPaintConnections(const Value: TOnPaintConnectionsEvent);
|
||||||
|
|
||||||
protected
|
protected
|
||||||
procedure Paint; override; // For design-time border
|
procedure Paint; override;
|
||||||
|
|
||||||
public
|
public
|
||||||
constructor Create(AOwner: TComponent); override;
|
constructor Create(AOwner: TComponent); override;
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
|
|
||||||
// Must be called after creation to link the AST Environment
|
|
||||||
procedure Init(const AEnv: TAstEnvironment);
|
procedure Init(const AEnv: TAstEnvironment);
|
||||||
|
|
||||||
procedure ValidateAndShow;
|
procedure ValidateAndShow;
|
||||||
|
|
||||||
// AClearHistory=False ensures stacks are kept (e.g. after internal edits)
|
|
||||||
procedure SetRoot(const AstNode: IAstNode; AClearHistory: Boolean = True);
|
procedure SetRoot(const AstNode: IAstNode; AClearHistory: Boolean = True);
|
||||||
|
|
||||||
procedure Undo;
|
procedure Undo;
|
||||||
@@ -123,7 +114,6 @@ type
|
|||||||
property Enabled;
|
property Enabled;
|
||||||
property Opacity;
|
property Opacity;
|
||||||
|
|
||||||
// Expose events relevant to the outside world
|
|
||||||
property OnPaintConnections: TOnPaintConnectionsEvent read FOnPaintConnections write SetOnPaintConnections;
|
property OnPaintConnections: TOnPaintConnectionsEvent read FOnPaintConnections write SetOnPaintConnections;
|
||||||
property OnMouseDown: TMouseEvent read FOnWorkspaceMouseDown write FOnWorkspaceMouseDown;
|
property OnMouseDown: TMouseEvent read FOnWorkspaceMouseDown write FOnWorkspaceMouseDown;
|
||||||
end;
|
end;
|
||||||
@@ -131,8 +121,7 @@ type
|
|||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses
|
uses
|
||||||
Myc.Data.Value,
|
Myc.Data.Value;
|
||||||
Myc.Fmx.AstEditor.Visualizer;
|
|
||||||
|
|
||||||
{ TAstEditor }
|
{ TAstEditor }
|
||||||
|
|
||||||
@@ -143,14 +132,12 @@ begin
|
|||||||
Height := 300;
|
Height := 300;
|
||||||
ClipChildren := True;
|
ClipChildren := True;
|
||||||
|
|
||||||
// Create Internal Workspace
|
|
||||||
FWorkspace := TWorkspace.Create(Self);
|
FWorkspace := TWorkspace.Create(Self);
|
||||||
FWorkspace.Parent := Self;
|
FWorkspace.Parent := Self;
|
||||||
FWorkspace.Align := TAlignLayout.Client;
|
FWorkspace.Align := TAlignLayout.Client;
|
||||||
FWorkspace.Stored := False;
|
FWorkspace.Stored := False;
|
||||||
FWorkspace.Locked := True;
|
FWorkspace.Locked := True;
|
||||||
|
|
||||||
// Wire Internal Events
|
|
||||||
FWorkspace.OnNodeDragDrop := HandleNodeDragDrop;
|
FWorkspace.OnNodeDragDrop := HandleNodeDragDrop;
|
||||||
FWorkspace.OnKeyDown := HandleKeyDown;
|
FWorkspace.OnKeyDown := HandleKeyDown;
|
||||||
FWorkspace.OnPaintConnections := HandleWorkspacePaintConnections;
|
FWorkspace.OnPaintConnections := HandleWorkspacePaintConnections;
|
||||||
@@ -205,21 +192,18 @@ begin
|
|||||||
if FWorkspace.CanFocus then
|
if FWorkspace.CanFocus then
|
||||||
FWorkspace.SetFocus;
|
FWorkspace.SetFocus;
|
||||||
|
|
||||||
|
var WorldP := FWorkspace.ScreenToWorld(FWorkspace.LocalToScreen(TPointF.Create(X, Y)));
|
||||||
|
var Hit := FWorkspace.RootNode.HitTest(WorldP);
|
||||||
|
|
||||||
|
if (Button = TMouseButton.mbLeft) and (Hit is TAstViewNode) then
|
||||||
|
begin
|
||||||
|
SetFocusedNode(TAstViewNode(Hit));
|
||||||
|
end;
|
||||||
|
|
||||||
if Assigned(FOnWorkspaceMouseDown) then
|
if Assigned(FOnWorkspaceMouseDown) then
|
||||||
FOnWorkspaceMouseDown(Self, Button, Shift, X, Y);
|
FOnWorkspaceMouseDown(Self, Button, Shift, X, Y);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TAstEditor.HandleNodeMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
|
||||||
begin
|
|
||||||
if FWorkspace.CanFocus then
|
|
||||||
FWorkspace.SetFocus;
|
|
||||||
|
|
||||||
if (Button = TMouseButton.mbLeft) and (Sender is TAstViewNode) then
|
|
||||||
begin
|
|
||||||
SetFocusedNode(TAstViewNode(Sender));
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstEditor.CommitSnapshot;
|
procedure TAstEditor.CommitSnapshot;
|
||||||
begin
|
begin
|
||||||
if Assigned(FCurrentAst) and (not FIsUndoing) then
|
if Assigned(FCurrentAst) and (not FIsUndoing) then
|
||||||
@@ -242,7 +226,7 @@ begin
|
|||||||
FRedoStack.Push(FCurrentAst);
|
FRedoStack.Push(FCurrentAst);
|
||||||
|
|
||||||
prevAst := FUndoStack.Pop;
|
prevAst := FUndoStack.Pop;
|
||||||
SetRoot(prevAst); // Default Clear=True, but guarded by FIsUndoing
|
SetRoot(prevAst);
|
||||||
finally
|
finally
|
||||||
FIsUndoing := False;
|
FIsUndoing := False;
|
||||||
end;
|
end;
|
||||||
@@ -277,56 +261,49 @@ begin
|
|||||||
Result := FRedoStack.Count > 0;
|
Result := FRedoStack.Count > 0;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TAstEditor.HandleNodeDragDrop(ASource, ATarget: TControl; AIndex: Integer);
|
procedure TAstEditor.HandleNodeDragDrop(ASource, ATarget: TVisualNode; AIndex: Integer);
|
||||||
var
|
var
|
||||||
SourceParent: TControl;
|
SourceView, TargetView: TAstViewNode;
|
||||||
ContainerOwner: TAstViewNode;
|
ContainerOwner: TAstViewNode;
|
||||||
Reorderable: IReorderable;
|
Reorderable: IReorderable;
|
||||||
SourceIdx, TargetIdx: Integer;
|
SourceIdx: Integer;
|
||||||
NewAst: IAstNode;
|
NewAst: IAstNode;
|
||||||
begin
|
begin
|
||||||
if (ASource = nil) or (AIndex < 0) then
|
if (ASource = nil) or (AIndex < 0) then
|
||||||
exit;
|
exit;
|
||||||
if not (ASource is TAstViewNode) then
|
if not (ASource is TAstViewNode) then
|
||||||
exit;
|
exit;
|
||||||
|
if not (ATarget is TAstViewNode) then
|
||||||
if not (ASource.Parent is TControl) then
|
|
||||||
exit;
|
exit;
|
||||||
SourceParent := TControl(ASource.Parent);
|
|
||||||
|
|
||||||
ContainerOwner := nil;
|
SourceView := TAstViewNode(ASource);
|
||||||
|
TargetView := TAstViewNode(ATarget);
|
||||||
|
ContainerOwner := TargetView;
|
||||||
|
|
||||||
if (SourceParent is TAstViewNode) then
|
if not Supports(ContainerOwner.Handler, IReorderable, Reorderable) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
if Reorderable.CanDrag(SourceView) and (SourceView.Parent = TargetView) then
|
||||||
begin
|
begin
|
||||||
ContainerOwner := TAstViewNode(SourceParent);
|
// Find current index of SourceView in Target
|
||||||
if not Supports(ContainerOwner.Handler, IReorderable, Reorderable) then
|
SourceIdx := -1;
|
||||||
ContainerOwner := nil;
|
for var i := 0 to TargetView.GetChildCount - 1 do
|
||||||
end;
|
|
||||||
|
|
||||||
if (ContainerOwner = nil) and (SourceParent.Parent is TAstViewNode) then
|
|
||||||
begin
|
|
||||||
ContainerOwner := TAstViewNode(SourceParent.Parent);
|
|
||||||
if not Supports(ContainerOwner.Handler, IReorderable, Reorderable) then
|
|
||||||
ContainerOwner := nil;
|
|
||||||
end;
|
|
||||||
|
|
||||||
if (ContainerOwner <> nil) and (Reorderable <> nil) then
|
|
||||||
begin
|
|
||||||
if Reorderable.CanDrag(ASource) then
|
|
||||||
begin
|
begin
|
||||||
SourceIdx := ASource.Index;
|
if TargetView.GetChild(i) = SourceView then
|
||||||
TargetIdx := AIndex;
|
|
||||||
|
|
||||||
if SourceIdx <> TargetIdx then
|
|
||||||
begin
|
begin
|
||||||
CommitSnapshot;
|
SourceIdx := i;
|
||||||
Reorderable.MoveChild(SourceIdx, TargetIdx);
|
Break;
|
||||||
NewAst := FRootViewNode.CreateAst;
|
|
||||||
|
|
||||||
// IMPORTANT: Pass False to preserve the undo stack we just updated
|
|
||||||
SetRoot(NewAst, False);
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
if SourceIdx >= 0 then
|
||||||
|
begin
|
||||||
|
CommitSnapshot;
|
||||||
|
Reorderable.MoveChild(SourceIdx, AIndex);
|
||||||
|
|
||||||
|
NewAst := FRootViewNode.CreateAst;
|
||||||
|
SetRoot(NewAst, False);
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -334,7 +311,7 @@ procedure TAstEditor.HandleKeyDown(Sender: TObject; var Key: Word; var KeyChar:
|
|||||||
var
|
var
|
||||||
NewRoot: IAstNode;
|
NewRoot: IAstNode;
|
||||||
begin
|
begin
|
||||||
// Undo / Redo Handling
|
// Undo / Redo
|
||||||
if (ssCtrl in Shift) then
|
if (ssCtrl in Shift) then
|
||||||
begin
|
begin
|
||||||
if (Key = vkZ) then
|
if (Key = vkZ) then
|
||||||
@@ -361,7 +338,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Delete Handling
|
// Delete
|
||||||
if (Key = vkDelete) then
|
if (Key = vkDelete) then
|
||||||
begin
|
begin
|
||||||
if Assigned(FFocusedNode) and Assigned(FFocusedNode.Node) and Assigned(FCurrentAst) then
|
if Assigned(FFocusedNode) and Assigned(FFocusedNode.Node) and Assigned(FCurrentAst) then
|
||||||
@@ -369,7 +346,6 @@ begin
|
|||||||
CommitSnapshot;
|
CommitSnapshot;
|
||||||
if TAstNodeRemover.TryRemove(FCurrentAst, FFocusedNode.Node, NewRoot) then
|
if TAstNodeRemover.TryRemove(FCurrentAst, FFocusedNode.Node, NewRoot) then
|
||||||
begin
|
begin
|
||||||
// IMPORTANT: Pass False to preserve the undo stack we just updated
|
|
||||||
SetRoot(NewRoot, False);
|
SetRoot(NewRoot, False);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -391,7 +367,6 @@ var
|
|||||||
begin
|
begin
|
||||||
FCurrentAst := AstNode;
|
FCurrentAst := AstNode;
|
||||||
|
|
||||||
// Only clear if explicitly requested AND not currently performing an undo/redo
|
|
||||||
if AClearHistory and (not FIsUndoing) then
|
if AClearHistory and (not FIsUndoing) then
|
||||||
begin
|
begin
|
||||||
FUndoStack.Clear;
|
FUndoStack.Clear;
|
||||||
@@ -399,55 +374,47 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
FFocusedNode := nil;
|
FFocusedNode := nil;
|
||||||
|
FWorkspace.RootNode := nil;
|
||||||
|
|
||||||
FWorkspace.World.DeleteChildren;
|
|
||||||
if Assigned(FRootViewNode) then
|
if Assigned(FRootViewNode) then
|
||||||
FRootViewNode.OnResized := nil;
|
FRootViewNode := nil;
|
||||||
FRootViewNode := nil;
|
|
||||||
|
|
||||||
if not Assigned(AstNode) then
|
if not Assigned(AstNode) then
|
||||||
exit;
|
exit;
|
||||||
|
|
||||||
Visualizer := TAstVisualizer.Create(FWorkspace, FWorkspace.World, 0);
|
Visualizer := TAstVisualizer.Create(FWorkspace, nil, 0);
|
||||||
FRootViewNode := Visualizer.CallAccept(AstNode);
|
FRootViewNode := Visualizer.CallAccept(AstNode);
|
||||||
|
|
||||||
if Assigned(FRootViewNode) then
|
if Assigned(FRootViewNode) then
|
||||||
begin
|
begin
|
||||||
FWorkspace.World.Size := FRootViewNode.Size;
|
FWorkspace.RootNode := FRootViewNode;
|
||||||
FRootViewNode.OnResized := RootResized;
|
FRootViewNode.Position := TPointF.Create(50, 50);
|
||||||
FRootViewNode.Position.Point := TPointF.Create(50, 50);
|
|
||||||
end;
|
|
||||||
|
|
||||||
if Assigned(FRootViewNode) then
|
|
||||||
ValidateAndShow;
|
ValidateAndShow;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TAstEditor.CollectNodes(Parent: TControl);
|
procedure TAstEditor.CollectNodes(Parent: TVisualNode);
|
||||||
var
|
var
|
||||||
i: Integer;
|
i: Integer;
|
||||||
child: TControl;
|
child: TVisualNode;
|
||||||
viewNode: TAstViewNode;
|
viewNode: TAstViewNode;
|
||||||
begin
|
begin
|
||||||
for i := 0 to Parent.ChildrenCount - 1 do
|
for i := 0 to Parent.GetChildCount - 1 do
|
||||||
begin
|
begin
|
||||||
if Parent.Children[i] is TControl then
|
child := Parent.GetChild(i);
|
||||||
|
|
||||||
|
if child is TAstViewNode then
|
||||||
begin
|
begin
|
||||||
child := TControl(Parent.Children[i]);
|
viewNode := TAstViewNode(child);
|
||||||
|
|
||||||
if child is TAstViewNode then
|
if Assigned(viewNode.Node) and Assigned(viewNode.Node.Identity) then
|
||||||
begin
|
begin
|
||||||
viewNode := TAstViewNode(child);
|
FNodeMap.AddOrSetValue(viewNode.Node.Identity, viewNode);
|
||||||
viewNode.OnMouseDown := HandleNodeMouseDown;
|
|
||||||
|
|
||||||
if Assigned(viewNode.Node) and Assigned(viewNode.Node.Identity) then
|
|
||||||
begin
|
|
||||||
FNodeMap.AddOrSetValue(viewNode.Node.Identity, viewNode);
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if child.ChildrenCount > 0 then
|
|
||||||
CollectNodes(child);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
if child.GetChildCount > 0 then
|
||||||
|
CollectNodes(child);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -495,11 +462,6 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TAstEditor.RootResized(Sender: TObject);
|
|
||||||
begin
|
|
||||||
FWorkspace.World.Size := FRootViewNode.Size;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstEditor.ValidateAndShow;
|
procedure TAstEditor.ValidateAndShow;
|
||||||
var
|
var
|
||||||
logicalAst: IAstNode;
|
logicalAst: IAstNode;
|
||||||
@@ -510,7 +472,7 @@ begin
|
|||||||
logicalAst := FRootViewNode.CreateAst;
|
logicalAst := FRootViewNode.CreateAst;
|
||||||
|
|
||||||
FNodeMap.Clear;
|
FNodeMap.Clear;
|
||||||
CollectNodes(FWorkspace.World);
|
CollectNodes(FWorkspace.RootNode);
|
||||||
|
|
||||||
ClearVisuals;
|
ClearVisuals;
|
||||||
|
|
||||||
@@ -527,6 +489,9 @@ begin
|
|||||||
var compiled := FEnv.Link(specAst.AsLambdaExpression, log);
|
var compiled := FEnv.Link(specAst.AsLambdaExpression, log);
|
||||||
|
|
||||||
ApplyErrors(log);
|
ApplyErrors(log);
|
||||||
|
|
||||||
|
FWorkspace.RootNode.RecalcLayout;
|
||||||
|
FWorkspace.InvalidateConnections;
|
||||||
except
|
except
|
||||||
on E: ECompilationFailed do
|
on E: ECompilationFailed do
|
||||||
begin
|
begin
|
||||||
@@ -537,13 +502,9 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
FWorkspace.World.Repaint;
|
FWorkspace.RequestLayout;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// =================================================================================================
|
|
||||||
// == NAVIGATION IMPLEMENTATION
|
|
||||||
// =================================================================================================
|
|
||||||
|
|
||||||
procedure TAstEditor.SetFocusedNode(Node: TAstViewNode);
|
procedure TAstEditor.SetFocusedNode(Node: TAstViewNode);
|
||||||
begin
|
begin
|
||||||
if FFocusedNode = Node then
|
if FFocusedNode = Node then
|
||||||
@@ -565,338 +526,96 @@ procedure TAstEditor.EnsureVisible(Node: TAstViewNode);
|
|||||||
const
|
const
|
||||||
cMargin = 40;
|
cMargin = 40;
|
||||||
var
|
var
|
||||||
NodeRect, ViewRect: TRectF;
|
NodeRect: TRectF;
|
||||||
Delta: TPointF;
|
|
||||||
begin
|
begin
|
||||||
if (Node = nil) or (FWorkspace = nil) then
|
if (Node = nil) or (FWorkspace = nil) then
|
||||||
Exit;
|
Exit;
|
||||||
|
|
||||||
NodeRect := Node.AbsoluteRect;
|
NodeRect := Node.AbsoluteRect;
|
||||||
ViewRect := FWorkspace.AbsoluteRect;
|
|
||||||
ViewRect.Inflate(-cMargin, -cMargin);
|
|
||||||
|
|
||||||
Delta := TPointF.Zero;
|
var ScreenCenter := TRectF.Create(0, 0, FWorkspace.Width, FWorkspace.Height).CenterPoint;
|
||||||
|
var NodeCenterWorld := NodeRect.CenterPoint;
|
||||||
|
|
||||||
if NodeRect.Width > ViewRect.Width then
|
FWorkspace.ContentOffset := ScreenCenter - (NodeCenterWorld * FWorkspace.Zoom);
|
||||||
Delta.X := ViewRect.CenterPoint.X - NodeRect.CenterPoint.X
|
|
||||||
else if NodeRect.Left < ViewRect.Left then
|
|
||||||
Delta.X := ViewRect.Left - NodeRect.Left
|
|
||||||
else if NodeRect.Right > ViewRect.Right then
|
|
||||||
Delta.X := ViewRect.Right - NodeRect.Right;
|
|
||||||
|
|
||||||
if NodeRect.Height > ViewRect.Height then
|
|
||||||
Delta.Y := ViewRect.CenterPoint.Y - NodeRect.CenterPoint.Y
|
|
||||||
else if NodeRect.Top < ViewRect.Top then
|
|
||||||
Delta.Y := ViewRect.Top - NodeRect.Top
|
|
||||||
else if NodeRect.Bottom > ViewRect.Bottom then
|
|
||||||
Delta.Y := ViewRect.Bottom - NodeRect.Bottom;
|
|
||||||
|
|
||||||
if not (SameValue(Delta.X, 0, TEpsilon.Position) and SameValue(Delta.Y, 0, TEpsilon.Position)) then
|
|
||||||
begin
|
|
||||||
FWorkspace.ContentOffset := FWorkspace.ContentOffset + Delta;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TAstEditor.GetParentNode(StartNode: TAstViewNode): TAstViewNode;
|
|
||||||
var
|
|
||||||
curr: TControl;
|
|
||||||
begin
|
|
||||||
Result := nil;
|
|
||||||
if StartNode = nil then
|
|
||||||
Exit;
|
|
||||||
|
|
||||||
curr := StartNode.ParentControl;
|
|
||||||
while (curr <> nil) and (curr <> FWorkspace.World) do
|
|
||||||
begin
|
|
||||||
if curr is TAstViewNode then
|
|
||||||
Exit(TAstViewNode(curr));
|
|
||||||
curr := curr.ParentControl;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TAstEditor.GetFirstChild(StartNode: TAstViewNode): TAstViewNode;
|
|
||||||
function FindFirst(Parent: TControl): TAstViewNode;
|
|
||||||
var
|
|
||||||
i: Integer;
|
|
||||||
begin
|
|
||||||
Result := nil;
|
|
||||||
for i := 0 to Parent.ChildrenCount - 1 do
|
|
||||||
begin
|
|
||||||
if Parent.Children[i] is TAstViewNode then
|
|
||||||
Exit(TAstViewNode(Parent.Children[i]))
|
|
||||||
else if (Parent.Children[i] is TControl) and (TControl(Parent.Children[i]).ChildrenCount > 0) then
|
|
||||||
begin
|
|
||||||
Result := FindFirst(TControl(Parent.Children[i]));
|
|
||||||
if Result <> nil then
|
|
||||||
Exit;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
begin
|
|
||||||
Result := FindFirst(StartNode);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function FindFirstViewNode(Container: TControl): TAstViewNode;
|
|
||||||
var
|
|
||||||
i: Integer;
|
|
||||||
Child: TControl;
|
|
||||||
begin
|
|
||||||
Result := nil;
|
|
||||||
if Container is TAstViewNode then
|
|
||||||
Exit(TAstViewNode(Container));
|
|
||||||
|
|
||||||
for i := 0 to Container.ChildrenCount - 1 do
|
|
||||||
begin
|
|
||||||
if Container.Children[i] is TControl then
|
|
||||||
begin
|
|
||||||
Child := TControl(Container.Children[i]);
|
|
||||||
if Child.Visible then
|
|
||||||
begin
|
|
||||||
Result := FindFirstViewNode(Child);
|
|
||||||
if Result <> nil then
|
|
||||||
Exit;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function FindLastViewNode(Container: TControl): TAstViewNode;
|
|
||||||
var
|
|
||||||
i: Integer;
|
|
||||||
Child: TControl;
|
|
||||||
begin
|
|
||||||
Result := nil;
|
|
||||||
if Container is TAstViewNode then
|
|
||||||
exit(TAstViewNode(Container));
|
|
||||||
|
|
||||||
for i := Container.ChildrenCount - 1 downto 0 do
|
|
||||||
begin
|
|
||||||
if Container.Children[i] is TControl then
|
|
||||||
begin
|
|
||||||
Child := TControl(Container.Children[i]);
|
|
||||||
if Child.Visible then
|
|
||||||
begin
|
|
||||||
var Found := FindLastViewNode(Child);
|
|
||||||
if Found <> nil then
|
|
||||||
Exit(Found);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function FindNextSiblingInList(Parent: TControl; AfterControl: TControl): TAstViewNode;
|
|
||||||
var
|
|
||||||
i, StartIndex: Integer;
|
|
||||||
Candidate: TControl;
|
|
||||||
begin
|
|
||||||
Result := nil;
|
|
||||||
StartIndex := -1;
|
|
||||||
for i := 0 to Parent.ChildrenCount - 1 do
|
|
||||||
if Parent.Children[i] = AfterControl then
|
|
||||||
begin
|
|
||||||
StartIndex := i;
|
|
||||||
Break;
|
|
||||||
end;
|
|
||||||
|
|
||||||
if StartIndex >= 0 then
|
|
||||||
begin
|
|
||||||
for i := StartIndex + 1 to Parent.ChildrenCount - 1 do
|
|
||||||
begin
|
|
||||||
if Parent.Children[i] is TControl then
|
|
||||||
begin
|
|
||||||
Candidate := TControl(Parent.Children[i]);
|
|
||||||
if Candidate.Visible then
|
|
||||||
begin
|
|
||||||
Result := FindFirstViewNode(Candidate);
|
|
||||||
if Result <> nil then
|
|
||||||
Exit;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function FindPrevSiblingInList(Parent: TControl; BeforeControl: TControl): TAstViewNode;
|
|
||||||
var
|
|
||||||
i, StartIndex: Integer;
|
|
||||||
Candidate: TControl;
|
|
||||||
begin
|
|
||||||
Result := nil;
|
|
||||||
StartIndex := -1;
|
|
||||||
for i := 0 to Parent.ChildrenCount - 1 do
|
|
||||||
if Parent.Children[i] = BeforeControl then
|
|
||||||
begin
|
|
||||||
StartIndex := i;
|
|
||||||
Break;
|
|
||||||
end;
|
|
||||||
|
|
||||||
if StartIndex >= 0 then
|
|
||||||
begin
|
|
||||||
for i := StartIndex - 1 downto 0 do
|
|
||||||
begin
|
|
||||||
if Parent.Children[i] is TControl then
|
|
||||||
begin
|
|
||||||
Candidate := TControl(Parent.Children[i]);
|
|
||||||
if Candidate.Visible then
|
|
||||||
begin
|
|
||||||
Result := FindLastViewNode(Candidate);
|
|
||||||
if Result <> nil then
|
|
||||||
Exit;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TAstEditor.GetNextVerticalNeighbor(StartNode: TAstViewNode): TAstViewNode;
|
|
||||||
var
|
|
||||||
Current, Parent: TControl;
|
|
||||||
begin
|
|
||||||
Result := nil;
|
|
||||||
Current := StartNode;
|
|
||||||
|
|
||||||
while (Current <> nil) and (Current <> FWorkspace.World) do
|
|
||||||
begin
|
|
||||||
Parent := Current.ParentControl;
|
|
||||||
if Parent = nil then
|
|
||||||
Break;
|
|
||||||
|
|
||||||
if (Parent is TAutoFitControl) and (TAutoFitControl(Parent).Orientation = TLayoutOrientation.loVertical) then
|
|
||||||
begin
|
|
||||||
Result := FindNextSiblingInList(Parent, Current);
|
|
||||||
if Result <> nil then
|
|
||||||
Exit;
|
|
||||||
end;
|
|
||||||
|
|
||||||
Current := Parent;
|
|
||||||
end;
|
|
||||||
|
|
||||||
if Result = nil then
|
|
||||||
Result := GetNextLinearNode(StartNode)
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TAstEditor.GetPrevVerticalNeighbor(StartNode: TAstViewNode): TAstViewNode;
|
|
||||||
var
|
|
||||||
Current, Parent: TControl;
|
|
||||||
begin
|
|
||||||
Result := nil;
|
|
||||||
Current := StartNode;
|
|
||||||
|
|
||||||
while (Current <> nil) and (Current <> FWorkspace.World) do
|
|
||||||
begin
|
|
||||||
Parent := Current.ParentControl;
|
|
||||||
if Parent = nil then
|
|
||||||
Break;
|
|
||||||
|
|
||||||
if (Parent is TAutoFitControl) and (TAutoFitControl(Parent).Orientation = TLayoutOrientation.loVertical) then
|
|
||||||
begin
|
|
||||||
Result := FindPrevSiblingInList(Parent, Current);
|
|
||||||
if Result <> nil then
|
|
||||||
Exit;
|
|
||||||
end;
|
|
||||||
|
|
||||||
Current := Parent;
|
|
||||||
end;
|
|
||||||
|
|
||||||
if Result = nil then
|
|
||||||
Result := GetPrevLinearNode(StartNode)
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstEditor.GetNextLinearNode(StartNode: TAstViewNode): TAstViewNode;
|
function TAstEditor.GetNextLinearNode(StartNode: TAstViewNode): TAstViewNode;
|
||||||
var
|
function FindFirst(Node: TVisualNode): TAstViewNode;
|
||||||
CurrentControl, ParentControl: TControl;
|
|
||||||
i, StartIndex: Integer;
|
|
||||||
Candidate: TControl;
|
|
||||||
begin
|
|
||||||
Result := GetFirstChild(StartNode);
|
|
||||||
if Result <> nil then
|
|
||||||
Exit;
|
|
||||||
|
|
||||||
CurrentControl := StartNode;
|
|
||||||
while (CurrentControl <> nil) and (CurrentControl <> FWorkspace.World) do
|
|
||||||
begin
|
begin
|
||||||
ParentControl := CurrentControl.ParentControl;
|
if Node is TAstViewNode then
|
||||||
if ParentControl = nil then
|
Exit(TAstViewNode(Node));
|
||||||
Break;
|
for var i := 0 to Node.GetChildCount - 1 do
|
||||||
|
|
||||||
StartIndex := -1;
|
|
||||||
for i := 0 to ParentControl.ChildrenCount - 1 do
|
|
||||||
if ParentControl.Children[i] = CurrentControl then
|
|
||||||
begin
|
|
||||||
StartIndex := i;
|
|
||||||
Break;
|
|
||||||
end;
|
|
||||||
|
|
||||||
if StartIndex >= 0 then
|
|
||||||
begin
|
begin
|
||||||
for i := StartIndex + 1 to ParentControl.ChildrenCount - 1 do
|
Result := FindFirst(Node.GetChild(i));
|
||||||
begin
|
if Result <> nil then
|
||||||
if ParentControl.Children[i] is TControl then
|
Exit;
|
||||||
begin
|
|
||||||
Candidate := TControl(ParentControl.Children[i]);
|
|
||||||
if Candidate.Visible then
|
|
||||||
begin
|
|
||||||
Result := FindFirstViewNode(Candidate);
|
|
||||||
if Result <> nil then
|
|
||||||
Exit;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
Result := nil;
|
||||||
CurrentControl := ParentControl;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := nil;
|
function FindNext(Node: TVisualNode): TAstViewNode;
|
||||||
|
var
|
||||||
|
Parent: TVisualNode;
|
||||||
|
Idx: Integer;
|
||||||
|
begin
|
||||||
|
Result := nil;
|
||||||
|
if Node.GetChildCount > 0 then
|
||||||
|
begin
|
||||||
|
Result := FindFirst(Node.GetChild(0));
|
||||||
|
if Result <> nil then
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Parent := Node.Parent;
|
||||||
|
while Parent <> nil do
|
||||||
|
begin
|
||||||
|
Idx := -1;
|
||||||
|
for var k := 0 to Parent.GetChildCount - 1 do
|
||||||
|
if Parent.GetChild(k) = Node then
|
||||||
|
begin
|
||||||
|
Idx := k;
|
||||||
|
break;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if (Idx >= 0) and (Idx < Parent.GetChildCount - 1) then
|
||||||
|
begin
|
||||||
|
for var k := Idx + 1 to Parent.GetChildCount - 1 do
|
||||||
|
begin
|
||||||
|
Result := FindFirst(Parent.GetChild(k));
|
||||||
|
if Result <> nil then
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Node := Parent;
|
||||||
|
Parent := Node.Parent;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
begin
|
||||||
|
for var i := 0 to StartNode.GetChildCount - 1 do
|
||||||
|
begin
|
||||||
|
Result := FindFirst(StartNode.GetChild(i));
|
||||||
|
if Result <> nil then
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
Result := FindNext(StartNode);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstEditor.GetPrevLinearNode(StartNode: TAstViewNode): TAstViewNode;
|
function TAstEditor.GetPrevLinearNode(StartNode: TAstViewNode): TAstViewNode;
|
||||||
var
|
|
||||||
CurrentControl, ParentControl: TControl;
|
|
||||||
i, StartIndex: Integer;
|
|
||||||
Candidate: TControl;
|
|
||||||
begin
|
begin
|
||||||
CurrentControl := StartNode;
|
|
||||||
while (CurrentControl <> nil) and (CurrentControl <> FWorkspace.World) do
|
|
||||||
begin
|
|
||||||
ParentControl := CurrentControl.ParentControl;
|
|
||||||
if ParentControl = nil then
|
|
||||||
Break;
|
|
||||||
|
|
||||||
StartIndex := -1;
|
|
||||||
for i := 0 to ParentControl.ChildrenCount - 1 do
|
|
||||||
if ParentControl.Children[i] = CurrentControl then
|
|
||||||
begin
|
|
||||||
StartIndex := i;
|
|
||||||
Break;
|
|
||||||
end;
|
|
||||||
|
|
||||||
if StartIndex >= 0 then
|
|
||||||
begin
|
|
||||||
for i := StartIndex - 1 downto 0 do
|
|
||||||
begin
|
|
||||||
if ParentControl.Children[i] is TControl then
|
|
||||||
begin
|
|
||||||
Candidate := TControl(ParentControl.Children[i]);
|
|
||||||
if Candidate.Visible then
|
|
||||||
begin
|
|
||||||
Result := FindLastViewNode(Candidate);
|
|
||||||
if Result <> nil then
|
|
||||||
Exit;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
if ParentControl is TAstViewNode then
|
|
||||||
Exit(TAstViewNode(ParentControl));
|
|
||||||
|
|
||||||
CurrentControl := ParentControl;
|
|
||||||
end;
|
|
||||||
|
|
||||||
Result := nil;
|
Result := nil;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TAstEditor.GetNextVerticalNeighbor(StartNode: TAstViewNode): TAstViewNode;
|
||||||
|
begin
|
||||||
|
Result := GetNextLinearNode(StartNode);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstEditor.GetPrevVerticalNeighbor(StartNode: TAstViewNode): TAstViewNode;
|
||||||
|
begin
|
||||||
|
Result := GetPrevLinearNode(StartNode);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TAstEditor.Navigate(Key: Word; Shift: TShiftState);
|
procedure TAstEditor.Navigate(Key: Word; Shift: TShiftState);
|
||||||
var
|
var
|
||||||
Current: TAstViewNode;
|
Current: TAstViewNode;
|
||||||
@@ -923,19 +642,14 @@ begin
|
|||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if Key = vkEscape then
|
|
||||||
begin
|
|
||||||
Target := GetParentNode(Current);
|
|
||||||
if Target <> nil then
|
|
||||||
SetFocusedNode(Target);
|
|
||||||
Exit;
|
|
||||||
end;
|
|
||||||
|
|
||||||
case Key of
|
case Key of
|
||||||
vkLeft: Target := GetPrevLinearNode(Current);
|
vkLeft: Target := GetPrevLinearNode(Current);
|
||||||
vkRight: Target := GetNextLinearNode(Current);
|
vkRight: Target := GetNextLinearNode(Current);
|
||||||
vkUp: Target := GetPrevVerticalNeighbor(Current);
|
vkUp: Target := GetPrevVerticalNeighbor(Current);
|
||||||
vkDown: Target := GetNextVerticalNeighbor(Current);
|
vkDown: Target := GetNextVerticalNeighbor(Current);
|
||||||
|
vkEscape:
|
||||||
|
if Current.Parent is TAstViewNode then
|
||||||
|
Target := TAstViewNode(Current.Parent);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if Target <> nil then
|
if Target <> nil then
|
||||||
|
|||||||
Reference in New Issue
Block a user