Tulip.UI.Controls.pas

Source Files

{******************************************************************************}
{                                                                              }
{                        Tulip - User Interface Library                        }
{                                                                              }
{         Copyright(c) 2012 - 2013 Marcos Gomes. All rights Reserved.          }
{                                                                              }
{  --------------------------------------------------------------------------  }
{                                                                              }
{  This product is based on Asphyre Sphinx (c) 2000 - 2012  Yuriy Kotsarenko.  }
{       All rights reserved. Official web site: http://www.afterwarp.net       }
{                                                                              }
{******************************************************************************}
{                                                                              }
{  Important Notice:                                                           }
{                                                                              }
{  If you modify/use this code or one of its parts either in original or       }
{  modified form, you must comply with Mozilla Public License Version 2.0,     }
{  including section 3, "Responsibilities". Failure to do so will result in    }
{  the license breach, which will be resolved in the court. Remember that      }
{  violating author's rights either accidentally or intentionally is           }
{  considered a serious crime in many countries. Thank you!                    }
{                                                                              }
{  !! Please *read* Mozilla Public License 2.0 document located at:            }
{  http://www.mozilla.org/MPL/                                                 }
{                                                                              }
{  --------------------------------------------------------------------------  }
{                                                                              }
{  The contents of this file are subject to the Mozilla Public License         }
{  Version 2.0 (the "License"); you may not use this file except in            }
{  compliance with the License. You may obtain a copy of the License at        }
{  http://www.mozilla.org/MPL/                                                 }
{                                                                              }
{  Software distributed under the License is distributed on an "AS IS"         }
{  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the     }
{  License for the specific language governing rights and limitations          }
{  under the License.                                                          }
{                                                                              }
{  The Original Code is Tulip.UI.Controls.pas.                                 }
{                                                                              }
{  The Initial Developer of the Original Code is Marcos Gomes.                 }
{  Portions created by Marcos Gomes are Copyright (C) 2012, Marcos Gomes.      }
{  All Rights Reserved.                                                        }
{                                                                              }
{******************************************************************************}
{                                                                              }
{  Tulip.UI.Controls.pas                                Modified: 23-Mar-2013  }
{  --------------------------------------------------------------------------  }
{                                                                              }
{             Base Implementations for Controls and ControlManager             }
{                                                                              }
{                                Version 1.03                                  }
{                                                                              }
{******************************************************************************}

unit Tulip.UI.Controls;

interface

uses
  Winapi.Windows, System.SysUtils, System.Classes,
  // Asphyre units
  AbstractDevices, AbstractCanvas, AsphyreArchives, AsphyreFonts, AsphyreImages,
  AsphyreTypes,
  // Tulip UI Units
  Tulip.UI.Types, Tulip.UI.Classes, Tulip.UI.Utils, Tulip.UI.Helpers;

type
{$REGION 'Forward declarations'}
  TAControl = class;
  TWControl = class;
  TCustomManager = class;
{$ENDREGION}
{$REGION 'TAControl'}

  TAControl = class(TComponent)
  private
    FControlManager: Pointer;
    FControlState: TControlState;
    FLeft: Integer;
    FTop: Integer;
    FWidth: Integer;
    FHandle: Pointer;
    FHeight: Integer;
    FEnabled: Boolean;
    FVisible: Boolean;
    FParent: Pointer;

    FOnClick: TNotifyEvent;
    FOnDblClick: TNotifyEvent;
    FOnMouseLeave: TNotifyEvent;
    FOnMouseEnter: TNotifyEvent;
    FOnMouseDown: TMouseEvent;
    FOnMouseMove: TMouseMoveEvent;
    FOnMouseUp: TMouseEvent;
    FOnMouseWheel: TMouseWheelEvent;
    FOnMouseWheelDown: TMouseWheelUpDownEvent;
    FOnMouseWheelUp: TMouseWheelUpDownEvent;
    FOnResize: TNotifyEvent;

    FWheelAccumulator: Integer;

    function GetControlManager: TCustomManager;
    function GetParent: TWControl;

    procedure SetVisible(Value: Boolean);
    procedure SetZOrderPosition(Position: Integer);
  protected
    function GetClientLeft: Integer; virtual;
    function GetClientRect: TRect; virtual;
    function GetClientTop: Integer; virtual;
    function GetEnabled: Boolean; virtual;
    function GetHandle: TWControl; virtual;

    procedure AssignTo(Dest: TPersistent); override;

    procedure Paint; dynamic; abstract;
    procedure ReadState(Reader: TReader); override;
    procedure Resize; dynamic;

    procedure SetControlManager(AControlManager: TCustomManager); virtual;
    procedure SetEnabled(Value: Boolean); virtual;
    procedure SetHeight(Value: Integer); virtual;
    procedure SetLeft(Value: Integer); virtual;
    procedure SetParent(AParent: TWControl); virtual;
    procedure SetParentComponent(Value: TComponent); override;
    procedure SetTop(Value: Integer); virtual;
    procedure SetWidth(Value: Integer); virtual;
    procedure SetZOrder(TopMost: Boolean); dynamic;
  public
    procedure Click; dynamic;
    procedure DblClick; dynamic;
    procedure MouseEnter; dynamic;
    procedure MouseLeave; dynamic;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer); dynamic;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); dynamic;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer); dynamic;
    function MouseWheel(Shift: TShiftState; WheelDelta: Integer;
      MousePos: TPoint): Boolean; dynamic;
    function MouseWheelDown(Shift: TShiftState; MousePos: TPoint)
      : Boolean; dynamic;
    function MouseWheelUp(Shift: TShiftState; MousePos: TPoint)
      : Boolean; dynamic;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    function GetParentComponent: TComponent; override;
    function IsVisible: Boolean; virtual;
    function HasParent: Boolean; override;

    procedure Assign(Source: TPersistent); override;
    procedure BringToFront;
    procedure SendToBack;
    procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); virtual;

    property ClientLeft: Integer read GetClientLeft;
    property ClientTop: Integer read GetClientTop;
    property ClientRect: TRect read GetClientRect;
    property ControlManager: TCustomManager read GetControlManager;
    property ControlState: TControlState read FControlState write FControlState;
    property Handle: TWControl read GetHandle;
    property OnClick: TNotifyEvent read FOnClick write FOnClick;
    property OnDblClick: TNotifyEvent read FOnDblClick write FOnDblClick;
    property OnMouseDown: TMouseEvent read FOnMouseDown write FOnMouseDown;
    property OnMouseEnter: TNotifyEvent read FOnMouseEnter write FOnMouseEnter;
    property OnMouseLeave: TNotifyEvent read FOnMouseLeave write FOnMouseLeave;
    property OnMouseMove: TMouseMoveEvent read FOnMouseMove write FOnMouseMove;
    property OnMouseUp: TMouseEvent read FOnMouseUp write FOnMouseUp;
    property OnMouseWheel: TMouseWheelEvent read FOnMouseWheel
      write FOnMouseWheel;
    property OnMouseWheelDown: TMouseWheelUpDownEvent read FOnMouseWheelDown
      write FOnMouseWheelDown;
    property OnMouseWheelUp: TMouseWheelUpDownEvent read FOnMouseWheelUp
      write FOnMouseWheelUp;
    property OnResize: TNotifyEvent read FOnResize write FOnResize;
    property Parent: TWControl read GetParent;
    property WheelAccumulator: Integer read FWheelAccumulator
      write FWheelAccumulator;
  published
    property Enabled: Boolean read GetEnabled write SetEnabled;
    property Height: Integer read FHeight write SetHeight;
    property Left: Integer read FLeft write SetLeft;
    property Top: Integer read FTop write SetTop;
    property Width: Integer read FWidth write SetWidth;
    property Visible: Boolean read FVisible write SetVisible;
  end;

  TAControlClass = class of TAControl;
{$ENDREGION}
{$REGION 'TWControl'}

  TWControl = class(TAControl)
  private
    FControls: TList;
    FTabList: TList;
    FTabStop: Boolean;
    FWControls: TList;

    FOnEnter: TNotifyEvent;
    FOnExit: TNotifyEvent;
    FOnKeyDown: TKeyEvent;
    FOnKeyPress: TKeyPressEvent;
    FOnKeyUp: TKeyEvent;

    function GetControl(Index: Integer): TAControl;
    function GetControlCount: Integer;
    function GetTabOrder: TTabOrder;

    procedure Insert(AControl: TAControl);
    procedure Remove(AControl: TAControl);
    procedure SetTabOrder(Value: TTabOrder);
    procedure SetTabStop(Value: Boolean);
    procedure SetZOrderPosition(Position: Integer);
    procedure UpdateTabOrder(Value: TTabOrder);
  protected
    function FindNextControl(CurControl: TWControl;
      GoForward, CheckTabStop, CheckParent: Boolean): TWControl;
    function GetChildOwner: TComponent; override;

    procedure AssignTo(Dest: TPersistent); override;
    procedure Paint; override;
    procedure SetChildOrder(Child: TComponent; Order: Integer); override;
    procedure SetZOrder(TopMost: Boolean); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    function CanFocus: Boolean; dynamic;
    function ControlAtPos(const Pos: TPoint; AllowDisabled: Boolean;
      AllowWControls: Boolean = False; AllLevels: Boolean = False): TAControl;
    function FindChildControl(const ControlName: string;
      AllLevels: Boolean = False): TAControl;
    function GetControls: TList;
    function GetWControls: TList;

    procedure DoEnter; dynamic;
    procedure DoExit; dynamic;
    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
    procedure GetTabOrderList(List: TList); dynamic;
    procedure InsertControl(AControl: TAControl);
    procedure KeyDown(var Key: Word; Shift: TShiftState); dynamic;
    procedure KeyUp(var Key: Word; Shift: TShiftState); dynamic;
    procedure KeyPress(var Key: Char); dynamic;
    procedure RemoveControl(AControl: TAControl);
    procedure SelectFirst;
    procedure SelectNext(CurControl: TWControl;
      GoForward, CheckTabStop: Boolean);
    procedure SetFocus; virtual;

    property Controls[Index: Integer]: TAControl read GetControl;
    property ControlCount: Integer read GetControlCount;
    property OnEnter: TNotifyEvent read FOnEnter write FOnEnter;
    property OnExit: TNotifyEvent read FOnExit write FOnExit;
    property OnKeyDown: TKeyEvent read FOnKeyDown write FOnKeyDown;
    property OnKeyPress: TKeyPressEvent read FOnKeyPress write FOnKeyPress;
    property OnKeyUp: TKeyEvent read FOnKeyUp write FOnKeyUp;
  published
    property TabOrder: TTabOrder read GetTabOrder write SetTabOrder;
    property TabStop: Boolean read FTabStop write SetTabStop;
  end;

  TWControlClass = class of TWControl;
{$ENDREGION}
{$REGION 'TWRoot'}

  TWRoot = class(TWControl)
  private
    FFonts: TAStringList;
    FImages: TAStringList;

    procedure SetFonts(Value: TAStringList);
    procedure SetImages(Value: TAStringList);
  protected
    procedure AssignTo(Dest: TPersistent); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Fonts: TAStringList read FFonts write SetFonts;
    property Images: TAStringList read FImages write SetImages;
  end;
{$ENDREGION}
{$REGION 'TCustomManager'}

  TCustomManager = class(TObject)
  private
    FDevice: TAsphyreDevice;
    FCanvas: TAsphyreCanvas;
    FImages: TAsphyreImages;
    FFonts: TAsphyreFonts;

    FActiveControl: TWControl;
    FPrevControl: TAControl;
    FRoot: TWRoot;
    FActive: Boolean;
    FLoading: Boolean;
    FDesign: Boolean;

    FOwnerClick: TNotifyEvent;
    FOwnerDblClick: TNotifyEvent;
    FOwnerKeyDown: TKeyEvent;
    FOwnerKeyPress: TKeyPressEvent;
    FOwnerKeyUp: TKeyEvent;
    FOwnerMouseLeave: TNotifyEvent;
    FOwnerMouseEnter: TNotifyEvent;
    FOwnerMouseDown: TMouseEvent;
    FOwnerMouseUp: TMouseEvent;
    FOwnerMouseMove: TMouseMoveEvent;
    FOwnerMouseWheel: TMouseWheelEvent;
    FOwnerMouseWheelDown: TMouseWheelUpDownEvent;
    FOwnerMouseWheelUp: TMouseWheelUpDownEvent;
    FOwnerShortCut: TShortCutEvent;

    procedure SetActiveControl(const Control: TWControl);
    procedure SetDesign(const Value: Boolean);
    procedure SetLoading(const Value: Boolean);

  protected
    FParent: TComponent;

    procedure SetParent(const AParent: TComponent); virtual;

    property OwnerClick: TNotifyEvent read FOwnerClick write FOwnerClick;
    property OwnerDblClick: TNotifyEvent read FOwnerDblClick
      write FOwnerDblClick;
    property OwnerKeyDown: TKeyEvent read FOwnerKeyDown write FOwnerKeyDown;
    property OwnerKeyPress: TKeyPressEvent read FOwnerKeyPress
      write FOwnerKeyPress;
    property OwnerKeyUp: TKeyEvent read FOwnerKeyUp write FOwnerKeyUp;
    property OwnerMouseEnter: TNotifyEvent read FOwnerMouseEnter
      write FOwnerMouseEnter;
    property OwnerMouseLeave: TNotifyEvent read FOwnerMouseLeave
      write FOwnerMouseLeave;

    property OwnerMouseDown: TMouseEvent read FOwnerMouseDown
      write FOwnerMouseDown;
    property OwnerMouseUp: TMouseEvent read FOwnerMouseUp write FOwnerMouseUp;
    property OwnerMouseMove: TMouseMoveEvent read FOwnerMouseMove
      write FOwnerMouseMove;

    property OwnerMouseWheel: TMouseWheelEvent read FOwnerMouseWheel
      write FOwnerMouseWheel;
    property OwnerMouseWheelDown: TMouseWheelUpDownEvent
      read FOwnerMouseWheelDown write FOwnerMouseWheelDown;
    property OwnerMouseWheelUp: TMouseWheelUpDownEvent read FOwnerMouseWheelUp
      write FOwnerMouseWheelUp;
    property OwnerShortCut: TShortCutEvent read FOwnerShortCut
      write FOwnerShortCut;
  public
    constructor Create(const AOwner: TComponent; const ADevice: TAsphyreDevice;
      const ACanvas: TAsphyreCanvas); virtual;

    destructor Destroy; override;

    function LoadFromArchive(const Archive: TAsphyreArchive): Boolean;
    function LoadFromArchiveFile(const FileName: string): Boolean;

    function SaveToArchive(const Archive: TAsphyreArchive): Boolean;
    function SaveToArchiveFile(const FileName: string): Boolean;

    procedure Render; // Render all Controls.
    procedure Clear;

    property Active: Boolean read FActive write FActive;
    property ActiveControl: TWControl read FActiveControl
      write SetActiveControl;
    property PreviousControl: TAControl read FPrevControl write FPrevControl;

    property DesignMode: Boolean read FDesign write SetDesign;
    property Loading: Boolean read FLoading write SetLoading;

    property Root: TWRoot read FRoot;
    // The root container that contains all the components

    property Device: TAsphyreDevice read FDevice write FDevice;
    property Canvas: TAsphyreCanvas read FCanvas write FCanvas;
    property Fonts: TAsphyreFonts read FFonts write FFonts;
    property Images: TAsphyreImages read FImages;
    property Parent: TComponent read FParent write SetParent;
  end;

  TCustomManagerClass = class of TCustomManager;
{$ENDREGION}

var
  VirtualPoint: TPoint;

implementation

{$REGION 'TAControl'}
  { TAControl }

procedure TAControl.Assign(Source: TPersistent);
begin
  ControlState := ControlState + [csReadingState];
  inherited;
  ControlState := ControlState - [csReadingState];
end;

procedure TAControl.AssignTo(Dest: TPersistent);
begin
  if Dest is TAControl then
    with TAControl(Dest) do
    begin
      // inherited properties
      try
        Name := Self.Name;
      except
        on EComponentError do
        begin
        end;
      end;
      Tag := Self.Tag;

      Enabled := Self.Enabled;
      Height := Self.Height;
      Left := Self.Left;
      OnClick := Self.OnClick;
      OnDblClick := Self.OnDblClick;
      OnMouseLeave := Self.OnMouseLeave;
      OnMouseEnter := Self.OnMouseEnter;
      OnMouseDown := Self.OnMouseDown;
      OnMouseMove := Self.OnMouseMove;
      OnMouseUp := Self.OnMouseUp;
      OnMouseWheel := Self.OnMouseWheel;
      OnMouseWheelDown := Self.OnMouseWheelDown;
      OnMouseWheelUp := Self.OnMouseWheelUp;
      OnResize := Self.OnResize;
      Top := Self.Top;
      WheelAccumulator := Self.WheelAccumulator;
      Width := Self.Width;
      Visible := Self.Visible;
    end
  else
    inherited AssignTo(Dest);

end;

procedure TAControl.BringToFront;
begin
  SetZOrder(True);
end;

procedure TAControl.Click;
var
  BoundsRect: TRect;
begin
  BoundsRect := Rect(ClientLeft, ClientTop, ClientLeft + Width,
    ClientTop + Height);

  if (PtInRect(BoundsRect, VirtualPoint)) then
    if Assigned(FOnClick) then
      FOnClick(Self);
end;

constructor TAControl.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  FLeft := 0;
  FTop := 0;
  FWidth := 0;
  FHeight := 0;
  FHandle := Self;
  FEnabled := True;
  FVisible := True;

  if (AOwner <> nil) and (AOwner <> Self) and (AOwner is TWControl) then
  begin
    TWControl(AOwner).InsertControl(Self);
    SetControlManager(TWControl(AOwner).ControlManager);
  end;
end;

procedure TAControl.DblClick;
var
  BoundsRect: TRect;
begin
  BoundsRect := Rect(ClientLeft, ClientTop, ClientLeft + Width,
    ClientTop + Height);

  if (PtInRect(BoundsRect, VirtualPoint)) then
    if Assigned(FOnDblClick) then
      FOnDblClick(Self);
end;

destructor TAControl.Destroy;
begin

  inherited;
end;

function TAControl.GetClientLeft: Integer;
var
  Temp: TWControl;
begin
  Temp := Parent;
  Result := FLeft;
  while Temp <> nil do
  begin
    Result := Result + Temp.FLeft;
    Temp := Temp.Parent;
  end;
end;

function TAControl.GetClientRect: TRect;
begin
  Result.Left := 0;
  Result.Top := 0;
  Result.Right := Width;
  Result.Bottom := Height;
end;

function TAControl.GetClientTop: Integer;
var
  Temp: TWControl;
begin
  Temp := Parent;
  Result := FTop;
  while Temp <> nil do
  begin
    Result := Result + Temp.FTop;
    Temp := Temp.Parent;
  end;
end;

function TAControl.GetControlManager: TCustomManager;
begin
  Result := FControlManager;
end;

function TAControl.GetEnabled: Boolean;
begin
  Result := FEnabled;
end;

function TAControl.GetHandle: TWControl;
begin
  Result := FHandle;
end;

function TAControl.GetParent: TWControl;
begin
  Result := FParent;
end;

function TAControl.GetParentComponent: TComponent;
begin
  Result := FParent;
end;

function TAControl.HasParent: Boolean;
begin
  Result := FParent <> nil;
end;

function TAControl.IsVisible: Boolean;
var
  Temp: TAControl;
begin
  Temp := Self;
  Result := FVisible;
  while Temp.Parent <> nil do
  begin
    Result := (Result and Temp.FVisible);
    Temp := Temp.Parent;
  end;
end;

procedure TAControl.MouseDown(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
begin
  Include(FControlState, csClicked);
  // get the down point
  VirtualPoint.X := X;
  VirtualPoint.Y := Y;

  if ControlManager.ActiveControl <> Self then
  begin
    // Bring Handle to front and set focus
    Handle.BringToFront;

    if ControlManager.ActiveControl <> nil then
    begin
      if (Self is TWControl) then
      begin
        if TWControl(Self).FindChildControl(ControlManager.ActiveControl.Name) = nil
        then
        begin
          TWControl(Self).SetFocus;
          TWControl(Self).SelectFirst;
        end;
      end
      else
      begin
        if Handle.FindChildControl(ControlManager.ActiveControl.Name) = nil then
        begin
          Handle.SetFocus;
          Handle.SelectFirst;
        end;
      end;
    end
    else
    begin
      Handle.SetFocus;
      Handle.SelectFirst;
    end;
  end;

  if Assigned(FOnMouseDown) then
    FOnMouseDown(Self, Button, Shift, X, Y);
end;

procedure TAControl.MouseEnter;
begin
  if Assigned(FOnMouseEnter) then
    FOnMouseEnter(Self);
end;

procedure TAControl.MouseLeave;
begin
  if Assigned(FOnMouseLeave) then
    FOnMouseLeave(Self);
end;

procedure TAControl.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
  // get the current point
  VirtualPoint.X := X;
  VirtualPoint.Y := Y;

  if Assigned(FOnMouseMove) then
    FOnMouseMove(Self, Shift, X, Y);
end;

procedure TAControl.MouseUp(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
begin
  Exclude(FControlState, csClicked);

  // get the release point
  VirtualPoint.X := X;
  VirtualPoint.Y := Y;

  if Assigned(FOnMouseUp) then
    FOnMouseUp(Self, Button, Shift, X, Y);
end;

function TAControl.MouseWheel(Shift: TShiftState; WheelDelta: Integer;
  MousePos: TPoint): Boolean;
var
  IsNeg: Boolean;
begin
  Result := False;
  if Assigned(FOnMouseWheel) then
    FOnMouseWheel(Self, Shift, WheelDelta, MousePos, Result);
  if not Result then
  begin
    Inc(FWheelAccumulator, WheelDelta);
    while Abs(FWheelAccumulator) >= WHEEL_DELTA do
    begin
      IsNeg := FWheelAccumulator < 0;
      FWheelAccumulator := Abs(FWheelAccumulator) - WHEEL_DELTA;
      if IsNeg then
      begin
        if FWheelAccumulator <> 0 then
          FWheelAccumulator := -FWheelAccumulator;
        Result := MouseWheelDown(Shift, MousePos);
      end
      else
        Result := MouseWheelUp(Shift, MousePos);
    end;
  end;
end;

function TAControl.MouseWheelDown(Shift: TShiftState; MousePos: TPoint)
  : Boolean;
begin
  Result := False;
  if Assigned(FOnMouseWheelDown) then
    FOnMouseWheelDown(Self, Shift, MousePos, Result);
end;

function TAControl.MouseWheelUp(Shift: TShiftState; MousePos: TPoint): Boolean;
begin
  Result := False;
  if Assigned(FOnMouseWheelUp) then
    FOnMouseWheelUp(Self, Shift, MousePos, Result);
end;

procedure TAControl.ReadState(Reader: TReader);
begin
  Include(FControlState, csReadingState);
  inherited ReadState(Reader);
  Exclude(FControlState, csReadingState);
end;

procedure TAControl.Resize;
begin
  if Assigned(FOnResize) then
    FOnResize(Self);
end;

procedure TAControl.SendToBack;
begin
  SetZOrder(False);
end;

procedure TAControl.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
  if ((ALeft <> FLeft) or (ATop <> FTop) or (AWidth <> FWidth) or
    (AHeight <> FHeight)) then
  begin
    FLeft := ALeft;
    FTop := ATop;
    FWidth := AWidth;
    FHeight := AHeight;
    // if not(csLoading in ComponentState) then
    Resize;
  end;
end;

procedure TAControl.SetControlManager(AControlManager: TCustomManager);
begin
  FControlManager := AControlManager;
end;

procedure TAControl.SetEnabled(Value: Boolean);
begin
  if FEnabled <> Value then
  begin
    FEnabled := Value;
  end;
end;

procedure TAControl.SetHeight(Value: Integer);
begin
  SetBounds(FLeft, FTop, FWidth, Value);
end;

procedure TAControl.SetLeft(Value: Integer);
begin
  SetBounds(Value, FTop, FWidth, FHeight);
end;

procedure TAControl.SetParent(AParent: TWControl);
begin
  if FParent <> AParent then
  begin
    if AParent = Self then
      Exit;
    if FParent <> nil then
      Parent.RemoveControl(Self);
    if AParent <> nil then
    begin
      AParent.InsertControl(Self);
    end;
  end;
end;

procedure TAControl.SetParentComponent(Value: TComponent);
begin
  if (Parent <> Value) and (Value is TWControl) then
    SetParent(TWControl(Value));
end;

procedure TAControl.SetTop(Value: Integer);
begin
  SetBounds(FLeft, Value, FWidth, FHeight);
end;

procedure TAControl.SetVisible(Value: Boolean);
begin
  if FVisible <> Value then
  begin
    FVisible := Value;

    // Execute events that possible haven't been executed yet.
    if Value = False then
    begin
      MouseLeave;
      if Self is TWControl then
        TWControl(Self).DoExit;
    end;
  end;
end;

procedure TAControl.SetWidth(Value: Integer);
begin
  SetBounds(FLeft, FTop, Value, FHeight);
end;

procedure TAControl.SetZOrder(TopMost: Boolean);
var
  AParent: TWControl;
begin
  AParent := Parent;

  if AParent <> nil then
  begin
    if TopMost then
    begin
      SetZOrderPosition(AParent.FControls.Count - 1)
    end
    else
      SetZOrderPosition(0);
  end;
end;

procedure TAControl.SetZOrderPosition(Position: Integer);
var
  I, Count: Integer;
  AParent: TWControl;
begin
  AParent := Parent;

  if AParent <> nil then
  begin
    I := AParent.FControls.IndexOf(Self);
    if I >= 0 then
    begin
      Count := AParent.FControls.Count;
      if Position < 0 then
        Position := 0;
      if Position >= Count then
        Position := Count - 1;
      if Position <> I then
      begin
        AParent.FControls.Delete(I);
        AParent.FControls.Insert(Position, Self);
      end;
    end;
  end;
end;
{$ENDREGION}
{$REGION 'TWControl'}
{ TWControl }

procedure TWControl.AssignTo(Dest: TPersistent);
var
  I: Integer;
  AClass: TAControlClass;
  Control: TAControl;
begin
  inherited AssignTo(Dest);

  if Dest is TWControl then
    with TWControl(Dest) do
    begin
      TabStop := Self.TabStop;
      OnEnter := Self.OnEnter;
      OnExit := Self.OnExit;
      OnKeyDown := Self.OnKeyDown;
      OnKeyPress := Self.OnKeyPress;
      OnKeyUp := Self.OnKeyUp;

      if Self.ControlCount <> 0 then
      begin
        for I := 0 to Self.ControlCount - 1 do
        begin
          AClass := TAControlClass(Self.Controls[I].ClassType);
          Control := AClass.Create(TWControl(Dest));
          Control.Assign(Self.Controls[I]);
        end;
      end;
    end;
end;

function TWControl.CanFocus: Boolean;
var
  Control: TWControl;
begin
  Result := False;

  Control := Self;
  while Control.Parent <> ControlManager.Root do
  begin
    if not(Control.FVisible and Control.Enabled) then
      Exit;
    Control := Control.Parent;
  end;
  Result := True;

end;

function TWControl.ControlAtPos(const Pos: TPoint;
  AllowDisabled, AllowWControls, AllLevels: Boolean): TAControl;
var
  I: Integer;
  P: TPoint;
  LControl: TAControl;

  function GetControlAtPos(AControl: TAControl): Boolean;
  begin
    with AControl do
    begin
      P := Point(Pos.X - ClientLeft, Pos.Y - ClientTop);
      Result := (PtInRect(ClientRect, P) and
        (IsVisible or (IsVisible and (Enabled or AllowDisabled))))
      { or ((IsVisible or (Parent <> ControlManager.Root)) and
        PtInRect(ClientRect, P) and (ControlManager.DesignMode)) };
      if Result then
        LControl := AControl;
    end;
  end;

begin
  LControl := nil;

  // // do not check for child controls if self control is not visible
  // if (IsVisible = false) {and
  // ((Parent = ControlManager.Root) or (ControlManager.DesignMode = False))} then
  // begin
  // Result := LControl;
  // Exit;
  // end;

  if AllowWControls and (FWControls <> nil) then
    for I := FWControls.Count - 1 downto 0 do
    begin
      if AllLevels then
        if TWControl(FWControls[I]).FWControls <> nil then
          LControl := TWControl(FWControls[I]).ControlAtPos(Pos, AllowDisabled,
            True, True);

      // if found a WControl on Sub Level
      if (LControl <> nil) then
      begin
        Break;
      end;

      // Not found on sub Level, check curent level
      if (LControl = nil) and GetControlAtPos(TWControl(FWControls[I])) then
        Break;

    end;

  // find FControls on result WControl
  if (LControl <> nil) and (LControl is TWControl) then
    if (TWControl(LControl).FControls <> nil) then
      for I := TWControl(LControl).FControls.Count - 1 downto 0 do
        if GetControlAtPos(TWControl(LControl).FControls[I]) then
          Break;

  // if nothing found and has FControls, search in FControls list
  if (FControls <> nil) and (LControl = nil) then
  begin
    for I := FControls.Count - 1 downto 0 do
      if GetControlAtPos(FControls[I]) then
        Break;
  end;

  Result := LControl;
end;

constructor TWControl.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  FTabStop := False;
end;

destructor TWControl.Destroy;
var
  I: Integer;
  Instance: TAControl;
begin
  Destroying;

  I := ControlCount;
  while I <> 0 do
  begin
    Instance := Controls[I - 1];
    Remove(Instance);
    FreeAndNil(Instance);
    I := ControlCount;
  end;

  inherited Destroy;
end;

procedure TWControl.DoEnter;
begin
  if Assigned(FOnEnter) then
    FOnEnter(Self);
end;

procedure TWControl.DoExit;
begin
  if Assigned(FOnExit) then
    FOnExit(Self);
end;

function TWControl.FindChildControl(const ControlName: string;
  AllLevels: Boolean): TAControl;
var
  I: Integer;
begin
  Result := nil;

  if FControls <> nil then
    for I := 0 to FControls.Count - 1 do
      if CompareText(TWControl(FControls[I]).Name, ControlName) = 0 then
      begin
        Result := TAControl(FControls[I]);
        Exit;
      end;

  if FWControls <> nil then
    for I := 0 to FWControls.Count - 1 do
    begin
      if CompareText(TWControl(FWControls[I]).Name, ControlName) = 0 then
      begin
        Result := TAControl(FWControls[I]);
        Exit;
      end;
      if AllLevels = True then
        if TWControl(FWControls[I]).FindChildControl(ControlName, AllLevels) <> nil
        then
        begin
          Result := TWControl(FWControls[I]).FindChildControl(ControlName,
            AllLevels);
          Exit;
        end;
    end;
end;

function TWControl.FindNextControl(CurControl: TWControl;
  GoForward, CheckTabStop, CheckParent: Boolean): TWControl;
var
  I, StartIndex: Integer;
  List: TList;
begin
  Result := nil;
  List := TList.Create;
  try
    GetTabOrderList(List);
    if List.Count > 0 then
    begin
      StartIndex := List.IndexOf(CurControl);
      if StartIndex = -1 then
        if GoForward then
          StartIndex := List.Count - 1
        else
          StartIndex := 0;
      I := StartIndex;
      repeat
        if GoForward then
        begin
          Inc(I);
          if I = List.Count then
            I := 0;
        end
        else
        begin
          if I = 0 then
            I := List.Count;
          Dec(I);
        end;
        CurControl := TWControl(List[I]);
        if CurControl.CanFocus and (not CheckTabStop or CurControl.TabStop) and
          (not CheckParent or (CurControl.Parent = Self)) then
          Result := CurControl;
      until (Result <> nil) or (I = StartIndex);
    end;
  finally
    List.Free;
  end;
end;

function TWControl.GetChildOwner: TComponent;
begin
  Result := Self;
end;

procedure TWControl.GetChildren(Proc: TGetChildProc; Root: TComponent);
var
  I: Integer;
  Control: TAControl;
begin
  for I := 0 to ControlCount - 1 do
  begin
    Control := Controls[I];
    // if Control.Owner = Root then
    Proc(Control);
  end;
end;

function TWControl.GetControl(Index: Integer): TAControl;
var
  N: Integer;
begin
  if FControls <> nil then
    N := FControls.Count
  else
    N := 0;
  if Index < N then
    Result := FControls[Index]
  else
    Result := FWControls[Index - N];
end;

function TWControl.GetControlCount: Integer;
begin
  Result := 0;
  if FControls <> nil then
    Inc(Result, FControls.Count);
  if FWControls <> nil then
    Inc(Result, FWControls.Count);
end;

function TWControl.GetControls: TList;
begin
  Result := FControls;
end;

function TWControl.GetTabOrder: TTabOrder;
begin
  if FParent <> nil then
    Result := Parent.FTabList.IndexOf(Self)
  else
    Result := -1;
end;

procedure TWControl.GetTabOrderList(List: TList);
var
  I: Integer;
  Control: TWControl;
begin
  if FTabList <> nil then
    for I := 0 to FTabList.Count - 1 do
    begin
      Control := TWControl(FTabList[I]);
      List.Add(Control);
      Control.GetTabOrderList(List);
    end;
end;

function TWControl.GetWControls: TList;
begin
  Result := FWControls
end;

procedure TWControl.Insert(AControl: TAControl);
var
  Form: TWControl;
begin
  if AControl <> nil then
  begin
    if AControl is TWControl then
    begin
      ListAdd(FWControls, AControl);
      ListAdd(FTabList, AControl);
    end
    else
      ListAdd(FControls, AControl);
    AControl.FParent := Self;

    // Get the parent Form from Engine.Root and set it as handler
    Form := Self;
    if (ControlManager <> nil) then
    begin
      if not(Form = ControlManager.Root) then
      begin
        while Form.Parent <> ControlManager.Root do
          Form := Form.Parent;
        AControl.FHandle := Form;
      end
      else
      begin
        AControl.FHandle := AControl;
      end;
    end;
  end;

end;

procedure TWControl.InsertControl(AControl: TAControl);
begin
  AControl.ValidateContainer(Self);
  Insert(AControl);
end;

procedure TWControl.KeyDown(var Key: Word; Shift: TShiftState);
begin
  if (Shift = [ssShift, ssCtrl]) or (Shift = [ssShift]) then
  begin
    case Key of
      vk_Tab:
        Self.Handle.SelectNext(Self, False, True);
    end;
  end;

  if (Shift = [ssCtrl]) or (Shift = []) then
  begin
    case Key of
      vk_Tab:
        Self.Handle.SelectNext(Self, True, True);
    end;
  end;

  if Assigned(FOnKeyDown) then
    FOnKeyDown(Self, Key, Shift);
end;

procedure TWControl.KeyPress(var Key: Char);
begin
  if Assigned(FOnKeyPress) then
    FOnKeyPress(Self, Key);
end;

procedure TWControl.KeyUp(var Key: Word; Shift: TShiftState);
begin
  if Assigned(FOnKeyUp) then
    FOnKeyUp(Self, Key, Shift);
end;

procedure TWControl.Paint;
var
  Control, WControl: Integer;
begin
  if FControls <> nil then
  begin
    for Control := 0 to FControls.Count - 1 do
    begin
      with TAControl(FControls[Control]) do
        if (Visible) { or (Self.ControlManager.FDesign) } then
          Paint;
    end;
  end;
  if FWControls <> nil then
  begin
    for WControl := 0 to FWControls.Count - 1 do
    begin
      with TWControl(FWControls[WControl]) do
        if (Visible) { or (Self.ControlManager.FDesign) } then
          Paint;
    end;
  end;
end;

procedure TWControl.Remove(AControl: TAControl);
begin
  if AControl is TWControl then
  begin
    ListRemove(FTabList, AControl);
    ListRemove(FWControls, AControl);
  end
  else
    ListRemove(FControls, AControl);
  AControl.FParent := nil;
  AControl.FHandle := AControl;
end;

procedure TWControl.RemoveControl(AControl: TAControl);
begin
  Remove(AControl);
end;

procedure TWControl.SelectFirst;
var
  Control: TWControl;
begin
  Control := FindNextControl(nil, True, True, False);
  if Control = nil then
    Control := FindNextControl(nil, True, False, False);
  if Control <> nil then
  begin
    Control.SetFocus;
  end;
end;

procedure TWControl.SelectNext(CurControl: TWControl;
  GoForward, CheckTabStop: Boolean);
begin
  CurControl := FindNextControl(CurControl, GoForward, CheckTabStop,
    not CheckTabStop);
  if CurControl <> nil then
    CurControl.SetFocus;
end;

procedure TWControl.SetChildOrder(Child: TComponent; Order: Integer);
begin
  if Child is TWControl then
    TWControl(Child).SetZOrderPosition(Order)
  else if Child is TAControl then
    TAControl(Child).SetZOrderPosition(Order);
end;

procedure TWControl.SetFocus;
var
  Control: TWControl;
begin
  Control := Self;
  if Control.CanFocus then
  begin
    if ControlManager.ActiveControl <> nil then
      if ControlManager.ActiveControl <> Control then
        ControlManager.ActiveControl.DoExit;
    ControlManager.ActiveControl := Control;
    Control.DoEnter;
  end;
end;

procedure TWControl.SetTabOrder(Value: TTabOrder);
begin
  UpdateTabOrder(Value);
end;

procedure TWControl.SetTabStop(Value: Boolean);
begin
  if FTabStop <> Value then
  begin
    FTabStop := Value;
  end;
end;

procedure TWControl.SetZOrder(TopMost: Boolean);
var
  N, M: Integer;
begin
  if FParent <> nil then
  begin
    if TopMost then
      N := Parent.FWControls.Count - 1
    else
      N := 0;
    M := 0;
    if Parent.FControls <> nil then
      M := Parent.FControls.Count;
    SetZOrderPosition(M + N);
  end;
end;

procedure TWControl.SetZOrderPosition(Position: Integer);
var
  I, Count: Integer;
begin
  if FParent <> nil then
  begin
    if Parent.FControls <> nil then
      Dec(Position, Parent.FControls.Count);
    I := Parent.FWControls.IndexOf(Self);
    if I >= 0 then
    begin
      Count := Parent.FWControls.Count;
      if Position < 0 then
        Position := 0;
      if Position >= Count then
        Position := Count - 1;
      if Position <> I then
      begin
        Parent.FWControls.Delete(I);
        Parent.FWControls.Insert(Position, Self);
      end;
    end;
  end;
end;

procedure TWControl.UpdateTabOrder(Value: TTabOrder);
var
  CurIndex, Count: Integer;
begin
  CurIndex := GetTabOrder;
  if CurIndex >= 0 then
  begin
    Count := Parent.FTabList.Count;
    if Value < 0 then
      Value := 0;
    if Value >= Count then
      Value := Count - 1;
    if Value <> CurIndex then
    begin
      Parent.FTabList.Delete(CurIndex);
      Parent.FTabList.Insert(Value, Self);
    end;
  end;
end;
{$ENDREGION}
{$REGION 'TWRoot'}
{ TWRoot }

procedure TWRoot.AssignTo(Dest: TPersistent);
begin
  inherited AssignTo(Dest);

  if Dest is TWRoot then
    with TWRoot(Dest) do
    begin
      Fonts := Self.Fonts;
      Images := Self.Images;
    end;
end;

constructor TWRoot.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  FFonts := TAStringList.Create;
  FImages := TAStringList.Create;
end;

destructor TWRoot.Destroy;
begin
  FFonts.Free;
  FImages.Free;

  inherited;
end;

procedure TWRoot.SetFonts(Value: TAStringList);
begin
  if (Value <> nil) then
    FFonts.Assign(Value);
end;

procedure TWRoot.SetImages(Value: TAStringList);
begin
  if Value <> nil then
    FImages.Assign(Value);
end;

{$ENDREGION}
{$REGION 'TCustomManager'}
{ TCustomManager }

procedure TCustomManager.Clear;
var
  I: Integer;
  Instance: TAControl;
begin
  FActiveControl := nil;
  FPrevControl := nil;

  I := FRoot.ControlCount;
  while I <> 0 do
  begin
    Instance := FRoot.Controls[I - 1];
    FRoot.Remove(Instance);
    FreeAndNil(Instance);
    I := FRoot.ControlCount;
  end;

  // Clear Font and Image Lists
  FRoot.Fonts.Clear;
  FRoot.Images.Clear;

  // free current fonts
  FFonts.RemoveAll;
  FFonts.Images.RemoveAll;
  FFonts.Canvas := FCanvas;

  // free current images
  FImages.RemoveAll;
end;

constructor TCustomManager.Create(const AOwner: TComponent;
  const ADevice: TAsphyreDevice; const ACanvas: TAsphyreCanvas);
begin
  FDevice := ADevice;
  FCanvas := ACanvas;
  FFonts := TAsphyreFonts.Create;
  FFonts.Images := TAsphyreImages.Create;
  FImages := TAsphyreImages.Create;
  Parent := AOwner;

  FRoot := TWRoot.Create(nil);
  FRoot.Left := 0;
  FRoot.Top := 0;

  if ADevice <> nil then
  begin
    FRoot.Width := ADevice.SwapChains.Items[0].Width;
    FRoot.Height := ADevice.SwapChains.Items[0].Height;
  end
  else
  begin
    FRoot.Width := 0;
    FRoot.Height := 0;
  end;

  FRoot.FControlManager := Self;

  FDesign := False;

  FActive := True;
  FLoading := False;
end;

destructor TCustomManager.Destroy;
begin
  FDevice := nil;
  FCanvas := nil;
  FParent := nil;

  FFonts.Images.RemoveAll;
  FFonts.Images.Free;
  FImages.RemoveAll;
  FreeAndNil(FFonts);
  FreeAndNil(FImages);
  FreeAndNil(FRoot);

  inherited Destroy;
end;

function TCustomManager.LoadFromArchive(const Archive: TAsphyreArchive)
  : Boolean;
var
  AControl: TWRoot;
  Stream: TMemoryStream;
  I: Integer;
begin
  // Clear all controls
  Self.Clear;

  Stream := TMemoryStream.Create();

  Result := Archive.ReadMemStream('Interface.ui', Stream);

  if Result = True then
  begin
    try
      AControl := Stream.ReadComponent(nil) as TWRoot;
      Root.Assign(AControl);
    finally
      FreeAndNil(AControl);
    end;

    // Set Device size again
    if FDevice <> nil then
    begin
      FRoot.Width := FDevice.SwapChains.Items[0].Width;
      FRoot.Height := FDevice.SwapChains.Items[0].Height;
    end;

    if FRoot.Fonts.Count > 0 then
    begin
      for I := 0 to FRoot.Fonts.Count - 1 do
        FFonts.InsertFromArchive(FRoot.Fonts.Items[I], Archive, foFonts);
    end;

    if FRoot.Images.Count > 0 then
    begin
      for I := 0 to FRoot.Images.Count - 1 do
        FImages.InsertFromArchive(FRoot.Images.Items[I], Archive, foImages);
    end;
  end;

  Stream.Free();
end;

function TCustomManager.LoadFromArchiveFile(const FileName: string): Boolean;
var
  Media: TAsphyreArchive;
begin
  Media := TAsphyreArchive.Create;

  ArchiveTypeAccess := ataAnyFile;
  Media.OpenMode := aomReadOnly;

  Result := Media.OpenFile(FileName);

  if Result = True then
  begin
    LoadFromArchive(Media);
  end;

  Media.Free;
end;

procedure TCustomManager.Render;
begin
  if FLoading then
    Exit;

  FRoot.Paint;
end;

function TCustomManager.SaveToArchive(const Archive: TAsphyreArchive): Boolean;
var
  Stream: TMemoryStream;
  I: Integer;
begin
  Result := False;

  if Archive = nil then
    Exit;

  if (FFonts.Count > 0) then
  begin
    if not(FFonts.SaveAllToArchive(Archive, foFonts)) then
    begin
      Exit;
    end;

    FRoot.Fonts.Clear;
    for I := 0 to FFonts.Count - 1 do
    begin
      FRoot.Fonts.Add(FFonts.Items[I].Name);
    end;
  end;

  if (FImages.ItemCount > 0) then
  begin
    if not(FImages.SaveAllToArchive(Archive, foImages)) then
    begin
      Exit;
    end;

    FRoot.Images.Clear;
    for I := 0 to FImages.ItemCount - 1 do
    begin
      if not (Assigned(Self.Images[I])) then
      begin
        Continue;
      end;

      FRoot.Images.Add(FImages.Items[I].Name);
    end;
  end;

  Stream := TMemoryStream.Create();
  Stream.WriteComponent(FRoot);

  Result := Archive.WriteRecord('Interface.ui', Stream.Memory,
    Stream.Size, artFile);

  Stream.Free();
end;

function TCustomManager.SaveToArchiveFile(const FileName: string): Boolean;
var
  Media: TAsphyreArchive;
begin
  Media := TAsphyreArchive.Create;

  ArchiveTypeAccess := ataAnyFile;
  Media.OpenMode := aomOverwrite;

  Result := Media.OpenFile(FileName);

  if (Result = True) then
  begin
    SaveToArchive(Media);
  end;

  Media.Free;
end;

procedure TCustomManager.SetActiveControl(const Control: TWControl);
begin
  if FActiveControl <> Control then
    FActiveControl := Control;
end;

procedure TCustomManager.SetDesign(const Value: Boolean);
begin
  if FDesign <> Value then
    FDesign := Value;
end;

procedure TCustomManager.SetLoading(const Value: Boolean);
begin
  if FLoading <> Value then
    FLoading := Value;
end;

procedure TCustomManager.SetParent(const AParent: TComponent);
begin
  if FParent <> AParent then
  begin
    FParent := AParent;
  end;
end;
{$ENDREGION}

initialization

RegisterClasses([TAControl, TWControl, TWRoot]);

finalization

UnRegisterClasses([TAControl, TWControl, TWRoot]);

end.