{********************************************************************}
{                                                                    }
{ written by TMS Software                                            }
{            copyright © 2013                                        }
{            Email : info@tmssoftware.com                            }
{            Web : http://www.tmssoftware.com                        }
{                                                                    }
{ The source code is given as is. The author is not responsible      }
{ for any possible damage done due to the use of this code.          }
{ The complete source code remains property of the author and may    }
{ not be distributed, published, given or sold in any form as such.  }
{ No parts of the source code can be included in any other component }
{ or application without written authorization of the author.        }
{********************************************************************}

unit FMX.TMSTileList;

interface

{$I TMSDEFS.INC}

uses
  Classes, Types, SysUtils, Math, StrUtils, FMX.Types, UITypes, FMX.TMSBaseControl,
  FMX.Objects, FMX.TMSHTMLText, FMX.Controls, FMX.TMSBitmapContainer, FMX.TMSBitmap,
  FMX.Ani, FMX.TMSBadge, FMX.TMSHTMLEngine, FMX.Layouts, FMX.Dialogs, FMX.InertialMovement
  , UIConsts, FMX.Styles , Generics.Collections, FMX.StdCtrls, FMX.Platform
  {$IFDEF DELPHIXE5_LVL}
  , FMX.Graphics
  {$ENDIF}
  ;

const
  MAJ_VER = 1; // Major version nr.
  MIN_VER = 6; // Minor version nr.
  REL_VER = 1; // Release nr.
  BLD_VER = 1; // Build nr.

  // version history
  // v1.0.0.0 : First Release
  // v1.0.0.1 : Improved : TMSIOS define
  // v1.0.0.2 : Repaint bug in XE3
  // v1.0.0.3 : Repaint bug in XE3 in drag/drop tiles
  // v1.0.1.0 : Improved : OnTileAnchorClick event exposed
  // v1.6.0.0 : Fixed : Replacement for TScrollBox issues at designtime / runtime.
  // v1.6.1.0 : LiveBinding support for Notes
  // v1.6.1.1 : Fixed : Issue with badge

type
  TTMSFMXTileListOption = (loBitmap, loCaption, loNotes, loContent, loDetail,
    loDetailCaption, loDetailNotes, loDetailContent, loDetailBitmap);

  TTMSFMXTileListOptions = set of TTMSFMXTileListOption;

  TTMSFMXTileState = (tsNormal, tsDisabled, tsHover, tsDown);

const
  DefaultTileOptions = [loContent, loCaption, loNotes, loDetailCaption, loDetailNotes, loDetailContent];

type

  TTMSFMXScrollBox = class(TLayout)
  private
    FContentChild: TControl;
    FHScrollBar: TScrollBar;
    FContent: TLayout;
  protected
    procedure ScrollTo(DX, DY: Single);
    procedure AddContent(AContent: TControl);
    procedure UpdateScrollBars;
    procedure DoUpdateScrollBars;
    procedure HScrollBarChanged(Sender: TObject);
  public
    procedure Resize; override;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property Content: TLayout read FContent write FContent;
    property HScrollBar: TScrollBar read FHScrollBar write FHScrollBar;
  end;

  TTMSFMXTileList = class;
  TTMSFMXTile = class;

  TTMSFMXClipRectangle = class(TRectangle)
    function GetClipRect: TRectF; override;
  end;

  TTMSFMXTileShape = class(TTMSFMXBaseControl)
  private
    FTile: TTMSFMXTile;
    FFillSelected: TBrush;
    FStrokeSelected: TBrush;
    FSelected: Boolean;
    FTransparent: Boolean;
    FStrokeHover: TBrush;
    FFillDown: TBrush;
    FStrokeDown: TBrush;
    FFillHover: TBrush;
    FState: TTMSFMXTileState;
    procedure SetFillSelected(const Value: TBrush);
    procedure SetSelected(const Value: Boolean);
    procedure SetStrokeSelected(const Value: TBrush);
    procedure SetTransparent(const Value: Boolean);
    procedure SetFillDown(const Value: TBrush);
    procedure SetFillHover(const Value: TBrush);
    procedure SetStrokeDown(const Value: TBrush);
    procedure SetStrokeHover(const Value: TBrush);
    procedure SetState(const Value: TTMSFMXTileState);
  protected
    procedure AnimationProcess(Sender: TObject);
  public
    function ShapeDetail: TControl;
    function ShapeDetailBitmap: TControl;
    function ShapeDetailCaption: TControl;
    function ShapeDetailNotes: TControl;
    function ShapeDetailContent: TControl;
    function ShapeBitmap: TControl;
    function ShapeCaption: TControl;
    function ShapeBadge: TControl;
    function ShapeNotes: TControl;
    function ShapeContent: TControl;
    function ShapeContentWrapper: TControl;
    function ShapeDetailAnimation: TAnimation;
    function ShapeAnimationX: TAnimation;
    function ShapeAnimationY: TAnimation;
    function ShapeAnimationW: TAnimation;
    function ShapeAnimationH: TAnimation;
    property ATile: TTMSFMXTile read FTile write FTile;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Paint; override;
    property State: TTMSFMXTileState read FState write SetState default tsNormal;
  published
    property Selected: Boolean read FSelected write SetSelected default False;
    property Transparent: Boolean read FTransparent write SetTransparent default False;
    property FillSelected: TBrush read FFillSelected write SetFillSelected;
    property StrokeSelected: TBrush read FStrokeSelected write SetStrokeSelected;
    property FillHover: TBrush read FFillHover write SetFillHover;
    property StrokeHover: TBrush read FStrokeHover write SetStrokeHover;
    property FillDown: TBrush read FFillDown write SetFillDown;
    property StrokeDown: TBrush read FStrokeDown write SetStrokeDown;
    property Fill;
    property Stroke;
    property StrokeThickness;
    property StrokeCap;
    property StrokeDash;
    property StrokeJoin;
  end;

  TTMSFMXTileListArrowShapeDirection = (sdLeft, sdRight);

  TTMSFMXTileListBulletShape = class(TEllipse)
  private
    FFillSelected: TBrush;
    FStrokeSelected: TBrush;
    FSelected: Boolean;
    FPageIndex: Integer;
    procedure SetFillSelected(const Value: TBrush);
    procedure SetStrokeSelected(const Value: TBrush);
    procedure SetSelected(const Value: Boolean);
  public
    constructor Create(AOwner: TComponent); override;
    procedure Paint; override;
    destructor Destroy; override;
    property PageIndex: Integer read FPageIndex write FPageIndex;
  published
    property FillSelected: TBrush read FFillSelected write SetFillSelected;
    property StrokeSelected: TBrush read FStrokeSelected write SetStrokeSelected;
    property Selected: Boolean read FSelected write SetSelected default false;
  end;

  TTMSFMXTileListArrowShape = class(TRectangle)
  private
    FDirection: TTMSFMXTileListArrowShapeDirection;
    FArrowFill: TBrush;
    FArrowStroke: TBrush;
    FArrowSize: Single;
    procedure SetDirection(const Value: TTMSFMXTileListArrowShapeDirection);
    procedure SetArrowFill(const Value: TBrush);
    procedure SetArrowStroke(const Value: TBrush);
    procedure SetArrowSize(const Value: Single);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Paint; override;
  published
    property Direction: TTMSFMXTileListArrowShapeDirection read FDirection write SetDirection default sdLeft;
    property ArrowFill: TBrush read FArrowFill write SetArrowFill;
    property ArrowStroke: TBrush read FArrowStroke write SetArrowStroke;
    property ArrowSize: Single read FArrowSize write SetArrowSize;
  end;

  TTilePosItem = class(TPersistent)
  public
    TileIdx: Integer;
    TileSet: Boolean;
    Page: Integer;
    ColSpan, RowSpan, Col, Row: Integer;
    Empty: Boolean;
    R: TRectF;
  end;

  TTMSFMXStyle = class(TCollectionItem)
  private
    FOwner: TTMSFMXTileList;
    FStyleName: String;
    procedure SetStyleName(const Value: String);
  protected
    procedure UpdateTileStyle;
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
  published
    property StyleName: String read FStyleName write SetStyleName;
  end;

  TTMSFMXStyles = class(TTMSFMXBaseCollection)
  private
    FOwner: TTMSFMXTileList;
    FOnChange: TNotifyEvent;
    function GetItem(Index: Integer): TTMSFMXStyle;
    procedure SetItem(Index: Integer; const Value: TTMSFMXStyle);
  protected
    function GetOwner: TPersistent; override;
  public
    procedure Clear;
    constructor Create(AOwner: TTMSFMXTileList);
    function Add: TTMSFMXStyle;
    function Insert(Index: Integer): TTMSFMXStyle;
    property Items[Index: Integer]: TTMSFMXStyle read GetItem write SetItem; default;
  published
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
  end;

  TTMSFMXTile = class(TCollectionItem)
  private
    FOwner: TTMSFMXTileList;
    FRowSpan: Integer;
    FColumnSpan: Integer;
    FEmpty: Boolean;
    FCaption: String;
    FBitmapName: String;
    FNotes: String;
    FCanSelect: Boolean;
    FSelected: Boolean;
    FTransparent: Boolean;
    FBadge: String;
    FTilePosition: TTilePosItem;
    FDetailNotes: String;
    FDetailBitmapName: String;
    FDetailCaption: String;
    FDetailSizePercentage: Single;
    FEnableDetail: Boolean;
    FStyleLookup: String;
    FAffected: Boolean;
    FTag: Integer;
    FReadOnly: Boolean;
    FDataObject: TObject;
    FDataString: String;
    FBitmap: TBitmap;
    FDetailBitmap: TBitmap;
    procedure SetColumnSpan(const Value: Integer);
    procedure SetRowSpan(const Value: Integer);
    procedure SetEmpty(const Value: Boolean);
    procedure SetCaption(const Value: String);
    procedure SetBitmapName(const Value: String);
    procedure SetNotes(const Value: String);
    procedure SetCanSelect(const Value: Boolean);
    procedure SetSelected(const Value: Boolean);
    procedure SetTransparent(const Value: Boolean);
    procedure SetBadge(const Value: String);
    function GetCanSelect: Boolean;
    procedure SetDetailBitmapName(const Value: String);
    procedure SetDetailCaption(const Value: String);
    procedure SetDetailNotes(const Value: String);
    procedure SetDetailSizePercentage(const Value: Single);
    procedure SetEnableDetail(const Value: Boolean);
    procedure SetStyleLookup(const Value: String);
    procedure SetReadOnly(const Value: Boolean);
    function GetState: TTMSFMXTileState;
    procedure SetState(const Value: TTMSFMXTileState);
    procedure SetDataObject(const Value: TObject);
    procedure SetBitmap(const Value: TBitmap);
    procedure SetDetailBitmap(const Value: TBitmap);
    function GetBitmapContainer: TTMSFMXBitmapContainer;
  protected
    procedure BitmapChanged(Sender: TObject);
    procedure DetailBitmapChanged(Sender: TObject);
    function GetStrippedCaption: String;
    procedure UpdateBadge;
    procedure UpdateBitmap;
    procedure UpdateCaption;
    procedure UpdateDetailCaption;
    procedure UpdateNotes;
    procedure UpdateDetailNotes;
    procedure UpdateDetailBitmap;
    property Affected: Boolean read FAffected write FAffected;
  public
    function ShapeDetail: TControl;
    function ShapeDetailBitmap: TControl;
    function ShapeDetailCaption: TControl;
    function ShapeDetailNotes: TControl;
    function ShapeDetailContent: TControl;
    function ShapeBitmap: TControl;
    function ShapeCaption: TControl;
    function ShapeBadge: TControl;
    function ShapeNotes: TControl;
    function ShapeContent: TControl;
    function ShapeContentWrapper: TControl;
    function ShapeDetailAnimation: TAnimation;
    function ShapeAnimationX: TAnimation;
    function ShapeAnimationY: TAnimation;
    function ShapeAnimationW: TAnimation;
    function ShapeAnimationH: TAnimation;

    function TileList: TTMSFMXTileList;
    procedure AnimationDetailProcessing(Sender: TObject);
    procedure AnimationDetailFinishedShowing(Sender: TObject);
    procedure AnimationDetailFinishedHiding(Sender: TObject);
    function AllowDisplay: Boolean;
    function Shape: TTMSFMXTileShape;
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure UpdateTile;
    property TilePosition: TTilePosItem read FTilePosition write FTilePosition;
    procedure ShowDetail;
    procedure HideDetail;
    property State: TTMSFMXTileState read GetState write SetState;
    property BitmapContainer: TTMSFMXBitmapContainer read GetBitmapContainer;
  published
    property Badge: String read FBadge write SetBadge;
    property ColumnSpan: Integer read FColumnSpan write SetColumnSpan default 1;
    property RowSpan: Integer read FRowSpan write SetRowSpan default 1;
    property Empty: Boolean read FEmpty write SetEmpty default false;
    property Transparent: Boolean read FTransparent write SetTransparent default False;
    property Caption: String read FCaption write SetCaption;
    property DataString: String read FDataString write FDataString;
    property DataObject: TObject read FDataObject write SetDataObject;
    property DetailCaption: String read FDetailCaption write SetDetailCaption;
    property DetailNotes: String read FDetailNotes write SetDetailNotes;
    property Notes: String read FNotes write SetNotes;
    property Bitmap: TBitmap read FBitmap write SetBitmap;
    property BitmapName: String read FBitmapName write SetBitmapName;
    property DetailBitmapName: String read FDetailBitmapName write SetDetailBitmapName;
    property DetailBitmap: TBitmap read FDetailBitmap write SetDetailBitmap;
    property CanSelect: Boolean read GetCanSelect write SetCanSelect default True;
    property Selected: Boolean read FSelected write SetSelected default False;
    property DetailSizePercentage: Single read FDetailSizePercentage write SetDetailSizePercentage;
    property EnableDetail: Boolean read FEnableDetail write SetEnableDetail default True;
    property StyleLookup: String read FStyleLookup write SetStyleLookup;
    property Tag: Integer read FTag write FTag;
    property ReadOnly: Boolean read FReadOnly write SetReadOnly default False;
  end;

  TTMSFMXTiles = class(TTMSFMXBaseCollection)
  private
    FOwner: TTMSFMXTileList;
    FOnChange: TNotifyEvent;
    function GetItem(Index: Integer): TTMSFMXTile;
    procedure SetItem(Index: Integer; const Value: TTMSFMXTile);
  protected
    function GetOwner: TPersistent; override;
  public
    procedure Clear;
    constructor Create(AOwner: TTMSFMXTileList);
    function Add: TTMSFMXTile;
    function AddTransparent: TTMSFMXTile;
    function Insert(Index: Integer): TTMSFMXTile;
    property Items[Index: Integer]: TTMSFMXTile read GetItem write SetItem; default;
  published
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
  end;

  TTMSFMXTileStateChanged = procedure(Sender: TObject; ATile: TTMSFMXTile; ATileShape: TControl; AState, AOldState: TTMSFMXTileState) of object;

  TTMSFMXTileListLoadEvent = procedure(Sender: TObject; ATile: TTMSFMXTile; ATileShape: TControl) of object;

  TTMSFMXTileListCustomizeEvent = procedure(Sender: TObject; ATile: TTMSFMXTile; ATileShape: TControl) of object;

  TTMSFMXTileListCustomizeBadgeEvent = procedure(Sender: TObject; ATile: TTMSFMXTile; ATileBadge: TControl) of object;

  TTMSFMXTileListKeyBoardMode = (kmPageNavigation, kmTileNavigation);

  TTMSFMXTileListBulletClickEvent = procedure(Sender: TObject; APageIndex: Integer; ABullet: TControl) of object;

  TTMSFMXTileListBulletCustomizeEvent = procedure(Sender: TObject; PageIndex: Integer; ABullet: TControl) of object;

  TTMSFMXTileClickEvent = procedure(Sender: TObject; ATile: TTMSFMXTile) of object;

  TTMSFMXTileMouseMoveEvent = procedure(Sender: TObject; ATile: TTMSFMXTile; Shift: TShiftState; X, Y: Single) of object;

  TTMSFMXTileMouseUpEvent = procedure(Sender: TObject; ATile: TTMSFMXTile; Button: TMouseButton; Shift: TShiftState; X, Y: Single) of object;

  TTMSFMXTileMouseDownEvent = procedure(Sender: TObject; ATile: TTMSFMXTile; Button: TMouseButton; Shift: TShiftState; X, Y: Single) of object;

  TTMSFMXTileMouseLeaveEvent = procedure(Sender: TObject; ATile: TTMSFMXTile) of object;

  TTMSFMXTileMouseEnterEvent = procedure(Sender: TObject; ATile: TTMSFMXTile) of object;

  TTMSFMXTileHideDetailEvent = procedure(Sender: TObject; ATile: TTMSFMXTile) of object;

  TTMSFMXTileShowDetailEvent = procedure(Sender: TObject; ATile: TTMSFMXTile) of object;

  TTMSFMXTileListFilterEvent = procedure(Sender: TObject; var FilterKey: String; var Allow: Boolean) of object;

  TTMSFMXTileListFilterFinishedEvent = procedure(Sender: TObject; OldFilterKey: String; var NewFilterKey: String; var Allow: Boolean) of object;

  TTMSFMXTileAnchorClickEvent = procedure(Sender: TObject; ATile: TTMSFMXTile; AAnchor: string) of object;

  TTilePos = record
    TileSet: Boolean;
  end;

  TTilePosArray = array of array of TTilePos;

  TTMSFMXTileListFilterMode = (lfmFilter, lfmSearch);

  TTMSFMXTileListFiltering = (lfRandom, lfStart);

  TTMSFMXTileListNavigationMode = (nmScrolling, nmPaging);

  TDisplayList = class(TList<TFMXObject>);

  TTileList = class(TObjectList<TPersistent>);

  [ComponentPlatformsAttribute(TMSPlatforms)]

  TTMSFMXTileList = class(TTMSFMXBaseControl)
  private
    FClearTiles: Boolean;
    FFirstSel: Boolean;
    FSelTile: TTMSFMXTile;
    FLookupKey: String;
    FTimerCount: Integer;
    FTimer: TTimer;
    FFilter: String;
    DragTile: TTMSFMXTile;
    FShiftState: TShiftState;
    FBlockDeselection: Boolean;
    FCurrentPos: Single;
    FAnimationStarted: Boolean;
    FDoAnimation: Boolean;
    FBuilding, FResizing: Boolean;
    FPrevX, FClickX, FClickY, FPrevVal, FDownX, FPrevY: Single;
    FMouseDown, FCheckTile: Boolean;
    FTilePos: Single;
    FPageCount: Integer;
    FDisplayList: TDisplayList;
    FTileList: TTileList;
    FBlockUpdate: Boolean;
    FUpdateCount: Integer;
    FBitmapContainer: TTMSFMXBitmapContainer;
    FTiles: TTMSFMXTiles;
    FRows: Integer;
    FColumns: Integer;
    FVerticalSpacing: Integer;
    FHorizontalSpacing: Integer;
    FOnCustomizeTile: TTMSFMXTileListCustomizeEvent;
    FOnCustomizeTileBadge: TTMSFMXTileListCustomizeBadgeEvent;
    FPageIndex: Integer;
    FOnCustomizeBullet: TTMSFMXTileListBulletCustomizeEvent;
    FOnPreviousPageClick: TNotifyEvent;
    FOnNextPageClick: TNotifyEvent;
    FOnBulletClick: TTMSFMXTileListBulletClickEvent;
    FTileOptions: TTMSFMXTileListOptions;
    FColumnWidth: Single;
    FRowHeight: Single;
    FOnTileClick: TTMSFMXTileClickEvent;
    FMultiSelect: Boolean;
    FKeyBoardMode: TTMSFMXTileListKeyBoardMode;
    FSelectedTileIndex: Integer;
    FReorder: Boolean;
    FFiltering: TTMSFMXTileListFiltering;
    FNavigationMode: TTMSFMXTileListNavigationMode;
    FPageWidth: Single;
    FPageHeight: Single;
    FOnTileMouseLeave: TTMSFMXTileMouseLeaveEvent;
    FOnTileMouseDown: TTMSFMXTileMouseDownEvent;
    FOnTileMouseEnter: TTMSFMXTileMouseEnterEvent;
    FOnTileMouseMove: TTMSFMXTileMouseMoveEvent;
    FOnTileMouseUp: TTMSFMXTileMouseUpEvent;
    FKeyBoardFilter: Boolean;
    FFilterMode: TTMSFMXTileListFilterMode;
    FStyles: TTMSFMXStyles;
    FOnTileHideDetail: TTMSFMXTileHideDetailEvent;
    FOnTileShowDetail: TTMSFMXTileShowDetailEvent;
    FOnLoadTile: TTMSFMXTileListLoadEvent;
    FOnUnloadTile: TTMSFMXTileListLoadEvent;
    FOnFilter: TTMSFMXTileListFilterEvent;
    FOnTileStateChanged: TTMSFMXTileStateChanged;
    FOnFilterFinished: TTMSFMXTileListFilterFinishedEvent;
    FLookupTime: Integer;
    FBindingsCount: Integer;
    FOnTileAnchorClick: TTMSFMXTileAnchorClickEvent;
    procedure SetBitmapContainer(const Value: TTMSFMXBitmapContainer);
    procedure SetTiles(const Value: TTMSFMXTiles);
    procedure SetColumns(const Value: Integer);
    procedure SetRows(const Value: Integer);
    procedure SetHorizontalSpacing(const Value: Integer);
    procedure SetVerticalSpacing(const Value: Integer);
    procedure SetPageIndex(const Value: Integer);
    procedure SetColumnWidth(const Value: Single);
    procedure SetRowHeight(const Value: Single);
    procedure SetMultiSelect(const Value: Boolean);
    procedure SetKeyBoardMode(const Value: TTMSFMXTileListKeyBoardMode);
    procedure SetSelectedTileIndex(const Value: Integer);
    function GetSelectedTileIndex: Integer;
    procedure SetReorder(const Value: Boolean);
    procedure SetFiltering(const Value: TTMSFMXTileListFiltering);
    procedure SetNavigationMode(const Value: TTMSFMXTileListNavigationMode);
    procedure SetPageWidth(const Value: Single);
    procedure SetPageHeight(const Value: Single);
    procedure SetKeyBoardFilter(const Value: Boolean);
    procedure SetFilterMode(const Value: TTMSFMXTileListFilterMode);
    procedure SetStyles(const Value: TTMSFMXStyles);
    procedure SetLookupTime(const Value: Integer);
    function GetVersion: String;
    function GetVersionNr: Integer;
    procedure SetVersion(const Value: String);
  protected
    procedure Loaded; override;
    function CanObserve(const ID: Integer): Boolean; override;
    property BindingsCount: Integer read FBindingsCount write FBindingsCount;
    procedure UpdateTileStyles(AControl: TFMXObject);
    procedure TimerEvent(Sender: TObject);
    procedure DoKeyDown(Sender: TObject; var Key: Word; var KeyChar: WideChar; Shift: TShiftState);
    procedure DoMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
    procedure DoMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
    procedure DoMouseLeaveAction(Sender: TObject);
    procedure DoMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);

    procedure DoTileMouseMove(Sender: TObject; ATile: TTMSFMXTile; Shift: TShiftState; X, Y: Single);
    procedure DoTileMouseUp(Sender: TObject; ATile: TTMSFMXTile; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
    procedure DoTileMouseDown(Sender: TObject; ATile: TTMSFMXTile; Button: TMouseButton; Shift: TShiftState; X, Y: Single);

    procedure DoTileClick(Sender: TObject);
    function XYToTile(X, Y: Single): TTMSFMXTile;

    procedure SetFocusOnBackGround;
    procedure DoEnter; override;
    function GetPageWidth: Single;
    function GetPageHeight: Single;

    procedure AnimationFinished(Sender: TObject);
    procedure AnimationProcessing(Sender: TObject);

    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    function GetInstance: NativeUInt; override;
    procedure UpdateTiles(UpdateContent: Boolean);
    procedure AnimateTiles;
    procedure UpdateContentList(DoAnimation, Animation: Boolean);
    procedure UpdateTilesPos;
    procedure UpdateBullets;
    procedure DoUpdateTiles;
    procedure AffectTiles;
    procedure CleanUpTiles(innerElement: TFmxObject);
    procedure CleanUpStyles(AControl: TFmxObject);
    function DoesTileExist(innerelement: TFmxObject; TileName: String): TControl;
    procedure RightArrowClick(Sender: TObject);
    procedure LeftArrowClick(Sender: TObject);
    procedure BulletClick(Sender: TObject);
    procedure CloneChildren(ATile: TTMSFMXTile; Source, Destination: TFMXObject);
    function CanClone(ATile: TTMSFMXTile; Main, Source: TFMXObject): Boolean;
    procedure ApplyAdditionalStyle(AControl: TFMXObject); override;
    procedure DoTileAnchorClick(Sender: TObject; AAnchor: String);
  public
    function GetLookupKey: String;
    procedure ApplyFilter(AFilter: String; TriggerEvent: Boolean);
    procedure ClearFilter;
    function GetIndexAtPosition(ATile: TTMSFMXTile; X, Y: Single): Integer;
    procedure ClearAllTiles(AShape: TTMSFMXTileShape);
    procedure SelectAllTilesBetween(AStartTileIndex, AStopTileIndex: Integer);
    procedure DeSelectAllTiles(ATile: TTMSFMXTile = nil);
    procedure SelectTiles(ArrTiles: array of integer);
    procedure SelectAllTiles(ATile: TTMSFMXTile = nil);
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure ApplyStyle; override;
    procedure AssignEvents;
    procedure ApplyNavigationMode;
    procedure BeginUpdate; override;
    procedure EndUpdate; override;
    function GetHeader: TControl;
    function GetFooter: TControl;
    function GetContent: TControl;
    function GetScrollContent: TControl;
    function GetHeaderBullet: TControl;
    function GetHeaderBulletList: TControl;
    function GetFooterBulletList: TControl;
    function GetFooterBullet: TControl;
    function GetHeaderArrowLeft: TControl;
    function GetHeaderArrowRight: TControl;
    function GetFooterArrowLeft: TControl;
    function GetFooterArrowRight: TControl;
    function GetContentList: TControl;
    function GetContentListAnim: TAnimation;
    function GetContentListScroll: TControl;
    function GetDefaultDetailTile(Source: TFMXObject): TControl;
    function GetDefaultDetailTileCaption(Source: TFMXObject): TControl;
    function GetDefaultDetailTileNotes(Source: TFMXObject): TControl;
    function GetDefaultDetailTileBitmap(Source: TFMXObject): TControl;
    function GetDefaultDetailTileContent(Source: TFMXObject): TControl;

    function GetDefaultTileByName(AStyleName: String): TControl;
    function GetDefaultTile: TControl;
    function GetDefaultTileCaption(Source: TFMXObject): TControl;
    function GetDefaultTileNotes(Source: TFMXObject): TControl;
    function GetDefaultTileBitmap(Source: TFMXObject): TControl;
    function GetDefaultTileBadge: TControl;
    function GetDefaultTileContent(Source: TFMXObject): TControl;
    function FindTileIndexBy(ARow, AColumn: Integer): Integer;
    function FindNextTileIndexBy(ARow, AColumn, APageIndex: Integer): Integer;
    function FindPreviousTileIndexBy(ARow, AColumn, APageIndex: Integer): Integer;
    procedure FillBulletList(List, Bullet: TControl; AStyleName: String);
    procedure AddTile(innerElement, itemElement: TControl; TileX, TileY, TileW, TileH: Single; ATile: TTMSFMXTile);
    function PageCount: Integer;
    procedure NextPage;
    procedure PreviousPage;
    procedure GoToPage(APageIndex: Integer);
    procedure Resize; override;
  published
    property PageWidth: Single read FPageWidth write SetPageWidth;
    property PageHeight: Single read FPageHeight write SetPageHeight;
    property NavigationMode: TTMSFMXTileListNavigationMode read FNavigationMode write SetNavigationMode default nmPaging;
    property Reorder: Boolean read FReorder write SetReorder default True;
    property ColumnWidth: Single read FColumnWidth write SetColumnWidth;
    property RowHeight: Single read FRowHeight write SetRowHeight;
    property BitmapContainer: TTMSFMXBitmapContainer read FBitmapContainer write SetBitmapContainer;
    property Tiles: TTMSFMXTiles read FTiles write SetTiles;
    property Styles: TTMSFMXStyles read FStyles write SetStyles;
    property Rows: Integer read FRows write SetRows default 3;
    property Columns: Integer read FColumns write SetColumns default 3;
    property VerticalSpacing: Integer read FVerticalSpacing write SetVerticalSpacing default 10;
    property HorizontalSpacing: Integer read FHorizontalSpacing write SetHorizontalSpacing default 10;
    property PageIndex: Integer read FPageIndex write SetPageIndex;
    property TileOptions: TTMSFMXTileListOptions read FTileOptions write FTileOptions default DefaultTileOptions;
    property MultiSelect: Boolean read FMultiSelect write SetMultiSelect default False;
    property KeyBoardMode: TTMSFMXTileListKeyBoardMode read FKeyBoardMode write SetKeyBoardMode default kmPageNavigation;
    property KeyBoardFilter: Boolean read FKeyBoardFilter write SetKeyBoardFilter;
    property SelectedTileIndex: Integer read GetSelectedTileIndex write SetSelectedTileIndex default -1;

    property OnBulletClick: TTMSFMXTileListBulletClickEvent read FOnBulletClick write FOnBulletClick;
    property OnLoadTile: TTMSFMXTileListLoadEvent read FOnLoadTile write FOnLoadTile;
    property OnUnLoadTile: TTMSFMXTileListLoadEvent read FOnUnloadTile write FOnUnloadTile;
    property OnCustomizeTile: TTMSFMXTileListCustomizeEvent read FOnCustomizeTile write FOnCustomizeTile;
    property OnCustomizeTileBadge: TTMSFMXTileListCustomizeBadgeEvent read FOnCustomizeTileBadge write FOnCustomizeTileBadge;
    property OnCustomizeBullet: TTMSFMXTileListBulletCustomizeEvent read FOnCustomizeBullet write FOnCustomizeBullet;
    property OnNextPageClick: TNotifyEvent read FOnNextPageClick write FOnNextPageClick;
    property OnPreviousPageClick: TNotifyEvent read FOnPreviousPageClick write FOnPreviousPageClick;
    property OnTileClick: TTMSFMXTileClickEvent read FOnTileClick write FOnTileClick;
    property Filtering: TTMSFMXTileListFiltering read FFiltering write SetFiltering default lfRandom;
    property FilterMode: TTMSFMXTileListFilterMode read FFilterMode write SetFilterMode default lfmFilter;

    property OnTileMouseDown: TTMSFMXTileMouseDownEvent read FOnTileMouseDown write FOnTileMouseDown;
    property OnTileMouseUp: TTMSFMXTileMouseUpEvent read FOnTileMouseUp write FOnTileMouseUp;
    property OnTileMouseMove: TTMSFMXTileMouseMoveEvent read FOnTileMouseMove write FOnTileMouseMove;
    property OnTileMouseEnter: TTMSFMXTileMouseEnterEvent read FOnTileMouseEnter write FOnTileMouseEnter;
    property OnTileMouseLeave: TTMSFMXTileMouseLeaveEvent read FOnTileMouseLeave write FOnTileMouseLeave;
    property OnTileShowDetail: TTMSFMXTileShowDetailEvent read FOnTileShowDetail write FOnTileShowDetail;
    property OnTileHideDetail: TTMSFMXTileHideDetailEvent read FOnTileHideDetail write FOnTileHideDetail;
    property OnTileAnchorClick: TTMSFMXTileAnchorClickEvent read FOnTileAnchorClick write FOnTileAnchorClick;
    property OnFilter: TTMSFMXTileListFilterEvent read FOnFilter write FOnFilter;
    property OnFilterFinished: TTMSFMXTileListFilterFinishedEvent read FOnFilterFinished write FOnFilterFinished;

    property LookupTime: Integer read FLookupTime write SetLookupTime default 2;

    property OnTileStateChanged: TTMSFMXTileStateChanged read FOnTileStateChanged write FOnTileStateChanged;
    property Version: String read GetVersion write SetVersion;
  end;

implementation

{$R FMX.TMSTileList.res}

{ TTMSFMXTileList }

procedure TTMSFMXTileList.AddTile(innerElement, itemElement: TControl; TileX, TileY, TileW, TileH: Single; ATile: TTMSFMXTile);
var
  sn: String;
  sec, newsec: TControl;
  tile: TTMSFMXTileShape;
  animx, animy, animw, animh: TAnimation;
  detail, cnt: TControl;
  ti: TControl;
  firstload: Boolean;
begin
  if ATile.Empty then
    Exit;

  firstload := false;

  sn := 'FMXTileListItem'+inttostr(ATile.Index);
  sec := DoesTileExist(innerelement, sn);
  if Assigned(sec) and ATile.Affected then
  begin
    FDisplayList.Remove(sec);
    ATile.Affected := False;
    if Assigned(OnUnLoadTile) then
      OnUnLoadTile(Self, ATile, sec);
    innerelement.RemoveObject(sec);
    sec.Free;
    sec := nil;
  end;

  if Assigned(sec) then
  begin
    newsec := sec;
    newsec.BeginUpdate;
  end
  else
  begin
    ti := nil;
    if ATile.StyleLookup <> '' then
      ti := GetDefaultTileByName(ATile.StyleLookup);
    if not Assigned(ti) then
      ti := itemElement;

    if not Assigned(ti) then
      Exit;

    newsec := ti.Clone(innerElement) as TControl;
    newsec.BeginUpdate;
    CloneChildren(ATile, ti, newsec);
    newsec.HitTest := False;
//    newsec.Name := sn + 'Name';
    newsec.Visible := True;

    firstload := True;
  end;

  newsec.SetBounds(TileX, TileY, TileW, TileH);

  newsec.StyleName := sn;

  if (newsec is TTMSFMXTileShape) then
  begin
    tile := (newsec as TTMSFMXTileShape);
    cnt := tile.ShapeContentWrapper;
    if Assigned(cnt) then
      cnt.ClipChildren := True;

    detail := tile.ShapeDetail;
    if Assigned(detail) then
    begin
      detail.BoundsRect := newsec.BoundsRect;
      detail.Position.Y := newsec.Height;
    end;

    tile.ATile := ATile;
    tile.Selected := ATile.Selected;
    tile.Transparent := ATile.Transparent;
    animx := tile.ShapeAnimationX;
    animy := tile.ShapeAnimationY;
    animw := tile.ShapeAnimationX;
    animh := tile.ShapeAnimationY;

    if Assigned(animx) then
    begin
      animx.Duration := 0.8;
      animx.OnProcess := tile.AnimationProcess;
    end;

    if Assigned(animy) then
    begin
      animy.Duration := 0.8;
      animy.OnProcess := tile.AnimationProcess;
    end;

    if Assigned(animw) then
    begin
      animw.Duration := 0.8;
      animw.OnProcess := tile.AnimationProcess;
    end;

    if Assigned(animh) then
    begin
      animh.Duration := 0.8;
      animh.OnProcess := tile.AnimationProcess;
    end;
  end;

  innerElement.InsertObject(innerElement.ChildrenCount, newsec);
  FDisplayList.Add(newsec);
  newsec.EndUpdate;

  ATile.UpdateBitmap;
  ATile.UpdateCaption;
  ATile.UpdateNotes;
  ATile.UpdateDetailBitmap;
  ATile.UpdateDetailCaption;
  Atile.UpdateDetailNotes;
  ATile.UpdateBadge;

  if Assigned(OnLoadTile) and firstload then
    OnLoadTile(Self, Atile, newsec);

  if Assigned(OnCustomizeTile) then
    OnCustomizeTile(Self, ATile, newsec);
end;

procedure TTMSFMXTileList.AffectTiles;
var
  I: Integer;
begin
  for I := 0 to Tiles.Count - 1 do
    Tiles[I].Affected := True;
end;

procedure TTMSFMXTileList.AnimateTiles;
var
  innerelement, content: TControl;
  hs, vs, w, h: Single;
  TileW, TileH, TileX, TileY: Single;
  start, stop: Integer;
  pgidx, I: Integer;
  tilepos: TTilePosItem;
  ATileIndex: Integer;
  ATile: TTMSFMXTile;
  exw, exh: Single;
  animx, animy, animw, animh: TAnimation;
  shp: TTMSFMXTileShape;
  tw: Single;
begin
  innerelement := GetContentList;
  content := GetContent;
  if Assigned(innerElement) and Assigned(content) then
  begin
    tw := GetPageWidth;
    hs := HorizontalSpacing;
    vs := VerticalSpacing;
    if ColumnWidth > 0 then
      TileW := ColumnWidth
    else
    begin
      w := tw - Padding.Right - Padding.Left - vs;
      TileW := w;
      if Columns > 0 then
       TileW := (w - (Columns * hs)) / Columns;
    end;

    if RowHeight > 0 then
      TileH := RowHeight
    else
    begin
      h := GetPageHeight - Padding.Bottom - Padding.Top - vs;
      TileH := h;
      if Rows > 0 then
       TileH := (h - (Rows * vs)) / Rows;
    end;

    if NavigationMode = nmPaging then
    begin
      start := PageIndex - 1;
      stop := PageIndex + 1;
    end
    else
    begin
      start := 0;
      stop := PageCount - 1;
    end;

    for pgidx := start to stop do
    begin
      for I := 0 to FTileList.Count - 1 do
      begin
        tilepos := TTilePosItem(FTileList[I]);

        if Assigned(tilepos) and (tilepos.Page = pgidx) then
        begin
          ATileIndex := tilepos.TileIdx;
          if (ATileIndex >= 0) and (ATileIndex <= Tiles.Count - 1) then
          begin
            ATile := Tiles[ATileIndex];
            if ATile <> DragTile then
            begin
              exw := TileW * tilepos.ColSpan + (vs * (tilepos.ColSpan - 1));
              exh := TileH * tilepos.RowSpan + (hs * (tilepos.RowSpan - 1));
              if NavigationMode = nmPaging then
                TileX := tw + (tw * (pgidx - PageIndex)) + Padding.Left + hs + TileW * tilepos.col + (hs * tilepos.col)
              else
                TileX := (tw * pgidx) + Padding.Left + hs + TileW * tilepos.col + (hs * tilepos.col);

              TileY := Padding.Top + vs + TileH * tilepos.row + (hs * tilepos.row);
              tilepos.R := RectF(TileX, TileY, TileX + exw, TileY + exh);

              shp := ATile.Shape;
              if Assigned(shp) then
              begin
                animx := ATile.Shape.ShapeAnimationX;
                animx.OnProcess := AnimationProcessing;
                animy := ATile.Shape.ShapeAnimationY;
                animy.OnProcess := AnimationProcessing;
                animw := ATile.Shape.ShapeAnimationW;
                animw.OnProcess := AnimationProcessing;
                animh := ATile.Shape.ShapeAnimationH;
                animh.OnProcess := AnimationProcessing;

                if Assigned(animx) then
                begin
                  if (animx is TFloatAnimation) then
                    (animx as TFloatAnimation).StopValue := TileX;

                  animx.Start;
                end;

                if Assigned(animy) then
                begin
                  if (animy is TFloatAnimation) then
                    (animy as TFloatAnimation).StopValue := TileY;

                  animy.Start;
                end;

                if Assigned(animw) then
                begin
                  if (animw is TFloatAnimation) then
                    (animw as TFloatAnimation).StopValue := exw;

                  animw.Start;
                end;

                if Assigned(animh) then
                begin
                  if (animh is TFloatAnimation) then
                    (animh as TFloatAnimation).StopValue := exh;

                  animh.Start;
                end;
              end;
            end;
          end;
        end;
      end;
    end;
  end;
end;

procedure TTMSFMXTileList.AnimationFinished(Sender: TObject);
var
  content, contentlist: TControl;
begin
  FAnimationStarted := False;
  if not FDoAnimation then
    Exit;

  contentlist := GetContentList;
  content := GetContent;
  FTilePos := 0;
  if Assigned(content) and Assigned(contentlist) then
  begin
    contentlist.position.X := -GetPageWidth;
    UpdateTiles(False);
  end;
end;

procedure TTMSFMXTileList.AnimationProcessing(Sender: TObject);
begin
  Repaint;
end;

procedure TTMSFMXTileList.ApplyAdditionalStyle(AControl: TFMXObject);
begin
  inherited;
  UpdateTileStyles(AControl);
end;

procedure TTMSFMXTileList.ApplyFilter(AFilter: String; TriggerEvent: Boolean);
var
  i: integer;
  AllowFilter: Boolean;
begin
  AllowFilter := True;
  if Assigned(OnFilter) and TriggerEvent then
    OnFilter(Self, AFilter, AllowFilter);

  if AllowFilter then
  begin
    FFilter := AFilter;
    DoUpdateTiles;

    if FilterMode = lfmSearch then
    begin
      for I := 0 to Tiles.Count - 1 do
      begin
        if Tiles[i].AllowDisplay and Tiles[I].CanSelect then
        begin
          SelectedTileIndex := i;
          Break;
        end;
      end;
    end;
  end;
end;

procedure TTMSFMXTileList.ApplyNavigationMode;
var
  content, contentscrolllist, contentlist: TControl;
begin
  content := GetContent;
  contentscrolllist := GetScrollContent;
  contentlist := GetContentListScroll;
  if assigned(content) and assigned(contentscrolllist) and assigned(contentlist) then
  begin
    contentscrolllist.CanFocus := False;
    if NavigationMode = nmPaging then
    begin
      content.InsertObject(content.ChildrenCount, contentlist);
      contentscrolllist.Visible := False;
    end
    else
    begin
      contentscrolllist.Visible := True;
      TTMSFMXScrollBox(contentscrolllist).AddContent(contentlist);
    end;
  end;
end;

procedure TTMSFMXTileList.ApplyStyle;
begin
  inherited;
  if FResizing then
    Exit;
  UpdateContentList(false, false);
  ApplyNavigationMode;
  AssignEvents;
  if FBuilding then
    Exit;
  DoUpdateTiles;
end;

procedure TTMSFMXTileList.AssignEvents;
var
  contentlistanim: TAnimation;
  hrl, hrr, frl, frr: TControl;
  content: TControl;
begin
  if NavigationMode = nmPaging then
    content := GetContent
  else
    content := GetScrollContent;

  if Assigned(content) then
  begin
    content.CanFocus := True;
    content.OnMouseLeave := DoMouseLeaveAction;
    content.OnMouseDown := DoMouseDown;
    content.OnKeyDown := DoKeyDown;
    content.OnMouseUp := DoMouseUp;
    content.OnMouseMove := DoMouseMove;
  end;
  contentlistanim := GetContentListAnim;
  if Assigned(contentlistanim) then
  begin
    contentlistanim.Duration := 1.2;
    contentlistanim.OnFinish := AnimationFinished;
  end;

  hrl := GetHeaderArrowLeft;
  hrr := GetHeaderArrowRight;
  frl := GetFooterArrowLeft;
  frr := GetFooterArrowRight;

  if Assigned(hrl) then
    hrl.OnClick := LeftArrowClick;

  if Assigned(hrr) then
    hrr.OnClick := RightArrowClick;

  if Assigned(frl) then
    frl.OnClick := LeftArrowClick;

  if Assigned(frr) then
    frr.OnClick := RightArrowClick;

end;

procedure TTMSFMXTileList.BeginUpdate;
begin
  inherited;
  Inc(FUpdateCount);
end;

procedure TTMSFMXTileList.BulletClick(Sender: TObject);
begin
  if Sender is TTMSFMXTileListBulletShape then
    PageIndex := (Sender as TTMSFMXTileListBulletShape).PageIndex;

  if Assigned(OnBulletClick) then
    OnBulletClick(Self, PageIndex, Sender as TControl);
end;

procedure TTMSFMXTileList.DoEnter;
begin
  inherited;
  SetFocusOnBackGround;
end;

function TTMSFMXTileList.DoesTileExist(innerelement: TFmxObject; TileName: String): TControl;
var
  i: integer;
  ctrl: TFMXObject;
  l: TControl;
begin
  Result := nil;
  if Assigned(innerElement) then
  begin
    for I := 0 to innerElement.ChildrenCount - 1 do
    begin
      ctrl := innerElement.Children[i];
      if ctrl is TControl then
      begin
        l := ctrl as TControl;
        if UpperCase(l.StyleName) = UpperCase(TileName) then
        begin
          Result := l;
          Break;
        end;
      end;
    end;
  end;
end;

procedure TTMSFMXTileList.DoKeyDown(Sender: TObject; var Key: Word;
  var KeyChar: WideChar; Shift: TShiftState);
var
  t: TTMSFMXTile;
  sel: Integer;
  pos: TTilePosItem;
  DoLookup: Boolean;
  lookup: String;
  seltile: Boolean;
begin
  DoLookup := True;
  case Key of
    vkUp, vkLeft, vkRight, vkDown, vkHome, vkEnd, vkNext, vkPrior, vkReturn: DoLookup := False;
  end;

  if DoLookup and KeyBoardFilter then
  begin
    if not FTimer.Enabled then
      FTimer.Enabled := LookupTime > 0;

    FTimerCount := 0;

    lookup := FLookupKey;

    if ((KeyChar >= '0') and (KeyChar <= 'z')) or (Key = vkSpace) then
      FLookupKey := FLookupKey + KeyChar;

    if (Key = vkBack) or (Key = vkDelete) then
    begin
      if Length(FLookupKey) > 0 then
        Delete(FLookupKey, Length(FLookupKey), 1);
    end;

    if lookup <> FLookupKey then
      ApplyFilter(FLookupKey, True);
  end
  else
  begin
    case KeyBoardMode of
      kmPageNavigation:
      begin
        case Key of
          vkLeft, vkUp: PreviousPage;
          vkRight, vkDown: NextPage;
          vkHome: PageIndex := 0;
          vkEnd: PageIndex := PageCount - 1;
          vkNext: PageIndex := PageIndex + 5;
          vkPrior: PageIndex := PageIndex - 5;
        end;
      end;
      kmTileNavigation:
      begin
        sel := -1;
        seltile := not (((ssShift in Shift) or (ssCtrl in Shift)) and MultiSelect);
        t := nil;
        if (SelectedTileIndex >= 0) and (SelectedTileIndex <= Tiles.Count - 1) then
        begin
          t := Tiles[SelectedTileIndex];
          if seltile or not FFirstSel then
          begin
            FSelTile := t;
            FFirstSel := True;
          end;
        end;

        if Assigned(t) then
        begin
          if Assigned(t.TilePosition) then
          begin
            sel := -1;
            case Key of
              vkUp: sel := FindPreviousTileIndexBy(t.TilePosition.Row - 1, t.TilePosition.Col, t.TilePosition.Page);
              vkLeft: sel := FindPreviousTileIndexBy(t.TilePosition.Row, t.TilePosition.Col - 1, t.TilePosition.Page);
              vkRight: sel := FindNextTileIndexBy(t.TilePosition.Row, t.TilePosition.Col + 1, t.TilePosition.Page);
              vkDown: sel := FindNextTileIndexBy(t.TilePosition.Row + 1, t.TilePosition.Col, t.TilePosition.Page);
              vkHome: sel := FindNextTileIndexBy(0, 0, 0);
              vkEnd: sel := FindPreviousTileIndexBy(Rows, Columns, PageCount - 1);
              vkNext: sel := FindNextTileIndexBy(0, 0, t.TilePosition.Page + 1);
              vkPrior: sel := FindNextTileIndexBy(0, 0, t.TilePosition.Page - 1);
            end;

            if sel = -1 then
            begin
              case Key of
                vkUp, vkLeft: sel := FindPreviousTileIndexBy(Rows, Columns, t.TilePosition.Page - 1);
                vkDown, vkRight: sel := FindNextTileIndexBy(0, 0, t.TilePosition.Page + 1);
              end;
            end;
          end;
        end;

        if (sel >= 0) and (sel <= Tiles.Count - 1) then
        begin
          FShiftState := Shift;
          SelectedTileIndex := sel;
          if seltile then
            FSelTile := Tiles[SelectedTileIndex];

          if Assigned(FSelTile) and (sel <> -1) and (ssShift in FShiftState) and MultiSelect then
            SelectAllTilesBetween(sel, FSelTile.Index);

          FShiftState := [];
          pos := Tiles[SelectedTileIndex].TilePosition;
          if Assigned(pos) and Assigned(t) and (pos.Page <> t.TilePosition.Page) then
            PageIndex := pos.Page;
        end;
      end;
    end;
  end;


  case Key of
    vkReturn:
    begin
      if (SelectedTileIndex >= 0) and (SelectedTileIndex <= Tiles.Count - 1) then
      begin
        if Assigned(OnTileClick) then
          OnTileClick(Self, Tiles[SelectedTileIndex]);
      end;
    end;
  end;

end;

procedure TTMSFMXTileList.DoMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
var
  ATile: TTMSFMXTile;
  shp: TTMSFMXTileShape;
begin
  FMouseDown := True;
  FCheckTile := True;
  FPrevX := X;
  FPrevY := Y;
  FClickX := X;
  FClickY := Y;
  FPrevVal := FTilePos;
  FDownX := X;

  ATile := XYToTile(X, Y);
  if Assigned(ATile) then
  begin
    shp := ATile.Shape;
    shp.State := tsDown;
    if Assigned(OnTileMouseDown) then
      OnTileMouseDown(Self, ATile, Button, Shift, X, Y);
  end;
end;

procedure TTMSFMXTileList.DoMouseLeaveAction(Sender: TObject);
begin
  if FMouseDown then
  begin
    FDownX := -1;
    PageIndex := PageIndex;
  end;
  FMouseDown := False;
  FCheckTile := False;
  if FClearTiles then
  begin
    FClearTiles := False;
    Exit;
  end;
  ClearAllTiles(nil);
end;

procedure TTMSFMXTileList.DoMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Single);
var
  ATile: TTMSFMXTile;
  shp, shpdrag: TTMSFMXTileShape;
  newtileidx: Integer;
  contentlist: TControl;
  posx, posy: Single;
begin
  ATile := XYToTile(X, Y);
  shp := nil;
  if Assigned(ATile) then
    shp := ATile.Shape;

  if FMouseDown and ((X < FClickX - 3) or (X > FClickX + 3) or (Y < FClickY - 3) or (Y > FClickY + 3)) then
  begin
    if Assigned(ATile) and ATile.Selected and Reorder then
    begin
      DragTile := ATile;
      if Assigned(shp) then
        shp.BringToFront;
    end;
    if Assigned(DragTile) then
    begin
      shpdrag := DragTile.Shape;
      if Assigned(shpdrag) then
      begin
        contentlist := GetContentList;
        posx := X;
        posy := Y;
        if Assigned(contentlist) then
        begin
          posx := posx - contentlist.Position.X;
          posy := posy - contentlist.Position.Y;
        end;

        shpdrag.Position.X := DragTile.Shape.Position.X - (FPrevX - X);
        shpdrag.Position.Y := DragTile.Shape.Position.Y - (FPrevY - Y);
        shpdrag.State := tsNormal;
        Repaint;
        newtileidx := GetIndexAtPosition(DragTile, posx, posy);
        if (DragTile.Index <> newtileidx) and (newtileidx >= 0) and (newtileidx <= Tiles.Count - 1) then
        begin
          posx := shpdrag.Position.X;
          posy := shpdrag.Position.Y;
          DragTile.Index := newtileidx;
          AffectTiles;
          DoUpdateTiles;
          if Assigned(DragTile) then
          begin
            shpdrag := DragTile.Shape;
            if Assigned(shpdrag) then
            begin
              shpdrag.Position.X := posx;
              shpdrag.Position.Y := posy;
            end;
          end;
        end;
      end;
    end
    else
    begin
      if Assigned(shp) then
        shp.State := tsNormal;

      FCheckTile := False;
      FTilePos := FTilePos - (FPrevX - X);
      UpdateContentList(False, False);
    end;

    FPrevX := X;
    FPrevY := Y;
  end
  else
  begin
    if not FMouseDown then
    begin
      ClearAllTiles(shp);
      if Assigned(shp) then
        shp.State := tsHover;

      if Assigned(OnTileMouseMove) and Assigned(ATile) then
        OnTileMouseMove(Self, ATile, Shift, X, Y);
    end;
  end;
end;

procedure TTMSFMXTileList.DoMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
var
  contentlist, content: TControl;
  ATile: TTMSFMXTile;
  shp: TTMSFMXTileShape;
begin
  FMouseDown := False;
  if Assigned(DragTile) then
  begin
    DragTile := nil;
    AnimateTiles;
    Exit;
  end;
  content := GetContent;
  contentlist := GetContentList;
  ATile := XYToTile(X, Y);
  if Assigned(ATile) then
  begin
    shp := ATile.Shape;
    if Assigned(shp) then
      shp.State := tsHover;

    if Assigned(OnTileMouseUp) then
      OnTileMouseUp(Self, ATile, Button, Shift, X, Y);
  end
  else
    ClearAllTiles(nil);

  if FCheckTile then
  begin
    if Assigned(ATile) then
    begin
      FShiftState := Shift;
      FBlockDeselection := ((ssShift in FShiftState) or (ssCtrl in FShiftState)) and MultiSelect;
      ATile.Selected := not ATile.Selected;
      if ATile.Selected then
        SelectedTileIndex := ATile.Index;
      if not (((ssShift in Shift) or (ssCtrl in Shift)) and MultiSelect) then
        FSelTile := ATile;
      if (SelectedTileIndex <> -1) and Assigned(FSelTile) and (ssShift in FShiftState) and MultiSelect then
        SelectAllTilesBetween(SelectedTileIndex, FSelTile.Index);
      FShiftState := [];
      if Assigned(OnTileClick) then
        OnTileClick(Self, ATile);
    end;
  end
  else
  begin
    if Assigned(content) and Assigned(contentlist) and (FDownX <> -1) then
    begin
      FCurrentPos := X - FDownX;


      if NavigationMode = nmPaging then
      begin
        if (FCurrentPos < -GetPageWidth / 3) then
          NextPage
        else if (FCurrentPos > GetPageWidth / 3) then
          PreviousPage
        else
          PageIndex := PageIndex;
      end;
    end;
  end;
end;

procedure TTMSFMXTileList.DoTileAnchorClick(Sender: TObject; AAnchor: String);
var
  ATile: TTMSFMXTile;
  p: TFmxObject;

  function GetParent(AFMXObject: TFMXObject): TFMXObject;
  begin
    Result := nil;
    if Assigned(AFMXObject) then
    begin
      if Assigned(AFMXObject.Parent) then
      begin
        if AFMXOBject.Parent is TTMSFMXTileShape then
          Result := AFMXObject.Parent
        else
          Result := GetParent(AFMXObject.Parent);
      end;
    end;
  end;
begin
  if Assigned(Sender) and (Sender is TTMSFMXHTMLText) then
  begin
    p := GetParent((Sender as TTMSFMXHTMLText));
    if Assigned(p) and (p is TTMSFMXTileShape) then
    begin
      ATile := (p as TTMSFMXTileShape).ATile;
      if Assigned(OnTileAnchorClick) then
        OnTileAnchorClick(Sender, ATile, AAnchor);
    end;
  end;
end;

procedure TTMSFMXTileList.DoTileClick(Sender: TObject);
var
  ATile: TTMSFMXTile;
begin
  if Assigned(Sender) and (Sender is TTMSFMXTileShape) then
  begin
    ATile := (Sender as TTMSFMXTileShape).ATile;
    if Assigned(OnTileClick) then
      OnTileClick(Sender, ATile);
  end;
end;

procedure TTMSFMXTileList.DoTileMouseDown(Sender: TObject; ATile: TTMSFMXTile; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
//
end;

procedure TTMSFMXTileList.DoTileMouseMove(Sender: TObject; ATile: TTMSFMXTile; Shift: TShiftState;
  X, Y: Single);
begin
//
end;

procedure TTMSFMXTileList.DoTileMouseUp(Sender: TObject; ATile: TTMSFMXTile; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
//
end;

procedure TTMSFMXTileList.DoUpdateTiles;
begin
  UpdateTilesPos;
  UpdateTiles(True);
  UpdateBullets;
end;

function TTMSFMXTileList.CanClone(ATile: TTMSFMXTile; Main, Source: TFMXObject): Boolean;
begin
  Result := True;
  if Source = GetDefaultDetailTile(Main) then
  begin
    if Assigned(ATile) then
      Result := (loDetail in TileOptions);
  end
  else if Source = GetDefaultDetailTileCaption(Main) then
    Result := loDetailCaption in TileOptions
  else if Source = GetDefaultDetailTileNotes(Main) then
    Result := loDetailNotes in TileOptions
  else if Source = GetDefaultDetailTileContent(Main) then
    Result := loDetailContent in TileOptions
  else if Source = GetDefaultDetailTileBitmap(Main) then
    Result := loDetailBitmap in TileOptions
  else if Source = GetDefaultTileCaption(Main) then
    Result := loCaption in TileOptions
  else if Source = GetDefaultTileNotes(Main) then
    Result := loNotes in TileOptions
  else if Source = GetDefaultTileContent(Main) then
    Result := loContent in TileOptions
  else if Source = GetDefaultTileBitmap(Main) then
    Result := loBitmap in TileOptions;
end;

function TTMSFMXTileList.CanObserve(const ID: Integer): Boolean;
begin
  Result := False;
  if ID = TObserverMapping.EditGridLinkID then
    Result := True
  else if ID = TObserverMapping.PositionLinkID then
    Result := True;
end;

procedure TTMSFMXTileList.CleanUpStyles(AControl: TFMXObject);
var
  str: TStringList;
  lName: String;
  i: integer;
  ctrl: TFMXObject;
  l: TTMSFMXTileShape;
begin
  str := TStringList.Create;
  for i := 0 to Styles.Count - 1 do
  begin
    lName := Styles[I].StyleName;
    str.Add(UpperCase(lName));
  end;

  if Assigned(AControl) then
  begin
    for I := AControl.ChildrenCount - 1 downto 0 do
    begin
      ctrl := AControl.Children[i];
      if ctrl is TTMSFMXTileShape then
      begin
        l := ctrl as TTMSFMXTileShape;
        if (UpperCase(l.StyleName) <> 'DEFAULTTILE') and (str.IndexOf(UpperCase(l.StyleName)) = -1) then
        begin
          {$IFDEF DELPHI_LLVM}
          l.Release;
          {$ELSE}
          l.Free;
          {$ENDIF}
        end;
      end;
    end;
  end;

  str.Free;
end;

procedure TTMSFMXTileList.CleanUpTiles(innerElement: TFmxObject);
var
  str: TStringList;
  lName: String;
  i: integer;
  ctrl: TFMXObject;
  l: TTMSFMXTileShape;
  shp: TTMSFMXTileShape;
begin
  str := TStringList.Create;
  for i := 0 to FDisplayList.Count - 1 do
  begin
    if TObject(FDisplayList.Items[I]) is TTMSFMXTileShape then
    begin
      shp := TTMSFMXTileShape(FDisplayList.Items[I]);
      if Assigned(shp.ATile) then
      begin
        lName := 'FMXTileListItem'+inttostr(shp.ATile.Index);
        str.Add(UpperCase(lName));
      end;
    end;
  end;

  if Assigned(innerElement) then
  begin
    for I := innerElement.ChildrenCount - 1 downto 0 do
    begin
      ctrl := innerElement.Children[i];
      if ctrl is TTMSFMXTileShape then
      begin
        l := ctrl as TTMSFMXTileShape;
        if str.IndexOf(UpperCase(l.StyleName)) = -1 then
        begin
          if Assigned(OnUnLoadTile) then
            OnUnLoadTile(Self, l.ATile, l);

          {$IFDEF DELPHI_LLVM}
          l.Release;
          {$ELSE}
          l.Free;
          {$ENDIF}
        end;
      end;
    end;
  end;

  str.Free;
end;

procedure TTMSFMXTileList.ClearAllTiles(AShape: TTMSFMXTileShape);
var
  I: Integer;
  obj: TObject;
begin
  for I := 0 to FDisplayList.Count - 1 do
  begin
    obj := TObject(FDisplayList.Items[I]);
    if Assigned(obj) and (obj is TTMSFMXTileShape) and (obj <> AShape) then
      (obj as TTMSFMXTileShape).State := tsNormal;
  end;
end;

procedure TTMSFMXTileList.ClearFilter;
begin
  ApplyFilter('', False);
end;

procedure TTMSFMXTileList.CloneChildren(ATile: TTMSFMXTile; Source, Destination: TFMXObject);
var
  i: integer;
  fmxclone: TFmxObject;
begin
  for I := 0 to Source.ChildrenCount - 1 do
  begin
    if CanClone(ATile, Source, Source.Children[I]) then
    begin
      fmxclone := Source.Children[I].Clone(Destination);
      Destination.InsertObject(Destination.ChildrenCount, fmxclone);
      CloneChildren(ATile, Source.Children[I], fmxclone);
    end;
  end;
end;

procedure TTMSFMXTileList.UpdateTileStyles(AControl: TFMXObject);
var
  tl, tlClone: TFMXObject;
  I: Integer;
  ctl, ctlt: TControl;
begin
  if Assigned(AControl) then
  begin
    CleanUpStyles(AControl);
    tl := AControl.FindStyleResource('defaulttile');
    if Assigned(tl) then
    begin
      for I := 0 to Styles.Count - 1 do
      begin
        tlClone := AControl.FindStyleResource(Styles[i].StyleName);
        if not Assigned(tlClone) then
        begin
          tlClone := tl.Clone(AControl);
          tlClone.StyleName := Styles[i].StyleName;
          AControl.InsertObject(AControl.ChildrenCount, tlClone);
        end;
        if (tl is TControl) and (tlClone is TControl) then
        begin
          ctl := (tlClone as TControl);
          ctlt := (tl as TControl);
          ctl.Position.X := ctlt.Position.X + ((ctl.Width + 5) * (i + 1));
          ctl.Position.Y := ctlt.Position.Y;
        end;
      end;
    end;
  end;
end;

constructor TTMSFMXTileList.Create(AOwner: TComponent);
begin
  inherited;
  CanFocus := True;
  FSelectedTileIndex := -1;
  FLookupTime := 2;
  FNavigationMode := nmPaging;
  FKeyBoardFilter := True;
  FRows := 3;
  FReorder := True;
  FPageWidth := 0;
  FColumns := 3;
  FVerticalSpacing := 10;
  FHorizontalSpacing := 10;
  FTimerCount := 0;
  FDisplayList := TDisplayList.Create;
  FTileList := TTileList.Create;
  FTiles := TTMSFMXTiles.Create(Self);
  FStyles := TTMSFMXStyles.Create(Self);
  FTileOptions := DefaultTileOptions;
  FKeyBoardMode := kmPageNavigation;
  FFiltering := lfRandom;
  FFilterMode := lfmFilter;

  FTimer := TTimer.Create(Self);
  FTimer.OnTimer := TimerEvent;
  FTimer.Enabled := False;

  Width :=  450;
  Height := 450;
end;

procedure TTMSFMXTileList.DeSelectAllTiles(ATile: TTMSFMXTile = nil);
var
  I: Integer;
begin
  if FBlockDeselection then
    Exit;
  FBlockDeselection := True;
  for I := 0 to Tiles.Count - 1 do
  begin
    if Tiles[I] <> ATile then
      Tiles[I].Selected := False;
  end;
  FBlockDeselection := False;
end;

destructor TTMSFMXTileList.Destroy;
begin
  FTimer.Free;
  FStyles.Free;
  FTileList.Free;
  FDisplayList.Free;
  FTiles.Clear;
  FTiles.Free;
  inherited;
end;

procedure TTMSFMXTileList.EndUpdate;
begin
  inherited;
  Dec(FUpdateCount);
  if not FBlockUpdate and (FUpdateCount = 0) and not (csDestroying in ComponentState) then
  begin
    ApplyStyle;
    Repaint;
  end;
end;

procedure TTMSFMXTileList.FillBulletList(List, Bullet: TControl; AStyleName: String);
var
  I: Integer;
  c: TControl;
  w, x: Single;
begin
  if Assigned(list) and Assigned(bullet) then
  begin
    list.BeginUpdate;
    for I := List.ChildrenCount - 1 downto 0 do
    begin
      if UpperCase(List.Children[I].StyleName) = UpperCase(AStyleName) then
        List.RemoveObject(List.Children[I]);
    end;

    w := FPageCount * (bullet.Width + 5);
    for I := 0 to FPageCount - 1 do
    begin
      x := (list.Width / 2) - (w / 2) + ((bullet.Width + 5) * I) + 2.5;
      if (x > 0) and (x < list.Width - bullet.Width) then
      begin
        c := bullet.Clone(list) as TControl;
        if c is TTMSFMXTileListBulletShape then
        begin
          (c as TTMSFMXTileListBulletShape).Selected := PageIndex = I;
          (c as TTMSFMXTileListBulletShape).PageIndex := I;
        end;
        {$IFDEF DELPHIXE6_LVL}
        c.Align := TAlignLayout.None;
        {$ELSE}
        c.Align := TAlignLayout.alNone;
        {$ENDIF}
        c.StyleName := bullet.StyleName + 'clone';
        c.Visible := True;
        c.OnClick := BulletClick;

        list.InsertObject(list.ChildrenCount, c);

        c.SetBounds(x, 2 + (list.Height - c.Height) / 2, c.Width, c.Height);

        if Assigned(OnCustomizeBullet) then
          OnCustomizeBullet(Self, I, c);
      end;
    end;
    list.EndUpdate;
  end;
end;

function TTMSFMXTileList.FindNextTileIndexBy(ARow, AColumn, APageIndex: Integer): Integer;
var
  pos: TTilePosItem;
  i: Integer;
  c: Integer;
  r: Integer;
begin
  Result := -1;
  for r := ARow to Rows do
  begin
    for c := AColumn to Columns do
    begin
      for I := 0 to FTileList.Count - 1 do
      begin
        pos := TTilePosItem(FTileList[I]);
        if (pos.Row = r) and (pos.Col = c) and (pos.Page = APageIndex) and (not pos.Empty) then
        begin
          Result := pos.TileIdx;
          Exit;
        end;
      end;
    end;
    AColumn := 0;
  end;
end;

function TTMSFMXTileList.FindPreviousTileIndexBy(ARow, AColumn,
  APageIndex: Integer): Integer;
var
  pos: TTilePosItem;
  i: Integer;
  c: Integer;
  r: Integer;
begin
  Result := -1;
  for r := ARow downto 0 do
  begin
    for c := AColumn downto 0 do
    begin
      for I := 0 to FTileList.Count - 1 do
      begin
        pos := TTilePosItem(FTileList[I]);
        if (pos.Row = r) and (pos.Col = c) and (pos.Page = APageIndex) and (not pos.Empty) then
        begin
          Result := pos.TileIdx;
          Exit;
        end;
      end;
    end;
    AColumn := Columns;
  end;
end;

function TTMSFMXTileList.FindTileIndexBy(ARow, AColumn: Integer): Integer;
var
  pos: TTilePosItem;
  i: Integer;
begin
  Result := -1;
  for I := 0 to FTileList.Count - 1 do
  begin
    pos := TTilePosItem(FTileList[I]);
    if (pos.Row = ARow) and (pos.Col = AColumn)then
    begin
      Result := pos.TileIdx;
      Break;
    end;
  end;
end;

function TTMSFMXTileList.GetContent: TControl;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('content');
  if obj is TControl then
    Result := obj as TControl;
end;

function TTMSFMXTileList.GetContentList: TControl;
var
  obj: TFMXObject;
  ctl: TControl;
begin
  Result := nil;
  if NavigationMode = nmScrolling then
  begin
    ctl := GetScrollContent;

    if Assigned(ctl) then
    begin
      if Assigned(TTMSFMXScrollBox(ctl)) then
      begin
        obj := TTMSFMXScrollBox(ctl).FindStyleResource('contentlist');
        if obj is TControl then
          Result := obj as TControl;
      end;
    end;
  end
  else
  begin
    ctl := Self;

    if Assigned(ctl) then
    begin
      obj := ctl.FindStyleResource('content');
      if Assigned(obj) then
        obj := obj.FindStyleResource('contentlist');

      if obj is TControl then
        Result := obj as TControl;
    end;
  end;
end;

function TTMSFMXTileList.GetContentListAnim: TAnimation;
var
  ctl: TControl;
  I: Integer;
begin
  Result := nil;
  ctl := GetContentList;
  if Assigned(ctl) then
  begin
    for I := 0 to ctl.ChildrenCount - 1 do
    begin
      if (ctl.Children[I] is TAnimation) and (ctl.Children[I].StyleName = 'contentlistanim') then
      begin
        Result := ctl.Children[I] as TAnimation;
        Break;
      end;
    end;
  end;
end;

function TTMSFMXTileList.GetContentListScroll: TControl;
var
  obj: TFMXObject;
  ctl: TControl;
begin
  Result := nil;
  ctl := GetScrollContent;
  if Assigned(ctl) then
  begin
    obj := TTMSFMXScrollBox(ctl).FindStyleResource('contentlist');
    if obj is TControl then
      Result := obj as TControl;
  end;
end;

function TTMSFMXTileList.GetDefaultDetailTile(Source: TFMXObject): TControl;
var
  dt, obj: TFMXObject;
begin
  Result := nil;
  if Assigned(Source) then
    dt := Source
  else
    dt := GetDefaultTile;
  if Assigned(dt) then
  begin
    obj := dt.FindStyleResource('defaultdetailtile');
    if obj is TControl then
      Result := obj as TControl;
  end;
end;

function TTMSFMXTileList.GetDefaultDetailTileBitmap(Source: TFMXObject): TControl;
var
  dt, obj: TFMXObject;
begin
  Result := nil;
  if Assigned(Source) then
    dt := Source
  else
    dt := GetDefaultTile;
  if Assigned(dt) then
  begin
    obj := dt.FindStyleResource('defaultdetailtilebitmap');
    if obj is TControl then
      Result := obj as TControl;
  end;
end;

function TTMSFMXTileList.GetDefaultDetailTileCaption(Source: TFMXObject): TControl;
var
  dt, obj: TFMXObject;
begin
  Result := nil;
  if Assigned(Source) then
    dt := Source
  else
    dt := GetDefaultTile;
  if Assigned(dt) then
  begin
    obj := dt.FindStyleResource('defaultdetailtilecaption');
    if obj is TControl then
      Result := obj as TControl;
  end;
end;

function TTMSFMXTileList.GetDefaultDetailTileContent(Source: TFMXObject): TControl;
var
  dt, obj: TFMXObject;
begin
  Result := nil;
  if Assigned(Source) then
    dt := Source
  else
    dt := GetDefaultTile;

  if Assigned(dt) then
  begin
    obj := dt.FindStyleResource('defaultdetailtilecontent');
    if obj is TControl then
      Result := obj as TControl;
  end;
end;

function TTMSFMXTileList.GetDefaultDetailTileNotes(Source: TFMXObject): TControl;
var
  dt, obj: TFMXObject;
begin
  Result := nil;
  if Assigned(Source) then
    dt := Source
  else
    dt := GetDefaultTile;

  if Assigned(dt) then
  begin
    obj := dt.FindStyleResource('defaultdetailtilenotes');
    if obj is TControl then
      Result := obj as TControl;
  end;
end;

function TTMSFMXTileList.GetDefaultTile: TControl;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('defaulttile');
  if obj is TControl then
    Result := obj as TControl;
end;

function TTMSFMXTileList.GetDefaultTileBadge: TControl;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('defaulttilebadge');
  if obj is TControl then
    Result := obj as TControl;
end;

function TTMSFMXTileList.GetDefaultTileBitmap(Source: TFMXObject): TControl;
var
  obj, dt: TFMXObject;
begin
  Result := nil;
  if Assigned(Source) then
    dt := Source
  else
    dt := GetDefaultTile;

  if Assigned(dt) then
  begin
    obj := dt.FindStyleResource('defaulttilebitmap');
    if obj is TControl then
      Result := obj as TControl;
  end;
end;

function TTMSFMXTileList.GetDefaultTileByName(AStyleName: String): TControl;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource(AStyleName);
  if obj is TControl then
    Result := obj as TControl;
end;

function TTMSFMXTileList.GetDefaultTileContent(Source: TFMXObject): TControl;
var
  dt, obj: TFMXObject;
begin
  Result := nil;
  if Assigned(Source) then
    dt := Source
  else
    dt := GetDefaultTile;

  if Assigned(dt) then
  begin
    obj := dt.FindStyleResource('defaulttilecontent');
    if obj is TControl then
      Result := obj as TControl;
  end;
end;


function TTMSFMXTileList.GetDefaultTileNotes(Source: TFMXObject): TControl;
var
  obj, dt: TFMXObject;
begin
  Result := nil;
  if Assigned(Source) then
    dt := Source
  else
    dt := GetDefaultTile;

  if Assigned(dt) then
  begin
    obj := dt.FindStyleResource('defaulttilenotes');
    if obj is TControl then
      Result := obj as TControl;
  end;
end;

function TTMSFMXTileList.GetDefaultTileCaption(Source: TFMXObject): TControl;
var
  obj, dt: TFMXObject;
begin
  Result := nil;
  if Assigned(Source) then
    dt := Source
  else
    dt := GetDefaultTile;

  if Assigned(dt) then
  begin
    obj := dt.FindStyleResource('defaulttilecaption');
    if obj is TControl then
      Result := obj as TControl;
  end;
end;

function TTMSFMXTileList.GetFooter: TControl;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('footer');
  if obj is TControl then
    Result := obj as TControl;
end;

function TTMSFMXTileList.GetFooterArrowLeft: TControl;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('footerarrowleft');
  if obj is TControl then
    Result := obj as TControl;
end;

function TTMSFMXTileList.GetFooterArrowRight: TControl;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('footerarrowright');
  if obj is TControl then
    Result := obj as TControl;
end;

function TTMSFMXTileList.GetFooterBullet: TControl;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('footerbullet');
  if obj is TControl then
    Result := obj as TControl;
end;

function TTMSFMXTileList.GetFooterBulletList: TControl;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('footerbulletlist');
  if obj is TControl then
    Result := obj as TControl;
end;

function TTMSFMXTileList.GetHeader: TControl;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('header');
  if obj is TControl then
    Result := obj as TControl;
end;

function TTMSFMXTileList.GetHeaderArrowLeft: TControl;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('headerarrowleft');
  if obj is TControl then
    Result := obj as TControl;
end;

function TTMSFMXTileList.GetHeaderArrowRight: TControl;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('headerarrowright');
  if obj is TControl then
    Result := obj as TControl;
end;

function TTMSFMXTileList.GetHeaderBullet: TControl;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('headerbullet');
  if obj is TControl then
    Result := obj as TControl;
end;

function TTMSFMXTileList.GetHeaderBulletList: TControl;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('headerbulletlist');
  if obj is TControl then
    Result := obj as TControl;
end;

function TTMSFMXTileList.GetIndexAtPosition(ATile: TTMSFMXTile; X, Y: Single): Integer;
var
  I: Integer;
  pos: TTilePosItem;
begin
  Result := -1;
  for I := 0 to FTileList.Count - 1 do
  begin
    pos := TTilePosItem(FTileList[I]);
    if Assigned(pos) and not (pos.TileIdx = ATile.Index) then
    begin
      if PointInRect(PointF(X, Y), pos.R) then
      begin
        Result := pos.TileIdx;
        break;
      end;
    end;
  end;
end;

function TTMSFMXTileList.GetInstance: NativeUInt;
begin
  Result := HInstance;
end;

function TTMSFMXTileList.GetLookupKey: String;
begin
  Result := FLookupKey;
end;

function TTMSFMXTileList.GetPageHeight: Single;
var
  content, contentscrolllist: TControl;
begin
  Result := 0;
  if PageHeight > 0 then
    Result := PageHeight
  else
  begin
    content := GetContent;    
    contentscrolllist := GetScrollContent;
    if Assigned(content) then
    begin
      Result := content.height;
      if (NavigationMode = nmScrolling) and Assigned(contentscrolllist) then
        if Assigned(TTMSFMXScrollBox(contentscrolllist).HScrollBar) then
          if TTMSFMXScrollBox(contentscrolllist).HScrollBar.Visible then
          Result := Result - TTMSFMXScrollBox(contentscrolllist).HScrollBar.Height;
    end;
  end;
end;

function TTMSFMXTileList.GetPageWidth: Single;
var
  content: TControl;
begin
  Result := 0;
  if PageWidth > 0 then
    Result := PageWidth
  else
  begin
    content := GetContent;
    if Assigned(content) then
      Result := content.width;
  end;
end;

function TTMSFMXTileList.GetScrollContent: TControl;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('contentscroll');
  if obj is TControl then
    Result := obj as TControl;
end;

function TTMSFMXTileList.GetSelectedTileIndex: Integer;
begin
  Result := FSelectedTileIndex;
end;

function TTMSFMXTileList.GetVersion: String;
var
  vn: Integer;
begin
  vn := GetVersionNr;
  Result := IntToStr(Hi(Hiword(vn)))+'.'+IntToStr(Lo(Hiword(vn)))+'.'+IntToStr(Hi(Loword(vn)))+'.'+IntToStr(Lo(Loword(vn)));
end;

function TTMSFMXTileList.GetVersionNr: integer;
begin
  Result := MakeLong(MakeWord(BLD_VER,REL_VER),MakeWord(MIN_VER,MAJ_VER));
end;

procedure TTMSFMXTileList.GoToPage(APageIndex: Integer);
begin
  PageIndex := APageIndex;
end;

procedure TTMSFMXTileList.LeftArrowClick(Sender: TObject);
begin
  PreviousPage;
  if Assigned(OnPreviousPageClick) then
    OnPreviousPageClick(Self);
end;

procedure TTMSFMXTileList.Loaded;
begin
  inherited;
  ApplyStyle;
end;

procedure TTMSFMXTileList.NextPage;
begin
  PageIndex := PageIndex + 1;
end;

procedure TTMSFMXTileList.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if (Operation = opRemove) and (AComponent = FBitmapContainer) then
    FBitmapContainer := nil;
end;

function TTMSFMXTileList.PageCount: Integer;
begin
  Result := FPageCount;
end;

procedure TTMSFMXTileList.PreviousPage;
begin
  PageIndex := PageIndex - 1;
end;

procedure TTMSFMXTileList.Resize;
begin
  inherited;
  if FResizing then
    Exit;

  FResizing := True;
  Width := Width + 1;
  Width := Width - 1;
  DoUpdateTiles;
  FResizing := False;
end;

procedure TTMSFMXTileList.RightArrowClick(Sender: TObject);
begin
  NextPage;
  if Assigned(OnNextPageClick) then
    OnNextPageClick(Self);
end;

procedure TTMSFMXTileList.SelectAllTiles(ATile: TTMSFMXTile);
var
  I: Integer;
begin
  if FBlockDeselection then
    Exit;
  FBlockDeselection := True;
  for I := 0 to Tiles.Count - 1 do
  begin
    if Tiles[I] <> ATile then
      Tiles[I].Selected := True;
  end;
  FBlockDeselection := False;
end;

procedure TTMSFMXTileList.SelectAllTilesBetween(AStartTileIndex,
  AStopTileIndex: Integer);
var
  I: Integer;
begin
  FBlockDeselection := False;
  DeSelectAllTiles(nil);
  FBlockDeselection := True;
  if AStartTileIndex > AStopTileIndex then
  begin
    for I := AStartTileIndex downto AStopTileIndex do
    begin
      if (I >= 0) and (i <= Tiles.Count - 1) then
        Tiles[i].Selected := True;
    end;
  end
  else
  begin
    for I := AStartTileIndex to AStopTileIndex do
    begin
      if (I >= 0) and (i <= Tiles.Count - 1) then
        Tiles[i].Selected := True;
    end;
  end;
  FBlockDeselection := False;
end;

procedure TTMSFMXTileList.SelectTiles(ArrTiles: array of integer);
var
  I: Integer;
begin
  FBlockDeselection := MultiSelect;
  for I := 0 to Length(ArrTiles) - 1 do
  begin
    if (arrTiles[I] >= 0) and (ArrTiles[i] <= Tiles.Count - 1) then
      Tiles[ArrTiles[i]].Selected := true;
  end;
  FBlockDeselection := False;
end;

procedure TTMSFMXTileList.SetBitmapContainer(
  const Value: TTMSFMXBitmapContainer);
begin
  if FBitmapContainer <> Value then
    FBitmapContainer := Value;
end;

procedure TTMSFMXTileList.SetColumns(const Value: Integer);
begin
  if FColumns <> Value then
  begin
    FColumns := Value;
    DoUpdateTiles;
  end;
end;

procedure TTMSFMXTileList.SetColumnWidth(const Value: Single);
begin
  if FColumnWidth <> Value then
  begin
    FColumnWidth := Value;
    DoUpdateTiles;
  end;
end;

procedure TTMSFMXTileList.SetReorder(const Value: Boolean);
begin
  FReorder := Value;
end;

procedure TTMSFMXTileList.SetFiltering(const Value: TTMSFMXTileListFiltering);
begin
  FFiltering := Value;
end;

procedure TTMSFMXTileList.SetFilterMode(const Value: TTMSFMXTileListFilterMode);
begin
  if FFilterMode <> Value then
    FFilterMode := Value;
end;

procedure TTMSFMXTileList.SetFocusOnBackGround;
var
  content: TControl;
begin
  content := GetContent;
  if Assigned(content) then
    content.SetFocus;
end;

procedure TTMSFMXTileList.SetHorizontalSpacing(const Value: Integer);
begin
  if FHorizontalSpacing <> Value then
  begin
    FHorizontalSpacing := Value;
    DoUpdateTiles;
  end;
end;

procedure TTMSFMXTileList.SetKeyBoardFilter(const Value: Boolean);
begin
  if FKeyBoardFilter then
    FKeyBoardFilter := Value;
end;

procedure TTMSFMXTileList.SetKeyBoardMode(
  const Value: TTMSFMXTileListKeyBoardMode);
begin
  if FKeyBoardMode <> Value then
    FKeyBoardMode := Value;
end;

procedure TTMSFMXTileList.SetLookupTime(const Value: Integer);
begin
  if FLookupTime <> Value then
    FLookupTime := Value;
end;

procedure TTMSFMXTileList.SetMultiSelect(const Value: Boolean);
begin
  if FMultiSelect <> Value then
    FMultiSelect := Value;
end;

procedure TTMSFMXTileList.SetNavigationMode(
  const Value: TTMSFMXTileListNavigationMode);
begin
  if FNavigationMode <> Value then
  begin
    FNavigationMode := Value;
    ApplyStyle;
    Repaint;
    UpdateTiles(True);
  end;
end;

procedure TTMSFMXTileList.SetPageHeight(const Value: Single);
begin
  if FPageHeight <> Value then
    FPageHeight := Value;
end;

procedure TTMSFMXTileList.SetPageIndex(const Value: Integer);
var
  i: Integer;
  content: TControl;
  contentlist: TControl;
  contentanim: TAnimation;
  UpdateList: Boolean;
  pw: Single;
begin
  i := FPageIndex;
  FPageIndex := Max(0, Min(Value, PageCount - 1));
  FPrevVal := FTilePos;
  content := GetContent;
  contentlist := GetContentList;
  UpdateList := true;
  pw := GetPageWidth;
  if Assigned(content) and assigned(contentlist) then
  begin
    if NavigationMode = nmPaging then
    begin
      if FAnimationStarted then
      begin
        contentanim := GetContentListAnim;
        if Assigned(contentanim) then
          contentanim.Stop;

        if i = FPageIndex - 1 then
          contentlist.Position.X := contentlist.Position.X + pw
        else if i = FPageIndex + 1 then
          contentlist.Position.X := contentlist.Position.X - pw
      end;
      if i = FPageIndex - 1 then
        FTilePos := contentlist.Position.X - pw
      else if i = FPageIndex + 1 then
        FTilePos := contentlist.Position.X + pw
      else if i = FPageIndex then
        FTilePos := - pw
      else
      begin
        UpdateList := False;
        UpdateTiles(false);
      end;
    end;
  end;

  FTilePos := Max(-(pw * 2), Min(FTilePos, 0));

  if UpdateList then
    UpdateContentList(True, True);

  UpdateBullets;
end;

procedure TTMSFMXTileList.SetPageWidth(const Value: Single);
begin
  if FPageWidth <> Value then
  begin
    FPageWidth := Value;
    ApplyStyle;
    Repaint;
  end;
end;

procedure TTMSFMXTileList.SetRowHeight(const Value: Single);
begin
  if FRowHeight <> Value then
  begin
    FRowHeight := Value;
    DoUpdateTiles;
  end;
end;

procedure TTMSFMXTileList.SetRows(const Value: Integer);
begin
  if FRows <> Value then
  begin
    FRows := Value;
    DoUpdateTiles;
  end;
end;

procedure TTMSFMXTileList.SetSelectedTileIndex(const Value: Integer);
begin
  if FSelectedTileIndex <> Value then
  begin
    FSelectedTileIndex := Value;
    if (FSelectedTileIndex >= 0) and (FSelectedTileIndex <= Tiles.Count - 1) then
      Tiles[FSelectedTileIndex].Selected := True;
  end;
end;

procedure TTMSFMXTileList.SetStyles(const Value: TTMSFMXStyles);
begin
  if FStyles <> Value then
    FStyles.Assign(Value);
end;

procedure TTMSFMXTileList.SetTiles(const Value: TTMSFMXTiles);
begin
  if FTiles <> Value then
    FTiles.Assign(Value);
end;

procedure TTMSFMXTileList.SetVersion(const Value: String);
begin

end;

procedure TTMSFMXTileList.SetVerticalSpacing(const Value: Integer);
begin
  if FVerticalSpacing <> Value then
  begin
    FVerticalSpacing := Value;
    DoUpdateTiles;
  end;
end;

procedure TTMSFMXTileList.TimerEvent(Sender: TObject);
var
  AllowFinish: Boolean;
  OldLookupKey, NewLookupKey: String;
begin
  Inc(FTimerCount);
  if FTimerCount >= LookupTime then
  begin
    AllowFinish := True;
    OldLookupKey := FLookupKey;
    NewLookupKey := '';
    if Assigned(OnFilterFinished) then
      OnFilterFinished(Self, OldLookupKey, NewLookupKey, AllowFinish);
    if AllowFinish then
      FLookupKey := NewLookupKey;
    FTimer.Enabled := False;
  end;
end;

procedure TTMSFMXTileList.UpdateBullets;
var
  bulletlisth, bulletlistf, bulleth, bulletf: TControl;
begin

  bulletlisth := GetHeaderBulletList;
  bulletlistf := GetFooterBulletList;
  bulleth := GetHeaderBullet;
  bulletf := GetFooterBullet;

  FillBulletList(bulletlisth, bulleth, 'headerbulletclone');
  FillBulletList(bulletlistf, bulletf, 'footerbulletclone');
end;

procedure TTMSFMXTileList.UpdateContentList(DoAnimation, Animation: Boolean);
var
  contentlist, content: TControl;
  contentanim: TAnimation;
  contentscrolllist: TControl;
  newpos, hpos, pw: Single;
begin
  contentList := GetContentList;
  contentanim := GetContentListAnim;
  content := GetContent;
  contentscrolllist := GetScrollContent;

  if Assigned(contentlist) and assigned(content) then
  begin
    pw := GetPageWidth;
    if NavigationMode = nmPaging then
    begin
      {$IFDEF DELPHIXE6_LVL}
      content.align := TAlignLayout.Client;
      {$ELSE}
      content.align := TAlignLayout.alClient;
      {$ENDIF}
      contentlist.Height := GetPageHeight;
      contentlist.Width := pw * 3;
      if not Animation then
      begin
         if Assigned(contentanim) then
           contentanim.Stop;

        contentlist.Position.X := FTilePos - pw;
      end
      else if Assigned(contentanim) then
      begin
        if contentanim is TFloatAnimation then
        begin
          (contentanim as tFloatAnimation).StartFromCurrent := True;
          (contentanim as tFloatAnimation).StopValue := FTilePos;
        end;
        FDoAnimation := DoAnimation;
        FAnimationStarted := True;
        contentanim.Start;
      end;
    end
    else
    begin
      contentlist.Height := GetPageHeight;
      contentlist.Width := pw * PageCount;
      newpos := -pw * PageIndex;
      hpos := 0;
      if Assigned(TTMSFMXScrollBox(contentscrolllist).HScrollBar) then
        hpos := TTMSFMXScrollBox(contentscrolllist).HScrollBar.Value;

      TTMSFMXScrollBox(contentscrolllist).ScrollTo(hpos + newpos, 0);
    end;
  end;
end;

procedure TTMSFMXTileList.UpdateTiles(UpdateContent: Boolean);
var
  innerElement, itemElement: TControl;
  TileX, TileY, TileW, TileH: Single;
  hs, vs, w, h: Single;
  ATileIndex: Integer;
  ATile: TTMSFMXTile;
  exw, exh: Single;
  I: Integer;
  tilepos: TTilePosItem;
  pgidx: Integer;
  start, stop: Integer;
  content: TControl;
  pw: Single;
begin
  if (csDestroying in ComponentState) or (csLoading in ComponentState) or (FUpdateCount > 0) then
    Exit;

  BeginUpdate;

  FDisplayList.Clear;

  content := GetContent;
  innerElement := GetContentList;
  itemElement := GetDefaultTile;
  pw := GetPageWidth;

  if UpdateContent then
    UpdateContentList(False, False);

  if (Tiles.Count = 0) or (FTileList.Count = 0) then
  begin
    FPageCount := 0;
    CleanUpTiles(innerelement);

    FBlockUpdate := True;
    EndUpdate;
    FBlockUpdate := False;
    Exit;
  end;

  if Assigned(innerElement) and Assigned(content) then
  begin
    hs := HorizontalSpacing;
    vs := VerticalSpacing;
    if ColumnWidth > 0 then
      TileW := ColumnWidth
    else
    begin
      w := pw - Padding.Right - Padding.Left - vs;
      TileW := w;
      if Columns > 0 then
       TileW := (w - (Columns * hs)) / Columns;
    end;

    if RowHeight > 0 then
      TileH := RowHeight
    else
    begin
      h := GetPageHeight - Padding.Bottom - Padding.Top - vs;
      TileH := h;
      if Rows > 0 then
       TileH := (h - (Rows * vs)) / Rows;
    end;

    if NavigationMode = nmPaging then
    begin
      start := PageIndex - 1;
      stop := PageIndex + 1;
    end
    else
    begin
      start := 0;
      stop := PageCount - 1;
    end;

    for pgidx := start to stop do
    begin
      for I := 0 to FTileList.Count - 1 do
      begin
        tilepos := TTilePosItem(FTileList[I]);

        if Assigned(tilepos) and (tilepos.Page = pgidx) then
        begin
          ATileIndex := tilepos.TileIdx;
          if (ATileIndex >= 0) and (ATileIndex <= Tiles.Count - 1) then
          begin
            ATile := Tiles[ATileIndex];
            if (ATile.AllowDisplay and (FilterMode = lfmFilter)) or (FilterMode = lfmSearch) then
            begin
              exw := TileW * tilepos.ColSpan + (vs * (tilepos.ColSpan - 1));
              exh := TileH * tilepos.RowSpan + (hs * (tilepos.RowSpan - 1));
              if NavigationMode = nmPaging then
                TileX := pw + (pw * (pgidx - PageIndex)) + Padding.Left + hs + TileW * tilepos.col + (hs * tilepos.col)
              else
                TileX := (pw * pgidx) + Padding.Left + hs + TileW * tilepos.col + (hs * tilepos.col);

              TileY := Padding.Top + vs + TileH * tilepos.row + (hs * tilepos.row);
              AddTile(innerelement, itemElement, TileX, TileY, exw, exh, ATile);
              tilepos.R := RectF(TileX, TileY, TileX + exw, TileY + exh);
            end;
          end;
        end;
      end;
    end;
  end;

  CleanUpTiles(innerelement);

  FBlockUpdate := True;
  EndUpdate;
  FBlockUpdate := False;
end;

procedure TTMSFMXTileList.UpdateTilesPos;
  procedure FindNewTilePos(ATile: TTMSFMXTile; var ANewR: Integer; var ANewC: Integer; var AFound: Boolean; ARows , ACurRow, AColumns, ACurCol: Integer; PosArr: TTilePosArray);
  var
    i, k: integer;
    cspan, rspan: Integer;
    J, L: Integer;
  begin

    AFound := False;
    for I := ANewr to ARows - 1 do
    begin
      for K := ANewC to AColumns - 1 do
      begin
        cspan := ATile.ColumnSpan;
        cspan := Min(cspan, Columns - ANewC);
        rspan := ATile.RowSpan;
        rspan := Min(rspan, Rows - ANewR);

        AFound := true;
        for J := 0 to rspan - 1 do
        begin
          for L := 0 to cspan - 1 do
          begin
            if PosArr[I + J, K + L].TileSet then
              AFound := False;
          end;
        end;

        if AFound then
          Break;
        Inc(ANewC);
      end;
      if AFound then
        Break;

      ANewC := 0;
      Inc(ANewr);
    end;
end;

var
  TilePosArr: TTilePosArray;
  r, newr, c, newc: Integer;
  ATileIndex: Integer;
  ATile: TTMSFMXTile;
  cspan, rspan, newcspan, newrspan: Integer;
  I: Integer;
  K: Integer;
  f: Boolean;
  APageIndex: Integer;
  tilepos: TTilePosItem;
begin
  if (csDestroying in ComponentState) or (csLoading in ComponentState) or (FUpdateCount > 0) then
    Exit;

  BeginUpdate;

  FTileList.Clear;

  if Tiles.Count = 0 then
  begin
    FBlockUpdate := True;
    EndUpdate;
    FBlockUpdate := False;
    Exit;
  end;

  ATileIndex := 0;
  APageIndex := 0;
  while ATileIndex <= Tiles.Count - 1 do
  begin
    c := 0;
    r := 0;
    SetLength(TilePosArr, 0, 0);
    SetLength(TilePosArr, Rows, Columns);

    while r < Rows do
    begin
      while c < Columns do
      begin
        if (ATileIndex >= 0) and (ATileIndex <= Tiles.Count - 1) then
        begin
          ATile := Tiles[ATileIndex];
          if not ((ATile.AllowDisplay and (FilterMode = lfmFilter)) or (FilterMode = lfmSearch)) then
          begin
            Inc(ATileIndex);
            Continue;
          end;

          cspan := ATile.ColumnSpan;
          cspan := Min(cspan, Columns - c);
          rspan := ATile.RowSpan;
          rspan := Min(rspan, Rows - r);

          newcspan := c;
          newrspan := r;
          newcspan := newcspan + cspan - 1;
          newrspan := newrspan + rspan - 1;

          tilepos := TTilePosItem.Create;
          tilepos.TileIdx := ATileIndex;
          tilepos.Empty := ATile.Empty;
          tilepos.TileSet := True;
          tilepos.Page := APageIndex;
          tilepos.ColSpan := cspan;
          tilepos.RowSpan := rspan;
          tilepos.Row := r;
          tilepos.Col := c;
          ATile.TilePosition := tilepos;

          FTileList.Add(tilepos);

          for I := r to newrspan do
            for K := c to newcspan do
              TilePosArr[I, K].TileSet := True;
        end;
        Inc(ATileIndex);
        Inc(c);
        newc := c;
        newr := r;
        if (ATileIndex >= 0) and (ATileIndex <= Tiles.Count - 1) then
          findnewtilepos(Tiles[ATileIndex], newr, newc, f, Rows, newr, Columns, newc, TilePosArr);
        c := newc;
        r := newr;

        if (c >= Columns) or (r >= Rows) then
          Break;
      end;
      c := 0;
      Inc(r);
      newc := c;
      newr := r;
      if (ATileIndex >= 0) and (ATileIndex <= Tiles.Count - 1) then
        findnewtilepos(Tiles[ATileIndex], newr, newc, f, Rows, newr, Columns, newc, TilePosArr);
      c := newc;
      r := newr;
      if r >= Rows then
        Break;
    end;
    Inc(APageIndex);
  end;

  FPageCount := APageIndex;

  FBlockUpdate := True;
  EndUpdate;
  FBlockUpdate := False;
end;

function TTMSFMXTileList.XYToTile(X, Y: Single): TTMSFMXTile;
var
  I: Integer;
  obj: TObject;
  shp: TTMSFMXTileShape;
  p: TPointF;
  r: TRectF;
  contentlist: TControl;
  contentscroll: TControl;
begin
  Result := nil;
  contentlist := GetContentList;
  contentscroll := GetScrollContent;
  if Assigned(contentlist) then
  begin
    if NavigationMode = nmPaging then
    begin
      X := X - Contentlist.Position.X;
      Y := Y - Contentlist.Position.Y;
    end
    else if Assigned(contentscroll) then
    begin
      if Assigned(TTMSFMXScrollBox(contentscroll).HScrollBar) then
        X := X + TTMSFMXScrollBox(contentscroll).HScrollBar.Value;
    end;
    for I := 0 to FDisplayList.Count - 1 do
    begin
      obj := TObject(FDisplayList.Items[i]);
      if (obj is TTMSFMXTileShape) then
      begin
        shp := obj as TTMSFMXTileShape;
        if Assigned(shp) then
        begin
          p := PointF(shp.Position.X, shp.Position.Y);
          r := TRectF.Create(p, shp.Width, shp.Height);
          if PointInRect(PointF(X, Y), r) then
          begin
            if Assigned(shp.ATile) and (not shp.ATile.ReadOnly) then
            begin
              Result := shp.ATile;
              Break;
            end;
          end;
        end;
      end;
    end;
  end;
end;

{ TTMSFMXTiles }

function TTMSFMXTiles.Add: TTMSFMXTile;
begin
  result := TTMSFMXTile(inherited Add);
end;

function TTMSFMXTiles.AddTransparent: TTMSFMXTile;
begin
  Result := TTMSFMXTile(inherited Add);
  Result.Transparent := True;
end;

procedure TTMSFMXTiles.Clear;
begin
  FOwner.BeginUpdate;
  TCollection(Self).Clear;
  FOwner.EndUpdate;
end;

constructor TTMSFMXTiles.Create(AOwner: TTMSFMXTileList);
begin
  inherited Create(TTMSFMXTile);
  FOwner := AOwner;
end;

function TTMSFMXTiles.GetItem(
  Index: Integer): TTMSFMXTile;
begin
  Result := TTMSFMXTile(inherited Items[Index]);
end;

function TTMSFMXTiles.GetOwner: TPersistent;
begin
  Result := FOwner;
end;

function TTMSFMXTiles.Insert(Index: Integer): TTMSFMXTile;
begin
  Result := TTMSFMXTile(inherited Insert(Index));
end;

procedure TTMSFMXTiles.SetItem(Index: Integer;
  const Value: TTMSFMXTile);
begin
  inherited Items[Index] := Value;
end;

{ TTMSFMXTile }

function TTMSFMXTile.AllowDisplay: Boolean;
var
  str: String;
begin
  Result := True;
  if (FOwner.FFilter <> '') then
  begin
    str := GetStrippedCaption;
    Result := AnsiContainsText(str, FOwner.FFilter);
    if FOwner.Filtering = lfStart then
      Result := Result and AnsiStartsText(FOwner.FFilter, str);
  end;
end;

procedure TTMSFMXTile.AnimationDetailFinishedHiding(Sender: TObject);
begin
  if Assigned(FOwner.OnTileHideDetail) then
    FOwner.OnTileHideDetail(Self, Self);
end;

procedure TTMSFMXTile.AnimationDetailFinishedShowing(Sender: TObject);
begin
  if Assigned(FOwner.OnTileShowDetail) then
    FOwner.OnTileShowDetail(Self, Self);
end;

procedure TTMSFMXTile.AnimationDetailProcessing(Sender: TObject);
var
  shpdetail: TControl;
  shp: TTMSFMXTileShape;
begin
  shp := Shape;
  if Assigned(shp) then
  begin
    shp.Repaint;
    shpdetail := shp.ShapeDetail;
    if Assigned(shpdetail) then
      shpdetail.Repaint;
  end;
end;

procedure TTMSFMXTile.BitmapChanged(Sender: TObject);
begin
  UpdateBitmap;
end;

constructor TTMSFMXTile.Create(Collection: TCollection);
begin
  inherited;
  FOwner := (Collection as TTMSFMXTiles).FOwner;
  FBitmap := TBitmap.Create(0, 0);
  FBitmap.OnChange := BitmapChanged;

  FDetailBitmap := TBitmap.Create(0, 0);
  FDetailBitmap.OnChange := DetailBitmapChanged;
  FRowSpan := 1;
  FColumnSpan := 1;
  FCanSelect := True;
  FDetailSizePercentage := 0;
  FEnableDetail := True;
  FEmpty := False;
  FSelected := False;
  FReadOnly := False;
  if FOwner.FUpdateCount = 0 then
    UpdateTile;
end;

destructor TTMSFMXTile.Destroy;
begin
  FBitmap.Free;
  FDetailBitmap.Free;
  inherited;
  if FOwner.FUpdateCount = 0 then
    UpdateTile;
end;

procedure TTMSFMXTile.DetailBitmapChanged(Sender: TObject);
begin
  UpdateDetailBitmap;
end;

function TTMSFMXTile.GetBitmapContainer: TTMSFMXBitmapContainer;
begin
  Result := FOwner.BitmapContainer
end;

function TTMSFMXTile.GetCanSelect: Boolean;
begin
  Result := FCanSelect and not Empty;
end;

function GetStrippedHTMLText(ARect: TRectF; ACanvas: TCanvas; AString: String): String;
var
  a, s, fa: String;
  XSize, YSize: Single;
  hl, ml: Integer;
  hr: TRectF;
  str: String;
begin
  str := AString;
  if (AnsiPos('</', str) > 0) or (AnsiPos('/>', str)  > 0) or (AnsiPos('<BR>', UpperCase(str)) > 0) then
  begin
    HTMLDrawEx(ACanvas,str, ARect,0,0,-1,-1,0,False,True,False,False,False,False,false,1.0,claBlue,
      claNull,claNull,claNull,a,s,fa,XSize,YSize,hl,ml,hr,0, nil, 1);
    Result := s;
  end
  else
    Result := AString;
end;

function TTMSFMXTile.GetState: TTMSFMXTileState;
var
  shp: TTMSFMXTileShape;
begin
  Result := tsNormal;
  shp := Shape;
  if Assigned(shp) then
    Result := shp.State;
end;

function TTMSFMXTile.GetStrippedCaption: String;
begin
  Result := GetStrippedHTMLText(FOwner.BoundsRect, FOwner.Canvas, Caption);
end;

procedure TTMSFMXTile.HideDetail;
var
  a: TAnimation;
  f: TFloatAnimation;
  shp: TTMSFMXTileShape;
begin
  shp := Shape;
  if Assigned(shp) then
  begin
    a := shp.ShapeDetailAnimation;
    if Assigned(a) and (a is TFloatAnimation) then
    begin
      f := (a as TFloatAnimation);
      if Assigned(f) then
      begin
        f.Enabled := True;
        f.StopValue := shp.Height;
        f.OnProcess := AnimationDetailProcessing;
        f.OnFinish := AnimationDetailFinishedHiding;
        f.Start;
      end;
    end;
  end;
end;

procedure TTMSFMXTile.SetBadge(const Value: String);
begin
  if FBadge <> Value then
  begin
    FBadge := Value;
    if FOwner.FUpdateCount = 0 then
      UpdateBadge;
  end;
end;

procedure TTMSFMXTile.SetBitmap(const Value: TBitmap);
begin
  if FBitmap <> Value then
  begin
    FBitmap := Value;
    if FOwner.FUpdateCount = 0 then
      UpdateBitmap;
  end;
end;

procedure TTMSFMXTile.SetBitmapName(const Value: String);
begin
  if FBitmapName <> Value then
  begin
    FBitmapName := Value;
    if FOwner.FUpdateCount = 0 then
      UpdateBitmap;
  end;
end;

procedure TTMSFMXTile.SetCanSelect(const Value: Boolean);
begin
  if FCanSelect <> Value then
  begin
    FCanSelect := Value;
    if not FCanSelect then
      Selected := False;
  end;
end;

procedure TTMSFMXTile.SetCaption(const Value: String);
begin
  FCaption := Value;
  if FOwner.FUpdateCount = 0 then
    UpdateCaption;
end;

procedure TTMSFMXTile.SetColumnSpan(const Value: Integer);
begin
  if FColumnSpan <> Value then
  begin
    FColumnSpan := Max(1, Value);
    UpdateTile;
  end;
end;

procedure TTMSFMXTile.SetDataObject(const Value: TObject);
begin
  if FDataObject <> Value then
    FDataObject := Value;
end;

procedure TTMSFMXTile.SetDetailBitmap(const Value: TBitmap);
begin
  if FDetailBitmap <> Value then
  begin
    FDetailBitmap := Value;
    if FOwner.FUpdateCount = 0 then
      UpdateDetailBitmap;
  end;
end;

procedure TTMSFMXTile.SetDetailBitmapName(const Value: String);
begin
  if FDetailBitmapName <> Value then
  begin
    FDetailBitmapName := Value;
    if FOwner.FUpdateCount = 0 then
      UpdateDetailBitmap;
  end;
end;

procedure TTMSFMXTile.SetDetailCaption(const Value: String);
begin
  if FDetailCaption <> Value then
  begin
    FDetailCaption := Value;
    if FOwner.FUpdateCount = 0 then
      UpdateDetailCaption;
  end;
end;

procedure TTMSFMXTile.SetDetailNotes(const Value: String);
begin
  if FDetailNotes <> Value then
  begin
    FDetailNotes := Value;
    if FOwner.FUpdateCount = 0 then
      UpdateDetailNotes;
  end;
end;

procedure TTMSFMXTile.SetDetailSizePercentage(const Value: Single);
begin
  FDetailSizePercentage := Value;
end;

procedure TTMSFMXTile.SetEmpty(const Value: Boolean);
begin
  if FEmpty <> Value then
  begin
    FEmpty := Value;
    UpdateTile;
  end;
end;

procedure TTMSFMXTile.SetEnableDetail(const Value: Boolean);
begin
  if FEnableDetail <> Value then
    FEnableDetail := Value;
end;

procedure TTMSFMXTile.SetNotes(const Value: String);
begin
  if FNotes <> Value then
  begin
    FNotes := Value;
    if FOwner.FUpdateCount = 0 then
      UpdateNotes;
  end;
end;

procedure TTMSFMXTile.SetReadOnly(const Value: Boolean);
begin
  if FReadOnly <> Value then
  begin
    FReadOnly := Value;
    UpdateTile;
  end;
end;

procedure TTMSFMXTile.SetRowSpan(const Value: Integer);
begin
  if FRowSpan <> Value then
  begin
    FRowSpan := Max(1, Value);
    UpdateTile;
  end;
end;

procedure TTMSFMXTile.SetSelected(const Value: Boolean);
var
  shp: TTMSFMXTileShape;
begin
  if FSelected <> Value then
  begin
    if (CanSelect and Value) or (not Value) then
    begin
      FSelected := Value;

      shp := Shape;
      if Assigned(shp) then
        shp.Selected := Selected;

      if Selected then
      begin
        if Assigned(TilePosition) then
          FOwner.PageIndex := Self.TilePosition.Page;
      end;

      FOwner.DeSelectAllTiles(Self);
    end;
  end;
end;

procedure TTMSFMXTile.SetState(const Value: TTMSFMXTileState);
var
  shp: TTMSFMXTileShape;
begin
  shp := Shape;
  if Assigned(shp) then
    shp.State := Value;
end;

procedure TTMSFMXTile.SetStyleLookup(const Value: String);
begin
  FStyleLookup := Value;
  UpdateTile;
end;

procedure TTMSFMXTile.SetTransparent(const Value: Boolean);
var
  shp: TTMSFMXTileShape;
begin
  if FTransparent <> Value then
  begin
    FTransparent := Value;
    shp := Shape;
    if Assigned(shp) then
      shp.Transparent := Transparent;
  end;
end;

function TTMSFMXTile.Shape: TTMSFMXTileShape;
var
  innerelement: TControl;
  fmxobj: TFMXObject;
begin
  Result := nil;
  innerelement := FOwner.GetContentList;
  if Assigned(innerelement) then
  begin
    fmxobj := innerelement.FindStyleResource('FMXTileListItem'+inttostr(Index));
    if fmxobj is TTMSFMXTileShape then
      Result := fmxobj as TTMSFMXTileShape;
  end;
end;

function TTMSFMXTile.ShapeAnimationH: TAnimation;
begin
  Result := Shape.ShapeAnimationH;
end;

function TTMSFMXTile.ShapeAnimationW: TAnimation;
begin
  Result := Shape.ShapeAnimationW;
end;

function TTMSFMXTile.ShapeAnimationX: TAnimation;
begin
  Result := Shape.ShapeAnimationX;
end;

function TTMSFMXTile.ShapeAnimationY: TAnimation;
begin
  Result := Shape.ShapeAnimationY;
end;

function TTMSFMXTile.ShapeBadge: TControl;
begin
  Result := Shape.ShapeBadge;
end;

function TTMSFMXTile.ShapeBitmap: TControl;
begin
  Result := Shape.ShapeBitmap;
end;

function TTMSFMXTile.ShapeCaption: TControl;
begin
  Result := Shape.ShapeCaption;
end;

function TTMSFMXTile.ShapeContent: TControl;
begin
  Result := Shape.ShapeContent;
end;

function TTMSFMXTile.ShapeContentWrapper: TControl;
begin
  Result := Shape.ShapeContentWrapper;
end;

function TTMSFMXTile.ShapeDetail: TControl;
begin
  Result := Shape.ShapeDetail;
end;

function TTMSFMXTile.ShapeDetailAnimation: TAnimation;
begin
  Result := Shape.ShapeDetailAnimation;
end;

function TTMSFMXTile.ShapeDetailBitmap: TControl;
begin
  Result := Shape.ShapeDetailBitmap;
end;

function TTMSFMXTile.ShapeDetailCaption: TControl;
begin
  Result := Shape.ShapeDetailCaption;
end;

function TTMSFMXTile.ShapeDetailContent: TControl;
begin
  Result := Shape.ShapeDetailContent;
end;

function TTMSFMXTile.ShapeDetailNotes: TControl;
begin
  Result := Shape.ShapeDetailNotes;
end;

function TTMSFMXTile.ShapeNotes: TControl;
begin
  Result := Shape.ShapeNotes;
end;

procedure TTMSFMXTile.ShowDetail;
var
  a: TAnimation;
  f: TFloatAnimation;
  shp: TTMSFMXTileShape;
begin
  FOwner.FClearTiles := True;
  shp := Shape;
  if Assigned(shp) then
  begin
    a := shp.ShapeDetailAnimation;
    if Assigned(a) and (a is TFloatAnimation) then
    begin
      f := (a as TFloatAnimation);
      if Assigned(f) then
      begin
        f.Enabled := True;
        if DetailSizePercentage > 0 then
          f.StopValue := shp.Height - (DetailSizePercentage * shp.Height) / 100
        else
          f.StopValue := 0;

        f.OnProcess := AnimationDetailProcessing;
        f.OnFinish := AnimationDetailFinishedShowing;
        f.Start;
      end;
    end;
  end;
end;

function TTMSFMXTile.TileList: TTMSFMXTileList;
begin
  Result := FOwner;
end;

procedure TTMSFMXTile.UpdateBadge;
var
  shptile: TTMSFMXTileShape;
  obj: TControl;
  bd: TControl;
begin
  shptile := Shape;
  if Assigned(shptile) then
  begin
    obj := shptile.ShapeBadge;
    bd := FOwner.GetDefaultTileBadge;
    if not Assigned(obj) and assigned(bd) and (Badge <> '') then
    begin
      obj := bd.Clone(shptile) as TControl;
      obj.Visible := True;
      obj.StyleName := 'defaulttilebadge';
      obj.Position.X := -obj.Width / 2;
      obj.Position.Y := -obj.Height / 2;
      shptile.InsertObject(shptile.ChildrenCount, obj);
    end;

    if Assigned(obj) and (obj is TTMSFMXBadge) then
    begin
      (obj as TTMSFMXBadge).Text := Badge;
      obj.Visible := Badge <> '';
    end;

    if Assigned(obj) and Assigned(FOwner.OnCustomizeTileBadge) then
      FOwner.OnCustomizeTileBadge(Self, Self, obj);
  end;
end;

procedure TTMSFMXTile.UpdateBitmap;
var
  shp: TTMSFMXTileShape;
  shpbmp: TControl;
begin
  shp := Shape;
  if Assigned(shp) and (shp is TTMSFMXTileShape) then
  begin
    shpbmp := (shp as TTMSFMXTileShape).ShapeBitmap;
    if Assigned(shpbmp) and (shpbmp is TTMSFMXBitmap) then
    begin
      (shpbmp as TTMSFMXBitmap).BitmapName := BitmapName;
      (shpbmp as TTMSFMXBitmap).BitmapContainer := FOwner.BitmapContainer;
      (shpbmp as TTMSFMXBitmap).Bitmap.Assign(Bitmap);
    end;
  end;
end;

procedure TTMSFMXTile.UpdateDetailBitmap;
var
  shp: TTMSFMXTileShape;
  shpbmp: TControl;
begin
  shp := Shape;
  if Assigned(shp) and (shp is TTMSFMXTileShape) then
  begin
    shpbmp := (shp as TTMSFMXTileShape).ShapeDetailBitmap;
    if Assigned(shpbmp) and (shpbmp is TTMSFMXBitmap) then
    begin
      (shpbmp as TTMSFMXBitmap).BitmapName := DetailBitmapName;
      (shpbmp as TTMSFMXBitmap).BitmapContainer := FOwner.BitmapContainer;
      (shpbmp as TTMSFMXBitmap).Bitmap.Assign(DetailBitmap);
    end;
  end;

end;

procedure TTMSFMXTile.UpdateCaption;
var
  shp: TTMSFMXTileShape;
  shpc: TControl;
begin
  shp := Shape;
  if Assigned(shp) and (shp is TTMSFMXTileShape) then
  begin
    shpc := (shp as TTMSFMXTileShape).ShapeCaption;
    if Assigned(shpc) and (shpc is TText) then
    begin
      (shpc as TText).Text := Caption;
      if shpc is TTMSFMXHTMLText then
      begin
        (shpc as TTMSFMXHTMLText).HitTest := Caption <> '';
        (shpc as TTMSFMXHTMLText).BitmapContainer := FOwner.BitmapContainer;
        (shpc as TTMSFMXHTMLText).OnAnchorClick := FOwner.DoTileAnchorClick;
      end;
    end;
  end;
end;

procedure TTMSFMXTile.UpdateDetailCaption;
var
  shp: TTMSFMXTileShape;
  shpc: TControl;
begin
  shp := Shape;
  if Assigned(shp) and (shp is TTMSFMXTileShape) then
  begin
    shpc := (shp as TTMSFMXTileShape).ShapeDetailCaption;
    if Assigned(shpc) and (shpc is TText) then
    begin
      (shpc as TText).Text := DetailCaption;
      if shpc is TTMSFMXHTMLText then
        (shpc as TTMSFMXHTMLText).BitmapContainer := FOwner.BitmapContainer;
    end;
  end;

end;

procedure TTMSFMXTile.UpdateDetailNotes;
var
  shp: TTMSFMXTileShape;
  shpc: TControl;
begin
  shp := Shape;
  if Assigned(shp) and (shp is TTMSFMXTileShape) then
  begin
    shpc := (shp as TTMSFMXTileShape).ShapeDetailNotes;
    if Assigned(shpc) and (shpc is TText) then
    begin
      (shpc as TText).Text := DetailNotes;
      if shpc is TTMSFMXHTMLText then
        (shpc as TTMSFMXHTMLText).BitmapContainer := FOwner.BitmapContainer;
    end;
  end;
end;

procedure TTMSFMXTile.UpdateNotes;
var
  shp: TTMSFMXTileShape;
  shpc: TControl;
begin
  shp := Shape;
  if Assigned(shp) and (shp is TTMSFMXTileShape) then
  begin
    shpc := (shp as TTMSFMXTileShape).ShapeNotes;
    if Assigned(shpc) and (shpc is TText) then
    begin
      (shpc as TText).Text := Notes;
      if shpc is TTMSFMXHTMLText then
        (shpc as TTMSFMXHTMLText).BitmapContainer := FOwner.BitmapContainer;
    end;
  end;
end;

procedure TTMSFMXTile.UpdateTile;
begin
  if (csDestroying in FOwner.ComponentState) then
    Exit;
  if FOwner.FUpdateCount = 0 then
  begin
    FOwner.BeginUpdate;
    FOwner.EndUpdate;
  end;
end;

{ TTMSFMXTileShape }

procedure TTMSFMXTileShape.AnimationProcess(Sender: TObject);
begin
  Repaint;
end;

constructor TTMSFMXTileShape.Create(AOwner: TComponent);
begin
  inherited;
  Fill.Color := MakeColor(240,240,240);
  Stroke.Color := claGray;
  FSelected := False;
  FTransparent := False;
  {$IFDEF DELPHIXE6_LVL}
  Fill.Kind := TBrushKind.Solid;
  Stroke.Kind := TBrushKind.Solid;
  FFillSelected := TBrush.Create(TBrushKind.Solid, claLightblue);
  FStrokeSelected := TBrush.Create(TBrushKind.Solid, Stroke.Color);
  FFillDown := TBrush.Create(TBrushKind.Solid, claGray);
  FStrokeDown := TBrush.Create(TBrushKind.Solid, Stroke.Color);
  FFillHover := TBrush.Create(TBrushKind.Solid, claLightgray);
  FStrokeHover := TBrush.Create(TBrushKind.Solid, Stroke.Color);
  {$ELSE}
  Fill.Kind := TBrushKind.bkSolid;
  Stroke.Kind := TBrushKind.bkSolid;
  FFillSelected := TBrush.Create(TBrushKind.bkSolid, claLightblue);
  FStrokeSelected := TBrush.Create(TBrushKind.bkSolid, Stroke.Color);
  FFillDown := TBrush.Create(TBrushKind.bkSolid, claGray);
  FStrokeDown := TBrush.Create(TBrushKind.bkSolid, Stroke.Color);
  FFillHover := TBrush.Create(TBrushKind.bkSolid, claLightgray);
  FStrokeHover := TBrush.Create(TBrushKind.bkSolid, Stroke.Color);
  {$ENDIF}
end;

destructor TTMSFMXTileShape.Destroy;
begin
  FFillSelected.Free;
  FStrokeSelected.Free;
  FFillHover.Free;
  FStrokeHover.Free;
  FFillDown.Free;
  FStrokeDown.Free;
  inherited;
end;

procedure TTMSFMXTileShape.Paint;
begin
  if Transparent then
    Exit;

  case State of
    tsNormal:
    begin
      if Selected then
      begin
        Canvas.Fill.Assign(FillSelected);
        Canvas.Stroke.Assign(StrokeSelected);
      end
      else
      begin
        Canvas.Fill.Assign(Fill);
        Canvas.Stroke.Assign(Stroke);
      end;
    end;
    tsDisabled: ;
    tsHover:
    begin
      Canvas.Fill.Assign(FillHover);
      Canvas.Stroke.Assign(StrokeHover);
    end;
    tsDown:
    begin
      Canvas.Fill.Assign(FillDown);
      Canvas.Stroke.Assign(StrokeDown);
    end;
  end;

  Canvas.StrokeThickness := StrokeThickness;
  Canvas.StrokeCap := StrokeCap;
  Canvas.StrokeDash := StrokeDash;
  Canvas.StrokeJoin := StrokeJoin;
  Canvas.DrawRect(BoundsRect, 0, 0, AllCorners, AbsoluteOpacity);
  Canvas.FillRect(BoundsRect, 0, 0, AllCorners, AbsoluteOpacity);
end;

procedure TTMSFMXTileShape.SetFillDown(const Value: TBrush);
begin
  if FFillDown <> Value then
  begin
    FFillDown.Assign(Value);
    Repaint;
  end;
end;

procedure TTMSFMXTileShape.SetFillHover(const Value: TBrush);
begin
  if FFillHover <> Value then
  begin
    FFillHover := Value;
    Repaint;
  end;
end;

procedure TTMSFMXTileShape.SetFillSelected(const Value: TBrush);
begin
  if FFillSelected <> Value then
  begin
    FFillSelected.Assign(Value);
    Repaint;
  end;
end;

procedure TTMSFMXTileShape.SetSelected(const Value: Boolean);
begin
  if FSelected <> Value then
  begin
    FSelected := Value;
    Repaint;
  end;
end;

procedure TTMSFMXTileShape.SetState(const Value: TTMSFMXTileState);
var
  oldstate: TTMSFMXTileState;
begin
  if FState <> Value then
  begin
    oldState := FState;
    FState := Value;
    if Assigned(ATile) then
    begin
      if FState = tsHover then
      begin
        if ATile.EnableDetail then
          ATile.ShowDetail;
        if Assigned(ATile.FOwner) and Assigned(ATile.FOwner.OnTileMouseEnter) then
          ATile.FOwner.OnTileMouseEnter(Self, ATile);
      end
      else if FState = tsNormal then
      begin
        if ATile.EnableDetail then
          ATile.HideDetail;
        if Assigned(ATile.FOwner) and Assigned(ATile.FOwner.OnTileMouseLeave) and (oldstate = tsHover) then
          ATile.FOwner.OnTileMouseLeave(Self, ATile);
      end;
    end;

    if Assigned(ATile) then
      if Assigned(ATile.FOwner.OnTileStateChanged) then
        ATile.FOwner.OnTileStateChanged(Self, ATile, Self, State, OldState);

    Repaint;
  end;
end;

procedure TTMSFMXTileShape.SetStrokeDown(const Value: TBrush);
begin
  if FStrokeDown <> Value then
  begin
    FStrokeDown.Assign(Value);
    Repaint;
  end;
end;

procedure TTMSFMXTileShape.SetStrokeHover(const Value: TBrush);
begin
  if FStrokeHover <> Value then
  begin
    FStrokeHover.Assign(Value);
    Repaint;
  end;
end;

procedure TTMSFMXTileShape.SetStrokeSelected(const Value: TBrush);
begin
  if FStrokeSelected <> Value then
  begin
    FStrokeSelected.Assign(Value);
    Repaint;
  end;
end;

procedure TTMSFMXTileShape.SetTransparent(const Value: Boolean);
begin
  if FTransparent <> Value then
  begin
    FTransparent := Value;
    Repaint;
  end;
end;

function TTMSFMXTileShape.ShapeAnimationH: TAnimation;
var
  I: Integer;
begin
  Result := nil;
  for I := 0 to childrenCount - 1 do
  begin
    if (Children[I].StyleName = 'defaulttileanimh') and (Children[I] is TAnimation) then
    begin
      Result := Children[I] as TAnimation;
      Break;
    end;
  end;
end;

function TTMSFMXTileShape.ShapeAnimationW: TAnimation;
var
  I: Integer;
begin
  Result := nil;
  for I := 0 to childrenCount - 1 do
  begin
    if (Children[I].StyleName = 'defaulttileanimw') and (Children[I] is TAnimation) then
    begin
      Result := Children[I] as TAnimation;
      Break;
    end;
  end;
end;

function TTMSFMXTileShape.ShapeAnimationX: TAnimation;
var
  I: Integer;
begin
  Result := nil;
  for I := 0 to childrenCount - 1 do
  begin
    if (Children[I].StyleName = 'defaulttileanimx') and (Children[I] is TAnimation) then
    begin
      Result := Children[I] as TAnimation;
      Break;
    end;
  end;
end;

function TTMSFMXTileShape.ShapeAnimationY: TAnimation;
var
  I: Integer;
begin
  Result := nil;
  for I := 0 to childrenCount - 1 do
  begin
    if (Children[I].StyleName = 'defaulttileanimy') and (Children[I] is TAnimation) then
    begin
      Result := Children[I] as TAnimation;
      Break;
    end;
  end;
end;

function TTMSFMXTileShape.ShapeBadge: TControl;
var
  I: Integer;
  obj: TFmxObject;
begin
  Result := nil;
  for I := 0 to ChildrenCount - 1 do
  begin
    obj := Children[I];
    if (obj is TTMSFMXBadge) and (obj.StyleName = 'defaulttilebadge') then
    begin
      Result := Children[I] as TControl;
      Break;
    end;
  end;
end;

function TTMSFMXTileShape.ShapeBitmap: TControl;
var
  obj: TFmxObject;
begin
  Result := nil;
  obj := ShapeContentWrapper;
  if Assigned(obj) then
    obj := obj.FindStyleResource('defaulttilebitmap');
  if assigned(obj) and (obj is TControl) then
    Result := obj as TControl;
end;

function TTMSFMXTileShape.ShapeCaption: TControl;
var
  obj: TFmxObject;
begin
  Result := nil;
  obj := ShapeContentWrapper;
  if Assigned(obj) then
    obj := obj.FindStyleResource('defaulttilecaption');
  if assigned(obj) and (obj is TControl) then
    Result := obj as TControl;
end;

function TTMSFMXTileShape.ShapeContent: TControl;
var
  obj: TFmxObject;
begin
  Result := nil;
  obj := ShapeContentWrapper;
  if Assigned(obj) then
    obj := obj.FindStyleResource('defaulttilecontent');
  if assigned(obj) and (obj is TControl) then
    Result := obj as TControl;
end;

function TTMSFMXTileShape.ShapeContentWrapper: TControl;
var
  I: Integer;
begin
  Result := nil;
  for I := 0 to ChildrenCount - 1 do
  begin
    if Children[I].StyleName = 'defaulttilecontentwrapper' then
    begin
      Result := Children[I] as TControl;
      Break;
    end;
  end;
end;

function TTMSFMXTileShape.ShapeDetail: TControl;
var
  obj: TFmxObject;
begin
  Result := nil;
  obj := ShapeContentWrapper;
  if Assigned(obj) then
    obj := obj.FindStyleResource('defaultdetailtile');
  if assigned(obj) and (obj is TControl) then
    Result := obj as TControl;
end;

function TTMSFMXTileShape.ShapeDetailAnimation: TAnimation;
var
  obj: TFmxObject;
begin
  Result := nil;
  obj := ShapeDetail;
  if Assigned(obj) then
    obj := obj.FindStyleResource('defaultdetailtileanim');
  if assigned(obj) and (obj is TAnimation) then
    Result := obj as TAnimation;
end;

function TTMSFMXTileShape.ShapeDetailBitmap: TControl;
var
  obj: TFmxObject;
begin
  Result := nil;
  obj := ShapeContentWrapper;
  if Assigned(obj) then
    obj := obj.FindStyleResource('defaultdetailtilebitmap');
  if assigned(obj) and (obj is TControl) then
    Result := obj as TControl;
end;

function TTMSFMXTileShape.ShapeDetailCaption: TControl;
var
  obj: TFmxObject;
begin
  Result := nil;
  obj := ShapeContentWrapper;
  if Assigned(obj) then
    obj := obj.FindStyleResource('defaultdetailtilecaption');
  if assigned(obj) and (obj is TControl) then
    Result := obj as TControl;
end;

function TTMSFMXTileShape.ShapeDetailContent: TControl;
var
  obj: TFmxObject;
begin
  Result := nil;
  obj := ShapeContentWrapper;
  if Assigned(obj) then
    obj := obj.FindStyleResource('defaultdetailtilecontent');
  if assigned(obj) and (obj is TControl) then
    Result := obj as TControl;

end;

function TTMSFMXTileShape.ShapeDetailNotes: TControl;
var
  obj: TFmxObject;
begin
  Result := nil;
  obj := ShapeContentWrapper;
  if Assigned(obj) then
    obj := obj.FindStyleResource('defaultdetailtilenotes');
  if assigned(obj) and (obj is TControl) then
    Result := obj as TControl;
end;

function TTMSFMXTileShape.ShapeNotes: TControl;
var
  obj: TFmxObject;
begin
  Result := nil;
  obj := ShapeContentWrapper;
  if Assigned(obj) then
    obj := obj.FindStyleResource('defaulttilenotes');
  if assigned(obj) and (obj is TControl) then
    Result := obj as TControl;
end;

{ TTMSFMXTileListArrowShape }

constructor TTMSFMXTileListArrowShape.Create(AOwner: TComponent);
begin
  inherited;
  {$IFDEF DELPHIXE6_LVL}
  FArrowFill := TBrush.Create(TBrushKind.Solid, claWhite);
  FArrowStroke := TBrush.Create(TBrushKind.Solid, $FF535353);
  {$ELSE}
  FArrowFill := TBrush.Create(TBrushKind.bkSolid, claWhite);
  FArrowStroke := TBrush.Create(TBrushKind.bkSolid, $FF535353);
  {$ENDIF}
  FDirection := sdLeft;
  FArrowSize := 15;
  CanFocus := True;
end;

destructor TTMSFMXTileListArrowShape.Destroy;
begin
  FArrowFill.Free;
  FArrowStroke.Free;
  inherited;
end;

procedure TTMSFMXTileListArrowShape.Paint;
var
  pth: TPathData;
  r: TRectF;
  s: Single;
begin
  inherited;
  Canvas.Fill.Assign(ArrowFill);
  Canvas.Stroke.Assign(ArrowStroke);

  pth := TPathData.Create;
  r := BoundsRect;
  s := ArrowSize;
  case Direction of
    sdLeft:
    begin
      pth.MoveTo(PointF(r.Left + r.Width / 2 - s / 2, r.Top + r.Height / 2));
      pth.LineTo(PointF(r.Left + r.Width / 2 + s / 2, r.Top + r.Height / 2 - s / 2));
      pth.LineTo(PointF(r.Left + r.Width / 2 + s / 2, r.Top + r.Height / 2 - s / 2));
      pth.LineTo(PointF(r.Left + r.Width / 2 + s / 2, r.Top + r.Height / 2 + s / 2));
      pth.LineTo(PointF(r.Left + r.Width / 2 - s / 2, r.Top + r.Height / 2));
    end;
    sdRight:
    begin
      pth.MoveTo(PointF(r.Left + r.Width / 2 + s / 2, r.Top + r.Height / 2));
      pth.LineTo(PointF(r.Left + r.Width / 2 - s / 2, r.Top + r.Height / 2 + s / 2));
      pth.LineTo(PointF(r.Left + r.Width / 2 - s / 2, r.Top + r.Height / 2 + s / 2));
      pth.LineTo(PointF(r.Left + r.Width / 2 - s / 2, r.Top + r.Height / 2 - s / 2));
      pth.LineTo(PointF(r.Left + r.Width / 2 + s / 2, r.Top + r.Height / 2));
    end;
  end;
  pth.ClosePath;
  Canvas.FillPath(pth, Opacity);
  Canvas.DrawPath(pth, Opacity);
  pth.Free;
end;

procedure TTMSFMXTileListArrowShape.SetArrowFill(const Value: TBrush);
begin
  if FArrowFill <> Value then
  begin
    FArrowFill.Assign(Value);
    Repaint;
  end;
end;

procedure TTMSFMXTileListArrowShape.SetArrowSize(const Value: Single);
begin
  if FArrowSize <> Value then
  begin
    FArrowSize := Value;
    Repaint;
  end;
end;

procedure TTMSFMXTileListArrowShape.SetArrowStroke(const Value: TBrush);
begin
  if FArrowStroke <> Value then
  begin
    FArrowStroke := Value;
    Repaint;
  end;
end;

procedure TTMSFMXTileListArrowShape.SetDirection(
  const Value: TTMSFMXTileListArrowShapeDirection);
begin
  FDirection := Value;
  Repaint;
end;

{ TTMSFMXTileListBulletShape }

constructor TTMSFMXTileListBulletShape.Create(AOwner: TComponent);
begin
  inherited;
  FSelected := False;
  {$IFDEF DELPHIXE6_LVL}
  FFillSelected := TBrush.Create(TBrushKind.Solid, claOrange);
  FStrokeSelected := TBrush.Create(TBrushKind.Solid, claBlack);
  {$ELSE}
  FFillSelected := TBrush.Create(TBrushKind.bkSolid, claOrange);
  FStrokeSelected := TBrush.Create(TBrushKind.bkSolid, claBlack);
  {$ENDIF}
end;

destructor TTMSFMXTileListBulletShape.Destroy;
begin
  FFillSelected.Free;
  FStrokeSelected.Free;
  inherited;
end;

procedure TTMSFMXTileListBulletShape.Paint;
begin
  if Selected then
  begin
    Fill.Assign(FillSelected);
    Stroke.Assign(StrokeSelected);
  end;
  inherited;

end;

procedure TTMSFMXTileListBulletShape.SetFillSelected(const Value: TBrush);
begin
  if FFillSelected <> Value then
  begin
    FFillSelected.Assign(Value);
    Repaint;
  end;
end;

procedure TTMSFMXTileListBulletShape.SetSelected(const Value: Boolean);
begin
  if FSelected <> Value then
  begin
    FSelected := Value;
    Repaint;
  end;
end;

procedure TTMSFMXTileListBulletShape.SetStrokeSelected(const Value: TBrush);
begin
  if FStrokeSelected <> Value then
  begin
    FStrokeSelected.Assign(Value);
    Repaint;
  end;
end;

{ TTMSFMXStyle }

constructor TTMSFMXStyle.Create(Collection: TCollection);
begin
  inherited;
  FOwner := (Collection as TTMSFMXStyles).FOwner;
  FStyleName := '[custom]defaulttile' + inttostr((collection as TTMSFMXStyles).Count);
  UpdateTileStyle;
end;

destructor TTMSFMXStyle.Destroy;
begin
  inherited;
  UpdateTileStyle;
end;

procedure TTMSFMXStyle.SetStyleName(const Value: String);
begin
  FStyleName := Value;
end;

procedure TTMSFMXStyle.UpdateTileStyle;
var
  obj: TControl;
begin
  if csDesigning in FOwner.ComponentState then
  begin
    obj := nil;
    if Assigned(FOwner.Scene) and Assigned(FOwner.Scene.GetStyleBook) and Assigned(FOwner.Scene.GetStyleBook.Root) then
    begin
      if FOwner.StyleLookup <> '' then
        obj := TControl(FOwner.Scene.GetStyleBook.FindStyleResource(FOwner.StyleLookup))
      else
        obj := TControl(FOwner.Scene.GetStyleBook.FindStyleResource(FOwner.GetClassStyleName));
    end;

    if Assigned(obj) then
      FOwner.UpdateTileStyles(obj);

    FOwner.ApplyStyle;
    FOwner.Repaint;
  end
  else
    FOwner.UpdateTileStyles(FOwner);
end;

{ TTMSFMXStyles }

function TTMSFMXStyles.Add: TTMSFMXStyle;
begin
  Result := TTMSFMXStyle(inherited Add);
end;

procedure TTMSFMXStyles.Clear;
begin
  TCollection(Self).Clear;
  FOwner.ApplyStyle;
  FOwner.Repaint;
end;

constructor TTMSFMXStyles.Create(AOwner: TTMSFMXTileList);
begin
  inherited Create(TTMSFMXStyle);
  FOwner := AOwner;
end;

function TTMSFMXStyles.GetItem(
  Index: Integer): TTMSFMXStyle;
begin
  Result := TTMSFMXStyle(inherited Items[Index]);
end;

function TTMSFMXStyles.GetOwner: TPersistent;
begin
  Result := FOwner;
end;

function TTMSFMXStyles.Insert(Index: Integer): TTMSFMXStyle;
begin
  Result := TTMSFMXStyle(inherited Insert(Index));
end;

procedure TTMSFMXStyles.SetItem(Index: Integer;
  const Value: TTMSFMXStyle);
begin
  inherited Items[Index] := Value;
end;

{ TTMSFMXClipRectangle }

function TTMSFMXClipRectangle.GetClipRect: TRectF;
begin
  Result := RectF(1, 1, Width - 1, Height - 1);
end;

{ TTMSFMXScrollBox }

procedure TTMSFMXScrollBox.AddContent(AContent: TControl);
begin
  FContentChild := AContent;
  FContentChild.Parent := FContent;
  UpdateScrollBars;
end;

constructor TTMSFMXScrollBox.Create(AOwner: TComponent);
begin
  inherited;
  HitTest := False;
  FHScrollBar := TScrollBar.Create(Self);
  FHScrollBar.OnChange := HScrollBarChanged;
  {$IFDEF DELPHIXE6_LVL}
  FHScrollBar.Orientation := TOrientation.Horizontal;
  FHScrollBar.Align := TAlignLayout.Bottom;
  {$ELSE}
  FHScrollBar.Orientation := TOrientation.orHorizontal;
  FHScrollBar.Align := TAlignLayout.alBottom;
  {$ENDIF}
  FHScrollBar.Stored := False;
  FHScrollBar.Parent := Self;

  FContent := TLayout.Create(Self);
  FContent.Stored := False;
  {$IFDEF DELPHIXE6_LVL}
  FContent.Align := TAlignLayout.Client;
  {$ELSE}
  FContent.Align := TAlignLayout.alClient;
  {$ENDIF}
  FContent.Parent := Self;
  FContent.HitTest := False;
  FContent.ClipChildren := True;
end;

destructor TTMSFMXScrollBox.Destroy;
begin
  if Assigned(FHScrollBar) then
    FHScrollBar.Free;

  if Assigned(FContent) then
    FContent.Free;
  inherited;
end;

procedure TTMSFMXScrollBox.DoUpdateScrollBars;
begin
  if Assigned(FContentChild) and Assigned(FHScrollBar) then
  begin
    FHScrollBar.Max := FContentChild.Width;
    FHScrollBar.ViewportSize := FContent.Width;
    FHScrollBar.Visible := FHScrollBar.ViewportSize < FHScrollBar.Max;
  end;
end;

procedure TTMSFMXScrollBox.HScrollBarChanged(Sender: TObject);
begin
  if Assigned(FContentChild) then
    FContentChild.Position.X := -HScrollBar.Value;
end;

procedure TTMSFMXScrollBox.Resize;
begin
  inherited;
  UpdateScrollBars;
end;

procedure TTMSFMXScrollBox.ScrollTo(DX, DY: Single);
begin
  if Assigned(HScrollBar) then
     HScrollBar.Value := -DX;
end;

procedure TTMSFMXScrollBox.UpdateScrollBars;
begin
  DoUpdateScrollBars;
end;

initialization
begin
  RegisterFmxClasses([TTMSFMXTileShape, TTMSFMXClipRectangle, TTMSFMXTileListArrowShape,
    TTMSFMXTileListBulletShape, TTMSFMXScrollBox]);
end;

end.
