{ ------------------------------------------------------------- }
{ Purpose : NextLayout 6                                        }
{ By      : Bojan Nikolic                                       }
{ For     : BergSoft                                            }
{ ------------------------------------------------------------- }
{ Copyright (C) BergSoft.net 2018. All Rights Reserved.         }
{ ------------------------------------------------------------- }
{$I ..\NxSuite6.inc}
unit NxLayout6;

interface

uses
  { Standard }
  Classes, Types, Windows, Graphics, Controls, Messages,
  { Next Standard 6 }
  NxConsts6, NxIntf6, NxClasses6, NxTypes6, NxControls6, NxEdit6, NxStylePaint6, NxAssociates6,
  { Next Layout 6 }
  NxLayoutIntf6, NxLayoutNodes6;

const
  szDefaultLineHeight = 20;

type
  TNxLayoutNodeEvent = procedure (Sender: TObject; Node: TNxLayoutNode6) of object;

  { TNextForm6 }

  TNxProcessNodesResult = record
    Result: Boolean;
    BoundsRect: TRect;
    ClientRect: TRect;
    Node: TNxLayoutNode6;
  end;

  { TNextForm6 }

  TNextLayout6 = class(TNxDesignableControl6, INxNodeOwnerDepr, INxLayoutControl)
  private
    FActiveNode: TNxLayoutNode6;
    FAssociates: TNxAssociates6;
    FCapturedView: TNxLayoutNodeView;
    FDesigningNode: TNxLayoutNode6;
    FEditingNode: TNxLayoutNode6;
    FHoverLocation: TPoint;
    FHoverNode: TNxLayoutNode6;
    FImages: TImageList;
    FInplaceEdit: INxInplaceEdit;
    FLineHeight: Integer;
    FNodeList: TNxLayoutNodeList;
    FOnNodeChange: TNxLayoutNodeEvent;
    FOnSetProperty: TNxLayoutNodeEvent;
    FShowNodeBounds: Boolean;
    FStyle: TNxAppearanceStyle;
    FStylePaint: INxStylePaint;
    FWantReturns: Boolean;
    FWantTabs: Boolean;
    { Property Accessors }
    function GetActiveNode: TNxLayoutNode6;
    function GetAssociates: TNxAssociates6;
    function GetNodes: TNxLayoutNodeList;
    procedure SetActiveNode(const Value: TNxLayoutNode6);
    procedure SetDesigningNode(const Value: TNxLayoutNode6);
    procedure SetHoverNode(const Value: TNxLayoutNode6);
    procedure SetImages(const Value: TImageList);
    procedure SetLineHeight(const Value: Integer);
    procedure SetShowNodeBounds(const Value: Boolean);
    procedure SetStyle(const Value: TNxAppearanceStyle);
  protected
    { Protected Fields }
    FMouseDownNode: TNxLayoutNode6;
    procedure CreateStylePaint; virtual;
    { DFM }
    function GetChildOwner: TComponent; override;
    { Event Handlers }
    procedure DoClear; dynamic;
    procedure DoNodeChange(Node: TNxLayoutNode6); dynamic;
    procedure DoSetProperty(Node: TNxLayoutNode6); dynamic;
    { Keyboard & Mouse }
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure KeyPress(var Key: Char); override;
    procedure KeyUp(var Key: Word; Shift: TShiftState); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    { Painting }
    procedure Paint; override;
    { Invalidation }
    procedure InvalidateNode(LayoutNode: TNxLayoutNode6; Entity: TNxNodeEntity = enNone);
    { Methods }
    function GetNodeAtPos(X, Y: Integer): TNxLayoutNode6;
    procedure NodeChanged(Node: TNxLayoutNode6; Entity: TNxNodeEntity);
    function ProcNodes(Operations: TNxProcNodesOperation;
      const X, Y: Integer; ANode: TNxLayoutNode6): TNxProcessNodesResult;
    { Delphi Messages }
    procedure CMChildKey(var AMsg: TCMChildKey); message CM_CHILDKEY;
    procedure CMDesignHitTest(var Message: TCMDesignHitTest); message CM_DESIGNHITTEST;
    procedure CMFocusChanged(var Message: TCMFocusChanged); message CM_FOCUSCHANGED;
    procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
    { WinApi Messages }
    procedure WMActivateApp(var Message: TWMActivateApp); message WM_ACTIVATEAPP;
    procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
    procedure WMKillFocus(var Message: TWMSetFocus); message WM_KILLFOCUS;
    procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
    procedure WMSize(var Message: TWMSize); message WM_SIZE;
    procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL;
    { Properties }

  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    { DFM }
    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
    { Methods }
    procedure AssignEditing;
    function EditNode(Node: TNxLayoutNode6): Boolean;
    function EndEditing(Assign: Boolean = True): Boolean;
    function GetEditRect(Node: TNxLayoutNode6): TRect;
    function GetNodeRect(Node: TNxLayoutNode6): TRect;
    function GetNodeEntityRect(Node: TNxLayoutNode6; Entity: TNxNodeEntity): TRect;
    function InplaceEditFocused: Boolean;
    procedure ReadProperties(const AParent: TNxLayoutNode6 = nil);
    procedure RefreshNode(ANode: TNxLayoutNode6);
    procedure RefreshNodeFocus(ANode: TNxLayoutNode6);
    { Properties }
    property Canvas;
    property DesigningNode: TNxLayoutNode6 read FDesigningNode write SetDesigningNode;
    property EditingNode: TNxLayoutNode6 read FEditingNode;
    property HoverNode: TNxLayoutNode6 read FHoverNode write SetHoverNode;
  published
    { Properties }
    property ActiveNode: TNxLayoutNode6 read GetActiveNode write SetActiveNode;
    property Align;
    property Anchors;
    property Associates: TNxAssociates6 read GetAssociates;
    property BiDiMode;
    property Caption;
    property Color;
    property Constraints;
    property Ctl3D;
    property DragCursor;
    property DragKind;
    property DragMode;
    property Enabled;
    property Font;
    property Hint;
    property Images: TImageList read FImages write SetImages;
    property LineHeight: Integer read FLineHeight write SetLineHeight default szDefaultLineHeight;
    property Nodes: TNxLayoutNodeList read GetNodes write FNodeList;
    property Padding;
    property ParentBiDiMode;
    property ParentColor;
    property ParentFont;
    property PopupMenu;
    property Style: TNxAppearanceStyle read FStyle write SetStyle default stNative;
    property ShowNodeBounds: Boolean read FShowNodeBounds write SetShowNodeBounds;
    property ShowHint;
    property TabOrder;
    property TabStop;
    property Visible;
    property WantReturns: Boolean read FWantReturns write FWantReturns default False;
    property WantTabs: Boolean read FWantTabs write FWantTabs default False;
    { Events }
    property OnClick;
    property OnContextPopup;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDrag;
    property OnEnter;
    property OnExit;
    {$IFDEF GESTURES}
    property OnGesture;
    {$ENDIF}
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property OnMouseDown;
    {$IFDEF MOUSEEVENTS}
    property OnMouseEnter;
    property OnMouseLeave;
    {$ENDIF}
    property OnMouseMove;
    property OnMouseUp;
    property OnMouseWheelDown;
    property OnMouseWheelUp;
    property OnNodeChange: TNxLayoutNodeEvent read FOnNodeChange write FOnNodeChange;
    property OnSetProperty: TNxLayoutNodeEvent read FOnSetProperty write FOnSetProperty;
    property OnResize;
    property OnStartDrag;
  end;

  procedure Register;

implementation

uses
  { Standard }
  SysUtils, Math, Dialogs,
  { Next Standard 6 }
  NxGraphics6,
  { Next Layout 6 }
  NxLayoutStylePaint6, NxNodes6;

procedure Register;
begin
  RegisterComponents('Next Suite 6', [TNextLayout6]);
end;

{ TNextForm6 }

procedure TNextLayout6.AssignEditing;
begin
  if Assigned(EditingNode) then EditingNode.AssignEditing;
end;

procedure TNextLayout6.CMChildKey(var AMsg: TCMChildKey);
var
  Ref: IInterfaceComponentReference;
  DispatchKey: Boolean;
  Shift: TShiftState;
begin
  inherited;
  if Supports(FInplaceEdit, IInterfaceComponentReference, Ref) then
  begin
    { 10/23/11: If Child is FInplaceEdit then send KeyDown and
                stop further processing if FInplaceEdit is released
                within KeyDown (!) }
    if Ref.GetComponent = AMsg.Sender then
    begin
      case AMsg.CharCode of
        VK_TAB: DispatchKey := WantTabs;
        else DispatchKey := True;
      end;

      Shift := ShiftState;

      { Ask InplaceEdit first (!) }
      if DispatchKey and FInplaceEdit.CanDispatchKey(AMsg.CharCode)
        then KeyDown(AMsg.CharCode, Shift);

      { 2/18/12: Potential problem with Tab key
                 and Button InplaceEdit's  }
      if AMsg.CharCode = VK_TAB then AMsg.Result := 1;

      { 10/22/11: Must be set in order to stop processing
                  if FInplaceEdit is nil (!) }
      if FInplaceEdit = nil then AMsg.Result := 1;
    end;
  end;
end;

procedure TNextLayout6.CMDesignHitTest(var Message: TCMDesignHitTest);
begin
  inherited;
  if GetNodeAtPos(Message.XPos, Message.YPos) <> nil then Message.Result := 1
    else Message.Result := 0;
end;

procedure TNextLayout6.CMFocusChanged(var Message: TCMFocusChanged);
begin
  if not Focused and not InplaceEditFocused then EndEditing;
end;

procedure TNextLayout6.CMMouseLeave(var Message: TMessage);
begin
  inherited;

end;

constructor TNextLayout6.Create(AOwner: TComponent);
begin
  inherited;
  FActiveNode := nil;
  FAssociates := TNxAssociates6.Create;
  FCapturedView := nil;
  FDesigningNode := nil;
  FEditingNode := nil;
  FImages := nil;
  FLineHeight := szDefaultLineHeight;
  FNodeList := TNxLayoutNodeList.Create(nil);
  FNodeList.Control := Self;
  FShowNodeBounds := False;
  FStyle := stNative;
  FWantReturns := False;
  FWantTabs := False;

  { Set Prop. }
  ParentColor := False;
  ScrollBars := [];
  Color := clWindow;
  Width := 250;
  Height := 150;

  { Initializations }
  CreateStylePaint;
end;

procedure TNextLayout6.CreateStylePaint;
begin
  case Style of
    stNative: FStylePaint := TNxNativeLayoutStylePaint.Create(Self);
    stModern: FStylePaint := TNxModernLayoutStylePaint.Create(Self);
  end;
  FStylePaint.Canvas := Canvas;
  FStylePaint.Control := Self;
end;

destructor TNextLayout6.Destroy;
begin
  FInplaceEdit := nil;

  { Destory Nodes & NodeList }
  FNodeList.Free;

  FStylePaint := nil;

  FAssociates.Free;
  inherited;
end;

procedure TNextLayout6.DoClear;
begin
  FActiveNode := nil;
  FEditingNode := nil;
  FHoverNode := nil;
  FInplaceEdit := nil;
end;

procedure TNextLayout6.DoNodeChange(Node: TNxLayoutNode6);
begin
  if Assigned(FOnNodeChange) then FOnNodeChange(Self, Node);
end;

procedure TNextLayout6.DoSetProperty(Node: TNxLayoutNode6);
begin
  if Assigned(FOnSetProperty) then FOnSetProperty(Self, Node);
end;

function TNextLayout6.EditNode(Node: TNxLayoutNode6): Boolean;
var
  EditRect: TRect;
begin
  { Default }
  Result := False;

  if Assigned(FInplaceEdit)
    then EndEditing(True);

  with Node do
    if CanEdit and BeginEdit then
    begin
      FInplaceEdit := InplaceEdit;

      FInplaceEdit.Hide;

      FInplaceEdit.Parent := Self;

      FInplaceEdit.BeginEdit;

      EditRect := GetEditRect(Node);

      FInplaceEdit.Padding.SetBounds(pdStandard, pdStandard, pdStandard, pdStandard);
      FInplaceEdit.SetBounds(EditRect.Left, EditRect.Top, EditRect.Right - EditRect.Left, EditRect.Bottom - EditRect.Top);
      FInplaceEdit.Show;

      FInplaceEdit.SetFocus;

      { Set `EditingNode` }
      FEditingNode := Node;

      Result := True;
    end;
end;

function TNextLayout6.EndEditing(Assign: Boolean): Boolean;
begin
  Result := False;
  if Assigned(FEditingNode) then
  begin
    { Final Properties set-up by Editor }
    try
      FInplaceEdit.EndEdit;
      FInplaceEdit := nil;
    except
      on EAccessViolation do
      begin
        ShowMessage('FInplaceEdit.EndEdit');
      end;
    end;

    { End Edit & Assign Value to Node }
    try
      FEditingNode.EndEdit(Assign);
      FEditingNode := nil;
    except
      on EAccessViolation do
      begin
        ShowMessage('FEditingNode.EndEdit(Assign)');
      end;
    end;

    Result := True;
  end;
end;

function TNextLayout6.GetActiveNode: TNxLayoutNode6;
begin
  Result := FActiveNode;
end;

function TNextLayout6.GetAssociates: TNxAssociates6;
begin
  Result := FAssociates;
end;

function TNextLayout6.GetChildOwner: TComponent;
begin
  Result := nil;
end;

procedure TNextLayout6.GetChildren(Proc: TGetChildProc; Root: TComponent);
var
  i: Integer;
begin
  inherited;
  for i := 0 to Pred(Nodes.Count) do Proc(Nodes[i]);
end;

function TNextLayout6.GetEditRect(Node: TNxLayoutNode6): TRect;
begin
  { Node Rect }
  Result := ProcNodes([noCalc], 0, 0, Node).ClientRect;

  with Node do
  begin
    View.ClientRect := Result;

    { Adjust }
    Result := View.EntityRect[enEdit];
  end;

end;

function TNextLayout6.GetNodeAtPos(X, Y: Integer): TNxLayoutNode6;
begin
  Result := ProcNodes([noHitTest], X, Y, nil).Node;
end;

function TNextLayout6.GetNodeEntityRect(Node: TNxLayoutNode6;
  Entity: TNxNodeEntity): TRect;
begin
  { Node Rect }
  Result := ProcNodes([noCalc], 0, 0, Node).ClientRect;

  Node.View.ClientRect := Result;

  { Adjust }
  Result := Node.View.EntityRect[Entity];
end;

///<summary>Return TRect of whole Node</summary>
///<param name="Node">Asking Node</param>
///<returns>Whole Node rectangle</returns>
function TNextLayout6.GetNodeRect(Node: TNxLayoutNode6): TRect;
begin
  Result := ProcNodes([noCalc], 0, 0, Node).ClientRect;
end;

function TNextLayout6.GetNodes: TNxLayoutNodeList;
begin
  Result := FNodeList;
end;

function TNextLayout6.InplaceEditFocused: Boolean;
begin
  Result := Assigned(FInplaceEdit) and FInplaceEdit.Focused;
end;

procedure TNextLayout6.InvalidateNode(LayoutNode: TNxLayoutNode6; Entity: TNxNodeEntity);
var
  InvalidationRect: TRect;
begin
  InvalidationRect := GetNodeRect(LayoutNode);
  InvalidateRect(InvalidationRect);
end;

procedure TNextLayout6.KeyDown(var Key: Word; Shift: TShiftState);
var
  Done: Boolean;

  procedure TabBackList(Node: TNxLayoutNode6; NodeList: TNxLayoutNodeList);
  var
    i: Integer;
    Index: Integer;
    ANode: TNxLayoutNode6;
  begin
    if Done then Exit;

    if Assigned(Node) then Index := Node.Index else Index := Pred(NodeList.Count);

    for i := Index downto 0 do
    begin
      ANode := NodeList[i];

      if ANode.CanFocus and (Node <> ANode) then
      begin
        ActiveNode := ANode;

        { Found }
        Done := True;
        Exit;
      end;

      { Step into Child Nodes }
      if ANode.HasChildren and (Node <> ANode) then
      begin
        if ANode.HasChildren then TabBackList(nil, ANode.ChildNodes);
      end;

    end;

    { Step out to Parent Node }
    if Assigned(Node.ParentNode) then
    begin
      TabBackList(Node.ParentNode, Node.ParentNode.ParentList);
    end else
    begin
      Done := True;
      Exit;
    end;
  end;

  procedure TabList(Node: TNxLayoutNode6; NodeList: TNxLayoutNodeList);
  var
    i: Integer;
    Index: Integer;
    ANode: TNxLayoutNode6;
  begin
    if Done then Exit;

    if Assigned(Node) then Index := Node.Index else Index := 0;

    for i := Index to Pred(NodeList.Count) do
    begin
      ANode := NodeList[i];

      if ANode.CanFocus and (Node <> ANode) then
      begin
        ActiveNode := ANode;

        { Found }
        Done := True;
        Exit;
      end;

      { Step into Child Nodes }
      if ANode.HasChildren and (Node <> ANode) then
      begin
        if ANode.HasChildren then TabList(nil, ANode.ChildNodes);
      end;

    end;

    { Step out to Parent Node }
    if Assigned(Node.ParentNode) then
    begin
      TabList(Node.ParentNode, Node.ParentNode.ParentList);
    end else
    begin
      Done := True;
      Exit;
    end;
  end;

begin
  inherited;
  Done := False;
  case Key of
    VK_RETURN: AssignEditing;
    VK_TAB:
      if Assigned(ActiveNode) then
      begin
        if ssShift in Shift
          then TabBackList(ActiveNode, ActiveNode.ParentList)
          else TabList(ActiveNode, ActiveNode.ParentList);
      end;
    VK_ESCAPE: EndEditing(False);
  end;
end;

procedure TNextLayout6.KeyPress(var Key: Char);
begin
  inherited;

end;

procedure TNextLayout6.KeyUp(var Key: Word; Shift: TShiftState);
begin
  inherited;
  FMouseDownNode := nil;
end;

procedure TNextLayout6.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
begin
  inherited;
  FMouseDownNode := GetNodeAtPos(X, Y);

  if csDesigning in ComponentState then
  begin
    DesigningNode := FMouseDownNode;

    Exit;
  end;

  if not Focused then SetFocus;

//  if FMouseDownNode <> EditingNode then EndEditing(True);

  { Transf. to view }
  if Assigned(FMouseDownNode) then
  begin
    FCapturedView := FMouseDownNode.View;
    if Assigned(FCapturedView) then
    begin
      FCapturedView.Captured := True;
      FCapturedView.MouseDown(Button, Shift, X, Y);
    end;
  end;
  ActiveNode := FMouseDownNode;
end;

procedure TNextLayout6.MouseMove(Shift: TShiftState; X, Y: Integer);
var
  Res: TNxProcessNodesResult;
begin
  inherited;

  if csDesigning in ComponentState then Exit;

  { Node already captured? }
  if Assigned(FCapturedView) then
  begin

    { Dispatch to captured view directly }
    FCapturedView.MouseMove(Shift, X, Y);

  end else
  begin

    Res := ProcNodes([noCalc], X, Y, nil);

    { Transf. to view }
    if Assigned(Res.Node) then
    begin
      Res.Node.View.ClientRect := Res.ClientRect;

      if PtInRect(Res.Node.View.EntityRect[enEdit], Point(X, Y))
      then
        Cursor := crIBeam
      else
        Cursor := crDefault;

      FHoverLocation := Point(X, Y + 16);

      if Res.Node.Enabled then
      begin
        Res.Node.View.ClientRect := Res.ClientRect;
        Res.Node.View.MouseMove(Shift, X, Y);
      end;

    end
    else Cursor := crDefault;

    HoverNode := Res.Node;
  end;
end;

procedure TNextLayout6.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
begin
  inherited;

  if Assigned(FCapturedView) then
  begin
    FCapturedView.MouseUp(Button, Shift, X, Y);

    { Un-capture }
    FCapturedView := nil;
  end;
end;

procedure TNextLayout6.NodeChanged(Node: TNxLayoutNode6; Entity: TNxNodeEntity);
begin
  case Entity of
    enNone: ;
    enToggleButton:
    begin
      EndEditing(True);
      Invalidate;
    end;
    enEdit: ;
    enIconBefore: ;
    enTextBefore:
      InvalidateNode(Node);
    enContent:
    begin
      RefreshNode(Node);

      { Trigger event }
      DoNodeChange(Node);
    end;
    enIconAfter: ;
    enTextAfter: ;
  end;
end;

procedure TNextLayout6.Paint;
var
  FocusRect: TRect;
begin
  inherited;

  FillPadding(Canvas, Color, ClientRect, Padding);

  ProcNodes([noPaint], 0, 0, nil);

  if Assigned(ActiveNode)
    and ActiveNode.CanFocus then
  begin
    FocusRect := GetNodeEntityRect(ActiveNode, enContent);
    Canvas.Brush.Color := FStylePaint.HighlightColor;
    InflateRect(FocusRect, 1, 1);
    Canvas.FrameRect(FocusRect);
    InflateRect(FocusRect, 1, 1);
    Canvas.FrameRect(FocusRect);
  end;

  if csDesigning in ComponentState then
    if Assigned(DesigningNode) then
    begin
      FocusRect := GetNodeRect(DesigningNode);
      Canvas.Brush.Color := clHighlight;
      Canvas.FrameRect(FocusRect);
      InflateRect(FocusRect, -1, -1);
      Canvas.FrameRect(FocusRect);
    end;

end;

function TNextLayout6.ProcNodes(Operations: TNxProcNodesOperation; const X,
  Y: Integer; ANode: TNxLayoutNode6): TNxProcessNodesResult;
var
  i: Integer;
  Pos: TPoint;
  Offset: TSize;
  Cleared, Done: Boolean;
  Count: Integer;
  PaddingRect: TRect;
  BackColor: TColor;

  procedure ProcNode(Node: TNxLayoutNode6; ARect: TRect; LineHeight: Integer);
  var
    j: Integer;
    ViewRect, PadRect: TRect;

    procedure ClearLine(Force: Boolean = False);
    begin

      if not Cleared or Force then
      begin

        { Erase right space }
        if noPaint in Operations then
        begin
          EraseBkGnd(Rect(Pos.X, Pos.Y, ARect.Right, Pos.Y + Offset.cy), BackColor);
        end;

        Pos.X := ARect.Left;

        Inc(Pos.Y, Offset.cy);
        Offset.cy := 0;

        Cleared := False;
      end;

    end;

  begin
    Cleared := False;

    Canvas.Font.Assign(Font);
    Node.View.Canvas := Canvas;

    Node.View.StylePaint := FStylePaint as INxLayoutStylePaint;
    Node.View.Images := Self.Images;
    Node.View.Font := Self.Font;
    Node.View.NodeStyle := [];
    Node.View.Control := Self;
    Node.View.LineHeight := IfThen(Node.Height > 0, Node.Height, LineHeight);

    if clLeft in Node.Clearing
      then ClearLine;

    { Root }
    if Node.IsTopLevel then
    begin
      ViewRect := Rect(Pos.X, Pos.Y, ARect.Right, ARect.Top + LineHeight);

      Offset.cy := LineHeight;

      ClearLine;
    end
    else if Node.Level = 1 then { 1st Level }
    begin
      Pos.X := ARect.Left;

      ViewRect := Bounds(Pos.X, Pos.Y, Node.BestSize.cx, LineHeight);

      Inc(Pos.X, Node.BestSize.cx);

      if not Node.Expanded then
      begin
        if noPaint in Operations then
        begin
          EraseBkGnd(Rect(Pos.X, Pos.Y, ARect.Right, ViewRect.Bottom + Node.Padding.Top), BackColor);
        end;

        Pos.Y := ViewRect.Bottom;
      end;

    end else
    begin

      { Auto-clear }
      if Pos.X + Node.BestSize.cx > ARect.Right
        then ClearLine;

      if Node.Height = 0
      then
        Offset.cy := Max(Offset.cy, LineHeight + Node.Margins.Bottom)
      else
        Offset.cy := Max(Offset.cy, Node.Height + Node.Margins.Bottom);

      ViewRect := Bounds(Pos.X, Pos.Y, Node.BestSize.cx, IfThen(Node.Height > 0, Node.Height, LineHeight));

      Inc(Pos.X, Node.BestSize.cx);

      if noPaint in Operations then
      begin
        { Right Margin }
        EraseBkGnd(Bounds(Pos.X, Pos.Y, Node.Margins.Right, Offset.cy), BackColor);
      end;

      Inc(Pos.X, Node.Margins.Right);

    end;


    { Clearing }
    if clRight in Node.Clearing then
    begin
      if Node.BestSize.cx = 0
        then ViewRect.Right := ARect.Right;

      ClearLine;
    end;

    try

      { Hit-test }
      if (noCalc in Operations)
        or (noHitTest in Operations) then
      begin

        { Get bounds }
        if Node = ANode then
        begin
          Result.ClientRect := ViewRect;

          { Done. }
          if Operations = [noCalc]
            then Exit;
        end;

        { Hit-test }
        if PtInRect(ViewRect, Point(X, Y)) then
        begin
          Result.Node := Node;
          Result.ClientRect := ViewRect;

          if Operations = [noHitTest] then
          begin
            { Done. }
            Done := True;

            Exit;
          end;
        end;

      end;

      { Paint }
      if noPaint in Operations then
      begin
        Node.View.Options := [];

        if ShowNodeBounds then
        begin
          Node.View.Options := [npShowBounds];
        end;

        if Node = ActiveNode then
        begin
          Node.View.NodeStyle := [ntFocused];
        end;

        Node.View.ClientRect := ViewRect;
        Node.View.Color := BackColor;

        try
          Node.View.Paint;
        finally
//          Node.View.StylePaint := nil;
        end;

        if Node.Level > 1 then
        begin
          EraseBkGnd(Rect(ViewRect.Left, ViewRect.Bottom, ViewRect.Right, Pos.Y + Offset.cy), BackColor);
        end;
      end;

    finally
      Inc(Count);
    end;

    { Child nodes }
    if Node.HasChildren then
    begin
      { Clear after }

      if Node.Level > 1 then
        ClearLine;

      { Reset }
      Count := 0;

      ARect.Top := Pos.Y;

      if Node.Expanded then
      begin

        if Node.Level = 1 then
        begin
          Inc(ARect.Left, Node.BestSize.cx);
        end;

        PadRect := ARect;

        Pos.X := ARect.Left + Node.Padding.Left;

        SetPadding(ARect, Node.Padding);

        Pos.Y := ARect.Top;

        { Child nodes }
        for j := 0 to Pred(Node.ChildNodes.Count) do
        begin
          if Node.Color <> clNone
            then BackColor := Node.Color;

          ProcNode(Node.ChildNodes[j], ARect, Node.LineHeight);

          if Done then Exit;
        end;


      end else
      begin
        Pos.Y := ViewRect.Bottom;
      end;

      { Clear after }
      ClearLine(True);

      if Node.Expanded then Inc(Pos.Y, Node.Padding.Bottom);

      if noPaint in Operations then
      begin

        if Node.Expanded then
        begin
          PadRect.Bottom := Pos.Y;

          FillPadding(Canvas, BackColor, PadRect, Node.Padding);

          if Node.Level = 1 then
          begin
            EraseBkGnd(Rect(ViewRect.Left, ViewRect.Bottom, ViewRect.Right, Pos.Y), BackColor);
          end;
        end;

      end;

    end else { Node.HasChildren }
    begin

      // Bug here
      if noPaint in Operations then
      begin
//        Canvas.Brush.Color := clLime;
//        Canvas.FillRect(Rect(ViewRect.Right, Pos.Y, ClientWidth - Padding.Right, Pos.Y + ViewRect.Bottom));
//        EraseBkGnd(Rect(ViewRect.Right, Pos.Y, ClientWidth - Padding.Right, Pos.Y + ViewRect.Bottom));
      end;

      if Node.Level = 1 then
      begin
        Inc(Pos.Y, ViewRect.Bottom - ViewRect.Top);
      end;

    end;

  end;

begin
  inherited;
  BackColor := Color;

  Result.Node := nil;

  Pos.X := Padding.Left;
  Pos.Y := Padding.Top;

  Cleared := False;

  { Can end with processing? }
  Done := False;

  Canvas.Font.Assign(Font);
//  SetFontQuality(Canvas.Font, NONANTIALIASED_QUALITY);

  Offset.cy := 0;

  { Not clearing first }
  Count := 0;

  { Assign padding }
  PaddingRect := ClientRect;
  SetPadding(PaddingRect, Padding);

  { Top-level nodes }
  for i := 0 to Pred(FNodeList.Count) do
  begin
    if FNodeList.Node[i].Color <> clNone
      then BackColor := FNodeList.Node[i].Color;

    ProcNode(FNodeList.Node[i], PaddingRect, FLineHeight);

    if Done then Break;

    PaddingRect.Top := Pos.Y;

    Pos.X := PaddingRect.Left;
  end;

  with Canvas do
    if noPaint in Operations then
    begin
//      Brush.Color := clRed;
//      FillRect(Rect(Padding.Left, Pos.Y, ClientWidth - Padding.Right, ClientHeight - Padding.Bottom));
      EraseBkGnd(Rect(Padding.Left, Pos.Y, ClientWidth - Padding.Right, ClientHeight - Padding.Bottom));
    end;

end;

procedure TNextLayout6.ReadProperties(const AParent: TNxLayoutNode6);

  procedure ReadNodeList(List: TNxLayoutNodeList);
  var
    i: Integer;
    Node: TNxLayoutNode6;
  begin

    for i := 0 to Pred(List.Count) do
    begin

      Node := List[i];

      try
        Node.State := Node.State + [nsReadingProperty];

        { Get Value }
        FAssociates.GetPropValue(Node.BindProperty, Node);
      finally
        Node.State := Node.State - [nsReadingProperty];
      end;

      if Node.HasChildren then
      begin
        ReadNodeList(Node.ChildNodes);
      end;

    end;

  end;

begin
  if AParent = nil
  then
    ReadNodeList(Nodes)
  else
    ReadNodeList(AParent.ChildNodes);
end;

procedure TNextLayout6.RefreshNode(ANode: TNxLayoutNode6);
var
  R: TRect;
begin
  if Assigned(ANode) then
  begin
    R := ProcNodes([noCalc], 0, 0, ANode).ClientRect;
    InvalidateRect(R);
  end;
end;

procedure TNextLayout6.RefreshNodeFocus(ANode: TNxLayoutNode6);
var
  R: TRect;
begin
  if Assigned(ANode) then
  begin
    R := ProcNodes([noCalc], 0, 0, ANode).ClientRect;

    with ANode do
    begin
      View.ClientRect := R;

      { Adjust }
      R := View.EntityRect[enEdit];

      InflateRect(R, 3, 3);
      InvalidateRect(R);

//      InflateRect(R, -1, -1);
//      ValidateRect(R);
    end;

  end;

end;

procedure TNextLayout6.SetActiveNode(const Value: TNxLayoutNode6);
var
  T: TNxLayoutNode6;
begin
  if Value <> FActiveNode then
  begin
    T := FActiveNode;

    if Assigned(Value) then
    begin
      if Value <> EditingNode then
      begin
        EndEditing(True);
      end;

      { Assign new }
      if Value.CanFocus then
      begin
        FActiveNode := Value;

        RefreshNodeFocus(FActiveNode);

        //TODO: Add TryEdit Method
        if FActiveNode <> EditingNode then
        begin
          EditNode(FActiveNode);
        end;

      end else FActiveNode := nil;

      if Assigned(T) and T.CanFocus
        then RefreshNodeFocus(T);

    end else
    begin
      { It is nil }
      FActiveNode := Value;

      if Assigned(T) and T.CanFocus
        then RefreshNodeFocus(T);
    end;
  end;

end;

procedure TNextLayout6.SetDesigningNode(const Value: TNxLayoutNode6);
begin
  if Value <> FDesigningNode then
  begin
    FDesigningNode := Value;
    Invalidate;
  end;
  NotifyDesigning(FDesigningNode);
end;

procedure TNextLayout6.SetHoverNode(const Value: TNxLayoutNode6);
var
  T: TNxLayoutNode6;
begin
  if Value <> FHoverNode then
  begin
    T := FHoverNode;
    FHoverNode := Value;

    { Hint }
    DeactivateHint('HoverNode');

    if Assigned(FHoverNode)
      then ActivateHint(FHoverLocation, FHoverNode.Hint, 'HoverNode');

    if Assigned(T) then
    begin
      GetNodeRect(T);
      T.View.MouseLeave;
    end;
  end;
end;

procedure TNextLayout6.SetImages(const Value: TImageList);
begin
  if Value <> FImages then
  begin
    FImages := Value;
    Invalidate;
  end;
end;

procedure TNextLayout6.SetLineHeight(const Value: Integer);
begin
  if Value <> FLineHeight then
  begin
    FLineHeight := Value;
    Invalidate;
  end;
end;

procedure TNextLayout6.SetShowNodeBounds(const Value: Boolean);
begin
  if Value <> FShowNodeBounds then
  begin
    FShowNodeBounds := Value;
    Invalidate;
  end;
end;

procedure TNextLayout6.SetStyle(const Value: TNxAppearanceStyle);
begin
  if Value <> Style then
  begin
    FStyle := Value;
    Invalidate;
  end;
end;

procedure TNextLayout6.WMActivateApp(var Message: TWMActivateApp);
begin
  inherited;

end;

procedure TNextLayout6.WMGetDlgCode(var Message: TWMGetDlgCode);
var
  SkipTab: Boolean;
begin
  inherited;
  Message.Result := Message.Result or DLGC_WANTCHARS or DLGC_WANTARROWS;
  SkipTab := False;
  if WantTabs and not SkipTab then Message.Result := Message.Result or DLGC_WANTTAB
  else Message.Result := Message.Result and not DLGC_WANTTAB;
end;

///<see>CMFocusChanged</see>
procedure TNextLayout6.WMKillFocus(var Message: TWMSetFocus);
begin
  inherited;
end;

procedure TNextLayout6.WMSetFocus(var Message: TWMSetFocus);
begin
  inherited;

end;

procedure TNextLayout6.WMSize(var Message: TWMSize);
begin
  inherited;

end;

procedure TNextLayout6.WMVScroll(var Message: TWMVScroll);
begin
  inherited;

end;

end.
