unit UTilesGrid;
interface
uses System.SysUtils, System.Classes, System.Types, System.UITypes,
FMX.Graphics, FMX.Types, FMX.Controls, FMX.Layouts, FMX.StdCtrls,
System.Generics.Collections;
type
TTileItem =
class(TControl)
protected
FZoomFactor: Single;
FBackgroundColor: TAlphaColor;
FOnPaint: TNotifyEvent;
procedure Paint;
override;
procedure DoEnter;
override;
procedure DoExit;
override;
procedure SetZoomFactor(AValue: Single);
procedure SetBackgroundColor(AValue: TAlphaColor);
public
constructor Create(AOwner: TComponent);
override;
destructor Destroy;
override;
published
property BackgroundColor: TAlphaColor
read FBackgroundColor
write SetBackgroundColor;
property ZoomFactor: Single
read FZoomFactor
write SetZoomFactor;
property OnPaint: TNotifyEvent
read FOnPaint
write FOnPaint;
end;
TTileRow =
class(TControl)
protected
FTiles: TObjectList<TTileItem>;
FItemIndex: Integer;
FScrollBox: THorzScrollBox;
FTitle: TLabel;
FSelected: TTileItem;
function GetCount: Integer;
function GetItem(AIndex: Integer): TTileItem;
procedure SetItemIndex(AIndex: Integer);
function GetScrollPos: Single;
procedure SetScrollPos(
const AValue: Single);
public
constructor Create(AOwner: TComponent);
override;
destructor Destroy;
override;
function Add: TTileItem;
procedure Delete(
const AIndex: Integer);
procedure ScrollIntoView(
const AItem: TTileItem);
property Items[
index: Integer]: TTileItem
read GetItem;
default;
property Count: Integer
read GetCount;
property ItemIndex: Integer
read FItemIndex
write SetItemIndex;
published
property ScrollPos: Single
read GetScrollPos
write SetScrollPos;
property Title: TLabel
read FTitle;
property Scale;
end;
TTileGrid =
class(TControl)
protected
FRows: TObjectList<TTileRow>;
FItemIndex: Integer;
FScrollBox: TVertScrollBox;
function GetItem(AIndex: Integer): TTileRow;
function GetCount: Integer;
procedure SetItemIndex(AIndex: Integer);
function GetScrollPos: Single;
procedure SetScrollPos(
const AValue: Single);
public
constructor Create(AOwner: TComponent);
override;
destructor Destroy;
override;
procedure ScrollIntoView(
const AItem: TTileRow);
function Add: TTileRow;
procedure Delete(
const AIndex: Integer);
property Items[
index: Integer]: TTileRow
read GetItem;
default;
property Count: Integer
read GetCount;
property ItemIndex: Integer
read FItemIndex
write SetItemIndex;
published
property ScrollPos: Single
read GetScrollPos
write SetScrollPos;
property Scale;
end;
implementation
uses FMX.Ani;
const
cZoomIn = 0.9;
cZoomOut = 1.0;
cZoomTime = 0.2;
type
TCustomScrollBoxCracker =
class(TCustomScrollBox);
function IfThen(
const AState: Boolean;
const ATrue, AFalse: Integer)
: Integer;
inline;
begin
if AState
then
result := ATrue
else
result := AFalse;
end;
{ TTileItem }
constructor TTileItem.Create(AOwner: TComponent);
begin
inherited;
CanFocus := true;
ZoomFactor := 0;
FBackgroundColor := TAlphaColors.Gray;
end;
destructor TTileItem.Destroy;
begin
inherited;
end;
procedure TTileItem.DoEnter;
begin
BringToFront;
TAnimator.AnimateFloat(self, '
ZoomFactor', cZoomOut, cZoomTime,
TAnimationType.
Out, TInterpolationType.Quadratic);
if Owner
is TTileRow
then
TTileRow(Owner).ScrollIntoView(self);
end;
procedure TTileItem.DoExit;
begin
SendToBack;
TAnimator.AnimateFloat(self, '
ZoomFactor', cZoomIn, cZoomTime, TAnimationType.
In, TInterpolationType.Linear);
end;
procedure TTileItem.SetBackgroundColor(AValue: TAlphaColor);
begin
if FBackgroundColor <> AValue
then
begin
FBackgroundColor := AValue;
repaint;
end;
end;
procedure TTileItem.SetZoomFactor(AValue: Single);
begin
if AValue < cZoomIn
then
AValue := cZoomIn;
if AValue > cZoomOut
then
AValue := cZoomOut;
if FZoomFactor <> AValue
then
begin
FZoomFactor := AValue;
repaint;
end;
end;
procedure TTileItem.Paint;
var
w, h: Single;
R: TRectF;
begin
if Locked
then
Exit;
w := Width * FZoomFactor;
h := Height * FZoomFactor;
R := RectF((Width - w) / 2, (Height - h) / 2, w, h);
Canvas.Fill.Color := FBackgroundColor;
Canvas.FillRect(R, 5, 5, AllCorners, AbsoluteOpacity);
if assigned(FOnPaint)
then
FOnPaint(self);
end;
{ TTileRow }
constructor TTileRow.Create(AOwner: TComponent);
begin
inherited;
Height := 300;
FTiles := TObjectList<TTileItem>.Create(true);
FItemIndex := -1;
FTitle := TLabel.Create(self);
FTitle.Parent := self;
FTitle.Align := TAlignLayout.Top;
FTitle.Text := '
RowTitle';
FScrollBox := THorzScrollBox.Create(self);
FScrollBox.Parent := self;
FScrollBox.Align := TAlignLayout.Client;
FScrollBox.ShowScrollBars := false;
HitTest := true;
end;
destructor TTileRow.Destroy;
begin
FTiles.Free;
FScrollBox.Free;
inherited;
end;
function TTileRow.GetCount: Integer;
begin
result := FTiles.Count;
end;
function TTileRow.Add: TTileItem;
begin
result := TTileItem.Create(self);
result.Parent := FScrollBox;
FTiles.Add(result);
end;
function TTileRow.GetScrollPos: Single;
begin
if assigned(TCustomScrollBoxCracker(FScrollBox).HScrollBar)
then
result := TCustomScrollBoxCracker(FScrollBox).HScrollBar.Value;
end;
procedure TTileRow.SetScrollPos(
const AValue: Single);
begin
if assigned(TCustomScrollBoxCracker(FScrollBox).HScrollBar)
then
TCustomScrollBoxCracker(FScrollBox).HScrollBar.Value := AValue;
end;
procedure TTileRow.ScrollIntoView(
const AItem: TTileItem);
var
NewScrollViewPos: Single;
MinWidth, MinHeight: Single;
begin
if FSelected <> AItem
then
begin
NewScrollViewPos := ScrollPos;
MinWidth := 0;
MinHeight := 0;
if (AItem.BoundsRect.Left - NewScrollViewPos < MinWidth)
then
NewScrollViewPos := AItem.BoundsRect.Left;
if (AItem.BoundsRect.Right - NewScrollViewPos > FScrollBox.Width)
then
NewScrollViewPos := AItem.BoundsRect.Right - FScrollBox.Width;
TAnimator.AnimateFloat(self, '
ScrollPos', NewScrollViewPos, cZoomTime,
TAnimationType.
In, TInterpolationType.Linear);
FItemIndex := FTiles.IndexOf(AItem);
FSelected := AItem;
end;
if Owner
is TTileGrid
then
TTileGrid(Owner).ScrollIntoView(self);
end;
procedure TTileRow.Delete(
const AIndex: Integer);
begin
FTiles.Delete(AIndex);
end;
function TTileRow.GetItem(AIndex: Integer): TTileItem;
begin
if (AIndex >= 0)
and (AIndex < FTiles.Count)
then
result := FTiles[AIndex]
else
result :=
nil;
end;
procedure TTileRow.SetItemIndex(AIndex: Integer);
begin
if AIndex < 0
then
AIndex := Count - 1
else if AIndex >= Count
then
AIndex := 0;
FTiles[AIndex].SetFocus;
end;
{ TTileGrid }
constructor TTileGrid.Create(AOwner: TComponent);
begin
inherited;
FRows := TObjectList<TTileRow>.Create;
FItemIndex := -1;
FScrollBox := TVertScrollBox.Create(self);
FScrollBox.Parent := self;
FScrollBox.Align := TAlignLayout.Client;
FScrollBox.ShowScrollBars := false;
HitTest := true;
end;
destructor TTileGrid.Destroy;
begin
FRows.Free;
FScrollBox.Free;
inherited;
end;
function TTileGrid.GetItem(AIndex: Integer): TTileRow;
begin
result := FRows[AIndex];
end;
function TTileGrid.GetCount: Integer;
begin
result := FRows.Count;
end;
function TTileGrid.Add: TTileRow;
begin
result := TTileRow.Create(self);
FRows.Add(result);
result.Parent := FScrollBox;
result.Align := TAlignLayout.Top;
end;
procedure TTileGrid.Delete(
const AIndex: Integer);
begin
FRows.Delete(AIndex);
end;
procedure TTileGrid.SetItemIndex(AIndex: Integer);
begin
if AIndex < 0
then
AIndex := Count - 1
else if AIndex >= Count
then
AIndex := 0;
with FRows[AIndex]
do
ItemIndex := IfThen(ItemIndex = -1, 0, ItemIndex);
end;
function TTileGrid.GetScrollPos: Single;
begin
if assigned(TCustomScrollBoxCracker(FScrollBox).VScrollBar)
then
result := TCustomScrollBoxCracker(FScrollBox).VScrollBar.Value;
end;
procedure TTileGrid.SetScrollPos(
const AValue: Single);
begin
if assigned(TCustomScrollBoxCracker(FScrollBox).VScrollBar)
then
TCustomScrollBoxCracker(FScrollBox).VScrollBar.Value := AValue;
end;
procedure TTileGrid.ScrollIntoView(
const AItem: TTileRow);
var
NewScrollViewPos: Single;
MinWidth, MinHeight: Single;
i: Integer;
begin
NewScrollViewPos := ScrollPos;
MinWidth := 0;
MinHeight := 0;
if (AItem.BoundsRect.Top - NewScrollViewPos < MinHeight)
then
NewScrollViewPos := AItem.BoundsRect.Top;
if (AItem.BoundsRect.Bottom - NewScrollViewPos > FScrollBox.Height)
then
NewScrollViewPos := AItem.BoundsRect.Bottom - FScrollBox.Height;
TAnimator.AnimateFloat(self, '
ScrollPos', NewScrollViewPos, cZoomTime,
TAnimationType.
In, TInterpolationType.Linear);
AItem.SetFocus;
FItemIndex := FRows.IndexOf(AItem);
end;
end.