Focus & cursor movement in UI
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -29,9 +29,9 @@ uses
|
|||||||
Myc.Fmx.AstEditor.Handlers in '..\Src\AST\Myc.Fmx.AstEditor.Handlers.pas',
|
Myc.Fmx.AstEditor.Handlers in '..\Src\AST\Myc.Fmx.AstEditor.Handlers.pas',
|
||||||
Myc.Fmx.AstEditor.Visualizer in '..\Src\AST\Myc.Fmx.AstEditor.Visualizer.pas',
|
Myc.Fmx.AstEditor.Visualizer in '..\Src\AST\Myc.Fmx.AstEditor.Visualizer.pas',
|
||||||
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.Controller in '..\Src\AST\Myc.Fmx.AstEditor.Controller.pas',
|
Myc.Fmx.AstEditor.Layout in '..\Src\AST\Myc.Fmx.AstEditor.Layout.pas',
|
||||||
Myc.Fmx.AstEditor in '..\Src\AST\Myc.Fmx.AstEditor.pas',
|
Myc.Fmx.AstEditor.Node in '..\Src\AST\Myc.Fmx.AstEditor.Node.pas',
|
||||||
Myc.Fmx.AstEditor.Layout in '..\Src\AST\Myc.Fmx.AstEditor.Layout.pas';
|
Myc.Fmx.AstEditor in '..\Src\AST\Myc.Fmx.AstEditor.pas';
|
||||||
|
|
||||||
{$R *.res}
|
{$R *.res}
|
||||||
|
|
||||||
|
|||||||
@@ -159,9 +159,9 @@
|
|||||||
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Handlers.pas"/>
|
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Handlers.pas"/>
|
||||||
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Visualizer.pas"/>
|
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Visualizer.pas"/>
|
||||||
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Workspace.pas"/>
|
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Workspace.pas"/>
|
||||||
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Controller.pas"/>
|
|
||||||
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.pas"/>
|
|
||||||
<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.pas"/>
|
||||||
<BuildConfiguration Include="Base">
|
<BuildConfiguration Include="Base">
|
||||||
<Key>Base</Key>
|
<Key>Base</Key>
|
||||||
</BuildConfiguration>
|
</BuildConfiguration>
|
||||||
|
|||||||
+30
-40
@@ -44,9 +44,7 @@ uses
|
|||||||
Myc.Ast.RTL,
|
Myc.Ast.RTL,
|
||||||
Myc.Ast.Compiler.Macros,
|
Myc.Ast.Compiler.Macros,
|
||||||
// Editor Units
|
// Editor Units
|
||||||
Myc.Fmx.AstEditor,
|
Myc.Fmx.AstEditor; // Die neue Komponente (ersetzt Controller und Workspace-Access)
|
||||||
Myc.Fmx.AstEditor.Workspace,
|
|
||||||
Myc.Fmx.AstEditor.Controller; // Controller integration
|
|
||||||
|
|
||||||
type
|
type
|
||||||
// A test record
|
// A test record
|
||||||
@@ -93,7 +91,6 @@ type
|
|||||||
procedure ClearButtonClick(Sender: TObject);
|
procedure ClearButtonClick(Sender: TObject);
|
||||||
procedure CompilerStageBoxChange(Sender: TObject);
|
procedure CompilerStageBoxChange(Sender: TObject);
|
||||||
procedure FormCreate(Sender: TObject);
|
procedure FormCreate(Sender: TObject);
|
||||||
procedure FormDestroy(Sender: TObject);
|
|
||||||
procedure CreateTriggerExampleButtonClick(Sender: TObject);
|
procedure CreateTriggerExampleButtonClick(Sender: TObject);
|
||||||
procedure DebugBoxChange(Sender: TObject);
|
procedure DebugBoxChange(Sender: TObject);
|
||||||
procedure DoTrigger2ButtonClick(Sender: TObject);
|
procedure DoTrigger2ButtonClick(Sender: TObject);
|
||||||
@@ -119,17 +116,16 @@ type
|
|||||||
FCurrUnboundAst: IAstNode;
|
FCurrUnboundAst: IAstNode;
|
||||||
FCurrExec: TCompiledFunction;
|
FCurrExec: TCompiledFunction;
|
||||||
FEnvironment: TAstEnvironment;
|
FEnvironment: TAstEnvironment;
|
||||||
FWorkspace: TWorkspace; // Changed to TWorkspace
|
FAstEditor: TAstEditor; // Die Komponente (Facade)
|
||||||
FController: TAstEditorController; // The UI Controller
|
|
||||||
FScriptUpdate: Boolean;
|
FScriptUpdate: Boolean;
|
||||||
FTriggerTest: TAstEnvironment;
|
FTriggerTest: TAstEnvironment;
|
||||||
|
|
||||||
procedure WorkspaceMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
procedure AstEditorMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
||||||
|
|
||||||
// Helper function to encapsulate the Compile -> Evaluate pattern
|
// Helper function to encapsulate the Compile -> Evaluate pattern
|
||||||
function ExecuteAst(const ANode: IAstNode): TDataValue;
|
function ExecuteAst(const ANode: IAstNode): TDataValue;
|
||||||
procedure UpdateScript;
|
procedure UpdateScript;
|
||||||
procedure ShowVizualization(X, Y: Single);
|
procedure ShowVizualization;
|
||||||
procedure PrintScript(const Node: IAstNode);
|
procedure PrintScript(const Node: IAstNode);
|
||||||
public
|
public
|
||||||
{ Public declarations }
|
{ Public declarations }
|
||||||
@@ -158,19 +154,20 @@ begin
|
|||||||
// 1. Initialize Environment
|
// 1. Initialize Environment
|
||||||
FEnvironment := TAstEnvironment.Construct(TAst.CreateScope(nil));
|
FEnvironment := TAstEnvironment.Construct(TAst.CreateScope(nil));
|
||||||
|
|
||||||
// 2. Initialize Workspace
|
// 2. Initialize AST Editor Component
|
||||||
// TWorkspace is now a simple viewport control
|
// This replaces the manual Workspace + Controller setup.
|
||||||
FWorkspace := TWorkspace.Create(Panel2);
|
// The Editor encapsulates logic for Navigation, Zoom, Drag&Drop, Undo/Redo.
|
||||||
FWorkspace.Parent := Panel2;
|
FAstEditor := TAstEditor.Create(Self);
|
||||||
FWorkspace.Align := TAlignLayout.Client;
|
FAstEditor.Parent := Panel2;
|
||||||
FWorkspace.ClipChildren := true;
|
FAstEditor.Align := TAlignLayout.Client;
|
||||||
FWorkspace.OnMouseDown := WorkspaceMouseDown;
|
|
||||||
|
|
||||||
// 3. Initialize Controller (connects Env and Workspace)
|
// Wire up events exposed by the component
|
||||||
// The Controller will manage the nodes inside FWorkspace.World
|
FAstEditor.OnMouseDown := AstEditorMouseDown;
|
||||||
FController := TAstEditorController.Create(FEnvironment, FWorkspace);
|
|
||||||
|
|
||||||
// 4. Register the SMA factory into the environment
|
// Initialize the logic backend of the editor
|
||||||
|
FAstEditor.Init(FEnvironment);
|
||||||
|
|
||||||
|
// 3. Register the SMA factory into the environment
|
||||||
var RegFunc :=
|
var RegFunc :=
|
||||||
procedure(const Scope: IExecutionScope)
|
procedure(const Scope: IExecutionScope)
|
||||||
var
|
var
|
||||||
@@ -286,19 +283,13 @@ begin
|
|||||||
ClearButtonClick(Self);
|
ClearButtonClick(Self);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TForm1.FormDestroy(Sender: TObject);
|
procedure TForm1.ShowVizualization;
|
||||||
begin
|
|
||||||
FController.Free;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TForm1.ShowVizualization(X, Y: Single);
|
|
||||||
begin
|
begin
|
||||||
if FCurrUnboundAst = nil then
|
if FCurrUnboundAst = nil then
|
||||||
exit;
|
exit;
|
||||||
|
|
||||||
// Use the Controller to build, validate and show the UI
|
// Use the Editor Component to build, validate and show the UI
|
||||||
// The controller internally clears FWorkspace.World and adds nodes there.
|
FAstEditor.SetRoot(FCurrUnboundAst);
|
||||||
FController.SetRoot(FCurrUnboundAst);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Simplified ExecuteAst using TEnvironment and handling errors gracefully
|
// Simplified ExecuteAst using TEnvironment and handling errors gracefully
|
||||||
@@ -422,14 +413,13 @@ end;
|
|||||||
|
|
||||||
procedure TForm1.ClearButtonClick(Sender: TObject);
|
procedure TForm1.ClearButtonClick(Sender: TObject);
|
||||||
begin
|
begin
|
||||||
// Clear content of world (not workspace itself, as it has the root layer)
|
// Clear content of component
|
||||||
FWorkspace.World.DeleteChildren;
|
FAstEditor.SetRoot(nil);
|
||||||
FWorkspace.World.Repaint;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TForm1.CompilerStageBoxChange(Sender: TObject);
|
procedure TForm1.CompilerStageBoxChange(Sender: TObject);
|
||||||
begin
|
begin
|
||||||
ShowVizualization(14, 14);
|
ShowVizualization;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TForm1.CreateTriggerExampleButtonClick(Sender: TObject);
|
procedure TForm1.CreateTriggerExampleButtonClick(Sender: TObject);
|
||||||
@@ -731,8 +721,8 @@ begin
|
|||||||
var result := ExecuteAst(FCurrUnboundAst);
|
var result := ExecuteAst(FCurrUnboundAst);
|
||||||
Memo1.Lines.Add(Format('Script executed. Final result: %s', [result.ToString]));
|
Memo1.Lines.Add(Format('Script executed. Final result: %s', [result.ToString]));
|
||||||
finally
|
finally
|
||||||
// Trigger visualization update via Controller (inside ShowVizualization)
|
// Trigger visualization update via Component
|
||||||
ShowVizualization(14, 14);
|
ShowVizualization;
|
||||||
end;
|
end;
|
||||||
except
|
except
|
||||||
on E: Exception do
|
on E: Exception do
|
||||||
@@ -890,15 +880,15 @@ procedure TForm1.UpdateScript;
|
|||||||
begin
|
begin
|
||||||
PrintScript(FCurrUnboundAst);
|
PrintScript(FCurrUnboundAst);
|
||||||
|
|
||||||
// Clear and redraw via Controller
|
// Clear and redraw via Component
|
||||||
FController.SetRoot(FCurrUnboundAst);
|
FAstEditor.SetRoot(FCurrUnboundAst);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TForm1.WorkspaceMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
procedure TForm1.AstEditorMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
||||||
begin
|
begin
|
||||||
if Button <> TMouseButton.mbMiddle then
|
// Nur zur Demo: Wir könnten hier klicken, um etwas im Memo zu loggen
|
||||||
exit;
|
if Button = TMouseButton.mbMiddle then
|
||||||
ShowVizualization(X, Y);
|
ShowVizualization;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TForm1.SaveUserLibButtonClick(Sender: TObject);
|
procedure TForm1.SaveUserLibButtonClick(Sender: TObject);
|
||||||
|
|||||||
@@ -1,432 +0,0 @@
|
|||||||
unit Myc.Fmx.AstEditor.Controller;
|
|
||||||
|
|
||||||
interface
|
|
||||||
|
|
||||||
uses
|
|
||||||
System.SysUtils,
|
|
||||||
System.Classes,
|
|
||||||
System.Types,
|
|
||||||
System.Math.Vectors,
|
|
||||||
System.Generics.Collections,
|
|
||||||
FMX.Types,
|
|
||||||
FMX.Controls,
|
|
||||||
Myc.Ast,
|
|
||||||
Myc.Ast.Nodes,
|
|
||||||
Myc.Ast.Identities,
|
|
||||||
Myc.Ast.Types,
|
|
||||||
Myc.Ast.Visitor,
|
|
||||||
Myc.Ast.Scope,
|
|
||||||
Myc.Ast.Environment,
|
|
||||||
Myc.Fmx.AstEditor,
|
|
||||||
Myc.Fmx.AstEditor.Workspace;
|
|
||||||
|
|
||||||
type
|
|
||||||
TAstEditorController = class
|
|
||||||
private
|
|
||||||
type
|
|
||||||
TNodeMap = TDictionary<IAstIdentity, TAstViewNode>;
|
|
||||||
TUndoStack = TStack<IAstNode>;
|
|
||||||
|
|
||||||
TTypePropagator = class(TAstVisitor)
|
|
||||||
private
|
|
||||||
FMap: TNodeMap;
|
|
||||||
procedure ApplyType(const Node: IAstNode);
|
|
||||||
protected
|
|
||||||
procedure Accept(const Node: IAstNode); override;
|
|
||||||
public
|
|
||||||
constructor Create(AMap: TNodeMap);
|
|
||||||
end;
|
|
||||||
|
|
||||||
private
|
|
||||||
FEnv: TAstEnvironment;
|
|
||||||
FWorkspace: TWorkspace;
|
|
||||||
FNodeMap: TNodeMap;
|
|
||||||
FRootViewNode: TAstViewNode;
|
|
||||||
|
|
||||||
// State Tracking
|
|
||||||
FCurrentAst: IAstNode;
|
|
||||||
|
|
||||||
// Undo/Redo Stacks
|
|
||||||
FUndoStack: TUndoStack;
|
|
||||||
FRedoStack: TUndoStack;
|
|
||||||
FIsUndoing: Boolean;
|
|
||||||
|
|
||||||
procedure CollectNodes(Parent: TControl);
|
|
||||||
procedure ClearVisuals;
|
|
||||||
procedure ApplyErrors(const Log: ICompilerLog);
|
|
||||||
procedure ApplyTypes(const TypedAst: IAstNode);
|
|
||||||
procedure RootResized(Sender: TObject);
|
|
||||||
|
|
||||||
procedure HandleNodeDragDrop(ASource, ATarget: TControl; AIndex: Integer);
|
|
||||||
|
|
||||||
procedure CommitSnapshot;
|
|
||||||
|
|
||||||
public
|
|
||||||
constructor Create(const AEnv: TAstEnvironment; AWorkspace: TWorkspace);
|
|
||||||
destructor Destroy; override;
|
|
||||||
|
|
||||||
procedure ValidateAndShow;
|
|
||||||
procedure SetRoot(const AstNode: IAstNode);
|
|
||||||
|
|
||||||
procedure Undo;
|
|
||||||
procedure Redo;
|
|
||||||
function CanUndo: Boolean;
|
|
||||||
function CanRedo: Boolean;
|
|
||||||
|
|
||||||
// Expose current AST for external use (saving etc.)
|
|
||||||
property CurrentAst: IAstNode read FCurrentAst;
|
|
||||||
end;
|
|
||||||
|
|
||||||
implementation
|
|
||||||
|
|
||||||
uses
|
|
||||||
Myc.Data.Value,
|
|
||||||
Myc.Fmx.AstEditor.Visualizer;
|
|
||||||
|
|
||||||
{ TAstEditorController }
|
|
||||||
|
|
||||||
constructor TAstEditorController.Create(const AEnv: TAstEnvironment; AWorkspace: TWorkspace);
|
|
||||||
begin
|
|
||||||
inherited Create;
|
|
||||||
FEnv := AEnv;
|
|
||||||
FWorkspace := AWorkspace;
|
|
||||||
FWorkspace.OnNodeDragDrop := HandleNodeDragDrop;
|
|
||||||
FNodeMap := TNodeMap.Create;
|
|
||||||
|
|
||||||
FUndoStack := TUndoStack.Create;
|
|
||||||
FRedoStack := TUndoStack.Create;
|
|
||||||
FIsUndoing := False;
|
|
||||||
FCurrentAst := nil;
|
|
||||||
end;
|
|
||||||
|
|
||||||
destructor TAstEditorController.Destroy;
|
|
||||||
begin
|
|
||||||
// Cleaning up FCurrentAst is done by ARC automatically
|
|
||||||
SetRoot(nil);
|
|
||||||
FNodeMap.Free;
|
|
||||||
FUndoStack.Free;
|
|
||||||
FRedoStack.Free;
|
|
||||||
inherited;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstEditorController.CommitSnapshot;
|
|
||||||
begin
|
|
||||||
// Optimization: Push the already known AST instead of recreating it from UI.
|
|
||||||
// Since AST nodes are immutable interfaces, storing the reference is safe.
|
|
||||||
if Assigned(FCurrentAst) and (not FIsUndoing) then
|
|
||||||
begin
|
|
||||||
FUndoStack.Push(FCurrentAst);
|
|
||||||
FRedoStack.Clear;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstEditorController.Undo;
|
|
||||||
var
|
|
||||||
prevAst: IAstNode;
|
|
||||||
begin
|
|
||||||
if FUndoStack.Count = 0 then
|
|
||||||
exit;
|
|
||||||
|
|
||||||
FIsUndoing := True;
|
|
||||||
try
|
|
||||||
// Capture current state for Redo
|
|
||||||
if Assigned(FCurrentAst) then
|
|
||||||
FRedoStack.Push(FCurrentAst);
|
|
||||||
|
|
||||||
// Restore previous state
|
|
||||||
prevAst := FUndoStack.Pop;
|
|
||||||
SetRoot(prevAst);
|
|
||||||
finally
|
|
||||||
FIsUndoing := False;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstEditorController.Redo;
|
|
||||||
var
|
|
||||||
nextAst: IAstNode;
|
|
||||||
begin
|
|
||||||
if FRedoStack.Count = 0 then
|
|
||||||
exit;
|
|
||||||
|
|
||||||
FIsUndoing := True;
|
|
||||||
try
|
|
||||||
// Capture current state for Undo
|
|
||||||
if Assigned(FCurrentAst) then
|
|
||||||
FUndoStack.Push(FCurrentAst);
|
|
||||||
|
|
||||||
// Restore next state
|
|
||||||
nextAst := FRedoStack.Pop;
|
|
||||||
SetRoot(nextAst);
|
|
||||||
finally
|
|
||||||
FIsUndoing := False;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TAstEditorController.CanUndo: Boolean;
|
|
||||||
begin
|
|
||||||
Result := FUndoStack.Count > 0;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TAstEditorController.CanRedo: Boolean;
|
|
||||||
begin
|
|
||||||
Result := FRedoStack.Count > 0;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstEditorController.HandleNodeDragDrop(ASource, ATarget: TControl; AIndex: Integer);
|
|
||||||
var
|
|
||||||
SourceParent: TControl;
|
|
||||||
ContainerOwner: TAstViewNode;
|
|
||||||
Reorderable: IReorderable;
|
|
||||||
SourceIdx, TargetIdx: Integer;
|
|
||||||
NewAst: IAstNode;
|
|
||||||
begin
|
|
||||||
if (ASource = nil) or (AIndex < 0) then
|
|
||||||
exit;
|
|
||||||
if not (ASource is TAstViewNode) then
|
|
||||||
exit;
|
|
||||||
|
|
||||||
if not (ASource.Parent is TControl) then
|
|
||||||
exit;
|
|
||||||
SourceParent := TControl(ASource.Parent);
|
|
||||||
|
|
||||||
ContainerOwner := nil;
|
|
||||||
|
|
||||||
// Logic to find the Reorderable container (same as before)
|
|
||||||
if (SourceParent is TAstViewNode) then
|
|
||||||
begin
|
|
||||||
ContainerOwner := TAstViewNode(SourceParent);
|
|
||||||
if not Supports(ContainerOwner.Handler, IReorderable, Reorderable) then
|
|
||||||
ContainerOwner := nil;
|
|
||||||
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
|
|
||||||
SourceIdx := ASource.Index;
|
|
||||||
TargetIdx := AIndex;
|
|
||||||
|
|
||||||
if SourceIdx <> TargetIdx then
|
|
||||||
begin
|
|
||||||
// 1. Commit the CURRENT (clean) AST before visual changes
|
|
||||||
CommitSnapshot;
|
|
||||||
|
|
||||||
// 2. Perform Visual Change (Dirty State)
|
|
||||||
Reorderable.MoveChild(SourceIdx, TargetIdx);
|
|
||||||
|
|
||||||
// 3. Generate NEW AST from the modified visual tree
|
|
||||||
NewAst := FRootViewNode.CreateAst;
|
|
||||||
|
|
||||||
// 4. Update the system with the new AST (Updates FCurrentAst)
|
|
||||||
SetRoot(NewAst);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstEditorController.SetRoot(const AstNode: IAstNode);
|
|
||||||
var
|
|
||||||
Visualizer: IAstVisualizer;
|
|
||||||
begin
|
|
||||||
// Update the "Truth"
|
|
||||||
FCurrentAst := AstNode;
|
|
||||||
|
|
||||||
if not FIsUndoing then
|
|
||||||
begin
|
|
||||||
FUndoStack.Clear;
|
|
||||||
FRedoStack.Clear;
|
|
||||||
end;
|
|
||||||
|
|
||||||
FWorkspace.World.DeleteChildren;
|
|
||||||
if Assigned(FRootViewNode) then
|
|
||||||
FRootViewNode.OnResized := nil;
|
|
||||||
FRootViewNode := nil;
|
|
||||||
|
|
||||||
if not Assigned(AstNode) then
|
|
||||||
exit;
|
|
||||||
|
|
||||||
Visualizer := TAstVisualizer.Create(FWorkspace, FWorkspace.World, 0);
|
|
||||||
FRootViewNode := Visualizer.CallAccept(AstNode);
|
|
||||||
|
|
||||||
if Assigned(FRootViewNode) then
|
|
||||||
begin
|
|
||||||
FWorkspace.World.Size := FRootViewNode.Size;
|
|
||||||
FRootViewNode.OnResized := RootResized;
|
|
||||||
FRootViewNode.Position.Point := TPointF.Create(50, 50);
|
|
||||||
end;
|
|
||||||
|
|
||||||
if Assigned(FRootViewNode) then
|
|
||||||
ValidateAndShow;
|
|
||||||
end;
|
|
||||||
|
|
||||||
// ... (Rest: CollectNodes, ClearVisuals, ApplyErrors, ApplyTypes, RootResized, TTypePropagator bleiben gleich)
|
|
||||||
|
|
||||||
procedure TAstEditorController.CollectNodes(Parent: TControl);
|
|
||||||
var
|
|
||||||
i: Integer;
|
|
||||||
child: TControl;
|
|
||||||
viewNode: TAstViewNode;
|
|
||||||
begin
|
|
||||||
for i := 0 to Parent.ChildrenCount - 1 do
|
|
||||||
begin
|
|
||||||
if Parent.Children[i] is TControl then
|
|
||||||
begin
|
|
||||||
child := TControl(Parent.Children[i]);
|
|
||||||
|
|
||||||
if child is TAstViewNode then
|
|
||||||
begin
|
|
||||||
viewNode := TAstViewNode(child);
|
|
||||||
if Assigned(viewNode.Node) and Assigned(viewNode.Node.Identity) then
|
|
||||||
begin
|
|
||||||
FNodeMap.AddOrSetValue(viewNode.Node.Identity, viewNode);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
if child.ChildrenCount > 0 then
|
|
||||||
CollectNodes(child);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstEditorController.ClearVisuals;
|
|
||||||
begin
|
|
||||||
for var viewNode in FNodeMap.Values do
|
|
||||||
begin
|
|
||||||
viewNode.ErrorMessage := '';
|
|
||||||
viewNode.TypeInfoText := '';
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstEditorController.ApplyErrors(const Log: ICompilerLog);
|
|
||||||
var
|
|
||||||
entry: TCompilerError;
|
|
||||||
viewNode: TAstViewNode;
|
|
||||||
begin
|
|
||||||
for entry in Log.GetEntries do
|
|
||||||
begin
|
|
||||||
if (entry.Level = elError) and Assigned(entry.Node) and Assigned(entry.Node.Identity) then
|
|
||||||
begin
|
|
||||||
if FNodeMap.TryGetValue(entry.Node.Identity, viewNode) then
|
|
||||||
begin
|
|
||||||
if viewNode.ErrorMessage = '' then
|
|
||||||
viewNode.ErrorMessage := entry.Message
|
|
||||||
else
|
|
||||||
viewNode.ErrorMessage := viewNode.ErrorMessage + sLineBreak + entry.Message;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstEditorController.ApplyTypes(const TypedAst: IAstNode);
|
|
||||||
var
|
|
||||||
propagator: TTypePropagator;
|
|
||||||
begin
|
|
||||||
if not Assigned(TypedAst) then
|
|
||||||
exit;
|
|
||||||
|
|
||||||
propagator := TTypePropagator.Create(FNodeMap);
|
|
||||||
try
|
|
||||||
propagator.Accept(TypedAst);
|
|
||||||
finally
|
|
||||||
propagator.Free;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstEditorController.RootResized(Sender: TObject);
|
|
||||||
begin
|
|
||||||
FWorkspace.World.Size := FRootViewNode.Size;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstEditorController.ValidateAndShow;
|
|
||||||
var
|
|
||||||
logicalAst: IAstNode;
|
|
||||||
begin
|
|
||||||
if not Assigned(FRootViewNode) then
|
|
||||||
exit;
|
|
||||||
|
|
||||||
// Note: We use FCurrentAst here if we trust it, or recreate for safety.
|
|
||||||
// Since SetRoot sets FCurrentAst, and any UI manipulation (DragDrop) calls SetRoot afterwards,
|
|
||||||
// FCurrentAst should be in sync.
|
|
||||||
// However, strictly speaking, ValidateAndShow operates on the VISUAL tree to find Mapping.
|
|
||||||
// But since we have FCurrentAst, we can use it directly for binding.
|
|
||||||
|
|
||||||
// BUT: ValidateAndShow is responsible for mapping NodeMap.
|
|
||||||
logicalAst := FRootViewNode.CreateAst;
|
|
||||||
// ^ This is still needed to rebuild the Mapping if we haven't tracked ViewNodes perfectly.
|
|
||||||
// Or we rely on FCurrentAst being the source of truth and we assume ViewNodes match.
|
|
||||||
// For now, let's keep CreateAst here to be 100% sure the mapping corresponds to what is on screen.
|
|
||||||
// The performance hit here is acceptable as it's part of the validation cycle, not the drag-start.
|
|
||||||
|
|
||||||
FNodeMap.Clear;
|
|
||||||
CollectNodes(FWorkspace.World);
|
|
||||||
|
|
||||||
ClearVisuals;
|
|
||||||
|
|
||||||
try
|
|
||||||
var log := TCompilerLog.Create as ICompilerLog;
|
|
||||||
|
|
||||||
var expanded := FEnv.ExpandMacros(logicalAst);
|
|
||||||
var typedAst := FEnv.Bind(expanded, [], log);
|
|
||||||
var specAst := FEnv.Specialize(typedAst);
|
|
||||||
|
|
||||||
ApplyTypes(specAst);
|
|
||||||
|
|
||||||
if specAst.Kind = akLambdaExpression then
|
|
||||||
var compiled := FEnv.Link(specAst.AsLambdaExpression, log);
|
|
||||||
|
|
||||||
ApplyErrors(log);
|
|
||||||
except
|
|
||||||
on E: ECompilationFailed do
|
|
||||||
begin
|
|
||||||
var tempLog := TCompilerLog.Create as ICompilerLog;
|
|
||||||
for var err in E.Errors do
|
|
||||||
tempLog.Add(err.Level, err.Message, err.Node);
|
|
||||||
ApplyErrors(tempLog);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
FWorkspace.World.Repaint;
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ TAstEditorController.TTypePropagator }
|
|
||||||
|
|
||||||
constructor TAstEditorController.TTypePropagator.Create(AMap: TNodeMap);
|
|
||||||
begin
|
|
||||||
inherited Create;
|
|
||||||
FMap := AMap;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstEditorController.TTypePropagator.ApplyType(const Node: IAstNode);
|
|
||||||
var
|
|
||||||
viewNode: TAstViewNode;
|
|
||||||
begin
|
|
||||||
if Assigned(Node) and Assigned(Node.Identity) then
|
|
||||||
begin
|
|
||||||
if FMap.TryGetValue(Node.Identity, viewNode) then
|
|
||||||
begin
|
|
||||||
if Node.IsTyped then
|
|
||||||
begin
|
|
||||||
var st := Node.AsTypedNode.StaticType;
|
|
||||||
if Assigned(st) then
|
|
||||||
viewNode.TypeInfoText := st.ToString;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TAstEditorController.TTypePropagator.Accept(const Node: IAstNode);
|
|
||||||
begin
|
|
||||||
if Node = nil then
|
|
||||||
exit;
|
|
||||||
ApplyType(Node);
|
|
||||||
Node.Accept(Self);
|
|
||||||
end;
|
|
||||||
|
|
||||||
end.
|
|
||||||
@@ -18,7 +18,7 @@ uses
|
|||||||
Myc.Ast.Nodes,
|
Myc.Ast.Nodes,
|
||||||
Myc.Ast.Identities,
|
Myc.Ast.Identities,
|
||||||
Myc.Fmx.AstEditor.Layout,
|
Myc.Fmx.AstEditor.Layout,
|
||||||
Myc.Fmx.AstEditor;
|
Myc.Fmx.AstEditor.Node;
|
||||||
|
|
||||||
type
|
type
|
||||||
// --- Abstract List Base ---
|
// --- Abstract List Base ---
|
||||||
|
|||||||
@@ -0,0 +1,542 @@
|
|||||||
|
unit Myc.Fmx.AstEditor.Node;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.SysUtils,
|
||||||
|
System.Classes,
|
||||||
|
System.UITypes,
|
||||||
|
System.Types,
|
||||||
|
System.Math,
|
||||||
|
System.Math.Vectors,
|
||||||
|
System.Generics.Defaults,
|
||||||
|
System.Generics.Collections,
|
||||||
|
FMX.Types,
|
||||||
|
FMX.Controls,
|
||||||
|
FMX.Objects,
|
||||||
|
FMX.Graphics,
|
||||||
|
FMX.StdCtrls,
|
||||||
|
Myc.Ast.Nodes,
|
||||||
|
Myc.Fmx.AstEditor.Workspace,
|
||||||
|
Myc.Fmx.AstEditor.Layout;
|
||||||
|
|
||||||
|
const
|
||||||
|
cNodePadding = 10;
|
||||||
|
cTitleTopPadding = 2;
|
||||||
|
cTitleBottomPadding = 4;
|
||||||
|
cMinNodeWidth = 10;
|
||||||
|
cMinNodeHeight = 10;
|
||||||
|
cDragGutterWidth = 20; // Width of the trigger zone for dragging
|
||||||
|
|
||||||
|
type
|
||||||
|
TAstViewNode = class;
|
||||||
|
|
||||||
|
IAstViewNodeHandler = interface
|
||||||
|
function GetAstNode: IAstNode;
|
||||||
|
procedure BuildUI(OwnerNode: TAstViewNode);
|
||||||
|
function ReconstructAst(OwnerNode: TAstViewNode): IAstNode;
|
||||||
|
property Node: IAstNode read GetAstNode;
|
||||||
|
end;
|
||||||
|
|
||||||
|
IReorderable = interface
|
||||||
|
['{9FA7C504-D327-4E00-A5FE-DD2876886212}']
|
||||||
|
function CanDrag(Child: TControl): Boolean;
|
||||||
|
procedure MoveChild(SourceIndex, TargetIndex: Integer);
|
||||||
|
end;
|
||||||
|
|
||||||
|
IAstVisualizer = interface
|
||||||
|
{$region 'private'}
|
||||||
|
function GetExprDepth: Integer;
|
||||||
|
function GetParentControl: TControl;
|
||||||
|
function GetWorkspace: TWorkspace;
|
||||||
|
{$endregion}
|
||||||
|
|
||||||
|
function Clone(ParentControl: TControl; ExprDepth: Integer): IAstVisualizer;
|
||||||
|
function CallAccept(const Node: IAstNode): TAstViewNode;
|
||||||
|
|
||||||
|
property ExprDepth: Integer read GetExprDepth;
|
||||||
|
property ParentControl: TControl read GetParentControl;
|
||||||
|
property Workspace: TWorkspace read GetWorkspace;
|
||||||
|
end;
|
||||||
|
|
||||||
|
TAstViewNode = class(TAutoFitControl)
|
||||||
|
private
|
||||||
|
FBackgroundColor: TAlphaColor;
|
||||||
|
FBorderColor: TAlphaColor;
|
||||||
|
FBorderWidth: Single;
|
||||||
|
FBorderRadius: Single;
|
||||||
|
FFrameless: Boolean;
|
||||||
|
FVisualizer: IAstVisualizer;
|
||||||
|
FHandler: IAstViewNodeHandler;
|
||||||
|
|
||||||
|
FErrorMessage: string;
|
||||||
|
FTypeInfoText: string;
|
||||||
|
|
||||||
|
FIsFocused: Boolean;
|
||||||
|
|
||||||
|
procedure SetBackgroundColor(const Value: TAlphaColor);
|
||||||
|
procedure SetBorderColor(const Value: TAlphaColor);
|
||||||
|
procedure SetBorderWidth(const Value: Single);
|
||||||
|
procedure SetBorderRadius(const Value: Single);
|
||||||
|
procedure SetFrameless(const Value: Boolean);
|
||||||
|
function GetNode: IAstNode;
|
||||||
|
|
||||||
|
procedure SetErrorMessage(const Value: string);
|
||||||
|
procedure SetTypeInfoText(const Value: string);
|
||||||
|
procedure UpdateVisualState;
|
||||||
|
procedure SetIsFocused(const Value: Boolean);
|
||||||
|
|
||||||
|
protected
|
||||||
|
procedure Paint; override;
|
||||||
|
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
|
||||||
|
constructor Create(const AVisualizer: IAstVisualizer; const AHandler: IAstViewNodeHandler); reintroduce;
|
||||||
|
destructor Destroy; override;
|
||||||
|
|
||||||
|
procedure AfterConstruction; override;
|
||||||
|
|
||||||
|
function AddLabel(Parent: TControl; const Txt: String): TLabel;
|
||||||
|
function AddContainer(Parent: TControl; Orientation: TLayoutOrientation; Alignment: TLayoutAlignment): TAutoFitControl;
|
||||||
|
function AddExpr(Parent: TControl; const Title: String; const Node: IAstNode): TAstViewNode;
|
||||||
|
|
||||||
|
function CreateAst: IAstNode;
|
||||||
|
|
||||||
|
property Visualizer: IAstVisualizer read FVisualizer;
|
||||||
|
property Node: IAstNode read GetNode;
|
||||||
|
property Handler: IAstViewNodeHandler read FHandler;
|
||||||
|
|
||||||
|
property ErrorMessage: string read FErrorMessage write SetErrorMessage;
|
||||||
|
property TypeInfoText: string read FTypeInfoText write SetTypeInfoText;
|
||||||
|
property IsFocused: Boolean read FIsFocused write SetIsFocused;
|
||||||
|
|
||||||
|
published
|
||||||
|
property BorderColor: TAlphaColor read FBorderColor write SetBorderColor;
|
||||||
|
property BorderWidth: Single read FBorderWidth write SetBorderWidth;
|
||||||
|
property BorderRadius: Single read FBorderRadius write SetBorderRadius;
|
||||||
|
property BackgroundColor: TAlphaColor read FBackgroundColor write SetBackgroundColor;
|
||||||
|
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;
|
||||||
|
|
||||||
|
TBaseNodeHandler<T: IAstNode> = class(TInterfacedObject, IAstViewNodeHandler)
|
||||||
|
protected
|
||||||
|
FNode: T;
|
||||||
|
function GetAstNode: IAstNode;
|
||||||
|
public
|
||||||
|
constructor Create(const ANode: T);
|
||||||
|
procedure BuildUI(OwnerNode: TAstViewNode); virtual; abstract;
|
||||||
|
function ReconstructAst(OwnerNode: TAstViewNode): IAstNode; virtual; abstract;
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
uses
|
||||||
|
Myc.Ast;
|
||||||
|
|
||||||
|
{ TAstViewNode }
|
||||||
|
|
||||||
|
constructor TAstViewNode.Create(const AVisualizer: IAstVisualizer; const AHandler: IAstViewNodeHandler);
|
||||||
|
begin
|
||||||
|
inherited Create(AVisualizer.Workspace);
|
||||||
|
|
||||||
|
Parent := AVisualizer.ParentControl;
|
||||||
|
|
||||||
|
FVisualizer := AVisualizer.Clone(Self, AVisualizer.ExprDepth);
|
||||||
|
FHandler := AHandler;
|
||||||
|
|
||||||
|
FBorderColor := TAlphaColors.Gray;
|
||||||
|
FBorderWidth := 1;
|
||||||
|
FBorderRadius := 9;
|
||||||
|
FBackgroundColor := $080a0a0a;
|
||||||
|
|
||||||
|
FFrameless := False;
|
||||||
|
FIsFocused := False;
|
||||||
|
|
||||||
|
Width := cMinNodeWidth;
|
||||||
|
Height := cMinNodeHeight;
|
||||||
|
|
||||||
|
HitTest := True;
|
||||||
|
ClipChildren := True;
|
||||||
|
|
||||||
|
Margins.Left := 4;
|
||||||
|
Margins.Top := 4;
|
||||||
|
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 c: Cardinal := $ff000000 or (cn shl 16) or (cn shl 8) or cn;
|
||||||
|
BackgroundColor := c;
|
||||||
|
end;
|
||||||
|
|
||||||
|
destructor TAstViewNode.Destroy;
|
||||||
|
begin
|
||||||
|
FHandler := nil;
|
||||||
|
inherited;
|
||||||
|
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;
|
||||||
|
begin
|
||||||
|
inherited;
|
||||||
|
SetupNode;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstViewNode.CreateAst: IAstNode;
|
||||||
|
begin
|
||||||
|
if Assigned(FHandler) then
|
||||||
|
Result := FHandler.ReconstructAst(Self)
|
||||||
|
else
|
||||||
|
Result := TAst.Nop;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAstViewNode.GetNode: IAstNode;
|
||||||
|
begin
|
||||||
|
if Assigned(FHandler) then
|
||||||
|
Result := FHandler.Node
|
||||||
|
else
|
||||||
|
Result := nil;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAstViewNode.SetErrorMessage(const Value: string);
|
||||||
|
begin
|
||||||
|
if FErrorMessage <> Value then
|
||||||
|
begin
|
||||||
|
FErrorMessage := Value;
|
||||||
|
UpdateVisualState;
|
||||||
|
Repaint;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAstViewNode.SetTypeInfoText(const Value: string);
|
||||||
|
begin
|
||||||
|
if FTypeInfoText <> Value then
|
||||||
|
begin
|
||||||
|
FTypeInfoText := Value;
|
||||||
|
UpdateVisualState;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAstViewNode.SetIsFocused(const Value: Boolean);
|
||||||
|
begin
|
||||||
|
if FIsFocused <> Value then
|
||||||
|
begin
|
||||||
|
FIsFocused := Value;
|
||||||
|
Repaint;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAstViewNode.UpdateVisualState;
|
||||||
|
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
|
||||||
|
rect: TRectF;
|
||||||
|
effBorderColor: TAlphaColor;
|
||||||
|
effBorderWidth: Single;
|
||||||
|
effDash: TStrokeDash;
|
||||||
|
isHovering: Boolean;
|
||||||
|
shouldIgnoreHover: Boolean;
|
||||||
|
begin
|
||||||
|
inherited;
|
||||||
|
|
||||||
|
shouldIgnoreHover := False;
|
||||||
|
if Assigned(Node) then
|
||||||
|
begin
|
||||||
|
case Node.Kind of
|
||||||
|
akBlockExpression, akParameterList, akArgumentList, akExpressionList, akRecordFieldList: shouldIgnoreHover := True;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
isHovering := IsMouseOver and (not shouldIgnoreHover);
|
||||||
|
|
||||||
|
// 1. Determine Base Style
|
||||||
|
if FFrameless then
|
||||||
|
begin
|
||||||
|
effBorderColor := FBorderColor;
|
||||||
|
effBorderWidth := FBorderWidth;
|
||||||
|
effDash := TStrokeDash.Dot;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
effBorderColor := FBorderColor;
|
||||||
|
effBorderWidth := FBorderWidth;
|
||||||
|
effDash := TStrokeDash.Solid;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// 2. Hover Overlay
|
||||||
|
if isHovering then
|
||||||
|
begin
|
||||||
|
effBorderColor := TAlphaColors.White;
|
||||||
|
effBorderWidth := Max(2.0, FBorderWidth + 1.0);
|
||||||
|
effDash := TStrokeDash.Solid;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// 3. Error Overlay (High Priority for Color)
|
||||||
|
if FErrorMessage <> '' then
|
||||||
|
begin
|
||||||
|
effBorderColor := TAlphaColors.Red;
|
||||||
|
effBorderWidth := 2.0;
|
||||||
|
effDash := TStrokeDash.Solid;
|
||||||
|
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
|
||||||
|
begin
|
||||||
|
effBorderColor := TAlphaColors.Dodgerblue;
|
||||||
|
effBorderWidth := 2.0;
|
||||||
|
effDash := TStrokeDash.Solid;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Canvas.Fill.Kind := TBrushKind.Solid;
|
||||||
|
Canvas.Fill.Color := FBackgroundColor;
|
||||||
|
|
||||||
|
if FFrameless and (FErrorMessage = '') and (not isHovering) and (not FIsFocused) then
|
||||||
|
begin
|
||||||
|
Canvas.FillRect(TRectF.Create(0, 0, Width, Height), 0, 0, [], 1.0);
|
||||||
|
|
||||||
|
if FBorderWidth > 0 then
|
||||||
|
begin
|
||||||
|
Canvas.Stroke.Kind := TBrushKind.Solid;
|
||||||
|
Canvas.Stroke.Color := effBorderColor;
|
||||||
|
Canvas.Stroke.Thickness := effBorderWidth;
|
||||||
|
Canvas.Stroke.Dash := effDash;
|
||||||
|
Canvas.DrawRect(TRectF.Create(0, 0, Width, Height), 0, 0, [], 1.0);
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
rect := TRectF.Create(0, 0, Width, Height);
|
||||||
|
if (effBorderWidth > 0) then
|
||||||
|
rect.Inflate(-effBorderWidth / 2, -effBorderWidth / 2);
|
||||||
|
|
||||||
|
Canvas.FillRect(rect, FBorderRadius, FBorderRadius, AllCorners, 1.0, TCornerType.Round);
|
||||||
|
|
||||||
|
if (effBorderWidth > 0) and (effBorderColor <> TAlphaColors.Null) then
|
||||||
|
begin
|
||||||
|
Canvas.Stroke.Kind := TBrushKind.Solid;
|
||||||
|
Canvas.Stroke.Color := effBorderColor;
|
||||||
|
Canvas.Stroke.Thickness := effBorderWidth;
|
||||||
|
Canvas.Stroke.Dash := effDash;
|
||||||
|
Canvas.DrawRect(rect, FBorderRadius, FBorderRadius, AllCorners, 1.0, TCornerType.Round);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if isHovering and (Parent is TAutoFitControl) then
|
||||||
|
begin
|
||||||
|
Canvas.Fill.Color := TAlphaColors.DimGray;
|
||||||
|
Canvas.FillEllipse(TRectF.Create(4, 6, 8, 10), 1.0);
|
||||||
|
Canvas.FillEllipse(TRectF.Create(4, 12, 8, 16), 1.0);
|
||||||
|
Canvas.FillEllipse(TRectF.Create(4, 18, 8, 22), 1.0);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAstViewNode.SetBorderColor(const Value: TAlphaColor);
|
||||||
|
begin
|
||||||
|
if FBorderColor <> Value then
|
||||||
|
begin
|
||||||
|
FBorderColor := Value;
|
||||||
|
Repaint;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAstViewNode.SetBorderRadius(const Value: Single);
|
||||||
|
begin
|
||||||
|
if FBorderRadius <> Value then
|
||||||
|
begin
|
||||||
|
FBorderRadius := Value;
|
||||||
|
Repaint;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAstViewNode.SetBorderWidth(const Value: Single);
|
||||||
|
begin
|
||||||
|
if FBorderWidth <> Value then
|
||||||
|
begin
|
||||||
|
FBorderWidth := Value;
|
||||||
|
Repaint;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAstViewNode.SetFrameless(const Value: Boolean);
|
||||||
|
begin
|
||||||
|
if FFrameless <> Value then
|
||||||
|
begin
|
||||||
|
FFrameless := Value;
|
||||||
|
Repaint;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAstViewNode.SetBackgroundColor(const Value: TAlphaColor);
|
||||||
|
begin
|
||||||
|
FBackgroundColor := Value;
|
||||||
|
Repaint;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAstViewNode.SetupNode;
|
||||||
|
begin
|
||||||
|
if Assigned(FHandler) then
|
||||||
|
FHandler.BuildUI(Self);
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TBaseNodeHandler<T> }
|
||||||
|
|
||||||
|
constructor TBaseNodeHandler<T>.Create(const ANode: T);
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FNode := ANode;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TBaseNodeHandler<T>.GetAstNode: IAstNode;
|
||||||
|
begin
|
||||||
|
Result := FNode;
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
@@ -10,7 +10,7 @@ uses
|
|||||||
Myc.Ast.Nodes,
|
Myc.Ast.Nodes,
|
||||||
Myc.Ast.Visitor,
|
Myc.Ast.Visitor,
|
||||||
Myc.Fmx.AstEditor.Workspace,
|
Myc.Fmx.AstEditor.Workspace,
|
||||||
Myc.Fmx.AstEditor,
|
Myc.Fmx.AstEditor.Node,
|
||||||
Myc.Fmx.AstEditor.Handlers;
|
Myc.Fmx.AstEditor.Handlers;
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ uses
|
|||||||
|
|
||||||
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: TControl; AIndex: Integer) of object;
|
||||||
|
|
||||||
TAstWorld = class(TControl)
|
TAstWorld = class(TControl)
|
||||||
@@ -29,7 +28,7 @@ type
|
|||||||
FDropP1, FDropP2: TPointF;
|
FDropP1, FDropP2: TPointF;
|
||||||
protected
|
protected
|
||||||
procedure Paint; override;
|
procedure Paint; override;
|
||||||
procedure AfterPaint; override; // <--- HIER: Für Zeichnen ÜBER den Kindern
|
procedure AfterPaint; override;
|
||||||
public
|
public
|
||||||
constructor Create(AOwner: TComponent); override;
|
constructor Create(AOwner: TComponent); override;
|
||||||
procedure InvalidateConnections;
|
procedure InvalidateConnections;
|
||||||
@@ -163,6 +162,8 @@ begin
|
|||||||
inherited Create(AOwner);
|
inherited Create(AOwner);
|
||||||
ClipChildren := True;
|
ClipChildren := True;
|
||||||
HitTest := True;
|
HitTest := True;
|
||||||
|
CanFocus := True; // Enable Keyboard Input
|
||||||
|
AutoCapture := True;
|
||||||
|
|
||||||
FRootLayer := TAstWorld.Create(Self);
|
FRootLayer := TAstWorld.Create(Self);
|
||||||
FRootLayer.Parent := Self;
|
FRootLayer.Parent := Self;
|
||||||
@@ -369,6 +370,10 @@ end;
|
|||||||
procedure TWorkspace.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
procedure TWorkspace.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
|
||||||
begin
|
begin
|
||||||
inherited;
|
inherited;
|
||||||
|
// Capture Keyboard Focus
|
||||||
|
if CanFocus then
|
||||||
|
SetFocus;
|
||||||
|
|
||||||
if not FIsDraggingNode and (Button in [TMouseButton.mbLeft, TMouseButton.mbMiddle]) then
|
if not FIsDraggingNode and (Button in [TMouseButton.mbLeft, TMouseButton.mbMiddle]) then
|
||||||
begin
|
begin
|
||||||
FIsPanning := True;
|
FIsPanning := True;
|
||||||
|
|||||||
+881
-385
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user