Ast node delete
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -31,7 +31,8 @@ uses
|
|||||||
Myc.Fmx.AstEditor.Workspace in '..\Src\AST\Myc.Fmx.AstEditor.Workspace.pas',
|
Myc.Fmx.AstEditor.Workspace in '..\Src\AST\Myc.Fmx.AstEditor.Workspace.pas',
|
||||||
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';
|
||||||
|
|
||||||
{$R *.res}
|
{$R *.res}
|
||||||
|
|
||||||
|
|||||||
@@ -162,6 +162,7 @@
|
|||||||
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Layout.pas"/>
|
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Layout.pas"/>
|
||||||
<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"/>
|
||||||
<BuildConfiguration Include="Base">
|
<BuildConfiguration Include="Base">
|
||||||
<Key>Base</Key>
|
<Key>Base</Key>
|
||||||
</BuildConfiguration>
|
</BuildConfiguration>
|
||||||
|
|||||||
@@ -0,0 +1,429 @@
|
|||||||
|
unit Myc.Ast.Refactoring.Remove;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.SysUtils,
|
||||||
|
System.Classes,
|
||||||
|
System.Generics.Collections,
|
||||||
|
Myc.Ast,
|
||||||
|
Myc.Ast.Nodes,
|
||||||
|
Myc.Ast.Visitor,
|
||||||
|
Myc.Ast.Types;
|
||||||
|
|
||||||
|
type
|
||||||
|
// Removes a specific node from the AST.
|
||||||
|
// - If the node is part of a list, it is removed from the list.
|
||||||
|
// - If the node is a mandatory child of a structure (e.g. If-Condition),
|
||||||
|
// it is replaced by a Nop node to maintain tree integrity.
|
||||||
|
TAstNodeRemover = class(TAstTransformer)
|
||||||
|
private
|
||||||
|
FTarget: IAstNode;
|
||||||
|
FRemoved: Boolean;
|
||||||
|
|
||||||
|
// Generic helper to filter nil items from lists during transformation
|
||||||
|
function FilterList<T: IAstNode>(const Source: INodeList<T>): TArray<T>;
|
||||||
|
|
||||||
|
// Helper to ensure a mandatory slot is never nil (replaces with Nop)
|
||||||
|
function EnsureNode(const Node: IAstNode; const ContextNode: IAstNode): IAstNode;
|
||||||
|
protected
|
||||||
|
function Accept(const Node: IAstNode): IAstNode; override;
|
||||||
|
|
||||||
|
// --- List Containers (Filter Logic) ---
|
||||||
|
function VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; override;
|
||||||
|
function VisitExpressionList(const Node: IExpressionList): IAstNode; override;
|
||||||
|
function VisitArgumentList(const Node: IArgumentList): IAstNode; override;
|
||||||
|
function VisitParameterList(const Node: IParameterList): IAstNode; override;
|
||||||
|
function VisitRecordFieldList(const Node: IRecordFieldList): IAstNode; override;
|
||||||
|
|
||||||
|
// --- Structural Nodes (Replace Logic) ---
|
||||||
|
function VisitIfExpression(const Node: IIfExpressionNode): IAstNode; override;
|
||||||
|
function VisitTernaryExpression(const Node: ITernaryExpressionNode): IAstNode; override;
|
||||||
|
function VisitAssignment(const Node: IAssignmentNode): IAstNode; override;
|
||||||
|
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; override;
|
||||||
|
function VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; override;
|
||||||
|
function VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; override;
|
||||||
|
function VisitIndexer(const Node: IIndexerNode): IAstNode; override;
|
||||||
|
function VisitMemberAccess(const Node: IMemberAccessNode): IAstNode; override;
|
||||||
|
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): IAstNode; override;
|
||||||
|
|
||||||
|
public
|
||||||
|
constructor Create(const ATarget: IAstNode);
|
||||||
|
|
||||||
|
// Tries to remove TargetNode from RootNode.
|
||||||
|
// Returns True if the node was found and removed/replaced.
|
||||||
|
// If successful, NewRootNode contains the modified AST.
|
||||||
|
// If not found, NewRootNode returns the original RootNode.
|
||||||
|
class function TryRemove(const RootNode, TargetNode: IAstNode; out NewRootNode: IAstNode): Boolean; static;
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
{ TAstNodeRemover }
|
||||||
|
|
||||||
|
constructor TAstNodeRemover.Create(const ATarget: IAstNode);
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FTarget := ATarget;
|
||||||
|
FRemoved := False;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TAstNodeRemover.TryRemove(const RootNode, TargetNode: IAstNode; out NewRootNode: IAstNode): Boolean;
|
||||||
|
var
|
||||||
|
Remover: TAstNodeRemover;
|
||||||
|
begin
|
||||||
|
// Edge case: Removing the root itself
|
||||||
|
if (RootNode = TargetNode) then
|
||||||
|
begin
|
||||||
|
// We replace the root with a Nop, as we cannot return 'nil' for a valid AST root expectation usually
|
||||||
|
NewRootNode := TAst.Nop(RootNode.Identity);
|
||||||
|
Result := True;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Remover := TAstNodeRemover.Create(TargetNode);
|
||||||
|
try
|
||||||
|
NewRootNode := Remover.Accept(RootNode);
|
||||||
|
Result := Remover.FRemoved;
|
||||||
|
|
||||||
|
if not Result then
|
||||||
|
begin
|
||||||
|
// If nothing was removed, return the original node to ensure reference stability
|
||||||
|
NewRootNode := RootNode;
|
||||||
|
end
|
||||||
|
else if (NewRootNode = nil) then
|
||||||
|
begin
|
||||||
|
// Safety fallback: if traversal somehow resulted in nil (should be caught by EnsureNode logic usually)
|
||||||
|
NewRootNode := TAst.Nop(RootNode.Identity);
|
||||||
|
end;
|
||||||
|
finally
|
||||||
|
Remover.Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstNodeRemover.Accept(const Node: IAstNode): IAstNode;
|
||||||
|
begin
|
||||||
|
// If we matched the target, flag it as removed and return nil.
|
||||||
|
// The parent visitor is responsible for handling the nil result (Filter or Replace).
|
||||||
|
if Node = FTarget then
|
||||||
|
begin
|
||||||
|
FRemoved := True;
|
||||||
|
Exit(nil);
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := inherited Accept(Node);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstNodeRemover.EnsureNode(const Node: IAstNode; const ContextNode: IAstNode): IAstNode;
|
||||||
|
begin
|
||||||
|
if Assigned(Node) then
|
||||||
|
Result := Node
|
||||||
|
else
|
||||||
|
// Mandatory child was deleted -> Replace with Nop using parent's identity location
|
||||||
|
Result := TAst.Nop(ContextNode.Identity);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstNodeRemover.FilterList<T>(const Source: INodeList<T>): TArray<T>;
|
||||||
|
var
|
||||||
|
i: Integer;
|
||||||
|
item: T;
|
||||||
|
transformed: IAstNode;
|
||||||
|
list: TList<T>;
|
||||||
|
begin
|
||||||
|
list := TList<T>.Create;
|
||||||
|
try
|
||||||
|
for i := 0 to Source.Count - 1 do
|
||||||
|
begin
|
||||||
|
item := Source[i];
|
||||||
|
transformed := Accept(item);
|
||||||
|
|
||||||
|
// If transformed is not nil, keep it.
|
||||||
|
// If it is nil, it was the target, so we skip (delete) it.
|
||||||
|
if Assigned(transformed) then
|
||||||
|
list.Add(T(transformed));
|
||||||
|
end;
|
||||||
|
Result := list.ToArray;
|
||||||
|
finally
|
||||||
|
list.Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// List Visitors (Filtering)
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function TAstNodeRemover.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode;
|
||||||
|
var
|
||||||
|
newExprs: TArray<IAstNode>;
|
||||||
|
newList: IExpressionList;
|
||||||
|
begin
|
||||||
|
newExprs := FilterList<IAstNode>(Node.Expressions);
|
||||||
|
|
||||||
|
// If counts differ, something was removed
|
||||||
|
if Length(newExprs) = Node.Expressions.Count then
|
||||||
|
begin
|
||||||
|
// If not marked as removed, we assume no change in this branch
|
||||||
|
if not FRemoved then
|
||||||
|
Exit(Node);
|
||||||
|
|
||||||
|
// Deep check for replacement
|
||||||
|
var changed := False;
|
||||||
|
for var i := 0 to High(newExprs) do
|
||||||
|
if newExprs[i] <> Node.Expressions[i] then
|
||||||
|
changed := True;
|
||||||
|
|
||||||
|
if not changed then
|
||||||
|
Exit(Node);
|
||||||
|
end;
|
||||||
|
|
||||||
|
newList := TExpressionList.Create(newExprs, Node.Expressions.Identity);
|
||||||
|
Result := TAst.Block(Node.Identity, newList, Node.StaticType);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstNodeRemover.VisitExpressionList(const Node: IExpressionList): IAstNode;
|
||||||
|
var
|
||||||
|
newExprs: TArray<IAstNode>;
|
||||||
|
begin
|
||||||
|
newExprs := FilterList<IAstNode>(Node);
|
||||||
|
|
||||||
|
if Length(newExprs) = Node.Count then
|
||||||
|
begin
|
||||||
|
var changed := False;
|
||||||
|
for var i := 0 to High(newExprs) do
|
||||||
|
if newExprs[i] <> Node[i] then
|
||||||
|
changed := True;
|
||||||
|
if not changed then
|
||||||
|
Exit(Node);
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := TExpressionList.Create(newExprs, Node.Identity);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstNodeRemover.VisitArgumentList(const Node: IArgumentList): IAstNode;
|
||||||
|
var
|
||||||
|
newArgs: TArray<IAstNode>;
|
||||||
|
begin
|
||||||
|
newArgs := FilterList<IAstNode>(Node);
|
||||||
|
|
||||||
|
if Length(newArgs) = Node.Count then
|
||||||
|
begin
|
||||||
|
var changed := False;
|
||||||
|
for var i := 0 to High(newArgs) do
|
||||||
|
if newArgs[i] <> Node[i] then
|
||||||
|
changed := True;
|
||||||
|
if not changed then
|
||||||
|
Exit(Node);
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := TArgumentList.Create(newArgs, Node.Identity);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstNodeRemover.VisitParameterList(const Node: IParameterList): IAstNode;
|
||||||
|
var
|
||||||
|
newParams: TArray<IIdentifierNode>;
|
||||||
|
begin
|
||||||
|
newParams := FilterList<IIdentifierNode>(Node);
|
||||||
|
|
||||||
|
if Length(newParams) = Node.Count then
|
||||||
|
begin
|
||||||
|
var changed := False;
|
||||||
|
for var i := 0 to High(newParams) do
|
||||||
|
if newParams[i] <> Node[i] then
|
||||||
|
changed := True;
|
||||||
|
if not changed then
|
||||||
|
Exit(Node);
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := TParameterList.Create(newParams, Node.Identity);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstNodeRemover.VisitRecordFieldList(const Node: IRecordFieldList): IAstNode;
|
||||||
|
var
|
||||||
|
newFields: TArray<IRecordFieldNode>;
|
||||||
|
begin
|
||||||
|
newFields := FilterList<IRecordFieldNode>(Node);
|
||||||
|
|
||||||
|
if Length(newFields) = Node.Count then
|
||||||
|
begin
|
||||||
|
var changed := False;
|
||||||
|
for var i := 0 to High(newFields) do
|
||||||
|
if newFields[i] <> Node[i] then
|
||||||
|
changed := True;
|
||||||
|
if not changed then
|
||||||
|
Exit(Node);
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := TRecordFieldList.Create(newFields, Node.Identity);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Structural Visitors (Replacement with Nop/Void)
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function TAstNodeRemover.VisitIfExpression(const Node: IIfExpressionNode): IAstNode;
|
||||||
|
var
|
||||||
|
newCond, newThen, newElse: IAstNode;
|
||||||
|
begin
|
||||||
|
newCond := Accept(Node.Condition);
|
||||||
|
newThen := Accept(Node.ThenBranch);
|
||||||
|
newElse := Accept(Node.ElseBranch);
|
||||||
|
|
||||||
|
// If condition or Then-branch is deleted, replace with Nop
|
||||||
|
newCond := EnsureNode(newCond, Node);
|
||||||
|
newThen := EnsureNode(newThen, Node);
|
||||||
|
|
||||||
|
// Else branch is optional
|
||||||
|
if (newCond = Node.Condition) and (newThen = Node.ThenBranch) and (newElse = Node.ElseBranch) then
|
||||||
|
Exit(Node);
|
||||||
|
|
||||||
|
Result := TAst.IfExpr(Node.Identity, newCond, newThen, newElse, Node.StaticType);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstNodeRemover.VisitTernaryExpression(const Node: ITernaryExpressionNode): IAstNode;
|
||||||
|
var
|
||||||
|
newCond, newThen, newElse: IAstNode;
|
||||||
|
begin
|
||||||
|
newCond := EnsureNode(Accept(Node.Condition), Node);
|
||||||
|
newThen := EnsureNode(Accept(Node.ThenBranch), Node);
|
||||||
|
newElse := EnsureNode(Accept(Node.ElseBranch), Node);
|
||||||
|
|
||||||
|
if (newCond = Node.Condition) and (newThen = Node.ThenBranch) and (newElse = Node.ElseBranch) then
|
||||||
|
Exit(Node);
|
||||||
|
|
||||||
|
Result := TAst.TernaryExpr(Node.Identity, newCond, newThen, newElse, Node.StaticType);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstNodeRemover.VisitAssignment(const Node: IAssignmentNode): IAstNode;
|
||||||
|
var
|
||||||
|
newTarget, newValue: IAstNode;
|
||||||
|
begin
|
||||||
|
newTarget := EnsureNode(Accept(Node.Target), Node);
|
||||||
|
newValue := EnsureNode(Accept(Node.Value), Node);
|
||||||
|
|
||||||
|
if (newTarget = Node.Target) and (newValue = Node.Value) then
|
||||||
|
Exit(Node);
|
||||||
|
|
||||||
|
Result := TAst.Assign(Node.Identity, newTarget, newValue, Node.StaticType);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstNodeRemover.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode;
|
||||||
|
var
|
||||||
|
newTarget, newInit: IAstNode;
|
||||||
|
begin
|
||||||
|
newTarget := EnsureNode(Accept(Node.Target), Node);
|
||||||
|
newInit := Accept(Node.Initializer); // Initializer is optional
|
||||||
|
|
||||||
|
if (newTarget = Node.Target) and (newInit = Node.Initializer) then
|
||||||
|
Exit(Node);
|
||||||
|
|
||||||
|
Result := TAst.VarDecl(Node.Identity, newTarget, newInit, Node.StaticType, Node.IsBoxed);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstNodeRemover.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode;
|
||||||
|
var
|
||||||
|
newParams: IAstNode;
|
||||||
|
newBody: IAstNode;
|
||||||
|
begin
|
||||||
|
newParams := Accept(Node.Parameters);
|
||||||
|
// If the parameter list itself was the target, we replace it with an empty list
|
||||||
|
if newParams = nil then
|
||||||
|
newParams := TParameterList.Create([], Node.Parameters.Identity);
|
||||||
|
|
||||||
|
newBody := EnsureNode(Accept(Node.Body), Node);
|
||||||
|
|
||||||
|
if (newParams = Node.Parameters) and (newBody = Node.Body) then
|
||||||
|
Exit(Node);
|
||||||
|
|
||||||
|
Result :=
|
||||||
|
TAst.LambdaExpr(
|
||||||
|
Node.Identity,
|
||||||
|
newParams.AsParameterList,
|
||||||
|
newBody,
|
||||||
|
Node.Layout,
|
||||||
|
Node.Descriptor,
|
||||||
|
Node.Upvalues,
|
||||||
|
Node.HasNestedLambdas,
|
||||||
|
Node.IsPure,
|
||||||
|
Node.StaticType
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstNodeRemover.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
|
||||||
|
var
|
||||||
|
newCallee: IAstNode;
|
||||||
|
newArgs: IAstNode;
|
||||||
|
begin
|
||||||
|
newCallee := EnsureNode(Accept(Node.Callee), Node);
|
||||||
|
|
||||||
|
newArgs := Accept(Node.Arguments);
|
||||||
|
if newArgs = nil then
|
||||||
|
newArgs := TArgumentList.Create([], Node.Arguments.Identity);
|
||||||
|
|
||||||
|
if (newCallee = Node.Callee) and (newArgs = Node.Arguments) then
|
||||||
|
Exit(Node);
|
||||||
|
|
||||||
|
Result :=
|
||||||
|
TAst.FunctionCall(
|
||||||
|
Node.Identity,
|
||||||
|
newCallee,
|
||||||
|
newArgs.AsArgumentList,
|
||||||
|
Node.StaticType,
|
||||||
|
Node.IsTailCall,
|
||||||
|
Node.StaticTarget,
|
||||||
|
Node.IsTargetPure
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstNodeRemover.VisitIndexer(const Node: IIndexerNode): IAstNode;
|
||||||
|
var
|
||||||
|
newBase, newIndex: IAstNode;
|
||||||
|
begin
|
||||||
|
newBase := EnsureNode(Accept(Node.Base), Node);
|
||||||
|
newIndex := EnsureNode(Accept(Node.Index), Node);
|
||||||
|
|
||||||
|
if (newBase = Node.Base) and (newIndex = Node.Index) then
|
||||||
|
Exit(Node);
|
||||||
|
|
||||||
|
Result := TAst.Indexer(Node.Identity, newBase, newIndex, Node.StaticType);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstNodeRemover.VisitMemberAccess(const Node: IMemberAccessNode): IAstNode;
|
||||||
|
var
|
||||||
|
newBase: IAstNode;
|
||||||
|
newMember: IAstNode;
|
||||||
|
begin
|
||||||
|
newBase := EnsureNode(Accept(Node.Base), Node);
|
||||||
|
|
||||||
|
newMember := Accept(Node.Member);
|
||||||
|
if newMember = nil then
|
||||||
|
begin
|
||||||
|
// If member name is deleted, the access is invalid. Replace whole node with Nop.
|
||||||
|
Exit(TAst.Nop(Node.Identity));
|
||||||
|
end;
|
||||||
|
|
||||||
|
if (newBase = Node.Base) and (newMember = Node.Member) then
|
||||||
|
Exit(Node);
|
||||||
|
|
||||||
|
Result := TAst.MemberAccess(Node.Identity, newBase, newMember.AsKeyword, Node.StaticType);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstNodeRemover.VisitAddSeriesItem(const Node: IAddSeriesItemNode): IAstNode;
|
||||||
|
var
|
||||||
|
newSeries: IAstNode;
|
||||||
|
newValue: IAstNode;
|
||||||
|
newLookback: IAstNode;
|
||||||
|
begin
|
||||||
|
newSeries := Accept(Node.Series);
|
||||||
|
if newSeries = nil then
|
||||||
|
Exit(TAst.Nop(Node.Identity)); // Cannot exist without series identifier
|
||||||
|
|
||||||
|
newValue := EnsureNode(Accept(Node.Value), Node);
|
||||||
|
newLookback := Accept(Node.Lookback); // Optional
|
||||||
|
|
||||||
|
if (newSeries = Node.Series) and (newValue = Node.Value) and (newLookback = Node.Lookback) then
|
||||||
|
Exit(Node);
|
||||||
|
|
||||||
|
Result := TAst.AddSeriesItem(Node.Identity, newSeries.AsIdentifier, newValue, newLookback, Node.StaticType);
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
@@ -20,6 +20,7 @@ uses
|
|||||||
Myc.Ast.Visitor,
|
Myc.Ast.Visitor,
|
||||||
Myc.Ast.Scope,
|
Myc.Ast.Scope,
|
||||||
Myc.Ast.Environment,
|
Myc.Ast.Environment,
|
||||||
|
Myc.Ast.Refactoring.Remove,
|
||||||
Myc.Fmx.AstEditor.Node,
|
Myc.Fmx.AstEditor.Node,
|
||||||
Myc.Fmx.AstEditor.Layout,
|
Myc.Fmx.AstEditor.Layout,
|
||||||
Myc.Fmx.AstEditor.Workspace;
|
Myc.Fmx.AstEditor.Workspace;
|
||||||
@@ -71,8 +72,6 @@ type
|
|||||||
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);
|
||||||
|
|
||||||
// NEU: Handler für Klicks auf die Nodes selbst
|
|
||||||
procedure HandleNodeMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
procedure HandleNodeMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
||||||
|
|
||||||
procedure CommitSnapshot;
|
procedure CommitSnapshot;
|
||||||
@@ -99,7 +98,9 @@ type
|
|||||||
procedure Init(const AEnv: TAstEnvironment);
|
procedure Init(const AEnv: TAstEnvironment);
|
||||||
|
|
||||||
procedure ValidateAndShow;
|
procedure ValidateAndShow;
|
||||||
procedure SetRoot(const AstNode: IAstNode);
|
|
||||||
|
// AClearHistory=False ensures stacks are kept (e.g. after internal edits)
|
||||||
|
procedure SetRoot(const AstNode: IAstNode; AClearHistory: Boolean = True);
|
||||||
|
|
||||||
procedure Undo;
|
procedure Undo;
|
||||||
procedure Redo;
|
procedure Redo;
|
||||||
@@ -201,32 +202,21 @@ end;
|
|||||||
|
|
||||||
procedure TAstEditor.HandleWorkspaceMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
procedure TAstEditor.HandleWorkspaceMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
||||||
begin
|
begin
|
||||||
// Wenn wir in den leeren Bereich klicken:
|
|
||||||
// 1. Tastaturfokus sicherstellen (wichtig!)
|
|
||||||
if FWorkspace.CanFocus then
|
if FWorkspace.CanFocus then
|
||||||
FWorkspace.SetFocus;
|
FWorkspace.SetFocus;
|
||||||
|
|
||||||
// Optional: Fokus löschen, wenn man ins Leere klickt?
|
|
||||||
// SetFocusedNode(nil); // Geschmackssache. Viele Editoren behalten den Fokus.
|
|
||||||
|
|
||||||
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);
|
procedure TAstEditor.HandleNodeMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
||||||
begin
|
begin
|
||||||
// 1. Fokus für Navigation sicherstellen (Keyboard Events gehen an Workspace)
|
|
||||||
if FWorkspace.CanFocus then
|
if FWorkspace.CanFocus then
|
||||||
FWorkspace.SetFocus;
|
FWorkspace.SetFocus;
|
||||||
|
|
||||||
// 2. Visuellen/Logischen Node-Fokus setzen
|
|
||||||
if (Button = TMouseButton.mbLeft) and (Sender is TAstViewNode) then
|
if (Button = TMouseButton.mbLeft) and (Sender is TAstViewNode) then
|
||||||
begin
|
begin
|
||||||
SetFocusedNode(TAstViewNode(Sender));
|
SetFocusedNode(TAstViewNode(Sender));
|
||||||
|
|
||||||
// Stoppt die Event-Propagation nach oben zum Workspace, damit wir nicht
|
|
||||||
// sofort wieder deselektieren (falls wir das im WorkspaceMouseDown implementiert hätten).
|
|
||||||
// TControl.MouseDown hat kein 'Handled', aber wir sind hier im Event-Handler.
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -252,7 +242,7 @@ begin
|
|||||||
FRedoStack.Push(FCurrentAst);
|
FRedoStack.Push(FCurrentAst);
|
||||||
|
|
||||||
prevAst := FUndoStack.Pop;
|
prevAst := FUndoStack.Pop;
|
||||||
SetRoot(prevAst);
|
SetRoot(prevAst); // Default Clear=True, but guarded by FIsUndoing
|
||||||
finally
|
finally
|
||||||
FIsUndoing := False;
|
FIsUndoing := False;
|
||||||
end;
|
end;
|
||||||
@@ -332,17 +322,62 @@ begin
|
|||||||
CommitSnapshot;
|
CommitSnapshot;
|
||||||
Reorderable.MoveChild(SourceIdx, TargetIdx);
|
Reorderable.MoveChild(SourceIdx, TargetIdx);
|
||||||
NewAst := FRootViewNode.CreateAst;
|
NewAst := FRootViewNode.CreateAst;
|
||||||
SetRoot(NewAst);
|
|
||||||
|
|
||||||
// Optional: Fokus auf den verschobenen Node wiederherstellen
|
// IMPORTANT: Pass False to preserve the undo stack we just updated
|
||||||
// Da der Node neu erzeugt wurde, müssten wir ihn über die Identity suchen.
|
SetRoot(NewAst, False);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TAstEditor.HandleKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState);
|
procedure TAstEditor.HandleKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState);
|
||||||
|
var
|
||||||
|
NewRoot: IAstNode;
|
||||||
begin
|
begin
|
||||||
|
// Undo / Redo Handling
|
||||||
|
if (ssCtrl in Shift) then
|
||||||
|
begin
|
||||||
|
if (Key = vkZ) then
|
||||||
|
begin
|
||||||
|
if (ssShift in Shift) then
|
||||||
|
begin
|
||||||
|
if CanRedo then
|
||||||
|
Redo;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
if CanUndo then
|
||||||
|
Undo;
|
||||||
|
end;
|
||||||
|
Key := 0;
|
||||||
|
Exit;
|
||||||
|
end
|
||||||
|
else if (Key = vkY) then
|
||||||
|
begin
|
||||||
|
if CanRedo then
|
||||||
|
Redo;
|
||||||
|
Key := 0;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Delete Handling
|
||||||
|
if (Key = vkDelete) then
|
||||||
|
begin
|
||||||
|
if Assigned(FFocusedNode) and Assigned(FFocusedNode.Node) and Assigned(FCurrentAst) then
|
||||||
|
begin
|
||||||
|
CommitSnapshot;
|
||||||
|
if TAstNodeRemover.TryRemove(FCurrentAst, FFocusedNode.Node, NewRoot) then
|
||||||
|
begin
|
||||||
|
// IMPORTANT: Pass False to preserve the undo stack we just updated
|
||||||
|
SetRoot(NewRoot, False);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
Key := 0;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Navigation
|
||||||
if Key in [vkLeft, vkRight, vkUp, vkDown, vkTab, vkEscape] then
|
if Key in [vkLeft, vkRight, vkUp, vkDown, vkTab, vkEscape] then
|
||||||
begin
|
begin
|
||||||
Navigate(Key, Shift);
|
Navigate(Key, Shift);
|
||||||
@@ -350,13 +385,14 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TAstEditor.SetRoot(const AstNode: IAstNode);
|
procedure TAstEditor.SetRoot(const AstNode: IAstNode; AClearHistory: Boolean);
|
||||||
var
|
var
|
||||||
Visualizer: IAstVisualizer;
|
Visualizer: IAstVisualizer;
|
||||||
begin
|
begin
|
||||||
FCurrentAst := AstNode;
|
FCurrentAst := AstNode;
|
||||||
|
|
||||||
if not FIsUndoing then
|
// Only clear if explicitly requested AND not currently performing an undo/redo
|
||||||
|
if AClearHistory and (not FIsUndoing) then
|
||||||
begin
|
begin
|
||||||
FUndoStack.Clear;
|
FUndoStack.Clear;
|
||||||
FRedoStack.Clear;
|
FRedoStack.Clear;
|
||||||
@@ -401,8 +437,6 @@ begin
|
|||||||
if child is TAstViewNode then
|
if child is TAstViewNode then
|
||||||
begin
|
begin
|
||||||
viewNode := TAstViewNode(child);
|
viewNode := TAstViewNode(child);
|
||||||
|
|
||||||
// HIER: Das Click-Event wiring
|
|
||||||
viewNode.OnMouseDown := HandleNodeMouseDown;
|
viewNode.OnMouseDown := HandleNodeMouseDown;
|
||||||
|
|
||||||
if Assigned(viewNode.Node) and Assigned(viewNode.Node.Identity) then
|
if Assigned(viewNode.Node) and Assigned(viewNode.Node.Identity) then
|
||||||
@@ -529,7 +563,7 @@ end;
|
|||||||
|
|
||||||
procedure TAstEditor.EnsureVisible(Node: TAstViewNode);
|
procedure TAstEditor.EnsureVisible(Node: TAstViewNode);
|
||||||
const
|
const
|
||||||
cMargin = 40; // Abstand zum Rand in Pixeln
|
cMargin = 40;
|
||||||
var
|
var
|
||||||
NodeRect, ViewRect: TRectF;
|
NodeRect, ViewRect: TRectF;
|
||||||
Delta: TPointF;
|
Delta: TPointF;
|
||||||
@@ -537,50 +571,26 @@ begin
|
|||||||
if (Node = nil) or (FWorkspace = nil) then
|
if (Node = nil) or (FWorkspace = nil) then
|
||||||
Exit;
|
Exit;
|
||||||
|
|
||||||
// 1. Hole absolute Koordinaten
|
|
||||||
NodeRect := Node.AbsoluteRect;
|
NodeRect := Node.AbsoluteRect;
|
||||||
ViewRect := FWorkspace.AbsoluteRect;
|
ViewRect := FWorkspace.AbsoluteRect;
|
||||||
|
|
||||||
// 2. Definiere den "sicheren" Bereich (Viewport minus Rand)
|
|
||||||
ViewRect.Inflate(-cMargin, -cMargin);
|
ViewRect.Inflate(-cMargin, -cMargin);
|
||||||
|
|
||||||
Delta := TPointF.Zero;
|
Delta := TPointF.Zero;
|
||||||
|
|
||||||
// 3. Horizontal prüfen
|
|
||||||
if NodeRect.Width > ViewRect.Width then
|
if NodeRect.Width > ViewRect.Width then
|
||||||
begin
|
Delta.X := ViewRect.CenterPoint.X - NodeRect.CenterPoint.X
|
||||||
// Node ist breiter als der Screen -> Zentrieren
|
|
||||||
Delta.X := ViewRect.CenterPoint.X - NodeRect.CenterPoint.X;
|
|
||||||
end
|
|
||||||
else if NodeRect.Left < ViewRect.Left then
|
else if NodeRect.Left < ViewRect.Left then
|
||||||
begin
|
Delta.X := ViewRect.Left - NodeRect.Left
|
||||||
// Node ragt links raus -> nach rechts schieben
|
|
||||||
Delta.X := ViewRect.Left - NodeRect.Left;
|
|
||||||
end
|
|
||||||
else if NodeRect.Right > ViewRect.Right then
|
else if NodeRect.Right > ViewRect.Right then
|
||||||
begin
|
|
||||||
// Node ragt rechts raus -> nach links schieben
|
|
||||||
Delta.X := ViewRect.Right - NodeRect.Right;
|
Delta.X := ViewRect.Right - NodeRect.Right;
|
||||||
end;
|
|
||||||
|
|
||||||
// 4. Vertikal prüfen
|
|
||||||
if NodeRect.Height > ViewRect.Height then
|
if NodeRect.Height > ViewRect.Height then
|
||||||
begin
|
Delta.Y := ViewRect.CenterPoint.Y - NodeRect.CenterPoint.Y
|
||||||
// Node ist höher als der Screen -> Zentrieren
|
|
||||||
Delta.Y := ViewRect.CenterPoint.Y - NodeRect.CenterPoint.Y;
|
|
||||||
end
|
|
||||||
else if NodeRect.Top < ViewRect.Top then
|
else if NodeRect.Top < ViewRect.Top then
|
||||||
begin
|
Delta.Y := ViewRect.Top - NodeRect.Top
|
||||||
// Node ragt oben raus -> nach unten schieben
|
|
||||||
Delta.Y := ViewRect.Top - NodeRect.Top;
|
|
||||||
end
|
|
||||||
else if NodeRect.Bottom > ViewRect.Bottom then
|
else if NodeRect.Bottom > ViewRect.Bottom then
|
||||||
begin
|
|
||||||
// Node ragt unten raus -> nach oben schieben
|
|
||||||
Delta.Y := ViewRect.Bottom - NodeRect.Bottom;
|
Delta.Y := ViewRect.Bottom - NodeRect.Bottom;
|
||||||
end;
|
|
||||||
|
|
||||||
// 5. Nur anwenden, wenn eine Verschiebung nötig ist
|
|
||||||
if not (SameValue(Delta.X, 0, TEpsilon.Position) and SameValue(Delta.Y, 0, TEpsilon.Position)) then
|
if not (SameValue(Delta.X, 0, TEpsilon.Position) and SameValue(Delta.Y, 0, TEpsilon.Position)) then
|
||||||
begin
|
begin
|
||||||
FWorkspace.ContentOffset := FWorkspace.ContentOffset + Delta;
|
FWorkspace.ContentOffset := FWorkspace.ContentOffset + Delta;
|
||||||
@@ -674,8 +684,6 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Helper um den nächsten ViewNode in einer Liste von Controls zu finden
|
|
||||||
|
|
||||||
function FindNextSiblingInList(Parent: TControl; AfterControl: TControl): TAstViewNode;
|
function FindNextSiblingInList(Parent: TControl; AfterControl: TControl): TAstViewNode;
|
||||||
var
|
var
|
||||||
i, StartIndex: Integer;
|
i, StartIndex: Integer;
|
||||||
@@ -740,8 +748,6 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// --- Vertical Axis Navigation (Skip Row) ---
|
|
||||||
|
|
||||||
function TAstEditor.GetNextVerticalNeighbor(StartNode: TAstViewNode): TAstViewNode;
|
function TAstEditor.GetNextVerticalNeighbor(StartNode: TAstViewNode): TAstViewNode;
|
||||||
var
|
var
|
||||||
Current, Parent: TControl;
|
Current, Parent: TControl;
|
||||||
@@ -755,7 +761,6 @@ begin
|
|||||||
if Parent = nil then
|
if Parent = nil then
|
||||||
Break;
|
Break;
|
||||||
|
|
||||||
// Wenn der Parent vertikal ist, suchen wir hier den Nachbarn.
|
|
||||||
if (Parent is TAutoFitControl) and (TAutoFitControl(Parent).Orientation = TLayoutOrientation.loVertical) then
|
if (Parent is TAutoFitControl) and (TAutoFitControl(Parent).Orientation = TLayoutOrientation.loVertical) then
|
||||||
begin
|
begin
|
||||||
Result := FindNextSiblingInList(Parent, Current);
|
Result := FindNextSiblingInList(Parent, Current);
|
||||||
@@ -803,12 +808,10 @@ var
|
|||||||
i, StartIndex: Integer;
|
i, StartIndex: Integer;
|
||||||
Candidate: TControl;
|
Candidate: TControl;
|
||||||
begin
|
begin
|
||||||
// 1. Try Child (Drill Down)
|
|
||||||
Result := GetFirstChild(StartNode);
|
Result := GetFirstChild(StartNode);
|
||||||
if Result <> nil then
|
if Result <> nil then
|
||||||
Exit;
|
Exit;
|
||||||
|
|
||||||
// 2. Walk up visually and check for next siblings at every layout level
|
|
||||||
CurrentControl := StartNode;
|
CurrentControl := StartNode;
|
||||||
while (CurrentControl <> nil) and (CurrentControl <> FWorkspace.World) do
|
while (CurrentControl <> nil) and (CurrentControl <> FWorkspace.World) do
|
||||||
begin
|
begin
|
||||||
@@ -816,7 +819,6 @@ begin
|
|||||||
if ParentControl = nil then
|
if ParentControl = nil then
|
||||||
Break;
|
Break;
|
||||||
|
|
||||||
// Find index of current control
|
|
||||||
StartIndex := -1;
|
StartIndex := -1;
|
||||||
for i := 0 to ParentControl.ChildrenCount - 1 do
|
for i := 0 to ParentControl.ChildrenCount - 1 do
|
||||||
if ParentControl.Children[i] = CurrentControl then
|
if ParentControl.Children[i] = CurrentControl then
|
||||||
@@ -825,7 +827,6 @@ begin
|
|||||||
Break;
|
Break;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Scan forward from next index
|
|
||||||
if StartIndex >= 0 then
|
if StartIndex >= 0 then
|
||||||
begin
|
begin
|
||||||
for i := StartIndex + 1 to ParentControl.ChildrenCount - 1 do
|
for i := StartIndex + 1 to ParentControl.ChildrenCount - 1 do
|
||||||
@@ -835,7 +836,6 @@ begin
|
|||||||
Candidate := TControl(ParentControl.Children[i]);
|
Candidate := TControl(ParentControl.Children[i]);
|
||||||
if Candidate.Visible then
|
if Candidate.Visible then
|
||||||
begin
|
begin
|
||||||
// Check if this candidate IS a node or CONTAINS a node
|
|
||||||
Result := FindFirstViewNode(Candidate);
|
Result := FindFirstViewNode(Candidate);
|
||||||
if Result <> nil then
|
if Result <> nil then
|
||||||
Exit;
|
Exit;
|
||||||
@@ -844,7 +844,6 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Move up one physical level (e.g. from ParameterList to TitleContainer)
|
|
||||||
CurrentControl := ParentControl;
|
CurrentControl := ParentControl;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -857,12 +856,6 @@ var
|
|||||||
i, StartIndex: Integer;
|
i, StartIndex: Integer;
|
||||||
Candidate: TControl;
|
Candidate: TControl;
|
||||||
begin
|
begin
|
||||||
// No simple "Drill Down" here, because Prev means "Left of me".
|
|
||||||
// If I have children, "Left of me" is OUTSIDE of me (Parent or Sibling).
|
|
||||||
// Wait: Standard Tab behavior:
|
|
||||||
// Tab: Current -> Child -> Next Sibling
|
|
||||||
// Shift-Tab: Current -> Prev Sibling (Deepest Child) -> Parent
|
|
||||||
|
|
||||||
CurrentControl := StartNode;
|
CurrentControl := StartNode;
|
||||||
while (CurrentControl <> nil) and (CurrentControl <> FWorkspace.World) do
|
while (CurrentControl <> nil) and (CurrentControl <> FWorkspace.World) do
|
||||||
begin
|
begin
|
||||||
@@ -870,7 +863,6 @@ begin
|
|||||||
if ParentControl = nil then
|
if ParentControl = nil then
|
||||||
Break;
|
Break;
|
||||||
|
|
||||||
// Find index
|
|
||||||
StartIndex := -1;
|
StartIndex := -1;
|
||||||
for i := 0 to ParentControl.ChildrenCount - 1 do
|
for i := 0 to ParentControl.ChildrenCount - 1 do
|
||||||
if ParentControl.Children[i] = CurrentControl then
|
if ParentControl.Children[i] = CurrentControl then
|
||||||
@@ -879,7 +871,6 @@ begin
|
|||||||
Break;
|
Break;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Scan backward
|
|
||||||
if StartIndex >= 0 then
|
if StartIndex >= 0 then
|
||||||
begin
|
begin
|
||||||
for i := StartIndex - 1 downto 0 do
|
for i := StartIndex - 1 downto 0 do
|
||||||
@@ -889,7 +880,6 @@ begin
|
|||||||
Candidate := TControl(ParentControl.Children[i]);
|
Candidate := TControl(ParentControl.Children[i]);
|
||||||
if Candidate.Visible then
|
if Candidate.Visible then
|
||||||
begin
|
begin
|
||||||
// Find the deepest last node inside this sibling
|
|
||||||
Result := FindLastViewNode(Candidate);
|
Result := FindLastViewNode(Candidate);
|
||||||
if Result <> nil then
|
if Result <> nil then
|
||||||
Exit;
|
Exit;
|
||||||
@@ -898,19 +888,15 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// If no sibling found to the left, the Parent is the previous logical node
|
|
||||||
if ParentControl is TAstViewNode then
|
if ParentControl is TAstViewNode then
|
||||||
Exit(TAstViewNode(ParentControl));
|
Exit(TAstViewNode(ParentControl));
|
||||||
|
|
||||||
// If Parent is just a Layout Container, keep walking up
|
|
||||||
CurrentControl := ParentControl;
|
CurrentControl := ParentControl;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := nil;
|
Result := nil;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// --- Main Navigate Method ---
|
|
||||||
|
|
||||||
procedure TAstEditor.Navigate(Key: Word; Shift: TShiftState);
|
procedure TAstEditor.Navigate(Key: Word; Shift: TShiftState);
|
||||||
var
|
var
|
||||||
Current: TAstViewNode;
|
Current: TAstViewNode;
|
||||||
@@ -925,7 +911,6 @@ begin
|
|||||||
Current := FFocusedNode;
|
Current := FFocusedNode;
|
||||||
Target := nil;
|
Target := nil;
|
||||||
|
|
||||||
// 1. Linear Navigation (Tab) - Bleibt wie gehabt
|
|
||||||
if Key = vkTab then
|
if Key = vkTab then
|
||||||
begin
|
begin
|
||||||
if ssShift in Shift then
|
if ssShift in Shift then
|
||||||
@@ -938,7 +923,6 @@ begin
|
|||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// 2. Escape - Bleibt "Notausgang" zum Parent
|
|
||||||
if Key = vkEscape then
|
if Key = vkEscape then
|
||||||
begin
|
begin
|
||||||
Target := GetParentNode(Current);
|
Target := GetParentNode(Current);
|
||||||
@@ -947,27 +931,11 @@ begin
|
|||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// 3. Axis Analysis
|
|
||||||
case Key of
|
case Key of
|
||||||
vkLeft:
|
vkLeft: Target := GetPrevLinearNode(Current);
|
||||||
begin
|
vkRight: Target := GetNextLinearNode(Current);
|
||||||
Target := GetPrevLinearNode(Current);
|
vkUp: Target := GetPrevVerticalNeighbor(Current);
|
||||||
end;
|
vkDown: Target := GetNextVerticalNeighbor(Current);
|
||||||
|
|
||||||
vkRight:
|
|
||||||
begin
|
|
||||||
Target := GetNextLinearNode(Current);
|
|
||||||
end;
|
|
||||||
|
|
||||||
vkUp:
|
|
||||||
begin
|
|
||||||
Target := GetPrevVerticalNeighbor(Current)
|
|
||||||
end;
|
|
||||||
|
|
||||||
vkDown:
|
|
||||||
begin
|
|
||||||
Target := GetNextVerticalNeighbor(Current)
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if Target <> nil then
|
if Target <> nil then
|
||||||
|
|||||||
Reference in New Issue
Block a user