Files
MycLib/Src/AST/Myc.Fmx.AstEditor.Handlers.pas
T
2025-12-06 20:36:33 +01:00

143 lines
4.0 KiB
ObjectPascal

unit Myc.Fmx.AstEditor.Handlers;
interface
uses
System.SysUtils,
System.Generics.Collections,
System.UITypes,
Myc.Ast.Nodes,
Myc.Ast.Identities,
Myc.Fmx.AstEditor.Render,
Myc.Fmx.AstEditor.Node;
type
// --- Abstract List Base ---
// Implements IReorderable to support drag & drop reordering within the virtual tree
TNodeListHandler<T: IAstNode> = class(TBaseNodeHandler<INodeList<T>>, IReorderable)
protected
FChildNodes: TList<TAstViewNode>;
function GetOrientation: TLayoutOrientation; virtual; abstract;
function GetAlignment: TLayoutAlignment; virtual;
public
constructor Create(const ANode: INodeList<T>);
destructor Destroy; override;
procedure BuildUI(OwnerNode: TAstViewNode); override;
function ReconstructAst(OwnerNode: TAstViewNode): IAstNode; override; abstract;
// IReorderable Implementation
function CanDrag(Child: TVisualNode): Boolean;
procedure MoveChild(SourceIndex, TargetIndex: Integer);
end;
implementation
{ TNodeListHandler<T> }
constructor TNodeListHandler<T>.Create(const ANode: INodeList<T>);
begin
inherited Create(ANode);
FChildNodes := TList<TAstViewNode>.Create;
end;
destructor TNodeListHandler<T>.Destroy;
begin
FChildNodes.Free;
inherited;
end;
function TNodeListHandler<T>.GetAlignment: TLayoutAlignment;
begin
Result := TLayoutAlignment.laCenter;
end;
procedure TNodeListHandler<T>.BuildUI(OwnerNode: TAstViewNode);
var
visu: IAstVisualizer;
i: Integer;
childView: TAstViewNode;
openTok, closeTok, sep: string;
listId: IListIdentity;
begin
if (FNode.Identity <> nil) and (FNode.Identity.Kind = ikList) then
begin
listId := FNode.Identity.AsList;
openTok := listId.OpenToken;
closeTok := listId.CloseToken;
sep := listId.Separator;
end
else
begin
openTok := '';
closeTok := '';
sep := ' ';
end;
visu := OwnerNode.Visualizer.Clone(OwnerNode, OwnerNode.Visualizer.ExprDepth);
// No BeginUpdate/EndUpdate needed for TVisualNode, just property setting
OwnerNode.Frameless := True;
OwnerNode.Orientation := GetOrientation;
OwnerNode.Alignment := GetAlignment;
if openTok <> '' then
OwnerNode.AddLabel(OwnerNode, openTok);
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
var lbl := OwnerNode.AddLabel(OwnerNode, 'ø');
lbl.Opacity := 0.5;
end;
end;
end;
// IReorderable Implementation
function TNodeListHandler<T>.CanDrag(Child: TVisualNode): Boolean;
begin
// Only allow dragging if the child is one of our managed ViewNodes
if Child is TAstViewNode then
Result := FChildNodes.Contains(TAstViewNode(Child))
else
Result := False;
end;
procedure TNodeListHandler<T>.MoveChild(SourceIndex, TargetIndex: Integer);
var
item: TAstViewNode;
begin
if (SourceIndex < 0) or (SourceIndex >= FChildNodes.Count) then
Exit;
if (TargetIndex < 0) or (TargetIndex > FChildNodes.Count) then
Exit;
// Adjust target index if moving downwards (since removing source shifts indices)
if SourceIndex < TargetIndex then
Dec(TargetIndex);
if SourceIndex = TargetIndex then
Exit;
item := FChildNodes[SourceIndex];
FChildNodes.Delete(SourceIndex);
FChildNodes.Insert(TargetIndex, item);
// The visual order update happens via the controller's compilation cycle
end;
end.