diff --git a/Alexandria(11)/FMX.ListView.Types.pas b/Alexandria(11)/FMX.ListView.Types.pas new file mode 100644 index 0000000..bd6e77c --- /dev/null +++ b/Alexandria(11)/FMX.ListView.Types.pas @@ -0,0 +1,3745 @@ +{*******************************************************} +{ } +{ Delphi FireMonkey Platform } +{ } +{ Copyright(c) 2011-2021 Embarcadero Technologies, Inc. } +{ All rights reserved } +{ } +{*******************************************************} + +unit FMX.ListView.Types; + +interface + +{$SCOPEDENUMS ON} + +uses + System.Types, System.UITypes, System.Classes, System.Generics.Collections, System.Generics.Defaults, System.SysUtils, + FMX.Types, FMX.Controls,FMX.TextLayout, System.Math.Vectors, System.Rtti, FMX.Objects, FMX.Graphics, FMX.ActnList, + FMX.Styles.Objects, FMX.ImgList; + +{$IF DEFINED(IOS) OR DEFINED(ANDROID)} +{$DEFINE LISTVIEW_TOUCH} +{$ENDIF} + +{.$DEFINE PIXEL_ALIGNMENT} +{.$DEFINE DRAW_ITEM_MARGINS} + +type + TListItemAlign = (Leading, Center, Trailing); + TListItemPurpose = (None, Header, Footer); + TListItemPurposes = set of TListItemPurpose; + + TListItemPurposeHelper = record helper for TListItemPurpose + function ToString: string; + end; + + TListItem = class; + IListViewAdapter = interface; + IListViewController = interface; + + TListItemStyleResources = class; + + TListItemDrawState = (Selected, Deleting, EditMode); + TListItemDrawStates = set of TListItemDrawState; + + TListItemDrawable = class; + TListItemView = class; + TListItemCallbackOp = (CreateDrawables, InvalidateOwner, Click); + TListItemCallback = TProc; + + /// TListItem view is comprised of TListViewDrawables. These are the actual + /// view elements that are being painted in the item cells. + TListItemDrawable = class(TInterfacedPersistent) + public type + TParams = record + AbsoluteOpacity: Single; + ItemSelectedAlpha: Single; + DeletingUnwantedOpacity: Single; + EditModeTransitionAlpha: Single; + ParentAbsoluteRect: TRectF; + Images: TCustomImageList; + end; + strict private + FPlaceOffsetX: TPosition; + protected type + TStyleResource = (FontSize, FontFamily, FontStyle, TextColor, SelectedTextColor, + TextShadowColor, PressedTextColor); + TStyleResources = set of TStyleResource; + protected const + TextResources: set of TStyleResource = [TStyleResource.FontFamily, + TStyleResource.FontSize, TStyleResource.FontStyle, TStyleResource.TextColor, + TStyleResource.TextShadowColor, TStyleResource.SelectedTextColor, TStyleResource.PressedTextColor]; + private + FAlign: TListItemAlign; + FVertAlign: TListItemAlign; + FVisible: Boolean; + FWidth: Single; + FHeight: Single; + FOpacity: Single; + FUpdating: Integer; + NeedRepaint: Boolean; + FOnSelect: TNotifyEvent; + FName: string; + [Weak] FTagObject: TObject; + FTagFloat: Single; + FTagString: string; + FLocalRect: TRectF; + FStyleValuesNeedUpdate: TStyleResources; + FController: IListViewController; + FCallback: TListItemCallback; + + procedure SetOneDimension(const Index: Integer; const Value: Single); + procedure SetSize(const Value: TPointF); + function GetSize: TPointF; + procedure SetOpacity(const Value: Single); + procedure SetAlign(const Value: TListItemAlign); + procedure SetVertAlign(const Value: TListItemAlign); + procedure SetVisible(const Value: Boolean); + function GetData: TValue; virtual; + procedure SetData(const Value: TValue); virtual; + function GetPlaceOffset: TPosition; inline; + procedure SetInvalidateCallback(const Callback: TListItemCallback); virtual; + procedure PlaceOffsetChanged(Sender: TObject); + + protected + /// Called when the Size of this drawable changes + procedure DoResize; virtual; + /// Called when the Opacity of this drawable changes + procedure DoOpacityChange; virtual; + /// Called when TListItem comprising + /// this drawable is selected. + procedure DoSelect; virtual; + /// Called when PlaceOffset changes + procedure DoPlaceOffsetChanged; virtual; + /// Called when Align or VertAlign changes + procedure DoAlignChanged; virtual; + /// Finds an embedded TControl at given Point + function ObjectAtPoint(const Point: TPointF): TControl; virtual; + + /// Handle MouseDown event. Called by + /// TListItem.MouseDown + function MouseDown(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF): Boolean; virtual; + /// Handle MouseMove event. Called by host + /// TListItem.MouseMove + procedure MouseMove(const Shift: TShiftState; const MousePos: TPointF); virtual; + /// Handle MouseUp event. Called by + /// TListItem.MouseUp + procedure MouseUp(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF); virtual; + /// Called by UpdateValuesFromResources. + /// Updates default parameters defined by style in descendants such as font, font syle and colors. + procedure DoUpdateValuesFromResources(const Resources: TListItemStyleResources; const Purpose: TListItemPurpose); virtual; + public + constructor Create(const AOwner: TListItem); virtual; + destructor Destroy; override; + function ToString: string; override; + + /// Return amount of rendering passes. See TListViewBase.DrawListItems + function GetRenderPassCount: Integer; virtual; + /// Align and calculate this drawable's local rectangle for given item's DestRect and DrawStates + procedure CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); virtual; + /// Return True if Point is inside local rect + function InLocalRect(const Point: TPointF): Boolean; + /// Request repaint. When between BeginUpdate/EndUpdate the request will be held back until the + /// update cycle is finished + procedure Invalidate; + /// Render this drawable + /// Canvas destination canvas + /// DrawItemIndex index within parent TListView of item being rendered + /// DrawStates which of the item states to render: Selected, Deleting, EditMode; + /// see TListItemDrawStates + /// Resources default style resources to use for rendering; + /// TListItemStyleResources + /// Params extra rendering parameters; see + /// TListItemDrawable.TParams + /// + procedure Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates; + const Resources: TListItemStyleResources; const Params: TParams; const SubPassNo: Integer = 0); virtual; abstract; + /// Begin update. During update calling InvalidateCallback + /// will be held back until EndUpdate + procedure BeginUpdate; + /// End update. If invalidation was required during the update cycle, + /// InvalidateCallback will be invoked + procedure EndUpdate; + /// Initializes or updates default parameters defined by style in descendants such as font, font syle + /// and colors. + procedure UpdateValuesFromResources(const Resources: TListItemStyleResources; const Purpose: TListItemPurpose); + /// Set flag that enables UpdateValuesFromResources + procedure UpdateValuesFromStyle; + /// Local width of list item inside its designated area + property Width: Single index 0 read FWidth write SetOneDimension; + /// Local height of list item inside its designated area + property Height: Single index 1 read FHeight write SetOneDimension; + /// Size of this drawable + property Size: TPointF read GetSize write SetSize; + /// Horizontal alignment of drawable inside its designated area + property Align: TListItemAlign read FAlign write SetAlign; + /// Vertical alignment of drawable inside its designated area + property VertAlign: TListItemAlign read FVertAlign write SetVertAlign; + /// Determines whether the current drawable is visible or not + property Visible: Boolean read FVisible write SetVisible; + /// The offset in logical units regarding aligned location for finer placement control + property PlaceOffset: TPosition read GetPlaceOffset; + /// Name of this drawable + property Name: string read FName write FName; + /// Drawing opacity + property Opacity: Single read FOpacity write SetOpacity; + /// LocalRect of this drawable + property LocalRect: TRectF read FLocalRect; + /// Invoked when owner TListItem is selected + property OnSelect: TNotifyEvent read FOnSelect write FOnSelect; + // Polymorphic property access + property Data: TValue read GetData write SetData; + /// User-defined object reference for this drawable + property TagObject: TObject read FTagObject write FTagObject; + /// User-defined floating-point number for this drawable + property TagFloat: Single read FTagFloat write FTagFloat; + /// User-defined string for this drawable + property TagString: string read FTagString write FTagString; + /// Callback invoked when item is being invalidated + /// TListItemCallback + /// + property InvalidateCallback: TListItemCallback write SetInvalidateCallback; + end; + + /// Declared for compatibility + TListItemObject = class(TListItemDrawable) + end; + + /// Represents a text drawable in ListView items + TListItemText = class(TListItemDrawable) + private const + DefaultFontFamily = 'Helvetica'; // do not localize + DefaultFontSize = 14; + ShadowOffset: TPointF = (X: 0; Y: 1); + private type + TFontSettings = record + Family: string; + Size: Single; + Style: TFontStyles; + end; + strict private + FFontX: TFont; + FFontSettings: TFontSettings; + private + FTextLayout: TTextLayout; + FText: string; + FTextAlign: TTextAlign; + FTextVertAlign: TTextAlign; + FWordWrap: Boolean; + LayoutChanged: Boolean; + FTextColor: TAlphaColor; + FSelectedTextColor: TAlphaColor; + FTrimming: TTextTrimming; + FTextShadowOffsetX: TPosition; + FTextShadowColor: TAlphaColor; + FIsDetailText: Boolean; + + procedure FontChanged(Sender: TObject); + procedure TextShadowOffsetChanged(Sender: TObject); + procedure SetText(const Value: string); + procedure SetTextAlign(const Value: TTextAlign); + procedure SetTextVertAlign(const Value: TTextAlign); + procedure SetWordWrap(const Value: Boolean); + procedure SetTextColor(const Value: TAlphaColor); + procedure SetTrimming(const Value: TTextTrimming); + procedure SetSelectedTextColor(const Value: TAlphaColor); + procedure SetTextShadowColor(const Value: TAlphaColor); + procedure SetIsDetailText(const Value: Boolean); + procedure SetData(const AValue: TValue); override; + function GetData: TValue; override; + function GetShadowOffset: TPosition; inline; + function GetFont: TFont; + function FontSettingsSnapshot: TFontSettings; + protected + procedure DoResize; override; + procedure DoUpdateValuesFromResources(const Resources: TListItemStyleResources; const Purpose: TListItemPurpose); override; + public + constructor Create(const AOwner: TListItem); override; + destructor Destroy; override; + + procedure CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); override; + procedure Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates; + const Resources: TListItemStyleResources; const Params: TListItemDrawable.TParams; const SubPassNo: Integer = 0); override; + /// Font used to drawing text in this drawable + property Font: TFont read GetFont; + /// Text displayed in this drawable + property Text: string read FText write SetText; + /// Horizontal text alignment inside local item rectangle + property TextAlign: TTextAlign read FTextAlign write SetTextAlign; + /// Vertical text alignment inside local item rectangle + property TextVertAlign: TTextAlign read FTextVertAlign write SetTextVertAlign; + /// Wrap the text it does not fit in the available width + property WordWrap: Boolean read FWordWrap write SetWordWrap; + /// Text color in neutral state + property TextColor: TAlphaColor read FTextColor write SetTextColor; + /// Text color in selected state + property SelectedTextColor: TAlphaColor read FSelectedTextColor write SetSelectedTextColor; + /// Text shadow color. The text shadow will appear behind normal text only when its color is + /// set to non-zero value (default) + property TextShadowColor: TAlphaColor read FTextShadowColor write SetTextShadowColor; + /// Text shadow offset. The text shadow will appear behind normal text only when its color is + /// set to non-zero value (default) + property TextShadowOffset: TPosition read GetShadowOffset; + /// Text trimming + /// TTextTrimming + /// + property Trimming: TTextTrimming read FTrimming write SetTrimming; + /// Hints regarding the contents of this text object, which affecs visual style + property IsDetailText: Boolean read FIsDetailText write SetIsDetailText; + end; + + /// An empty drawable + TListItemDummy = class(TListItemDrawable) + public + procedure Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates; + const Resources: TListItemStyleResources; const Params: TListItemDrawable.TParams; + const SubPassNo: Integer = 0); override; + end; + + /// Image scaling modes for TListItemImage + /// TListItemImage.ScalingMode + /// StretchWithAspect stretch while preserving aspect ratio + /// Original keep original size and proportion + /// Stretch stertch to fill available area disregarding proportions + /// + TImageScalingMode = (StretchWithAspect, Original, Stretch); + + /// A TListItemDrawable representing an image + TListItemImage = class(TListItemDrawable) + public type + /// Image source: + /// None no image source + /// Bitmap bitmap or bitmap reference + /// ImageList bitmap is specified by an index in an ImageList + /// + TImageSource = (None, Bitmap, ImageList); + private + FStaticBitmap: TBitmap; + [Weak]FReferBitmap: TBitmap; + FSrcRect: TRectF; + FOwnsBitmap: Boolean; + FImageScalingMode: TImageScalingMode; + FImageIndex: TImageIndex; + FImageSource: TImageSource; + function GetBitmap: TBitmap; + procedure SetBitmap(const Value: TBitmap); + procedure SetOwnsBitmap(const Value: Boolean); + procedure SetSrcRect(const Value: TRectF); + procedure SetImageScalingMode(const Value: TImageScalingMode); + procedure SetImageIndex(const Value: TImageIndex); + function GetImageSource: TImageSource; inline; + procedure SetData(const Value: TValue); override; + public + constructor Create(const AOwner: TListItem); override; + destructor Destroy; override; + procedure CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); override; + procedure Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates; + const Resources: TListItemStyleResources; const Params: TListItemDrawable.TParams; + const SubPassNo: Integer = 0); override; + /// Bitmap, used if ImageSource equals TImageSource.Bitmap + property Bitmap: TBitmap read GetBitmap write SetBitmap; + /// Determines whether this list owns and maintains the bitmap, or whether it is a reference only. + /// It is must faster and memory efficient to have multiple references to a single bitmap rather than multiple + /// copies of the same bitmap copied across the items + property OwnsBitmap: Boolean read FOwnsBitmap write SetOwnsBitmap; + /// Fit InputRect part of Bitmap into DestinationRect according to current ScalingMode + /// Bitmap TBitmap being fit + /// InputRect source rectangle + /// DestinationRect destination rectangle where the bitmap is to be placed + /// + procedure FitInto(const Bitmap: TBitmap; var InputRect, DestinationRect: TRectF); + /// Source rectangle in ImageSource + property SrcRect: TRectF read FSrcRect write SetSrcRect; + /// Scaling mode used for fitting the bitmap into destination rectangle + property ScalingMode: TImageScalingMode read FImageScalingMode write SetImageScalingMode + default TImageScalingMode.StretchWithAspect; + /// Indicates whether the images are obtained from TImageList or directly by using Bitmap property. + property ImageSource: TImageSource read GetImageSource; + /// Zero based index of an image. The default is -1. + /// See also FMX.ActnList.IGlyph + /// If non-existing index is specified, an image is not drawn and no exception is raised + property ImageIndex: TImageIndex read FImageIndex write SetImageIndex default -1; + end; + + TListItemEmbeddedControl = class; + /// IScene implementation for TListItemEmbeddedControl + TListItemControlScene = class(TFmxObject, IStyleBookOwner, IScene) + strict private + FCanvas: TCanvas; + [Weak] FContainer: TControl; + [Weak] FOwnerItem: TListItem; + FDrawing: Boolean; + FDisableUpdating: Integer; + private + FLayoutSize: TPoint; + function GetRealScene: IScene; + protected + // TFmxObject + procedure DoAddObject(const AObject: TFmxObject); override; + procedure DoRemoveObject(const AObject: TFmxObject); override; + // IStyleBookOwner + function GetStyleBook: TStyleBook; + procedure SetStyleBook(const Value: TStyleBook); + // IScene + function GetCanvas: TCanvas; + function GetSceneScale: Single; + function GetObject: TFmxObject; + procedure AddUpdateRect(const R: TRectF); + function GetUpdateRectsCount: Integer; + function GetUpdateRect(const Index: Integer): TRectF; + function LocalToScreen(const P: TPointF): TPointF; + function ScreenToLocal(const P: TPointF): TPointF; + procedure ChangeScrollingState(const AControl: TControl; const Active: Boolean); + procedure DisableUpdating; + procedure EnableUpdating; + /// Set item that contains this scene + procedure SetOwnerItem(const Item: TListItem); + /// Set container control + procedure SetContainer(const Container: TControl); + /// Get IScene of the parent control, i.e. TListView + property RealScene: IScene read GetRealScene; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + /// Repaint scene on canvas Canvas + procedure RepaintScene(const Canvas: TCanvas); + /// Container control + property Container: TControl read FContainer; + end; + + /// A dummy TControl that contains a TListItemEmbeddedControl + TListItemControlContainer = class(TControl) + private + [weak]FItemOwner: TListItemEmbeddedControl; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + end; + + /// Base class for TListItem embedded controls + TListItemEmbeddedControl = class(TListItemDrawable) + private + FScene: TListItemControlScene; + FContainer: TListItemControlContainer; + protected + /// Return TControl located at given Point + function ObjectAtPoint(const Point: TPointF): TControl; override; + public + constructor Create(const AOwner: TListItem); override; + destructor Destroy; override; + procedure Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates; + const Resources: TListItemStyleResources; const Params: TListItemDrawable.TParams; + const SubPassNo: Integer = 0); override; + /// TListItemControlContainer of this embedded control + property Container: TListItemControlContainer read FContainer; + end; + + /// Simple embedded control base class + TListItemSimpleControl = class(TListItemDrawable) + private const + DisabledOpacity = 0.6; + private + FEnabled: Boolean; + FPressed: Boolean; + FMouseOver: Boolean; + FTouchExpand: Single; + procedure SetEnabled(const Value: Boolean); + protected + /// Shall intercept clicks + function IsClickOpaque: Boolean; virtual; + function MouseDown(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF): Boolean; + override; + procedure MouseMove(const Shift: TShiftState; const MousePos: TPointF); override; + procedure MouseUp(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF); override; + /// Handle click + procedure DoClick; virtual; + /// Handle change of Enabled + procedure DoEnabledChange; virtual; + public + constructor Create(const AOwner: TListItem); override; + /// Test if point Pos belongs to local rectangle of this control + function PointInLocalRect(const Pos: TPointF): Boolean; + /// This method is called when click event is sent to the control to handle any actions associated + /// with this item + procedure Click; + /// Control state: Enabled + property Enabled: Boolean read FEnabled write SetEnabled; + /// Control state: Pressed + property Pressed: Boolean read FPressed; + /// Control state: MouseOver + property MouseOver: Boolean read FMouseOver; + /// Additional area (in logical units) around the control that is sensitive to touch + property TouchExpand: Single read FTouchExpand write FTouchExpand; + end; + + /// Accessory type for TListItemAccessory + /// More more + /// Checkmark checkmark + /// Detail detail disclosure + /// + TAccessoryType = (More, Checkmark, Detail); + + /// List item accessory, a glyph typically displayed at the right edge of the list item + TListItemAccessory = class(TListItemDrawable) + private + FAccessoryType: TAccessoryType; + protected + /// Set accessory type: More, Checkmark or Detail + procedure SetAccessoryType(Value: TAccessoryType); + public + constructor Create(const AOwner: TListItem); override; + + procedure CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); override; + procedure Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates; + const Resources: TListItemStyleResources; const Params: TListItemDrawable.TParams; + const SubPassNo: Integer = 0); override; + + /// Accessory type, TAccessoryType + property AccessoryType: TAccessoryType read FAccessoryType write SetAccessoryType; + end; + + /// Glyph type for TListItemGlyphButton + /// Add plus button + /// Delete delete button + /// Checkbox selection checkbox + /// + TGlyphButtonType = (Add, Delete, Checkbox); + + /// Glyph button is an additional control usually used in Edit mode. It can be an Add/Plus sign, + /// a Delete button or a Checkbox + TListItemGlyphButton = class(TListItemSimpleControl) + private const + CheckedAnimationFrameRate = 60; + CheckedAnimationDuration = 0.15; // in seconds + private + FButtonType: TGlyphButtonType; + FClickOnSelect: Boolean; + FChecked: Boolean; + FTransitionEnabled: Boolean; + FTransitionAlpha: Single; + FTransitionTimer: TTimer; + FTransitionStartTicks: Double; + FTimerService: IFMXTimerService; + procedure SetButtonType(const Value: TGlyphButtonType); + procedure SetChecked(const Value: Boolean); + procedure InitCheckedTransition; + procedure ResetCheckedTransition; + procedure TransitionTimerNotify(Sender: TObject); + protected + procedure DoSelect; override; + procedure DoClick; override; + function MouseDown(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF): Boolean; + override; + procedure SetData(const AValue: TValue); override; + public + constructor Create(const AOwner: TListItem); override; + destructor Destroy; override; + procedure CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); override; + procedure Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates; + const Resources: TListItemStyleResources; const Params: TListItemDrawable.TParams; + const SubPassNo: Integer = 0); override; + /// Button type, TGlyphButtonType + property ButtonType: TGlyphButtonType read FButtonType write SetButtonType; + /// If set to True, this button will receive click events from entire list by using of selection + property ClickOnSelect: Boolean read FClickOnSelect write FClickOnSelect; + /// Determines whether checkbox is checked, has no effect for other button types + property Checked: Boolean read FChecked write SetChecked; + end; + + /// Type of TListItemTextButton + /// Normal a regular button + /// Delete a delete button + /// + TTextButtonType = (Normal, Delete); + + /// A button with text that can be clicked inside of a TListItem + TListItemTextButton = class(TListItemSimpleControl) + private + FTextDrawable: TListItemText; + FButtonType: TTextButtonType; + FTintColor: TAlphaColor; + FPressedTextColor: TAlphaColor; + FTextColor: TAlphaColor; + + // getters routing to FTextDrawable + function GetFont: TFont; + function GetText: string; + procedure SetText(const Value: string); + procedure SetTextAlign(const Value: TTextAlign); + function GetTextAlign: TTextAlign; + procedure SetTextVertAlign(const Value: TTextAlign); + function GetTextVertAlign: TTextAlign; + procedure SetWordWrap(const Value: Boolean); + function GetWordWrap: Boolean; + procedure SetTextColor(const Value: TAlphaColor); + function GetTextColor: TAlphaColor; + procedure SetTrimming(const Value: TTextTrimming); + function GetTrimming: TTextTrimming; + procedure SetTextShadowColor(const Value: TAlphaColor); + function GetTextShadowColor: TAlphaColor; + + // button-specific text properties + procedure SetPressedTextColor(const Value: TAlphaColor); + function GetTextShadowOffset: TPosition; inline; + procedure SetButtonType(const Value: TTextButtonType); + procedure SetTintColor(const Value: TAlphaColor); + procedure SetInvalidateCallback(const Callback: TListItemCallback); override; + protected + function IsClickOpaque: Boolean; override; + procedure DoResize; override; + procedure DoOpacityChange; override; + procedure DoPlaceOffsetChanged; override; + procedure DoAlignChanged; override; + procedure SetData(const AValue: TValue); override; + public + constructor Create(const AOwner: TListItem); override; + destructor Destroy; override; + + procedure DoUpdateValuesFromResources(const Resources: TListItemStyleResources; + const Purpose: TListItemPurpose); override; + function GetRenderPassCount: Integer; override; + procedure CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); override; + procedure Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates; + const Resources: TListItemStyleResources; const Params: TListItemDrawable.TParams; + const SubPassNo: Integer = 0); override; + + /// Button type, TTextButtonType + property ButtonType: TTextButtonType read FButtonType write SetButtonType; + /// Button tint color + property TintColor: TAlphaColor read FTintColor write SetTintColor; + /// Font used to draw button text + property Font: TFont read GetFont; + /// Button text + property Text: string read GetText write SetText; + /// Horizontal alignment of text within the button area + property TextAlign: TTextAlign read GetTextAlign write SetTextAlign; + /// Vertical alignment of text within the button area + property TextVertAlign: TTextAlign read GetTextVertAlign write SetTextVertAlign; + /// If true, the text that does not fit in button width will be wrapped + property WordWrap: Boolean read GetWordWrap write SetWordWrap; + /// Text color in neutral state + property TextColor: TAlphaColor read GetTextColor write SetTextColor; + /// Text color in pressed state + property PressedTextColor: TAlphaColor read FPressedTextColor write SetPressedTextColor; + /// Text shadow color + property TextShadowColor: TAlphaColor read GetTextShadowColor write SetTextShadowColor; + /// Text shadow offset + property TextShadowOffset: TPosition read GetTextShadowOffset; + /// Text trimming + /// TTextTrimming + /// + property Trimming: TTextTrimming read GetTrimming write SetTrimming; + end; + + /// TListItemView is a collection of drawables that comprise the view of a TListItem + TListItemView = class + private type + TViewList = TObjectList; + private + FCallback: TListItemCallback; + FViewList: TListItemView.TViewList; + FInitialized: Boolean; + + function GetCount: Integer; + function GetObject(const Index: Integer): TListItemDrawable; + function GetViewList: TViewList; inline; + protected + procedure Include(const AItem: TListItemDrawable); + procedure Exclude(const AItem: TListItemDrawable); + function GetInitialized: Boolean; + procedure SetInitialized(const Value: Boolean); + public + constructor Create(const AOwner: TListItem); + destructor Destroy; override; + /// Add a drawable to view + function Add(const AItem: TListItemDrawable): Integer; + /// Insert a drawable at a position indicated by Index + procedure Insert(const Index: Integer; const Value: TListItemDrawable); + /// Clear the view + procedure Clear; virtual; + /// Delete drawable at index Index + procedure Delete(Index: Integer); + /// Find TListItemDrawable + /// specified by name AName + function FindDrawable(const AName: string): TListItemDrawable; + /// Find TListItemDrawable specified by name AName + function FindObject(const AName: string): TListItemDrawable; deprecated 'Use FindDrawable'; + + + /// Find TListItemDrawable specified by name AName and throw an error if not found + function DrawableByName(const AName: string): TListItemDrawable; + /// Find TListItemDrawable specified by name AName and throw an error if not found + function ObjectByName(const AName: string): TListItemDrawable; deprecated 'Use DrawableByName'; + /// Number of drawables in this view + property Count: Integer read GetCount; + /// TListItemDrawable by index + property Drawables[const Index: Integer]: TListItemDrawable read GetObject; default; + /// Get TViewList that contains the drawables + property ViewList: TViewList read GetViewList; + /// Used internally to notify owner about changes + property Callback: TListItemCallback write FCallback; + /// Used internally + property Initialized: Boolean read GetInitialized write SetInitialized; + end; + + /// Compatibility class for TListItemView + TListItemObjects = class(TListItemView) + end; + + /// TListItem is an element that comprises TListView. Each individual item contains a View, + /// which in turn is comprised of instances of TListItemDrawable + TListItem = class + public type + TListItemViewType = class of TListItemView; + TListItemNotifyEvent = procedure(const Item: TListItem) of object; + strict private + FHeaderRef: Integer; + FAdapter: IListViewAdapter; + FController: IListViewController; + private + FIndex: Integer; + FHeight: Integer; + FPurpose: TListItemPurpose; + FUpdating: Integer; + FNeedRepaint: Boolean; + FView: TListItemView; + [Weak] FTagObject: TObject; + FTag: NativeInt; + FTagString: string; + function GetCount: Integer; + procedure SetHeight(const Value: Integer); + function GetController: IListViewController; + protected + /// Invoked when item height changes and heights of all items need to be recounted + procedure InvalidateHeights; virtual; + /// Setter for Purpose property + procedure SetPurpose(const AValue: TListItemPurpose); virtual; + /// Require repaint, notify the controller that the item is invalid + procedure Repaint; virtual; + /// Return class of TListItemView + function ListItemObjectsClass: TListItemViewType; virtual; + /// Getter for Index property + function GetIndex: Integer; virtual; + /// Setter for Index property + procedure SetIndex(const Value: Integer); virtual; + public + constructor Create(const AAdapter: IListViewAdapter; const AController: IListViewController = nil); + destructor Destroy; override; + function ToString: string; override; + /// Invalidate item, request repainting + procedure Invalidate; + /// Begin update. Invalidation request will not cause immediate action during update. + procedure BeginUpdate; + /// End update + procedure EndUpdate; + /// Called when the view drawables need to be created + procedure CreateObjects; virtual; + /// Called when the item is about to be painted + procedure WillBePainted; virtual; + /// Locate a control at a point, used with embedded controls + function ObjectAtPoint(const Point: TPointF): TControl; + /// Handle MouseDown event + function MouseDown(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF): Boolean; + /// Handle MouseMove event + procedure MouseMove(const Shift: TShiftState; const MousePos: TPointF); + /// Handle MouseUp event + procedure MouseUp(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF); + /// Handle selection + procedure MouseSelect; + /// True if there are items inside that react as clicked when the item is selected + function HasClickOnSelectItems: Boolean; + /// Adapter that observes this item + property Adapter: IListViewAdapter read FAdapter; + /// Number of drawables in the View, shorthand for View.Count + property Count: Integer read GetCount; + /// The view, drawables that comprise the visual of this item + property View: TListItemView read FView; + /// Height of this item + property Height: Integer read FHeight write SetHeight; + /// Reference to IListViewController + property Controller: IListViewController read GetController; + /// Which purpose item serves for: normal item (none), header or footer. + /// TListItemPurpose + property Purpose: TListItemPurpose read FPurpose write SetPurpose; + /// Reference to header of the group this item belongs to, for grouped views + property HeaderRef: Integer read FHeaderRef write FHeaderRef; + /// Index of this item in TListView.Adapter + property Index: Integer read GetIndex write SetIndex; + /// User-defined integer tag for this item + property Tag: NativeInt read FTag write FTag; + /// User-defined object reference for this item + property TagObject: TObject read FTagObject write FTagObject; + /// User-defined string tag for this item + property TagString: string read FTagString write FTagString; + end; + + /// A container used for passing various style-defined properties used in TListView + TListItemStyleResources = class + private + FOwnsObjects: Boolean; + public type + /// References to TStyleObjects used for rendering of TListItemAccessory + TAccessoryStyleObject = record + /// Accessory TStyleObject in neutral state + [Weak] Normal: TStyleObject; + /// Accessory TStyleObject in selected state + [Weak] Selected: TStyleObject; + end; + + /// Style objects used for button rendering + TButtonStyleObject = record + /// Button TStyleObject in neutral state + [Weak] Normal: TStyleObject; + /// Button TStyleObject in pressed state + [Weak] Pressed: TStyleObject; + end; + public + /// Style references used for Accessory rendering + AccessoryImages: array [TAccessoryType] of TAccessoryStyleObject; + /// Font used in header items + HeaderTextFont: TFont; + /// Color of header text + HeaderTextColor: TAlphaColor; + /// Color of header text shadow + HeaderTextShadowColor: TAlphaColor; + /// Default font for text drawables + DefaultTextFont: TFont; + /// Default color for text drawables + DefaultTextColor: TAlphaColor; + /// Default font for detail text + DetailTextFont: TFont; + /// Default color for detail text + DetailTextColor: TAlphaColor; + /// Default color for text in selected state + DefaultTextSelectedColor: TAlphaColor; + /// Style objects for Add Item TListItemGlyphButton + ButtonAddItemStyleImage: TButtonStyleObject; + /// Style objects for Delete Item TListItemGlyphButton + ButtonDeleteItemStyleImage: TButtonStyleObject; + /// Background image for regular TListItemTextButton + ButtonNormalStyleImage: TButtonStyleObject; + /// Background image for delete-themed TListItemTextButton + ButtonDeleteStyleImage: TButtonStyleObject; + /// Style objects for Checkbox TListItemGlyphButton + ButtonCheckboxStyleImage: TButtonStyleObject; + /// Font for TListItemTextButton + ButtonTextFont: TFont; + /// Text color for neutral state TListItemTextButton + ButtonTextColor: TAlphaColor; + /// Text color for pressed state TListItemTextButton + ButtonTextPressedColor: TAlphaColor; + /// Font for delete-themed TListItemTextButton + DeleteButtonTextFont: TFont; + /// Text color for neutral state delete-themed TListItemTextButton + DeleteButtonTextColor: TAlphaColor; + /// Text color for pressed state delete-themed TListItemTextButton + DeleteButtonTextPressedColor: TAlphaColor; + /// Glow color that appears at the top or the bottom of screen when scrolling + /// is being stretched out + ScrollingStretchGlowColor: TAlphaColor; + /// Indicator color for drawing Pull Refresh indicator + PullRefreshIndicatorColor: TAlphaColor; + /// Stroke color for drawing Pull Refresh indicator + PullRefreshStrokeColor: TAlphaColor; + constructor Create; overload; + constructor Create(const Source: TListItemStyleResources); overload; + destructor Destroy; override; + end; + + /// Interface providing access to style resources. Implemented by TListViewBase + IListItemStyleResources = interface + ['{0328C6F1-432C-4F8B-994B-7AB2543CD172}'] + /// Getter for StyleResources property + function GetStyleResources: TListItemStyleResources; + /// TListView style resources + property StyleResources: TListItemStyleResources read GetStyleResources; + /// True if style resources should be reloaded + function StyleResourcesNeedUpdate: Boolean; + end; + + /// Interface providing loose coupling between TListView and TListItem. Implemented by + /// TListViewBase + IListViewController = interface + ['{3855EF72-3B32-41BE-9068-7B109B2DD8E5}'] + function GetEditModeTransitionAlpha: Single; + function GetDeleteModeTransitionAlpha: Single; + function GetImages: TCustomImageList; + /// True if delete mode is enabled + function IsDeleteModeAllowed: Boolean; + /// Left offset of item rectangle in edit mode + function GetItemEditOffset(const Item: TListItem): Single; + /// Right cutoff of item rectangle in delete mode + function GetItemDeleteCutoff(const Item: TListItem): Single; + /// Item selection alpha + function GetItemSelectionAlpha(const Item: TListItem): Single; + /// Client area margins + function GetClientMargins: TRectF; + /// IScene of the TListView control + function GetScene: IScene; + /// Request TListView to initialize item indices; + /// see TListViewItem.GetIndex + /// + procedure RequestReindexing(const Item: TListItem); + /// Notify TListView that item size has changed; + /// see TListViewItem.InvalidateHeights + /// + procedure ItemResized(const Item: TListItem); + /// Notify TListView that item requires repainting + procedure ItemInvalidated(const Item: TListItem); + /// Notify TListView that a control inside of an item has been clicked + procedure ControlClicked(const Item: TListItem; const Control: TListItemDrawable); + /// Notify TListView that a control inside of an item has been clicked + procedure CheckStateChanged(const Item: TListItem; const Control: TListItemDrawable); + /// Item alpha during edit mode transition + property EditModeTransitionAlpha: Single read GetEditModeTransitionAlpha; + /// Item alpha during delete transition + property DeleteModeTransitionAlpha: Single read GetDeleteModeTransitionAlpha; + ///TImageList used when ImageSource = ImageList; can be nil + property Images: TCustomImageList read GetImages; + end; + + /// Presentation of TPresentedListView. The control passes messages to the + /// presentation layer by the means of this interface. + IListViewPresentation = interface + ['{85C07617-2BB7-44DC-BBCB-2E3FE422B006}'] + /// Called when ancestor's visible property is changed + procedure AncestorVisibleChanged(const Visible: Boolean); + /// Called when Parent is changed + procedure ParentChanged; + /// Called when position in parent list is changed + procedure OrderChanged; + /// Called when control size has changed + procedure SizeChanged; + /// Called when edit mode has changed + procedure EditModeChanged; + /// Called when visibility status has changed + procedure StatusChanged; + /// Called when item presentation requires update + procedure ItemsUpdated; + /// Item has been invalidated, presentation should reinitialize its view + procedure ItemInvalidated(const Item: TListItem); + /// Item has been selected by the control, presentation should update its selection + procedure SetItemSelected(const ItemIndex: Integer; const Value: Boolean); + /// Selected item index has been changed by the control, presentation should update selection + procedure SetItemIndex(const ItemIndex: Integer); + /// Force stop pull refresh + procedure StopPullRefresh; + end; + + /// Base interface from presented control to presentation + IListViewCustomPresentationParent = interface + ['{EBBE5FAA-F2B3-4606-AE32-8027DB97EC92}'] + /// Get root object, normally Root.GetObject + function GetRootObject: TObject; + /// Get content frame of the presented control + function GetContentFrame: TRect; + /// Get opacity of the presented control + function GetControlOpacity: Single; + end; + + /// Enumeration of internally used boolean flags + TListViewModeFlag = (Edit, Enabled, Visible, Deletion, PullRefresh, Buttons, Search, SearchOnTop, PullRefreshWait, + SwipeDelete); + /// Set of boolean flags used internally + TListViewModeFlags = set of TListViewModeFlag; + + /// View options specific to native iOS presentation + TListViewNativeOption = (Grouped, Indexed, Styled); + /// View options specific to native iOS presentation. + /// IListViewPresentationParent + TListViewNativeOptions = set of TListViewNativeOption; + + /// Interface from presented control to presentation + IListViewPresentationParent = interface(IListViewCustomPresentationParent) + ['{F5657E45-0955-4A9F-9FE6-6C5E019846E4}'] + /// Obtains IListViewAdapter + function GetAdapter: IListViewAdapter; + /// Obtains client rectangle for item with index ItemIndex + function GetItemClientRect(const ItemIndex: Integer): TRectF; + /// Obtains item count + function GetItemCount: Integer; + /// Obtains height of item ItemIndex + function GetItemHeight(const ItemIndex: Integer): Integer; + /// Guess item height before exact values can be calculated + function GetEstimatedItemHeight: Single; + /// Guess header height before exact values can be calculated + function GetEstimatedHeaderHeight: Single; + /// Guess footer height before exact values can be calculated + function GetEstimatedFooterHeight: Single; + /// Get main text of item ItemIndex + function GetItemText(const ItemIndex: Integer): string; + /// Get index title for item ItemIndex + function GetItemIndexTitle(const ItemIndex: Integer): string; + /// Query if item ItemIndex is selectable + function CanSelectItem(const ItemIndex: Integer): Boolean; + /// Notify control when item ItemIndex was selected + procedure DidSelectItem(const ItemIndex: Integer); + /// Query if item ItemIndex can be unselected + function CanUnselectItem(const ItemIndex: Integer): Boolean; + /// Notify control that item ItemIndex was unselected + procedure DidUnselectItem(const ItemIndex: Integer); + /// Notify control that an item button was clicked + procedure ItemButtonClicked(const ItemIndex: Integer); + /// Obtain presentation-specific boolean flags + function GetFlags: TListViewModeFlags; + /// Obtain native presentation-specific boolean flags + function GetOptions: TListViewNativeOptions; + /// Commit deletion of item ItemIndex + function DeleteItem(const ItemIndex: Integer): Boolean; + /// Invoke pull refresh + procedure InvokePullRefresh; + /// Set search filter string + procedure SetSearchFilter(const Filter: string); + /// Pass scroll view position to the control + procedure SetScrollViewPos(const Value: Single); + /// Require rebuild of the list + procedure RebuildList; + /// Set boolean flag that indicates that native view is being created. It is used + /// as a guard condition to prevent TListView logic from interfering with the presentation + /// while a view is being initialized + procedure SetCreatingNativeView(const Value: Boolean); + /// True if TListView is transparent + function GetIsTransparent: Boolean; + /// Obtain control opacity + function GetOpacity: Single; + /// Obtain background color defined by TListView style + function GetBackgroundStyleColor: TAlphaColor; + /// Request complete recreation of presentation + procedure RecreateNativePresentation; + end; + + /// Used to check if there is a design presentation attached to ListView + IListViewDesignPresentationParent = interface + ['{C62F3FE5-FE96-47A7-99CB-2EEBC85664FA}'] + ///True if design presentation is attached + function HasDesignPresentationAttached: Boolean; + end; + + /// A drawable shim extension to apply bounds changes from + /// the designer. + IListViewDrawableShim = interface + ['{9FB67E2A-37B9-473B-A95A-13EDD19ED91B}'] + ///Calculate appearance PlaceOffset, Width, Height for given rect from designer + function CalcAppearanceBounds(const AValue: TRect; const CurrentBounds: TRectF): TRectF; + end; + + /// Predicate used for item filtering. + /// + TFilterPredicate = TPredicate; + + TListItemsList = TList; + + /// IListViewAdapter provides interface between the data and their representation. + /// The essential part of this interface is implemented in FMX.ListView.Adapters.Base.TAbstractListViewAdapter + /// + IListViewAdapter = interface + ['{6E850F76-BABD-4756-BF05-A30C66A692AD}'] + /// Return number of items that this Adapter represents. See Item[Index]. + function GetCount: Integer; + /// Get TListItem by index Index + function GetItem(const Index: Integer): TListItem; + /// Get index of given TListItem + function IndexOf(const AItem: TListItem): Integer; + /// Return TEnumerator<TListItem> + function GetEnumerator: TEnumerator; + /// Height of items that do not have it explicitly defined + function GetDefaultViewHeight: Integer; + /// Subscribe to OnChanged event + procedure SetOnChanged(const Value: TNotifyEvent); + /// Subscribe to OnItemsMayChange event + procedure SetOnItemsMayChange(const Value: TNotifyEvent); + /// Subscribe to OnItemsCouldHaveChanged event + procedure SetOnItemsCouldHaveChanged(const Value: TNotifyEvent); + /// Subscribe to OnItemsInvalidate event + procedure SetOnItemsInvalidate(const Value: TNotifyEvent); + /// Subscribe to OnItemsResize event + procedure SetOnItemsResize(const Value: TNotifyEvent); + /// Subscribe to OnResetView event + procedure SetOnResetView(const Value: TNotifyEvent); + /// Notifies adapter about item's changes. + procedure Changed; + /// Sort data + procedure Sort(AComparer: IComparer); + /// Called by TListView every time it needs to paint. In dynamic adapters this is where + /// new TListItems should be created for data. + procedure CreateNewViews; + /// Recreate views for items that have specified purposes. May be called when item + /// views may need update, for example when host TListView is being resized + procedure ResetViews(const Purposes: TListItemPurposes); + // Called by ResetViews to invoke OnResetView event for each ListItem + procedure ResetView(const Item: TListItem); + /// Get item by Index. Items would normally be already created by CreateNewViews by the time + /// this property is accessed. Index should be in range of [0, Count) + property Item[const Index: Integer]: TListItem read GetItem; default; + /// Count of items + property Count: Integer read GetCount; + /// Invoked when items have changed + property OnChanged: TNotifyEvent write SetOnChanged; + /// Invoked before an edit operation, items or their contents may change + property OnItemsMayChange: TNotifyEvent write SetOnItemsMayChange; + /// Invoked after an edit operation, items could have been changed + property OnItemsCouldHaveChanged: TNotifyEvent write SetOnItemsCouldHaveChanged; + /// Invoked when items are invalidated and a repaint is necessary + property OnItemsInvalidate: TNotifyEvent write SetOnItemsInvalidate; + /// Item sizes were changed, notify the view that the sizes of items need recalculation + property OnItemsResize: TNotifyEvent write SetOnItemsResize; + /// View has been reset + property OnResetView: TNotifyEvent write SetOnResetView; + end; + + IListViewEditor = interface; + TBeforeItemAddedNotify = procedure(Sender: IListViewEditor) of object; + TAfterItemAddedNotify = procedure(Sender: IListViewEditor; const Item: TListItem) of object; + TBeforeItemDeletedNotify = procedure(Sender: IListViewEditor; const Index: Integer) of object; + TAfterItemDeletedNotify = procedure(Sender: IListViewEditor) of object; + + /// Extension of IListViewAdapter for editable content. Implemented by TAppearanceListViewItems + IListViewEditor = interface + ['{19A0606B-8C8E-49B2-A6B3-A708B7B8AD46}'] + /// Create a new Item and append at end + function Add: TListItem; + /// Delete Item at Index + procedure Delete(const Index: Integer); + /// Create a new Item and insert at Index + function Insert(const Index: Integer): TListItem; + /// Delete all items + procedure Clear; + /// Set OnBeforeItemAdded handler + procedure SetBeforeItemAdded(const AHandler: TBeforeItemAddedNotify); + /// Set OnAfterItemAdded handler + procedure SetAfterItemAdded(const AHandler: TAfterItemAddedNotify); + /// Set OnBeforeItemDeleted handler + procedure SetBeforeItemDeleted(const AHandler: TBeforeItemDeletedNotify); + /// Set OnAfterItemDeleted handler + procedure SetAfterItemDeleted(const AHandler: TAfterItemDeletedNotify); + /// Notification before item is added + property OnBeforeItemAdded: TBeforeItemAddedNotify write SetBeforeItemAdded; + /// Notification after item is added + property OnAfterItemAdded: TAfterItemAddedNotify write SetAfterItemAdded; + /// Notification before item is deleted + property OnBeforeItemDeleted: TBeforeItemDeletedNotify write SetBeforeItemDeleted; + /// Notification after item is deleted + property OnAfterItemDeleted: TAfterItemDeletedNotify write SetAfterItemDeleted; + end; + + /// Extension of IListViewAdapter for items with checkboxes. + /// Implemented by TAppearanceListViewItems + IListViewCheckProvider = interface + ['{032EB974-1C25-4B5E-BB07-01FA82554748}'] + /// Index of first checked item + function FirstChecked(const Checked: Boolean = True): Integer; + /// A quick query to see if one or all of the items are in checked state. + /// If AChecked = True, True if at least one item is checked + /// If AChecked = False, True if every item is checked + /// + function AnyChecked(const AChecked: Boolean = True): Boolean; + /// Change check state of all items + procedure CheckAll(const NewState: Boolean = True); + /// Get checked state for item at Index + function GetChecked(const Index: Integer): Boolean; + /// Set checked state for item at Index + procedure SetChecked(const Index: Integer; const Value: Boolean); + /// Get/set checked state of item at Index + property Checked[const Index: Integer]: Boolean read GetChecked write SetChecked; default; + end; + + /// Extension of IListViewAdapter for items with text and indexes. + /// Implemented by TAppearanceListViewItems + IListViewTextProvider = interface + ['{C6D52C15-423D-4B2F-AC87-7D7D47A9C7CC}'] + /// Get primary text of item at Index + function GetText(const Index: Integer): string; + /// Get index title at Index + function GetIndexTitle(const Index: Integer): string; + /// Primary text of item at Index + property Text[const Index: Integer]: string read GetText; + /// Index title of item at Index + property IndexTitle[const Index: Integer]: string read GetIndexTitle; + end; + + /// Extension of IListViewAdapter for items with embedded text buttons. + /// Implemented by TAppearanceListViewItems + IListViewTextButtonProvider = interface + ['{42CC3926-0A23-465B-9ECE-229C60B3BA8E}'] + /// Get drawable of text button for item at Index + function GetTextButtonDrawable(const Index: Integer): TListItemTextButton; + /// Drawable of text button for item at Index + property TextButtonDrawable[const Index: Integer]: TListItemTextButton read GetTextButtonDrawable; + end; + + /// Extension of IListViewAdapter for items with glyph buttons. + /// Implemented by TAppearanceListViewItems + IListViewGlyphButtonProvider = interface + ['{64FF4B01-E378-4F40-A9A5-E4C1A7C942D6}'] + /// Get drawable of button for item at Index + function GetGlyphButtonDrawable(const Index: Integer): TListItemGlyphButton; + /// Drawable of button for item at Index + property GlyphButtonDrawable[const Index: Integer]: TListItemGlyphButton read GetGlyphButtonDrawable; + end; + + /// Extension of IListViewAdapter for items with data. + /// Implemented by TAppearanceListViewItems + IListViewExtrasProvider = interface + ['{0BCFB611-3763-4C49-974F-1104F6116D6E}'] + /// Get indexed data DataIndex for item at Index + function GetItemData(const Index: Integer; const DataIndex: string): TValue; + /// Set indexed data DataIndex for item at Index + procedure SetItemData(const Index: Integer; const DataIndex: string; const AValue: TValue); + /// True if item at Index has indexed data DataIndex + function GetHasData(const Index: Integer; const DataIndex: string): Boolean; + /// Get tag of item at Index + function GetItemTag(const Index: Integer): NativeInt; + /// Set tag of item at Index + procedure SetItemTag(const Index: Integer; const AValue: NativeInt); + + /// Indexed data DataIndex of item at Index + property Data[const Index: Integer; const DataIndex: string]: TValue read GetItemData write SetItemData; + /// Tag of item at Index + property Tag[const Index: Integer]: NativeInt read GetItemTag write SetItemTag; + /// True if item at Index has Indexed data DataIndex + property HasData[const Index: Integer; const DataIndex: string]: Boolean read GetHasData; + end; + + /// Extension of IListViewAdapter items of which can be filtered by a search box. + /// Implemented by TAppearanceListViewItems + IListViewFilterable = interface + ['{02F85899-8787-4378-9622-105820EB4577}'] + /// Get filter predicate + function GetFilterPredicate: TFilterPredicate; + /// Set filter predicate + procedure SetFilterPredicate(const Value: TFilterPredicate); + /// Return a complete list of all items regardless of filter state + function GetUnfilteredItems: TListItemsList; + /// True if filter is applied + function GetFiltered: Boolean; + + /// Notifies the adapter that all items have been deleted + procedure ItemsCleared; + /// Notifies the adapter that item at Index have been deleted + procedure ItemDeleted(const Index: Integer); + /// Notifies the adapter that item at Index have been added + procedure ItemAdded(const Index: Integer; const Item: TListItem); + + /// Complete list of all items regardless of filter state + property UnfilteredItems: TListItemsList read GetUnfilteredItems; + /// True if filter is applied + property Filtered: Boolean read GetFiltered; + /// Filter predicate + property Filter: TFilterPredicate read GetFilterPredicate write SetFilterPredicate; + end; + +implementation + +uses + System.RTLConsts, System.UIConsts, System.Math, System.Messaging, System.TypInfo, FMX.Consts, FMX.Styles, + FMX.Platform; + +type + TDrawableElement = (FontSize, FontFamily, FontStyle, TextColor, SelectedTextColor, TextShadowColor, PressedTextColor); + TDrawableElements = set of TDrawableElement; + + TDefaultSettingsHelper = class + private + class constructor Create; + class destructor Destroy; + class var FItems: TDictionary; + public + class procedure Register(const ADrawable: TListItemDrawable); + class procedure Unregister(const ADrawable: TListItemDrawable); + + class function IsDefault(const ADrawable: TListItemDrawable; const AElement: TDrawableElement): Boolean; + class procedure SetDefault(const ADrawable: TListItemDrawable; const AElement: TDrawableElement; const ADefault: Boolean); + end; + +{$REGION 'Global Functions'} + +const + GlyphOffsetX = -3; + MinusOffsetX = 7; + OpacityEpsilon = 0.003921569; // 1 / 255 + AnimationDeltaEpsilon = 0.01; + +type + TOpenControl = class(TControl); +{$IFDEF DRAW_ITEM_MARGINS} +var + TempBrush: TBrush = nil; + +function MarginBrush: TBrush; +begin + if TempBrush = nil then + TempBrush := TBrush.Create(TBrushKind.Solid, $50808080); + + Result := TempBrush; +end; + +{$ENDIF} + +function GetDeletingUnwantedOpacity(const Parent: TControl): Single; +var + Controller: IListViewController; +begin + if Supports(Parent, IListViewController, Controller) then + if Controller.DeleteModeTransitionAlpha > AnimationDeltaEpsilon then + Result := Max(0, 1 - (Controller.DeleteModeTransitionAlpha * 2)) + else + Result := 1 + else + Result := 1; +end; + +function Blend(const Color1, Color2: TAlphaColor; const Alpha: Single): TAlphaColor; + + function BlendComponent(const Value1, Value2: Integer; const Alpha: Single): Integer; inline; + begin + Result := Value1 + Round((Value2 - Value1) * Alpha); + end; + +begin + TAlphaColorRec(Result).R := BlendComponent(TAlphaColorRec(Color1).R, TAlphaColorRec(Color2).R, Alpha); + TAlphaColorRec(Result).G := BlendComponent(TAlphaColorRec(Color1).G, TAlphaColorRec(Color2).G, Alpha); + TAlphaColorRec(Result).B := BlendComponent(TAlphaColorRec(Color1).B, TAlphaColorRec(Color2).B, Alpha); + TAlphaColorRec(Result).A := BlendComponent(TAlphaColorRec(Color1).A, TAlphaColorRec(Color2).A, Alpha); +end; + + +{$ENDREGION} +{$REGION 'List Item Object'} + +constructor TListItemDrawable.Create(const AOwner: TListItem); +begin + inherited Create; + + if AOwner <> nil then + begin + AOwner.View.Include(Self); + FController := AOwner.Controller; + end; + + FWidth := 0; + FHeight := 0; + FVisible := True; + + FAlign := TListItemAlign.Leading; + FVertAlign := TListItemAlign.Leading; + + FUpdating := 0; + NeedRepaint := False; + FOpacity := 1; + + TDefaultSettingsHelper.Register(Self); +end; + +destructor TListItemDrawable.Destroy; +begin + TDefaultSettingsHelper.Unregister(Self); + FController := nil; + FCallback := nil; + FPlaceOffsetX.Free; + inherited; +end; + +procedure TListItemDrawable.DoResize; +begin + Invalidate; +end; + +procedure TListItemDrawable.DoAlignChanged; +begin +end; + +procedure TListItemDrawable.DoOpacityChange; +begin + Invalidate; +end; + +procedure TListItemDrawable.DoPlaceOffsetChanged; +begin +end; + +procedure TListItemDrawable.DoSelect; +begin + if Assigned(FOnSelect) then + FOnSelect(Self); +end; + +procedure TListItemDrawable.DoUpdateValuesFromResources(const Resources: TListItemStyleResources; + const Purpose: TListItemPurpose); +begin +end; + +procedure TListItemDrawable.SetSize(const Value: TPointF); +var + Adjusted: TPointF; +begin + Adjusted := Value; + if Adjusted.X < 0 then + Adjusted.X := 0; + if Adjusted.Y < 0 then + Adjusted.Y := 0; + + if not SameValue(Adjusted.X, FWidth, TEpsilon.Position) or + not SameValue(Adjusted.Y, FHeight, TEpsilon.Position) then + begin + FWidth := Adjusted.X; + FHeight := Adjusted.Y; + DoResize; + end; +end; + +function TListItemDrawable.GetSize: TPointF; +begin + Result := TPointF.Create(FWidth, FHeight); +end; + +procedure TListItemDrawable.SetOneDimension(const Index: Integer; const Value: Single); +var + NewValue: Single; +begin + NewValue := Value; + if NewValue < 0 then + NewValue := 0; + + case Index of + 0: + if FWidth <> NewValue then + begin + FWidth := NewValue; + DoResize; + end; + 1: + if FHeight <> NewValue then + begin + FHeight := NewValue; + DoResize; + end; + end; +end; + +procedure TListItemDrawable.SetAlign(const Value: TListItemAlign); +begin + if FAlign <> Value then + begin + FAlign := Value; + DoAlignChanged; + Invalidate; + end; +end; + +procedure TListItemDrawable.SetData(const Value: TValue); +begin +end; + +procedure TListItemDrawable.SetInvalidateCallback(const Callback: TListItemCallback); +begin + FCallback := Callback; +end; + +procedure TListItemDrawable.SetVertAlign(const Value: TListItemAlign); +begin + if FVertAlign <> Value then + begin + FVertAlign := Value; + DoAlignChanged; + Invalidate; + end; +end; + +procedure TListItemDrawable.SetVisible(const Value: Boolean); +begin + if FVisible <> Value then + begin + FVisible := Value; + Invalidate; + end; +end; + +function TListItemDrawable.ToString: string; + function GetClassName: string; + begin + if Self <> nil then + Result := ClassName + else + Result := 'TListItemDrawable(nil)'; + end; +begin + Result := Format('%s@%x', [GetClassName, NativeUInt(Self)]); +end; + +procedure TListItemDrawable.UpdateValuesFromResources(const Resources: TListItemStyleResources; + const Purpose: TListItemPurpose); +begin + if FStyleValuesNeedUpdate <> [] then + begin + DoUpdateValuesFromResources(Resources, Purpose); + FStyleValuesNeedUpdate := []; + end; +end; + +procedure TListItemDrawable.UpdateValuesFromStyle; +begin + FStyleValuesNeedUpdate := [Low(TStyleResource)..High(TStyleResource)]; +end; + +procedure TListItemDrawable.SetOpacity(const Value: Single); +var + NewValue: Single; +begin + NewValue := Value; + if NewValue < 0 then + NewValue := 0; + if NewValue > 1 then + NewValue := 1; + + if FOpacity <> NewValue then + begin + FOpacity := NewValue; + DoOpacityChange; + end; +end; + +procedure TListItemDrawable.PlaceOffsetChanged(Sender: TObject); +begin + Invalidate; + DoPlaceOffsetChanged; +end; + +procedure TListItemDrawable.CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); + +{$IFDEF PIXEL_ALIGNMENT} + function PixelAlign(const Value: Single): Single; + begin + Result := Int(Value * SceneScale) / SceneScale + end; +{$ELSE} + function PixelAlign(const Value: Single): Single; inline; + begin + Result := Value; + end; +{$ENDIF} + + procedure AlignValue(Align: TListItemAlign; out Left, Right: Single; + Width, DesLeft, DesRight, CustomOffset: Single); inline; + begin + case Align of + TListItemAlign.Center: + begin + Left := PixelAlign(CustomOffset + (DesLeft + DesRight - Width) * 0.5); + Right := Left + Width; + end; + + TListItemAlign.Trailing: + begin + Right := PixelAlign(CustomOffset + DesRight); + Left := Right - Width; + end; + + else + begin + Left := PixelAlign(CustomOffset + DesLeft); + Right := Left + Width; + end; + end; + end; + +var + LOffset: TPosition; + +begin + LOffset := PlaceOffset; + if (FWidth > 0) and (FWidth < DestRect.Width) then + begin + AlignValue(FAlign, FLocalRect.Left, FLocalRect.Right, FWidth, DestRect.Left, DestRect.Right, LOffset.X); + end + else + begin + FLocalRect.Left := PixelAlign(DestRect.Left + LOffset.X); + FLocalRect.Right := FLocalRect.Left + DestRect.Width; + end; + + if (FHeight > 0) and (FHeight < DestRect.Height) then + begin + AlignValue(FVertAlign, FLocalRect.Top, FLocalRect.Bottom, FHeight, DestRect.Top, DestRect.Bottom, LOffset.Y); + end + else + begin + FLocalRect.Top := PixelAlign(DestRect.Top + LOffset.Y); + FLocalRect.Bottom := FLocalRect.Top + DestRect.Height; + end; +end; + +procedure TListItemDrawable.BeginUpdate; +begin + Inc(FUpdating); +end; + +procedure TListItemDrawable.EndUpdate; +begin + if FUpdating > 0 then + begin + Dec(FUpdating); + if (FUpdating <= 0) and NeedRepaint then + begin + NeedRepaint := False; + Invalidate; + end; + end; +end; + +function TListItemDrawable.InLocalRect(const Point: TPointF): Boolean; +begin + Result := FLocalRect.Contains(Point); +end; + +procedure TListItemDrawable.Invalidate; +begin + if FUpdating < 1 then + begin + if Assigned(FCallback) then + FCallback(nil, Self, TListItemCallbackOp.InvalidateOwner); + end + else + NeedRepaint := True; +end; + + +function TListItemDrawable.ObjectAtPoint(const Point: TPointF): TControl; +begin + Result := nil; +end; + +function TListItemDrawable.GetData: TValue; +begin + Result := TValue.Empty; +end; + +function TListItemDrawable.GetPlaceOffset: TPosition; +begin + if FPlaceOffsetX = nil then + begin + FPlaceOffsetX := TPosition.Create(TPointF.Zero); + FPlaceOffsetX.OnChange := PlaceOffsetChanged; + end; + Result := FPlaceOffsetX; +end; + +function TListItemDrawable.GetRenderPassCount: Integer; +begin + Result := 0; +end; + +function TListItemDrawable.MouseDown(const Button: TMouseButton; const Shift: TShiftState; + const MousePos: TPointF): Boolean; +begin + Result := False; +end; + +procedure TListItemDrawable.MouseMove(const Shift: TShiftState; const MousePos: TPointF); +begin +end; + +procedure TListItemDrawable.MouseUp(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF); +begin +end; + +{$ENDREGION} +{$REGION 'List Item Text'} + +constructor TListItemText.Create(const AOwner: TListItem); +begin + inherited; + + FTextColor := claBlack; + FSelectedTextColor := claWhite; + FTextShadowColor := 0; + FTextAlign := TTextAlign.Leading; + FTextVertAlign := TTextAlign.Leading; + + UpdateValuesFromStyle; + + FWordWrap := False; + FTrimming := TTextTrimming.None; +end; + +destructor TListItemText.Destroy; +begin + FreeAndNil(FTextLayout); + FFontX.Free; + FTextShadowOffsetX.Free; + inherited; +end; + +procedure TListItemText.DoUpdateValuesFromResources(const Resources: TListItemStyleResources; + const Purpose: TListItemPurpose); +var + ResourceFont: TFont; +begin + ResourceFont := nil; + if Resources <> nil then + begin + if Purpose <> TListItemPurpose.None then + begin + ResourceFont := Resources.HeaderTextFont; + if (TStyleResource.TextColor in FStyleValuesNeedUpdate) and (Resources.HeaderTextColor > 0) and + TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.TextColor) then + FTextColor := Resources.HeaderTextColor; + if (TStyleResource.TextShadowColor in FStyleValuesNeedUpdate) and (Resources.HeaderTextShadowColor > 0) and + TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.TextShadowColor) then + FTextShadowColor := Resources.HeaderTextShadowColor; + FTextVertAlign := TTextAlign.Center; + end + else if FIsDetailText then + begin + ResourceFont := Resources.DetailTextFont; + if (TStyleResource.TextColor in FStyleValuesNeedUpdate) and (Resources.DetailTextColor > 0) and + TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.TextColor) then + FTextColor := Resources.DetailTextColor; + if (TStyleResource.SelectedTextColor in FStyleValuesNeedUpdate) and (Resources.DefaultTextSelectedColor > 0) and + TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.SelectedTextColor) then + FSelectedTextColor := Resources.DefaultTextSelectedColor; + end + else + begin + ResourceFont := Resources.DefaultTextFont; + if (TStyleResource.TextColor in FStyleValuesNeedUpdate) and (Resources.DefaultTextColor > 0) and + TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.TextColor) then + FTextColor := Resources.DefaultTextColor; + + if (TStyleResource.SelectedTextColor in FStyleValuesNeedUpdate) and (Resources.DefaultTextSelectedColor > 0) and + TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.SelectedTextColor) then + FSelectedTextColor := Resources.DefaultTextSelectedColor; + end; + end; + + if ResourceFont <> nil then + begin + if FFontX <> nil then + begin + FFontX.OnChanged := nil; + try + if (TStyleResource.FontFamily in FStyleValuesNeedUpdate) and TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.FontFamily) then + FFontX.Family := ResourceFont.Family; + if (TStyleResource.FontSize in FStyleValuesNeedUpdate) and TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.FontSize) then + FFontX.Size := ResourceFont.Size; + if (TStyleResource.FontStyle in FStyleValuesNeedUpdate) and TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.FontStyle) then + FFontX.StyleExt := ResourceFont.StyleExt; + finally + FFontX.OnChanged := FontChanged; + end; + end + else + begin + Font.Assign(ResourceFont); + Font.OnChanged := FontChanged; + end; + + FStyleValuesNeedUpdate := FStyleValuesNeedUpdate - TextResources; + end + else + Font.SetSettings(DefaultFontFamily, DefaultFontSize, Font.StyleExt); +end; + +procedure TListItemText.DoResize; +begin + inherited; + LayoutChanged := True; + Invalidate; +end; + +procedure TListItemText.FontChanged(Sender: TObject); +var + NewSettings: TFontSettings; +begin + NewSettings := FontSettingsSnapshot; + if not SameStr(NewSettings.Family, FFontSettings.Family) then + begin + TDefaultSettingsHelper.SetDefault(Self, TDrawableElement.FontFamily, False); + Exclude(FStyleValuesNeedUpdate, TStyleResource.FontFamily); + end; + if not SameValue(NewSettings.Size, FFontSettings.Size, TEpsilon.Position) then + begin + TDefaultSettingsHelper.SetDefault(Self, TDrawableElement.FontSize, False); + Exclude(FStyleValuesNeedUpdate, TStyleResource.FontSize); + end; + if NewSettings.Style <> FFontSettings.Style then + begin + TDefaultSettingsHelper.SetDefault(Self, TDrawableElement.FontStyle, False); + Exclude(FStyleValuesNeedUpdate, TStyleResource.FontStyle); + end; + FFontSettings := NewSettings; + + LayoutChanged := True; + Invalidate; +end; + +function TListItemText.FontSettingsSnapshot: TFontSettings; +begin + Result.Size := FFontX.Size; + Result.Style := FFontX.Style; + Result.Family := FFontX.Family; +end; + +function TListItemText.GetData: TValue; +begin + Result := Text; +end; + +function TListItemText.GetFont: TFont; +begin + if FFontX = nil then + begin + FFontX := TFont.Create; + FFontX.OnChanged := FontChanged; + end; + FFontSettings := FontSettingsSnapshot; + Result := FFontX; +end; + +function TListItemText.GetShadowOffset: TPosition; +begin + if FTextShadowOffsetX = nil then + begin + FTextShadowOffsetX := TPosition.Create(ShadowOffset); + FTextShadowOffsetX.OnChange := TextShadowOffsetChanged; + end; + Result := FTextShadowOffsetX; +end; + +procedure TListItemText.TextShadowOffsetChanged(Sender: TObject); +begin + Invalidate; +end; + +procedure TListItemText.SetTextShadowColor(const Value: TAlphaColor); +begin + if FTextShadowColor <> Value then + begin + FTextShadowColor := Value; + TDefaultSettingsHelper.SetDefault(Self, TDrawableElement.TextShadowColor, Value = TAlphaColorRec.Null); + Exclude(FStyleValuesNeedUpdate, TStyleResource.TextShadowColor); + Invalidate; + end; +end; + +procedure TListItemText.SetText(const Value: string); +begin + if FText <> Value then + begin + FText := Value; + LayoutChanged := True; + Invalidate; + end; +end; + +procedure TListItemText.SetTextAlign(const Value: TTextAlign); +begin + if FTextAlign <> Value then + begin + FTextAlign := Value; + LayoutChanged := True; + Invalidate; + end; +end; + +procedure TListItemText.SetTextColor(const Value: TAlphaColor); +begin + if FTextColor <> Value then + begin + FTextColor := Value; + TDefaultSettingsHelper.SetDefault(Self, TDrawableElement.TextColor, Value = TAlphaColorRec.Null); + Exclude(FStyleValuesNeedUpdate, TStyleResource.TextColor); + LayoutChanged := True; + Invalidate; + end; +end; + +procedure TListItemText.SetData(const AValue: TValue); +begin + if AValue.IsEmpty then + Text := '' + else + Text := AValue.ToString; +end; + +procedure TListItemText.SetIsDetailText(const Value: Boolean); +begin + if FIsDetailText <> Value then + FIsDetailText := Value; +end; + +procedure TListItemText.SetSelectedTextColor(const Value: TAlphaColor); +begin + if FSelectedTextColor <> Value then + begin + FSelectedTextColor := Value; + TDefaultSettingsHelper.SetDefault(Self, TDrawableElement.SelectedTextColor, Value = TAlphaColorRec.Null); + Exclude(FStyleValuesNeedUpdate, TStyleResource.SelectedTextColor); + LayoutChanged := True; + Invalidate; + end; +end; + +procedure TListItemText.SetTextVertAlign(const Value: TTextAlign); +begin + if FTextVertAlign <> Value then + begin + FTextVertAlign := Value; + LayoutChanged := True; + Invalidate; + end; +end; + +procedure TListItemText.SetTrimming(const Value: TTextTrimming); +begin + if FTrimming <> Value then + begin + FTrimming := Value; + LayoutChanged := True; + Invalidate; + end; +end; + +procedure TListItemText.SetWordWrap(const Value: Boolean); +begin + if FWordWrap <> Value then + begin + FWordWrap := Value; + LayoutChanged := True; + Invalidate; + end; +end; + +procedure TListItemText.CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); +var + NewRect: TRectF; + DeadSpace: Single; +begin + if TListItemDrawState.EditMode in DrawStates then + begin + NewRect := DestRect; + + if Item.Controller <> nil then + begin + DeadSpace := Item.Controller.GetItemEditOffset(Item) * Item.Controller.EditModeTransitionAlpha; + NewRect.Left := NewRect.Left + DeadSpace; + + if FIsDetailText then + NewRect.Right := Max(NewRect.Right + DeadSpace, NewRect.Left); + end; + + inherited CalculateLocalRect(NewRect, SceneScale, DrawStates, Item); + + // Text should no go over right client area when moved in edit mode. + if Item.Controller <> nil then + FLocalRect.Right := Min(FLocalRect.Right, Item.Controller.GetClientMargins.Right); + end + else + inherited; + + { Delete button cuts through the entire right side, so it needs to be marginalized globally. + This is because LocalRect may actually leave the boundaries of DestRect depending on PlacementOffset. } + if (TListItemDrawState.Deleting in DrawStates) and (Item.Controller <> nil) then + FLocalRect.Right := Min(FLocalRect.Right, Item.Controller.GetItemDeleteCutoff(Item)); +end; + +procedure TListItemText.Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates; const Resources: TListItemStyleResources; const Params: TListItemDrawable.TParams; const SubPassNo: Integer = 0); +var + CurColor: TAlphaColor; + SelectedAlpha, LOpacity: Single; +begin + if SubPassNo <> 0 then + Exit; + + if FTextLayout = nil then + begin + FTextLayout := TTextLayoutManager.TextLayoutByCanvas(Canvas.ClassType).Create(Canvas); + LayoutChanged := True + end; + + if not LayoutChanged then + LayoutChanged := (FTextLayout.MaxSize.X <> FLocalRect.Width) or (FTextLayout.MaxSize.Y <> FLocalRect.Height); + + CurColor := FTextColor; + LOpacity := Params.AbsoluteOpacity; + if TListItemDrawState.Selected in DrawStates then + begin + SelectedAlpha := Params.ItemSelectedAlpha; + + if (SelectedAlpha > 0) and (SelectedAlpha < 1) then + CurColor := Blend(CurColor, FSelectedTextColor, SelectedAlpha) + else if SelectedAlpha >= 1 then + CurColor := FSelectedTextColor; + end; + + if (not LayoutChanged) and (FTextLayout.Color <> CurColor) then + LayoutChanged := True; + + if LayoutChanged then + begin + FTextLayout.BeginUpdate; + try + FTextLayout.HorizontalAlign := FTextAlign; + FTextLayout.VerticalAlign := FTextVertAlign; + FTextLayout.Font := Font; + FTextLayout.Color := CurColor; + FTextLayout.RightToLeft := False; + FTextLayout.MaxSize := TPointF.Create(FLocalRect.Width, FLocalRect.Height); + FTextLayout.Text := FText; + FTextLayout.Trimming := FTrimming; + FTextLayout.WordWrap := FWordWrap; + finally + FTextLayout.EndUpdate; + LayoutChanged := False; + end; + end; + + if TAlphaColorRec(FTextShadowColor).A > 0 then + begin + FTextLayout.BeginUpdate; + FTextLayout.Opacity := LOpacity; + FTextLayout.Color := FTextShadowColor; + FTextLayout.TopLeft := FLocalRect.TopLeft + TextShadowOffset.Point; + FTextLayout.EndUpdate; + FTextLayout.RenderLayout(Canvas); + + FTextLayout.BeginUpdate; + FTextLayout.Color := CurColor; + FTextLayout.EndUpdate; + end; + +{$IFDEF DRAW_ITEM_MARGINS} + if FIsDetailText then + MarginBrush.Color := $50FF0000 + else + MarginBrush.Color := $5000FF00; + + Canvas.FillRect(LocalRect, 0, 0, AllCorners, 1, MarginBrush); +{$ENDIF} + + FTextLayout.BeginUpdate; + FTextLayout.Opacity := LOpacity; + FTextLayout.TopLeft := FLocalRect.TopLeft; + FTextLayout.EndUpdate; + FTextLayout.RenderLayout(Canvas); +end; + +{$ENDREGION} +{$REGION 'List Item Dummy'} + +procedure TListItemDummy.Render(const Canvas: TCanvas; const DrawItemIndex: Integer; + const DrawStates: TListItemDrawStates; const Resources: TListItemStyleResources; + const Params: TListItemDrawable.TParams; const SubPassNo: Integer = 0); +begin +end; + +{$ENDREGION} +{$REGION 'List Item Image'} + +constructor TListItemImage.Create(const AOwner: TListItem); +begin + inherited; + FImageIndex := -1; + FImageScalingMode := TImageScalingMode.StretchWithAspect; +end; + +destructor TListItemImage.Destroy; +begin + FStaticBitmap.Free; + inherited; +end; + +function TListItemImage.GetBitmap: TBitmap; +begin + if FOwnsBitmap then + Result := FStaticBitmap + else + Result := FReferBitmap; +end; + +procedure TListItemImage.SetBitmap(const Value: TBitmap); +begin + if FOwnsBitmap then + FStaticBitmap := Value + else + FReferBitmap := Value; + if FImageSource <> TImageSource.ImageList then + Invalidate; +end; + +procedure TListItemImage.SetData(const Value: TValue); +var + LBitmap: TBitmap; + LIndex: Integer; + LEIndex: Extended; + LSIndex: string; + LCode: Integer; +begin + if Value.TryAsType(LBitmap) then + Bitmap := LBitmap + else if Value.TryAsType(LIndex) then + SetImageIndex(LIndex) + else if Value.TryAsType(LSIndex) then + begin + Val(LSIndex, LIndex, LCode); + if LCode = 0 then + SetImageIndex(LIndex); + end + else if Value.TryAsType(LEIndex) then + SetImageIndex(Trunc(LEIndex)); +end; + +procedure TListItemImage.SetOwnsBitmap(const Value: Boolean); +begin + if FOwnsBitmap = Value then + Exit; + + if FOwnsBitmap and (not Value) then + begin + FReferBitmap := FStaticBitmap; + FStaticBitmap := nil; + end + else if (not FOwnsBitmap) and Value then + begin + FStaticBitmap := FReferBitmap; + FReferBitmap := nil; + end; + + FOwnsBitmap := Value; +end; + +procedure TListItemImage.SetImageIndex(const Value: TImageIndex); +begin + if FImageIndex <> Value then + begin + FImageIndex := Value; + Invalidate; + end; +end; + +procedure TListItemImage.SetImageScalingMode(const Value: TImageScalingMode); +begin + if FImageScalingMode <> Value then + begin + FImageScalingMode := Value; + Invalidate; + end; +end; + +procedure TListItemImage.SetSrcRect(const Value: TRectF); +begin + if FSrcRect <> Value then + begin + FSrcRect := Value; + Invalidate; + end; +end; + +procedure TListItemImage.CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); +var + NewRect: TRectF; + DeadSpace: Single; +begin + if TListItemDrawState.EditMode in DrawStates then + begin + NewRect := DestRect; + + if Item.Controller <> nil then + begin + DeadSpace := Item.Controller.GetItemEditOffset(Item) * Item.Controller.EditModeTransitionAlpha; + NewRect.Left := NewRect.Left + DeadSpace; + end; + + inherited CalculateLocalRect(NewRect, SceneScale, DrawStates, Item); + end + else + inherited; +end; + +function TListItemImage.GetImageSource: TImageSource; +begin + if FImageIndex <> -1 then + FImageSource := TImageSource.ImageList + else if GetBitmap <> nil then + FImageSource := TImageSource.Bitmap + else + FImageSource := TImageSource.None; + Result := FImageSource; +end; + +procedure TListItemImage.FitInto(const Bitmap: TBitmap; var InputRect, DestinationRect: TRectF); + procedure ClipRects(var InpRect, DestRect: TRectF; const LocalRect: TRectF); + var + Delta: Single; + begin + if DestRect.Right > LocalRect.Right then + begin + Delta := 1 - ((DestRect.Right - LocalRect.Right) / DestRect.Width); + + InpRect.Right := InpRect.Left + InpRect.Width * Delta; + DestRect.Right := LocalRect.Right; + end; + + if DestRect.Bottom > LocalRect.Bottom then + begin + Delta := 1 - ((DestRect.Bottom - LocalRect.Bottom) / DestRect.Height); + + InpRect.Bottom := InpRect.Top + InpRect.Height * Delta; + DestRect.Bottom := LocalRect.Bottom; + end; + end; + +var + LocRect, InpRect, DestRect: TRectF; + Aspect: Single; + XAspect, YAspect: Single; +begin + LocRect := FLocalRect; + + if FSrcRect.Width < 1 then + begin + InpRect.Left := 0; + InpRect.Right := Bitmap.Width; + end + else + begin + InpRect.Left := FSrcRect.Left; + InpRect.Right := FSrcRect.Right; + end; + + if FSrcRect.Height < 1 then + begin + InpRect.Top := 0; + InpRect.Bottom := Bitmap.Height; + end + else + begin + InpRect.Top := FSrcRect.Top; + InpRect.Bottom := FSrcRect.Bottom; + end; + + case FImageScalingMode of + TImageScalingMode.Original: + begin + DestRect.Left := LocRect.Left; + DestRect.Top := LocRect.Top; + DestRect.Right := DestRect.Left + InpRect.Width; + DestRect.Bottom := DestRect.Top + InpRect.Height; + + ClipRects(InpRect, DestRect, LocRect); + + // Center image + DestRect.Offset((FLocalRect.Right - DestRect.Right) / 2, (FLocalRect.Bottom - DestRect.Bottom) / 2); + end; + + TImageScalingMode.Stretch: + DestRect := LocRect; + + TImageScalingMode.StretchWithAspect: + begin + // Calc ratios + if InpRect.Width > 0 then + XAspect := FLocalRect.Width / InpRect.Width + else + XAspect := 1; + + if InpRect.Height > 0 then + YAspect := FLocalRect.Height / InpRect.Height + else + YAspect := 1; + + // Use smallest ratio + if YAspect < XAspect then + Aspect := YAspect + else + Aspect := XAspect; + + DestRect := FLocalRect; + DestRect.Right := DestRect.Left + InpRect.Width * Aspect; + DestRect.Bottom := DestRect.Top + InpRect.Height * Aspect; + // Center image + DestRect.Offset((FLocalRect.Right - DestRect.Right) / 2, (FLocalRect.Bottom - DestRect.Bottom) / 2); + end; + end; + + InputRect := InpRect; + DestinationRect := DestRect; +end; + +procedure TListItemImage.Render(const Canvas: TCanvas; const DrawItemIndex: Integer; + const DrawStates: TListItemDrawStates; const Resources: TListItemStyleResources; + const Params: TListItemDrawable.TParams; const SubPassNo: Integer = 0); +var + Bitmap: TBitmap; + InpRect, DestRect: TRectF; +begin + if SubPassNo <> 0 then + Exit; + +{$IFDEF DRAW_ITEM_MARGINS} + MarginBrush.Color := $50808000; + Canvas.FillRect(LocalRect, 0, 0, AllCorners, 1, MarginBrush); +{$ENDIF} + + if (ImageSource = TImageSource.ImageList) and (Params.Images <> nil) then + Bitmap := Params.Images.Bitmap(TPointF(FLocalRect.Size) * Canvas.Scale, FImageIndex) + else + Bitmap := GetBitmap; + + if Bitmap = nil then + Exit; + + FitInto(Bitmap, InpRect, DestRect); + + Canvas.DrawBitmap(Bitmap, InpRect, DestRect, Params.AbsoluteOpacity); +end; + +{$ENDREGION} +{$REGION 'List Item Simple Control'} + +constructor TListItemSimpleControl.Create(const AOwner: TListItem); +begin + inherited; + + FEnabled := True; + +{$IF DEFINED(IOS) OR DEFINED(ANDROID)} + FTouchExpand := 8; +{$ENDIF} +end; + +procedure TListItemSimpleControl.SetEnabled(const Value: Boolean); +begin + if FEnabled <> Value then + begin + FEnabled := Value; + DoEnabledChange; + end; +end; + +function TListItemSimpleControl.PointInLocalRect(const Pos: TPointF): Boolean; +var + LocRect: TRectF; +begin + LocRect := FLocalRect; + LocRect.Inflate(FTouchExpand, FTouchExpand); + + Result := LocRect.Contains(Pos); +end; + +function TListItemSimpleControl.IsClickOpaque: Boolean; +begin + Result := True; +end; + +function TListItemSimpleControl.MouseDown(const Button: TMouseButton; const Shift: TShiftState; + const MousePos: TPointF): Boolean; +begin + Result := False; + + if (Button = TMouseButton.mbLeft) and FEnabled and PointInLocalRect(MousePos) and IsClickOpaque then + begin + FPressed := True; + FMouseOver := True; + + Invalidate; + + Result := True; + end; +end; + +procedure TListItemSimpleControl.MouseMove(const Shift: TShiftState; const MousePos: TPointF); +begin +// On iOS, once "mouse" is outside of control area, the control never regains pressed/over state, requiring a new tap. +// FMouseOver := FMouseOver and PointInLocalRect(MousePos); + FMouseOver := PointInLocalRect(MousePos); + Invalidate; +end; + +procedure TListItemSimpleControl.MouseUp(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF); +var + ShouldClick: Boolean; +begin + if FPressed then + begin + ShouldClick := FMouseOver; + + FPressed := False; + FMouseOver := False; + + if ShouldClick then + DoClick; + + Invalidate; + end; +end; + +procedure TListItemSimpleControl.DoClick; +begin + FCallback(nil, Self, TListItemCallbackOp.Click); +end; + +procedure TListItemSimpleControl.DoEnabledChange; +begin + Invalidate; +end; + +procedure TListItemSimpleControl.Click; +begin + DoClick; +end; + +{$ENDREGION} +{$REGION 'List Item Accessory'} + +constructor TListItemAccessory.Create(const AOwner: TListItem); +begin + inherited; + +end; + +procedure TListItemAccessory.CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); +var + NewRect: TRectF; +begin + if TListItemDrawState.EditMode in DrawStates then + begin + NewRect := DestRect; + if FController <> nil then + NewRect.Offset(FController.GetItemEditOffset(Item) * FController.EditModeTransitionAlpha, 0); + + inherited CalculateLocalRect(NewRect, SceneScale, DrawStates, Item); + end + else + inherited; +end; + +procedure TListItemAccessory.Render(const Canvas: TCanvas; const DrawItemIndex: Integer; + const DrawStates: TListItemDrawStates; const Resources: TListItemStyleResources; + const Params: TListItemDrawable.TParams; const SubPassNo: Integer = 0); +var + Image: TStyleObject; + TempOpacity, SelectedAlpha: Single; +begin + if (SubPassNo <> 0) or (TListItemDrawState.Deleting in DrawStates) then + Exit; + + TempOpacity := Params.AbsoluteOpacity; + + + + + + if TempOpacity < OpacityEpsilon then + Exit; + +{$IFDEF DRAW_ITEM_MARGINS} + MarginBrush.Color := $500040FF; + Canvas.FillRect(LocalRect, 0, 0, AllCorners, 1, MarginBrush); +{$ENDIF} + + if Resources <> nil then + begin + if TListItemDrawState.Selected in DrawStates then + begin // Apply crossfading between selected and non-selected states. + SelectedAlpha := Params.ItemSelectedAlpha; + + if SelectedAlpha < 1 then + begin + Image := Resources.AccessoryImages[FAccessoryType].Normal; + + if Image <> nil then + Image.DrawToCanvas(Canvas, FLocalRect, TempOpacity * (1 - SelectedAlpha)); + end; + + if SelectedAlpha > 0 then + begin + Image := Resources.AccessoryImages[FAccessoryType].Selected; + + if Image <> nil then + Image.DrawToCanvas(Canvas, FLocalRect, TempOpacity * SelectedAlpha); + end; + end + else + begin // Draw normal non-selected image. + Image := Resources.AccessoryImages[FAccessoryType].Normal; + + if Image <> nil then + Image.DrawToCanvas(Canvas, FLocalRect, TempOpacity); + end; + end; +end; + +procedure TListItemAccessory.SetAccessoryType(Value: TAccessoryType); +begin + if FAccessoryType <> Value then + begin + FAccessoryType := Value; + Invalidate; + end; +end; + +{$ENDREGION} +{$REGION 'List Item Glyph Button'} + +constructor TListItemGlyphButton.Create(const AOwner: TListItem); +begin + inherited; + FTransitionEnabled := False; + FTransitionAlpha := 0; +end; + +destructor TListItemGlyphButton.Destroy; +begin + FreeAndNil(FTransitionTimer); + inherited; +end; + +procedure TListItemGlyphButton.SetButtonType(const Value: TGlyphButtonType); +begin + if FButtonType <> Value then + begin + FButtonType := Value; + Invalidate; + end; +end; + +procedure TListItemGlyphButton.SetData(const AValue: TValue); +var + LChecked: Boolean; +begin + if AValue.TryAsType(LChecked) then + Checked := LChecked; +end; + +procedure TListItemGlyphButton.InitCheckedTransition; +begin + FTransitionEnabled := True; + + TPlatformServices.Current.SupportsPlatformService(IFMXTimerService, FTimerService); + + if FTransitionTimer = nil then + begin + FTransitionTimer := TTimer.Create(nil); + FTransitionTimer.Enabled := False; + FTransitionTimer.Interval := Round(1000 / CheckedAnimationFrameRate); + FTransitionTimer.OnTimer := TransitionTimerNotify; + end; + + FTransitionTimer.Enabled := True; + + if FTimerService <> nil then + FTransitionStartTicks := FTimerService.GetTick + else + FTransitionStartTicks := 0; +end; + +procedure TListItemGlyphButton.ResetCheckedTransition; +begin + FTransitionEnabled := False; + + if FTransitionTimer <> nil then + begin + FTransitionTimer.Enabled := False; + FTransitionTimer.Parent := nil; + FreeAndNil(FTransitionTimer); + end; + + if FTimerService <> nil then + FTimerService := nil; + + if FChecked then + FTransitionAlpha := 1 + else + FTransitionAlpha := 0; +end; + +procedure TListItemGlyphButton.TransitionTimerNotify(Sender: TObject); +const + CheckedAnimationIncrement = 1 / (60 * CheckedAnimationDuration); +begin + if FChecked then + begin + if FTimerService <> nil then + FTransitionAlpha := Min(Abs(FTimerService.GetTick - FTransitionStartTicks) / CheckedAnimationDuration, 1) + else + FTransitionAlpha := FTransitionAlpha + CheckedAnimationIncrement; + + if FTransitionAlpha >= 1 then + ResetCheckedTransition; + end + else + begin + if FTimerService <> nil then + FTransitionAlpha := Max(1 - (Abs(FTimerService.GetTick - FTransitionStartTicks) / CheckedAnimationDuration), 0) + else + FTransitionAlpha := FTransitionAlpha - CheckedAnimationIncrement; + + if FTransitionAlpha <= 0 then + ResetCheckedTransition; + end; + + Invalidate; +end; + +procedure TListItemGlyphButton.DoClick; +begin + inherited; + if not FClickOnSelect then + FCallback(nil, Self, TListItemCallbackOp.Click); +end; + +procedure TListItemGlyphButton.SetChecked(const Value: Boolean); +begin + if FChecked <> Value then + begin + InitCheckedTransition; + FChecked := Value; + if not FTransitionEnabled then + ResetCheckedTransition; + Invalidate; + end; +end; + +procedure TListItemGlyphButton.DoSelect; +begin + inherited; + if FClickOnSelect then + FCallback(nil, Self, TListItemCallbackOp.Click); +end; + +function TListItemGlyphButton.MouseDown(const Button: TMouseButton; const Shift: TShiftState; + const MousePos: TPointF): Boolean; +begin + if not FClickOnSelect then + Result := inherited MouseDown(Button, Shift, MousePos) + else + Result := False; +end; + +procedure TListItemGlyphButton.CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); +var + NewRect: TRectF; +begin + if TListItemDrawState.EditMode in DrawStates then + begin + NewRect := DestRect; + + if Item.Controller <> nil then + NewRect.Offset(Item.Controller.GetItemEditOffset(Item) * (Item.Controller.EditModeTransitionAlpha - 1), 0); + + inherited CalculateLocalRect(NewRect, SceneScale, DrawStates, Item); + end + else + inherited; + +{$IF DEFINED(MSWINDOWS) OR DEFINED(OSX)} + FLocalRect := FLocalRect.SnapToPixel(SceneScale, False); +{$ENDIF} +end; + +procedure TListItemGlyphButton.Render(const Canvas: TCanvas; const DrawItemIndex: Integer; + const DrawStates: TListItemDrawStates; const Resources: TListItemStyleResources; + const Params: TListItemDrawable.TParams; const SubPassNo: Integer = 0); +var + ReallyPressed: Boolean; + TempOpacity: Single; + PrevMatrix, M: TMatrix; + LocRect: TRectF; + CenterShift: TPointF; + RenderGlyphButtonType: TGlyphButtonType; +begin + if SubPassNo <> 0 then + Exit; + + ReallyPressed := FPressed and FMouseOver; + + LocRect := FLocalRect; + TempOpacity := Params.AbsoluteOpacity; + + if not FEnabled then + TempOpacity := TempOpacity * DisabledOpacity; + + if TListItemDrawState.EditMode in DrawStates then + TempOpacity := TempOpacity * Max(0.1, FController.EditModeTransitionAlpha * 2 - 1) + else + Exit; + +{$IFDEF DRAW_ITEM_MARGINS} + MarginBrush.Color := $50804020; + Canvas.FillRect(LocalRect, 0, 0, AllCorners, 1, MarginBrush); +{$ENDIF} + + if Resources <> nil then + begin + RenderGlyphButtonType := FButtonType; + + if (RenderGlyphButtonType = TGlyphButtonType.Delete) and (not FController.IsDeleteModeAllowed) then + RenderGlyphButtonType := TGlyphButtonType.Checkbox; + case RenderGlyphButtonType of + TGlyphButtonType.Add: + begin + if (not ReallyPressed) and (Resources.ButtonAddItemStyleImage.Normal <> nil) then + Resources.ButtonAddItemStyleImage.Normal.DrawToCanvas(Canvas, LocRect, TempOpacity); + + if ReallyPressed and (Resources.ButtonAddItemStyleImage.Pressed <> nil) then + Resources.ButtonAddItemStyleImage.Pressed.DrawToCanvas(Canvas, LocRect, TempOpacity); + end; + + TGlyphButtonType.Delete: + begin + LocRect.Left := LocRect.Left + MinusOffsetX; + if Resources.ButtonDeleteItemStyleImage.Normal <> nil then + Resources.ButtonDeleteItemStyleImage.Normal.DrawToCanvas(Canvas, LocRect, TempOpacity); + + if Resources.ButtonDeleteItemStyleImage.Pressed <> nil then + begin + if FTransitionAlpha <= 0 then + Resources.ButtonDeleteItemStyleImage.Pressed.DrawToCanvas(Canvas, LocRect, TempOpacity) + else + begin + PrevMatrix := Canvas.Matrix; + + CenterShift.X := LocRect.Left + LocRect.Width * 0.5; + CenterShift.Y := LocRect.Top + LocRect.Height * 0.5; + + M := TMatrix.CreateTranslation(-CenterShift.X, -CenterShift.Y) + * TMatrix.CreateRotation(-FTransitionAlpha * Pi * 0.5) + * TMatrix.CreateTranslation(CenterShift.X, CenterShift.Y) + * PrevMatrix; + + Canvas.SetMatrix(M); + + Resources.ButtonDeleteItemStyleImage.Pressed.DrawToCanvas(Canvas, LocRect, TempOpacity); + + Canvas.SetMatrix(PrevMatrix); + end; + end; + end; + + TGlyphButtonType.Checkbox: + begin + if (not FChecked) and (Resources.ButtonCheckboxStyleImage.Normal <> nil) then + Resources.ButtonCheckboxStyleImage.Normal.DrawToCanvas(Canvas, LocRect, TempOpacity); + + if FChecked and (Resources.ButtonCheckboxStyleImage.Pressed <> nil) then + Resources.ButtonCheckboxStyleImage.Pressed.DrawToCanvas(Canvas, LocRect, TempOpacity); + end; + end; + end; +end; + +{$ENDREGION} +{$REGION 'List Item Text Button'} + + +{ TListItemTextButton } + +constructor TListItemTextButton.Create(const AOwner: TListItem); +begin + FTextDrawable := TListItemText.Create(nil); + inherited; + + FTintColor := TAlphaColorRec.Null; + FPressedTextColor := claWhite; + + UpdateValuesFromStyle; +end; + +destructor TListItemTextButton.Destroy; +begin + FTextDrawable.Free; + inherited; +end; + +procedure TListItemTextButton.CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); +begin + inherited; + + FTextDrawable.CalculateLocalRect(DestRect, SceneScale, DrawStates, Item); + FLocalRect := FTextDrawable.FLocalRect; +{$IF DEFINED(MSWINDOWS) OR DEFINED(OSX)} + FLocalRect := FLocalRect.SnapToPixel(SceneScale, False); +{$ENDIF} +end; + +procedure TListItemTextButton.DoAlignChanged; +begin + inherited; + FTextDrawable.Align := Align; + FTextDrawable.VertAlign := VertAlign; +end; + +procedure TListItemTextButton.DoOpacityChange; +begin + inherited; + FTextDrawable.DoOpacityChange; +end; + +procedure TListItemTextButton.DoPlaceOffsetChanged; +begin + FTextDrawable.PlaceOffset.Point := PlaceOffset.Point; +end; + +procedure TListItemTextButton.DoResize; +begin + inherited; + FTextDrawable.SetSize(Size); +end; + + +procedure TListItemTextButton.DoUpdateValuesFromResources(const Resources: TListItemStyleResources; + const Purpose: TListItemPurpose); +var + Surrogate: TListItemStyleResources; +begin + Surrogate := TListItemStyleResources.Create(Resources); + try + case FButtonType of + TTextButtonType.Normal: + begin + Surrogate.DefaultTextFont := Resources.ButtonTextFont; + Surrogate.HeaderTextFont := Resources.ButtonTextFont; + + if TStyleResource.TextColor in FStyleValuesNeedUpdate then + begin + if TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.TextColor) then + FTextColor := Resources.ButtonTextColor; + if TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.PressedTextColor) then + FPressedTextColor := Resources.ButtonTextPressedColor; + end; + end; + TTextButtonType.Delete: + begin + Surrogate.DefaultTextFont := Resources.DeleteButtonTextFont; + Surrogate.HeaderTextFont := Resources.DeleteButtonTextFont; + + if TStyleResource.TextColor in FStyleValuesNeedUpdate then + begin + if TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.TextColor) then + FTextColor := Resources.DeleteButtonTextColor; + if TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.PressedTextColor) then + FPressedTextColor := Resources.DeleteButtonTextPressedColor; + end; + end; + end; + + FTextDrawable.DoUpdateValuesFromResources(Surrogate, Purpose); + + FStyleValuesNeedUpdate := FStyleValuesNeedUpdate - TextResources; + + finally + Surrogate.Free; + end; +end; + +procedure TListItemTextButton.SetData(const AValue: TValue); +begin + if AValue.IsEmpty then + Text := '' + else + Text := AValue.ToString; +end; + +procedure TListItemTextButton.SetInvalidateCallback(const Callback: TListItemCallback); +begin + inherited; + FTextDrawable.InvalidateCallback := FCallback; +end; + +function TListItemTextButton.GetRenderPassCount: Integer; +begin + if FTextDrawable.Text.Length > 0 then + Result := 2 + else + Result := inherited; +end; + +function TListItemTextButton.IsClickOpaque: Boolean; +begin + Result := inherited; + + if Result and (FController <> nil) and (FController.EditModeTransitionAlpha > AnimationDeltaEpsilon) then + Result := False; +end; + +procedure TListItemTextButton.Render(const Canvas: TCanvas; const DrawItemIndex: Integer; + const DrawStates: TListItemDrawStates; const Resources: TListItemStyleResources; + const Params: TListItemDrawable.TParams; const SubPassNo: Integer); +var + ReallyPressed: Boolean; + TempOpacity: Single; +begin + TempOpacity := Params.AbsoluteOpacity; + + if not FEnabled then + TempOpacity := TempOpacity * DisabledOpacity; + + if TListItemDrawState.Deleting in DrawStates then + TempOpacity := TempOpacity * Params.DeletingUnwantedOpacity; + + if (TListItemDrawState.EditMode in DrawStates) and (FController <> nil) then + TempOpacity := TempOpacity * (1 - FController.EditModeTransitionAlpha); + + if TempOpacity < OpacityEpsilon then + Exit; + + ReallyPressed := FPressed and FMouseOver; + + // background + if SubPassNo = 0 then + begin +{$IFDEF DRAW_ITEM_MARGINS} + MarginBrush.Color := $50804020; + Canvas.FillRect(LocalRect, 0, 0, AllCorners, 1, MarginBrush); +{$ENDIF} + + if Resources <> nil then + begin + case FButtonType of + TTextButtonType.Normal: + begin + if (not ReallyPressed) and (Resources.ButtonNormalStyleImage.Normal <> nil) then + Resources.ButtonNormalStyleImage.Normal.DrawToCanvas(Canvas, FLocalRect, FTintColor, TempOpacity); + + if ReallyPressed and (Resources.ButtonNormalStyleImage.Pressed <> nil) then + Resources.ButtonNormalStyleImage.Pressed.DrawToCanvas(Canvas, FLocalRect, FTintColor, TempOpacity); + end; + + TTextButtonType.Delete: + begin + if (not ReallyPressed) and (Resources.ButtonDeleteStyleImage.Normal <> nil) then + Resources.ButtonDeleteStyleImage.Normal.DrawToCanvas(Canvas, FLocalRect, FTintColor, TempOpacity); + + if ReallyPressed and (Resources.ButtonDeleteStyleImage.Pressed <> nil) then + Resources.ButtonDeleteStyleImage.Pressed.DrawToCanvas(Canvas, FLocalRect, FTintColor, TempOpacity); + end; + end; + end; + end + else if SubPassNo = 1 then + begin + if ReallyPressed then + begin + FTextDrawable.FTextColor := FPressedTextColor; + FTextDrawable.FSelectedTextColor := FPressedTextColor; + end + else + begin + FTextDrawable.FTextColor := FTextColor; + FTextDrawable.FSelectedTextColor := FTextColor; + end; + FTextDrawable.Render(Canvas, DrawItemIndex, DrawStates, Resources, Params, 0); + end; +end; + +procedure TListItemTextButton.SetButtonType(const Value: TTextButtonType); +begin + if FButtonType <> Value then + begin + FButtonType := Value; + UpdateValuesFromStyle; + Invalidate; + end; +end; + + +function TListItemTextButton.GetTextColor: TAlphaColor; +begin + Result := FTextDrawable.TextColor; +end; + +function TListItemTextButton.GetTextShadowColor: TAlphaColor; +begin + Result := FTextDrawable.TextShadowColor; +end; + +function TListItemTextButton.GetTextShadowOffset: TPosition; +begin + Result := FTextDrawable.TextShadowOffset; +end; + +procedure TListItemTextButton.SetText(const Value: string); +begin + FTextDrawable.DoResize; + FTextDrawable.FUpdating := FUpdating; + try + FTextDrawable.Text := Value; + if FTextDrawable.NeedRepaint then + begin + FTextDrawable.UpdateValuesFromStyle; + Invalidate; + end; + finally + FTextDrawable.FUpdating := 0; + end; +end; + +function TListItemTextButton.GetText: string; +begin + Result := FTextDrawable.Text; +end; + + +procedure TListItemTextButton.SetTextAlign(const Value: TTextAlign); +begin + FTextDrawable.TextAlign := Value; + if FTextDrawable.NeedRepaint then + Invalidate; +end; + +function TListItemTextButton.GetTextAlign: TTextAlign; +begin + Result := FTextDrawable.TextAlign; +end; + +function TListItemTextButton.GetFont: TFont; +begin + Result := FTextDrawable.Font; +end; + +procedure TListItemTextButton.SetTextColor(const Value: TAlphaColor); +begin + if FTextColor <> Value then + begin + FTextColor := Value; + TDefaultSettingsHelper.SetDefault(Self, TDrawableElement.TextColor, Value = TAlphaColorRec.Null); + FTextDrawable.TextColor := Value; + Exclude(FStyleValuesNeedUpdate, TStyleResource.TextColor); + Invalidate; + end; +end; + +procedure TListItemTextButton.SetPressedTextColor(const Value: TAlphaColor); +begin + if FPressedTextColor <> Value then + begin + FPressedTextColor := Value; + TDefaultSettingsHelper.SetDefault(Self, TDrawableElement.PressedTextColor, Value = TAlphaColorRec.Null); + Exclude(FStyleValuesNeedUpdate, TStyleResource.PressedTextColor); + Invalidate; + end; +end; + +procedure TListItemTextButton.SetTextShadowColor(const Value: TAlphaColor); +begin + TDefaultSettingsHelper.SetDefault(Self, TDrawableElement.TextShadowColor, Value = TAlphaColorRec.Null); + FTextDrawable.TextShadowColor := Value; + Exclude(FStyleValuesNeedUpdate, TStyleResource.TextShadowColor); + if FTextDrawable.NeedRepaint then + Invalidate; +end; + +procedure TListItemTextButton.SetTextVertAlign(const Value: TTextAlign); +begin + FTextDrawable.TextVertAlign := Value; + if FTextDrawable.NeedRepaint then + Invalidate; +end; + +function TListItemTextButton.GetTextVertAlign: TTextAlign; +begin + Result := FTextDrawable.TextVertAlign; +end; + +procedure TListItemTextButton.SetTintColor(const Value: TAlphaColor); +begin + if FTintColor <> Value then + begin + FTintColor := Value; + Invalidate; + end; +end; + +procedure TListItemTextButton.SetTrimming(const Value: TTextTrimming); +begin + FTextDrawable.Trimming := Value; + if FTextDrawable.NeedRepaint then + Invalidate; +end; + +function TListItemTextButton.GetTrimming: TTextTrimming; +begin + Result := FTextDrawable.Trimming; +end; + +procedure TListItemTextButton.SetWordWrap(const Value: Boolean); +begin + FTextDrawable.WordWrap := Value; + if FTextDrawable.NeedRepaint then + Invalidate; +end; + +function TListItemTextButton.GetWordWrap: Boolean; +begin + Result := FTextDrawable.WordWrap; +end; + +{$ENDREGION} +{$REGION 'List Item Control Scene'} + +constructor TListItemControlScene.Create(AOwner: TComponent); +begin + inherited; +end; + +destructor TListItemControlScene.Destroy; +begin + inherited; +end; + +procedure TListItemControlScene.DisableUpdating; +begin + Inc(FDisableUpdating); +end; + +procedure TListItemControlScene.SetContainer(const Container: TControl); +begin + FContainer := Container; +end; + +procedure TListItemControlScene.SetOwnerItem(const Item: TListItem); +begin + FOwnerItem := Item; +end; + +procedure TListItemControlScene.SetStyleBook(const Value: TStyleBook); +begin +end; + +procedure TListItemControlScene.DoAddObject(const AObject: TFmxObject); +begin + inherited; + if AObject is TControl then + begin + TControl(AObject).SetNewScene(Self); + TControl(AObject).DisableDisappear := True; + end; + AObject.SetRoot(RealScene.GetObject.Root); +end; + +procedure TListItemControlScene.DoRemoveObject(const AObject: TFmxObject); +begin + inherited; + AObject.SetRoot(nil); + if AObject is TControl then + TControl(AObject).SetNewScene(nil); +end; + +procedure TListItemControlScene.EnableUpdating; +begin + Dec(FDisableUpdating); + if FDisableUpdating < 0 then + raise EInvalidSceneUpdatingPairCall.Create(SInvalidSceneUpdatingPairCall); +end; + +function TListItemControlScene.GetStyleBook: TStyleBook; +begin + Result := nil; +end; + +function TListItemControlScene.GetCanvas: TCanvas; +begin + Result := FCanvas +end; + +function TListItemControlScene.GetRealScene: IScene; +begin + Result := FOwnerItem.Controller.GetScene; +end; + +function TListItemControlScene.GetSceneScale: Single; +begin + Result := RealScene.GetSceneScale; +end; + +function TListItemControlScene.GetObject: TFmxObject; +begin + Result := Self; +end; + +procedure TListItemControlScene.ChangeScrollingState(const AControl: TControl; const Active: Boolean); +begin +end; + +function TListItemControlScene.LocalToScreen(const P: TPointF): TPointF; +begin + Result := RealScene.LocalToScreen(P); +end; + +function TListItemControlScene.ScreenToLocal(const P: TPointF): TPointF; +begin + Result := RealScene.ScreenToLocal(P); +end; + +function TListItemControlScene.GetUpdateRectsCount: Integer; +begin + Result := 1; +end; + +function TListItemControlScene.GetUpdateRect(const Index: Integer): TRectF; +begin + Result := TRectF.Create(0, 0, FLayoutSize.X, FLayoutSize.Y); +end; + +procedure TListItemControlScene.AddUpdateRect(const R: TRectF); +begin + if FDisableUpdating = 0 then + FOwnerItem.Invalidate; +end; + +procedure TListItemControlScene.RepaintScene(const Canvas: TCanvas); +var + Index: Integer; + {$IFDEF CPUARM}[unsafe]{$ENDIF}Control: TOpenControl; +begin + if (not FDrawing) then + begin + FDrawing := True; + FCanvas := Canvas; + try + for Index := 0 to ChildrenCount - 1 do + if (Children[Index] is TControl) and + (TControl(Children[Index]).Visible or ((not TControl(Children[Index]).Visible) and + (csDesigning in ComponentState) and (not TControl(Children[Index]).Locked))) then + begin + Control := TOpenControl(Children[Index]); + TOpenControl(Control).PaintInternal; + end; + finally + FCanvas := nil; + FDrawing := False; + end; + end; +end; + +{$ENDREGION} +{$REGION 'List Item Control Container'} + +constructor TListItemControlContainer.Create(AOwner: TComponent); +begin + inherited; +end; + +destructor TListItemControlContainer.Destroy; +begin + inherited; +end; + +{$ENDREGION} +{$REGION 'List Item Embedded Control'} + +constructor TListItemEmbeddedControl.Create(const AOwner: TListItem); +begin + inherited; + + FScene := TListItemControlScene.Create(nil); + FScene.SetOwnerItem(AOwner); + + FContainer := TListItemControlContainer.Create(nil); + FContainer.Parent := FScene; + FContainer.FItemOwner := Self; + + FScene.SetContainer(FContainer); +end; + +destructor TListItemEmbeddedControl.Destroy; +begin + FContainer.Free; + FScene.Free; + + inherited; +end; + +procedure TListItemEmbeddedControl.Render(const Canvas: TCanvas; const DrawItemIndex: Integer; + const DrawStates: TListItemDrawStates; const Resources: TListItemStyleResources; + const Params: TListItemDrawable.TParams; const SubPassNo: Integer = 0); +var + BoundsPos: TPointF; +begin + if SubPassNo <> 0 then + Exit; + + FScene.FLayoutSize := TPoint.Create(Trunc(Canvas.Width), Trunc(Canvas.Height)); + + BoundsPos := FLocalRect.TopLeft; + BoundsPos := BoundsPos + Params.ParentAbsoluteRect.TopLeft; + + FContainer.SetBounds(BoundsPos.X, BoundsPos.Y, FLocalRect.Width, FLocalRect.Height); + FScene.RepaintScene(Canvas); +end; + +function TListItemEmbeddedControl.ObjectAtPoint(const Point: TPointF): TControl; +var + Control: IControl; +begin + Control := FContainer.ObjectAtPoint(Point); + if (Control <> nil) and (Control.GetObject is TControl) and (Control.GetObject <> FContainer) then + Result := TControl(Control.GetObject) + else + Result := nil; +end; + +{$ENDREGION} +{$REGION 'List Item'} + +constructor TListItem.Create(const AAdapter: IListViewAdapter; + const AController: IListViewController); +begin + inherited Create; + FController := AController; + FAdapter := AAdapter; + FView := ListItemObjectsClass.Create(Self); + FView.Callback := procedure(View: TListItemView; Drawable: TListItemDrawable; Op: TListItemCallbackOp) begin + case Op of + TListItemCallbackOp.CreateDrawables: + CreateObjects; + TListItemCallbackOp.InvalidateOwner: + Invalidate; + TListItemCallbackOp.Click: + FController.ControlClicked(Self, Drawable); + end; + end; + FHeaderRef := -1; + FIndex := -1; + FUpdating := 0; + FNeedRepaint := False; +end; + +procedure TListItem.CreateObjects; +begin +// +end; + +destructor TListItem.Destroy; +begin + FView.Free; + inherited; +end; + +function TListItem.GetController: IListViewController; +begin + Result := FController; +end; + +function TListItem.GetCount: Integer; +begin + Result := FView.Count; +end; + +procedure TListItem.BeginUpdate; +begin + Inc(FUpdating); +end; + +procedure TListItem.EndUpdate; +begin + if FUpdating > 0 then + begin + Dec(FUpdating); + if (FUpdating <= 0) and FNeedRepaint then + begin + FNeedRepaint := False; + Invalidate; + end; + end; +end; + +procedure TListItem.Invalidate; +begin + if FUpdating < 1 then + Repaint + else + FNeedRepaint := True; +end; + +procedure TListItem.SetHeight(const Value: Integer); +var + NewValue: Integer; +begin + NewValue := Value; + if NewValue < 0 then + NewValue := 0; + + if NewValue <> FHeight then + begin + FHeight := NewValue; + InvalidateHeights; + end; +end; + +procedure TListItem.SetIndex(const Value: Integer); +begin + FIndex := Value; +end; + +function TListItem.GetIndex: Integer; +begin + Result := FIndex; +end; + +procedure TListItem.SetPurpose(const AValue: TListItemPurpose); +begin + if AValue <> FPurpose then + begin + FPurpose := AValue; + InvalidateHeights; + end; +end; + +function TListItem.ToString: string; + function GetClassName: string; + begin + if Self <> nil then + Result := ClassName + else + Result := 'TListItem(nil)'; + end; + + function GetPurpose: string; + begin + if Self <> nil then + Result := Purpose.ToString + else + Result := '(unknown)'; + end; + + function GetViewCount: Integer; + begin + if (Self <> nil) and (Self.View <> nil) then + Result := Self.View.Count + else + Result := -1; + end; +begin + Result := Format('%s@%x Purpose=%s |View|=%d', [GetClassName, NativeUInt(Self), GetPurpose, GetViewCount]); +end; + +procedure TListItem.WillBePainted; +begin +end; + +procedure TListItem.InvalidateHeights; +begin + // +end; + +function TListItem.ListItemObjectsClass: TListItemViewType; +begin + Result := TListItemView; +end; + +function TListItem.ObjectAtPoint(const Point: TPointF): TControl; +var + I: Integer; + Control: TControl; +begin + Result := nil; + for I := 0 to FView.Count - 1 do + if FView[I] <> nil then + begin + Control := FView[I].ObjectAtPoint(Point); + if Control <> nil then + Exit(Control); + end; +end; + +procedure TListItem.Repaint; +begin + FController.ItemInvalidated(Self); +end; + +function TListItem.MouseDown(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF): Boolean; +var + I: Integer; +begin + Result := False; + + for I := 0 to FView.Count - 1 do + if (FView[I] <> nil) and FView[I].Visible then + begin + Result := FView[I].MouseDown(Button, Shift, MousePos); + if Result then + Break; + end; +end; + +procedure TListItem.MouseMove(const Shift: TShiftState; const MousePos: TPointF); +var + I: Integer; +begin + for I := 0 to FView.Count - 1 do + if (FView[I] <> nil) and FView[I].Visible then + FView[I].MouseMove(Shift, MousePos); +end; + +procedure TListItem.MouseUp(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF); +var + I: Integer; +begin + for I := 0 to FView.Count - 1 do + if (FView[I] <> nil) and FView[I].Visible then + FView[I].MouseUp(Button, Shift, MousePos); +end; + +procedure TListItem.MouseSelect; +var + I: Integer; +begin + for I := 0 to FView.Count - 1 do + if (FView[I] <> nil) and FView[I].Visible then + FView[I].DoSelect; +end; + +function TListItem.HasClickOnSelectItems: Boolean; +var + I: Integer; +begin + Result := False; + + for I := 0 to FView.Count - 1 do + if (FView[I] <> nil) and FView[I].Visible and (FView[I] is TListItemGlyphButton) then + begin + Result := TListItemGlyphButton(FView[I]).ClickOnSelect; + if Result then + Break; + end; +end; + +{$ENDREGION} +{$REGION 'List Item Objects'} + +constructor TListItemView.Create(const AOwner: TListItem); +begin + inherited Create; + FViewList := TListItemView.TViewList.Create; +end; + +function TListItemView.GetCount: Integer; +begin + FCallback(Self, nil, TListItemCallbackOp.CreateDrawables); + Result := FViewList.Count; +end; + +function TListItemView.GetViewList: TViewList; +begin + Result := FViewList; +end; + +function TListItemView.GetObject(const Index: Integer): TListItemDrawable; +begin + Result := ViewList[Index]; +end; + +function TListItemView.Add(const AItem: TListItemDrawable): Integer; +begin + Result := ViewList.Add(AItem); + FCallback(Self, nil, TListItemCallbackOp.InvalidateOwner); +end; + +procedure TListItemView.Insert(const Index: Integer; const Value: TListItemDrawable); +begin + ViewList.Insert(Index, Value); +end; + +procedure TListItemView.Clear; +begin + ViewList.Clear; +end; + +procedure TListItemView.Delete(Index: Integer); +begin + ViewList.Delete(Index); + FCallback(Self, nil, TListItemCallbackOp.InvalidateOwner); +end; + +destructor TListItemView.Destroy; +begin + FCallback := nil; + FViewList.Free; + inherited; +end; + +procedure TListItemView.Include(const AItem: TListItemDrawable); +begin + if ViewList.IndexOf(AItem) = -1 then + begin + ViewList.Add(AItem); + AItem.InvalidateCallback := FCallback; + FCallback(Self, nil, TListItemCallbackOp.InvalidateOwner); + end; +end; + +procedure TListItemView.Exclude(const AItem: TListItemDrawable); +var + Index: Integer; +begin + Index := ViewList.IndexOf(AItem); + + if (Index <> -1) then + ViewList.Delete(Index); +end; + +function TListItemView.DrawableByName(const AName: string): TListItemDrawable; +begin + Result := FindDrawable(AName); + if Result = nil then + raise EListError.Create(SGenericItemNotFound); +end; + +function TListItemView.ObjectByName(const AName: string): TListItemDrawable; +begin + Exit(DrawableByName(AName)); +end; + +function TListItemView.GetInitialized: Boolean; +begin + Result := FInitialized; +end; + +procedure TListItemView.SetInitialized(const Value: Boolean); +begin + FInitialized := Value; +end; + +function TListItemView.FindDrawable(const AName: string): TListItemDrawable; +var + LObject: TListItemDrawable; + I: Integer; +begin + for I := 0 to ViewList.Count - 1 do + begin + LObject := ViewList[I]; + if LObject.Name = AName then + Exit(LObject); + end; + Result := nil; +end; + +function TListItemView.FindObject(const AName: string): TListItemDrawable; +begin + Exit(FindDrawable(AName)); +end; + +{$ENDREGION} +{$REGION 'List Item Style Objects'} + +{ TListItemStyleObjects } + +constructor TListItemStyleResources.Create; +begin + FOwnsObjects := True; + DefaultTextFont := TFont.Create; + DetailTextFont := TFont.Create; + HeaderTextFont := TFont.Create; + ButtonTextFont := TFont.Create; + DeleteButtonTextFont := TFont.Create; +end; + +constructor TListItemStyleResources.Create(const Source: TListItemStyleResources); +var + I: TAccessoryType; +begin + FOwnsObjects := False; + for I := Low(TAccessoryType) to High(TAccessoryType) do + AccessoryImages[I] := Source.AccessoryImages[I]; + HeaderTextFont := Source.HeaderTextFont; + HeaderTextColor := Source.HeaderTextColor; + HeaderTextShadowColor := Source.HeaderTextShadowColor; + DefaultTextFont := Source.DefaultTextFont; + DefaultTextColor := Source.DefaultTextColor; + DetailTextFont := Source.DetailTextFont; + DetailTextColor := Source.DetailTextColor; + DefaultTextSelectedColor := Source.DefaultTextSelectedColor; + ButtonAddItemStyleImage := Source.ButtonAddItemStyleImage; + ButtonDeleteItemStyleImage := Source.ButtonDeleteItemStyleImage; + ButtonNormalStyleImage := Source.ButtonNormalStyleImage; + ButtonDeleteStyleImage := Source.ButtonDeleteStyleImage; + ButtonCheckboxStyleImage := Source.ButtonCheckboxStyleImage; + ButtonTextFont := Source.ButtonTextFont; + ButtonTextColor := Source.ButtonTextColor; + ButtonTextPressedColor := Source.ButtonTextPressedColor; + DeleteButtonTextFont := Source.DeleteButtonTextFont; + DeleteButtonTextColor := Source.DeleteButtonTextColor; + DeleteButtonTextPressedColor := Source.DeleteButtonTextPressedColor; + ScrollingStretchGlowColor := Source.ScrollingStretchGlowColor; + PullRefreshIndicatorColor := Source.PullRefreshIndicatorColor; + PullRefreshStrokeColor := Source.PullRefreshStrokeColor; +end; + +destructor TListItemStyleResources.Destroy; +begin + if FOwnsObjects then + begin + DefaultTextFont.Free; + DetailTextFont.Free; + HeaderTextFont.Free; + ButtonTextFont.Free; + DeleteButtonTextFont.Free; + end; + inherited; +end; +{$ENDREGION} + +{ TListItemPurposeHelper } + +function TListItemPurposeHelper.ToString: string; +const + PurposeString: array [TListItemPurpose] of string = ('None', 'Header', 'Footer'); +begin + Result := PurposeString[Self]; +end; + +{ TDrawableDefaultSettings } + +class constructor TDefaultSettingsHelper.Create; +begin + FItems := TDictionary.Create; +end; + +class function TDefaultSettingsHelper.IsDefault(const ADrawable: TListItemDrawable; const AElement: TDrawableElement): Boolean; +begin + Result := AElement in FItems[ADrawable]; +end; + +class procedure TDefaultSettingsHelper.SetDefault(const ADrawable: TListItemDrawable; const AElement: TDrawableElement; + const ADefault: Boolean); +var + Elements: TDrawableElements; +begin + Elements := FItems[ADrawable]; + if ADefault then + Include(Elements, AElement) + else + Exclude(Elements, AElement); + FItems[ADrawable] := Elements; +end; + +class procedure TDefaultSettingsHelper.Register(const ADrawable: TListItemDrawable); +begin + FItems.Add(ADrawable, [Low(TDrawableElement)..High(TDrawableElement)]) +end; + +class procedure TDefaultSettingsHelper.Unregister(const ADrawable: TListItemDrawable); +begin + FItems.Remove(ADrawable); +end; + +class destructor TDefaultSettingsHelper.Destroy; +begin + FreeAndNil(FItems); +end; + +end. diff --git a/Alexandria(11)/FMX.ListView.pas b/Alexandria(11)/FMX.ListView.pas new file mode 100644 index 0000000..83f7acf --- /dev/null +++ b/Alexandria(11)/FMX.ListView.pas @@ -0,0 +1,7271 @@ +{*******************************************************} +{ } +{ Delphi FireMonkey Platform } +{ } +{ Copyright(c) 2011-2021 Embarcadero Technologies, Inc. } +{ All rights reserved } +{ } +{*******************************************************} + +unit FMX.ListView; + +interface + +{$SCOPEDENUMS ON} + +uses + System.Types, System.UITypes, System.SysUtils, System.Classes, System.Generics.Collections, System.Generics.Defaults, + System.UIConsts, System.ImageList, FMX.Types, FMX.Controls, FMX.InertialMovement, FMX.TextLayout, FMX.ListView.Types, + FMX.ListView.Adapters.Base, FMX.ListView.Appearances, FMX.Styles, FMX.Objects, FMX.StdCtrls, System.Rtti, + FMX.Graphics, FMX.Layouts, FMX.Styles.Objects, FMX.Edit, FMX.Platform, FMX.SearchBox, FMX.ActnList, FMX.ImgList, + FMX.Presentation.Messages, FMX.Controls.Presentation, FMX.ListView.DynamicAppearance; + +type + TSwipeDirection = (ToLeft, ToRight); // ZuBy + + /// List view class that provides members that subclasses may use to + /// communicate with a list view adapter that contains the actual items of the + /// list view. + TAdapterListView = class(TStyledControl) + strict private + FAdapter: IListViewAdapter; + FHeightSumsNeedUpdate: Boolean; + + procedure ItemsMayChange(Sender: TObject); + procedure ItemsCouldHaveChanged(Sender: TObject); + procedure ItemsChange(Sender: TObject); + procedure ItemsResize(Sender: TObject); + procedure ItemsInvalidate(Sender: TObject); + procedure ResetView(Sender: TObject); + protected + /// Called after the adapter had been set, SetAdapter + procedure DoAdapterSet; virtual; + /// Set IListViewAdapter + procedure SetAdapter(Adapter: IListViewAdapter); + /// Request update of item heights + procedure InvalidateHeights; + /// Handler of IListViewAdapter.OnItemsMayChange + procedure DoItemsMayChange; virtual; + /// Handler of + /// IListViewAdapter.OnItemsCouldHaveChanged + procedure DoItemsCouldHaveChanged; virtual; + /// Handler of IListViewAdapter.OnItemsResize + procedure DoItemsResize; virtual; + /// Handler of + /// IListViewAdapter.OnItemsInvalidate + procedure DoItemsInvalidate; virtual; + /// Handler of + /// IListViewAdapter.OnResetView + procedure DoResetView(const Sender: TListItem); virtual; + /// True if update of item height cache is required. Set by InvalidateHeights + property HeightSumsNeedUpdate: Boolean read FHeightSumsNeedUpdate write FHeightSumsNeedUpdate; + public + /// IListViewAdapter providing contents of this TAdapterListView + property Adapter: IListViewAdapter read FAdapter write SetAdapter; + end; + + /// The minimal ListView that's actually a real UI control. It implements all scrolling and drawing + /// functionality. It needs a valid IListViewAdapter to provide item views; + /// see TAdapterListView.Adapter + /// + /// + /// Implements: + /// ISearchResponder enables setting a filter predicate + /// IListItemStyleResources access to style resources + /// IListViewController mediator protocol between TListView and TListItem + /// IGlyph interface to TImageList + /// IMessageSendingCompatible TMessageSender object for FMX.Presentation compatibility + /// + TListViewBase = class(TAdapterListView, ISearchResponder, IListItemStyleResources, IListViewController, IGlyph, + IMessageSendingCompatible, IControlTypeSupportable) + private const + ChangeRepaintedIncidentDelay = 0.1; // seconds + PhysicsProcessingInterval = 8; // 8 ms for ~120 frames per second + RecurrentTimerInterval = 16; // 16 ms for ~60 frames per second + AutoTapScrollingSpeed = 8; // pixels per frame + AutoTapMaxScrollingTime = 1; // seconds + TapSelectWaitTime = 0.25; // seconds + SelectionFadeInTime = 0.125; // seconds + SelectionFadeOutTime = 0.25; // seconds + MinScrollThreshold = 10; + MinSwypeThreshold = 40; + + DefaultDeleteButtonWidth = 72; + + ItemSeparatorTop = 1; + ItemSeparatorBottom = 2; + + EditModeSelectionAlpha = 0.25; // how bright the checked items are in editmode + + EditModeAnimationDuration = 0.1; // in seconds + DeleteModeAnimationDuration = 0.15; // in seconds + DefaultDeleteButtonText = 'Delete'; + + PullRefreshIndicatorStrengthStart = 16; + PullRefreshIndicatorMaxSteps = 12; + + DefaultLeftMargin = 10; + DefaultRightMargin = 11; + + public type + /// Edit mode change event + /// event source (TListViewBase) + /// set to True if event was handled + THandleChangeEvent = procedure(const Sender: TObject; var AHandled: Boolean) of object; + /// Generic TListItem event; used for OnListItemClick, OnItemChange + /// event source (TListViewBase) + /// item being acted upon + TListItemEvent = procedure(const Sender: TObject; const AItem: TListItem) of object; + /// OnItemClickEx event + /// event source + /// index of item + /// click position in item local coordinates + /// TListItemDrawable that received the click + TListItemClickEventEx = procedure(const Sender: TObject; ItemIndex: Integer; const LocalClickPos: TPointF; + const ItemObject: TListItemDrawable) of object; + /// OnUpdateItemView event + TUpdateItemViewEvent = TListItemEvent; + /// OnUpdatingItemView event + TUpdatingItemViewEvent = procedure(const Sender: TObject; const AItem: TListItem; var AHandled: Boolean) of object; + /// OnDeletingItem event + TDeletingItemEvent = procedure(Sender: TObject; AIndex: Integer; var ACanDelete: Boolean) of object; + /// OnDeleteItem event + TDeleteItemEvent = procedure(Sender: TObject; AIndex: Integer) of object; + /// OnDeleteChangeVisible event + TDeleteChangeVisibilityEvent = procedure(Sender: TObject; AValue: Boolean) of object; + + TColumnClick = procedure(const Sender: TObject; const Column: Integer; + const X, Y: Single; const AItem: TListViewItem; const DrawebleName: string) of object; // ZuBy + TScrollEnd = procedure(Sender: TObject) of object; // ZuBy + TSwipeDirectionEvent = procedure(Sender: TObject; const Direction: TSwipeDirection) of object; // ZuBy + + private type + TItemHeightSums = TList; + + TDelayedIncident = (ChangeRepainted, Invalidate, SetItemIndex, ClickEvent); + + TDelayedIncidentEntry = record + Incident: TDelayedIncident; + Triggered: Boolean; + StartTime: Double; + TimeToWait: Double; + CustomData: NativeInt; + end; + + TDelayedIncidents = TList; + + TTransitionType = (None, EditMode, DeleteMode); + + TInternalDragMode = (None, Drag, Swype); + + TItemSelectionAlpha = record + StartTime: Double; + Alpha: Single; + StartAlpha: Single; + + class function Create(const StartTime: Double; const Alpha, StartAlpha: Single): TItemSelectionAlpha; static; inline; + end; + + TItemSelectionAlphas = TDictionary; + TPullRefreshAnimation = (NotPlaying, Playing, Finished); + TStateFlag = (NeedsRebuild, NeedsScrollingLimitsUpdate, Invalid, Painting, ResettingObjects, ScrollingActive, + ScrollingMouseTouch, NeedsScrollBarDisplay); + TStateFlags = set of TStateFlag; + + TEstimatedHeights = record + Item: Single; + Header: Single; + Footer: Single; + end; + + private + FTimerService: IFMXTimerService; + FSystemInformationService: IFMXSystemInformationService; + FListingService: IFMXListingService; + FStateFlags: TStateFlags; + FRecurrentTimerHandle: TFmxHandle; + FDelayedIncidents: TDelayedIncidents; + FSelectionAlphas: TItemSelectionAlphas; + FItemIndex: Integer; + FAniCalc: TAniCalculations; + FScrollViewPos: Single; + FBrush: TBrush; + FStroke: TStrokeBrush; + FMouseDownAt: TPointF; + FMouseClickPrev: TPointF; + FMouseClickDelta: TPointF; + FMouseClicked: Boolean; + FMouseClickIndex: Integer; + FMouseEventIndex: Integer; + FItemSpaces: TBounds; + FMousePrevScrollPos: Single; + FClickEventItemIndex: Integer; + FClickEventMousePos: TPointF; + [Weak] FClickEventControl: TListItemDrawable; + FHeightSums: TItemHeightSums; + FMaxKnownHeight: Integer; + FSideSpace: Integer; + FScrollScale: Single; + FBackgroundStyleColor: TAlphaColor; + FSelectionStyleColor: TAlphaColor; + FItemStyleFillColor: TAlphaColor; + FItemStyleFillAltColor: TAlphaColor; + FItemStyleFrameColor: TAlphaColor; + [Weak] FSelectionStyleImage: TStyleObject; + [Weak] FHeaderStyleImage: TStyleObject; + FTouchAnimationObject: ITouchAnimationObject; + FScrollBar: TScrollBar; + FTransparent: Boolean; + FAllowSelection: Boolean; + FAlternatingColors: Boolean; + FTapSelectItemIndex: Integer; + FTapSelectNewIndexApplied: Integer; + FTapSelectStartTime: Double; + FShowSelection: Boolean; + FOnChange: TNotifyEvent; + FOnChangeRepainted: TNotifyEvent; + FOnItemsChange: TNotifyEvent; + FOnScrollViewChange: TNotifyEvent; + FOnSearchChange: TNotifyEvent; + FOnFilter: TFilterEvent; + FAutoTapScroll: Boolean; + FAutoTapTreshold: Integer; + FAutoTapDistance: Integer; + FOnListItemClick: TListItemEvent; + FOnItemClickEx: TListItemClickEventEx; + FOnItemChange: TListItemEvent; + FOnEditModeChanging: THandleChangeEvent; + FOnEditModeChange: TNotifyEvent; + FOnUpdateItemView: TUpdateItemViewEvent; + FOnUpdatingItemView: TUpdatingItemViewEvent; + FOnDeleteChange: TDeleteChangeVisibilityEvent; + FOnDeletingItem: TDeletingItemEvent; + FOnDeleteItem: TDeleteItemEvent; + FOnPullRefresh: TNotifyEvent; + FDeleteButtonText: string; + FEditMode: Boolean; + FCanSwipeDelete: Boolean; + FDeleteButtonIndex: Integer; + FPrevDeleteButtonIndex: Integer; + FStyleResources: TListItemStyleResources; + FUpdatingStyleResources: Boolean; + FDisableMouseWheel: Boolean; + FTransitionStartTime: Double; + FTransitionType: TTransitionType; + FEditModeTransitionAlpha: Single; + FDeleteModeTransitionAlpha: Single; + FDeleteLayout: TLayout; + FDeleteButton: TSpeedButton; + FDragListMode: TInternalDragMode; + FSearchEdit: TSearchBox; + FSearchVisible: Boolean; + FSearchAlwaysOnTop: Boolean; + FSelectionCrossfade: Boolean; + FPullToRefresh: Boolean; + FPullRefreshWait: Boolean; + FPullRefreshTriggered: Boolean; + FPullRefreshAnimation: TPullRefreshAnimation; + FPullRefreshAnimationStartTime: Double; + FPullRefreshAnimationStopTime: Double; + FScrollStretchStrength: Single; + FControlType: TControlType; + FNativeOptions: TListViewNativeOptions; + FImageLink: TGlyphImageLink; + FMessageSender: TMessageSender; + FItemSelectedBeforeChange: TListItem; + FEstimatedHeights: TEstimatedHeights; + + FMouseClickSwipeEventSend: Boolean; // ZuBy + FHeaderStyleColor: TAlphaColor; + FOnColumnClick: TColumnClick; // ZuBy + FOnScrollEnd: TScrollEnd; // ZuBy + FOnSwipe: TSwipeDirectionEvent; // ZuBy + FTransparentSeparator: Boolean; // ZuBy + FTransparentItems: Boolean; // ZuBy + FTransparentHeaders: Boolean; // ZuBy + FAutoPositionToItem: Boolean; // ZuBy + FTopItemIndex: Integer; // ZuBy + FCanSwipeDirection: Boolean; // ZuBy + FSeparatorLeftOffset: Single; // ZuBy + FSeparatorRightOffset: Single; // ZuBy + FHorizontal: Boolean; // ZuBy + FItemBoxLight: TStyleObject; // ZuBy + FMakeSelectedItemVisible: Boolean; // ZuBy + FShowScrollBar: Boolean; // ZuBy + FColumnWidth: Single; // ZuBy + FTopOffset: Integer; // ZuBy + FBottomOffset: Integer; // ZuBy + FItemBottomOffset: Integer; // ZuBy + FColumns: Integer; // ZuBy + FMarg: Integer; // ZuBy + FAutoColumns: Boolean; // ZuBy + FCanScroll: Boolean; // ZuBy + FShowFirstSeparator: Boolean; // sinuke + FShowLastSeparator: Boolean; // sinuke + + function IsRunningOnDesktop: Boolean; + function HasTouchTracking: Boolean; + function HasSearchFeatures: Boolean; + function HasSearchAsItem: Boolean; + function IsDeleteModeAllowed: Boolean; + function HasStretchyScrolling: Boolean; + function HasPhysicsStretchyScrolling: Boolean; + function HasScrollingStretchGlow: Boolean; + function HasPullRefreshStroke: Boolean; + + function CanDisplaySelectionForItem(const Index: Integer; const Item: TListItem = nil; + const IncludeMultiSelect: Boolean = False; const IncludeCrossFaded: Boolean = False): Boolean; + function GetDefaultSelectionAlpha: Single; + function GetItemSelectionAlpha(const Index: Integer): Single; + procedure DestroyRecurrentTimer; + procedure UpdateRecurrentTimer; + function HasRecurrentTimerEvents: Boolean; + procedure RecurrentTimerEvent; + procedure StartIncident(const Incident: TDelayedIncident; const Triggered: Boolean = True; + const TimeToWait: Single = 0; const CustomData: NativeInt = 0); + procedure ProcessIncident(const Entry: TDelayedIncidentEntry); + procedure TriggerIncidents(const Incident: TDelayedIncident; const ResetStartupTime: Boolean = True); + procedure ProcessDelayedIncidents; + procedure ProcessTransitionAnimation; + procedure ProcessTapSelectItem; + procedure ProcessSelectionAlphas; + procedure InsertItemCrossFade(const Index: Integer; const ShowAnimation: Boolean); + procedure RemoveItemCrossFade(const Index: Integer); + procedure StartPullRefreshAnimation; + procedure ProcessPullRefreshAnimation; + function GetPullRefreshStrength: Single; + function GetPullRefreshIndicatorSteps: Integer; + function GetPullRefreshIndicatorAlpha: Single; + function GetPullRefreshStrokeWidth: Single; + procedure PaintPullRefreshIndicator(const ACanvas: TCanvas; const AStrength, AOpacity: Single); + procedure PaintPullRefreshStroke(const ACanvas: TCanvas; const AStrength, AOpacity: Single); + procedure PaintScrollingStretchGlow(const ACanvas: TCanvas; const AIntensity, AOpacity: Single); + procedure UpdatePullRefreshState; + procedure UpdateScrollStretchStrength(const NewValue: Single); + procedure DeleteButtonClicked(Sender: TObject); + procedure ScrollBarChange(Sender: TObject); + procedure ItemSpacesChange(Sender: TObject); + procedure AniCalcChange(Sender: TObject); + procedure AniCalcStart(Sender: TObject); + procedure AniCalcStop(Sender: TObject); + function GetItemIndex: Integer; + procedure SetItemIndex(const Value: Integer); + procedure SetItemIndexInternal(const Value: Integer; const DisableSelection: Boolean = False; + const DisableCrossfade: Boolean = False); + function GetMaxScrollViewPos: Integer; + procedure UpdateScrollViewPos(const Value: Single); + procedure UpdateSearchEditPos; + procedure SetScrollViewPos(const Value: Single); + procedure UpdateScrollingLimits; + procedure UpdateScrollBar; + procedure GetNumberOfRenderingPasses(const StartItem, EndItem: Integer; var Passes, Subpasses: Integer); + function GetItemHeight(const Index: Integer): Integer; overload; virtual; + function GetItemRelRect(const Index: Integer; const LocRect: TRectF; const SideSpace: Integer = 0): TRectF; inline; + function GetItemGroupSeparators(const Index: Integer): Integer; inline; + function FindLocalItemObjectAtPosition(const ItemIndex: Integer; const Position: TPointF): TListItemDrawable; + + function GetSeparatorLineHeight: Single; + function AlignValueToPixel(const Value: Single): Single; + procedure DrawItemsFill(const StartItem, EndItem: Integer; const LocRect: TRectF; const Opacity: Single; + const HeaderIndex: Integer = -1); + procedure DrawIndexFill(const AIndex: Integer; const LocRect: TRectF; const Opacity: Single); + procedure DrawTouchAnimation(const Index: Integer; const LocRect: TRectF; const Opacity: Single); + + function GetHeaderRelRect(const StartItem, HeaderIndex: Integer; const LocRect: TRectF; + const SideSpace: Integer = 0): TRectF; + procedure DrawHeaderItem(const LocRect: TRectF; const StartItem, HeaderIndex: Integer; const Opacity: Single); + + procedure DrawListItems(const AbsOpacity: Single); + + procedure UpdateItemLookups; + function FindItemAbsoluteAt(const ViewAt: Integer): Integer; + function FindItemAbsoluteAtWithCheck(const ViewAt: Integer): Integer; + procedure SetSideSpace(const Value: Integer); + procedure SetTransparent(const Value: Boolean); + procedure SetAlternatingColors(const Value: Boolean); + procedure SetShowSelection(const Value: Boolean); + procedure RecreateNativePresentation; virtual; + + procedure SetEditMode(const Value: Boolean); + procedure SetCanSwipeDelete(const Value: Boolean); + + procedure SelectItem(const ItemIndex: Integer); virtual; + procedure UnselectItem(const ItemIndex: Integer); virtual; + function GetSelected: TListItem; + procedure SetSelected(const Value: TListItem); + procedure SetNewItemIndex(const NewIndex: Integer); + + procedure SetSearchVisible(const Value: Boolean); + procedure SetSearchAlwaysOnTop(const Value: Boolean); + procedure SetOnFilter(const Value: TFilterEvent); + procedure OnSearchEditResize(Sender: TObject); + procedure OnSearchEditChange(Sender: TObject); + function DeleteButtonTextStored: Boolean; + // ISearchResponder + procedure SetFilterPredicate(const Predicate: TPredicate); + // IMessageSendingCompatible + function GetMessageSender: TMessageSender; + // Custom readers + procedure ReadCanSwipeDelete(Reader: TReader); + procedure ReadIsSearchVisible(Reader: TReader); + procedure ReadIsSearchAlwaysOnTop(Reader: TReader); + procedure ReadEditModeOptions(Reader: TReader); + + function GetItemCount: Integer; + + { IListViewController } + procedure RequestReindexing(const Item: TListItem); + procedure ItemResized(const Item: TListItem); + procedure ItemInvalidated(const Item: TListItem); + procedure ControlClicked(const Item: TListItem; const Control: TListItemDrawable); + procedure CheckStateChanged(const Item: TListItem; const Control: TListItemDrawable); + function GetScene: IScene; + + procedure RecalcTopViewItemIndex; // ZuBy + procedure SetSeparatorLeftOffset(const Value: Single); // ZuBy + procedure SetSeparatorRightOffset(const Value: Single); // ZuBy + procedure SetHorizontal(const Value: Boolean); // ZuBy + procedure SetShowScrollBar(const Value: Boolean); // ZuBy + procedure SetAutoColumns(const Value: Boolean); // ZuBy + procedure SetBottomOffset(const Value: Integer); // ZuBy + procedure SetColumnWidth(const Value: Single); // ZuBy + procedure SetTopOffset(const Value: Integer); + procedure SetItemBottomOffset(const Value: Integer); + procedure SetTransparentSeparator(const Value: Boolean); + procedure SetTransparentItems(const Value: Boolean); // ZuBy + function getTextSize(const aText: string; aFont: TFont; const aWordWrap: Boolean; aWidth, aHeight: Single): TSizeF; // ZuBy + procedure SetTransparentHeader(const Value: Boolean); // ZuBy + procedure SetCanScroll(const Value: Boolean); + procedure SetShowFirstSeparator(const Value: Boolean); + procedure SetShowLastSeparator(const Value: Boolean); + protected + procedure DefineProperties(Filer: TFiler); override; + /// True if in Edit Mode + function IsEditMode: Boolean; virtual; + /// Used internally by presentation hook + procedure DoSetItemIndexInternal(const Value: Integer); virtual; + /// Used internally by presentation hook + procedure DoUpdateScrollViewPos(const Value: Single); virtual; + /// Used internally by presentation hook + procedure DoSetScrollViewPos(const Value: Single); virtual; + /// Invoked when Edit Mode is being changed; if Edit Mode requires a different appearance, this + /// is where update of appearances should be initiated + procedure WillEnterEditMode(const Animated: Boolean); virtual; + /// Used internally by presentation hook + function HasButtonsInCells: Boolean; virtual; + /// Used internally by presentation hook + function HasDeletionEditMode: Boolean; virtual; + /// Used internally by presentation hook + function HasCheckboxMode: Boolean; virtual; + + /// Stop edit mode transition animation + procedure ResetEditModeAnimation; + /// Initialize edit mode transition animation + procedure InitEditModeAnimation; + /// Stop delete mode transition animation + procedure ResetDeleteModeAnimation; + /// Initialize delete mode transition animation + procedure InitDeleteModeAnimation; + /// Update layout to place a Delete button + procedure UpdateDeleteButtonLayout; + /// Perform item deletion + procedure ProceedDeleteItem; + + /// Invokes Pull-to-Refresh when applicable + procedure ScrollStretchChanged; virtual; + /// Scroll stretch threshold value when Pull-to-Refresh is invoked + property ScrollStretchStrength: Single read FScrollStretchStrength; + + procedure SetSelectionCrossfade(const Value: Boolean); + function GetDeleteButtonText: string; + procedure SetDeleteButtonText(const Value: string); + procedure SetPullToRefresh(const Value: Boolean); + { IControlTypeSupportable } + procedure SetControlType(const Value: TControlType); + function GetControlType: TControlType; + procedure SetNativeOptions(const Value: TListViewNativeOptions); + + { IListViewController } + function GetEditModeTransitionAlpha: Single; + function GetDeleteModeTransitionAlpha: Single; + procedure SetDeleteButtonIndex(const NewItemIndex: Integer); + function GetItemEditOffset(const Item: TListItem): Single; + function GetItemDeleteCutoff(const Item: TListItem): Single; + function GetClientMargins: TRectF; + function GetItemCurrentSelectionAlpha(const Item: TListItem): Single; + function IListViewController.GetItemSelectionAlpha = GetItemCurrentSelectionAlpha; + function GetImages: TCustomImageList; + procedure SetImages(const Value: TCustomImageList); + + /// Hook for IListViewController.RequestReindexing + procedure DoRequestReindexing(const Item: TListItem); virtual; + /// Hook for IListViewController.ItemResized + procedure DoItemResized(const Item: TListItem); virtual; + /// Hook for IListViewController.ItemInvalidated + procedure DoItemInvalidated(const Item: TListItem); virtual; + /// Hook for IListViewController.CheckStateChanged + procedure DoCheckStateChanged(const Item: TListItem; const Control: TListItemDrawable); virtual; + /// Hook for IListViewController.ControlClicked + procedure DoControlClicked(const Item: TListItem; const Control: TListItemDrawable); virtual; + + { IGlyph } + function GetImageIndex: TImageIndex; + procedure SetImageIndex(const Value: TImageIndex); + function GetImageList: TBaseImageList; inline; + procedure SetImageList(const Value: TBaseImageList); + function IGlyph.GetImages = GetImageList; + procedure IGlyph.SetImages = SetImageList; + { IListItemStyleResources } + function GetStyleResources: TListItemStyleResources; + function StyleResourcesNeedUpdate: Boolean; + + procedure SetItemSpaces(const Value: TBounds); + function GetItemClientRect(const Index: Integer): TRectF; // part of IListViewPresentationParent + function GetEstimatedItemHeight: Single; // part of IListViewPresentationParent + function GetEstimatedHeaderHeight: Single; // part of IListViewPresentationParent + function GetEstimatedFooterHeight: Single; // part of IListViewPresentationParent + + /// Called when an instance or reference to instance of TBaseImageList or the + /// ImageIndex property is changed. + /// See also FMX.ActnList.IGlyph + procedure ImagesChanged; virtual; + procedure Paint; override; + procedure AfterPaint; override; + procedure Loaded; override; + procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override; + procedure MouseMove(Shift: TShiftState; X, Y: Single); override; + procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override; + procedure MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean); override; + procedure KeyDown(var Key: Word; var KeyChar: System.WideChar; Shift: TShiftState); override; + function ObjectAtPoint(P: TPointF): IControl; override; + procedure DoMouseLeave; override; + procedure Resize; override; + function GetDefaultStyleLookupName: string; override; + procedure ApplyStyle; override; + procedure FreeStyle; override; + procedure Invalidate; + procedure DoRealign; override; + procedure DoExit; override; + /// General state change. Starts TDelayedIncident.ChangeRepainted incident and + /// invokes OnChange + procedure DoChange; virtual; + /// Handle TDelayedIncident.ChangeRepainted incident + procedure DoChangeRepainted; virtual; + /// Invoke OnItemChange handler + procedure DoListItemChange(const AListItem: TListItem); virtual; + /// Invoke OnListItemClick handler + procedure DoListItemClick(const AListItem: TListItem); virtual; + /// Invoke OnEditModeChange handler + procedure DoEditModeChange; virtual; + /// Invoke OnEditModeChanging handler + procedure DoEditModeChanging(var AHandled: Boolean); virtual; + /// Reset edit mode animation + procedure DoResetEditModeAnimation; virtual; + /// Update scrolling limits and animation boundaries + procedure DoUpdateScrollingLimits; virtual; + + // Notifications from IListViewAdapter + procedure DoItemsMayChange; override; + procedure DoItemsCouldHaveChanged; override; + procedure DoItemsInvalidate; override; + /// This virtual method is called immediately after list of items has been changed. + procedure DoItemsChange; override; + procedure DoAdapterSet; override; + /// Deletes an item + /// index of item to be deleted + /// True if deleted succesfully + function DeleteItem(const ItemIndex: Integer): Boolean; + /// Perform actual item deletion. Called from DeleteItem: Boolean + procedure DoDeleteItem(const ItemIndex: Integer); virtual; + /// Get area available to item layout + function GetFinalItemSpaces(const ForceIncludeScrollBar: Boolean = True): TRectF; virtual; + /// Get item size + function GetFinalItemSize(const ForceIncludeScrollBar: Boolean = True): TSizeF; virtual; + function CanObserve(const ID: Integer): Boolean; override; + /// Notify observers about selection change + procedure ObserversBeforeSelection(out LAllowSelection: Boolean); + /// Return True if this item should handle input events + function ShouldHandleEvents: Boolean; virtual; + /// Invoke OnUpdatingItemView handler + procedure DoUpdatingItemView(const AListItem: TListItem; var AHandled: Boolean); virtual; + // Invoke OnUpdateItemView handler + procedure DoUpdateItemView(const AListItem: TListItem); virtual; + /// Get glyph button for item Index + function GetGlyphButton(const Index: Integer): TListItemGlyphButton; + /// Invoked before item view will be updated (before calling ResetObjects) + property OnUpdatingItemView: TUpdatingItemViewEvent read FOnUpdatingItemView write FOnUpdatingItemView; + /// Invoked after item view has been updated (after calling ResetObjects) + property OnUpdateItemView: TUpdateItemViewEvent read FOnUpdateItemView write FOnUpdateItemView; + /// Invoked after EditMode has been changed + property OnEditModeChange: TNotifyEvent read FOnEditModeChange write FOnEditModeChange; + /// Invoked before changing EditMode + property OnEditModeChanging: THandleChangeEvent read FOnEditModeChanging write FOnEditModeChanging; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + class function GetDefaultMargins: TRectF; + procedure EndUpdate; override; + /// When using native presentation, re-creates the list and updates visible item content + procedure RebuildList; virtual; + /// Scrolls the view (instantly) to the desired item placing it within the view + procedure ScrollTo(const AItemIndex: Integer); + /// Index of selected item + property ItemIndex: Integer read GetItemIndex write SetItemIndex default -1; + /// Selected item + property Selected: TListItem read GetSelected write SetSelected; + /// Scroll position in dimension units + property ScrollViewPos: Single read FScrollViewPos write SetScrollViewPos; + /// Get rectangle of item, relative control + function GetItemRect(const AItemIndex: Integer): TRectF; + /// This method should be called when "pull to refresh" mode has been triggered to stop spinning wheel + /// + /// This has only effect in native iOS control and only when PullRefreshWait property is set to True + /// + procedure StopPullRefresh; virtual; + /// Space in logical units around the content of each list item + property ItemSpaces: TBounds read FItemSpaces write SetItemSpaces; + /// The list of images. Can be nil. See also FMX.ActnList.IGlyph + property Images: TCustomImageList read GetImages write SetImages; + /// Space in logical units on all sides around the list box encompassing the items + property SideSpace: Integer read FSideSpace write SetSideSpace default 0; + /// If the control is transparent, it will not draw its background + property Transparent: Boolean read FTransparent write SetTransparent; + /// Determines whether the items are selectable or not. If items are not selectable, user will still be + /// able to click on embedded controls + property AllowSelection: Boolean read FAllowSelection write FAllowSelection default True; + // Enabling this will switch fill colors for odd and even elements + property AlternatingColors: Boolean read FAlternatingColors write SetAlternatingColors default False; + /// Determines whether the selection is visible when selecting items. + /// It may be disabled when using list of checkboxes + property ShowSelection: Boolean read FShowSelection write SetShowSelection default True; + /// Enables swipe delete + property CanSwipeDelete: Boolean read FCanSwipeDelete write SetCanSwipeDelete default True; + /// Enable automatic scrolling to the top when tapped at the top edge + property AutoTapScroll: Boolean read FAutoTapScroll write FAutoTapScroll default False; + /// Threshold distance from the top edge at which the tap would initiate autoscroll to top + property AutoTapTreshold: Integer read FAutoTapTreshold write FAutoTapTreshold default 8; + /// Disables mouse wheel + property DisableMouseWheel: Boolean read FDisableMouseWheel write FDisableMouseWheel default False; + /// Item count + property ItemCount: Integer read GetItemCount; + /// Item click handler + property OnListItemClick: TListItemEvent read FOnListItemClick write FOnListItemClick; + /// Extended item click handler which gets click coordinates and clicked drawable + property OnItemClickEx: TListItemClickEventEx read FOnItemClickEx write FOnItemClickEx; + /// Reserved + property OnItemChange: TListItemEvent read FOnItemChange write FOnItemChange; + /// General OnChange event handler + property OnChange: TNotifyEvent read FOnChange write FOnChange; + /// TDelayedIncident.ChangeRepainted delayed incident handler + property OnChangeRepainted: TNotifyEvent read FOnChangeRepainted write FOnChangeRepainted; + /// This event occurs after list of items has been changed. + property OnItemsChange: TNotifyEvent read FOnItemsChange write FOnItemsChange; + /// Called when ScrollViewPos has changed (manually or in code) + property OnScrollViewChange: TNotifyEvent read FOnScrollViewChange write FOnScrollViewChange; + /// Query before item deletion, see TDeletingItemEvent. + /// Application can veto deletion by returning False in handler Result + /// Deletion is performed by DoDeleteItem + property OnDeletingItem: TDeletingItemEvent read FOnDeletingItem write FOnDeletingItem; + /// Invoked after item has been deleted + property OnDeleteItem: TDeleteItemEvent read FOnDeleteItem write FOnDeleteItem; + /// Invoked when Delete button changes visibility + property OnDeleteChangeVisible: TDeleteChangeVisibilityEvent read FOnDeleteChange write FOnDeleteChange; + /// + property OnSearchChange: TNotifyEvent read FOnSearchChange write FOnSearchChange; + /// Event handler for setting custom filter on text of TListView. + property OnFilter: TFilterEvent read FOnFilter write SetOnFilter; + /// Invoked when pull refresh is triggered + property OnPullRefresh: TNotifyEvent read FOnPullRefresh write FOnPullRefresh; + /// Text to display in Delete button + property DeleteButtonText: string read GetDeleteButtonText write SetDeleteButtonText stored + DeleteButtonTextStored nodefault; + /// Enable/disable Edit Mode + property EditMode: Boolean read FEditMode write SetEditMode default False; + /// True if search bar is visible + property SearchVisible: Boolean read FSearchVisible write SetSearchVisible default False; + /// Always display search bar + property SearchAlwaysOnTop: Boolean read FSearchAlwaysOnTop write SetSearchAlwaysOnTop default True; + /// Enable selection crossfade animation + property SelectionCrossfade: Boolean read FSelectionCrossfade write SetSelectionCrossfade default False; + /// Enable pull to refresh + property PullToRefresh: Boolean read FPullToRefresh write SetPullToRefresh default False; + /// When set to True, the spinning wheel does not disappear automatically and StopPullRefresh method needs + /// to be called after refresh operation is done. If this is set to False (default), then spinning wheel disappears + /// automatically shortly after triggering the effect. This option works only in native iOS control and has no + /// effect otherwise. + property PullRefreshWait: Boolean read FPullRefreshWait write FPullRefreshWait default False; + /// Control type: Styled or Native + property ControlType: TControlType read FControlType write SetControlType default TControlType.Styled; + /// Options for Native control; see ControlType + property NativeOptions: TListViewNativeOptions read FNativeOptions write SetNativeOptions default []; + + procedure RebuildOrientation; // ZuBy + procedure EnableTouchAnimation(Value: Boolean); // ZuBy + // Scroll Width + function GetScrollWidth: Single; // ZuBy + function FindItemByPosition(X, Y: Single): Integer; // ZuBy + // Get Item Height + function getHeightByIndex(Index: Integer): Integer; // ZuBy + function getItemTextHeight(const AItem: TListItemText; const aWidth: Single = 0): Integer; // ZuBy + function getItemTextButtonHeight(const AItem: TListItemTextButton; const aWidth: Single = 0): Integer; // ZuBy + function getItemTextWidth(const AItem: TListItemText; const aHeight: Single = 0): Integer; // ZuBy + function getItemTextButtonWidth(const AItem: TListItemTextButton; const aHeight: Single = 0): Integer; // ZuBy + // Ani Calculaction + function getAniCalc: TAniCalculations; // ZuBy + procedure SearchBoxClear; // ZuBy + property ShowFirstSeparator: Boolean read FShowFirstSeparator write SetShowFirstSeparator default True; + property ShowLastSeparator: Boolean read FShowLastSeparator write SetShowLastSeparator default True; + property TransparentSeparators: Boolean read FTransparentSeparator write SetTransparentSeparator default False; // ZuBy + property TransparentItems: Boolean read FTransparentItems write SetTransparentItems default False; // ZuBy + property TransparentHeaders: Boolean read FTransparentHeaders write SetTransparentHeader default False; // ZuBy + property AutoPositionToItem: Boolean read FAutoPositionToItem write FAutoPositionToItem default False; // ZuBy + // Separator Draw + property SeparatorLeftOffset: Single read FSeparatorLeftOffset write SetSeparatorLeftOffset; // ZuBy + property SeparatorRightOffset: Single read FSeparatorRightOffset write SetSeparatorRightOffset; // ZuBy + property ItemBottomOffset: Integer read FItemBottomOffset write SetItemBottomOffset; // ZuBy + property CanSwipeDirection: Boolean read FCanSwipeDirection write FCanSwipeDirection default False; // ZuBy + property Horizontal: Boolean read FHorizontal write SetHorizontal default False; // ZuBy + property MakeSelectedItemVisible: Boolean read FMakeSelectedItemVisible write FMakeSelectedItemVisible default True; + property OffsetTop: Integer read FTopOffset write SetTopOffset; // ZuBy + property OffsetBottom: Integer read FBottomOffset write SetBottomOffset; + // ZuBy + property AutoColumns: Boolean read FAutoColumns write SetAutoColumns; + // ZuBy + property CanScroll: Boolean read FCanScroll write SetCanScroll default True; + // sinuke + property Columns: Integer read FColumns; // ZuBy + property ColumnOffset: Integer read FMarg; // ZuBy + property ColumnWidth: Single read FColumnWidth write SetColumnWidth; // ZuBy + property ShowScrollBar: Boolean read FShowScrollBar write SetShowScrollBar default True; // ZuBy + property OnColumnClick: TColumnClick read FOnColumnClick write FOnColumnClick; // ZuBy + property OnScrollEnd: TScrollEnd read FOnScrollEnd write FOnScrollEnd; + // ZuBy + property OnSwipeDirection: TSwipeDirectionEvent read FOnSwipe write FOnSwipe; // ZuBy + end; + + /// TListView that supports native presentation + TPresentedListView = class(TListViewBase, IListViewPresentationParent, IListViewDesignPresentationParent) + strict private + FPresentation: IListViewPresentation; + FPresentationLocked: Integer; + FCreatingNativeView: Boolean; + protected + /// Lock presentation and execute P + procedure ExecuteInterlocked(const P: TProc); + /// True if item can be selected + function CanSelectItem(const AItemIndex: Integer): Boolean; + /// True if item can be unselected + function CanUnselectItem(const AItemIndex: Integer): Boolean; + /// Called after item has been selected + procedure DidSelectItem(const AItemIndex: Integer); + /// Called after item has been unselected + procedure DidUnselectItem(const AItemIndex: Integer); + procedure ChangeOrder; override; + procedure ParentChanged; override; + procedure PaintChildren; override; + procedure AncestorVisibleChanged(const Visible: Boolean); override; + procedure DoSetItemIndexInternal(const Value: Integer); override; + procedure DoEditModeChange; override; + procedure DoItemsChange; override; + procedure DoItemsInvalidate; override; + procedure DoItemInvalidated(const Item: TListItem); override; + procedure DoCheckStateChanged(const AItem: TListItem; const Control: TListItemDrawable); override; + procedure DoUpdateScrollViewPos(const Value: Single); override; + procedure DoSetScrollViewPos(const Value: Single); override; + procedure DoDeleteItem(const ItemIndex: Integer); override; + procedure DoResetEditModeAnimation; override; + procedure DoUpdateScrollingLimits; override; + procedure DoAbsoluteChanged; override; + // Presentation + /// Parent control has been loaded + procedure PMAncesstorPresentationLoaded(var AMessage: TDispatchMessageWithValue); message PM_ANCESTOR_PRESENTATION_LOADED; + procedure RecreateNativePresentation; override; + function ShouldHandleEvents: Boolean; override; + // IPresentationParent + function GetRootObject: TObject; + function GetContentFrame: TRect; + function GetControlOpacity: Single; + // IListViewPresentationParent + function GetAdapter: IListViewAdapter; + function GetItemText(const ItemIndex: Integer): string; + function GetItemIndexTitle(const ItemIndex: Integer): string; + procedure ItemButtonClicked(const ItemIndex: Integer); + procedure InvokePullRefresh; + procedure SetSearchFilter(const Filter: string); + function GetTableViewFlags: TListViewModeFlags; + function GetTableViewOptions: TListViewNativeOptions; + function IListViewPresentationParent.GetFlags = GetTableViewFlags; + function IListViewPresentationParent.GetOptions = GetTableViewOptions; + procedure SetCreatingNativeView(const Value: Boolean); + function GetIsTransparent: Boolean; + function GetOpacity: Single; + function GetBackgroundStyleColor: TAlphaColor; + procedure DoItemsResize; override; + // IListViewDesignPresentationParent + function HasDesignPresentationAttached: Boolean; + public + destructor Destroy; override; + procedure BeforeDestruction; override; + procedure RecalcEnabled; override; + procedure Show; override; + procedure Hide; override; + procedure Resize; override; + procedure Paint; override; + procedure RebuildList; override; + procedure StopPullRefresh; override; + procedure RecalcOpacity; override; + end; + + /// TAppearanceListView supports Appearances. Appearances are templates used for dynamic creation + /// of item views. Normally all items of the same purpose in the List View share the same appearance, differing + /// only in data + TAppearanceListView = class(TPresentedListView, IAppearanceItemOwner, IPublishedAppearanceOwner) + public type + /// Generic event invoked on TListViewItem + TItemEvent = procedure(const Sender: TObject; const AItem: TListViewItem) of object; + /// See DoUpdateItemView + TUpdateObjectsEvent = TItemEvent; + /// See DoUpdatingItemView + TUpdatingObjectsEvent = procedure(const Sender: TObject; const AItem: TListViewItem; var AHandled: Boolean) of object; + + strict private + FAppearanceViewItems: TAppearanceListViewItems; + FAppearanceProperties: TPublishedAppearance; + FItemAppearanceObjects: TPublishedObjects; + FItemAppearanceProperties: TItemAppearanceProperties; + FItemEditAppearanceProperties: TItemAppearanceProperties; + FHeaderAppearanceProperties: TItemAppearanceProperties; + FFooterAppearanceProperties: TItemAppearanceProperties; + FUpdatingAppearance: Integer; + FChangedAppearanceObjects: TListItemPurposes; + FChangedAppearanceHeights: TListItemPurposes; + // See also FItemSelectedBeforeChange + FItemSelectedBeforeEdit: TListItem; + FOnButtonClick: TItemControlEvent; + FOnButtonChange: TItemControlEvent; + FAppearanceAllowsCheckboxes: Boolean; + FAppearanceAllowsDeleteMode: Boolean; + FOnItemClick: TItemEvent; + FOnUpdatingObjects: TUpdatingObjectsEvent; + FOnUpdateObjects: TUpdateObjectsEvent; + + function GetFooterAppearanceName: string; + function GetFooterAppearanceClassName: string; + function GetHeaderAppearanceName: string; + function GetHeaderAppearanceClassName: string; + function GetItemAppearanceName: string; + function GetItemEditAppearanceName: string; + function GetItemObjectsClassName: string; + function GetItemEditObjectsClassName: string; + procedure SetFooterAppearanceClassName(const Value: string); + procedure SetHeaderAppearanceClassName(const Value: string); + procedure SetItemObjectsClassName(const Value: string); + procedure SetItemEditObjectsClassName(const Value: string); + procedure SetFooterAppearanceName(const Value: string); + procedure SetHeaderAppearanceName(const Value: string); + procedure SetItemAppearanceName(const Value: string); + procedure SetItemEditAppearanceName(const Value: string); + + procedure SetAppearanceProperties(const Value: TPublishedAppearance); + procedure SetItemAppearanceObjects(const Value: TPublishedObjects); + function GetItemAppearanceObjects: TPublishedObjects; + procedure AppearanceResetObjects(APurposes: TListItemPurposes); + procedure AppearanceResetHeights(APurposes: TListItemPurposes); + + { IPublishedAppearanceOwner } + + function GetFooterAppearanceProperties: TItemAppearanceProperties; + function GetHeaderAppearanceProperties: TItemAppearanceProperties; + function GetItemAppearanceProperties: TItemAppearanceProperties; + function GetItemEditAppearanceProperties: TItemAppearanceProperties; + + procedure EditorBeforeItemAdded(Sender: IListViewEditor); + procedure EditorAfterItemAdded(Sender: IListViewEditor; const Item: TListItem); + procedure EditorBeforeItemDeleted(Sender: IListViewEditor; const Index: Integer); + procedure EditorAfterItemDeleted(Sender: IListViewEditor); + procedure ResetViewAppearance(const AItem: TListViewItem); + + protected + procedure ApplyStyle; override; + /// Handler of + /// TAppearanceListViewItems.OnNotify + procedure ObjectsNotify(Sender: TObject; const Item: TListItem; Action: TCollectionNotification); + /// TAppearanceListView needs adapter to be TAppearanceListViewItems or derivative. + /// If TAppearanceListView is used with a custom adapter, use Items property to set it + /// instead of Adapter property of the base class + procedure SetAppearanceListViewItems(const AItems: TAppearanceListViewItems); + procedure DoResetView(const Item: TListItem); override; + + function HasButtonsInCells: Boolean; override; + function HasDeletionEditMode: Boolean; override; + function HasCheckboxMode: Boolean; override; + procedure SetItemHeight(const Value: Integer); virtual; + procedure SetItemEditHeight(const Value: Integer); virtual; + procedure SetHeaderHeight(const Value: Integer); virtual; + procedure SetFooterHeight(const Value: Integer); virtual; + + function GetAppearanceListViewItem(const Index: Integer): TListViewItem; virtual; + /// Get height of a specific item + function GetItemHeight(const Index: Integer): Integer; overload; override; + // See respective properties + function GetItemHeight: Integer; overload; virtual; + function GetItemEditHeight: Integer; overload; virtual; + function GetHeaderHeight: Integer; overload; virtual; + function GetFooterHeight: Integer; overload; virtual; + + procedure WillEnterEditMode(const Animated: Boolean); override; + procedure DoResetEditModeAnimation; override; + + procedure DoAdapterSet; override; + // hooks from IListViewController + procedure DoRequestReindexing(const Item: TListItem); override; + procedure DoItemResized(const Item: TListItem); override; + procedure DoCheckStateChanged(const AItem: TListItem; const Control: TListItemDrawable); override; + procedure DoControlClicked(const Item: TListItem; const Control: TListItemDrawable); override; + /// Returns an array of 4 elements comprised by + /// ItemEditAppearanceProperties + /// ItemAppearanceProperties + /// HeaderAppearanceProperties + /// FooterAppearanceProperties + function GetAppearanceProperties: TArray; + /// Refresh items with specified purposes; all items if the set is empty; + /// see also IListViewAdapter.ResetViews + procedure RefreshAppearances(const APurposes: TListItemPurposes = []); + /// Same as RefreshAppearances + procedure UpdateAppearanceStyleResources; + /// Invoked when item appearance changes; resets all item views + procedure ItemAppearanceChange(const Sender: TItemAppearanceProperties); + /// Invoked when Appearance Objects (view prototype) change + procedure ItemAppearanceChangeObjects(const Sender: TItemAppearanceProperties); + /// Invoked when appearance height is changed + procedure ItemAppearanceChangeHeight(const Sender: TItemAppearanceProperties); + /// Resets all item views when entering edit mode + procedure EditModeAppearances; + /// Reset appearance. When TAppearanceListView is created, it creates appearances + /// for Item, Edit mode Item, Header, Footer and initializes them by calling this virtual method. + /// By contract it must select TItemAppearanceProperties.AppearanceClass by searching in + /// appearances registry for item purpose specified during TItemAppearanceProperties creation + /// instance of TItemAppearanceProperties + /// See implementation in + /// TListView.InitializeItemAppearance + /// See also TAppearancesRegistry + /// + procedure InitializeItemAppearance(const AAppearance: TItemAppearanceProperties); virtual; + + procedure DoListItemClick(const AItem: TListItem); override; + procedure DoUpdatingItemView(const AListItem: TListItem; var AHandled: Boolean); override; + procedure DoUpdateItemView(const AListItem: TListItem); override; + + // General compatibility properties + /// Item height defined by appearance + property ItemHeight: Integer read GetItemHeight write SetItemHeight; + /// Item height in edit mode defined by appearance + property ItemEditHeight: Integer read GetItemEditHeight write SetItemEditHeight; + /// Header height defined by appearance + property HeaderHeight: Integer read GetHeaderHeight write SetHeaderHeight; + /// Footer height defined by appearance + property FooterHeight: Integer read GetFooterHeight write SetFooterHeight; + + // Appearance related properties + /// Item in edit mode appearance class name + /// Must be loaded prior to other Item, header and footer properties + /// Changing class name will load new appearance and reinitialize all views + /// + property ItemEditAppearanceClassName: string read GetItemEditObjectsClassName write SetItemEditObjectsClassName; + /// Item appearance class name + /// Must be loaded prior to other Item, header and footer properties + /// Changing class name will load new appearance and reinitialize all views + /// + property ItemAppearanceClassName: string read GetItemObjectsClassName write SetItemObjectsClassName; + /// Header appearance class name + /// Must be loaded prior to other Item, header and footer properties + /// Changing class name will load new appearance and reinitialize all views + /// + property HeaderAppearanceClassName: string read GetHeaderAppearanceClassName write SetHeaderAppearanceClassName; + /// Footer appearance class name + /// Must be loaded prior to other Item, header and footer properties + /// Changing class name will load new appearance and reinitialize all views + /// + property FooterAppearanceClassName: string read GetFooterAppearanceClassName write SetFooterAppearanceClassName; + + /// Assign new appearance by name; this will effectively change ItemAppearanceClassName and reload + /// all views + property ItemAppearanceName: string read GetItemAppearanceName write SetItemAppearanceName stored False; + /// Assign new appearance by name; this will effectively change ItemEditAppearanceClassName and reload + /// all views + property ItemEditAppearanceName: string read GetItemEditAppearanceName write SetItemEditAppearanceName stored False; + /// Assign new appearance by name; this will effectively change HeaderAppearanceClassName and reload + /// all views + property HeaderAppearanceName: string read GetHeaderAppearanceName write SetHeaderAppearanceName stored False; + /// Assign new appearance by name; this will effectively change FooterAppearanceClassName and reload + /// all views + property FooterAppearanceName: string read GetFooterAppearanceName write SetFooterAppearanceName stored False; + // TPublishedAppearance represents appearances in the object inspector + property ItemAppearance: TPublishedAppearance read FAppearanceProperties write SetAppearanceProperties; + /// TPublishedObjects represents appearance items (collections of objects comprising appearances) + /// in the object inspector + property ItemAppearanceObjects: TPublishedObjects read GetItemAppearanceObjects write SetItemAppearanceObjects; + /// Invoked on check button state change + property OnButtonChange: TItemControlEvent read FOnButtonChange write FOnButtonChange; + /// Invoked on embedded button click + property OnButtonClick: TItemControlEvent read FOnButtonClick write FOnButtonClick; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + procedure BeginUpdate; override; + procedure EndUpdate; override; + procedure Resize; override; + /// Access to the extended adapter that supports appearances and works with extended + /// items; see TListViewItem + property Items: TAppearanceListViewItems read FAppearanceViewItems write SetAppearanceListViewItems; + /// Item click event handler + property OnItemClick: TItemEvent read FOnItemClick write FOnItemClick; + /// Invoked when view is being created by TAppearanceListView.ResetViewAppearance before + /// calling ResetObjects. If not Handled, the view will be recreated + /// Call sequence: + /// TAppearanceListView.ResetViewAppearance: + /// OnUpdatingObjects -> + /// TItemAppearanceObjects.ResetObjects -> OnUpdateObjects + property OnUpdatingObjects: TUpdatingObjectsEvent read FOnUpdatingObjects write FOnUpdatingObjects; + /// Invoked when view is being created by TAppearanceListView.ResetViewAppearance after + /// calling ResetObjects + /// Call sequence: + /// TAppearanceListView.ResetViewAppearance: + /// OnUpdatingObjects -> + /// TItemAppearanceObjects.ResetObjects -> OnUpdateObjects + property OnUpdateObjects: TUpdateObjectsEvent read FOnUpdateObjects write FOnUpdateObjects; + + // True Items Clear + procedure ItemsClearTrue; // ZuBy + // Scroll + function getFirstVisibleItemIndex: Integer; // ZuBy + function getLastVisibleItemindex: Integer; // ZuBy + function getVisibleCount: Integer; // ZuBy + // Custom Item Draw + procedure SetCustomColorForItem(const ItemIndex: Integer; const aColor: TAlphaColor); // ZuBy + procedure SetDefaultColorForItem(const ItemIndex: Integer); // ZuBy + function IsCustomColorUsed(const ItemIndex: Integer): Boolean; // ZuBy + // Item Draw + procedure SetColorItemSelected(aColor: TAlphaColor); // ZuBy + procedure SetColorBackground(aColor: TAlphaColor); // ZuBy + procedure SetColorItemFill(aColor: TAlphaColor); // ZuBy + procedure SetColorItemFillAlt(aColor: TAlphaColor); // ZuBy + procedure SetColorItemSeparator(aColor: TAlphaColor); // ZuBy + procedure SetColorHeader(aColor: TAlphaColor); // ZuBy + // Text Draw + procedure SetColorText(aColor: TAlphaColor); // ZuBy + procedure SetColorTextSelected(aColor: TAlphaColor); // ZuBy + procedure SetColorTextDetail(aColor: TAlphaColor); // ZuBy + procedure SetColorTextHeader(aColor: TAlphaColor); // ZuBy + procedure SetColorTextHeaderShadow(aColor: TAlphaColor); // ZuBy + procedure SetColorButtonText(aColor: TAlphaColor); // ZuBy + procedure SetColorButtonTextPressed(aColor: TAlphaColor); // ZuBy + procedure SetColorDeleteText(aColor: TAlphaColor); // ZuBy + procedure SetColorDeleteTintColor(aColor: TAlphaColor); // ZuBy + procedure SetColorDeleteTextPressed(aColor: TAlphaColor); // ZuBy + // PullRefresh Draw + procedure SetColorPullRefresh(aColor: TAlphaColor); // ZuBy + procedure SetColorPullRefreshIndicator(aColor: TAlphaColor); // ZuBy + procedure SetColorStretchGlow(aColor: TAlphaColor); // ZuBy + end; + + TCustomListView = class(TAppearanceListView) + end; + + TListView = class(TCustomListView) + protected + procedure InitializeItemAppearance(const AAppearance: TItemAppearanceProperties); override; + public + // Hoist protected appearance properties + property ItemAppearanceName; + property ItemEditAppearanceName; + property HeaderAppearanceName; + property FooterAppearanceName; + published + // Hoist protected appearance properties + property ItemAppearanceClassName; + property ItemEditAppearanceClassName; + property HeaderAppearanceClassName; + property FooterAppearanceClassName; + + property OnUpdatingObjects; + property OnUpdateObjects; + property OnEditModeChange; + property OnEditModeChanging; + property EditMode; + + property Transparent default False; + property AllowSelection; + property AlternatingColors; + property ItemIndex; + property Images; + property ScrollViewPos; + property ItemSpaces; + property SideSpace; + + property Align; + property Anchors; + property CanFocus default True; + property CanParentFocus; + property ClipChildren default True; + property ClipParent default False; + property Cursor default crDefault; + property DisableFocusEffect default True; + property DragMode default TDragMode.dmManual; + property EnableDragHighlight default True; + property Enabled default True; + property Locked default False; + property Height; + property Hint; + property HitTest default True; + property Margins; + property Opacity; + property Padding; + property PopupMenu; + property Position; + property RotationAngle; + property RotationCenter; + property Scale; + property Size; + property TabOrder; + property TabStop; + property Visible default True; + property Width; + property ParentShowHint; + property ShowHint; + + property ShowFirstSeparator; // zubu + property ShowLastSeparator; // zubu + property TransparentSeparators; // zubu + property TransparentItems; // zubu + property AutoPositionToItem; // zubu + property SeparatorLeftOffset; // zubu + property SeparatorRightOffset; // zubu + property ItemBottomOffset; // zubu + property Horizontal; // zubu + + {events} + property OnApplyStyleLookup; + {Drag and Drop events} + property OnDragEnter; + property OnDragLeave; + property OnDragOver; + property OnDragDrop; + property OnDragEnd; + {Keyboard events} + property OnKeyDown; + property OnKeyUp; + {Mouse events} + property OnCanFocus; + + property OnEnter; + property OnExit; + property OnMouseDown; + property OnMouseMove; + property OnMouseUp; + property OnMouseWheel; + property OnMouseEnter; + property OnMouseLeave; + + property OnPainting; + property OnPaint; + property OnResize; + property OnResized; + + property ItemAppearance; + property ItemAppearanceObjects; + + property HelpContext; + property HelpKeyword; + property HelpType; + + property StyleLookup; + property TouchTargetExpansion; + property OnClick; + property OnDblClick; + + { ListView selection events } + property CanSwipeDelete; + + property OnChange; + property OnChangeRepainted; + property OnItemsChange; + property OnScrollViewChange; + property OnItemClick; + property OnItemClickEx; + property OnButtonClick; + property OnButtonChange; + + property OnDeletingItem; + property OnDeleteItem; + property OnDeleteChangeVisible; + property OnSearchChange; + property OnFilter; + property OnPullRefresh; + property DeleteButtonText; + + property AutoTapScroll; + property AutoTapTreshold; + property ShowSelection; + property DisableMouseWheel; + + property SearchVisible; + property SearchAlwaysOnTop; + property SelectionCrossfade; + property PullToRefresh; + property PullRefreshWait; + + property ControlType; + property NativeOptions; + end; +{$ENDREGION} + + EListViewError = class(Exception); + +implementation + +uses + {$IFDEF MACOS}Macapi.CoreFoundation,{$ENDIF} System.SyncObjs, System.Math, System.RTLConsts, System.TypInfo, + System.Math.Vectors, FMX.Consts, FMX.Ani, FMX.Utils, FMX.BehaviorManager, FMX.Forms + {$IFDEF IOS}, FMX.ListView.iOS{$ENDIF} {$IFDEF ANDROID}, FMX.ListView.Android{$ENDIF}; + +var + LVTextLayout: TTextLayout; + +{$REGION 'Types, constants and helper functions'} + +const +{$IFDEF IOS} + DefaultScrollBarWidth = 7; +{$ELSE} +{$IFDEF MACOS} + DefaultScrollBarWidth = 7; +{$ENDIF} +{$ENDIF} +{$IFDEF MSWINDOWS} + DefaultScrollBarWidth = 16; +{$ENDIF} +{$IFDEF ANDROID} + DefaultScrollBarWidth = 7; +{$ENDIF} +{$IFDEF LINUX} + DefaultScrollBarWidth = 16; +{$ENDIF} + +type + TOpenBitmap = class(TBitmap); + TOpenReader = class(TReader); + TEditModeOption = (DisallowSelection, HideSelection, RadioButtonMode, UncheckMode, ModalMode, ClearWhenStart, + MultiSelect); + TEditModeOptions = set of TEditModeOption; + +function RectF(X, Y, Width, Height: Single): TRectF; inline; +begin + Result.Left := X; + Result.Top := Y; + Result.Right := X + Width; + Result.Bottom := Y + Height; +end; + +procedure DisableHitTestForControl(const AControl: TControl); +var + LChild: TFmxObject; +begin + AControl.HitTest := False; + if AControl.Children <> nil then + for LChild in AControl.Children do + if LChild is TControl then + DisableHitTestForControl(TControl(LChild)); +end; + +{$ENDREGION} +{$REGION 'TListViewBase'} + +class function TListViewBase.TItemSelectionAlpha.Create(const StartTime: Double; + const Alpha, StartAlpha: Single): TItemSelectionAlpha; +begin + Result.StartTime := StartTime; + Result.Alpha := Alpha; + Result.StartAlpha := StartAlpha; +end; + +class function TListViewBase.GetDefaultMargins: TRectF; +begin + Result := TRectF.Create(DefaultLeftMargin, 0, DefaultRightMargin, 0); +end; + +constructor TListViewBase.Create(AOwner: TComponent); +begin + inherited; + + if not TPlatformServices.Current.SupportsPlatformService(IFMXTimerService, FTimerService) then + raise EUnsupportedPlatformService.Create('IFMXTimerService'); + + FImageLink := TGlyphImageLink.Create(Self); + FImageLink.IgnoreIndex := True; + TPlatformServices.Current.SupportsPlatformService(IFMXSystemInformationService, FSystemInformationService); + TPlatformServices.Current.SupportsPlatformService(IFMXListingService, FListingService); + + FDragListMode := TInternalDragMode.None; + FDeleteButtonIndex := -1; + FPrevDeleteButtonIndex := -1; + FSearchVisible := False; + FSearchAlwaysOnTop := True; + FCanSwipeDelete := False; + FPullToRefresh := False; + + FDelayedIncidents := TDelayedIncidents.Create; + FSelectionAlphas := TItemSelectionAlphas.Create; + + CanFocus := True; + DisableFocusEffect := True; + AutoCapture := True; + ClipChildren := True; + + FItemBottomOffset := 0; // ZuBy + FHorizontal := False; // ZuBy + FTopOffset := 0; // ZuBy + FBottomOffset := 0; // ZuBy + FAutoColumns := False; // ZuBy + FColumnWidth := 180; // ZuBy + FCanScroll := True; // sinuke + FColumns := 1; // ZuBy + FMarg := 0; // ZuBy + + FScrollBar := TScrollBar.Create(nil); + FScrollBar.Stored := False; + FScrollBar.Orientation := TOrientation.Vertical; + FScrollBar.Align := TAlignLayout.Right; + FScrollBar.Width := DefaultScrollBarWidth; + + FScrollBar.Parent := Self; + + if (not HasTouchTracking) or (csDesigning in ComponentState) then + begin + FScrollBar.Visible := False; + FScrollBar.OnChange := ScrollBarChange; + end; + + if HasTouchTracking then + begin + FAniCalc := TAniCalculations.Create(nil); + FAniCalc.Animation := True; + FAniCalc.OnChanged := AniCalcChange; + FAniCalc.Interval := PhysicsProcessingInterval; + FAniCalc.OnStart := AniCalcStart; + FAniCalc.OnStop := AniCalcStop; + FAniCalc.BoundsAnimation := HasPhysicsStretchyScrolling; + end; + + FItemSpaces := TBounds.Create(GetDefaultMargins); + FItemSpaces.OnChange := ItemSpacesChange; + + FBrush := TBrush.Create(TBrushKind.Solid, $FF000000); + FStroke := TStrokeBrush.Create(TBrushKind.Solid, $FF000000); + + FStyleResources := TListItemStyleResources.Create; + + FHeightSums := TItemHeightSums.Create; + + FItemIndex := -1; + FMouseClickIndex := -1; + FMouseEventIndex := -1; + FTransparent := False; + FAllowSelection := True; + FShowSelection := True; + FAutoTapTreshold := 8; + FTapSelectItemIndex := -1; + FTapSelectNewIndexApplied := -1; + FSelectionCrossfade := False; + FDeleteButtonText := DefaultDeleteButtonText; + + FMaxKnownHeight := 0; + FScrollScale := 1; + + FTransitionType := TTransitionType.None; +end; + +destructor TListViewBase.Destroy; +begin + FMessageSender.Free; + + DestroyRecurrentTimer; + + if FSearchEdit <> nil then + begin + FSearchEdit.Parent := nil; + FreeAndNil(FSearchEdit); + end; + + FHeightSums.Free; + FStroke.Free; + FBrush.Free; + FItemSpaces.Free; + FreeAndNil(FAniCalc); + + FSelectionAlphas.Free; + FDelayedIncidents.Free; + + FStyleResources.Free; + FTimerService := nil; + FListingService := nil; + FImageLink.DisposeOf; + inherited; +end; + +function TListViewBase.IsRunningOnDesktop: Boolean; +begin + Result := TOSVersion.Platform in [pfWindows, pfMacOS, pfLinux]; +end; + +function TListViewBase.HasTouchTracking: Boolean; +begin + // *** ZuBy +{$IFNDEF MSWINDOWS} + Result := True; +{$ELSE} + Result := (FAniCalc <> nil) or ((FSystemInformationService <> nil) and + (TScrollingBehaviour.TouchTracking in FSystemInformationService.GetScrollingBehaviour)); +{$ENDIF} + // *** ZuBy +end; + +function TListViewBase.HasSearchFeatures: Boolean; +begin + Result := ((FListingService <> nil) and (FListingService.GetSearchFeatures <> [])) or + (csDesigning in ComponentState); +end; + +function TListViewBase.HasSearchAsItem: Boolean; +begin + Result := (FSearchVisible and (csDesigning in ComponentState)) or + (FSearchVisible and (FListingService <> nil) and ((not FSearchAlwaysOnTop) or + (not (TListingSearchFeature.StayOnTop in FListingService.GetSearchFeatures))) and + (TListingSearchFeature.AsFirstItem in FListingService.GetSearchFeatures) and ((FSearchEdit = nil) or + (FSearchEdit.Text.Length < 1))); +end; + +function TListViewBase.IsDeleteModeAllowed: Boolean; +begin + Result := TListingEditModeFeature.Delete in FListingService.GetEditModeFeatures; +end; + +function TListViewBase.IsEditMode: Boolean; +begin + Result := FEditMode; +end; + +function TListViewBase.HasStretchyScrolling: Boolean; +begin +{$IFNDEF MSWINDOWS} + Result := True; +{$ELSE} + Result := HasTouchTracking and (FSystemInformationService <> nil) and + (TScrollingBehaviour.BoundsAnimation in FSystemInformationService.GetScrollingBehaviour); +{$ENDIF} +end; + +function TListViewBase.HasButtonsInCells: Boolean; +begin + Result := False; +end; + +function TListViewBase.HasCheckboxMode: Boolean; +begin + Result := False; +end; + +function TListViewBase.HasDeletionEditMode: Boolean; +begin + Result := False; +end; + +function TListViewBase.HasPhysicsStretchyScrolling: Boolean; +begin + Result := HasTouchTracking and (FPullToRefresh or HasStretchyScrolling); +end; + +function TListViewBase.HasScrollingStretchGlow: Boolean; +begin + Result := (FListingService <> nil) and (TListingTransitionFeature.ScrollGlow in FListingService.GetTransitionFeatures); +end; + +function TListViewBase.HasPullRefreshStroke: Boolean; +begin + Result := (FListingService <> nil) and + not (TListingTransitionFeature.PullToRefresh in FListingService.GetTransitionFeatures) and + ((FPullRefreshAnimation = TPullRefreshAnimation.Playing) or (GetPullRefreshStrength > 0)); +end; + +function TListViewBase.CanDisplaySelectionForItem(const Index: Integer; const Item: TListItem; const IncludeMultiSelect, + IncludeCrossFaded: Boolean): Boolean; +var + ItemAlpha: TItemSelectionAlpha; + Checkable: IListViewCheckProvider; + LItem: TListItem; +begin + LItem := Item; + if LItem = nil then + LItem := Adapter[Index]; + Result := ((FItemIndex = Index) and FShowSelection and LItem.View.Initialized and + (LItem.Purpose = TListItemPurpose.None) and (not FEditMode) and (FDeleteButtonIndex = -1)) or + (HasCheckboxMode and IncludeMultiSelect and LItem.View.Initialized and + Supports(Adapter, IListViewCheckProvider, Checkable) and Checkable.Checked[Index]); + + if (not Result) and IncludeCrossFaded and (FSelectionAlphas <> nil) then + if FSelectionAlphas.TryGetValue(Index, ItemAlpha) then + Result := ItemAlpha.Alpha > TEpsilon.Vector; +end; + +function TListViewBase.GetDefaultSelectionAlpha: Single; +begin + if FEditMode then + Result := EditModeSelectionAlpha + else + Result := 1; +end; + +procedure TListViewBase.OnSearchEditResize(Sender: TObject); +begin + InvalidateHeights; + StartIncident(TDelayedIncident.Invalidate); +end; + +procedure TListViewBase.OnSearchEditChange(Sender: TObject); +begin + if Assigned(FOnSearchChange) then + FOnSearchChange(Self); +end; + +function TListViewBase.GetItemCount: Integer; +begin + if Adapter <> nil then + Result := Adapter.Count + else + Result := -1; +end; + +procedure TListViewBase.DoItemsMayChange; +begin + inherited; + FItemSelectedBeforeChange := Selected; +end; + +procedure TListViewBase.DoItemsCouldHaveChanged; +var + SelectionChanged: Boolean; +begin + inherited; + SelectionChanged := (Selected <> nil) and (FItemSelectedBeforeChange <> nil) and (Selected <> FItemSelectedBeforeChange); + + if (FItemSelectedBeforeChange <> nil) and + (FItemSelectedBeforeChange.Index >= 0) and (FItemSelectedBeforeChange.Index < Adapter.Count) + and (Adapter[FItemSelectedBeforeChange.Index] = FItemSelectedBeforeChange) then + ItemIndex := FItemSelectedBeforeChange.Index + else + ItemIndex := -1; + + if not InRange(FTapSelectItemIndex, -1, ItemCount - 1) then + FTapSelectItemIndex := -1; + + FItemSelectedBeforeChange := nil; + + if SelectionChanged then + begin + TLinkObservers.ListSelectionChanged(Observers); + DoChange; + end; + + InvalidateHeights; + Invalidate; + RebuildList; +end; + +procedure TListViewBase.DoItemsInvalidate; +begin + inherited; + Invalidate; +end; + +procedure TListViewBase.ItemSpacesChange(Sender: TObject); +begin + Invalidate; +end; + +procedure TListViewBase.SetSideSpace(const Value: Integer); +var + NewValue: Integer; +begin + NewValue := Max(Value, 0); + + if FSideSpace <> NewValue then + begin + FSideSpace := NewValue; + Invalidate; + end; +end; + +procedure TListViewBase.SetTransparent(const Value: Boolean); +begin + if FTransparent <> Value then +begin + FTransparent := Value; + Invalidate; + end; +end; + +function TListViewBase.ShouldHandleEvents: Boolean; +begin + Result := True; +end; + +procedure TListViewBase.SelectItem(const ItemIndex: Integer); +var + HasChange: Boolean; + NewItemIndex: Integer; + Checkable: IListViewCheckProvider; +begin + NewItemIndex := ItemIndex; + + if (NewItemIndex >= 0) and (NewItemIndex < Adapter.Count) then + begin + if FEditMode and Supports(Adapter, IListViewCheckProvider, Checkable) then + Checkable[NewItemIndex] := True; + end + else + NewItemIndex := -1; + + HasChange := FItemIndex <> NewItemIndex; + + SetItemIndex(NewItemIndex); + + if NewItemIndex <> -1 then + DoListItemClick(Adapter[NewItemIndex]); + + if HasChange then + DoChange; + + Invalidate; + + FClickEventControl := nil; + FClickEventItemIndex := NewItemIndex; + FClickEventMousePos := TPointF.Zero; + if not FMouseClickSwipeEventSend then // ZuBy + StartIncident(TDelayedIncident.ClickEvent); +end; + +procedure TListViewBase.UnselectItem(const ItemIndex: Integer); +var + Checkable: IListViewCheckProvider; +begin + if (ItemIndex >= 0) and (ItemIndex < Adapter.Count) and + Supports(Adapter, IListViewCheckProvider, Checkable) then + Checkable[ItemIndex] := False; +end; + +procedure TListViewBase.SetAlternatingColors(const Value: Boolean); +begin + if FAlternatingColors <> Value then + begin + FAlternatingColors := Value; + Invalidate; + end; +end; + +procedure TListViewBase.SetItemIndexInternal(const Value: Integer; const DisableSelection, DisableCrossfade: Boolean); +var + NewValue: Integer; +begin + NewValue := Value; + + if (NewValue < 0) or (NewValue > Adapter.Count - 1) then + NewValue := -1; + + if NewValue <> FItemIndex then + begin + if (not FEditMode) and InRange(FItemIndex, 0, Adapter.Count - 1) and not DisableCrossfade + then + InsertItemCrossFade(FItemIndex, False); + + FItemIndex := NewValue; + + // ZuBy *** + if FMakeSelectedItemVisible then + begin + if FItemIndex <> -1 then + ScrollTo(FItemIndex); // Make selected item visible. + end; + // *** ZuBy + + if (not DisableSelection) and (FItemIndex >= 0) and (FItemIndex < Adapter.Count) then + Adapter[FItemIndex].MouseSelect; + + if (not FEditMode) and (FItemIndex <> -1) and not DisableCrossfade then + InsertItemCrossFade(FItemIndex, True); + + DoSetItemIndexInternal(FItemIndex); + Invalidate; + end; +end; + +function TListViewBase.GetItemIndex: Integer; +begin + Result := FItemIndex; +end; + +procedure TListViewBase.SetItemIndex(const Value: Integer); +begin + // The data may not be ready at the moment of component loading, cannot properly set item index at this time. + if (not (csLoading in ComponentState)) or (Value = -1) then + SetItemIndexInternal(Value) + else + StartIncident(TDelayedIncident.SetItemIndex, True, 0, Value); +end; + +procedure TListViewBase.SetEditMode(const Value: Boolean); +var + LHandled: Boolean; + Checkable: IListViewCheckProvider; +begin + if (FEditMode <> Value) and Supports(Adapter, IListViewCheckProvider, Checkable) then + begin + BeginUpdate; + try + Checkable.CheckAll(False); + + if FDeleteButtonIndex <> -1 then + begin + SetDeleteButtonIndex(-1); + ResetDeleteModeAnimation; + end; + + SetItemIndex(-1); + + LHandled := False; + + DoEditModeChanging(LHandled); + + if not LHandled then + begin + FEditMode := Value; + + if FSearchEdit <> nil then + FSearchEdit.Enabled := not FEditMode; + + if (FListingService <> nil) and (TListingTransitionFeature.EditMode in FListingService.GetTransitionFeatures) then + begin // Animated Edit Mode + if EditMode then + WillEnterEditMode(True); //EditModeAppearances; + InitEditModeAnimation; + end + else + begin // Instant Edit Mode + if FEditMode then + FEditModeTransitionAlpha := 1 + else + FEditModeTransitionAlpha := 0; + WillEnterEditMode(False); + Invalidate; + end; + end; + + DoEditModeChange; + finally + EndUpdate; + end; + end; +end; + +procedure TListViewBase.WillEnterEditMode(const Animated: Boolean); +begin +end; + +procedure TListViewBase.SetCanSwipeDelete(const Value: Boolean); +begin + if FCanSwipeDelete <> Value then + FCanSwipeDelete := Value; +end; + +procedure TListViewBase.SetSearchVisible(const Value: Boolean); +begin + if FSearchVisible <> Value then + begin + FSearchVisible := Value; + + if FSearchVisible and HasSearchFeatures and (FSearchEdit = nil) then + begin + FSearchEdit := TSearchBox.Create(Self); + FSearchEdit.Stored := False; + FSearchEdit.Locked := True; + FSearchEdit.Parent := Self; + FSearchEdit.OnResize := OnSearchEditResize; + FSearchEdit.OnChange := OnSearchEditChange; + FSearchEdit.OnFilter := OnFilter; + end; + + if FSearchEdit <> nil then + begin + FSearchEdit.Visible := FSearchVisible; + UpdateSearchEditPos; + end; + + InvalidateHeights; + Invalidate; + RecreateNativePresentation; + end; +end; + +procedure TListViewBase.SetSearchAlwaysOnTop(const Value: Boolean); +begin + if FSearchAlwaysOnTop <> Value then + begin + FSearchAlwaysOnTop := Value; + InvalidateHeights; + Invalidate; + RecreateNativePresentation; + end; +end; + +procedure TListViewBase.SetControlType(const Value: TControlType); +begin + if FControlType <> Value then + begin + FControlType := Value; + if not (csLoading in ComponentState) then + begin + RecreateNativePresentation; + Invalidate; + end; + end; +end; + +function TListViewBase.GetControlType: TControlType; +begin + Result := FControlType; +end; + +procedure TListViewBase.SetNativeOptions(const Value: TListViewNativeOptions); +begin + if FNativeOptions <> Value then + begin + FNativeOptions := Value; + RecreateNativePresentation; + end; +end; + +procedure TListViewBase.SetOnFilter(const Value: TFilterEvent); +begin + FOnFilter := Value; + if FSearchEdit <> nil then + FSearchEdit.OnFilter := OnFilter; +end; + +function TListViewBase.GetEditModeTransitionAlpha: Single; +begin + Result:= FEditModeTransitionAlpha; +end; + +function TListViewBase.GetDeleteModeTransitionAlpha: Single; +begin + Result:= FDeleteModeTransitionAlpha; +end; + +function TListViewBase.GetItemEditOffset(const Item: TListItem): Single; +var + Provider: IListViewGlyphButtonProvider; + Drawable: TListItemGlyphButton; +begin + Result := 0; + if (Item <> nil) and Supports(Adapter, IListViewGlyphButtonProvider, Provider) then + begin + Drawable := Provider.GlyphButtonDrawable[Item.Index]; + if Drawable <> nil then + Result := Drawable.Width; + end; +end; + +function TListViewBase.GetItemDeleteCutoff(const Item: TListItem): Single; +begin + if (Item <> nil) and ((Item.Index = FDeleteButtonIndex) or (Item.Index = FPrevDeleteButtonIndex)) and + (FDeleteLayout <> nil) then + Result := FDeleteLayout.Position.X + else + Result := 0; +end; + +function TListViewBase.GetClientMargins: TRectF; +begin + Result := LocalRect; +end; + +function TListViewBase.GetItemCurrentSelectionAlpha(const Item: TListItem): Single; +begin + if Item <> nil then + Result := Min(GetItemSelectionAlpha(Item.Index) / GetDefaultSelectionAlpha, 1) + else + Result := 0; +end; + +procedure TListViewBase.CheckStateChanged(const Item: TListItem; const Control: TListItemDrawable); +begin + DoCheckStateChanged(Item, Control); +end; + +procedure TListViewBase.ControlClicked(const Item: TListItem; const Control: TListItemDrawable); +begin + DoControlClicked(Item, Control); +end; + +procedure TListViewBase.RequestReindexing(const Item: TListItem); +begin + DoRequestReindexing(Item); +end; + +procedure TListViewBase.ItemInvalidated(const Item: TListItem); +begin + Invalidate; + DoItemInvalidated(Item); +end; + +procedure TListViewBase.ItemResized(const Item: TListItem); +begin + DoItemResized(Item); +end; + +procedure TListViewBase.DoEditModeChanging(var AHandled: Boolean); +begin + if Assigned(FOnEditModeChanging) then + FOnEditModeChanging(Self, AHandled); +end; + +procedure TListViewBase.DoEditModeChange; +begin + if Assigned(FOnEditModeChange) then + FOnEditModeChange(Self); +end; + +procedure TListViewBase.DoItemInvalidated(const Item: TListItem); +begin +end; + +procedure TListViewBase.DoItemResized(const Item: TListItem); +begin +end; + +procedure TListViewBase.DoRequestReindexing(const Item: TListItem); +begin +end; + +procedure TListViewBase.DoCheckStateChanged(const Item: TListItem; const Control: TListItemDrawable); +begin +end; + +procedure TListViewBase.DoControlClicked(const Item: TListItem; const Control: TListItemDrawable); +begin +end; + +procedure TListViewBase.DoItemsChange; +begin + UpdateScrollingLimits; + RebuildList; + + if Assigned(FOnItemsChange) then + FOnItemsChange(Self); +end; + +procedure TListViewBase.DoAdapterSet; +begin + UpdateScrollingLimits; +end; + +procedure TListViewBase.SetFilterPredicate(const Predicate: TPredicate); +var + Filterable: IListViewFilterable; +begin + if Supports(Adapter, IListViewFilterable, Filterable) then + Filterable.Filter := Predicate; +end; + +procedure TListViewBase.RebuildList; +begin +end; + +procedure TListViewBase.StopPullRefresh; +begin +end; + +procedure TListViewBase.SetItemSpaces(const Value: TBounds); +begin + FItemSpaces.Assign(Value); +end; + +function TListViewBase.GetDeleteButtonText: string; +begin + if FDeleteButton <> nil then + Result := FDeleteButton.Text + else + Result := FDeleteButtonText; +end; + +procedure TListViewBase.SetDeleteButtonText(const Value: string); +begin + FDeleteButtonText := Value; + + if FDeleteButton <> nil then + FDeleteButton.Text := Value; +end; + +function TListViewBase.DeleteButtonTextStored: Boolean; +begin + Result := FDeleteButtonText <> DefaultDeleteButtonText; +end; + +function TListViewBase.DeleteItem(const ItemIndex: Integer): Boolean; +begin + Result := (ItemIndex >= 0) and (ItemIndex < Adapter.Count); + + if Result and Assigned(FOnDeletingItem) then + FOnDeletingItem(Self, ItemIndex, Result); + + if Result then + begin + if (FItemIndex <> -1) and (FItemIndex >= ItemIndex) then + SetItemIndex(-1); + + DoDeleteItem(ItemIndex); + + if Assigned(FOnDeleteItem) then + FOnDeleteItem(Self, FDeleteButtonIndex); + end; +end; + +procedure TListViewBase.DoDeleteItem(const ItemIndex: Integer); +var + Editor: IListViewEditor; + + function Purpose(const AIndex: Integer): TListItemPurpose; + begin + Result := TListItemPurpose.None; + if (AIndex >= 0) and (AIndex < Adapter.Count) then + Result := Adapter[AIndex].Purpose; + end; + + procedure DeleteEmptySection(const Index: Integer); + begin + if Purpose(Index - 1) = TListItemPurpose.Header then + begin + if Purpose(Index) = TListItemPurpose.Footer then + Editor.Delete(Index); + if (Index = Adapter.Count) or (Purpose(Index) <> TListItemPurpose.None) then + Editor.Delete(Index - 1); + end; + end; + +begin + if Supports(Adapter, IListViewEditor, Editor) then + begin + Editor.Delete(ItemIndex); + DeleteEmptySection(ItemIndex); + end; +end; + +function TListViewBase.HasRecurrentTimerEvents: Boolean; +begin + Result := + // Delayed Incidents + ((FDelayedIncidents <> nil) and (FDelayedIncidents.Count > 0)) or + // Animation/transition + (FTransitionType <> TTransitionType.None) or + // Tap Selection + (FTapSelectItemIndex <> -1) or + // Selection Crossfading + ((FSelectionAlphas <> nil) and (FSelectionAlphas.Count > 0)) or + // Pull to Refresh animation + (FPullRefreshAnimation = TPullRefreshAnimation.Playing); +end; + +procedure TListViewBase.DestroyRecurrentTimer; +begin + if FRecurrentTimerHandle <> 0 then + begin + FTimerService.DestroyTimer(FRecurrentTimerHandle); + FRecurrentTimerHandle := 0; + end; +end; + +procedure TListViewBase.UpdateRecurrentTimer; +var + HasEvents: Boolean; +begin + HasEvents := HasRecurrentTimerEvents; + + if HasEvents and (FRecurrentTimerHandle = 0) then + FRecurrentTimerHandle := FTimerService.CreateTimer(RecurrentTimerInterval, RecurrentTimerEvent) + else if (not HasEvents) and (FRecurrentTimerHandle <> 0) then + DestroyRecurrentTimer; +end; + +procedure TListViewBase.RecurrentTimerEvent; +begin + if (FDelayedIncidents <> nil) and (FDelayedIncidents.Count > 0) then + ProcessDelayedIncidents; + + if FTransitionType <> TTransitionType.None then + ProcessTransitionAnimation; + + if FTapSelectItemIndex <> -1 then + ProcessTapSelectItem; + + if (FSelectionAlphas <> nil) and (FSelectionAlphas.Count > 0) then + ProcessSelectionAlphas; + + if FPullRefreshAnimation = TPullRefreshAnimation.Playing then + ProcessPullRefreshAnimation; + + UpdateRecurrentTimer; +end; + +procedure TListViewBase.StartIncident(const Incident: TDelayedIncident; const Triggered: Boolean; + const TimeToWait: Single; const CustomData: NativeInt); +var + Entry: TDelayedIncidentEntry; +begin + FillChar(Entry, SizeOf(TDelayedIncidentEntry), 0); + Entry.Incident := Incident; + Entry.Triggered := Triggered; + Entry.StartTime := FTimerService.GetTick; + Entry.TimeToWait := TimeToWait; + Entry.CustomData := CustomData; + + FDelayedIncidents.Add(Entry); + UpdateRecurrentTimer; +end; + +procedure TListViewBase.TriggerIncidents(const Incident: TDelayedIncident; const ResetStartupTime: Boolean); +var + I: Integer; + CurTime: Double; + Entry: TDelayedIncidentEntry; +begin + CurTime := FTimerService.GetTick; + + for I := 0 to FDelayedIncidents.Count - 1 do + begin + Entry := FDelayedIncidents[I]; + + if Entry.Incident = Incident then + begin + Entry.Triggered := True; + + if ResetStartupTime then + Entry.StartTime := CurTime; + end; + + FDelayedIncidents[I] := Entry; + end; +end; + +procedure TListViewBase.ProcessIncident(const Entry: TDelayedIncidentEntry); +var + J, RowColumns: Integer; + pW, ObjX: Single; + iObject: TListItemDrawable; + ObjPoint: TPointF; + DrawebleName: string; +begin + case Entry.Incident of + TDelayedIncident.ChangeRepainted: + DoChangeRepainted; + + TDelayedIncident.Invalidate: + Invalidate; + + TDelayedIncident.SetItemIndex: + SetItemIndexInternal(Entry.CustomData); + + TDelayedIncident.ClickEvent: + begin + if Assigned(FOnItemClickEx) and (not FMouseClickSwipeEventSend) then + FOnItemClickEx(Self, FClickEventItemIndex, FClickEventMousePos, + FClickEventControl); + // ZuBy *** + if (FAutoColumns) + { and (not FCanSwipeDirection) and (Assigned(FOnColumnClick)) } then + begin + pW := 0; + RowColumns := Min(TListViewItem(Adapter[FClickEventItemIndex]).Tag, + FColumns); + for J := 1 to RowColumns do + begin + pW := (J * FColumnWidth) - FColumnWidth; + if InRange(FClickEventMousePos.X, (pW + (J * FMarg)), + (pW + FColumnWidth) + (J * FMarg)) then + break; + end; + if (J > RowColumns) or (J < 0) then + exit; + if Assigned(FOnColumnClick) then + begin + ObjX := FClickEventMousePos.X - (pW + (J * FMarg)); + DrawebleName := ''; + iObject := TListViewItem(Adapter[FClickEventItemIndex]) + .Objects.FindObjectT('bitmap' + J.ToString); + if iObject <> nil then + begin + ObjPoint := iObject.PlaceOffset.Point; + if InRange(FClickEventMousePos.X, ObjPoint.X, + ObjPoint.X + iObject.Width) and InRange(FClickEventMousePos.Y, + ObjPoint.Y, ObjPoint.Y + iObject.Height) then + DrawebleName := iObject.Name; + end; + if DrawebleName.IsEmpty then + begin + iObject := TListViewItem(Adapter[FClickEventItemIndex]) + .Objects.FindObjectT + ('oi_values' + J.ToString); + if iObject <> nil then + begin + if iObject.Visible then + begin + ObjPoint := iObject.PlaceOffset.Point; + if InRange(FClickEventMousePos.X, ObjPoint.X, + ObjPoint.X + iObject.Width) and + InRange(FClickEventMousePos.Y, ObjPoint.Y, + ObjPoint.Y + iObject.Height) then + DrawebleName := iObject.Name; + end; + end; + end; + if DrawebleName.IsEmpty then + begin + iObject := TListViewItem(Adapter[FClickEventItemIndex]) + .Objects.FindObjectT('price' + J.ToString); + if iObject <> nil then + begin + ObjPoint := iObject.PlaceOffset.Point; + if InRange(FClickEventMousePos.X, ObjPoint.X, + ObjPoint.X + iObject.Width) and InRange(FClickEventMousePos.Y, + ObjPoint.Y, ObjPoint.Y + iObject.Height) then + DrawebleName := iObject.Name; + end; + end; + if DrawebleName.IsEmpty then + DrawebleName := 'item'; + FOnColumnClick(Self, J, ObjX, FClickEventMousePos.Y, + TListViewItem(Adapter[FClickEventItemIndex]), DrawebleName); + end; + // *** ZuBy + end; + end; + end; +end; + +procedure TListViewBase.ProcessDelayedIncidents; + + function IsUnsafeIncident(const Incident: TDelayedIncident): Boolean; + begin + Result := Incident in [TDelayedIncident.ChangeRepainted, TDelayedIncident.ClickEvent]; + end; + +var + I: Integer; + CurTime: Double; + Entry: TDelayedIncidentEntry; + UnsafeIncidents: TDelayedIncidents; +begin + UnsafeIncidents := nil; + try + CurTime := FTimerService.GetTick; + + for I := FDelayedIncidents.Count - 1 downto 0 do + begin + Entry := FDelayedIncidents[I]; + + if Entry.Triggered and (Abs(CurTime - Entry.StartTime) >= Entry.TimeToWait) then + begin + if IsUnsafeIncident(Entry.Incident) then + begin + if UnsafeIncidents = nil then + UnsafeIncidents := TDelayedIncidents.Create; + + UnsafeIncidents.Add(Entry); + end + else + ProcessIncident(Entry); + + FDelayedIncidents.Delete(I); + end; + end; + + if UnsafeIncidents <> nil then + for I := 0 to UnsafeIncidents.Count - 1 do + ProcessIncident(UnsafeIncidents[I]); + finally + UnsafeIncidents.Free; + end; +end; + +procedure TListViewBase.ProcessTransitionAnimation; +begin + case FTransitionType of + TTransitionType.EditMode: + if FEditMode then + begin + FEditModeTransitionAlpha := Min(Abs(FTimerService.GetTick - FTransitionStartTime) / + EditModeAnimationDuration, 1); + + if FEditModeTransitionAlpha >= 1 then + ResetEditModeAnimation; + end + else + begin + FEditModeTransitionAlpha := Max(1 - (Abs(FTimerService.GetTick - FTransitionStartTime) / + EditModeAnimationDuration), 0); + + if FEditModeTransitionAlpha <= 0 then + ResetEditModeAnimation; + end; + + TTransitionType.DeleteMode: + begin + if FDeleteButtonIndex <> -1 then + begin + FDeleteModeTransitionAlpha := Min(Abs(FTimerService.GetTick - FTransitionStartTime) / + DeleteModeAnimationDuration, 1); + + if FDeleteModeTransitionAlpha >= 1 then + ResetDeleteModeAnimation; + end + else + begin + FDeleteModeTransitionAlpha := Max(1 - (Abs(FTimerService.GetTick - FTransitionStartTime) / + DeleteModeAnimationDuration), 0); + + if FDeleteModeTransitionAlpha <= 0 then + ResetDeleteModeAnimation; + end; + + UpdateDeleteButtonLayout; + end; + end; + + if FTransitionType <> TTransitionType.None then + Invalidate; +end; + +procedure TListViewBase.ProcessTapSelectItem; +var + Checkable: IListViewCheckProvider; +begin + if Abs(FTimerService.GetTick - FTapSelectStartTime) >= TapSelectWaitTime then + begin + if FAllowSelection then + begin + if Adapter[FTapSelectItemIndex].HasClickOnSelectItems then + FItemIndex := -1; + + SetNewItemIndex(FTapSelectItemIndex); + FTapSelectNewIndexApplied := FTapSelectItemIndex; + + FClickEventItemIndex := FTapSelectItemIndex; + if not FMouseClickSwipeEventSend then // ZuBy + StartIncident(TDelayedIncident.ClickEvent); + end + else if FEditMode and Supports(Adapter, IListViewCheckProvider, Checkable) then + begin + Checkable[FTapSelectItemIndex] := not Checkable[FTapSelectItemIndex]; + FTapSelectNewIndexApplied := FTapSelectItemIndex; + end; + + FTapSelectItemIndex := -1; + end; +end; + +procedure TListViewBase.ProcessSelectionAlphas; +type + TTrashedItems = TList; +var + TrashedItems: TTrashedItems; + ItemAlpha: TItemSelectionAlpha; + Index: Integer; + MaxIndex: Integer; + CurTime: Double; + NewAlpha, Theta, FinalAlpha: Single; + NeedRepaint: Boolean; +begin + CurTime := FTimerService.GetTick; + NeedRepaint := False; + MaxIndex := Adapter.Count - 1; + TrashedItems := TTrashedItems.Create; + try + for Index in FSelectionAlphas.Keys do + begin + if not FSelectionAlphas.TryGetValue(Index, ItemAlpha) or not InRange(Index, 0, MaxIndex) then + begin + TrashedItems.Add(Index); + Continue; + end; + + if CanDisplaySelectionForItem(Index, Adapter[Index], True) then + begin + Theta := Abs(CurTime - ItemAlpha.StartTime) / SelectionFadeInTime; + FinalAlpha := GetDefaultSelectionAlpha; + end + else + begin + Theta := Abs(CurTime - ItemAlpha.StartTime) / SelectionFadeOutTime; + FinalAlpha := 0; + end; + + NewAlpha := ItemAlpha.StartAlpha + (FinalAlpha - ItemAlpha.StartAlpha) * Theta; + if not SameValue(NewAlpha, ItemAlpha.Alpha, TEpsilon.Vector) then + begin + ItemAlpha.Alpha := NewAlpha; + + if Theta >= 1 then + TrashedItems.Add(Index) + else + FSelectionAlphas.AddOrSetValue(Index, ItemAlpha); + + NeedRepaint := True; + end; + end; + + for Index in TrashedItems do + FSelectionAlphas.Remove(Index); + finally + TrashedItems.Free; + end; + + if NeedRepaint then + Invalidate; +end; + +procedure TListViewBase.InsertItemCrossFade(const Index: Integer; const ShowAnimation: Boolean); +var + ItemAlpha, PrevItemAlpha: TItemSelectionAlpha; +begin + if (not FSelectionCrossFade) or (FSelectionAlphas = nil) or (Adapter[Index].Purpose <> TListItemPurpose.None) then + exit; + + if ShowAnimation then + ItemAlpha := TItemSelectionAlpha.Create(FTimerService.GetTick, 0, 0) + else + ItemAlpha := TItemSelectionAlpha.Create(FTimerService.GetTick, GetDefaultSelectionAlpha, GetDefaultSelectionAlpha); + + if FSelectionAlphas.TryGetValue(Index, PrevItemAlpha) then + begin + ItemAlpha.StartAlpha := PrevItemAlpha.Alpha; + ItemAlpha.Alpha := PrevItemAlpha.Alpha; + end; + + FSelectionAlphas.AddOrSetValue(Index, ItemAlpha); + + UpdateRecurrentTimer; +end; + +procedure TListViewBase.RemoveItemCrossFade(const Index: Integer); +begin + if FSelectionAlphas.ContainsKey(Index) then + FSelectionAlphas.Remove(Index); +end; + +function TListViewBase.GetItemSelectionAlpha(const Index: Integer): Single; +var + ItemAlpha: TItemSelectionAlpha; +begin + if (FSelectionAlphas = nil) or (FSelectionAlphas.Count < 1) then + exit(GetDefaultSelectionAlpha); + + if FSelectionAlphas.TryGetValue(Index, ItemAlpha) then + Result := ItemAlpha.Alpha + else + Result := GetDefaultSelectionAlpha; +end; + +procedure TListViewBase.InitEditModeAnimation; +var + Checkable: IListViewCheckProvider; +begin + if Supports(Adapter, IListViewCheckProvider, Checkable) then + begin + if Checkable.FirstChecked(True) <> -1 then + begin + Checkable.CheckAll(False); + SetDeleteButtonIndex(-1); + end; + + UpdateDeleteButtonLayout; + + FTransitionType := TTransitionType.EditMode; + FTransitionStartTime := FTimerService.GetTick; + UpdateRecurrentTimer; + end; +end; + +procedure TListViewBase.ResetEditModeAnimation; +begin + FTransitionType := TTransitionType.None; + UpdateRecurrentTimer; + + if EditMode then + FEditModeTransitionAlpha := 1 + else + FEditModeTransitionAlpha := 0; + + InvalidateHeights; + Invalidate; +end; + +procedure TListViewBase.InitDeleteModeAnimation; +begin + FTransitionType := TTransitionType.DeleteMode; + FTransitionStartTime := FTimerService.GetTick; + UpdateRecurrentTimer; + + if FDeleteLayout = nil then + begin + FDeleteLayout := TLayout.Create(Self); + FDeleteLayout.Stored := False; + FDeleteLayout.Locked := True; + FDeleteLayout.Width := DefaultDeleteButtonWidth; + FDeleteLayout.ClipChildren := True; + FDeleteLayout.Parent := Self; + end; + + if FDeleteButton = nil then + begin + FDeleteButton := TSpeedButton.Create(FDeleteLayout); + FDeleteButton.Stored := False; + FDeleteButton.Locked := True; + FDeleteButton.Align := TAlignLayout.MostRight; + FDeleteButton.Width := DefaultDeleteButtonWidth; + FDeleteButton.StyleLookup := 'listitemdeletebutton'; + FDeleteButton.Text := FDeleteButtonText; + FDeleteButton.OnClick := DeleteButtonClicked; + FDeleteButton.Parent := FDeleteLayout; + end; + + UpdateDeleteButtonLayout; +end; + +{$WARN SYMBOL_DEPRECATED OFF} +procedure TListViewBase.ResetDeleteModeAnimation; +begin + FTransitionType := TTransitionType.None; + UpdateRecurrentTimer; + + FDeleteButton.Visible := Adapter.Count > 0; + Invalidate; + + if FDeleteButtonIndex <> -1 then + FDeleteModeTransitionAlpha := 1 + else + begin + FPrevDeleteButtonIndex := -1; + FDeleteModeTransitionAlpha := 0; + end; + + if FDeleteButtonIndex = -1 then + begin + // This method can be invoked from event handler of these controls, so we have to remove it later after exiting + // from their event handlers + if FDeleteButton <> nil then + begin + FDeleteButton.Release; + FDeleteButton := nil + end; + + if FDeleteLayout <> nil then + begin + FDeleteLayout.Release; + FDeleteLayout := nil + end; + end; +end; +{$WARN SYMBOL_DEPRECATED DEFAULT} + +procedure TListViewBase.StartPullRefreshAnimation; +begin + FPullRefreshAnimation := TPullRefreshAnimation.Playing; + FPullRefreshAnimationStartTime := FTimerService.GetTick; + FPullRefreshAnimationStopTime := FPullRefreshAnimationStartTime; + UpdateRecurrentTimer; +end; + +procedure TListViewBase.ProcessPullRefreshAnimation; +var + EndTrigger: Boolean; +begin + if TListingTransitionFeature.PullToRefresh in FListingService.GetTransitionFeatures then + EndTrigger := GetPullRefreshIndicatorAlpha <= 0 + else + begin + // ZuBy *** + if FHorizontal then + EndTrigger := GetPullRefreshStrokeWidth >= Height + else + EndTrigger := GetPullRefreshStrokeWidth >= Width; + // *** ZuBy + end; + + if EndTrigger then + begin + FPullRefreshAnimation := TPullRefreshAnimation.Finished; + UpdatePullRefreshState; + end; + + Invalidate; +end; + +function TListViewBase.GetPullRefreshStrength: Single; +begin + if FScrollStretchStrength < 0 then + Result := -FScrollStretchStrength + else + Result := 0; +end; + +function TListViewBase.GetPullRefreshIndicatorSteps: Integer; +const + IndicatorStrengthPerStep = 5; +begin + case FPullRefreshAnimation of + TListViewBase.TPullRefreshAnimation.NotPlaying: + Result := EnsureRange(Round((GetPullRefreshStrength - PullRefreshIndicatorStrengthStart) / + IndicatorStrengthPerStep), 0, PullRefreshIndicatorMaxSteps); + + TListViewBase.TPullRefreshAnimation.Playing: + Result := PullRefreshIndicatorMaxSteps; + + else + Result := 0; + end; +end; + +function TListViewBase.GetPullRefreshIndicatorAlpha: Single; +const + IndicatorFadeVelocity = 4; +begin + case FPullRefreshAnimation of + TListViewBase.TPullRefreshAnimation.NotPlaying: + Result := 1; + + TListViewBase.TPullRefreshAnimation.Playing: + if SameValue(FPullRefreshAnimationStartTime, FPullRefreshAnimationStopTime, TEpsilon.Vector) then + Result := 1 + else + Result := Max((1 - Abs(FTimerService.GetTick - FPullRefreshAnimationStopTime) * IndicatorFadeVelocity), 0); + + else + Result := 0; + end; +end; + +procedure TListViewBase.PaintPullRefreshIndicator(const ACanvas: TCanvas; const AStrength, AOpacity: Single); +const + IndicatorMinRadius = 6.5; + IndicatorMaxRadius = 13.5; + IndicatorThickness = 2; + IndicatorRotation = 2; + IndicatorDisappearFraction = 0.7; + PiMulTwo = 2 * Pi; + PiByTwo = Pi / 2; +var + Stroke: TStrokeBrush; + I, LineCount: Integer; + Center, P1, P2: TPointF; + LSin, LCos, LOpacity, Angle, TimeElapsed, ShrinkAlpha: Single; + MinRadius, MaxRadius, Thickness, TopAdjust: Single; +begin + if (FSearchEdit <> nil) and FSearchEdit.Visible and not HasSearchAsItem then + begin + // ZuBy *** + if FHorizontal then + TopAdjust := FSearchEdit.Width + else + TopAdjust := FSearchEdit.Height; + // *** ZuBy + end + else + TopAdjust := 0; + + LineCount := GetPullRefreshIndicatorSteps; + if LineCount < 1 then + exit; + + LOpacity := GetPullRefreshIndicatorAlpha; + if LOpacity <= 0 then + exit; + + // ZuBy *** + if FHorizontal then + begin + Center.X := Height / 2; + Center.Y := TopAdjust + PullRefreshIndicatorStrengthStart + + IndicatorMaxRadius; + end + else + begin + Center.X := Width / 2; + Center.Y := TopAdjust + PullRefreshIndicatorStrengthStart + + IndicatorMaxRadius; + end; + // *** ZuBy + + MinRadius := IndicatorMinRadius; + MaxRadius := IndicatorMaxRadius; + Thickness := IndicatorThickness; + + if FPullRefreshAnimation = TPullRefreshAnimation.Playing then + TimeElapsed := Abs(FTimerService.GetTick - FPullRefreshAnimationStartTime) + else + TimeElapsed := 0; + + if LOpacity <= IndicatorDisappearFraction then + begin + ShrinkAlpha := (1 - IndicatorDisappearFraction) + LOpacity; + MaxRadius := MaxRadius * ShrinkAlpha; + MinRadius := MinRadius * ShrinkAlpha; + Thickness := Thickness * ShrinkAlpha; + end; + + Stroke := TStrokeBrush.Create(TBrushKind.Solid, FStyleResources.PullRefreshIndicatorColor); + try + Stroke.Thickness := Thickness; + + for I := 0 to LineCount - 1 do + begin + Angle := ((I * PiMulTwo) / PullRefreshIndicatorMaxSteps) + TimeElapsed * IndicatorRotation - PiByTwo; + + if not SameValue(FPullRefreshAnimationStartTime, FPullRefreshAnimationStopTime, TEpsilon.Vector) then + Angle := Angle + Abs(FTimerService.GetTick - FPullRefreshAnimationStopTime) * IndicatorRotation * 2; + + SinCos(Angle, LSin, LCos); + P1.X := Center.X + LCos * MinRadius; + P1.Y := Center.Y + LSin * MinRadius; + P2.X := Center.X + LCos * MaxRadius; + P2.Y := Center.Y + LSin * MaxRadius; + // ZuBy *** + if FHorizontal then + ACanvas.DrawLine(P2, P1, AOpacity * LOpacity, Stroke) + else + ACanvas.DrawLine(P1, P2, AOpacity * LOpacity, Stroke); + // *** ZuBy + end; + finally + Stroke.Free; + end; +end; + +function TListViewBase.GetPullRefreshStrokeWidth: Single; +const + StrokeCollapseSpeed1 = 4; + StrokeCollapseSpeed2 = 256; + StrokeCollapsePower = 0.75; + StrokeGrowthSpeed = 0.25; +begin + if FPullRefreshAnimation = TPullRefreshAnimation.Playing then + begin + // ZuBy *** + if FHorizontal then + Result := Min + (Power(Abs(FTimerService.GetTick - FPullRefreshAnimationStartTime) * + StrokeCollapseSpeed1, StrokeCollapsePower) * + StrokeCollapseSpeed2, Height) + else + Result := Min + (Power(Abs(FTimerService.GetTick - FPullRefreshAnimationStartTime) * + StrokeCollapseSpeed1, StrokeCollapsePower) * + StrokeCollapseSpeed2, Width); + // *** ZuBy + end + else + begin + // ZuBy *** + if FHorizontal then + Result := Min + (Sqr(Max(GetPullRefreshStrength - PullRefreshIndicatorStrengthStart, + 0) * StrokeGrowthSpeed), Height) + else + Result := Min + (Sqr(Max(GetPullRefreshStrength - PullRefreshIndicatorStrengthStart, + 0) * StrokeGrowthSpeed), Width); + // *** ZuBy + end; +end; + +procedure TListViewBase.PaintPullRefreshStroke(const ACanvas: TCanvas; const AStrength, AOpacity: Single); +const + DefaultStrokeThickness = 2.5; +var + StrokeBrush: TBrush; + StrokeLength, TopAdjust: Single; +begin + if (FSearchEdit <> nil) and FSearchEdit.Visible and not HasSearchAsItem then + begin + // ZuBy *** + if FHorizontal then + TopAdjust := FSearchEdit.Width + else + TopAdjust := FSearchEdit.Height; + // *** ZuBy + end + else + TopAdjust := 0; + + StrokeBrush := TBrush.Create(TBrushKind.Gradient, FStyleResources.PullRefreshStrokeColor); + try + StrokeBrush.Kind := TBrushKind.Solid; + StrokeLength := GetPullRefreshStrokeWidth; + + if FPullRefreshAnimation = TPullRefreshAnimation.Playing then + begin + // ZuBy *** + if FHorizontal then + begin + ACanvas.FillRect(TRectF.Create(TopAdjust, 0, + TopAdjust + DefaultStrokeThickness, (Height - StrokeLength) / 2), 0, + 0, AllCorners, AOpacity, StrokeBrush); + ACanvas.FillRect(TRectF.Create(TopAdjust, (Height + StrokeLength) / 2, + TopAdjust + DefaultStrokeThickness, Height), 0, 0, AllCorners, + AOpacity, StrokeBrush); + end + else + begin + ACanvas.FillRect(TRectF.Create(0, TopAdjust, (Width - StrokeLength) / 2, + TopAdjust + DefaultStrokeThickness), 0, 0, AllCorners, AOpacity, + StrokeBrush); + ACanvas.FillRect(TRectF.Create((Width + StrokeLength) / 2, TopAdjust, + Width, TopAdjust + DefaultStrokeThickness), 0, 0, AllCorners, + AOpacity, StrokeBrush); + end; + // *** ZuBy + end; + + if FPullRefreshAnimation = TPullRefreshAnimation.NotPlaying then + begin + // ZuBy *** + if FHorizontal then + ACanvas.FillRect(TRectF.Create(TopAdjust, (Height - StrokeLength) / 2, + TopAdjust + DefaultStrokeThickness, (Height + StrokeLength) / 2), 0, + 0, AllCorners, AOpacity, StrokeBrush) + else + ACanvas.FillRect(TRectF.Create((Width - StrokeLength) / 2, TopAdjust, + (Width + StrokeLength) / 2, TopAdjust + DefaultStrokeThickness), 0, 0, + AllCorners, AOpacity, StrokeBrush); + // *** ZuBy + end; + finally + StrokeBrush.Free; + end; +end; + +procedure TListViewBase.PaintScrollingStretchGlow(const ACanvas: TCanvas; const AIntensity, AOpacity: Single); +var + TempPoint: TGradientPoint; + TempColor: TAlphaColor; + GlowBrush: TBrush; + GlowDepth: Single; + GlowRect: TRectF; +begin + GlowBrush := TBrush.Create(TBrushKind.Gradient, FStyleResources.ScrollingStretchGlowColor); + try + GlowBrush.Gradient.Style := TGradientStyle.Radial; + GlowBrush.Gradient.Points.Clear; + TempColor := FStyleResources.ScrollingStretchGlowColor; + + TempPoint := TGradientPoint.Create(GlowBrush.Gradient.Points); + TAlphaColorRec(TempColor).A := 0; + TempPoint.Color := TempColor; + TempPoint.Offset := 0; + + TempPoint := TGradientPoint.Create(GlowBrush.Gradient.Points); + TAlphaColorRec(TempColor).A := 255; + TempPoint.Color := TempColor; + TempPoint.Offset := 1; + + GlowDepth := Max((Sqrt(Abs(AIntensity)) - 3) * 3, 0); + if GlowDepth > TEpsilon.Position then + begin + if AIntensity < 0 then + begin + // ZuBy *** + if FHorizontal then + GlowRect := TRectF.Create(-GlowDepth, -Height / 8, GlowDepth, + Height + Height / 8) + else + GlowRect := TRectF.Create(-Width / 8, -GlowDepth, Width + Width / 8, + GlowDepth); + // *** ZuBy + if (FSearchEdit <> nil) and FSearchEdit.Visible and not HasSearchAsItem + then + GlowRect.Offset(0, FSearchEdit.Height); + end + else + begin + // ZuBy *** + if FHorizontal then + GlowRect := TRectF.Create(Width - GlowDepth, -Height / 8, + Width + GlowDepth, Height + Height / 8) + else + GlowRect := TRectF.Create(-Width / 8, Height - GlowDepth, + Width + Width / 8, Height + GlowDepth); + // *** ZuBy + end; + + ACanvas.FillEllipse(GlowRect, AOpacity, GlowBrush); + end; + finally + GlowBrush.Free; + end; +end; + +procedure TListViewBase.UpdatePullRefreshState; +var + Trigger: Boolean; +begin + if FPullRefreshTriggered and (GetPullRefreshStrength < 1) and + (FPullRefreshAnimation <> TPullRefreshAnimation.Playing) then + begin + FPullRefreshTriggered := False; + FPullRefreshAnimation := TPullRefreshAnimation.NotPlaying; + FPullRefreshAnimationStopTime := FPullRefreshAnimationStartTime; + end + else if not FPullRefreshTriggered then + begin + if TListingTransitionFeature.PullToRefresh in FListingService.GetTransitionFeatures then + Trigger := GetPullRefreshIndicatorSteps >= PullRefreshIndicatorMaxSteps + else + begin + // ZuBy *** + if FHorizontal then + Trigger := GetPullRefreshStrokeWidth >= Height + else + Trigger := GetPullRefreshStrokeWidth >= Width; + // *** ZuBy + end; + + if Trigger then + begin + FPullRefreshTriggered := True; + StartPullRefreshAnimation; + + TThread.Queue(nil, + procedure + begin + if Assigned(FOnPullRefresh) then + FOnPullRefresh(Self); + end); + end; + end; + if FPullRefreshTriggered and (FPullRefreshAnimation = TPullRefreshAnimation.Playing) and + SameValue(FPullRefreshAnimationStartTime, FPullRefreshAnimationStopTime, TEpsilon.Vector) and (FAniCalc <> nil) and + (not FAniCalc.Down) then + FPullRefreshAnimationStopTime := FTimerService.GetTick; +end; + +procedure TListViewBase.UpdateScrollStretchStrength(const NewValue: Single); +begin + if not SameValue(FScrollStretchStrength, NewValue, TEpsilon.Position) then + begin + FScrollStretchStrength := NewValue; + ScrollStretchChanged; + end; +end; + +procedure TListViewBase.ScrollStretchChanged; +begin + if FPullToRefresh then + UpdatePullRefreshState; + + if FPullToRefresh or HasScrollingStretchGlow then + Invalidate; +end; + +procedure TListViewBase.UpdateDeleteButtonLayout; +var + RelRect: TRectF; +begin + if (Adapter.Count < 1) or (FDeleteLayout = nil) or ((FDeleteButtonIndex = -1) and + (FPrevDeleteButtonIndex = -1)) then + exit; + + if (FListingService <> nil) and (TListingTransitionFeature.DeleteButtonSlide in FListingService.GetTransitionFeatures) then + begin + FDeleteLayout.Width := DefaultDeleteButtonWidth * FDeleteModeTransitionAlpha; + FDeleteButton.Opacity := 1; + end + else + begin + if FDeleteModeTransitionAlpha > 0 then + FDeleteLayout.Width := DefaultDeleteButtonWidth + else + FDeleteLayout.Width := 0; + + FDeleteButton.Opacity := 0.5 + (FDeleteModeTransitionAlpha / 2); + end; + + FDeleteLayout.Height := GetItemHeight(FDeleteButtonIndex); + + // ZuBy *** + if FHorizontal then + FDeleteLayout.Position.X := RelRect.Right - FDeleteLayout.Width + else + FDeleteLayout.Position.X := Width - FDeleteLayout.Width; + // *** ZuBy + + if FDeleteButtonIndex = -1 then + RelRect := GetItemRelRect(FPrevDeleteButtonIndex, LocalRect) + else + RelRect := GetItemRelRect(FDeleteButtonIndex, LocalRect); + + FDeleteLayout.Position.Y := + (RelRect.Top + RelRect.Bottom - FDeleteLayout.Height) / 2; +end; + +procedure TListViewBase.DeleteButtonClicked(Sender: TObject); +begin + if DeleteItem(FDeleteButtonIndex) then + begin + FSelectionAlphas.Remove(FDeleteButtonIndex); + SetDeleteButtonIndex(-1); + ResetDeleteModeAnimation; + end; +end; + +procedure TListViewBase.ProceedDeleteItem; +var + Editor: IListViewEditor; +begin + if (FDeleteButtonIndex = -1) or not Supports(Adapter, IListViewEditor, Editor) then + exit; + + if (FItemIndex <> -1) and (FItemIndex >= FDeleteButtonIndex) then + SetItemIndex(-1); + + Editor.Delete(FDeleteButtonIndex); + + if Assigned(FOnDeleteItem) then + FOnDeleteItem(Self, FDeleteButtonIndex); + + SetDeleteButtonIndex(-1); + + ResetDeleteModeAnimation; +end; + +procedure TListViewBase.SetDeleteButtonIndex(const NewItemIndex: Integer); +begin + if FDeleteButtonIndex <> NewItemIndex then + begin + if FTransitionType = TTransitionType.DeleteMode then + ResetDeleteModeAnimation; + + FPrevDeleteButtonIndex := FDeleteButtonIndex; + FDeleteButtonIndex := NewItemIndex; + + InitDeleteModeAnimation; + + if Assigned(FOnDeleteChange) then + FOnDeleteChange(Self, (FDeleteButtonIndex = -1) and (not FEditMode)); + + if FSearchEdit <> nil then + FSearchEdit.Enabled := (FDeleteButtonIndex = -1) and (not FEditMode); + end; +end; + +function TListViewBase.CanObserve(const ID: Integer): Boolean; +begin + Result := False; + if ID = TObserverMapping.EditLinkID then + Result := True + else if ID = TObserverMapping.PositionLinkID then + Result := True + else if ID = TObserverMapping.ControlValueID then + Result := True; +end; + +procedure TListViewBase.ObserversBeforeSelection(out LAllowSelection: Boolean); +begin + LAllowSelection := True; + if Observers.IsObserving(TObserverMapping.EditLinkID) and not TLinkObservers.EditLinkEdit(Observers) then + LAllowSelection := False; + if LAllowSelection then + TLinkObservers.PositionLinkPosChanging(Observers); +end; + +procedure TListViewBase.DoListItemChange(const AListItem: TListItem); +begin + if Assigned(FOnItemChange) then + FOnItemChange(Self, AListItem); +end; + +procedure TListViewBase.DoListItemClick(const AListItem: TListItem); +begin + if Assigned(FOnListItemClick) then + FOnListItemClick(Self, AListItem); +end; + +procedure TListViewBase.DoUpdateItemView(const AListItem: TListItem); +begin + if Assigned(FOnUpdateItemView) then + FOnUpdateItemView(Self, AListItem); +end; + +procedure TListViewBase.DoUpdatingItemView(const AListItem: TListItem; var AHandled: Boolean); +begin + if Assigned(FOnUpdatingItemView) then + FOnUpdatingItemView(Self, AListItem, AHandled); +end; + +procedure TListViewBase.DoUpdateScrollViewPos(const Value: Single); +begin +end; + +procedure TListViewBase.ReadCanSwipeDelete(Reader: TReader); +begin + CanSwipeDelete := Reader.ReadBoolean; +end; + +procedure TListViewBase.ReadIsSearchVisible(Reader: TReader); +begin + SearchVisible := Reader.ReadBoolean; +end; + +procedure TListViewBase.ReadIsSearchAlwaysOnTop(Reader: TReader); +begin + SearchAlwaysOnTop := Reader.ReadBoolean; +end; + +procedure TListViewBase.ReadEditModeOptions(Reader: TReader); +begin + TOpenReader(Reader).ReadSet(TypeInfo(TEditModeOptions)); +end; + +procedure TListViewBase.DefineProperties(Filer: TFiler); +begin + inherited; + + Filer.DefineProperty('CanSwypeDelete', ReadCanSwipeDelete, nil, False); + Filer.DefineProperty('DeleteButtonEnabled', ReadCanSwipeDelete, nil, False); + Filer.DefineProperty('EnabledDeleteButton', ReadCanSwipeDelete, nil, False); + Filer.DefineProperty('IsSearchVisible', ReadIsSearchVisible, nil, False); + Filer.DefineProperty('IsSearchAlwaysOnTop', ReadIsSearchAlwaysOnTop, nil, False); + Filer.DefineProperty('EditModeOptions', ReadEditModeOptions, nil, False); +end; + +procedure TListViewBase.DoRealign; +begin + inherited; + Repaint; +end; + +procedure TListViewBase.DoExit; +begin + inherited; + if Observers.IsObserving(TObserverMapping.EditLinkID) then + if TLinkObservers.EditLinkIsEditing(Observers) then + TLinkObservers.EditLinkUpdate(Observers); + if Observers.IsObserving(TObserverMapping.ControlValueID) then + TLinkObservers.ControlValueUpdate(Observers); +end; + +procedure TListViewBase.UpdateScrollViewPos(const Value: Single); +begin + if (not SameValue(FScrollViewPos, Value, TEpsilon.Vector) and FCanScroll) then + // sinuke + begin + FScrollViewPos := Value; + DoUpdateScrollViewPos(Value); + if Assigned(FOnScrollViewChange) then + FOnScrollViewChange(Self); + // ZuBy *** + if FScrollViewPos >= GetMaxScrollViewPos then + begin + if Assigned(FOnScrollEnd) then + FOnScrollEnd(Self); + end; + // *** ZuBy + end; + RecalcTopViewItemIndex; +end; + +procedure TListViewBase.UpdateSearchEditPos; +var + NewPos: Single; +begin + if FSearchEdit <> nil then + begin + if HasSearchAsItem then + NewPos := Max(0, FSearchEdit.Height - FScrollViewPos) - FSearchEdit.Height + else + NewPos := 0; + + if not SameValue(FSearchEdit.Position.Y, NewPos, TEpsilon.Position) then + begin + FSearchEdit.Position.Y := NewPos; + Invalidate; + end; + end; +end; + +function TListViewBase.GetMaxScrollViewPos: Integer; +begin + // ZuBy *** + if FHorizontal then + Result := Max(Trunc(FSideSpace * 2 + FMaxKnownHeight - LocalRect.Width), 0) + else + Result := Max(Trunc(FSideSpace * 2 + FMaxKnownHeight - + LocalRect.Height), 0); + // *** ZuBy +end; + +function TListViewBase.GetMessageSender: TMessageSender; +begin + if FMessageSender = nil then + begin + FMessageSender := TMessageSender.Create; + FMessageSender.Receiver := Self; + end; + Result := FMessageSender; +end; + +procedure TListViewBase.SetScrollViewPos(const Value: Single); +var + NewValue, MaxValue: Single; +begin + NewValue := Value; + + if NewValue < 0 then + NewValue := 0; + + MaxValue := GetMaxScrollViewPos; + if NewValue > MaxValue then + NewValue := MaxValue; + + UpdateScrollViewPos(NewValue); + DoSetScrollViewPos(NewValue); +end; + +procedure TListViewBase.SetSelected(const Value: TListItem); +begin + if Value = nil then + ItemIndex := -1 + else + ItemIndex := Adapter.IndexOf(Value); +end; + +procedure TListViewBase.SetSelectionCrossfade(const Value: Boolean); +begin + if FSelectionCrossfade <> Value then + begin + FSelectionCrossfade := Value; + Invalidate; + end; +end; + +procedure TListViewBase.SetPullToRefresh(const Value: Boolean); +begin + if FPullToRefresh <> Value then + begin + FPullToRefresh := Value; + if FAniCalc <> nil then + FAniCalc.BoundsAnimation := HasPhysicsStretchyScrolling; + RecreateNativePresentation; + end; +end; + +procedure TListViewBase.SetShowSelection(const Value: Boolean); +begin + if FShowSelection <> Value then + begin + FShowSelection := Value; + Invalidate; + end; +end; + +procedure TListViewBase.ScrollBarChange(Sender: TObject); +begin + UpdateScrollViewPos(FScrollBar.Value); + UpdateSearchEditPos; + UpdateDeleteButtonLayout; +end; + +procedure TListViewBase.AniCalcChange(Sender: TObject); +var + NewViewPos, MaxScrollViewPos: Single; +begin + // ZuBy *** + if FHorizontal then + NewViewPos := FAniCalc.ViewportPosition.X + else + NewViewPos := FAniCalc.ViewportPosition.Y; + // *** ZuBy + MaxScrollViewPos := GetMaxScrollViewPos; + + if NewViewPos < 0 then + UpdateScrollStretchStrength(NewViewPos) + else if NewViewPos > MaxScrollViewPos then + UpdateScrollStretchStrength(NewViewPos - MaxScrollViewPos) + else + UpdateScrollStretchStrength(0); + + if not HasStretchyScrolling then + NewViewPos := EnsureRange(NewViewPos, 0, MaxScrollViewPos); + + if (not SameValue(NewViewPos, FScrollViewPos, TEpsilon.Vector)) and + (TStateFlag.NeedsScrollBarDisplay in FStateFlags) then + begin + FScrollBar.StopPropertyAnimation('Opacity'); + FScrollBar.Opacity := 1; + + Exclude(FStateFlags, TStateFlag.NeedsScrollBarDisplay); + end; + + if TStateFlag.ScrollingActive in FStateFlags then + begin + UpdateScrollViewPos(NewViewPos); + UpdateSearchEditPos; + UpdateDeleteButtonLayout; + UpdateScrollBar; + end; +end; + +procedure TListViewBase.AniCalcStart(Sender: TObject); +begin + if IsRunningOnDesktop then + DisableHitTestForControl(FScrollBar); + + if Scene <> nil then + Scene.ChangeScrollingState(Self, True); + + // ZuBy *** + if FShowScrollBar then + FStateFlags := FStateFlags + [TStateFlag.NeedsScrollBarDisplay, + TStateFlag.ScrollingActive] + else + FStateFlags := FStateFlags + [TStateFlag.ScrollingActive]; + // *** ZuBy +end; + +procedure TListViewBase.AniCalcStop(Sender: TObject); +var + ScrollPixelAlign: Boolean; +begin + ScrollPixelAlign := TStateFlag.ScrollingActive in FStateFlags; + Exclude(FStateFlags, TStateFlag.ScrollingActive); + TAnimator.AnimateFloat(FScrollBar, 'Opacity', 0, 0.2); + + if Scene <> nil then + Scene.ChangeScrollingState(nil, False); + + if ScrollPixelAlign and (FScrollScale > TEpsilon.Scale) then + begin + // ZuBy *** + if FAutoPositionToItem then + ScrollTo(TAppearanceListView(Self).getFirstVisibleItemIndex) + else + SetScrollViewPos(Round(FScrollViewPos * FScrollScale) / FScrollScale); + // *** ZuBy + end; +end; + +procedure TListViewBase.UpdateScrollBar; +var + LocalHeight, ViewSize: Single; +begin + // ZuBy *** + if FHorizontal then + LocalHeight := LocalRect.Width + else + LocalHeight := LocalRect.Height; + // *** ZuBy + + if FScrollViewPos < 0 then + ViewSize := LocalHeight + FScrollViewPos + else if FScrollViewPos > FMaxKnownHeight - LocalHeight then + ViewSize := LocalHeight - (FScrollViewPos - (FMaxKnownHeight - LocalHeight)) + else + ViewSize := LocalHeight; + + FScrollBar.BeginUpdate; + try + FScrollBar.Max := FSideSpace * 2 + FMaxKnownHeight; + FScrollBar.SmallChange := Adapter.GetDefaultViewHeight * 0.5; + if not (csDesigning in ComponentState) then // Don't show at design time + FScrollBar.Visible := ((FMaxKnownHeight > LocalHeight) or + (HasTouchTracking and (TStateFlag.ScrollingActive in FStateFlags))) and + FShowScrollBar // ZuBy + else + FScrollBar.Visible := False; + FScrollBar.Value := FScrollViewPos; + FScrollBar.ViewportSize := ViewSize; + finally + FScrollBar.EndUpdate; + end; + RecalcTopViewItemIndex; // ZuBy +end; + +procedure TListViewBase.UpdateScrollingLimits; +begin + if not IsUpdating then + DoUpdateScrollingLimits + else + Include(FStateFlags, TStateFlag.NeedsScrollingLimitsUpdate); +end; + +procedure TListViewBase.DoUpdateScrollingLimits; +var + Targets: array of TAniCalculations.TTarget; +begin + if FAniCalc <> nil then + begin + SetLength(Targets, 2); + + Targets[0].TargetType := TAniCalculations.TTargetType.Min; + Targets[0].Point := TPointD.Create(0, 0); + Targets[1].TargetType := TAniCalculations.TTargetType.Max; + // ZuBy *** + if FHorizontal then + Targets[1].Point := TPointD.Create + (Max(FSideSpace * 2 + FMaxKnownHeight - LocalRect.Width, 0), 0) + else + Targets[1].Point := TPointD.Create(0, + Max(FSideSpace * 2 + FMaxKnownHeight - LocalRect.Height, 0)); + // *** ZuBy + + FAniCalc.SetTargets(Targets); + end; + + if not HasTouchTracking then + UpdateScrollBar; +end; + +procedure TListViewBase.GetNumberOfRenderingPasses(const StartItem, EndItem: Integer; var Passes, Subpasses: Integer); +var + I, J: Integer; + ListItem: TListItem; +begin + Passes := 0; + Subpasses := 1; + for J := StartItem to EndItem do + begin + ListItem := Adapter[J]; + ListItem.CreateObjects; + Passes := Max(Passes, ListItem.Count); + for I := 0 to ListItem.Count - 1 do + Subpasses := Max(Subpasses, ListItem.View[I].GetRenderPassCount); + end; +end; + +function TListViewBase.GetFinalItemSpaces(const ForceIncludeScrollBar: Boolean): TRectF; +begin + Result := FItemSpaces.Rect; + + // ZuBy *** + if FHorizontal then + begin + if (FScrollBar <> nil) and (not HasTouchTracking) and + (ForceIncludeScrollBar or FScrollBar.Visible) then + Result.Bottom := Result.Bottom - FScrollBar.Height; + + // fix SearchVisible + if (FSearchEdit <> nil) and FSearchEdit.Visible and not HasSearchAsItem then + Result.Top := Result.Top + FSearchEdit.Height; + end + else + begin + if (FScrollBar <> nil) and (not HasTouchTracking) and + (ForceIncludeScrollBar or FScrollBar.Visible) then + Result.Right := Result.Right + FScrollBar.Width; + end; + // *** ZuBy +end; + +function TListViewBase.GetFinalItemSize(const ForceIncludeScrollBar: Boolean): TSizeF; +var + FinalItemSpaces: TRectF; +begin + FinalItemSpaces := GetFinalItemSpaces(ForceIncludeScrollBar); + Result := TSizeF.Create(Width - FinalItemSpaces.Left - FinalItemSpaces.Right, + Height - FinalItemSpaces.Top - FinalItemSpaces.Bottom); +end; + +function TListViewBase.GetItemRelRect(const Index: Integer; const LocRect: TRectF; + const SideSpace: Integer = 0): TRectF; +begin + // ZuBy *** + if FHorizontal then + begin + Result := RectF(LocRect.Left + FSideSpace + FHeightSums[Index] - + FScrollViewPos, LocRect.Top + FSideSpace + SideSpace, + GetItemHeight(Index) - FSideSpace, + LocRect.Height - ((SideSpace + FSideSpace) * 2)); + + if (FScrollBar <> nil) and (not HasTouchTracking) and FScrollBar.Visible + then + Result.Bottom := Result.Bottom - FScrollBar.Height; + + // fix SearchVisible + if (FSearchEdit <> nil) and FSearchEdit.Visible and not HasSearchAsItem then + Result.Top := Result.Top + FSearchEdit.Height; + end + else + begin + Result := RectF(LocRect.Left + FSideSpace + SideSpace, + FItemBottomOffset + LocRect.Top + FSideSpace + FHeightSums[Index] - + FScrollViewPos, LocRect.Width - ((SideSpace + FSideSpace) * 2), + GetItemHeight(Index) - FItemBottomOffset); + + if (FScrollBar <> nil) and (not HasTouchTracking) and FScrollBar.Visible + then + Result.Right := Result.Right - FScrollBar.Width; + end; + // *** ZuBy +end; + +function TListViewBase.GetScene: IScene; +begin + Result := Scene; +end; + +function TListViewBase.GetSelected: TListItem; +begin + if (FItemIndex >= 0) and (FItemIndex < Adapter.Count) then + Result := Adapter[FItemIndex] + else + Result := nil; +end; + +function TListViewBase.GetStyleResources: TListItemStyleResources; +begin + ApplyStyleLookup; + Result := FStyleResources; +end; + +function TListViewBase.StyleResourcesNeedUpdate: Boolean; +begin + Result := FUpdatingStyleResources; +end; + +function TListViewBase.GetItemGroupSeparators(const Index: Integer): Integer; +var + EndIndex: Integer; + Prev, Next: TListItem; +begin + Result := 0; + + EndIndex := Adapter.Count - 1; + if (Index < 0) or (Index > EndIndex) then + exit; + if (Index = 0) and (not FShowFirstSeparator) then + exit; + if (Index = EndIndex) and (not FShowLastSeparator) then + exit; + + Prev := nil; + Next := nil; + if Index > 0 then + Prev := Adapter[Index - 1]; + if Index < EndIndex then + Next := Adapter[Index + 1]; + + if (Prev <> nil) and (Next <> nil) and (Prev.Count > 0) and (Next.Count > 0) then + exit; + + if (Index = 0) or ((Prev.Count < 1) and (Prev.Purpose = TListItemPurpose.None)) then + Result := Result or ItemSeparatorTop; + + if (Index >= EndIndex) or ((Next.Count < 1) and (Next.Purpose = TListItemPurpose.None)) then + Result := Result or ItemSeparatorBottom; +end; + +function TListViewBase.GetItemHeight(const Index: Integer): Integer; +begin + if (Index < 0) or (Index >= Adapter.Count) then + Result := 0 + else + begin + Result := Adapter[Index].Height; + if Result < 1 then + Result := Adapter.GetDefaultViewHeight; + end; +end; + +function TListViewBase.GetSeparatorLineHeight: Single; +begin + if FScrollScale > TEpsilon.Scale then + Result := 1 / FScrollScale + else + Result := 1; + + if FScrollScale >= 2 then + Result := Result * 2; +end; + +function TListViewBase.AlignValueToPixel(const Value: Single): Single; +begin + if FScrollScale > TEpsilon.Scale then + Result := Int(Value * FScrollScale) / FScrollScale + else + Result := Value; +end; + +procedure TListViewBase.DrawItemsFill(const StartItem, EndItem: Integer; const LocRect: TRectF; const Opacity: Single; + const HeaderIndex: Integer); +var + I, Sep, AltIndex: Integer; + DrawRect, DrawSubRect, SepRect: TRectF; + ListItem: TListItem; + SepHeight: Single; + HeaderBefore: Boolean; + DrawPanel: TRectF; + J, RowColumns: Integer; +begin + SepHeight := GetSeparatorLineHeight; + + for I := StartItem to EndItem do + if I <> HeaderIndex then + begin + ListItem := Adapter[I]; + HeaderBefore := (I > 0) and (Adapter[I - 1].Purpose <> TListItemPurpose.None); + if (ListItem <> nil) and ((ListItem.Count > 0) or HeaderBefore) then + begin + DrawRect := GetItemRelRect(I, LocRect); + + if ListItem.Purpose = TListItemPurpose.None then + begin + // ZuBy *** + if FItemBoxLight <> nil then + begin + if FAutoColumns then + begin + RowColumns := Min(TListViewItem(ListItem).Tag, FColumns); + for J := 1 to RowColumns { FColumns } do + begin + DrawPanel := RectF(((J * FColumnWidth) - FColumnWidth) + + (FMarg * J), DrawRect.Top, FColumnWidth, DrawRect.Height); + FItemBoxLight.DrawToCanvas(Canvas, DrawPanel, Opacity); + end; + end + else + FItemBoxLight.DrawToCanvas(Canvas, DrawRect, Opacity); + end + else + begin + // ZuBy *** + if not FTransparentItems then + begin + FBrush.Color := FItemStyleFillColor; + + if ListItem.HeaderRef <> -1 then + AltIndex := Max((I - ListItem.HeaderRef) - 1, 0) + else + AltIndex := I; + + if FAlternatingColors and (AltIndex mod 2 = 1) then + FBrush.Color := FItemStyleFillAltColor; + + // ZuBy *** + if TListViewItem(ListItem).Data['aUseCustomColor'].AsBoolean then + FBrush.Color := TListViewItem(ListItem).Data['aCustomColor'] + .AsInteger; + // *** ZuBy + end + else + FBrush.Kind := TBrushKind.None; + Canvas.FillRect(DrawRect, 0, 0, AllCorners, Opacity, FBrush); + FBrush.Kind := FBrush.DefaultKind; + // *** ZuBy + end; + // *** ZuBy + end; + + Sep := GetItemGroupSeparators(I); + // ZuBy *** + if not FTransparentSeparator then + FBrush.Color := FItemStyleFrameColor + else + FBrush.Kind := TBrushKind.None; + // *** ZuBy + + if (Sep and ItemSeparatorTop > 0) and (ListItem.Purpose = TListItemPurpose.None) then + begin + // ZuBy *** + if FHorizontal then + begin + SepRect.Left := AlignValueToPixel(DrawRect.Left); + SepRect.Right := SepRect.Right + 1; + SepRect.Top := DrawRect.Top + FSeparatorLeftOffset; + SepRect.Bottom := DrawRect.Bottom - FSeparatorRightOffset; + end + else + begin + SepRect.Left := DrawRect.Left + FSeparatorLeftOffset; + SepRect.Right := DrawRect.Right - FSeparatorRightOffset; + SepRect.Top := AlignValueToPixel(DrawRect.Top); + SepRect.Bottom := SepRect.Top + SepHeight; + end; + // *** ZuBy + + Canvas.FillRect(SepRect, 0, 0, AllCorners, Opacity, FBrush); + FBrush.Kind := FBrush.DefaultKind; + end; + end; + + if (not((I >= Adapter.Count - 1) and (not FShowLastSeparator))) and + (ListItem.Purpose = TListItemPurpose.None) and + ((I >= Adapter.Count - 1) or + (Adapter[I + 1].Purpose = TListItemPurpose.None)) then + begin + // ZuBy *** + if not FTransparentSeparator then + FBrush.Color := FItemStyleFrameColor + else + FBrush.Kind := TBrushKind.None; + // *** ZuBy + + // ZuBy *** + if FHorizontal then + begin + SepRect.Left := AlignValueToPixel(DrawRect.Left); + SepRect.Right := SepRect.Left + 1; + SepRect.Top := (DrawRect.Top - 1) + FSeparatorLeftOffset; + SepRect.Bottom := (DrawRect.Bottom + 1) - FSeparatorRightOffset; + end + else + begin + SepRect.Left := (DrawRect.Left - 1) + FSeparatorLeftOffset; + SepRect.Right := (DrawRect.Right + 1) - FSeparatorRightOffset; + SepRect.Top := AlignValueToPixel(DrawRect.Bottom - SepHeight); + SepRect.Bottom := SepRect.Top + SepHeight; + end; + // *** ZuBy + + Canvas.FillRect(SepRect, 0, 0, AllCorners, Opacity, FBrush); + FBrush.Kind := FBrush.DefaultKind; + end; + end; + + for I := StartItem to EndItem do + if I <> HeaderIndex then + begin + ListItem := Adapter[I]; + HeaderBefore := (I > 0) and + (Adapter[I - 1].Purpose <> TListItemPurpose.None); + + if (ListItem <> nil) and ((ListItem.Count > 0) or HeaderBefore) then + begin + DrawRect := GetItemRelRect(I, LocRect); + + if ListItem.Purpose <> TListItemPurpose.None then + begin + DrawSubRect := DrawRect; + + if I = 0 then + DrawSubRect.Top := DrawSubRect.Top + 1; + + // ZuBy *** + if not FTransparentHeaders then + begin + if FHeaderStyleImage <> nil then + FHeaderStyleImage.DrawToCanvas(Canvas, DrawSubRect, Opacity) + else + begin + FBrush.Color := FHeaderStyleColor; + Canvas.FillRect(DrawSubRect, 0, 0, AllCorners, Opacity, FBrush); + end; + end + else + FBrush.Kind := TBrushKind.None; + // *** ZuBy + end; + end; + end; +end; + +procedure TListViewBase.DrawIndexFill(const AIndex: Integer; const LocRect: TRectF; const Opacity: Single); +var + DrawRect, DrawPanel: TRectF; + SepHeight, pW: Single; + J, RowColumns: Integer; +begin + DrawRect := GetItemRelRect(AIndex, LocRect); + SepHeight := GetSeparatorLineHeight; + + if (AIndex >= Adapter.Count - 1) or + (Adapter[AIndex + 1].Purpose = TListItemPurpose.None) then + DrawRect.Bottom := DrawRect.Bottom - SepHeight; + +{$IFDEF MSWINDOWS} + // The selection seems to be broken on Windows (looks ugly, needs fixing). Meanwhile, attempt a temporal fix. + DrawRect.Inflate(-2, -2); +{$ENDIF} + if FSelectionStyleImage <> nil then + begin + DrawRect.Top := AlignValueToPixel(DrawRect.Top - SepHeight) + SepHeight; + DrawRect.Bottom := AlignValueToPixel(DrawRect.Bottom) + 2; + // ZuBy *** + if FItemBoxLight <> nil then + DrawRect.Top := DrawRect.Top + 1; + DrawRect.Bottom := DrawRect.Bottom - 2; + if FAutoColumns then + begin + RowColumns := Min(TListViewItem(Adapter[AIndex]).Tag, FColumns); + for J := 1 to RowColumns do + begin + pW := (J * FColumnWidth) - FColumnWidth; + if InRange(FClickEventMousePos.X, (pW + (J * FMarg)), + (pW + FColumnWidth) + (J * FMarg)) then + break; + end; + + if (J > RowColumns) then + exit; + + DrawPanel := RectF(((J * FColumnWidth) - FColumnWidth) + (FMarg * J), + DrawRect.Top, FColumnWidth, DrawRect.Height); + FSelectionStyleImage.DrawToCanvas(Canvas, DrawPanel, Opacity); + end + else + FSelectionStyleImage.DrawToCanvas(Canvas, DrawRect, Opacity); + // *** ZuBy + end + else + begin + FBrush.Color := FSelectionStyleColor; + // sinuke *** + DrawRect.Top := AlignValueToPixel(DrawRect.Top - SepHeight) + SepHeight; + DrawRect.Bottom := AlignValueToPixel(DrawRect.Bottom) + 2; + + if FItemBoxLight <> nil then + DrawRect.Top := DrawRect.Top + 1; + DrawRect.Bottom := DrawRect.Bottom - 2; + + if FAutoColumns then + begin + RowColumns := Min(TListViewItem(Adapter[AIndex]).Tag, FColumns); + for J := 1 to RowColumns do + begin + pW := (J * FColumnWidth) - FColumnWidth; + if InRange(FClickEventMousePos.X, (pW + (J * FMarg)), + (pW + FColumnWidth) + (J * FMarg)) then + break; + end; + + if (J > RowColumns) then + exit; + + DrawPanel := RectF(((J * FColumnWidth) - FColumnWidth) + (FMarg * J), + DrawRect.Top, FColumnWidth, DrawRect.Height); + Canvas.FillRect(DrawPanel, 0, 0, AllCorners, Opacity, FBrush); + end + else + Canvas.FillRect(DrawRect, 0, 0, AllCorners, Opacity, FBrush); + // *** sinuke + end; +end; + +procedure TListViewBase.DrawTouchAnimation(const Index: Integer; const LocRect: TRectF; const Opacity: Single); +var + R: TRectF; +begin + if (FTouchAnimationObject <> nil) and (ItemIndex >= 0) then + begin + R := GetItemRelRect(Index, LocRect); + FTouchAnimationObject.TouchAnimation.DrawTouchAnimation(Canvas, R); + end; +end; + +function TListViewBase.GetHeaderRelRect(const StartItem, HeaderIndex: Integer; const LocRect: TRectF; + const SideSpace: Integer): TRectF; +var + LimitRect: TRectF; +begin + Result := GetItemRelRect(HeaderIndex, LocRect, SideSpace); + if Result.Top < LocRect.Top then + Result.Offset(0, LocRect.Top - Result.Top); + + if (StartItem < Adapter.Count - 1) and (Adapter[StartItem + 1].HeaderRef <> HeaderIndex) then + begin + LimitRect := GetItemRelRect(StartItem, LocRect, SideSpace); + + if Result.Bottom > LimitRect.Bottom then + Result.Offset(0, LimitRect.Bottom - Result.Bottom); + end; +end; + +procedure TListViewBase.DrawHeaderItem(const LocRect: TRectF; const StartItem, HeaderIndex: Integer; + const Opacity: Single); +begin + // ZuBy *** + if FHeaderStyleImage <> nil then + FHeaderStyleImage.DrawToCanvas(Canvas, GetHeaderRelRect(StartItem, + HeaderIndex, LocRect), Opacity) + else + begin + FBrush.Color := FHeaderStyleColor; + Canvas.FillRect(GetHeaderRelRect(StartItem, HeaderIndex, LocRect), 0, 0, + AllCorners, Opacity, FBrush); + end; + // *** ZuBy +end; + +function TListViewBase.GetItemClientRect(const Index: Integer): TRectF; +var + MarginSize: TPointF; + FinalItemSpaces: TRectF; +begin + FinalItemSpaces := GetFinalItemSpaces(False); + + MarginSize.X := FinalItemSpaces.Left + FinalItemSpaces.Right; + MarginSize.Y := FinalItemSpaces.Top + FinalItemSpaces.Bottom; + + Result.Left := FSideSpace + FinalItemSpaces.Left; + Result.Top := FSideSpace + FinalItemSpaces.Top; + Result.Right := Width - (FSideSpace + FinalItemSpaces.Right); + Result.Bottom := GetItemHeight(Index) - MarginSize.Y; +end; + +function TListViewBase.GetEstimatedItemHeight: Single; +begin + Result := FEstimatedHeights.Item; +end; + +function TListViewBase.GetEstimatedHeaderHeight: Single; +begin + Result := FEstimatedHeights.Header; +end; + +function TListViewBase.GetEstimatedFooterHeight: Single; +begin + Result := FEstimatedHeights.Footer; +end; + +procedure TListViewBase.DrawListItems(const AbsOpacity: Single); + + function GetCleanClipRect: TRectF; + begin + Result := LocalRect; + + if (FSearchEdit <> nil) and FSearchEdit.Visible and not HasSearchAsItem then + Result.Top := Result.Top + FSearchEdit.Height; + end; + +const + DefaultParams: TListItemDrawable.TParams = (AbsoluteOpacity: 1.0; ItemSelectedAlpha: 1.0; + DeletingUnwantedOpacity: 1.0; ParentAbsoluteRect: (Left:0;Top:0;Right:0;Bottom:0); Images: nil); + AnimationDeltaEpsilon = 0.01; +var + I, StartItem, EndItem, MaxHeight, ItemHeaderIndex, SubPassNo: Integer; + VertMarginHeight, SceneScale: Single; + PassNo, NumberOfPasses, TopViewIndex, NumberOfSubPasses: Integer; + MarginSize: TPointF; + BorderRect, RelRect, LocRect, ClipRect: TRectF; + State: TCanvasSaveState; + ListItem: TListItem; + CurDrawable: TListItemDrawable; + DrawStates: TListItemDrawStates; + FinalItemSpaces: TRectF; + NeedPaintPullRefreshStroke, NeedPaintScrollingStretchGlow: Boolean; + PullRefreshStrength: Single; + ItemHeight: Integer; + MaxItemIndex: Integer; + Resources: TListItemStyleResources; + Params: TListItemDrawable.TParams; + Checkable: IListViewCheckProvider; +begin + Adapter.CreateNewViews; + UpdateItemLookups; + + // Precache local rectangle. + LocRect := LocalRect; + ClipRect := LocRect; + + PullRefreshStrength := GetPullRefreshStrength; + Params := DefaultParams; + Params.Images := GetImages; + if GetDeleteModeTransitionAlpha > AnimationDeltaEpsilon then + Params.DeletingUnwantedOpacity := Max(0, 1 - (GetDeleteModeTransitionAlpha * 2)); + Params.ParentAbsoluteRect := AbsoluteRect; + + + if FPullToRefresh and ((PullRefreshStrength > 0) or (FPullRefreshAnimation = TPullRefreshAnimation.Playing)) and + (FListingService <> nil) and (TListingTransitionFeature.PullToRefresh in FListingService.GetTransitionFeatures) then + PaintPullRefreshIndicator(Canvas, PullRefreshStrength, AbsOpacity); + + if FSearchVisible and (FSearchEdit <> nil) and (FListingService <> nil) then + if (FSearchAlwaysOnTop or (not (TListingSearchFeature.AsFirstItem in FListingService.GetSearchFeatures))) and + (TListingSearchFeature.StayOnTop in FListingService.GetSearchFeatures) then + ClipRect.Top := ClipRect.Top + FSearchEdit.Height + else + ClipRect.Top := ClipRect.Top + Max(0, FSearchEdit.Height - FScrollViewPos); + + // Set item clipping. + State := Canvas.SaveState; + Canvas.IntersectClipRect(ClipRect); + + // Calculate item height sums. + // *** ZuBy + if FHorizontal then + MaxHeight := Ceil(LocRect.Width + FScrollViewPos) + else + MaxHeight := Ceil(LocRect.Height + FScrollViewPos); + // ZuBy *** + + // Determine starting and final elements that are currently visible. + TopViewIndex := Trunc(FScrollViewPos - FSideSpace); + MaxItemIndex := Adapter.Count - 1; + StartItem := Min(Max(FindItemAbsoluteAt(TopViewIndex), 0), MaxItemIndex); + EndItem := MaxItemIndex; + + for I := StartItem + 1 to MaxItemIndex - 1 do + if FHeightSums[I + 1] > MaxHeight then + begin + EndItem := I; + break; + end; + + if (FListingService <> nil) and (TListingHeaderBehavior.Sticky in FListingService.GetHeaderBehaviors) then + ItemHeaderIndex := Adapter[StartItem].HeaderRef + else + ItemHeaderIndex := -1; + + FinalItemSpaces := GetFinalItemSpaces(False); + + // *** ZuBy + if FHorizontal then + VertMarginHeight := FinalItemSpaces.Left + FinalItemSpaces.Right + else + VertMarginHeight := FinalItemSpaces.Top + FinalItemSpaces.Bottom; + // ZuBy *** + + if (TAlphaColorRec(FItemStyleFillColor).A > 0) or (FAlternatingColors and + (TAlphaColorRec(FItemStyleFillAltColor).A > 0)) then + begin + if (TAlphaColorRec(FItemStyleFillColor).A >= 255) and (not FAlternatingColors or + (TAlphaColorRec(FItemStyleFillAltColor).A >= 255)) and SameValue(AbsOpacity, 1, TEpsilon.Vector) then + begin + Canvas.Blending := False; + try + DrawItemsFill(StartItem, EndItem, LocRect, 1, ItemHeaderIndex); + finally + Canvas.Blending := True; + end; + end + else + DrawItemsFill(StartItem, EndItem, LocRect, AbsOpacity, ItemHeaderIndex); + end; + + // Draw regular selection + if (not FEditMode) and (FItemIndex >= StartItem) and (FItemIndex <= EndItem) and (FSelectionAlphas.Count < 1) and + CanDisplaySelectionForItem(FItemIndex) then + DrawIndexFill(FItemIndex, LocRect, AbsOpacity); + + // Edit mode has current item and selected items + if FEditMode then + begin + // Highlight current item (e.g. for keyboard navigation) + if (FItemIndex >= StartItem) and (FItemIndex <= EndItem) then + DrawIndexFill(FItemIndex, LocRect, AbsOpacity * 0.5); + + // Highlight items with checkboxes + if Supports(Adapter, IListViewCheckProvider, Checkable) then + for I := StartItem to EndItem do + if Checkable.Checked[I] then + DrawIndexFill(I, LocRect, AbsOpacity * GetItemSelectionAlpha(I)); + end; + + MarginSize.X := FinalItemSpaces.Left + FinalItemSpaces.Right; + MarginSize.Y := FinalItemSpaces.Top + FinalItemSpaces.Bottom; + + BorderRect.Left := LocRect.Left + FSideSpace + FinalItemSpaces.Left; + BorderRect.Top := LocRect.Top + FSideSpace + FinalItemSpaces.Top; + // ZuBy *** + if FHorizontal then + begin + BorderRect.Right := BorderRect.Left + Adapter.GetDefaultViewHeight - + MarginSize.X; + BorderRect.Bottom := LocRect.Bottom + (FSideSpace + FinalItemSpaces.Bottom); + end + else + begin + BorderRect.Right := LocRect.Right - (FSideSpace + FinalItemSpaces.Right); + BorderRect.Bottom := BorderRect.Top + Adapter.GetDefaultViewHeight - + MarginSize.Y; + end; + // *** ZuBy + + SceneScale := Canvas.Scale; + + GetNumberOfRenderingPasses(StartItem, EndItem, NumberOfPasses, NumberOfSubPasses); + + DrawTouchAnimation(ItemIndex, LocRect, AbsOpacity); + + Resources := GetStyleResources; + + for PassNo := 0 to NumberOfPasses - 1 do + for SubPassNo := 0 to NumberOfSubPasses - 1 do + for I := StartItem to EndItem do + if I <> ItemHeaderIndex then + begin + ListItem := Adapter[I]; + + if (ListItem <> nil) and (ListItem.Count > PassNo) then + begin + CurDrawable := ListItem.View[PassNo]; + if (CurDrawable = nil) or (not CurDrawable.Visible) then + Continue; + + DrawStates := []; + + if (FDeleteButtonIndex = I) or (FPrevDeleteButtonIndex = I) then + Include(DrawStates, TListItemDrawState.Deleting); + + if CanDisplaySelectionForItem(I, ListItem, True, True) and (GetItemSelectionAlpha(I) > TEpsilon.Vector) then + Include(DrawStates, TListItemDrawState.Selected); + + if (FEditModeTransitionAlpha > 0) and (ListItem.Purpose = TListItemPurpose.None) then + Include(DrawStates, TListItemDrawState.EditMode); + + // During the first sub-pass the designated areas are calculated. + if SubPassNo = 0 then + begin + if I < MaxItemIndex then + ItemHeight := FHeightSums[I + 1] - FHeightSums[I] + else + ItemHeight := GetItemHeight(I); + ListItem.WillBePainted; + CurDrawable.UpdateValuesFromResources(GetStyleResources, ListItem.Purpose); + + // *** ZuBy + if FHorizontal then + CurDrawable.CalculateLocalRect + ((TRectF.Create(TPointF.Create(BorderRect.Left + FHeightSums + [I] - FScrollViewPos, BorderRect.Top), + ItemHeight - VertMarginHeight, BorderRect.Height)), + SceneScale, DrawStates, ListItem) + else // *** ZuBy + CurDrawable.CalculateLocalRect + (TRectF.Create(TPointF.Create(BorderRect.Left, + BorderRect.Top + FHeightSums[I] - FScrollViewPos), + BorderRect.Width, ItemHeight - VertMarginHeight), SceneScale, + DrawStates, ListItem); + end; + // *** ZuBy + + Params.AbsoluteOpacity := AbsoluteOpacity * CurDrawable.Opacity; + Params.ItemSelectedAlpha := GetItemSelectionAlpha(ListItem.Index); + CurDrawable.Render(Canvas, I, DrawStates, Resources, Params, + SubPassNo); + end; + end; + + if ItemHeaderIndex <> -1 then + begin + DrawHeaderItem(LocRect, StartItem, ItemHeaderIndex, AbsOpacity); + + for PassNo := 0 to NumberOfPasses - 1 do + for SubPassNo := 0 to NumberOfSubPasses - 1 do + begin + ListItem := Adapter[ItemHeaderIndex]; + + if (ListItem <> nil) and (ListItem.Count > PassNo) then + begin + CurDrawable := ListItem.View[PassNo]; + if (CurDrawable = nil) or (not CurDrawable.Visible) then + Continue; + + // During the first sub-pass the designated areas are calculated. + if SubPassNo = 0 then + begin + RelRect := GetHeaderRelRect(StartItem, ItemHeaderIndex, LocRect); + + // *** ZuBy + if FHorizontal then + begin + RelRect.Top := BorderRect.Top; + RelRect.Bottom := BorderRect.Bottom; + end + else + begin + RelRect.Left := BorderRect.Left; + RelRect.Right := BorderRect.Right; + end; // ZuBy *** + CurDrawable.UpdateValuesFromResources(GetStyleResources, + ListItem.Purpose); + CurDrawable.CalculateLocalRect(RelRect, SceneScale, [], ListItem); + end; + + Params.AbsoluteOpacity := AbsoluteOpacity * CurDrawable.Opacity; + Params.ItemSelectedAlpha := GetItemSelectionAlpha(ListItem.Index); + CurDrawable.Render(Canvas, ItemHeaderIndex, [], Resources, Params, SubPassNo); + end; + end; + end; + + // Restore previous clipping rectangle. + Canvas.RestoreState(State); + + if FPullToRefresh then + begin + NeedPaintScrollingStretchGlow := HasScrollingStretchGlow and (Abs(FScrollStretchStrength) > 0); + NeedPaintPullRefreshStroke := HasPullRefreshStroke; + + if NeedPaintScrollingStretchGlow or NeedPaintPullRefreshStroke then + begin + State := Canvas.SaveState; + try + Canvas.IntersectClipRect(GetCleanClipRect); + + if NeedPaintScrollingStretchGlow then + PaintScrollingStretchGlow(Canvas, FScrollStretchStrength, AbsOpacity); + + if NeedPaintPullRefreshStroke then + PaintPullRefreshStroke(Canvas, PullRefreshStrength, AbsOpacity); + finally + Canvas.RestoreState(State); + end; + end; + end; + + TriggerIncidents(TDelayedIncident.ChangeRepainted); +end; + +procedure TListViewBase.Paint; +var + LOpacity: Single; +begin + if not (TStateFlag.Painting in FStateFlags) then + begin + Include(FStateFlags, TStateFlag.Painting); + try + FScrollScale := Canvas.Scale; + LOpacity := GetAbsoluteOpacity; + if not FTransparent then + begin + FBrush.Color := FBackgroundStyleColor; + if SameValue(LOpacity, 1, TEpsilon.Vector) then + begin + Canvas.Blending := False; + try + Canvas.FillRect(LocalRect, 0, 0, AllCorners, 1, FBrush); + finally + Canvas.Blending := True; + end; + end + else + Canvas.FillRect(LocalRect, 0, 0, AllCorners, LOpacity, FBrush); + end; + if Adapter.Count > 0 then + DrawListItems(LOpacity); + finally + Exclude(FStateFlags, TStateFlag.Painting); + end; + end; +end; + +procedure TListViewBase.AfterPaint; +begin + inherited; + Exclude(FStateFlags, TStateFlag.Invalid); +end; + +procedure TListViewBase.RecreateNativePresentation; +begin +end; + +procedure TListViewBase.Loaded; +begin + inherited; + ImagesChanged; + RecreateNativePresentation; +end; + +procedure TListViewBase.Resize; +begin + inherited; + BeginUpdate; + try + Adapter.ResetViews([]); + if FAutoColumns then + begin + FColumns := floor(Width / FColumnWidth); +{$IFDEF MSWINDOWS} // *** sinuke + if FScrollBar.Visible then + FMarg := Trunc(((Width - FScrollBar.Width) - (FColumns * FColumnWidth)) / (FColumns + 1)) + else + FMarg := Trunc((Width - (FColumns * FColumnWidth)) / (FColumns + 1)); +{$ELSE} + FMarg := Trunc((Width - (FColumns * FColumnWidth)) / (FColumns + 1)); +{$ENDIF} // *** sinuke + end; + finally + EndUpdate; + end; + UpdateScrollingLimits; +end; + +procedure TListViewBase.EndUpdate; +begin + inherited; + if not IsUpdating then + begin + if TStateFlag.NeedsScrollingLimitsUpdate in FStateFlags then + UpdateScrollingLimits; + if TStateFlag.NeedsRebuild in FStateFlags then + RebuildList; + FStateFlags := FStateFlags - [TStateFlag.NeedsScrollingLimitsUpdate, TStateFlag.NeedsRebuild]; + end; +end; + +function TListViewBase.ObjectAtPoint(P: TPointF): IControl; +var + LocalPt: TPointF; + ItemAt: Integer; + Control: TControl; + ListItem: TListItem; +begin + if not FMouseClicked then + begin + LocalPt := ScreenToLocal(P); + // *** ZuBy + if FHorizontal then + ItemAt := FindItemAbsoluteAt + (Round(FScrollViewPos + LocalPt.X - (LocalRect.Left + FSideSpace))) + else + ItemAt := FindItemAbsoluteAt + (Round(FScrollViewPos + LocalPt.Y - (LocalRect.Top + FSideSpace))); + // ZuBy *** + if (ItemAt >= 0) and (ItemAt < Adapter.Count) then + begin + ListItem := Adapter[ItemAt]; + if ListItem.View.Initialized then + begin + Control := ListItem.ObjectAtPoint(P); + if Control <> nil then + exit(Control); + end; + end; + end; + Result := inherited ObjectAtPoint(P); +end; + +function TListViewBase.FindLocalItemObjectAtPosition(const ItemIndex: Integer; const Position: TPointF): TListItemDrawable; +var + I: Integer; + Item: TListItem; +begin + if (ItemIndex < 0) or (ItemIndex >= Adapter.Count) then + exit(nil); + + Item := Adapter[ItemIndex]; + + for I := 0 to Item.Count - 1 do + if Item.View[I].InLocalRect(Position) then + exit(Item.View[I]); + + Result := nil; +end; + +procedure TListViewBase.KeyDown(var Key: Word; var KeyChar: System.WideChar; Shift: TShiftState); +var + LFirstVisible, LLastVisible: Integer; + LItemIndex: Integer; + + procedure CalcVisible; + var + I: Integer; + MaxHeight: Integer; + TopViewIndex: Integer; + LocRect: TRectF; + begin + // Precache local rectangle. + LocRect := LocalRect; + + // Calculate item height sums. + // *** ZuBy + if FHorizontal then + MaxHeight := Ceil(LocRect.Width + FScrollViewPos) + else + MaxHeight := Ceil(LocRect.Height + FScrollViewPos); + // ZuBy *** + + // Determine starting and final elements that are currently visible. + TopViewIndex := Trunc(FScrollViewPos - FSideSpace); + + LFirstVisible := Min(Max(FindItemAbsoluteAt(TopViewIndex), 0), Adapter.Count - 1); + LLastVisible := Adapter.Count - 1; + + for I := LFirstVisible + 1 to Adapter.Count - 2 do + if FHeightSums[I + 1] >= MaxHeight then + begin + LLastVisible := I; + break; + end; + end; + + procedure CycleNewIndexUp; + begin + // Cycle through items up until non-header and non-footer is found. + repeat + LItemIndex := Max(LItemIndex - 1, 0); + until (LItemIndex <= 0) or (Adapter[LItemIndex].Purpose = TListItemPurpose.None); + end; + + procedure CycleNewIndexDown; + begin + // Cycle through items down until non-header and non-footer is found. + repeat + LItemIndex := Min(LItemIndex + 1, Adapter.Count - 1); + until (LItemIndex >= ItemCount - 1) or (Adapter[LItemIndex].Purpose = TListItemPurpose.None); + end; + +var + I: Integer; + LChanged: Boolean; + TextProvider: IListViewTextProvider; +begin + LItemIndex := ItemIndex; + if FAllowSelection then + if Observers.IsObserving(TObserverMapping.EditLinkID) then + if (KeyChar > ' ') or (Key in [vkHome, vkEnd, vkUp, vkDown, vkRight, vkLeft]) then + if TLinkObservers.EditLinkIsReadOnly(Observers) then + exit + else if not TLinkObservers.EditLinkEdit(Observers) then + exit; + inherited; + if ItemCount > 0 then + begin + if KeyChar <> #0 then + begin + if Supports(Adapter, IListViewTextProvider, TextProvider) then + for I := 0 to ItemCount - 1 do + if (TextProvider.Text[I] <> '') and (string(TextProvider.Text[I].Chars[0]).ToLower = string(KeyChar).ToLower) then + begin + LItemIndex := I; + break; + end; + + if KeyChar = #32 then + Key := vkSpace; + + KeyChar := #0; + end; + case Key of + vkHome: + begin + LItemIndex := 0; + + if Adapter[LItemIndex].Purpose <> TListItemPurpose.None then + CycleNewIndexDown; + end; + + vkEnd: + begin + LItemIndex := Adapter.Count - 1; + + if Adapter[LItemIndex].Purpose <> TListItemPurpose.None then + CycleNewIndexUp; + end; + + vkUp, + vkLeft: + begin + CycleNewIndexUp; + + if Adapter[LItemIndex].Purpose <> TListItemPurpose.None then + CycleNewIndexDown; + end; + + vkDown, + vkRight: + begin + CycleNewIndexDown; + if Adapter[LItemIndex].Purpose <> TListItemPurpose.None then + CycleNewIndexUp; + end; + vkPrior: + begin + CalcVisible; + LItemIndex := Max(0, LFirstVisible - Max(1, LLastVisible - LFirstVisible + 1)); + if Adapter[LItemIndex].Purpose <> TListItemPurpose.None then + CycleNewIndexDown; + end; + + + + vkNext: + begin + CalcVisible; + LItemIndex := Min(Adapter.Count - 1, LLastVisible + Max(1, LLastVisible - LFirstVisible + 1)); + + if Adapter[LItemIndex].Purpose <> TListItemPurpose.None then + CycleNewIndexUp; + end; + + vkSpace: + begin + SetNewItemIndex(FItemIndex); + if (not HasTouchTracking) and (FItemIndex >= 0) and (FItemIndex < Adapter.Count) then + Adapter[FItemIndex].MouseSelect; + end + else + exit; + end; + LChanged := LItemIndex <> ItemIndex; + if LChanged then + TLinkObservers.PositionLinkPosChanging(Observers); // Validation exception during this call + SetItemIndexInternal(LItemIndex, True); + if LChanged then + begin + TLinkObservers.ListSelectionChanged(Observers); + DoChange; + end; + Key := 0; + end; +end; + +procedure TListViewBase.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); + function CancelMode: Boolean; + var + FirstIndex: Integer; + Checkable: IListViewCheckProvider; + GlyphButtonDrawable: TListItemGlyphButton; + begin + Result := False; + + if HasTouchTracking then + begin + if (not FEditMode) and (FDeleteButtonIndex <> -1) then + begin + // hide Delete button + SetDeleteButtonIndex(-1); + SetItemIndexInternal(-1); + FDragListMode := TInternalDragMode.None; + FMouseClicked := False; + FTapSelectItemIndex := -1; + FTapSelectNewIndexApplied := -1; + exit(True); + end + else if Supports(Adapter, IListViewCheckProvider, Checkable) and Checkable.AnyChecked(True) then + begin + FirstIndex := Checkable.FirstChecked(True); + GlyphButtonDrawable := GetGlyphButton(FirstIndex); + if (FirstIndex <> -1) and (not (TStateFlag.ScrollingActive in FStateFlags)) and + (GlyphButtonDrawable <> nil) and (GlyphButtonDrawable.ButtonType in [TGlyphButtonType.Delete]) then + begin + Checkable.CheckAll(False); + SetDeleteButtonIndex(-1); + exit(True); + end + end; + end; + end; + +var + ItemAt: Integer; + IsCheckGlyphVisible: Boolean; + Distance: Single; + GlyphButtonDrawable: TListItemGlyphButton; +begin + inherited; + + if not ShouldHandleEvents then + exit; + + FDragListMode := TInternalDragMode.None; + + if (FAniCalc <> nil) and (TStateFlag.ScrollingActive in FStateFlags) and FAniCalc.Animation then + begin + FAniCalc.Averaging := ssTouch in Shift; + FAniCalc.MouseUp(X, Y); + FAniCalc.Animation := False; + end; + + if (FTransitionType <> TTransitionType.None) or CancelMode then + exit; + + if Button = TMouseButton.mbLeft then + begin + FMouseClicked := True; + FMouseClickIndex := -1; + FMouseEventIndex := -1; + + if HasTouchTracking then + begin + StopPropertyAnimation('ScrollViewPos'); + + // *** ZuBy + if FHorizontal then + Distance := X - LocalRect.Left + else + Distance := Y - LocalRect.Top; + // ZuBy *** + + if (not (TStateFlag.ScrollingActive in FStateFlags)) and FAutoTapScroll and (Distance < FAutoTapTreshold) then + FAutoTapDistance := Round(FScrollViewPos) + else + FAutoTapDistance := 0; + end; + + if not (TStateFlag.ScrollingActive in FStateFlags) then + begin + // ZuBy *** + if FHorizontal then + ItemAt := FindItemAbsoluteAtWithCheck + (Round(FScrollViewPos + X - (LocalRect.Left + FSideSpace))) + else + ItemAt := FindItemAbsoluteAtWithCheck + (Round(FScrollViewPos + Y - (LocalRect.Top + FSideSpace))); + // *** ZuBy + if (ItemAt >= 0) and (ItemAt < Adapter.Count) and + (Adapter[ItemAt].Count > 0) then + begin + if Adapter[ItemAt].MouseDown(Button, Shift, PointF(X, Y)) then + begin + FMouseEventIndex := ItemAt; + + FClickEventMousePos := TPointF.Create(X, Y) - GetItemRelRect(ItemAt, LocalRect).TopLeft; + + if FAllowSelection then + SetNewItemIndex(FMouseEventIndex); + + // ZuBy *** + if FAutoColumns then + Invalidate; + // *** ZuBy + end + else + begin + if Adapter[ItemAt].ObjectAtPoint(TPointF.Create(X, Y)) <> nil then + FMouseClickIndex := ItemAt; + + if (FMouseClickIndex <> -1) and FAllowSelection then + SetNewItemIndex(FMouseClickIndex); + + GlyphButtonDrawable := GetGlyphButton(ItemAt); + IsCheckGlyphVisible := FEditMode and (GlyphButtonDrawable <> nil) and + (GlyphButtonDrawable.ButtonType in [TGlyphButtonType.Checkbox]); + if ((FMouseClickIndex = -1) or IsCheckGlyphVisible) and (FDeleteButtonIndex = -1) then + begin + FTapSelectItemIndex := ItemAt; + FTapSelectNewIndexApplied := -1; + FTapSelectStartTime := FTimerService.GetTick; + + FClickEventMousePos := TPointF.Create(X, Y) - GetItemRelRect(ItemAt, LocalRect).TopLeft; + FClickEventControl := FindLocalItemObjectAtPosition(ItemAt, TPointF.Create(X, Y)); + + if FTouchAnimationObject <> nil then + FTouchAnimationObject.TouchAnimation.StartAnimation(Self, TTouchAnimationAdapter.TAnimationKind.Pressed); + + UpdateRecurrentTimer; + + // ZuBy *** + if FAutoColumns then + Invalidate; + // *** ZuBy + end; + end + end; + end + else + Include(FStateFlags, TStateFlag.ScrollingMouseTouch); + + if (FMouseClickIndex = -1) and (FMouseEventIndex = -1) then + begin + if FAniCalc <> nil then + begin + FAniCalc.Averaging := ssTouch in Shift; + FAniCalc.MouseDown(X, Y); + end; + + FMouseDownAt := TPointF.Create(X, Y); + FMouseClickPrev := FMouseDownAt; + FMouseClickDelta := TPointF.Zero; + FMousePrevScrollPos := FScrollViewPos; + FMouseClickSwipeEventSend := False; // ZuBy + end; + end; +end; + +procedure TListViewBase.MouseMove(Shift: TShiftState; X, Y: Single); +var + NewDeleteIndex: Integer; + Checkable: IListViewCheckProvider; + aMinScroll: Boolean; // ZuBy +begin + inherited; + + if (FTransitionType <> TTransitionType.None) or (not ShouldHandleEvents) then + exit; + + if not Enabled then + begin + FMouseClicked := False; + FTapSelectNewIndexApplied := -1; + FDragListMode := TInternalDragMode.None; + exit; + end; + + if FMouseClicked and (FMouseClickIndex = -1) and (FMouseEventIndex = -1) then + begin + FMouseClickDelta.X := FMouseClickDelta.X + (X - FMouseClickPrev.X); + FMouseClickDelta.Y := FMouseClickDelta.Y + (Y - FMouseClickPrev.Y); + + FMouseClickPrev := TPointF.Create(X, Y); + + if FDragListMode = TInternalDragMode.None then + begin + if HasTouchTracking and (Abs(FMouseClickDelta.X) > MinSwypeThreshold) + and (FCanSwipeDelete or FCanSwipeDirection) and (FTapSelectNewIndexApplied = -1) then + begin + if FCanSwipeDirection then + begin + if Assigned(FOnSwipe) and (not FMouseClickSwipeEventSend) then + begin + FMouseClickSwipeEventSend := True; + if Abs(FMouseDownAt.X) > (X + MinSwypeThreshold) then + FOnSwipe(Self, TSwipeDirection.ToLeft) + else if Abs(FMouseDownAt.X) < (X + MinSwypeThreshold) then + FOnSwipe(Self, TSwipeDirection.ToRight); + end; + end + else + FDragListMode := TInternalDragMode.Swype; + end + else + begin + // ZuBy *** + if FHorizontal then + aMinScroll := Abs(FMouseClickDelta.X) > MinScrollThreshold + else + aMinScroll := Abs(FMouseClickDelta.Y) > MinScrollThreshold; + + if aMinScroll then + begin + FDragListMode := TInternalDragMode.Drag; + + FTapSelectItemIndex := -1; + + if FAniCalc <> nil then + begin + FAniCalc.Averaging := ssTouch in Shift; + FAniCalc.Animation := True; + FAniCalc.MouseDown(FMouseClickPrev.X, FMouseClickPrev.Y); + end; + end; + // *** ZuBy + end; + end; + end; + + if (FTapSelectNewIndexApplied <> -1) and (FDragListMode = TInternalDragMode.Drag) then + begin + if FEditMode and Supports(Adapter, IListViewCheckProvider, Checkable) then + Checkable[FTapSelectNewIndexApplied] := not Checkable[FTapSelectNewIndexApplied]; + + FTapSelectNewIndexApplied := -1; + SetItemIndexInternal(-1); + end; + + if (not FEditMode) and (FDragListMode = TInternalDragMode.Swype) and + (not (TStateFlag.ScrollingMouseTouch in FStateFlags)) and (FDeleteButtonIndex = -1) and HasTouchTracking then + begin + // *** ZuBy + if FHorizontal then + NewDeleteIndex := FindItemAbsoluteAt + (Round(FScrollViewPos + X - (LocalRect.Left + FSideSpace))) + else + NewDeleteIndex := FindItemAbsoluteAt + (Round(FScrollViewPos + Y - (LocalRect.Top + FSideSpace))); + // ZuBy *** + + if (NewDeleteIndex <> -1) and (Adapter[NewDeleteIndex].Purpose <> TListItemPurpose.None) then + NewDeleteIndex := -1; + + if NewDeleteIndex <> -1 then + begin + SetDeleteButtonIndex(NewDeleteIndex); + SetItemIndexInternal(NewDeleteIndex); + FTapSelectItemIndex := -1; + + FDragListMode := TInternalDragMode.None; + FMouseClicked := False; + + if (FAniCalc <> nil) and (TStateFlag.ScrollingActive in FStateFlags) and FAniCalc.Animation then + begin + FAniCalc.Averaging := ssTouch in Shift; + FAniCalc.MouseUp(X, Y); + FAniCalc.Animation := False; + end; + + exit; + end; + end; + + if (FAniCalc <> nil) and FAniCalc.Down and (FDragListMode = TInternalDragMode.Drag) then + FAniCalc.MouseMove(X, Y); + + if (FMouseEventIndex <> -1) and (FDragListMode = TInternalDragMode.Drag) then + Adapter[FMouseEventIndex].MouseMove(Shift, PointF(X, Y)); + + if (FTouchAnimationObject <> nil) and (FAniCalc <> nil) and (TStateFlag.ScrollingActive in FStateFlags) then + FTouchAnimationObject.TouchAnimation.StopAnimation; +end; + +function TListViewBase.GetGlyphButton(const Index: Integer): TListItemGlyphButton; +var + Provider: IListViewGlyphButtonProvider; +begin + Result := nil; + if (Index <> -1) and Supports(Adapter, IListViewGlyphButtonProvider, Provider) then + Result := Provider.GlyphButtonDrawable[Index]; +end; + +procedure TListViewBase.SetNewItemIndex(const NewIndex: Integer); +var + AllowChange: Boolean; + Checkable: IListViewCheckProvider; + GlyphButtonDrawable: TListItemGlyphButton; + OldItemIndex: Integer; +begin + OldItemIndex := ItemIndex; + if NewIndex <> ItemIndex then + begin + AllowChange := True; + ObserversBeforeSelection(AllowChange); + + if AllowChange then + begin + SetItemIndexInternal(NewIndex); + TLinkObservers.ListSelectionChanged(Observers); + if FEditMode and Supports(Adapter, IListViewCheckProvider, Checkable) then + begin + Checkable[NewIndex] := not Checkable[NewIndex]; + GlyphButtonDrawable := GetGlyphButton(NewIndex); + if (not (TStateFlag.ScrollingActive in FStateFlags)) and (GlyphButtonDrawable <> nil) and + (GlyphButtonDrawable.ButtonType in [TGlyphButtonType.Delete]) then + SetDeleteButtonIndex(NewIndex); + end; + end; + end; + + if NewIndex <> -1 then + DoListItemClick(Adapter[NewIndex]); + + if (NewIndex <> OldItemIndex) and AllowChange then + begin + DoChange; + Invalidate; + end; +end; + +procedure TListViewBase.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); +var + NewIndex: Integer; +begin + inherited; + + if ((not FEditMode) and (FDeleteButtonIndex <> -1)) or (not ShouldHandleEvents) then + begin + FMouseClickSwipeEventSend := False; + exit; + end; + + if FTapSelectNewIndexApplied <> -1 then + begin + FMouseClicked := False; + FTapSelectNewIndexApplied := -1; + FDragListMode := TInternalDragMode.None; + if FAniCalc <> nil then + FAniCalc.MouseUp(FMouseDownAt.X, FMouseDownAt.Y); + DoChange; + Invalidate; + end; + + if not Enabled then + begin + FMouseClicked := False; + FTapSelectItemIndex := -1; + FTapSelectNewIndexApplied := -1; + FDragListMode := TInternalDragMode.None; + if FAniCalc <> nil then + FAniCalc.MouseUp(X, Y); + exit; + end; + + if (FAniCalc <> nil) and (TStateFlag.ScrollingActive in FStateFlags) then + FAniCalc.MouseUp(X, Y); + + if (FTouchAnimationObject <> nil) and not ((FAniCalc <> nil) and (TStateFlag.ScrollingActive in FStateFlags)) then + FTouchAnimationObject.TouchAnimation.StartAnimation(Self, TTouchAnimationAdapter.TAnimationKind.Unpressed); + + if FMouseClicked then + begin + FTapSelectItemIndex := -1; + + if not (TStateFlag.ScrollingMouseTouch in FStateFlags) then + begin + if FMouseEventIndex <> -1 then + begin + Adapter[FMouseEventIndex].MouseUp(Button, Shift, TPointF.Create(X, Y)); + FMouseEventIndex := -1; + end + else + begin + if (FAutoTapDistance > 0) and (FTapSelectNewIndexApplied = -1) then + begin + TAnimator.AnimateFloat(Self, 'ScrollViewPos', 0, // do not localize + Min(FAutoTapDistance / AutoTapScrollingSpeed, AutoTapMaxScrollingTime), + TAnimationType.Out, TInterpolationType.Sinusoidal); + FAutoTapDistance := 0; + end + else if (FMouseClickIndex = -1) and (FTapSelectNewIndexApplied = -1) then + begin + if FDragListMode = TInternalDragMode.None then + begin + // *** ZuBy + if FHorizontal then + NewIndex := FindItemAbsoluteAtWithCheck + (Round(FScrollViewPos + X - (LocalRect.Left + FSideSpace))) + else + NewIndex := FindItemAbsoluteAtWithCheck + (Round(FScrollViewPos + Y - (LocalRect.Top + FSideSpace))); + // ZuBy *** + + if (NewIndex >= 0) and (NewIndex < Adapter.Count) and (Adapter[NewIndex].Count > 0) and + (Adapter[NewIndex].Purpose = TListItemPurpose.None) then + begin + if FEditMode or FAllowSelection then + begin + if (FMouseClickIndex = -1) and Adapter[NewIndex].HasClickOnSelectItems then + FItemIndex := -1; + SetNewItemIndex(NewIndex); + end; + + FClickEventItemIndex := NewIndex; + FClickEventMousePos := TPointF.Create(X, Y) - GetItemRelRect(NewIndex, LocalRect).TopLeft; + FClickEventControl := FindLocalItemObjectAtPosition(FClickEventItemIndex, TPointF.Create(X, Y)); + + if not FMouseClickSwipeEventSend then + StartIncident(TDelayedIncident.ClickEvent); + // ZuBy *** + if FAutoColumns then + Invalidate; + // *** ZuBy + end; + end; + end + else + FMouseClickIndex := -1; + end; + end + else + Exclude(FStateFlags, TStateFlag.ScrollingMouseTouch); + + FMouseClicked := False; + FTapSelectNewIndexApplied := -1; + FDragListMode := TInternalDragMode.None; + end; + + FMouseClickSwipeEventSend := False; +end; + +procedure TListViewBase.MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean); +var + Offset: Single; +begin + inherited; + + if not ShouldHandleEvents then + exit; + + if (not Handled) and (not DisableMouseWheel) then + begin + if ssHorizontal in Shift then + begin + // ZuBy *** + if FScrollBar <> nil then + Offset := FScrollBar.SmallChange + else + Offset := Width / 5; + Offset := Offset * -1 * (WheelDelta / 120); + SetScrollViewPos(ScrollViewPos + Offset); + Handled := True; + // *** ZuBy + end + else + begin + if FScrollBar <> nil then + Offset := FScrollBar.SmallChange + else + Offset := Height / 5; + Offset := Offset * -1 * (WheelDelta / 120); + SetScrollViewPos(ScrollViewPos + Offset); + Handled := True; + end + end; +end; + +procedure TListViewBase.DoMouseLeave; +begin + inherited; + + if (FAniCalc <> nil) and ShouldHandleEvents then + FAniCalc.MouseLeave; +end; + +procedure TListViewBase.DoResetEditModeAnimation; +begin + InvalidateHeights; +end; + +procedure TListViewBase.DoSetItemIndexInternal(const Value: Integer); +begin +end; + +procedure TListViewBase.DoSetScrollViewPos(const Value: Single); +begin +end; + +procedure TListViewBase.Invalidate; +begin + if not (TStateFlag.Invalid in FStateFlags) then + begin + InvalidateRect(LocalRect); + Include(FStateFlags, TStateFlag.Invalid); + end; +end; + +procedure TListViewBase.DoChange; +begin + StartIncident(TDelayedIncident.ChangeRepainted, False, ChangeRepaintedIncidentDelay); + + if Assigned(FOnChange) then + FOnChange(Self); +end; + +procedure TListViewBase.DoChangeRepainted; +begin + if Assigned(FOnChangeRepainted) then + FOnChangeRepainted(Self); +end; + +procedure TListViewBase.UpdateItemLookups; +const + ZeroHeights: TEstimatedHeights = (Item: 0; Header: 0; Footer: 0); +var + I, TotalHeight, PrevItemHeight, CurHeaderRef: Integer; + ListItem: TListItem; +begin + if HeightSumsNeedUpdate then + begin + BeginUpdate; + try + TotalHeight := 0; + // ZuBy *** + if not FHorizontal then + begin + TotalHeight := FTopOffset; + if (FSearchEdit <> nil) and FSearchEdit.Visible then + TotalHeight := TotalHeight + Ceil(FSearchEdit.Height); + end; + // *** ZuBy + + FEstimatedHeights := ZeroHeights; + + FHeightSums.Clear; + FHeightSums.Add(TotalHeight); + + CurHeaderRef := -1; + + for I := 0 to Adapter.Count - 1 do + begin + if I > 0 then + begin + PrevItemHeight := GetItemHeight(I - 1) + FItemBottomOffset; + Inc(TotalHeight, PrevItemHeight); + + FHeightSums.Add(TotalHeight); + end; + + ListItem := Adapter[I]; + case ListItem.Purpose of + TListItemPurpose.None: + Adapter[I].HeaderRef := CurHeaderRef; + TListItemPurpose.Header: + begin + FEstimatedHeights.Header := GetItemHeight(I); + CurHeaderRef := I; + Adapter[I].HeaderRef := I; + end; + TListItemPurpose.Footer: + FEstimatedHeights.Footer := GetItemHeight(I); + end; + end; + + // ZuBy *** + if not FHorizontal then + Inc(TotalHeight, FBottomOffset); + // *** ZuBy + + if Adapter.Count > 0 then + begin + Inc(TotalHeight, GetItemHeight(Adapter.Count - 1)); + FEstimatedHeights.Item := TotalHeight / Adapter.Count; + end; + if TotalHeight <> FMaxKnownHeight then + begin + FMaxKnownHeight := TotalHeight; + UpdateScrollingLimits; + end; + + HeightSumsNeedUpdate := False; + finally + EndUpdate; + end; + end; +end; + +function TListViewBase.FindItemAbsoluteAt(const ViewAt: Integer): Integer; +var + Left, Right, Pivot, Value: Integer; +begin + UpdateItemLookups; + + if Adapter.Count < 1 then + exit(-1); + if ViewAt < 1 then + exit(0); + if ViewAt >= FHeightSums[FHeightSums.Count - 1] then + exit(FHeightSums.Count - 1); + + Left := 0; + Right := FHeightSums.Count - 1; + Pivot := 0; + + while Left <= Right do + begin + Pivot := (Left + Right) div 2; + + Value := FHeightSums[Pivot]; + if Value = ViewAt then + exit(Pivot); + + if Value > ViewAt then + Right := Pivot - 1 + else + Left := Pivot + 1; + end; + + Result := Pivot; + + if (Result > 0) and (Result < FHeightSums.Count) and (FHeightSums[Result] >= ViewAt) then + Dec(Result); +end; + +function TListViewBase.FindItemAbsoluteAtWithCheck(const ViewAt: Integer): Integer; +begin + Result := -1; + if Adapter.Count > 0 then + begin + UpdateItemLookups; + if ViewAt < FHeightSums[FHeightSums.Count - 1] + GetItemHeight(Adapter.Count - 1) then + Result := FindItemAbsoluteAt(ViewAt); + end; +end; + +function TListViewBase.GetDefaultStyleLookupName: string; +begin + Result := 'listviewstyle'; +end; + +procedure TListViewBase.ApplyStyle; + + function GetColorFromStyle(const ObjectName: string; const DefaultColor: TAlphaColor): TAlphaColor; + var + StyleObject: TFmxObject; + begin + StyleObject := FindStyleResource(ObjectName); + if StyleObject is TColorObject then + Result := TColorObject(StyleObject).Color + else if StyleObject is TText then + Result := TText(StyleObject).Color + else + Result := DefaultColor; + end; + + procedure AssignFontFromStyle(const Font: TFont; const ObjectName: string); + var + StyleObject: TFmxObject; + begin + StyleObject := FindStyleResource(ObjectName); + if StyleObject is TFontObject then + Font.Assign(TFontObject(StyleObject).Font) + else if StyleObject is TText then + Font.Assign(TText(StyleObject).Font); + end; + + function GetStyleObjectFromStyle(const ObjectName: string): TStyleObject; + var + StyleObject: TStyleObject; + begin + if FindStyleResource(ObjectName, StyleObject) then + Result := StyleObject + else + Result := nil; + end; + + procedure UpdateColorsInItemsObjects; + var + I: Integer; + ListItem: TListItem; + J: Integer; + Drawable: TListItemDrawable; + StyleResources: TListItemStyleResources; + begin + StyleResources := GetStyleResources; + + for I := 0 to Adapter.Count - 1 do + begin + ListItem := Adapter[I]; + for J := 0 to ListItem.View.Count - 1 do + begin + Drawable := ListItem.View.Drawables[J]; + Drawable.UpdateValuesFromStyle; + if ControlType = TControlType.Platform then + Drawable.UpdateValuesFromResources(StyleResources, ListItem.Purpose); + end; + end; + end; + +begin + inherited; + + // Control Colors + FBackgroundStyleColor := GetColorFromStyle('background', claWhite); + + FItemStyleFillColor := GetColorFromStyle('itembackground', claWhite); + FItemStyleFillAltColor := GetColorFromStyle('alternatingitembackground', claWhite); + FItemStyleFrameColor := GetColorFromStyle('frame', claBlack); + + // Item Colors + FStyleResources.DefaultTextColor := GetColorFromStyle('foreground', claBlack); + FStyleResources.DefaultTextSelectedColor := GetColorFromStyle('selectiontext', claBlack); + FStyleResources.DetailTextColor := GetColorFromStyle('detailtext', claBlack); + FStyleResources.HeaderTextColor := GetColorFromStyle('headertext', claWhite); + FStyleResources.HeaderTextShadowColor := GetColorFromStyle('headertextshadow', claWhite); + + FStyleResources.ButtonTextColor := GetColorFromStyle('buttontext', claWhite); + FStyleResources.ButtonTextPressedColor := GetColorFromStyle('buttontextpressed', claBlack); + FStyleResources.DeleteButtonTextColor := GetColorFromStyle('deletebuttontext', claWhite); + FStyleResources.DeleteButtonTextPressedColor := GetColorFromStyle('deletebuttontextpressed', claWhite); + + // Fonts + AssignFontFromStyle(FStyleResources.DefaultTextFont, 'font'); + AssignFontFromStyle(FStyleResources.DetailTextFont, 'detailfont'); + AssignFontFromStyle(FStyleResources.HeaderTextFont, 'headerfont'); + AssignFontFromStyle(FStyleResources.ButtonTextFont, 'buttonfont'); + AssignFontFromStyle(FStyleResources.DeleteButtonTextFont, 'deletebuttonfont'); + + // Style Images + // *** ZuBy + FItemBoxLight := GetStyleObjectFromStyle('boxlight'); + if FItemBoxLight <> nil then + begin + FItemStyleFrameColor := TAlphaColorRec.Null; + TAlphaColorRec(FBackgroundStyleColor).R := 238; + TAlphaColorRec(FBackgroundStyleColor).G := 238; + TAlphaColorRec(FBackgroundStyleColor).B := 238; + TAlphaColorRec(FBackgroundStyleColor).A := 255; + end; + + FSelectionStyleImage := GetStyleObjectFromStyle('boxdark'); + if FSelectionStyleImage = nil then + FSelectionStyleImage := GetStyleObjectFromStyle('selection'); + if FSelectionStyleImage = nil then + FSelectionStyleColor := GetColorFromStyle('selection', claBlue); + + FHeaderStyleImage := GetStyleObjectFromStyle('header'); + if FHeaderStyleImage = nil then + FHeaderStyleColor := claWhite; + + EnableTouchAnimation(True); + // ZuBy *** + + FStyleResources.ButtonAddItemStyleImage.Normal := GetStyleObjectFromStyle('additembutton'); + FStyleResources.ButtonAddItemStyleImage.Pressed := GetStyleObjectFromStyle('additembuttonpressed'); + FStyleResources.ButtonDeleteItemStyleImage.Normal := GetStyleObjectFromStyle('deleteitembutton'); + FStyleResources.ButtonDeleteItemStyleImage.Pressed := GetStyleObjectFromStyle('deleteitembuttonglyph'); + FStyleResources.ButtonNormalStyleImage.Normal := GetStyleObjectFromStyle('button'); + FStyleResources.ButtonNormalStyleImage.Pressed := GetStyleObjectFromStyle('buttonpressed'); + FStyleResources.ButtonDeleteStyleImage.Normal := GetStyleObjectFromStyle('deletebutton'); + FStyleResources.ButtonDeleteStyleImage.Pressed := GetStyleObjectFromStyle('deletebuttonpressed'); + FStyleResources.ButtonCheckboxStyleImage.Normal := GetStyleObjectFromStyle('checkboxunchecked'); + FStyleResources.ButtonCheckboxStyleImage.Pressed := GetStyleObjectFromStyle('checkboxchecked'); + + // Acessory Images + FStyleResources.AccessoryImages[TAccessoryType.More].Normal := GetStyleObjectFromStyle('accessorymore'); + FStyleResources.AccessoryImages[TAccessoryType.More].Selected := GetStyleObjectFromStyle('accessorymoreselected'); + FStyleResources.AccessoryImages[TAccessoryType.Checkmark].Normal := GetStyleObjectFromStyle('accessorycheckmark'); + FStyleResources.AccessoryImages[TAccessoryType.Checkmark].Selected := GetStyleObjectFromStyle('accessorycheckmarkselected'); + FStyleResources.AccessoryImages[TAccessoryType.Detail].Normal := GetStyleObjectFromStyle('accessorydetail'); + FStyleResources.AccessoryImages[TAccessoryType.Detail].Selected := GetStyleObjectFromStyle('accessorydetailselected'); + + FStyleResources.ScrollingStretchGlowColor := GetColorFromStyle('glow', $FF87C3DC); + FStyleResources.PullRefreshIndicatorColor := GetColorFromStyle('indicator', $FF686F7B); + FStyleResources.PullRefreshStrokeColor := GetColorFromStyle('pullrefreshstroke', $FF008CBB); + + UpdateColorsInItemsObjects; + // We don't use active notification model for native presentation and colors are set in presentation construction. + // So we recreate native presentation in this case. + if ControlType = TControlType.Platform then + begin + RecreateNativePresentation; + Invalidate; + end; +end; + +procedure TListViewBase.FreeStyle; +var + AccessoryType: TAccessoryType; +begin + // Control Colors + FBackgroundStyleColor := TAlphaColorRec.Null; + FItemStyleFillColor := TAlphaColorRec.Null; + FItemStyleFillAltColor := TAlphaColorRec.Null; + FItemStyleFrameColor := TAlphaColorRec.Null; + + // Item Colors + FStyleResources.DefaultTextColor := TAlphaColorRec.Null; + FStyleResources.DefaultTextSelectedColor := TAlphaColorRec.Null; + FStyleResources.DetailTextColor := TAlphaColorRec.Null; + FStyleResources.HeaderTextColor := TAlphaColorRec.Null; + FStyleResources.HeaderTextShadowColor := TAlphaColorRec.Null; + + FStyleResources.ButtonTextColor := TAlphaColorRec.Null; + FStyleResources.ButtonTextPressedColor := TAlphaColorRec.Null; + FStyleResources.DeleteButtonTextColor := TAlphaColorRec.Null; + FStyleResources.DeleteButtonTextPressedColor := TAlphaColorRec.Null; + + FStyleResources.ButtonAddItemStyleImage.Normal := nil; + FStyleResources.ButtonAddItemStyleImage.Pressed := nil; + FStyleResources.ButtonDeleteItemStyleImage.Normal := nil; + FStyleResources.ButtonDeleteItemStyleImage.Pressed := nil; + FStyleResources.ButtonNormalStyleImage.Normal := nil; + FStyleResources.ButtonNormalStyleImage.Pressed := nil; + FStyleResources.ButtonDeleteStyleImage.Normal := nil; + FStyleResources.ButtonDeleteStyleImage.Pressed := nil; + FStyleResources.ButtonCheckboxStyleImage.Normal := nil; + FStyleResources.ButtonCheckboxStyleImage.Pressed := nil; + + // Acessory Images + for AccessoryType in [Low(TAccessoryType)..High(TAccessoryType)] do + begin + FStyleResources.AccessoryImages[AccessoryType].Normal := nil; + FStyleResources.AccessoryImages[AccessoryType].Selected := nil; + end; + + FStyleResources.ScrollingStretchGlowColor := TAlphaColorRec.Null; + FStyleResources.PullRefreshIndicatorColor := TAlphaColorRec.Null; + FStyleResources.PullRefreshStrokeColor := TAlphaColorRec.Null; + + // Style Images + FSelectionStyleImage := nil; + FHeaderStyleImage := nil; + + // Touch Animation + if FTouchAnimationObject <> nil then + FTouchAnimationObject.TouchAnimation.CustomPaint := nil; + FTouchAnimationObject := nil; + inherited; +end; + +function TListViewBase.GetItemRect(const AItemIndex: Integer): TRectF; +begin + if (AItemIndex < 0) or (AItemIndex >= Adapter.Count) then + exit(TRectF.Create(0, 0, 0, 0)); + UpdateItemLookups; + Result := GetItemRelRect(AItemIndex, LocalRect); +end; + +procedure TListViewBase.ScrollTo(const AItemIndex: Integer); +var + LocRect, ItemRect: TRectF; + NewPos: Integer; +begin + if (AItemIndex < 0) or (AItemIndex >= Adapter.Count) then + exit; + + UpdateItemLookups; + + LocRect := LocalRect; + + ItemRect := GetItemRelRect(AItemIndex, LocRect); + // ZuBy *** + if FHorizontal then + begin + if ItemRect.Left < LocRect.Left then + begin + NewPos := FSideSpace + FHeightSums[AItemIndex]; + + // Take into account sticky header, so it does not clutter the view. + if HasTouchTracking and (Adapter[AItemIndex].HeaderRef <> -1) and + (Adapter[AItemIndex].HeaderRef <> AItemIndex) then + Dec(NewPos, GetItemHeight(Adapter[AItemIndex].HeaderRef)); + + SetScrollViewPos(NewPos); + end + else if ItemRect.Right > LocRect.Right then + SetScrollViewPos(FSideSpace + FHeightSums[AItemIndex] - + (LocRect.Width - GetItemHeight(AItemIndex))); + end + else + begin + if ItemRect.Top < LocRect.Top then + begin + NewPos := FSideSpace + FHeightSums[AItemIndex]; + + // Take into account sticky header, so it does not clutter the view. + if HasTouchTracking and (Adapter[AItemIndex].HeaderRef <> -1) and + (Adapter[AItemIndex].HeaderRef <> AItemIndex) then + Dec(NewPos, GetItemHeight(Adapter[AItemIndex].HeaderRef)); + + SetScrollViewPos(NewPos); + end + else if ItemRect.Bottom > LocRect.Bottom then + SetScrollViewPos(FSideSpace + FHeightSums[AItemIndex] - + (LocRect.Height - GetItemHeight(AItemIndex))); + end; + // *** ZuBy +end; + +procedure TListViewBase.ImagesChanged; +begin + if ([csLoading, csDestroying] * ComponentState = []) and (FImageLink <> nil) then + Invalidate; +end; + +function TListViewBase.GetImages: TCustomImageList; +begin + if FImageLink <> nil then + Result := TCustomImageList(FImageLink.Images) + else + Result := nil; +end; + +procedure TListViewBase.SetImages(const Value: TCustomImageList); +begin + if FImageLink <> nil then + FImageLink.Images := Value; +end; + +function TListViewBase.GetImageIndex: TImageIndex; +begin + Result := -1; +end; + +procedure TListViewBase.SetImageIndex(const Value: TImageIndex); +begin + // none +end; + +function TListViewBase.GetImageList: TBaseImageList; +begin + Result := GetImages; +end; + +procedure TListViewBase.SetImageList(const Value: TBaseImageList); +begin + ValidateInheritance(Value, TCustomImageList); + SetImages(TCustomImageList(Value)); +end; + +function TListViewBase.FindItemByPosition(X, Y: Single): Integer; +begin + // ZuBy *** + if FHorizontal then + Result := FindItemAbsoluteAtWithCheck + (Round(FScrollViewPos + X - (LocalRect.Left + FSideSpace))) + else + Result := FindItemAbsoluteAtWithCheck + (Round(FScrollViewPos + Y - (LocalRect.Top + FSideSpace))); + // *** ZuBy +end; + +procedure TListViewBase.EnableTouchAnimation(Value: Boolean); +var + TouchAnimation: TCustomStyleObject; +begin + if (Value) and (FTouchAnimationObject <> nil) then + exit; + + if Value then + begin + // Touch Animation + if FindStyleResource('touchanimation', TouchAnimation) + then + begin + Supports(TouchAnimation, ITouchAnimationObject, FTouchAnimationObject); + if FTouchAnimationObject <> nil then + FTouchAnimationObject.TouchAnimation.CustomPaint := Repaint; + end; + end + else + begin + if FTouchAnimationObject <> nil then + FTouchAnimationObject.TouchAnimation.CustomPaint := nil; + FTouchAnimationObject := nil; + end; +end; + +procedure TListViewBase.SearchBoxClear; +begin + if FSearchEdit = nil then + exit; + FSearchEdit.Text := ''; +end; + +procedure TListViewBase.RecalcTopViewItemIndex; // ZuBy +var + TopViewIndex: Integer; +begin + TopViewIndex := Trunc(FScrollViewPos - FSideSpace); + FTopItemIndex := Min(Max(FindItemAbsoluteAt(TopViewIndex), 0), Adapter.Count - 1); +end; + +function TListViewBase.getItemTextButtonHeight(const AItem: TListItemTextButton; const aWidth: Single = 0): Integer; +// ZuBy +var + aItemWidth: Single; +begin + if AItem.Text.IsEmpty then + begin + Result := Ceil(AItem.Height); + exit; + end; + + aItemWidth := 0; + if ShowScrollBar then + aItemWidth := DefaultScrollBarWidth; + + if SameValue(0, aWidth) then + aItemWidth := Width - (aItemWidth + ItemSpaces.Left + ItemSpaces.Right + (SideSpace * 2)) + else + aItemWidth := aWidth; + + Result := Ceil(getTextSize(AItem.Text, AItem.Font, AItem.WordWrap, aItemWidth,0).Height); +end; + +function TListViewBase.getItemTextButtonWidth(const AItem: TListItemTextButton; const aHeight: Single): Integer; +begin + if AItem.Text.IsEmpty then + begin + Result := Ceil(AItem.Height); + exit; + end; + + Result := Ceil(getTextSize(AItem.Text, AItem.Font, AItem.WordWrap, 0, 0).Width); +end; + +function TListViewBase.getItemTextHeight(const AItem: TListItemText; const aWidth: Single = 0): Integer; // ZuBy +var + aItemWidth: Single; +begin + if AItem.Text.IsEmpty then + begin + Result := Ceil(AItem.Height); + exit; + end; + + aItemWidth := 0; + if ShowScrollBar then + aItemWidth := DefaultScrollBarWidth; + + if SameValue(0, aWidth) then + aItemWidth := Width - (aItemWidth + ItemSpaces.Left + ItemSpaces.Right + (SideSpace * 2)) + else + aItemWidth := aWidth; + + Result := Ceil(getTextSize(AItem.Text, AItem.Font, AItem.WordWrap, aItemWidth, 0).Height); + +end; + +function TListViewBase.getItemTextWidth(const AItem: TListItemText; const aHeight: Single): Integer; +begin + if AItem.Text.IsEmpty then + begin + Result := Ceil(AItem.Height); + exit; + end; + + Result := Ceil(getTextSize(AItem.Text, AItem.Font, AItem.WordWrap, 0, 0).Width); +end; + +function TAppearanceListView.getLastVisibleItemindex: Integer; // ZuBy +begin + Result := Min(getFirstVisibleItemIndex + getVisibleCount, TListViewBase(Self).Adapter.Count - 1); +end; + +function TListViewBase.getTextSize(const aText: string; aFont: TFont; const aWordWrap: Boolean; aWidth, aHeight: Single): TSizeF; // ZuBy +var + aMaxSize: TPointF; +begin + LVTextLayout.BeginUpdate; + try + LVTextLayout.Text := aText; + + aMaxSize := PointF(aWidth, aHeight); + if aWidth = 0 then + aMaxSize.X := 9999; + if aHeight = 0 then + aMaxSize.Y := 9999; + + LVTextLayout.MaxSize := aMaxSize; + LVTextLayout.Font.Assign(aFont); + LVTextLayout.Font.Size := aFont.Size; + LVTextLayout.WordWrap := aWordWrap; + LVTextLayout.Trimming := TTextTrimming.None; + LVTextLayout.HorizontalAlign := TTextAlign.Leading; + LVTextLayout.VerticalAlign := TTextAlign.Leading; + finally + LVTextLayout.EndUpdate; + end; + + Result.Height := LVTextLayout.TextHeight; + Result.Width := LVTextLayout.TextWidth; +end; + +function TAppearanceListView.getVisibleCount: Integer; // ZuBy +begin + if FHorizontal then + Result := Round(Width / GetItemAppearanceProperties.Height) + else + Result := Round(Height / FItemAppearanceProperties.Height); +end; + +function TListViewBase.GetScrollWidth: Single; // ZuBy +begin + Result := DefaultScrollBarWidth; +end; + +procedure TListViewBase.SetHorizontal(const Value: Boolean); // ZuBy +begin + if FHorizontal <> Value then + begin + FHorizontal := Value; + RebuildOrientation; + UpdateScrollBar; + end; +end; + +procedure TListViewBase.SetAutoColumns(const Value: Boolean); // ZuBy +begin + if FAutoColumns <> Value then + begin + FAutoColumns := Value; + Resize; + end; +end; + +procedure TListViewBase.SetBottomOffset(const Value: Integer); // ZuBy +begin + if FBottomOffset <> Value then + begin + FBottomOffset := Value; + if not FHorizontal then + DoUpdateScrollViewPos(FScrollViewPos + FBottomOffset); // ??? + InvalidateHeights; + UpdateItemLookups; + Invalidate; + end; +end; + +procedure TListViewBase.SetCanScroll(const Value: Boolean); // sinuke +begin + StopPropertyAnimation('ScrollViewPos'); + FCanScroll := Value; +end; + +function TListViewBase.getHeightByIndex(Index: Integer): Integer; // ZuBy +begin + Result := GetItemHeight(index); +end; + +function TListViewBase.getAniCalc: TAniCalculations; // ZuBy +begin + Result := FAniCalc; +end; + +procedure TListViewBase.SetItemBottomOffset(const Value: Integer); +begin + if FItemBottomOffset <> Value then + begin + FItemBottomOffset := Value; + InvalidateHeights; + UpdateItemLookups; + Invalidate; + end; +end; + +procedure TListViewBase.SetSeparatorLeftOffset(const Value: Single); // ZuBy +begin + if FSeparatorLeftOffset <> Value then + begin + FSeparatorLeftOffset := Value; + InvalidateHeights; + UpdateItemLookups; + Invalidate; + end; +end; + +procedure TListViewBase.SetSeparatorRightOffset(const Value: Single); // ZuBy +begin + if FSeparatorRightOffset <> Value then + begin + FSeparatorRightOffset := Value; + InvalidateHeights; + UpdateItemLookups; + Invalidate; + end; +end; + +procedure TListViewBase.SetShowFirstSeparator(const Value: Boolean); +begin + FShowFirstSeparator := Value; +end; + +procedure TListViewBase.SetShowLastSeparator(const Value: Boolean); +begin + FShowLastSeparator := Value; +end; + +procedure TListViewBase.SetShowScrollBar(const Value: Boolean); // ZuBy +begin + if FShowScrollBar <> Value then + begin + FShowScrollBar := Value; + RebuildOrientation; + UpdateScrollBar; + end; +end; + +procedure TListViewBase.SetTopOffset(const Value: Integer); // ZuBy +begin + if FTopOffset <> Value then + begin + FTopOffset := Value; + if not FHorizontal then + DoUpdateScrollViewPos(FScrollViewPos - FTopOffset); + InvalidateHeights; + UpdateItemLookups; + Invalidate; + end; +end; + +procedure TListViewBase.SetTransparentHeader(const Value: Boolean); +begin + FTransparentHeaders := Value; + Invalidate; +end; + +procedure TListViewBase.SetTransparentItems(const Value: Boolean); +begin + FTransparentItems := Value; + Invalidate; +end; + +procedure TListViewBase.SetTransparentSeparator(const Value: Boolean); +begin + FTransparentSeparator := Value; + Invalidate; +end; + +procedure TListViewBase.SetColumnWidth(const Value: Single); // ZuBy +begin + if not SameValue(FColumnWidth, Value, TEpsilon.Position) then + begin + FColumnWidth := Value; + Resize; + end; +end; + +procedure TListViewBase.RebuildOrientation; // ZuBy +begin + if FHorizontal then + begin + FScrollBar.Orientation := TOrientation.Horizontal; + FScrollBar.Align := TAlignLayout.Bottom; + FScrollBar.Height := DefaultScrollBarWidth; + end + else + begin + FScrollBar.Orientation := TOrientation.Vertical; + FScrollBar.Align := TAlignLayout.Right; + FScrollBar.Width := DefaultScrollBarWidth; + end; + Resize; + FScrollBar.Visible := FShowScrollBar; + UpdateItemLookups; +end; + +{$ENDREGION} +{$REGION 'TPresentedListView'} + +procedure TPresentedListView.BeforeDestruction; +var + PresentationService: IFMXListViewPresentationService; +begin + inherited; + if (csDesigning in ComponentState) and + TPlatformServices.Current.SupportsPlatformService(IFMXListViewPresentationService, PresentationService) then + PresentationService.DetachPresentation(Self); +end; + +destructor TPresentedListView.Destroy; +begin + FPresentation := nil; + inherited; +end; + +procedure TPresentedListView.PMAncesstorPresentationLoaded(var AMessage: TDispatchMessageWithValue); +begin + ExecuteInterlocked(procedure begin + FPresentation.ParentChanged; + end); +end; + +procedure TPresentedListView.RecreateNativePresentation; +var + PresentationService: IFMXListViewPresentationService; + LPresentation: IInterface; +begin + if ((FControlType = TControlType.Platform) or (csDesigning in ComponentState)) and + TPlatformServices.Current.SupportsPlatformService(IFMXListViewPresentationService, PresentationService) then + begin + FPresentation := nil; // Make sure that presentation is purged before it's recreated + LPresentation := PresentationService.AttachPresentation(Self); + if Supports(LPresentation, IListViewPresentation, FPresentation) and (FItemIndex <> -1) and not FEditMode then + FPresentation.SetItemSelected(FItemIndex, True); + end + else + FPresentation := nil; +end; + +procedure TPresentedListView.ChangeOrder; +begin + inherited; + ExecuteInterlocked(procedure begin + FPresentation.OrderChanged; + end); +end; + +procedure TPresentedListView.AncestorVisibleChanged; +begin + inherited; + ExecuteInterlocked(procedure begin + FPresentation.AncestorVisibleChanged(Visible); + end); +end; + +procedure TPresentedListView.RecalcOpacity; +begin + inherited; + ExecuteInterlocked(procedure begin + FPresentation.AncestorVisibleChanged(Visible); + end); +end; + +procedure TPresentedListView.Paint; +begin + if FPresentation = nil then + inherited Paint + else + begin + Adapter.CreateNewViews; + UpdateItemLookups; + end; +end; + +procedure TPresentedListView.PaintChildren; +const + LabelMargins = 3; + LabelPadding = 3; + + function GetOverlayIcon: TBitmap; + var + Service: IPresentedControlBehavior; + begin + if TBehaviorServices.Current.SupportsBehaviorService(IPresentedControlBehavior, Service, Self) then + Result := Service.GetOverlayIcon + else + Result := nil; + end; + + procedure PaintDesignTimeCaption; + const + ControlTypeCaption = 'P'; + var + TextWidth: Double; + TextHeight: Double; + TextRect: TRectF; + begin + TextWidth := Canvas.TextWidth(ControlTypeCaption) + 2 * LabelPadding; + TextHeight := Canvas.TextHeight(ControlTypeCaption) + 2 * LabelPadding; + TextRect := TRectF.Create(TPointF.Create(Width - TextWidth - LabelMargins, Height - TextHeight - LabelMargins), + TextWidth, TextHeight); + Canvas.Fill.Color := TAlphaColorRec.Black; + Canvas.FillRect(TextRect, 3, 3, AllCorners, 0.5); + Canvas.Fill.Color := TAlphaColorRec.White; + Canvas.FillText(TextRect, ControlTypeCaption, False, 1, [], TTextAlign.Center, TTextAlign.Center); + end; + + procedure PaintDesignTimeIcon; + var + Icon: TBitmap; + IconRect: TRectF; + DestRect: TRectF; + begin + Icon := GetOverlayIcon; + if Icon <> nil then + begin + IconRect := TRectF.Create(0, 0, Icon.Width, Icon.Height); + DestRect := TRectF.Create(TPointF.Create(Width - LabelPadding - Icon.Width, Height - LabelPadding - Icon.Height), + Icon.Width, Icon.Height); + Canvas.DrawBitmap(Icon, IconRect, DestRect, 0.5); + end + else + PaintDesignTimeCaption; + end; + +begin + inherited; + if (csDesigning in ComponentState) and not Locked and not FInPaintTo and (ControlType = TControlType.Platform) then + begin + Canvas.SetMatrix(AbsoluteMatrix); + PaintDesignTimeIcon; + end; +end; + +procedure TPresentedListView.ParentChanged; +begin + inherited; + ExecuteInterlocked(procedure begin + RecalcAbsolute; // This call is required here because it called later in TControl.DoAddObject + FPresentation.ParentChanged; + end); +end; + +procedure TPresentedListView.RebuildList; +begin + inherited; + if not FCreatingNativeView then + begin + if (FPresentation <> nil) and not (csLoading in ComponentState) then + if IsUpdating then + Include(FStateFlags, TStateFlag.NeedsRebuild) + else + begin + UpdateItemLookups; + FPresentation.ItemsUpdated; + if FItemIndex <> -1 then + FPresentation.SetItemIndex(FItemIndex); + end; + end; +end; + +procedure TPresentedListView.StopPullRefresh; +begin + if FPresentation <> nil then + FPresentation.StopPullRefresh; +end; + +function TPresentedListView.GetRootObject: TObject; +begin + if Root <> nil then + Result := Root.GetObject + else + Result := nil; +end; + +function TPresentedListView.GetAdapter: IListViewAdapter; +begin + Result := Adapter; +end; + +function TPresentedListView.GetContentFrame: TRect; +begin + Result := GetBoundsRect.Round; +end; + +function TPresentedListView.GetControlOpacity: Single; +begin + Result := AbsoluteOpacity; +end; + +function TPresentedListView.GetItemText(const ItemIndex: Integer): string; +var + Provider: IListViewTextProvider; +begin + if Supports(Adapter, IListViewTextProvider, Provider) then + Result := Provider.Text[ItemIndex] + else + Result := string.Empty; +end; + +function TPresentedListView.GetBackgroundStyleColor: TAlphaColor; +begin + Result := FBackgroundStyleColor; +end; + +function TPresentedListView.GetIsTransparent: Boolean; +begin + Result := FTransparent; +end; + +function TPresentedListView.GetOpacity: Single; +begin + Result := FOpacity; +end; + +function TPresentedListView.GetItemIndexTitle(const ItemIndex: Integer): string; +var + Provider: IListViewTextProvider; +begin + Result := string.Empty; + if Supports(Adapter, IListViewTextProvider, Provider) then + Result := Provider.IndexTitle[ItemIndex]; +end; + +function TPresentedListView.CanSelectItem(const AItemIndex: Integer): Boolean; +begin + Result := True; + if ItemIndex <> AItemIndex then + ObserversBeforeSelection(Result); +end; + +procedure TPresentedListView.DidSelectItem(const AItemIndex: Integer); +begin + ExecuteInterlocked(procedure begin + inherited SelectItem(AItemIndex); + end); + TLinkObservers.ListSelectionChanged(Observers); +end; + +function TPresentedListView.CanUnselectItem(const AItemIndex: Integer): Boolean; +begin + ObserversBeforeSelection(Result); +end; + +procedure TPresentedListView.DidUnselectItem(const AItemIndex: Integer); +begin + ExecuteInterlocked(procedure begin + inherited UnselectItem(AItemIndex); + end); + TLinkObservers.ListSelectionChanged(Observers); +end; + +procedure TPresentedListView.DoDeleteItem(const ItemIndex: Integer); +begin + inherited; + if FPresentation <> nil then + FPresentation.ItemsUpdated; +end; + +procedure TPresentedListView.DoEditModeChange; +begin + inherited; + if FPresentation <> nil then + FPresentation.EditModeChanged; +end; + +procedure TPresentedListView.DoResetEditModeAnimation; +begin + inherited; + if FPresentation <> nil then + FPresentation.EditModeChanged; +end; + +procedure TPresentedListView.DoItemInvalidated(const Item: TListItem); +begin + inherited; + if FPresentation <> nil then + FPresentation.ItemInvalidated(Item); +end; + +procedure TPresentedListView.DoItemsChange; +begin + InvalidateHeights; + inherited; + if FPresentation = nil then + Invalidate; +end; + +procedure TPresentedListView.DoItemsInvalidate; +begin + inherited; + if not FCreatingNativeView then + begin + if IsUpdating then + Include(FStateFlags, TStateFlag.NeedsRebuild) + else + ExecuteInterlocked(procedure begin + UpdateItemLookups; + FPresentation.ItemsUpdated; + end); + end; +end; + +procedure TPresentedListView.DoCheckStateChanged(const AItem: TListItem; const Control: TListItemDrawable); +var + Checkable: IListViewCheckProvider; +begin + if Supports(Adapter, IListViewCheckProvider, Checkable) then + begin + ExecuteInterlocked(procedure begin + FPresentation.SetItemSelected(AItem.Index, Checkable[AItem.Index]); + end); + + if FSelectionCrossfade and (FPresentation = nil) then + InsertItemCrossFade(AItem.Index, Checkable[AItem.Index]); + end; + inherited; +end; + +procedure TPresentedListView.DoSetItemIndexInternal(const Value: Integer); +begin + inherited; + ExecuteInterlocked(procedure begin + FPresentation.SetItemIndex(FItemIndex); + end); +end; + +procedure TPresentedListView.DoSetScrollViewPos(const Value: Single); +begin + inherited; + if FPresentation = nil then + begin + if FAniCalc <> nil then + begin + // ZuBy *** + if FHorizontal then + FAniCalc.ViewportPosition := TPointD.Create(FScrollViewPos, 0) + else + FAniCalc.ViewportPosition := TPointD.Create(0, FScrollViewPos); + // *** ZuBy + end; + if not HasTouchTracking then + UpdateScrollBar; + end; +end; + +procedure TPresentedListView.DoUpdateScrollingLimits; +begin + if FPresentation = nil then + inherited; +end; + +procedure TPresentedListView.DoUpdateScrollViewPos(const Value: Single); +begin + if FPresentation = nil then + Invalidate; +end; + +procedure TPresentedListView.ItemButtonClicked(const ItemIndex: Integer); +var + Provider: IListViewTextButtonProvider; +begin + if Supports(Adapter, IListViewTextButtonProvider, Provider) and (Provider.TextButtonDrawable[ItemIndex] <> nil) then + Provider.TextButtonDrawable[ItemIndex].Click; +end; + +procedure TPresentedListView.InvokePullRefresh; +begin + TThread.Queue(nil, + procedure + begin + if Assigned(FOnPullRefresh) then + FOnPullRefresh(Self); + end); +end; + +function TPresentedListView.HasDesignPresentationAttached: Boolean; +begin + Result := (csDesigning in ComponentState) and (FPresentation <> nil); +end; + +procedure TPresentedListView.SetCreatingNativeView(const Value: Boolean); +begin + FCreatingNativeView := Value; +end; + +procedure TPresentedListView.SetSearchFilter(const Filter: string); +var + LText: string; +begin + LText := Filter.Trim.ToLower; + SetFilterPredicate( + function (X: string): Boolean + begin + Result := LText.IsEmpty or X.ToLower.Contains(LText); + end); +end; + +function TPresentedListView.GetTableViewFlags: TListViewModeFlags; +begin + Result := []; + if FEditMode then + Include(Result, TListViewModeFlag.Edit); + if Enabled then + Include(Result, TListViewModeFlag.Enabled); + if Visible then + Include(Result, TListViewModeFlag.Visible); + if HasDeletionEditMode then + Include(Result, TListViewModeFlag.Deletion); + if FPullToRefresh then + Include(Result, TListViewModeFlag.PullRefresh); + if HasButtonsInCells then + Include(Result, TListViewModeFlag.Buttons); + if FSearchVisible then + Include(Result, TListViewModeFlag.Search); + if FSearchAlwaysOnTop then + Include(Result, TListViewModeFlag.SearchOnTop); + if FPullRefreshWait then + Include(Result, TListViewModeFlag.PullRefreshWait); + if FCanSwipeDelete then + Include(Result, TListViewModeFlag.SwipeDelete); +end; + +function TPresentedListView.GetTableViewOptions: TListViewNativeOptions; +begin + Result := FNativeOptions; +end; + +procedure TPresentedListView.RecalcEnabled; +begin + inherited; + if FPresentation <> nil then + FPresentation.StatusChanged; +end; + +function TPresentedListView.ShouldHandleEvents: Boolean; +begin + Result := FPresentation = nil; +end; + +procedure TPresentedListView.Show; +begin + inherited; + if FPresentation <> nil then + FPresentation.StatusChanged; +end; + +procedure TPresentedListView.Hide; +begin + inherited; + if FPresentation <> nil then + FPresentation.StatusChanged; +end; + +procedure TPresentedListView.Resize; +begin + inherited; + + if FPresentation <> nil then + FPresentation.SizeChanged + else if Adapter.Count > 0 then + FScrollViewPos := Min(FScrollViewPos, GetMaxScrollViewPos); +end; + +procedure TPresentedListView.DoItemsResize; +begin + inherited; + if IsUpdating then + Include(FStateFlags, TStateFlag.NeedsRebuild) + else + ExecuteInterlocked(procedure begin + FPresentation.ItemsUpdated; + end); +end; + +procedure TPresentedListView.DoAbsoluteChanged; +begin + inherited; + if FPresentation <> nil then + FPresentation.ParentChanged; +end; + +procedure TPresentedListView.ExecuteInterlocked(const P: TProc); +begin + if (FPresentation <> nil) and (TInterlocked.CompareExchange(FPresentationLocked, 1, 0) = 0) then + try + P; + finally + TInterlocked.Exchange(FPresentationLocked, 0); + end; +end; + +{$ENDREGION} +{$REGION 'TAppearanceListView'} + +constructor TAppearanceListView.Create(AOwner: TComponent); +begin + inherited; + + FAppearanceProperties := TPublishedAppearance.Create(Self); + FItemAppearanceObjects := TPublishedObjects.Create(Self); + + FItemAppearanceProperties := TItemAppearanceProperties.Create(Self, TAppearanceType.Item); + InitializeItemAppearance(FItemAppearanceProperties); + FItemEditAppearanceProperties := TItemAppearanceProperties.Create(Self, TAppearanceType.ItemEdit); + InitializeItemAppearance(FItemEditAppearanceProperties); + FHeaderAppearanceProperties := TItemAppearanceProperties.Create(Self, TAppearanceType.Header); + InitializeItemAppearance(FHeaderAppearanceProperties); + FFooterAppearanceProperties := TItemAppearanceProperties.Create(Self, TAppearanceType.Footer); + InitializeItemAppearance(FFooterAppearanceProperties); + + // Create our own adapter + Items := TAppearanceListViewItems.Create(Self); + + FTransparentSeparator := False; + FTransparentItems := False; + FAutoPositionToItem := False; + FTopItemIndex := -1; // ZuBy + FSeparatorLeftOffset := 0; // ZuBy + FSeparatorRightOffset := 0; // ZuBy + FMakeSelectedItemVisible := True; // ZuBy + FHorizontal := False; // ZuBy + FShowScrollBar := True; // ZuBy +end; + +destructor TAppearanceListView.Destroy; +begin + FAppearanceProperties.Free; + FreeAndNil(FItemAppearanceObjects); + FItemAppearanceProperties.Free; + FItemEditAppearanceProperties.Free; + FHeaderAppearanceProperties.Free; + FFooterAppearanceProperties.Free; + SetAdapter(nil); + FAppearanceViewItems.Free; + inherited; +end; + +procedure TAppearanceListView.SetAppearanceListViewItems(const AItems: TAppearanceListViewItems); +begin + FAppearanceViewItems := AItems; + FAppearanceViewItems.OnNotify := ObjectsNotify; + SetAdapter(AItems as IListViewAdapter); +end; + +procedure TAppearanceListView.DoAdapterSet; +var + Editor: IListViewEditor; +begin + if FAppearanceViewItems = nil then + raise EListViewError.Create(SUseItemsPropertyToSetAdapter); + if Adapter <> nil then + begin + if Supports(Adapter, IListViewEditor, Editor) then + begin + Editor.OnBeforeItemAdded := EditorBeforeItemAdded; + Editor.OnAfterItemAdded := EditorAfterItemAdded; + Editor.OnBeforeItemDeleted := EditorBeforeItemDeleted; + Editor.OnAfterItemDeleted := EditorAfterItemDeleted; + end; + + if Adapter.Count > 0 then + begin + ItemAppearanceChange(FItemAppearanceProperties); + ItemAppearanceChange(FItemEditAppearanceProperties); + ItemAppearanceChange(FHeaderAppearanceProperties); + ItemAppearanceChange(FFooterAppearanceProperties); + end; + end; + DoChange; +end; + +function TAppearanceListView.GetAppearanceListViewItem(const Index: Integer): TListViewItem; +begin + Result := FAppearanceViewItems.AppearanceItem[Index]; +end; + +function TAppearanceListView.GetHeaderAppearanceName: string; +begin + Result := FHeaderAppearanceProperties.Name; +end; + +function TAppearanceListView.GetHeaderAppearanceProperties: TItemAppearanceProperties; +begin + Result := FHeaderAppearanceProperties; +end; + +function TAppearanceListView.GetHeaderAppearanceClassName: string; +begin + Result := FHeaderAppearanceProperties.AppearanceClassName; +end; + +function TAppearanceListView.GetFooterAppearanceName: string; +begin + Result := FFooterAppearanceProperties.Name; +end; + +function TAppearanceListView.GetFooterAppearanceProperties: TItemAppearanceProperties; +begin + Result := FFooterAppearanceProperties; +end; + +function TAppearanceListView.GetFooterAppearanceClassName: string; +begin + Result := FFooterAppearanceProperties.AppearanceClassName; +end; + +function TAppearanceListView.GetItemAppearanceName: string; +begin + Result := FItemAppearanceProperties.Name; +end; + +function TAppearanceListView.GetItemAppearanceObjects: TPublishedObjects; +begin + Result := FItemAppearanceObjects; +end; + +function TAppearanceListView.GetItemEditAppearanceName: string; +begin + Result := FItemEditAppearanceProperties.Name; +end; + +function TAppearanceListView.GetItemAppearanceProperties: TItemAppearanceProperties; +begin + Result := FItemAppearanceProperties; +end; + +function TAppearanceListView.GetItemEditAppearanceProperties: TItemAppearanceProperties; +begin + Result := FItemEditAppearanceProperties; +end; + +function TAppearanceListView.GetItemObjectsClassName: string; +begin + Result := FItemAppearanceProperties.AppearanceClassName; +end; + +function TAppearanceListView.HasButtonsInCells: Boolean; +begin + Result := ItemAppearance.ItemAppearance = TAppearanceNames.ImageListItemRightButton; +end; + +function TAppearanceListView.HasCheckboxMode: Boolean; +begin + Result := FEditMode and (FAppearanceAllowsCheckboxes or ((not IsDeleteModeAllowed) and HasDeletionEditMode)); +end; + +function TAppearanceListView.HasDeletionEditMode: Boolean; +begin + Result := FAppearanceAllowsDeleteMode; +end; + +procedure TAppearanceListView.SetItemHeight(const Value: Integer); +begin + FItemAppearanceProperties.Height := Value; +end; + +procedure TAppearanceListView.SetItemEditHeight(const Value: Integer); +begin + FItemEditAppearanceProperties.Height := Value; +end; + +procedure TAppearanceListView.SetHeaderHeight(const Value: Integer); +begin + FHeaderAppearanceProperties.Height := Value; +end; + +procedure TAppearanceListView.SetFooterHeight(const Value: Integer); +begin + FFooterAppearanceProperties.Height := Value; +end; + +function TAppearanceListView.GetItemEditObjectsClassName: string; +begin + Result := FItemEditAppearanceProperties.AppearanceClassName; +end; + +procedure TAppearanceListView.SetFooterAppearanceClassName(const Value: string); +begin + FFooterAppearanceProperties.AppearanceClassName := Value; +end; + +procedure TAppearanceListView.SetAppearanceProperties(const Value: TPublishedAppearance); +begin + Assert(False); + // Do nothing +end; + +procedure TAppearanceListView.SetItemAppearanceObjects(const Value: TPublishedObjects); +begin + Assert(False); + // Do nothing +end; + +procedure TAppearanceListView.SetItemObjectsClassName(const Value: string); +begin + FItemAppearanceProperties.AppearanceClassName := Value; +end; + +procedure TAppearanceListView.SetItemEditObjectsClassName(const Value: string); +begin + FItemEditAppearanceProperties.AppearanceClassName := Value; +end; + +procedure TAppearanceListView.SetHeaderAppearanceName(const Value: string); +begin + if FHeaderAppearanceProperties.Name <> Value then + begin + FHeaderAppearanceProperties.Name := Value; + RecreateNativePresentation; + end; +end; + +procedure TAppearanceListView.SetHeaderAppearanceClassName(const Value: string); +begin + FHeaderAppearanceProperties.AppearanceClassName := Value; +end; + +procedure TAppearanceListView.SetFooterAppearanceName(const Value: string); +begin + if FFooterAppearanceProperties.Name <> Value then + begin + FFooterAppearanceProperties.Name := Value; + RecreateNativePresentation; + end; +end; + +function TAppearanceListView.GetAppearanceProperties: TArray; +begin + SetLength(Result, 4); + Result[0] := FItemEditAppearanceProperties; + Result[1] := FItemAppearanceProperties; + Result[2] := FHeaderAppearanceProperties; + Result[3] := FFooterAppearanceProperties; +end; + +procedure TAppearanceListView.SetItemAppearanceName(const Value: string); +begin + if FItemAppearanceProperties.Name <> Value then + begin + FItemAppearanceProperties.Name := Value; + RecreateNativePresentation; + end; +end; + +procedure TAppearanceListView.SetItemEditAppearanceName(const Value: string); + + function AllowsCheckboxes(const Name: string): Boolean; + begin + Result := (Name = TAppearanceNames.ListItemRightDetailShowCheck) or + (Name = TAppearanceNames.ImageListItemShowCheck) or + (Name = TAppearanceNames.ImageListItemRightButtonShowCheck) or + (Name = TAppearanceNames.ListItemShowCheck) or + (Name = TAppearanceNames.ImageListItemBottomDetailShowCheck) or + (Name = TAppearanceNames.ImageListItemBottomDetailRightButtonShowCheck); + end; + + function AllowsDeleteMode(const Name: string): Boolean; + begin + Result := (Name = TAppearanceNames.ListItemDelete) or + (Name = TAppearanceNames.ListItemRightDetailDelete) or + (Name = TAppearanceNames.ImageListItemDelete) or + (Name = TAppearanceNames.ImageListItemRightButtonDelete); + end; + +var + Checkable: IListViewCheckProvider; +begin + if FItemEditAppearanceProperties.Name <> Value then + begin + FItemEditAppearanceProperties.Name := Value; + SetDeleteButtonIndex(-1); + FAppearanceAllowsCheckboxes := AllowsCheckboxes(Value); + FAppearanceAllowsDeleteMode := AllowsDeleteMode(Value); + if Supports(Adapter, IListViewCheckProvider, Checkable) then + Checkable.CheckAll(False); + RecreateNativePresentation; + end; +end; + +procedure TAppearanceListView.InitializeItemAppearance(const AAppearance: TItemAppearanceProperties); +begin + AAppearance.AppearanceClass := TNullItemObjects; +end; + +procedure TAppearanceListView.ItemAppearanceChange(const Sender: TItemAppearanceProperties); +var + Purposes: set of TListItemPurpose; +begin + if TStateFlag.ResettingObjects in FStateFlags then + exit; + if Sender = nil then + Purposes := [Low(TListItemPurpose)..High(TListItemPurpose)] + else + Purposes := [Sender.Purpose]; + if FUpdatingAppearance = 0 then + AppearanceResetObjects(Purposes) + else + FChangedAppearanceObjects := FChangedAppearanceObjects + Purposes; +end; + +procedure TAppearanceListView.ItemAppearanceChangeObjects(const Sender: TItemAppearanceProperties); +begin + ItemAppearanceChange(Sender); +end; + +procedure TAppearanceListView.ObjectsNotify(Sender: TObject; const Item: TListItem; Action: TCollectionNotification); +begin + if Action = TCollectionNotification.cnRemoved then + begin + FItemSelectedBeforeChange := nil; + FItemSelectedBeforeEdit := nil; + end; +end; + +procedure TAppearanceListView.AppearanceResetObjects(APurposes: TListItemPurposes); +var + LPurposes: TListItemPurposes; + LProperties: TItemAppearanceProperties; +begin + if Adapter <> nil then + begin + LPurposes := []; + for LProperties in GetAppearanceProperties do + if LProperties.Active then + Include(LPurposes, LProperties.Purpose); + LPurposes := LPurposes * APurposes; + if LPurposes <> [] then + begin + Adapter.ResetViews(LPurposes); + InvalidateHeights; + UpdateScrollingLimits; + Invalidate; + end; + end; +end; + +procedure TAppearanceListView.ApplyStyle; +begin + inherited; + UpdateAppearanceStyleResources; +end; + +procedure TAppearanceListView.AppearanceResetHeights(APurposes: TListItemPurposes); +var + LPurposes: TListItemPurposes; + LProperties: TItemAppearanceProperties; +begin + if Adapter <> nil then + begin + LPurposes := []; + for LProperties in GetAppearanceProperties do + if LProperties.Active then + Include(LPurposes, LProperties.Purpose); + LPurposes := LPurposes * APurposes; + if LPurposes <> [] then + begin + InvalidateHeights; + UpdateScrollingLimits; + Invalidate; + end; + end; +end; + +procedure TAppearanceListView.DoResetView(const Item: TListItem); +begin + inherited; + ResetViewAppearance(TListViewItem(Item)); +end; + +procedure TAppearanceListView.ResetViewAppearance(const AItem: TListViewItem); +var + LHandled: Boolean; + LItemObjects: TItemAppearanceObjects; + LPrevItemIndex: Integer; + LDoUpdatingItemViewP: procedure (const AListItem: TListItem; var AHandled: Boolean) of object; + LDoUpdateItemViewP: procedure (const AListItem: TListItem) of object; + LSync: Boolean; + + function IsIndexValid(AIndex: Integer): Boolean; + begin + Result := (AIndex >= 0) and (AIndex < Adapter.Count); + end; + + function GetSelectableIndex(AIndex: Integer): Integer; + var + LItem: TListItem; + begin + Result := AIndex; + if not IsIndexValid(AIndex) then + Exit; + LItem := Adapter[AIndex]; + case LItem.Purpose of + TListItemPurpose.Header: + while (Result < Adapter.Count) and (Adapter[Result].Purpose <> TListItemPurpose.None) do + Inc(Result); + TListItemPurpose.Footer: + while (Result >= 0) and (Adapter[Result].Purpose <> TListItemPurpose.None) do + Dec(Result); + end; + end; + +begin + if TStateFlag.ResettingObjects in FStateFlags then + Exit; + Include(FStateFlags, TStateFlag.ResettingObjects); + try + LItemObjects := nil; + case AItem.Purpose of + TListItemPurpose.None: + if FItemEditAppearanceProperties.Active then + LItemObjects := FItemEditAppearanceProperties.Objects + else if FItemAppearanceProperties.Active then + LItemObjects := FItemAppearanceProperties.Objects; + TListItemPurpose.Header: + if FHeaderAppearanceProperties.Active then + LItemObjects := FHeaderAppearanceProperties.Objects; + TListItemPurpose.Footer: + if FFooterAppearanceProperties.Active then + LItemObjects := FFooterAppearanceProperties.Objects; + else + Assert(False); + end; + if (LItemObjects <> nil) and not (LItemObjects is TNullItemObjects) then + begin + LHandled := False; + LPrevItemIndex := FItemIndex; + LDoUpdatingItemViewP := DoUpdatingItemView; + LDoUpdateItemViewP := DoUpdateItemView; + LSync := IsIndexValid(AItem.Index) and Observers.IsObserving(TObserverMapping.PositionLinkID) and ( + (TMethod(LDoUpdatingItemViewP).Code <> @TAppearanceListView.DoUpdatingItemView) or + (TMethod(LDoUpdateItemViewP).Code <> @TAppearanceListView.DoUpdateItemView) or + Assigned(OnUpdatingItemView) or Assigned(OnUpdatingObjects) or + Assigned(OnUpdateItemView) or Assigned(OnUpdateObjects)); + try + if LSync then + begin + FItemIndex := GetSelectableIndex(AItem.Index); + TLinkObservers.PositionLinkPosChanged(Observers); + end; + DoUpdatingItemView(AItem, LHandled); + if not LHandled then + LItemObjects.ResetObjects(AItem, GetFinalItemSize); + DoUpdateItemView(AItem); + finally + if LSync then + begin + FItemIndex := LPrevItemIndex; + TLinkObservers.PositionLinkPosChanged(Observers); + end; + end; + end; + finally + Exclude(FStateFlags, TStateFlag.ResettingObjects); + end; +end; + +procedure TAppearanceListView.RefreshAppearances(const APurposes: TListItemPurposes); +begin + Adapter.ResetViews(APurposes); + InvalidateHeights; // Object heights may have changed +end; + +procedure TAppearanceListView.UpdateAppearanceStyleResources; +begin + Assert(not FUpdatingStyleResources); + TNonReentrantHelper.Execute(FUpdatingStyleResources, + procedure begin + RefreshAppearances; + end); +end; + +procedure TAppearanceListView.WillEnterEditMode(const Animated: Boolean); +begin + if Animated then + EditModeAppearances + else + RefreshAppearances([TListItemPurpose.None]); +end; + +procedure TAppearanceListView.ItemAppearanceChangeHeight(const Sender: TItemAppearanceProperties); +begin + Assert(Sender <> nil); + if Sender <> nil then + if FUpdatingAppearance = 0 then + AppearanceResetHeights([Sender.Purpose]) + else + Include(FChangedAppearanceHeights, Sender.Purpose); +end; + +procedure TAppearanceListView.DoResetEditModeAnimation; +begin + inherited; +end; + +procedure TAppearanceListView.BeginUpdate; +begin + inherited; + Inc(FUpdatingAppearance); + if FUpdatingAppearance = 1 then + begin + FChangedAppearanceObjects := []; + FChangedAppearanceHeights := []; + end; + if FAppearanceViewItems <> nil then + FAppearanceViewItems.BeginUpdate; +end; + +procedure TAppearanceListView.EndUpdate; +begin + inherited; + if FUpdatingAppearance > 0 then + begin + Dec(FUpdatingAppearance); + if FUpdatingAppearance = 0 then + begin + AppearanceResetObjects(FChangedAppearanceObjects); + AppearanceResetHeights(FChangedAppearanceHeights - FChangedAppearanceObjects); + end; + end; + if FAppearanceViewItems <> nil then + FAppearanceViewItems.EndUpdate; +end; + +procedure TAppearanceListView.EditModeAppearances; + function CanResetObjects: Boolean; + var + LProperties: TItemAppearanceProperties; + begin + Result := False; + for LProperties in GetAppearanceProperties do + if LProperties.Active and not (LProperties.Objects is TNullItemObjects) then + exit(True); + end; + +var + Filter: IListViewFilterable; + Item: TListItem; +begin + if Supports(Adapter, IListViewFilterable, Filter) and CanResetObjects then + begin + BeginUpdate; + try + for Item in Filter.UnfilteredItems do + if Item is TListViewItem and (Item.Purpose = TListItemPurpose.None) then + Item.View.Initialized := False; + finally + EndUpdate; + end; + end; +end; + +function TAppearanceListView.GetItemHeight(const Index: Integer): Integer; +var + Item: TListItem; +begin + if (Index < 0) or (Index >= Adapter.Count) then + exit(0); + Item := Adapter[Index]; + Result := Item.Height; + if Result < 1 then + case Item.Purpose of + TListItemPurpose.None: + if EditMode and FItemEditAppearanceProperties.Active then + Result := ItemEditHeight + else + Result := ItemHeight; + TListItemPurpose.Header: + Result := HeaderHeight; + TListItemPurpose.Footer: + Result := FooterHeight; + else + Assert(False); + end; +end; + +function TAppearanceListView.GetItemHeight: Integer; +begin + Result := FItemAppearanceProperties.Height; +end; + +function TAppearanceListView.GetItemEditHeight: Integer; +begin + Result := FItemEditAppearanceProperties.Height; +end; + +function TAppearanceListView.GetHeaderHeight: Integer; +begin + Result := FHeaderAppearanceProperties.Height; +end; + +function TAppearanceListView.GetFooterHeight: Integer; +begin + Result := FFooterAppearanceProperties.Height; +end; + +procedure TAppearanceListView.Resize; +begin + inherited; +end; + +procedure TAppearanceListView.DoRequestReindexing(const Item: TListItem); +begin + FAppearanceViewItems.ReindexAndFindItem(TListViewItem(Item)); +end; + +procedure TAppearanceListView.DoCheckStateChanged(const AItem: TListItem; const Control: TListItemDrawable); +begin + inherited; + if Assigned(FOnButtonChange) and (Control is TListItemSimpleControl) then + FOnButtonChange(Self, AItem, TListItemSimpleControl(Control)); +end; + +procedure TAppearanceListView.DoControlClicked(const Item: TListItem; const Control: TListItemDrawable); +begin + inherited; + if Control is TListItemSimpleControl then + begin + if Assigned(FOnButtonClick) then + FOnButtonClick(Self, Item, TListItemSimpleControl(Control)); + FClickEventControl := Control; + + if not FMouseClickSwipeEventSend then // ZuBy + StartIncident(TDelayedIncident.ClickEvent); + end; +end; + +procedure TAppearanceListView.DoItemResized(const Item: TListItem); +begin + FAppearanceViewItems.ItemsResize; +end; + +procedure TAppearanceListView.DoListItemClick(const AItem: TListItem); +begin + inherited; + if Assigned(FOnItemClick) and (AItem is TListViewItem) and + (not FMouseClickSwipeEventSend) then + FOnItemClick(Self, TListViewItem(AItem)); +end; + +procedure TAppearanceListView.DoUpdateItemView(const AListItem: TListItem); +begin + inherited; + if Assigned(FOnUpdateObjects) and (AListItem is TListViewItem) then + FOnUpdateObjects(Self, TListViewItem(AListItem)); +end; + +procedure TAppearanceListView.DoUpdatingItemView(const AListItem: TListItem; var AHandled: Boolean); +begin + inherited; + if Assigned(FOnUpdatingObjects) and (AListItem is TListViewItem) then + FOnUpdatingObjects(Self, TListViewItem(AListItem), AHandled); +end; + +function TAppearanceListView.getFirstVisibleItemIndex: Integer; // ZuBy +begin + Result := FTopItemIndex; +end; + +procedure TAppearanceListView.SetColorBackground(aColor: TAlphaColor); // ZuBy +begin + FBackgroundStyleColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorButtonText(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.ButtonNormalStyleImage.Normal := nil; + FStyleResources.ButtonNormalStyleImage.Pressed := nil; + FStyleResources.ButtonTextColor := aColor; + FItemAppearanceObjects.ItemObjects.TextButton.TextColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorButtonTextPressed(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.ButtonTextPressedColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorDeleteText(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.DeleteButtonTextColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorDeleteTextPressed(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.DeleteButtonTextPressedColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorDeleteTintColor(aColor: TAlphaColor); // ZuBy +begin + FDeleteButton.TintColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorHeader(aColor: TAlphaColor); // ZuBy +begin + FHeaderStyleImage := nil; + FHeaderStyleColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorItemFill(aColor: TAlphaColor); // ZuBy +begin + FItemStyleFillColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorItemFillAlt(aColor: TAlphaColor); // ZuBy +begin + FItemStyleFillAltColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorItemSelected(aColor: TAlphaColor); // ZuBy +begin + FSelectionStyleImage := nil; + FSelectionStyleColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorItemSeparator(aColor: TAlphaColor); // ZuBy +begin + FItemStyleFrameColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorText(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.DefaultTextColor := aColor; + ItemAppearanceObjects.ItemObjects.Text.TextColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorTextDetail(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.DetailTextColor := aColor; + ItemAppearanceObjects.ItemObjects.Detail.TextColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorTextHeader(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.HeaderTextColor := aColor; + ItemAppearanceObjects.HeaderObjects.Text.TextColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorTextHeaderShadow(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.HeaderTextShadowColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorTextSelected(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.DefaultTextSelectedColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.ItemsClearTrue; // ZuBy +begin + ScrollViewPos := 0; + while Items.Count > 0 do + Items.Delete(0); +end; + +function TAppearanceListView.IsCustomColorUsed(const ItemIndex: Integer): Boolean; // ZuBy +begin + Result := Items[ItemIndex].Data['aUseCustomColor'].AsBoolean; +end; + + +procedure TAppearanceListView.SetCustomColorForItem(const ItemIndex: Integer; const aColor: TAlphaColor); // ZuBy +begin + with Items[ItemIndex] do + begin + Data['aUseCustomColor'] := True; + Data['aCustomColor'] := aColor; + end; + Repaint; +end; + +procedure TAppearanceListView.SetDefaultColorForItem(const ItemIndex: Integer); // ZuBy +begin + Items[ItemIndex].Data['aUseCustomColor'] := False; + Repaint; +end; + +procedure TAppearanceListView.SetColorPullRefresh(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.PullRefreshStrokeColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorPullRefreshIndicator(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.PullRefreshIndicatorColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorStretchGlow(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.ScrollingStretchGlowColor := aColor; + Repaint; +end; + +{$ENDREGION} +{$REGION 'TListView'} +procedure TListView.InitializeItemAppearance(const AAppearance: TItemAppearanceProperties); +begin + case AAppearance.AppearanceType of + TAppearanceType.Item: + AAppearance.AppearanceClass := TAppearancesRegistry.FindItemAppearanceObjectsClassByOption( + TRegisterAppearanceOption.DefaultItem); + TAppearanceType.ItemEdit: + AAppearance.AppearanceClass := TAppearancesRegistry.FindItemAppearanceObjectsClassByOption( + TRegisterAppearanceOption.DefaultItemEdit); + TAppearanceType.Header: + AAppearance.AppearanceClass := TAppearancesRegistry.FindItemAppearanceObjectsClassByOption( + TRegisterAppearanceOption.DefaultHeader); + TAppearanceType.Footer: + AAppearance.AppearanceClass := TAppearancesRegistry.FindItemAppearanceObjectsClassByOption( + TRegisterAppearanceOption.DefaultFooter); + else + Assert(False); + end; +end; + +procedure TAppearanceListView.EditorBeforeItemAdded(Sender: IListViewEditor); +begin + FItemSelectedBeforeEdit := Selected; +end; + +procedure TAppearanceListView.EditorAfterItemAdded(Sender: IListViewEditor; const Item: TListItem); +begin + if (FItemSelectedBeforeEdit <> nil) and (ItemIndex <> FItemSelectedBeforeEdit.Index) then + SetItemIndexInternal(FItemSelectedBeforeEdit.Index, True, True); + FItemSelectedBeforeEdit := nil; +end; + +procedure TAppearanceListView.EditorBeforeItemDeleted(Sender: IListViewEditor; const Index: Integer); +begin + if ItemIndex >= Adapter.Count - 1 then + ItemIndex := -1; + RemoveItemCrossFade(Index); + FItemSelectedBeforeEdit := Selected; + if (FItemSelectedBeforeEdit <> nil) and (FItemSelectedBeforeEdit.Index = Index) then + FItemSelectedBeforeEdit := nil; +end; + +procedure TAppearanceListView.EditorAfterItemDeleted(Sender: IListViewEditor); +begin + if (FItemSelectedBeforeEdit <> nil) and (ItemIndex <> FItemSelectedBeforeEdit.Index) then + SetItemIndexInternal(FItemSelectedBeforeEdit.Index, True, True); + FItemSelectedBeforeEdit := nil; +end; + +{$ENDREGION} +{$REGION 'TAdapterListView'} + +procedure TAdapterListView.SetAdapter(Adapter: IListViewAdapter); +begin + FAdapter := Adapter; + HeightSumsNeedUpdate := True; + if Adapter <> nil then + begin + FAdapter.OnItemsMayChange := ItemsMayChange; + FAdapter.OnItemsCouldHaveChanged := ItemsCouldHaveChanged; + FAdapter.OnChanged := ItemsChange; + FAdapter.OnItemsInvalidate := ItemsInvalidate; + FAdapter.OnItemsResize := ItemsResize; + FAdapter.OnResetView := ResetView; + DoAdapterSet; + end; +end; + +procedure TAdapterListView.ItemsChange(Sender: TObject); +begin + DoItemsChange; +end; + +procedure TAdapterListView.ItemsCouldHaveChanged(Sender: TObject); +begin + DoItemsCouldHaveChanged; +end; + +procedure TAdapterListView.ItemsMayChange(Sender: TObject); +begin + DoItemsMayChange; +end; + +procedure TAdapterListView.ItemsInvalidate(Sender: TObject); +begin + DoItemsInvalidate; +end; + +procedure TAdapterListView.ResetView(Sender: TObject); +begin + DoResetView(Sender as TListItem); +end; + +procedure TAdapterListView.ItemsResize(Sender: TObject); +begin + DoItemsResize; +end; + +procedure TAdapterListView.DoAdapterSet; +begin +end; + +procedure TAdapterListView.DoItemsChange; +begin +end; + +procedure TAdapterListView.DoItemsCouldHaveChanged; +begin +end; + +procedure TAdapterListView.DoItemsMayChange; +begin +end; + +procedure TAdapterListView.DoItemsInvalidate; +begin +end; + +procedure TAdapterListView.DoResetView(const Sender: TListItem); +begin +end; + +procedure TAdapterListView.DoItemsResize; +begin + InvalidateHeights; +end; + +procedure TAdapterListView.InvalidateHeights; +begin + FHeightSumsNeedUpdate := True; +end; +{$ENDREGION} + +initialization + + LVTextLayout := TTextLayoutManager.DefaultTextLayout.Create; + RegisterFmxClasses([TCustomListView, TListView]); +finalization + LVTextLayout.Free; +end. diff --git a/Alexandria(11.3)/FMX.ListView.Types.pas b/Alexandria(11.3)/FMX.ListView.Types.pas new file mode 100644 index 0000000..9e13cca --- /dev/null +++ b/Alexandria(11.3)/FMX.ListView.Types.pas @@ -0,0 +1,3740 @@ +{*******************************************************} +{ } +{ Delphi FireMonkey Platform } +{ } +{ Copyright(c) 2011-2023 Embarcadero Technologies, Inc. } +{ All rights reserved } +{ } +{*******************************************************} + +unit FMX.ListView.Types; + +interface + +{$SCOPEDENUMS ON} + +uses + System.Types, System.UITypes, System.Classes, System.Generics.Collections, System.Generics.Defaults, System.SysUtils, + FMX.Types, FMX.Controls,FMX.TextLayout, System.Math.Vectors, System.Rtti, FMX.Objects, FMX.Graphics, FMX.ActnList, + FMX.Styles.Objects, FMX.ImgList; + +{$IF DEFINED(IOS) OR DEFINED(ANDROID)} +{$DEFINE LISTVIEW_TOUCH} +{$ENDIF} + +{.$DEFINE PIXEL_ALIGNMENT} +{.$DEFINE DRAW_ITEM_MARGINS} + +type + TListItemAlign = (Leading, Center, Trailing); + TListItemPurpose = (None, Header, Footer); + TListItemPurposes = set of TListItemPurpose; + + TListItemPurposeHelper = record helper for TListItemPurpose + function ToString: string; + end; + + TListItem = class; + IListViewAdapter = interface; + IListViewController = interface; + + TListItemStyleResources = class; + + TListItemDrawState = (Selected, Deleting, EditMode); + TListItemDrawStates = set of TListItemDrawState; + + TListItemDrawable = class; + TListItemView = class; + TListItemCallbackOp = (CreateDrawables, InvalidateOwner, Click); + TListItemCallback = TProc; + + /// TListItem view is comprised of TListViewDrawables. These are the actual + /// view elements that are being painted in the item cells. + TListItemDrawable = class(TInterfacedPersistent) + public type + TParams = record + AbsoluteOpacity: Single; + ItemSelectedAlpha: Single; + DeletingUnwantedOpacity: Single; + EditModeTransitionAlpha: Single; + ParentAbsoluteRect: TRectF; + Images: TCustomImageList; + end; + strict private + FPlaceOffsetX: TPosition; + protected type + TStyleResource = (FontSize, FontFamily, FontStyle, TextColor, SelectedTextColor, + TextShadowColor, PressedTextColor); + TStyleResources = set of TStyleResource; + protected const + TextResources: set of TStyleResource = [TStyleResource.FontFamily, + TStyleResource.FontSize, TStyleResource.FontStyle, TStyleResource.TextColor, + TStyleResource.TextShadowColor, TStyleResource.SelectedTextColor, TStyleResource.PressedTextColor]; + private + FAlign: TListItemAlign; + FVertAlign: TListItemAlign; + FVisible: Boolean; + FWidth: Single; + FHeight: Single; + FOpacity: Single; + FUpdating: Integer; + NeedRepaint: Boolean; + FOnSelect: TNotifyEvent; + FName: string; + [Weak] FTagObject: TObject; + FTagFloat: Single; + FTagString: string; + FLocalRect: TRectF; + FStyleValuesNeedUpdate: TStyleResources; + FController: IListViewController; + FCallback: TListItemCallback; + + procedure SetOneDimension(const Index: Integer; const Value: Single); + procedure SetSize(const Value: TPointF); + function GetSize: TPointF; + procedure SetOpacity(const Value: Single); + procedure SetAlign(const Value: TListItemAlign); + procedure SetVertAlign(const Value: TListItemAlign); + procedure SetVisible(const Value: Boolean); + function GetData: TValue; virtual; + procedure SetData(const Value: TValue); virtual; + function GetPlaceOffset: TPosition; inline; + procedure SetInvalidateCallback(const Callback: TListItemCallback); virtual; + procedure PlaceOffsetChanged(Sender: TObject); + + protected + /// Called when the Size of this drawable changes + procedure DoResize; virtual; + /// Called when the Opacity of this drawable changes + procedure DoOpacityChange; virtual; + /// Called when TListItem comprising + /// this drawable is selected. + procedure DoSelect; virtual; + /// Called when PlaceOffset changes + procedure DoPlaceOffsetChanged; virtual; + /// Called when Align or VertAlign changes + procedure DoAlignChanged; virtual; + /// Finds an embedded TControl at given Point + function ObjectAtPoint(const Point: TPointF): TControl; virtual; + + /// Handle MouseDown event. Called by + /// TListItem.MouseDown + function MouseDown(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF): Boolean; virtual; + /// Handle MouseMove event. Called by host + /// TListItem.MouseMove + procedure MouseMove(const Shift: TShiftState; const MousePos: TPointF); virtual; + /// Handle MouseUp event. Called by + /// TListItem.MouseUp + procedure MouseUp(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF); virtual; + /// Called by UpdateValuesFromResources. + /// Updates default parameters defined by style in descendants such as font, font syle and colors. + procedure DoUpdateValuesFromResources(const Resources: TListItemStyleResources; const Purpose: TListItemPurpose); virtual; + public + constructor Create(const AOwner: TListItem); virtual; + destructor Destroy; override; + function ToString: string; override; + + /// Return amount of rendering passes. See TListViewBase.DrawListItems + function GetRenderPassCount: Integer; virtual; + /// Align and calculate this drawable's local rectangle for given item's DestRect and DrawStates + procedure CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); virtual; + /// Return True if Point is inside local rect + function InLocalRect(const Point: TPointF): Boolean; + /// Request repaint. When between BeginUpdate/EndUpdate the request will be held back until the + /// update cycle is finished + procedure Invalidate; + /// Render this drawable + /// Canvas destination canvas + /// DrawItemIndex index within parent TListView of item being rendered + /// DrawStates which of the item states to render: Selected, Deleting, EditMode; + /// see TListItemDrawStates + /// Resources default style resources to use for rendering; + /// TListItemStyleResources + /// Params extra rendering parameters; see + /// TListItemDrawable.TParams + /// + procedure Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates; + const Resources: TListItemStyleResources; const Params: TParams; const SubPassNo: Integer = 0); virtual; abstract; + /// Begin update. During update calling InvalidateCallback + /// will be held back until EndUpdate + procedure BeginUpdate; + /// End update. If invalidation was required during the update cycle, + /// InvalidateCallback will be invoked + procedure EndUpdate; + /// Initializes or updates default parameters defined by style in descendants such as font, font syle + /// and colors. + procedure UpdateValuesFromResources(const Resources: TListItemStyleResources; const Purpose: TListItemPurpose); + /// Set flag that enables UpdateValuesFromResources + procedure UpdateValuesFromStyle; + /// Local width of list item inside its designated area + property Width: Single index 0 read FWidth write SetOneDimension; + /// Local height of list item inside its designated area + property Height: Single index 1 read FHeight write SetOneDimension; + /// Size of this drawable + property Size: TPointF read GetSize write SetSize; + /// Horizontal alignment of drawable inside its designated area + property Align: TListItemAlign read FAlign write SetAlign; + /// Vertical alignment of drawable inside its designated area + property VertAlign: TListItemAlign read FVertAlign write SetVertAlign; + /// Determines whether the current drawable is visible or not + property Visible: Boolean read FVisible write SetVisible; + /// The offset in logical units regarding aligned location for finer placement control + property PlaceOffset: TPosition read GetPlaceOffset; + /// Name of this drawable + property Name: string read FName write FName; + /// Drawing opacity + property Opacity: Single read FOpacity write SetOpacity; + /// LocalRect of this drawable + property LocalRect: TRectF read FLocalRect; + /// Invoked when owner TListItem is selected + property OnSelect: TNotifyEvent read FOnSelect write FOnSelect; + // Polymorphic property access + property Data: TValue read GetData write SetData; + /// User-defined object reference for this drawable + property TagObject: TObject read FTagObject write FTagObject; + /// User-defined floating-point number for this drawable + property TagFloat: Single read FTagFloat write FTagFloat; + /// User-defined string for this drawable + property TagString: string read FTagString write FTagString; + /// Callback invoked when item is being invalidated + /// TListItemCallback + /// + property InvalidateCallback: TListItemCallback write SetInvalidateCallback; + end; + + /// Declared for compatibility + TListItemObject = class(TListItemDrawable) + end; + + /// Represents a text drawable in ListView items + TListItemText = class(TListItemDrawable) + private const + DefaultFontFamily = 'Helvetica'; // do not localize + DefaultFontSize = 14; + ShadowOffset: TPointF = (X: 0; Y: 1); + private type + TFontSettings = record + Family: string; + Size: Single; + Style: TFontStyles; + end; + strict private + FFontX: TFont; + FFontSettings: TFontSettings; + private + FTextLayout: TTextLayout; + FText: string; + FTextAlign: TTextAlign; + FTextVertAlign: TTextAlign; + FWordWrap: Boolean; + LayoutChanged: Boolean; + FTextColor: TAlphaColor; + FSelectedTextColor: TAlphaColor; + FTrimming: TTextTrimming; + FTextShadowOffsetX: TPosition; + FTextShadowColor: TAlphaColor; + FIsDetailText: Boolean; + + procedure FontChanged(Sender: TObject); + procedure TextShadowOffsetChanged(Sender: TObject); + procedure SetText(const Value: string); + procedure SetTextAlign(const Value: TTextAlign); + procedure SetTextVertAlign(const Value: TTextAlign); + procedure SetWordWrap(const Value: Boolean); + procedure SetTextColor(const Value: TAlphaColor); + procedure SetTrimming(const Value: TTextTrimming); + procedure SetSelectedTextColor(const Value: TAlphaColor); + procedure SetTextShadowColor(const Value: TAlphaColor); + procedure SetIsDetailText(const Value: Boolean); + procedure SetData(const AValue: TValue); override; + function GetData: TValue; override; + function GetShadowOffset: TPosition; inline; + function GetFont: TFont; + function FontSettingsSnapshot: TFontSettings; + protected + procedure DoResize; override; + procedure DoUpdateValuesFromResources(const Resources: TListItemStyleResources; const Purpose: TListItemPurpose); override; + public + constructor Create(const AOwner: TListItem); override; + destructor Destroy; override; + + procedure CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); override; + procedure Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates; + const Resources: TListItemStyleResources; const Params: TListItemDrawable.TParams; const SubPassNo: Integer = 0); override; + /// Font used to drawing text in this drawable + property Font: TFont read GetFont; + /// Text displayed in this drawable + property Text: string read FText write SetText; + /// Horizontal text alignment inside local item rectangle + property TextAlign: TTextAlign read FTextAlign write SetTextAlign; + /// Vertical text alignment inside local item rectangle + property TextVertAlign: TTextAlign read FTextVertAlign write SetTextVertAlign; + /// Wrap the text it does not fit in the available width + property WordWrap: Boolean read FWordWrap write SetWordWrap; + /// Text color in neutral state + property TextColor: TAlphaColor read FTextColor write SetTextColor; + /// Text color in selected state + property SelectedTextColor: TAlphaColor read FSelectedTextColor write SetSelectedTextColor; + /// Text shadow color. The text shadow will appear behind normal text only when its color is + /// set to non-zero value (default) + property TextShadowColor: TAlphaColor read FTextShadowColor write SetTextShadowColor; + /// Text shadow offset. The text shadow will appear behind normal text only when its color is + /// set to non-zero value (default) + property TextShadowOffset: TPosition read GetShadowOffset; + /// Text trimming + /// TTextTrimming + /// + property Trimming: TTextTrimming read FTrimming write SetTrimming; + /// Hints regarding the contents of this text object, which affecs visual style + property IsDetailText: Boolean read FIsDetailText write SetIsDetailText; + end; + + /// An empty drawable + TListItemDummy = class(TListItemDrawable) + public + procedure Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates; + const Resources: TListItemStyleResources; const Params: TListItemDrawable.TParams; + const SubPassNo: Integer = 0); override; + end; + + /// Image scaling modes for TListItemImage + /// TListItemImage.ScalingMode + /// StretchWithAspect stretch while preserving aspect ratio + /// Original keep original size and proportion + /// Stretch stertch to fill available area disregarding proportions + /// + TImageScalingMode = (StretchWithAspect, Original, Stretch); + + /// A TListItemDrawable representing an image + TListItemImage = class(TListItemDrawable) + public type + /// Image source: + /// None no image source + /// Bitmap bitmap or bitmap reference + /// ImageList bitmap is specified by an index in an ImageList + /// + TImageSource = (None, Bitmap, ImageList); + private + FStaticBitmap: TBitmap; + [Weak]FReferBitmap: TBitmap; + FSrcRect: TRectF; + FOwnsBitmap: Boolean; + FImageScalingMode: TImageScalingMode; + FImageIndex: TImageIndex; + FImageSource: TImageSource; + function GetBitmap: TBitmap; + procedure SetBitmap(const Value: TBitmap); + procedure SetOwnsBitmap(const Value: Boolean); + procedure SetSrcRect(const Value: TRectF); + procedure SetImageScalingMode(const Value: TImageScalingMode); + procedure SetImageIndex(const Value: TImageIndex); + function GetImageSource: TImageSource; inline; + procedure SetData(const Value: TValue); override; + public + constructor Create(const AOwner: TListItem); override; + destructor Destroy; override; + procedure CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); override; + procedure Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates; + const Resources: TListItemStyleResources; const Params: TListItemDrawable.TParams; + const SubPassNo: Integer = 0); override; + /// Bitmap, used if ImageSource equals TImageSource.Bitmap + property Bitmap: TBitmap read GetBitmap write SetBitmap; + /// Determines whether this list owns and maintains the bitmap, or whether it is a reference only. + /// It is must faster and memory efficient to have multiple references to a single bitmap rather than multiple + /// copies of the same bitmap copied across the items + property OwnsBitmap: Boolean read FOwnsBitmap write SetOwnsBitmap; + /// Fit InputRect part of Bitmap into DestinationRect according to current ScalingMode + /// Bitmap TBitmap being fit + /// InputRect source rectangle + /// DestinationRect destination rectangle where the bitmap is to be placed + /// + procedure FitInto(const Bitmap: TBitmap; var InputRect, DestinationRect: TRectF); + /// Source rectangle in ImageSource + property SrcRect: TRectF read FSrcRect write SetSrcRect; + /// Scaling mode used for fitting the bitmap into destination rectangle + property ScalingMode: TImageScalingMode read FImageScalingMode write SetImageScalingMode + default TImageScalingMode.StretchWithAspect; + /// Indicates whether the images are obtained from TImageList or directly by using Bitmap property. + property ImageSource: TImageSource read GetImageSource; + /// Zero based index of an image. The default is -1. + /// See also FMX.ActnList.IGlyph + /// If non-existing index is specified, an image is not drawn and no exception is raised + property ImageIndex: TImageIndex read FImageIndex write SetImageIndex default -1; + end; + + TListItemEmbeddedControl = class; + /// IScene implementation for TListItemEmbeddedControl + TListItemControlScene = class(TFmxObject, IStyleBookOwner, IScene) + strict private + FCanvas: TCanvas; + [Weak] FContainer: TControl; + [Weak] FOwnerItem: TListItem; + FDrawing: Boolean; + FDisableUpdating: Integer; + private + FLayoutSize: TPoint; + function GetRealScene: IScene; + protected + // TFmxObject + procedure DoAddObject(const AObject: TFmxObject); override; + procedure DoRemoveObject(const AObject: TFmxObject); override; + // IStyleBookOwner + function GetStyleBook: TStyleBook; + procedure SetStyleBook(const Value: TStyleBook); + // IScene + function GetCanvas: TCanvas; + function GetSceneScale: Single; + function GetObject: TFmxObject; + procedure AddUpdateRect(const R: TRectF); + function GetUpdateRectsCount: Integer; + function GetUpdateRect(const Index: Integer): TRectF; + function LocalToScreen(const P: TPointF): TPointF; + function ScreenToLocal(const P: TPointF): TPointF; + procedure ChangeScrollingState(const AControl: TControl; const Active: Boolean); + procedure DisableUpdating; + procedure EnableUpdating; + /// Set item that contains this scene + procedure SetOwnerItem(const Item: TListItem); + /// Set container control + procedure SetContainer(const Container: TControl); + /// Get IScene of the parent control, i.e. TListView + property RealScene: IScene read GetRealScene; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + /// Repaint scene on canvas Canvas + procedure RepaintScene(const Canvas: TCanvas); + /// Container control + property Container: TControl read FContainer; + end; + + /// A dummy TControl that contains a TListItemEmbeddedControl + TListItemControlContainer = class(TControl) + private + [weak]FItemOwner: TListItemEmbeddedControl; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + end; + + /// Base class for TListItem embedded controls + TListItemEmbeddedControl = class(TListItemDrawable) + private + FScene: TListItemControlScene; + FContainer: TListItemControlContainer; + protected + /// Return TControl located at given Point + function ObjectAtPoint(const Point: TPointF): TControl; override; + public + constructor Create(const AOwner: TListItem); override; + destructor Destroy; override; + procedure Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates; + const Resources: TListItemStyleResources; const Params: TListItemDrawable.TParams; + const SubPassNo: Integer = 0); override; + /// TListItemControlContainer of this embedded control + property Container: TListItemControlContainer read FContainer; + end; + + /// Simple embedded control base class + TListItemSimpleControl = class(TListItemDrawable) + private const + DisabledOpacity = 0.6; + private + FEnabled: Boolean; + FPressed: Boolean; + FMouseOver: Boolean; + FTouchExpand: Single; + procedure SetEnabled(const Value: Boolean); + protected + /// Shall intercept clicks + function IsClickOpaque: Boolean; virtual; + function MouseDown(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF): Boolean; + override; + procedure MouseMove(const Shift: TShiftState; const MousePos: TPointF); override; + procedure MouseUp(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF); override; + /// Handle click + procedure DoClick; virtual; + /// Handle change of Enabled + procedure DoEnabledChange; virtual; + public + constructor Create(const AOwner: TListItem); override; + /// Test if point Pos belongs to local rectangle of this control + function PointInLocalRect(const Pos: TPointF): Boolean; + /// This method is called when click event is sent to the control to handle any actions associated + /// with this item + procedure Click; + /// Control state: Enabled + property Enabled: Boolean read FEnabled write SetEnabled; + /// Control state: Pressed + property Pressed: Boolean read FPressed; + /// Control state: MouseOver + property MouseOver: Boolean read FMouseOver; + /// Additional area (in logical units) around the control that is sensitive to touch + property TouchExpand: Single read FTouchExpand write FTouchExpand; + end; + + /// Accessory type for TListItemAccessory + /// More more + /// Checkmark checkmark + /// Detail detail disclosure + /// + TAccessoryType = (More, Checkmark, Detail); + + /// List item accessory, a glyph typically displayed at the right edge of the list item + TListItemAccessory = class(TListItemDrawable) + private + FAccessoryType: TAccessoryType; + protected + /// Set accessory type: More, Checkmark or Detail + procedure SetAccessoryType(Value: TAccessoryType); + public + constructor Create(const AOwner: TListItem); override; + + procedure CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); override; + procedure Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates; + const Resources: TListItemStyleResources; const Params: TListItemDrawable.TParams; + const SubPassNo: Integer = 0); override; + + /// Accessory type, TAccessoryType + property AccessoryType: TAccessoryType read FAccessoryType write SetAccessoryType; + end; + + /// Glyph type for TListItemGlyphButton + /// Add plus button + /// Delete delete button + /// Checkbox selection checkbox + /// + TGlyphButtonType = (Add, Delete, Checkbox); + + /// Glyph button is an additional control usually used in Edit mode. It can be an Add/Plus sign, + /// a Delete button or a Checkbox + TListItemGlyphButton = class(TListItemSimpleControl) + private const + CheckedAnimationFrameRate = 60; + CheckedAnimationDuration = 0.15; // in seconds + private + FButtonType: TGlyphButtonType; + FClickOnSelect: Boolean; + FChecked: Boolean; + FTransitionEnabled: Boolean; + FTransitionAlpha: Single; + FTransitionTimer: TTimer; + FTransitionStartTicks: Double; + FTimerService: IFMXTimerService; + procedure SetButtonType(const Value: TGlyphButtonType); + procedure SetChecked(const Value: Boolean); + procedure InitCheckedTransition; + procedure ResetCheckedTransition; + procedure TransitionTimerNotify(Sender: TObject); + protected + procedure DoSelect; override; + procedure DoClick; override; + function MouseDown(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF): Boolean; + override; + procedure SetData(const AValue: TValue); override; + public + constructor Create(const AOwner: TListItem); override; + destructor Destroy; override; + procedure CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); override; + procedure Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates; + const Resources: TListItemStyleResources; const Params: TListItemDrawable.TParams; + const SubPassNo: Integer = 0); override; + /// Button type, TGlyphButtonType + property ButtonType: TGlyphButtonType read FButtonType write SetButtonType; + /// If set to True, this button will receive click events from entire list by using of selection + property ClickOnSelect: Boolean read FClickOnSelect write FClickOnSelect; + /// Determines whether checkbox is checked, has no effect for other button types + property Checked: Boolean read FChecked write SetChecked; + end; + + /// Type of TListItemTextButton + /// Normal a regular button + /// Delete a delete button + /// + TTextButtonType = (Normal, Delete); + + /// A button with text that can be clicked inside of a TListItem + TListItemTextButton = class(TListItemSimpleControl) + private + FTextDrawable: TListItemText; + FButtonType: TTextButtonType; + FTintColor: TAlphaColor; + FPressedTextColor: TAlphaColor; + FTextColor: TAlphaColor; + + // getters routing to FTextDrawable + function GetFont: TFont; + function GetText: string; + procedure SetText(const Value: string); + procedure SetTextAlign(const Value: TTextAlign); + function GetTextAlign: TTextAlign; + procedure SetTextVertAlign(const Value: TTextAlign); + function GetTextVertAlign: TTextAlign; + procedure SetWordWrap(const Value: Boolean); + function GetWordWrap: Boolean; + procedure SetTextColor(const Value: TAlphaColor); + function GetTextColor: TAlphaColor; + procedure SetTrimming(const Value: TTextTrimming); + function GetTrimming: TTextTrimming; + procedure SetTextShadowColor(const Value: TAlphaColor); + function GetTextShadowColor: TAlphaColor; + + // button-specific text properties + procedure SetPressedTextColor(const Value: TAlphaColor); + function GetTextShadowOffset: TPosition; inline; + procedure SetButtonType(const Value: TTextButtonType); + procedure SetTintColor(const Value: TAlphaColor); + procedure SetInvalidateCallback(const Callback: TListItemCallback); override; + protected + function IsClickOpaque: Boolean; override; + procedure DoResize; override; + procedure DoOpacityChange; override; + procedure DoPlaceOffsetChanged; override; + procedure DoAlignChanged; override; + procedure SetData(const AValue: TValue); override; + public + constructor Create(const AOwner: TListItem); override; + destructor Destroy; override; + + procedure DoUpdateValuesFromResources(const Resources: TListItemStyleResources; + const Purpose: TListItemPurpose); override; + function GetRenderPassCount: Integer; override; + procedure CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); override; + procedure Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates; + const Resources: TListItemStyleResources; const Params: TListItemDrawable.TParams; + const SubPassNo: Integer = 0); override; + + /// Button type, TTextButtonType + property ButtonType: TTextButtonType read FButtonType write SetButtonType; + /// Button tint color + property TintColor: TAlphaColor read FTintColor write SetTintColor; + /// Font used to draw button text + property Font: TFont read GetFont; + /// Button text + property Text: string read GetText write SetText; + /// Horizontal alignment of text within the button area + property TextAlign: TTextAlign read GetTextAlign write SetTextAlign; + /// Vertical alignment of text within the button area + property TextVertAlign: TTextAlign read GetTextVertAlign write SetTextVertAlign; + /// If true, the text that does not fit in button width will be wrapped + property WordWrap: Boolean read GetWordWrap write SetWordWrap; + /// Text color in neutral state + property TextColor: TAlphaColor read GetTextColor write SetTextColor; + /// Text color in pressed state + property PressedTextColor: TAlphaColor read FPressedTextColor write SetPressedTextColor; + /// Text shadow color + property TextShadowColor: TAlphaColor read GetTextShadowColor write SetTextShadowColor; + /// Text shadow offset + property TextShadowOffset: TPosition read GetTextShadowOffset; + /// Text trimming + /// TTextTrimming + /// + property Trimming: TTextTrimming read GetTrimming write SetTrimming; + end; + + /// TListItemView is a collection of drawables that comprise the view of a TListItem + TListItemView = class + private type + TViewList = TObjectList; + private + FCallback: TListItemCallback; + FViewList: TListItemView.TViewList; + FInitialized: Boolean; + + function GetCount: Integer; + function GetObject(const Index: Integer): TListItemDrawable; + function GetViewList: TViewList; inline; + protected + procedure Include(const AItem: TListItemDrawable); + procedure Exclude(const AItem: TListItemDrawable); + function GetInitialized: Boolean; + procedure SetInitialized(const Value: Boolean); + public + constructor Create(const AOwner: TListItem); + destructor Destroy; override; + /// Add a drawable to view + function Add(const AItem: TListItemDrawable): Integer; + /// Insert a drawable at a position indicated by Index + procedure Insert(const Index: Integer; const Value: TListItemDrawable); + /// Clear the view + procedure Clear; virtual; + /// Delete drawable at index Index + procedure Delete(Index: Integer); + /// Find TListItemDrawable + /// specified by name AName + function FindDrawable(const AName: string): TListItemDrawable; + /// Find TListItemDrawable specified by name AName + function FindObject(const AName: string): TListItemDrawable; deprecated 'Use FindDrawable'; + + + /// Find TListItemDrawable specified by name AName and throw an error if not found + function DrawableByName(const AName: string): TListItemDrawable; + /// Find TListItemDrawable specified by name AName and throw an error if not found + function ObjectByName(const AName: string): TListItemDrawable; deprecated 'Use DrawableByName'; + /// Number of drawables in this view + property Count: Integer read GetCount; + /// TListItemDrawable by index + property Drawables[const Index: Integer]: TListItemDrawable read GetObject; default; + /// Get TViewList that contains the drawables + property ViewList: TViewList read GetViewList; + /// Used internally to notify owner about changes + property Callback: TListItemCallback write FCallback; + /// Used internally + property Initialized: Boolean read GetInitialized write SetInitialized; + end; + + /// Compatibility class for TListItemView + TListItemObjects = class(TListItemView) + end; + + /// TListItem is an element that comprises TListView. Each individual item contains a View, + /// which in turn is comprised of instances of TListItemDrawable + TListItem = class + public type + TListItemViewType = class of TListItemView; + TListItemNotifyEvent = procedure(const Item: TListItem) of object; + strict private + FHeaderRef: Integer; + FAdapter: IListViewAdapter; + FController: IListViewController; + private + FIndex: Integer; + FHeight: Integer; + FPurpose: TListItemPurpose; + FUpdating: Integer; + FNeedRepaint: Boolean; + FView: TListItemView; + [Weak] FTagObject: TObject; + FTag: NativeInt; + FTagString: string; + function GetCount: Integer; + procedure SetHeight(const Value: Integer); + function GetController: IListViewController; + protected + /// Invoked when item height changes and heights of all items need to be recounted + procedure InvalidateHeights; virtual; + /// Setter for Purpose property + procedure SetPurpose(const AValue: TListItemPurpose); virtual; + /// Require repaint, notify the controller that the item is invalid + procedure Repaint; virtual; + /// Return class of TListItemView + function ListItemObjectsClass: TListItemViewType; virtual; + /// Getter for Index property + function GetIndex: Integer; virtual; + /// Setter for Index property + procedure SetIndex(const Value: Integer); virtual; + public + constructor Create(const AAdapter: IListViewAdapter; const AController: IListViewController = nil); + destructor Destroy; override; + function ToString: string; override; + /// Invalidate item, request repainting + procedure Invalidate; + /// Begin update. Invalidation request will not cause immediate action during update. + procedure BeginUpdate; + /// End update + procedure EndUpdate; + /// Called when the view drawables need to be created + procedure CreateObjects; virtual; + /// Called when the item is about to be painted + procedure WillBePainted; virtual; + /// Locate a control at a point, used with embedded controls + function ObjectAtPoint(const Point: TPointF): TControl; + /// Handle MouseDown event + function MouseDown(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF): Boolean; + /// Handle MouseMove event + procedure MouseMove(const Shift: TShiftState; const MousePos: TPointF); + /// Handle MouseUp event + procedure MouseUp(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF); + /// Handle selection + procedure MouseSelect; + /// True if there are items inside that react as clicked when the item is selected + function HasClickOnSelectItems: Boolean; + /// Adapter that observes this item + property Adapter: IListViewAdapter read FAdapter; + /// Number of drawables in the View, shorthand for View.Count + property Count: Integer read GetCount; + /// The view, drawables that comprise the visual of this item + property View: TListItemView read FView; + /// Height of this item + property Height: Integer read FHeight write SetHeight; + /// Reference to IListViewController + property Controller: IListViewController read GetController; + /// Which purpose item serves for: normal item (none), header or footer. + /// TListItemPurpose + property Purpose: TListItemPurpose read FPurpose write SetPurpose; + /// Reference to header of the group this item belongs to, for grouped views + property HeaderRef: Integer read FHeaderRef write FHeaderRef; + /// Index of this item in TListView.Adapter + property Index: Integer read GetIndex write SetIndex; + /// User-defined integer tag for this item + property Tag: NativeInt read FTag write FTag; + /// User-defined object reference for this item + property TagObject: TObject read FTagObject write FTagObject; + /// User-defined string tag for this item + property TagString: string read FTagString write FTagString; + end; + + /// A container used for passing various style-defined properties used in TListView + TListItemStyleResources = class + private + FOwnsObjects: Boolean; + public type + /// References to TStyleObjects used for rendering of TListItemAccessory + TAccessoryStyleObject = record + /// Accessory TStyleObject in neutral state + [Weak] Normal: TStyleObject; + /// Accessory TStyleObject in selected state + [Weak] Selected: TStyleObject; + end; + + /// Style objects used for button rendering + TButtonStyleObject = record + /// Button TStyleObject in neutral state + [Weak] Normal: TStyleObject; + /// Button TStyleObject in pressed state + [Weak] Pressed: TStyleObject; + end; + public + /// Style references used for Accessory rendering + AccessoryImages: array [TAccessoryType] of TAccessoryStyleObject; + /// Font used in header items + HeaderTextFont: TFont; + /// Color of header text + HeaderTextColor: TAlphaColor; + /// Color of header text shadow + HeaderTextShadowColor: TAlphaColor; + /// Default font for text drawables + DefaultTextFont: TFont; + /// Default color for text drawables + DefaultTextColor: TAlphaColor; + /// Default font for detail text + DetailTextFont: TFont; + /// Default color for detail text + DetailTextColor: TAlphaColor; + /// Default color for text in selected state + DefaultTextSelectedColor: TAlphaColor; + /// Style objects for Add Item TListItemGlyphButton + ButtonAddItemStyleImage: TButtonStyleObject; + /// Style objects for Delete Item TListItemGlyphButton + ButtonDeleteItemStyleImage: TButtonStyleObject; + /// Background image for regular TListItemTextButton + ButtonNormalStyleImage: TButtonStyleObject; + /// Background image for delete-themed TListItemTextButton + ButtonDeleteStyleImage: TButtonStyleObject; + /// Style objects for Checkbox TListItemGlyphButton + ButtonCheckboxStyleImage: TButtonStyleObject; + /// Font for TListItemTextButton + ButtonTextFont: TFont; + /// Text color for neutral state TListItemTextButton + ButtonTextColor: TAlphaColor; + /// Text color for pressed state TListItemTextButton + ButtonTextPressedColor: TAlphaColor; + /// Font for delete-themed TListItemTextButton + DeleteButtonTextFont: TFont; + /// Text color for neutral state delete-themed TListItemTextButton + DeleteButtonTextColor: TAlphaColor; + /// Text color for pressed state delete-themed TListItemTextButton + DeleteButtonTextPressedColor: TAlphaColor; + /// Glow color that appears at the top or the bottom of screen when scrolling + /// is being stretched out + ScrollingStretchGlowColor: TAlphaColor; + /// Indicator color for drawing Pull Refresh indicator + PullRefreshIndicatorColor: TAlphaColor; + /// Stroke color for drawing Pull Refresh indicator + PullRefreshStrokeColor: TAlphaColor; + constructor Create; overload; + constructor Create(const Source: TListItemStyleResources); overload; + destructor Destroy; override; + end; + + /// Interface providing access to style resources. Implemented by TListViewBase + IListItemStyleResources = interface + ['{0328C6F1-432C-4F8B-994B-7AB2543CD172}'] + /// Getter for StyleResources property + function GetStyleResources: TListItemStyleResources; + /// TListView style resources + property StyleResources: TListItemStyleResources read GetStyleResources; + /// True if style resources should be reloaded + function StyleResourcesNeedUpdate: Boolean; + end; + + /// Interface providing loose coupling between TListView and TListItem. Implemented by + /// TListViewBase + IListViewController = interface + ['{3855EF72-3B32-41BE-9068-7B109B2DD8E5}'] + function GetEditModeTransitionAlpha: Single; + function GetDeleteModeTransitionAlpha: Single; + function GetImages: TCustomImageList; + /// True if delete mode is enabled + function IsDeleteModeAllowed: Boolean; + /// Left offset of item rectangle in edit mode + function GetItemEditOffset(const Item: TListItem): Single; + /// Right cutoff of item rectangle in delete mode + function GetItemDeleteCutoff(const Item: TListItem): Single; + /// Item selection alpha + function GetItemSelectionAlpha(const Item: TListItem): Single; + /// Client area margins + function GetClientMargins: TRectF; + /// IScene of the TListView control + function GetScene: IScene; + /// Request TListView to initialize item indices; + /// see TListViewItem.GetIndex + /// + procedure RequestReindexing(const Item: TListItem); + /// Notify TListView that item size has changed; + /// see TListViewItem.InvalidateHeights + /// + procedure ItemResized(const Item: TListItem); + /// Notify TListView that item requires repainting + procedure ItemInvalidated(const Item: TListItem); + /// Notify TListView that a control inside of an item has been clicked + procedure ControlClicked(const Item: TListItem; const Control: TListItemDrawable); + /// Notify TListView that a control inside of an item has been clicked + procedure CheckStateChanged(const Item: TListItem; const Control: TListItemDrawable); + /// Item alpha during edit mode transition + property EditModeTransitionAlpha: Single read GetEditModeTransitionAlpha; + /// Item alpha during delete transition + property DeleteModeTransitionAlpha: Single read GetDeleteModeTransitionAlpha; + ///TImageList used when ImageSource = ImageList; can be nil + property Images: TCustomImageList read GetImages; + end; + + /// Presentation of TPresentedListView. The control passes messages to the + /// presentation layer by the means of this interface. + IListViewPresentation = interface + ['{85C07617-2BB7-44DC-BBCB-2E3FE422B006}'] + /// Called when ancestor's visible property is changed + procedure AncestorVisibleChanged(const Visible: Boolean); + /// Called when Parent is changed + procedure ParentChanged; + /// Called when position in parent list is changed + procedure OrderChanged; + /// Called when control size has changed + procedure SizeChanged; + /// Called when edit mode has changed + procedure EditModeChanged; + /// Called when visibility status has changed + procedure StatusChanged; + /// Called when item presentation requires update + procedure ItemsUpdated; + /// Item has been invalidated, presentation should reinitialize its view + procedure ItemInvalidated(const Item: TListItem); + /// Item has been selected by the control, presentation should update its selection + procedure SetItemSelected(const ItemIndex: Integer; const Value: Boolean); + /// Selected item index has been changed by the control, presentation should update selection + procedure SetItemIndex(const ItemIndex: Integer); + /// Force stop pull refresh + procedure StopPullRefresh; + end; + + /// Base interface from presented control to presentation + IListViewCustomPresentationParent = interface + ['{EBBE5FAA-F2B3-4606-AE32-8027DB97EC92}'] + /// Get root object, normally Root.GetObject + function GetRootObject: TObject; + /// Get content frame of the presented control + function GetContentFrame: TRect; + /// Get opacity of the presented control + function GetControlOpacity: Single; + end; + + /// Enumeration of internally used boolean flags + TListViewModeFlag = (Edit, Enabled, Visible, Deletion, PullRefresh, Buttons, Search, SearchOnTop, PullRefreshWait, + SwipeDelete); + /// Set of boolean flags used internally + TListViewModeFlags = set of TListViewModeFlag; + + /// View options specific to native iOS presentation + TListViewNativeOption = (Grouped, Indexed, Styled); + /// View options specific to native iOS presentation. + /// IListViewPresentationParent + TListViewNativeOptions = set of TListViewNativeOption; + + /// Interface from presented control to presentation + IListViewPresentationParent = interface(IListViewCustomPresentationParent) + ['{F5657E45-0955-4A9F-9FE6-6C5E019846E4}'] + /// Obtains IListViewAdapter + function GetAdapter: IListViewAdapter; + /// Obtains client rectangle for item with index ItemIndex + function GetItemClientRect(const ItemIndex: Integer): TRectF; + /// Obtains item count + function GetItemCount: Integer; + /// Obtains height of item ItemIndex + function GetItemHeight(const ItemIndex: Integer): Integer; + /// Guess item height before exact values can be calculated + function GetEstimatedItemHeight: Single; + /// Guess header height before exact values can be calculated + function GetEstimatedHeaderHeight: Single; + /// Guess footer height before exact values can be calculated + function GetEstimatedFooterHeight: Single; + /// Get main text of item ItemIndex + function GetItemText(const ItemIndex: Integer): string; + /// Get index title for item ItemIndex + function GetItemIndexTitle(const ItemIndex: Integer): string; + /// Query if item ItemIndex is selectable + function CanSelectItem(const ItemIndex: Integer): Boolean; + /// Notify control when item ItemIndex was selected + procedure DidSelectItem(const ItemIndex: Integer); + /// Query if item ItemIndex can be unselected + function CanUnselectItem(const ItemIndex: Integer): Boolean; + /// Notify control that item ItemIndex was unselected + procedure DidUnselectItem(const ItemIndex: Integer); + /// Notify control that an item button was clicked + procedure ItemButtonClicked(const ItemIndex: Integer); + /// Obtain presentation-specific boolean flags + function GetFlags: TListViewModeFlags; + /// Obtain native presentation-specific boolean flags + function GetOptions: TListViewNativeOptions; + /// Commit deletion of item ItemIndex + function DeleteItem(const ItemIndex: Integer): Boolean; + /// Invoke pull refresh + procedure InvokePullRefresh; + /// Set search filter string + procedure SetSearchFilter(const Filter: string); + /// Pass scroll view position to the control + procedure SetScrollViewPos(const Value: Single); + /// Require rebuild of the list + procedure RebuildList; + /// Set boolean flag that indicates that native view is being created. It is used + /// as a guard condition to prevent TListView logic from interfering with the presentation + /// while a view is being initialized + procedure SetCreatingNativeView(const Value: Boolean); + /// True if TListView is transparent + function GetIsTransparent: Boolean; + /// Obtain control opacity + function GetOpacity: Single; + /// Obtain background color defined by TListView style + function GetBackgroundStyleColor: TAlphaColor; + /// Request complete recreation of presentation + procedure RecreateNativePresentation; + end; + + /// Used to check if there is a design presentation attached to ListView + IListViewDesignPresentationParent = interface + ['{C62F3FE5-FE96-47A7-99CB-2EEBC85664FA}'] + ///True if design presentation is attached + function HasDesignPresentationAttached: Boolean; + end; + + /// A drawable shim extension to apply bounds changes from + /// the designer. + IListViewDrawableShim = interface + ['{9FB67E2A-37B9-473B-A95A-13EDD19ED91B}'] + ///Calculate appearance PlaceOffset, Width, Height for given rect from designer + function CalcAppearanceBounds(const AValue: TRect; const CurrentBounds: TRectF): TRectF; + end; + + /// Predicate used for item filtering. + /// + TFilterPredicate = TPredicate; + + TListItemsList = TList; + + /// IListViewAdapter provides interface between the data and their representation. + /// The essential part of this interface is implemented in FMX.ListView.Adapters.Base.TAbstractListViewAdapter + /// + IListViewAdapter = interface + ['{6E850F76-BABD-4756-BF05-A30C66A692AD}'] + /// Return number of items that this Adapter represents. See Item[Index]. + function GetCount: Integer; + /// Get TListItem by index Index + function GetItem(const Index: Integer): TListItem; + /// Get index of given TListItem + function IndexOf(const AItem: TListItem): Integer; + /// Return TEnumerator<TListItem> + function GetEnumerator: TEnumerator; + /// Height of items that do not have it explicitly defined + function GetDefaultViewHeight: Integer; + /// Subscribe to OnChanged event + procedure SetOnChanged(const Value: TNotifyEvent); + /// Subscribe to OnItemsMayChange event + procedure SetOnItemsMayChange(const Value: TNotifyEvent); + /// Subscribe to OnItemsCouldHaveChanged event + procedure SetOnItemsCouldHaveChanged(const Value: TNotifyEvent); + /// Subscribe to OnItemsInvalidate event + procedure SetOnItemsInvalidate(const Value: TNotifyEvent); + /// Subscribe to OnItemsResize event + procedure SetOnItemsResize(const Value: TNotifyEvent); + /// Subscribe to OnResetView event + procedure SetOnResetView(const Value: TNotifyEvent); + /// Notifies adapter about item's changes. + procedure Changed; + /// Sort data + procedure Sort(AComparer: IComparer); + /// Called by TListView every time it needs to paint. In dynamic adapters this is where + /// new TListItems should be created for data. + procedure CreateNewViews; + /// Recreate views for items that have specified purposes. May be called when item + /// views may need update, for example when host TListView is being resized + procedure ResetViews(const Purposes: TListItemPurposes); + // Called by ResetViews to invoke OnResetView event for each ListItem + procedure ResetView(const Item: TListItem); + /// Get item by Index. Items would normally be already created by CreateNewViews by the time + /// this property is accessed. Index should be in range of [0, Count) + property Item[const Index: Integer]: TListItem read GetItem; default; + /// Count of items + property Count: Integer read GetCount; + /// Invoked when items have changed + property OnChanged: TNotifyEvent write SetOnChanged; + /// Invoked before an edit operation, items or their contents may change + property OnItemsMayChange: TNotifyEvent write SetOnItemsMayChange; + /// Invoked after an edit operation, items could have been changed + property OnItemsCouldHaveChanged: TNotifyEvent write SetOnItemsCouldHaveChanged; + /// Invoked when items are invalidated and a repaint is necessary + property OnItemsInvalidate: TNotifyEvent write SetOnItemsInvalidate; + /// Item sizes were changed, notify the view that the sizes of items need recalculation + property OnItemsResize: TNotifyEvent write SetOnItemsResize; + /// View has been reset + property OnResetView: TNotifyEvent write SetOnResetView; + end; + + IListViewEditor = interface; + TBeforeItemAddedNotify = procedure(Sender: IListViewEditor) of object; + TAfterItemAddedNotify = procedure(Sender: IListViewEditor; const Item: TListItem) of object; + TBeforeItemDeletedNotify = procedure(Sender: IListViewEditor; const Index: Integer) of object; + TAfterItemDeletedNotify = procedure(Sender: IListViewEditor) of object; + + /// Extension of IListViewAdapter for editable content. Implemented by TAppearanceListViewItems + IListViewEditor = interface + ['{19A0606B-8C8E-49B2-A6B3-A708B7B8AD46}'] + /// Create a new Item and append at end + function Add: TListItem; + /// Delete Item at Index + procedure Delete(const Index: Integer); + /// Create a new Item and insert at Index + function Insert(const Index: Integer): TListItem; + /// Delete all items + procedure Clear; + /// Set OnBeforeItemAdded handler + procedure SetBeforeItemAdded(const AHandler: TBeforeItemAddedNotify); + /// Set OnAfterItemAdded handler + procedure SetAfterItemAdded(const AHandler: TAfterItemAddedNotify); + /// Set OnBeforeItemDeleted handler + procedure SetBeforeItemDeleted(const AHandler: TBeforeItemDeletedNotify); + /// Set OnAfterItemDeleted handler + procedure SetAfterItemDeleted(const AHandler: TAfterItemDeletedNotify); + /// Notification before item is added + property OnBeforeItemAdded: TBeforeItemAddedNotify write SetBeforeItemAdded; + /// Notification after item is added + property OnAfterItemAdded: TAfterItemAddedNotify write SetAfterItemAdded; + /// Notification before item is deleted + property OnBeforeItemDeleted: TBeforeItemDeletedNotify write SetBeforeItemDeleted; + /// Notification after item is deleted + property OnAfterItemDeleted: TAfterItemDeletedNotify write SetAfterItemDeleted; + end; + + /// Extension of IListViewAdapter for items with checkboxes. + /// Implemented by TAppearanceListViewItems + IListViewCheckProvider = interface + ['{032EB974-1C25-4B5E-BB07-01FA82554748}'] + /// Index of first checked item + function FirstChecked(const Checked: Boolean = True): Integer; + /// A quick query to see if one or all of the items are in checked state. + /// If AChecked = True, True if at least one item is checked + /// If AChecked = False, True if every item is checked + /// + function AnyChecked(const AChecked: Boolean = True): Boolean; + /// Change check state of all items + procedure CheckAll(const NewState: Boolean = True); + /// Get checked state for item at Index + function GetChecked(const Index: Integer): Boolean; + /// Set checked state for item at Index + procedure SetChecked(const Index: Integer; const Value: Boolean); + /// Get/set checked state of item at Index + property Checked[const Index: Integer]: Boolean read GetChecked write SetChecked; default; + end; + + /// Extension of IListViewAdapter for items with text and indexes. + /// Implemented by TAppearanceListViewItems + IListViewTextProvider = interface + ['{C6D52C15-423D-4B2F-AC87-7D7D47A9C7CC}'] + /// Get primary text of item at Index + function GetText(const Index: Integer): string; + /// Get index title at Index + function GetIndexTitle(const Index: Integer): string; + /// Primary text of item at Index + property Text[const Index: Integer]: string read GetText; + /// Index title of item at Index + property IndexTitle[const Index: Integer]: string read GetIndexTitle; + end; + + /// Extension of IListViewAdapter for items with embedded text buttons. + /// Implemented by TAppearanceListViewItems + IListViewTextButtonProvider = interface + ['{42CC3926-0A23-465B-9ECE-229C60B3BA8E}'] + /// Get drawable of text button for item at Index + function GetTextButtonDrawable(const Index: Integer): TListItemTextButton; + /// Drawable of text button for item at Index + property TextButtonDrawable[const Index: Integer]: TListItemTextButton read GetTextButtonDrawable; + end; + + /// Extension of IListViewAdapter for items with glyph buttons. + /// Implemented by TAppearanceListViewItems + IListViewGlyphButtonProvider = interface + ['{64FF4B01-E378-4F40-A9A5-E4C1A7C942D6}'] + /// Get drawable of button for item at Index + function GetGlyphButtonDrawable(const Index: Integer): TListItemGlyphButton; + /// Drawable of button for item at Index + property GlyphButtonDrawable[const Index: Integer]: TListItemGlyphButton read GetGlyphButtonDrawable; + end; + + /// Extension of IListViewAdapter for items with data. + /// Implemented by TAppearanceListViewItems + IListViewExtrasProvider = interface + ['{0BCFB611-3763-4C49-974F-1104F6116D6E}'] + /// Get indexed data DataIndex for item at Index + function GetItemData(const Index: Integer; const DataIndex: string): TValue; + /// Set indexed data DataIndex for item at Index + procedure SetItemData(const Index: Integer; const DataIndex: string; const AValue: TValue); + /// True if item at Index has indexed data DataIndex + function GetHasData(const Index: Integer; const DataIndex: string): Boolean; + /// Get tag of item at Index + function GetItemTag(const Index: Integer): NativeInt; + /// Set tag of item at Index + procedure SetItemTag(const Index: Integer; const AValue: NativeInt); + + /// Indexed data DataIndex of item at Index + property Data[const Index: Integer; const DataIndex: string]: TValue read GetItemData write SetItemData; + /// Tag of item at Index + property Tag[const Index: Integer]: NativeInt read GetItemTag write SetItemTag; + /// True if item at Index has Indexed data DataIndex + property HasData[const Index: Integer; const DataIndex: string]: Boolean read GetHasData; + end; + + /// Extension of IListViewAdapter items of which can be filtered by a search box. + /// Implemented by TAppearanceListViewItems + IListViewFilterable = interface + ['{02F85899-8787-4378-9622-105820EB4577}'] + /// Get filter predicate + function GetFilterPredicate: TFilterPredicate; + /// Set filter predicate + procedure SetFilterPredicate(const Value: TFilterPredicate); + /// Return a complete list of all items regardless of filter state + function GetUnfilteredItems: TListItemsList; + /// True if filter is applied + function GetFiltered: Boolean; + + /// Notifies the adapter that all items have been deleted + procedure ItemsCleared; + /// Notifies the adapter that item at Index have been deleted + procedure ItemDeleted(const Index: Integer); + /// Notifies the adapter that item at Index have been added + procedure ItemAdded(const Index: Integer; const Item: TListItem); + + /// Complete list of all items regardless of filter state + property UnfilteredItems: TListItemsList read GetUnfilteredItems; + /// True if filter is applied + property Filtered: Boolean read GetFiltered; + /// Filter predicate + property Filter: TFilterPredicate read GetFilterPredicate write SetFilterPredicate; + end; + +implementation + +uses + System.RTLConsts, System.UIConsts, System.Math, System.Messaging, System.TypInfo, FMX.Consts, FMX.Styles, + FMX.Platform; + +type + TDrawableElement = (FontSize, FontFamily, FontStyle, TextColor, SelectedTextColor, TextShadowColor, PressedTextColor); + TDrawableElements = set of TDrawableElement; + + TDefaultSettingsHelper = class + private + class constructor Create; + class destructor Destroy; + class var FItems: TDictionary; + public + class procedure Register(const ADrawable: TListItemDrawable); + class procedure Unregister(const ADrawable: TListItemDrawable); + + class function IsDefault(const ADrawable: TListItemDrawable; const AElement: TDrawableElement): Boolean; + class procedure SetDefault(const ADrawable: TListItemDrawable; const AElement: TDrawableElement; const ADefault: Boolean); + end; + +{$REGION 'Global Functions'} + +const + GlyphOffsetX = -3; + MinusOffsetX = 7; + OpacityEpsilon = 0.003921569; // 1 / 255 + AnimationDeltaEpsilon = 0.01; + +type + TOpenControl = class(TControl); +{$IFDEF DRAW_ITEM_MARGINS} +var + TempBrush: TBrush = nil; + +function MarginBrush: TBrush; +begin + if TempBrush = nil then + TempBrush := TBrush.Create(TBrushKind.Solid, $50808080); + + Result := TempBrush; +end; + +{$ENDIF} + +function GetDeletingUnwantedOpacity(const Parent: TControl): Single; +var + Controller: IListViewController; +begin + if Supports(Parent, IListViewController, Controller) then + if Controller.DeleteModeTransitionAlpha > AnimationDeltaEpsilon then + Result := Max(0, 1 - (Controller.DeleteModeTransitionAlpha * 2)) + else + Result := 1 + else + Result := 1; +end; + +function Blend(const Color1, Color2: TAlphaColor; const Alpha: Single): TAlphaColor; + + function BlendComponent(const Value1, Value2: Integer; const Alpha: Single): Integer; inline; + begin + Result := Value1 + Round((Value2 - Value1) * Alpha); + end; + +begin + TAlphaColorRec(Result).R := BlendComponent(TAlphaColorRec(Color1).R, TAlphaColorRec(Color2).R, Alpha); + TAlphaColorRec(Result).G := BlendComponent(TAlphaColorRec(Color1).G, TAlphaColorRec(Color2).G, Alpha); + TAlphaColorRec(Result).B := BlendComponent(TAlphaColorRec(Color1).B, TAlphaColorRec(Color2).B, Alpha); + TAlphaColorRec(Result).A := BlendComponent(TAlphaColorRec(Color1).A, TAlphaColorRec(Color2).A, Alpha); +end; + + +{$ENDREGION} +{$REGION 'List Item Object'} + +constructor TListItemDrawable.Create(const AOwner: TListItem); +begin + inherited Create; + + if AOwner <> nil then + begin + AOwner.View.Include(Self); + FController := AOwner.Controller; + end; + + FWidth := 0; + FHeight := 0; + FVisible := True; + + FAlign := TListItemAlign.Leading; + FVertAlign := TListItemAlign.Leading; + + FUpdating := 0; + NeedRepaint := False; + FOpacity := 1; + + TDefaultSettingsHelper.Register(Self); +end; + +destructor TListItemDrawable.Destroy; +begin + TDefaultSettingsHelper.Unregister(Self); + FController := nil; + FCallback := nil; + FPlaceOffsetX.Free; + inherited; +end; + +procedure TListItemDrawable.DoResize; +begin + Invalidate; +end; + +procedure TListItemDrawable.DoAlignChanged; +begin +end; + +procedure TListItemDrawable.DoOpacityChange; +begin + Invalidate; +end; + +procedure TListItemDrawable.DoPlaceOffsetChanged; +begin +end; + +procedure TListItemDrawable.DoSelect; +begin + if Assigned(FOnSelect) then + FOnSelect(Self); +end; + +procedure TListItemDrawable.DoUpdateValuesFromResources(const Resources: TListItemStyleResources; + const Purpose: TListItemPurpose); +begin +end; + +procedure TListItemDrawable.SetSize(const Value: TPointF); +var + Adjusted: TPointF; +begin + Adjusted := Value; + if Adjusted.X < 0 then + Adjusted.X := 0; + if Adjusted.Y < 0 then + Adjusted.Y := 0; + + if not SameValue(Adjusted.X, FWidth, TEpsilon.Position) or + not SameValue(Adjusted.Y, FHeight, TEpsilon.Position) then + begin + FWidth := Adjusted.X; + FHeight := Adjusted.Y; + DoResize; + end; +end; + +function TListItemDrawable.GetSize: TPointF; +begin + Result := TPointF.Create(FWidth, FHeight); +end; + +procedure TListItemDrawable.SetOneDimension(const Index: Integer; const Value: Single); +var + NewValue: Single; +begin + NewValue := Value; + if NewValue < 0 then + NewValue := 0; + + case Index of + 0: + if FWidth <> NewValue then + begin + FWidth := NewValue; + DoResize; + end; + 1: + if FHeight <> NewValue then + begin + FHeight := NewValue; + DoResize; + end; + end; +end; + +procedure TListItemDrawable.SetAlign(const Value: TListItemAlign); +begin + if FAlign <> Value then + begin + FAlign := Value; + DoAlignChanged; + Invalidate; + end; +end; + +procedure TListItemDrawable.SetData(const Value: TValue); +begin +end; + +procedure TListItemDrawable.SetInvalidateCallback(const Callback: TListItemCallback); +begin + FCallback := Callback; +end; + +procedure TListItemDrawable.SetVertAlign(const Value: TListItemAlign); +begin + if FVertAlign <> Value then + begin + FVertAlign := Value; + DoAlignChanged; + Invalidate; + end; +end; + +procedure TListItemDrawable.SetVisible(const Value: Boolean); +begin + if FVisible <> Value then + begin + FVisible := Value; + Invalidate; + end; +end; + +function TListItemDrawable.ToString: string; + function GetClassName: string; + begin + if Self <> nil then + Result := ClassName + else + Result := 'TListItemDrawable(nil)'; + end; +begin + Result := Format('%s@%x', [GetClassName, NativeUInt(Self)]); +end; + +procedure TListItemDrawable.UpdateValuesFromResources(const Resources: TListItemStyleResources; + const Purpose: TListItemPurpose); +begin + if FStyleValuesNeedUpdate <> [] then + begin + DoUpdateValuesFromResources(Resources, Purpose); + FStyleValuesNeedUpdate := []; + end; +end; + +procedure TListItemDrawable.UpdateValuesFromStyle; +begin + FStyleValuesNeedUpdate := [Low(TStyleResource)..High(TStyleResource)]; +end; + +procedure TListItemDrawable.SetOpacity(const Value: Single); +var + NewValue: Single; +begin + NewValue := Value; + if NewValue < 0 then + NewValue := 0; + if NewValue > 1 then + NewValue := 1; + + if FOpacity <> NewValue then + begin + FOpacity := NewValue; + DoOpacityChange; + end; +end; + +procedure TListItemDrawable.PlaceOffsetChanged(Sender: TObject); +begin + Invalidate; + DoPlaceOffsetChanged; +end; + +procedure TListItemDrawable.CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); + +{$IFDEF PIXEL_ALIGNMENT} + function PixelAlign(const Value: Single): Single; + begin + Result := Int(Value * SceneScale) / SceneScale + end; +{$ELSE} + function PixelAlign(const Value: Single): Single; inline; + begin + Result := Value; + end; +{$ENDIF} + + procedure AlignValue(Align: TListItemAlign; out Left, Right: Single; + Width, DesLeft, DesRight, CustomOffset: Single); inline; + begin + case Align of + TListItemAlign.Center: + begin + Left := PixelAlign(CustomOffset + (DesLeft + DesRight - Width) * 0.5); + Right := Left + Width; + end; + + TListItemAlign.Trailing: + begin + Right := PixelAlign(CustomOffset + DesRight); + Left := Right - Width; + end; + + else + begin + Left := PixelAlign(CustomOffset + DesLeft); + Right := Left + Width; + end; + end; + end; + +var + LOffset: TPosition; + +begin + LOffset := PlaceOffset; + if (FWidth > 0) and (FWidth < DestRect.Width) then + begin + AlignValue(FAlign, FLocalRect.Left, FLocalRect.Right, FWidth, DestRect.Left, DestRect.Right, LOffset.X); + end + else + begin + FLocalRect.Left := PixelAlign(DestRect.Left + LOffset.X); + FLocalRect.Right := FLocalRect.Left + DestRect.Width; + end; + + if (FHeight > 0) and (FHeight < DestRect.Height) then + begin + AlignValue(FVertAlign, FLocalRect.Top, FLocalRect.Bottom, FHeight, DestRect.Top, DestRect.Bottom, LOffset.Y); + end + else + begin + FLocalRect.Top := PixelAlign(DestRect.Top + LOffset.Y); + FLocalRect.Bottom := FLocalRect.Top + DestRect.Height; + end; +end; + +procedure TListItemDrawable.BeginUpdate; +begin + Inc(FUpdating); +end; + +procedure TListItemDrawable.EndUpdate; +begin + if FUpdating > 0 then + begin + Dec(FUpdating); + if (FUpdating <= 0) and NeedRepaint then + begin + NeedRepaint := False; + Invalidate; + end; + end; +end; + +function TListItemDrawable.InLocalRect(const Point: TPointF): Boolean; +begin + Result := FLocalRect.Contains(Point); +end; + +procedure TListItemDrawable.Invalidate; +begin + if FUpdating < 1 then + begin + if Assigned(FCallback) then + FCallback(nil, Self, TListItemCallbackOp.InvalidateOwner); + end + else + NeedRepaint := True; +end; + + +function TListItemDrawable.ObjectAtPoint(const Point: TPointF): TControl; +begin + Result := nil; +end; + +function TListItemDrawable.GetData: TValue; +begin + Result := TValue.Empty; +end; + +function TListItemDrawable.GetPlaceOffset: TPosition; +begin + if FPlaceOffsetX = nil then + begin + FPlaceOffsetX := TPosition.Create(TPointF.Zero); + FPlaceOffsetX.OnChange := PlaceOffsetChanged; + end; + Result := FPlaceOffsetX; +end; + +function TListItemDrawable.GetRenderPassCount: Integer; +begin + Result := 0; +end; + +function TListItemDrawable.MouseDown(const Button: TMouseButton; const Shift: TShiftState; + const MousePos: TPointF): Boolean; +begin + Result := False; +end; + +procedure TListItemDrawable.MouseMove(const Shift: TShiftState; const MousePos: TPointF); +begin +end; + +procedure TListItemDrawable.MouseUp(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF); +begin +end; + +{$ENDREGION} +{$REGION 'List Item Text'} + +constructor TListItemText.Create(const AOwner: TListItem); +begin + inherited; + + FTextColor := claBlack; + FSelectedTextColor := claWhite; + FTextShadowColor := 0; + FTextAlign := TTextAlign.Leading; + FTextVertAlign := TTextAlign.Leading; + + UpdateValuesFromStyle; + + FWordWrap := False; + FTrimming := TTextTrimming.None; +end; + +destructor TListItemText.Destroy; +begin + FreeAndNil(FTextLayout); + FFontX.Free; + FTextShadowOffsetX.Free; + inherited; +end; + +procedure TListItemText.DoUpdateValuesFromResources(const Resources: TListItemStyleResources; + const Purpose: TListItemPurpose); +var + ResourceFont: TFont; +begin + ResourceFont := nil; + if Resources <> nil then + begin + if Purpose <> TListItemPurpose.None then + begin + ResourceFont := Resources.HeaderTextFont; + if (TStyleResource.TextColor in FStyleValuesNeedUpdate) and (Resources.HeaderTextColor > 0) and + TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.TextColor) then + FTextColor := Resources.HeaderTextColor; + if (TStyleResource.TextShadowColor in FStyleValuesNeedUpdate) and (Resources.HeaderTextShadowColor > 0) and + TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.TextShadowColor) then + FTextShadowColor := Resources.HeaderTextShadowColor; + FTextVertAlign := TTextAlign.Center; + end + else if FIsDetailText then + begin + ResourceFont := Resources.DetailTextFont; + if (TStyleResource.TextColor in FStyleValuesNeedUpdate) and (Resources.DetailTextColor > 0) and + TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.TextColor) then + FTextColor := Resources.DetailTextColor; + if (TStyleResource.SelectedTextColor in FStyleValuesNeedUpdate) and (Resources.DefaultTextSelectedColor > 0) and + TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.SelectedTextColor) then + FSelectedTextColor := Resources.DefaultTextSelectedColor; + end + else + begin + ResourceFont := Resources.DefaultTextFont; + if (TStyleResource.TextColor in FStyleValuesNeedUpdate) and (Resources.DefaultTextColor > 0) and + TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.TextColor) then + FTextColor := Resources.DefaultTextColor; + + if (TStyleResource.SelectedTextColor in FStyleValuesNeedUpdate) and (Resources.DefaultTextSelectedColor > 0) and + TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.SelectedTextColor) then + FSelectedTextColor := Resources.DefaultTextSelectedColor; + end; + end; + + if ResourceFont <> nil then + begin + if FFontX <> nil then + begin + FFontX.OnChanged := nil; + try + if (TStyleResource.FontFamily in FStyleValuesNeedUpdate) and TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.FontFamily) then + FFontX.Family := ResourceFont.Family; + if (TStyleResource.FontSize in FStyleValuesNeedUpdate) and TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.FontSize) then + FFontX.Size := ResourceFont.Size; + if (TStyleResource.FontStyle in FStyleValuesNeedUpdate) and TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.FontStyle) then + FFontX.StyleExt := ResourceFont.StyleExt; + finally + FFontX.OnChanged := FontChanged; + end; + end + else + begin + Font.Assign(ResourceFont); + Font.OnChanged := FontChanged; + end; + + FStyleValuesNeedUpdate := FStyleValuesNeedUpdate - TextResources; + end + else + Font.SetSettings(DefaultFontFamily, DefaultFontSize, Font.StyleExt); +end; + +procedure TListItemText.DoResize; +begin + inherited; + LayoutChanged := True; +end; + +procedure TListItemText.FontChanged(Sender: TObject); +var + NewSettings: TFontSettings; +begin + NewSettings := FontSettingsSnapshot; + if not SameStr(NewSettings.Family, FFontSettings.Family) then + begin + TDefaultSettingsHelper.SetDefault(Self, TDrawableElement.FontFamily, False); + Exclude(FStyleValuesNeedUpdate, TStyleResource.FontFamily); + end; + if not SameValue(NewSettings.Size, FFontSettings.Size, TEpsilon.Position) then + begin + TDefaultSettingsHelper.SetDefault(Self, TDrawableElement.FontSize, False); + Exclude(FStyleValuesNeedUpdate, TStyleResource.FontSize); + end; + if NewSettings.Style <> FFontSettings.Style then + begin + TDefaultSettingsHelper.SetDefault(Self, TDrawableElement.FontStyle, False); + Exclude(FStyleValuesNeedUpdate, TStyleResource.FontStyle); + end; + FFontSettings := NewSettings; + + LayoutChanged := True; + Invalidate; +end; + +function TListItemText.FontSettingsSnapshot: TFontSettings; +begin + Result.Size := FFontX.Size; + Result.Style := FFontX.Style; + Result.Family := FFontX.Family; +end; + +function TListItemText.GetData: TValue; +begin + Result := Text; +end; + +function TListItemText.GetFont: TFont; +begin + if FFontX = nil then + begin + FFontX := TFont.Create; + FFontX.OnChanged := FontChanged; + end; + FFontSettings := FontSettingsSnapshot; + Result := FFontX; +end; + +function TListItemText.GetShadowOffset: TPosition; +begin + if FTextShadowOffsetX = nil then + begin + FTextShadowOffsetX := TPosition.Create(ShadowOffset); + FTextShadowOffsetX.OnChange := TextShadowOffsetChanged; + end; + Result := FTextShadowOffsetX; +end; + +procedure TListItemText.TextShadowOffsetChanged(Sender: TObject); +begin + Invalidate; +end; + +procedure TListItemText.SetTextShadowColor(const Value: TAlphaColor); +begin + if FTextShadowColor <> Value then + begin + FTextShadowColor := Value; + TDefaultSettingsHelper.SetDefault(Self, TDrawableElement.TextShadowColor, Value = TAlphaColorRec.Null); + Exclude(FStyleValuesNeedUpdate, TStyleResource.TextShadowColor); + Invalidate; + end; +end; + +procedure TListItemText.SetText(const Value: string); +begin + if FText <> Value then + begin + FText := Value; + LayoutChanged := True; + Invalidate; + end; +end; + +procedure TListItemText.SetTextAlign(const Value: TTextAlign); +begin + if FTextAlign <> Value then + begin + FTextAlign := Value; + LayoutChanged := True; + Invalidate; + end; +end; + +procedure TListItemText.SetTextColor(const Value: TAlphaColor); +begin + if FTextColor <> Value then + begin + FTextColor := Value; + TDefaultSettingsHelper.SetDefault(Self, TDrawableElement.TextColor, Value = TAlphaColorRec.Null); + Exclude(FStyleValuesNeedUpdate, TStyleResource.TextColor); + LayoutChanged := True; + Invalidate; + end; +end; + +procedure TListItemText.SetData(const AValue: TValue); +begin + if AValue.IsEmpty then + Text := '' + else + Text := AValue.ToString; +end; + +procedure TListItemText.SetIsDetailText(const Value: Boolean); +begin + if FIsDetailText <> Value then + FIsDetailText := Value; +end; + +procedure TListItemText.SetSelectedTextColor(const Value: TAlphaColor); +begin + if FSelectedTextColor <> Value then + begin + FSelectedTextColor := Value; + TDefaultSettingsHelper.SetDefault(Self, TDrawableElement.SelectedTextColor, Value = TAlphaColorRec.Null); + Exclude(FStyleValuesNeedUpdate, TStyleResource.SelectedTextColor); + LayoutChanged := True; + Invalidate; + end; +end; + +procedure TListItemText.SetTextVertAlign(const Value: TTextAlign); +begin + if FTextVertAlign <> Value then + begin + FTextVertAlign := Value; + LayoutChanged := True; + Invalidate; + end; +end; + +procedure TListItemText.SetTrimming(const Value: TTextTrimming); +begin + if FTrimming <> Value then + begin + FTrimming := Value; + LayoutChanged := True; + Invalidate; + end; +end; + +procedure TListItemText.SetWordWrap(const Value: Boolean); +begin + if FWordWrap <> Value then + begin + FWordWrap := Value; + LayoutChanged := True; + Invalidate; + end; +end; + +procedure TListItemText.CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); +var + NewRect: TRectF; + DeadSpace: Single; +begin + if TListItemDrawState.EditMode in DrawStates then + begin + NewRect := DestRect; + + if Item.Controller <> nil then + begin + DeadSpace := Item.Controller.GetItemEditOffset(Item) * Item.Controller.EditModeTransitionAlpha; + NewRect.Left := NewRect.Left + DeadSpace; + + if FIsDetailText then + NewRect.Right := Max(NewRect.Right + DeadSpace, NewRect.Left); + end; + + inherited CalculateLocalRect(NewRect, SceneScale, DrawStates, Item); + + // Text should no go over right client area when moved in edit mode. + if Item.Controller <> nil then + FLocalRect.Right := Min(FLocalRect.Right, Item.Controller.GetClientMargins.Right); + end + else + inherited; + + { Delete button cuts through the entire right side, so it needs to be marginalized globally. + This is because LocalRect may actually leave the boundaries of DestRect depending on PlacementOffset. } + if (TListItemDrawState.Deleting in DrawStates) and (Item.Controller <> nil) then + FLocalRect.Right := Min(FLocalRect.Right, Item.Controller.GetItemDeleteCutoff(Item)); +end; + +procedure TListItemText.Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates; const Resources: TListItemStyleResources; const Params: TListItemDrawable.TParams; const SubPassNo: Integer = 0); +var + CurColor: TAlphaColor; + SelectedAlpha, LOpacity: Single; +begin + if SubPassNo <> 0 then + Exit; + + if FTextLayout = nil then + begin + FTextLayout := TTextLayoutManager.TextLayoutByCanvas(Canvas.ClassType).Create(Canvas); + LayoutChanged := True + end; + + if not LayoutChanged then + LayoutChanged := (FTextLayout.MaxSize.X <> FLocalRect.Width) or (FTextLayout.MaxSize.Y <> FLocalRect.Height); + + CurColor := FTextColor; + LOpacity := Params.AbsoluteOpacity; + if TListItemDrawState.Selected in DrawStates then + begin + SelectedAlpha := Params.ItemSelectedAlpha; + + if (SelectedAlpha > 0) and (SelectedAlpha < 1) then + CurColor := Blend(CurColor, FSelectedTextColor, SelectedAlpha) + else if SelectedAlpha >= 1 then + CurColor := FSelectedTextColor; + end; + + if (not LayoutChanged) and (FTextLayout.Color <> CurColor) then + LayoutChanged := True; + + if LayoutChanged then + begin + FTextLayout.BeginUpdate; + try + FTextLayout.HorizontalAlign := FTextAlign; + FTextLayout.VerticalAlign := FTextVertAlign; + FTextLayout.Font := Font; + FTextLayout.Color := CurColor; + FTextLayout.RightToLeft := False; + FTextLayout.MaxSize := TPointF.Create(FLocalRect.Width, FLocalRect.Height); + FTextLayout.Text := FText; + FTextLayout.Trimming := FTrimming; + FTextLayout.WordWrap := FWordWrap; + finally + FTextLayout.EndUpdate; + LayoutChanged := False; + end; + end; + + if TAlphaColorRec(FTextShadowColor).A > 0 then + begin + FTextLayout.BeginUpdate; + FTextLayout.Opacity := LOpacity; + FTextLayout.Color := FTextShadowColor; + FTextLayout.TopLeft := FLocalRect.TopLeft + TextShadowOffset.Point; + FTextLayout.EndUpdate; + FTextLayout.RenderLayout(Canvas); + + FTextLayout.BeginUpdate; + FTextLayout.Color := CurColor; + FTextLayout.EndUpdate; + end; + +{$IFDEF DRAW_ITEM_MARGINS} + if FIsDetailText then + MarginBrush.Color := $50FF0000 + else + MarginBrush.Color := $5000FF00; + + Canvas.FillRect(LocalRect, 0, 0, AllCorners, 1, MarginBrush); +{$ENDIF} + + FTextLayout.BeginUpdate; + FTextLayout.Opacity := LOpacity; + FTextLayout.TopLeft := FLocalRect.TopLeft; + FTextLayout.EndUpdate; + FTextLayout.RenderLayout(Canvas); +end; + +{$ENDREGION} +{$REGION 'List Item Dummy'} + +procedure TListItemDummy.Render(const Canvas: TCanvas; const DrawItemIndex: Integer; + const DrawStates: TListItemDrawStates; const Resources: TListItemStyleResources; + const Params: TListItemDrawable.TParams; const SubPassNo: Integer = 0); +begin +end; + +{$ENDREGION} +{$REGION 'List Item Image'} + +constructor TListItemImage.Create(const AOwner: TListItem); +begin + inherited; + FImageIndex := -1; + FImageScalingMode := TImageScalingMode.StretchWithAspect; +end; + +destructor TListItemImage.Destroy; +begin + FStaticBitmap.Free; + inherited; +end; + +function TListItemImage.GetBitmap: TBitmap; +begin + if FOwnsBitmap then + Result := FStaticBitmap + else + Result := FReferBitmap; +end; + +procedure TListItemImage.SetBitmap(const Value: TBitmap); +begin + if FOwnsBitmap then + FStaticBitmap := Value + else + FReferBitmap := Value; + if FImageSource <> TImageSource.ImageList then + Invalidate; +end; + +procedure TListItemImage.SetData(const Value: TValue); +var + LBitmap: TBitmap; + LIndex: Integer; + LEIndex: Extended; + LSIndex: string; + LCode: Integer; +begin + if Value.TryAsType(LBitmap) then + Bitmap := LBitmap + else if Value.TryAsType(LIndex) then + SetImageIndex(LIndex) + else if Value.TryAsType(LSIndex) then + begin + Val(LSIndex, LIndex, LCode); + if LCode = 0 then + SetImageIndex(LIndex); + end + else if Value.TryAsType(LEIndex) then + SetImageIndex(Trunc(LEIndex)); +end; + +procedure TListItemImage.SetOwnsBitmap(const Value: Boolean); +begin + if FOwnsBitmap = Value then + Exit; + + if FOwnsBitmap and (not Value) then + begin + FReferBitmap := FStaticBitmap; + FStaticBitmap := nil; + end + else if (not FOwnsBitmap) and Value then + begin + FStaticBitmap := FReferBitmap; + FReferBitmap := nil; + end; + + FOwnsBitmap := Value; +end; + +procedure TListItemImage.SetImageIndex(const Value: TImageIndex); +begin + if FImageIndex <> Value then + begin + FImageIndex := Value; + Invalidate; + end; +end; + +procedure TListItemImage.SetImageScalingMode(const Value: TImageScalingMode); +begin + if FImageScalingMode <> Value then + begin + FImageScalingMode := Value; + Invalidate; + end; +end; + +procedure TListItemImage.SetSrcRect(const Value: TRectF); +begin + if FSrcRect <> Value then + begin + FSrcRect := Value; + Invalidate; + end; +end; + +procedure TListItemImage.CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); +var + NewRect: TRectF; + DeadSpace: Single; +begin + if TListItemDrawState.EditMode in DrawStates then + begin + NewRect := DestRect; + + if Item.Controller <> nil then + begin + DeadSpace := Item.Controller.GetItemEditOffset(Item) * Item.Controller.EditModeTransitionAlpha; + NewRect.Left := NewRect.Left + DeadSpace; + end; + + inherited CalculateLocalRect(NewRect, SceneScale, DrawStates, Item); + end + else + inherited; +end; + +function TListItemImage.GetImageSource: TImageSource; +begin + if FImageIndex <> -1 then + FImageSource := TImageSource.ImageList + else if GetBitmap <> nil then + FImageSource := TImageSource.Bitmap + else + FImageSource := TImageSource.None; + Result := FImageSource; +end; + +procedure TListItemImage.FitInto(const Bitmap: TBitmap; var InputRect, DestinationRect: TRectF); + procedure ClipRects(var InpRect, DestRect: TRectF; const LocalRect: TRectF); + var + Delta: Single; + begin + if DestRect.Right > LocalRect.Right then + begin + Delta := 1 - ((DestRect.Right - LocalRect.Right) / DestRect.Width); + + InpRect.Right := InpRect.Left + InpRect.Width * Delta; + DestRect.Right := LocalRect.Right; + end; + + if DestRect.Bottom > LocalRect.Bottom then + begin + Delta := 1 - ((DestRect.Bottom - LocalRect.Bottom) / DestRect.Height); + + InpRect.Bottom := InpRect.Top + InpRect.Height * Delta; + DestRect.Bottom := LocalRect.Bottom; + end; + end; + +var + LocRect, InpRect, DestRect: TRectF; + Aspect: Single; + XAspect, YAspect: Single; +begin + LocRect := FLocalRect; + + if FSrcRect.Width < 1 then + begin + InpRect.Left := 0; + InpRect.Right := Bitmap.Width; + end + else + begin + InpRect.Left := FSrcRect.Left; + InpRect.Right := FSrcRect.Right; + end; + + if FSrcRect.Height < 1 then + begin + InpRect.Top := 0; + InpRect.Bottom := Bitmap.Height; + end + else + begin + InpRect.Top := FSrcRect.Top; + InpRect.Bottom := FSrcRect.Bottom; + end; + + case FImageScalingMode of + TImageScalingMode.Original: + begin + DestRect.Left := LocRect.Left; + DestRect.Top := LocRect.Top; + DestRect.Right := DestRect.Left + InpRect.Width; + DestRect.Bottom := DestRect.Top + InpRect.Height; + + ClipRects(InpRect, DestRect, LocRect); + + // Center image + DestRect.Offset((FLocalRect.Right - DestRect.Right) / 2, (FLocalRect.Bottom - DestRect.Bottom) / 2); + end; + + TImageScalingMode.Stretch: + DestRect := LocRect; + + TImageScalingMode.StretchWithAspect: + begin + // Calc ratios + if InpRect.Width > 0 then + XAspect := FLocalRect.Width / InpRect.Width + else + XAspect := 1; + + if InpRect.Height > 0 then + YAspect := FLocalRect.Height / InpRect.Height + else + YAspect := 1; + + // Use smallest ratio + if YAspect < XAspect then + Aspect := YAspect + else + Aspect := XAspect; + + DestRect := FLocalRect; + DestRect.Right := DestRect.Left + InpRect.Width * Aspect; + DestRect.Bottom := DestRect.Top + InpRect.Height * Aspect; + // Center image + DestRect.Offset((FLocalRect.Right - DestRect.Right) / 2, (FLocalRect.Bottom - DestRect.Bottom) / 2); + end; + end; + + InputRect := InpRect; + DestinationRect := DestRect; +end; + +procedure TListItemImage.Render(const Canvas: TCanvas; const DrawItemIndex: Integer; + const DrawStates: TListItemDrawStates; const Resources: TListItemStyleResources; + const Params: TListItemDrawable.TParams; const SubPassNo: Integer = 0); +var + Bitmap: TBitmap; + InpRect, DestRect: TRectF; +begin + if SubPassNo <> 0 then + Exit; + +{$IFDEF DRAW_ITEM_MARGINS} + MarginBrush.Color := $50808000; + Canvas.FillRect(LocalRect, 0, 0, AllCorners, 1, MarginBrush); +{$ENDIF} + + if (ImageSource = TImageSource.ImageList) and (Params.Images <> nil) then + Bitmap := Params.Images.Bitmap(TPointF(FLocalRect.Size) * Canvas.Scale, FImageIndex) + else + Bitmap := GetBitmap; + + if Bitmap = nil then + Exit; + + FitInto(Bitmap, InpRect, DestRect); + + Canvas.DrawBitmap(Bitmap, InpRect, DestRect, Params.AbsoluteOpacity); +end; + +{$ENDREGION} +{$REGION 'List Item Simple Control'} + +constructor TListItemSimpleControl.Create(const AOwner: TListItem); +begin + inherited; + + FEnabled := True; + +{$IF DEFINED(IOS) OR DEFINED(ANDROID)} + FTouchExpand := 8; +{$ENDIF} +end; + +procedure TListItemSimpleControl.SetEnabled(const Value: Boolean); +begin + if FEnabled <> Value then + begin + FEnabled := Value; + DoEnabledChange; + end; +end; + +function TListItemSimpleControl.PointInLocalRect(const Pos: TPointF): Boolean; +var + LocRect: TRectF; +begin + LocRect := FLocalRect; + LocRect.Inflate(FTouchExpand, FTouchExpand); + + Result := LocRect.Contains(Pos); +end; + +function TListItemSimpleControl.IsClickOpaque: Boolean; +begin + Result := True; +end; + +function TListItemSimpleControl.MouseDown(const Button: TMouseButton; const Shift: TShiftState; + const MousePos: TPointF): Boolean; +begin + Result := False; + + if (Button = TMouseButton.mbLeft) and FEnabled and PointInLocalRect(MousePos) and IsClickOpaque then + begin + FPressed := True; + FMouseOver := True; + + Invalidate; + + Result := True; + end; +end; + +procedure TListItemSimpleControl.MouseMove(const Shift: TShiftState; const MousePos: TPointF); +begin +// On iOS, once "mouse" is outside of control area, the control never regains pressed/over state, requiring a new tap. +// FMouseOver := FMouseOver and PointInLocalRect(MousePos); + FMouseOver := PointInLocalRect(MousePos); + Invalidate; +end; + +procedure TListItemSimpleControl.MouseUp(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF); +var + ShouldClick: Boolean; +begin + if FPressed then + begin + ShouldClick := FMouseOver; + + FPressed := False; + FMouseOver := False; + + if ShouldClick then + DoClick; + + Invalidate; + end; +end; + +procedure TListItemSimpleControl.DoClick; +begin + FCallback(nil, Self, TListItemCallbackOp.Click); +end; + +procedure TListItemSimpleControl.DoEnabledChange; +begin + Invalidate; +end; + +procedure TListItemSimpleControl.Click; +begin + DoClick; +end; + +{$ENDREGION} +{$REGION 'List Item Accessory'} + +constructor TListItemAccessory.Create(const AOwner: TListItem); +begin + inherited; + +end; + +procedure TListItemAccessory.CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); +var + NewRect: TRectF; +begin + if TListItemDrawState.EditMode in DrawStates then + begin + NewRect := DestRect; + if FController <> nil then + NewRect.Offset(FController.GetItemEditOffset(Item) * FController.EditModeTransitionAlpha, 0); + + inherited CalculateLocalRect(NewRect, SceneScale, DrawStates, Item); + end + else + inherited; +end; + +procedure TListItemAccessory.Render(const Canvas: TCanvas; const DrawItemIndex: Integer; + const DrawStates: TListItemDrawStates; const Resources: TListItemStyleResources; + const Params: TListItemDrawable.TParams; const SubPassNo: Integer = 0); +var + Image: TStyleObject; + TempOpacity, SelectedAlpha: Single; +begin + if (SubPassNo <> 0) or (TListItemDrawState.Deleting in DrawStates) then + Exit; + + TempOpacity := Params.AbsoluteOpacity; + + + + + + if TempOpacity < OpacityEpsilon then + Exit; + +{$IFDEF DRAW_ITEM_MARGINS} + MarginBrush.Color := $500040FF; + Canvas.FillRect(LocalRect, 0, 0, AllCorners, 1, MarginBrush); +{$ENDIF} + + if Resources <> nil then + begin + if TListItemDrawState.Selected in DrawStates then + begin // Apply crossfading between selected and non-selected states. + SelectedAlpha := Params.ItemSelectedAlpha; + + if SelectedAlpha < 1 then + begin + Image := Resources.AccessoryImages[FAccessoryType].Normal; + + if Image <> nil then + Image.DrawToCanvas(Canvas, FLocalRect, TempOpacity * (1 - SelectedAlpha)); + end; + + if SelectedAlpha > 0 then + begin + Image := Resources.AccessoryImages[FAccessoryType].Selected; + + if Image <> nil then + Image.DrawToCanvas(Canvas, FLocalRect, TempOpacity * SelectedAlpha); + end; + end + else + begin // Draw normal non-selected image. + Image := Resources.AccessoryImages[FAccessoryType].Normal; + + if Image <> nil then + Image.DrawToCanvas(Canvas, FLocalRect, TempOpacity); + end; + end; +end; + +procedure TListItemAccessory.SetAccessoryType(Value: TAccessoryType); +begin + if FAccessoryType <> Value then + begin + FAccessoryType := Value; + Invalidate; + end; +end; + +{$ENDREGION} +{$REGION 'List Item Glyph Button'} + +constructor TListItemGlyphButton.Create(const AOwner: TListItem); +begin + inherited; + FTransitionEnabled := False; + FTransitionAlpha := 0; +end; + +destructor TListItemGlyphButton.Destroy; +begin + FreeAndNil(FTransitionTimer); + inherited; +end; + +procedure TListItemGlyphButton.SetButtonType(const Value: TGlyphButtonType); +begin + if FButtonType <> Value then + begin + FButtonType := Value; + Invalidate; + end; +end; + +procedure TListItemGlyphButton.SetData(const AValue: TValue); +var + LChecked: Boolean; +begin + if AValue.TryAsType(LChecked) then + Checked := LChecked; +end; + +procedure TListItemGlyphButton.InitCheckedTransition; +begin + FTransitionEnabled := True; + + TPlatformServices.Current.SupportsPlatformService(IFMXTimerService, FTimerService); + + if FTransitionTimer = nil then + begin + FTransitionTimer := TTimer.Create(nil); + FTransitionTimer.Enabled := False; + FTransitionTimer.Interval := Round(1000 / CheckedAnimationFrameRate); + FTransitionTimer.OnTimer := TransitionTimerNotify; + end; + + FTransitionTimer.Enabled := True; + + if FTimerService <> nil then + FTransitionStartTicks := FTimerService.GetTick + else + FTransitionStartTicks := 0; +end; + +procedure TListItemGlyphButton.ResetCheckedTransition; +begin + FTransitionEnabled := False; + + if FTransitionTimer <> nil then + begin + FTransitionTimer.Enabled := False; + FTransitionTimer.Parent := nil; + FreeAndNil(FTransitionTimer); + end; + + if FTimerService <> nil then + FTimerService := nil; + + if FChecked then + FTransitionAlpha := 1 + else + FTransitionAlpha := 0; +end; + +procedure TListItemGlyphButton.TransitionTimerNotify(Sender: TObject); +const + CheckedAnimationIncrement = 1 / (60 * CheckedAnimationDuration); +begin + if FChecked then + begin + if FTimerService <> nil then + FTransitionAlpha := Min(Abs(FTimerService.GetTick - FTransitionStartTicks) / CheckedAnimationDuration, 1) + else + FTransitionAlpha := FTransitionAlpha + CheckedAnimationIncrement; + + if FTransitionAlpha >= 1 then + ResetCheckedTransition; + end + else + begin + if FTimerService <> nil then + FTransitionAlpha := Max(1 - (Abs(FTimerService.GetTick - FTransitionStartTicks) / CheckedAnimationDuration), 0) + else + FTransitionAlpha := FTransitionAlpha - CheckedAnimationIncrement; + + if FTransitionAlpha <= 0 then + ResetCheckedTransition; + end; + + Invalidate; +end; + +procedure TListItemGlyphButton.DoClick; +begin + inherited; + if not FClickOnSelect then + FCallback(nil, Self, TListItemCallbackOp.Click); +end; + +procedure TListItemGlyphButton.SetChecked(const Value: Boolean); +begin + if FChecked <> Value then + begin + InitCheckedTransition; + FChecked := Value; + if not FTransitionEnabled then + ResetCheckedTransition; + Invalidate; + end; +end; + +procedure TListItemGlyphButton.DoSelect; +begin + inherited; + if FClickOnSelect then + FCallback(nil, Self, TListItemCallbackOp.Click); +end; + +function TListItemGlyphButton.MouseDown(const Button: TMouseButton; const Shift: TShiftState; + const MousePos: TPointF): Boolean; +begin + if not FClickOnSelect then + Result := inherited MouseDown(Button, Shift, MousePos) + else + Result := False; +end; + +procedure TListItemGlyphButton.CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); +var + NewRect: TRectF; +begin + if TListItemDrawState.EditMode in DrawStates then + begin + NewRect := DestRect; + + if Item.Controller <> nil then + NewRect.Offset(Item.Controller.GetItemEditOffset(Item) * (Item.Controller.EditModeTransitionAlpha - 1), 0); + + inherited CalculateLocalRect(NewRect, SceneScale, DrawStates, Item); + end + else + inherited; + +{$IF DEFINED(MSWINDOWS) OR DEFINED(OSX)} + FLocalRect := FLocalRect.SnapToPixel(SceneScale, False); +{$ENDIF} +end; + +procedure TListItemGlyphButton.Render(const Canvas: TCanvas; const DrawItemIndex: Integer; + const DrawStates: TListItemDrawStates; const Resources: TListItemStyleResources; + const Params: TListItemDrawable.TParams; const SubPassNo: Integer = 0); +var + ReallyPressed: Boolean; + TempOpacity: Single; + PrevMatrix, M: TMatrix; + LocRect: TRectF; + CenterShift: TPointF; + RenderGlyphButtonType: TGlyphButtonType; +begin + if SubPassNo <> 0 then + Exit; + + ReallyPressed := FPressed and FMouseOver; + + LocRect := FLocalRect; + TempOpacity := Params.AbsoluteOpacity; + + if not FEnabled then + TempOpacity := TempOpacity * DisabledOpacity; + + if TListItemDrawState.EditMode in DrawStates then + TempOpacity := TempOpacity * Max(0.1, FController.EditModeTransitionAlpha * 2 - 1) + else + Exit; + +{$IFDEF DRAW_ITEM_MARGINS} + MarginBrush.Color := $50804020; + Canvas.FillRect(LocalRect, 0, 0, AllCorners, 1, MarginBrush); +{$ENDIF} + + if Resources <> nil then + begin + RenderGlyphButtonType := FButtonType; + + if (RenderGlyphButtonType = TGlyphButtonType.Delete) and (not FController.IsDeleteModeAllowed) then + RenderGlyphButtonType := TGlyphButtonType.Checkbox; + case RenderGlyphButtonType of + TGlyphButtonType.Add: + begin + if (not ReallyPressed) and (Resources.ButtonAddItemStyleImage.Normal <> nil) then + Resources.ButtonAddItemStyleImage.Normal.DrawToCanvas(Canvas, LocRect, TempOpacity); + + if ReallyPressed and (Resources.ButtonAddItemStyleImage.Pressed <> nil) then + Resources.ButtonAddItemStyleImage.Pressed.DrawToCanvas(Canvas, LocRect, TempOpacity); + end; + + TGlyphButtonType.Delete: + begin + LocRect.Left := LocRect.Left + MinusOffsetX; + if Resources.ButtonDeleteItemStyleImage.Normal <> nil then + Resources.ButtonDeleteItemStyleImage.Normal.DrawToCanvas(Canvas, LocRect, TempOpacity); + + if Resources.ButtonDeleteItemStyleImage.Pressed <> nil then + begin + if FTransitionAlpha <= 0 then + Resources.ButtonDeleteItemStyleImage.Pressed.DrawToCanvas(Canvas, LocRect, TempOpacity) + else + begin + PrevMatrix := Canvas.Matrix; + + CenterShift.X := LocRect.Left + LocRect.Width * 0.5; + CenterShift.Y := LocRect.Top + LocRect.Height * 0.5; + + M := TMatrix.CreateTranslation(-CenterShift.X, -CenterShift.Y) + * TMatrix.CreateRotation(-FTransitionAlpha * Pi * 0.5) + * TMatrix.CreateTranslation(CenterShift.X, CenterShift.Y) + * PrevMatrix; + + Canvas.SetMatrix(M); + + Resources.ButtonDeleteItemStyleImage.Pressed.DrawToCanvas(Canvas, LocRect, TempOpacity); + + Canvas.SetMatrix(PrevMatrix); + end; + end; + end; + + TGlyphButtonType.Checkbox: + begin + if (not FChecked) and (Resources.ButtonCheckboxStyleImage.Normal <> nil) then + Resources.ButtonCheckboxStyleImage.Normal.DrawToCanvas(Canvas, LocRect, TempOpacity); + + if FChecked and (Resources.ButtonCheckboxStyleImage.Pressed <> nil) then + Resources.ButtonCheckboxStyleImage.Pressed.DrawToCanvas(Canvas, LocRect, TempOpacity); + end; + end; + end; +end; + +{$ENDREGION} +{$REGION 'List Item Text Button'} + + +{ TListItemTextButton } + +constructor TListItemTextButton.Create(const AOwner: TListItem); +begin + FTextDrawable := TListItemText.Create(nil); + inherited; + + FTintColor := TAlphaColorRec.Null; + FPressedTextColor := claWhite; + + UpdateValuesFromStyle; +end; + +destructor TListItemTextButton.Destroy; +begin + FTextDrawable.Free; + inherited; +end; + +procedure TListItemTextButton.CalculateLocalRect(const DestRect: TRectF; const SceneScale: Single; + const DrawStates: TListItemDrawStates; const Item: TListItem); +begin + inherited; + + FTextDrawable.CalculateLocalRect(DestRect, SceneScale, DrawStates, Item); + FLocalRect := FTextDrawable.FLocalRect; +{$IF DEFINED(MSWINDOWS) OR DEFINED(OSX)} + FLocalRect := FLocalRect.SnapToPixel(SceneScale, False); +{$ENDIF} +end; + +procedure TListItemTextButton.DoAlignChanged; +begin + inherited; + FTextDrawable.Align := Align; + FTextDrawable.VertAlign := VertAlign; +end; + +procedure TListItemTextButton.DoOpacityChange; +begin + inherited; + FTextDrawable.DoOpacityChange; +end; + +procedure TListItemTextButton.DoPlaceOffsetChanged; +begin + FTextDrawable.PlaceOffset.Point := PlaceOffset.Point; +end; + +procedure TListItemTextButton.DoResize; +begin + inherited; + FTextDrawable.SetSize(Size); +end; + + +procedure TListItemTextButton.DoUpdateValuesFromResources(const Resources: TListItemStyleResources; + const Purpose: TListItemPurpose); +var + Surrogate: TListItemStyleResources; +begin + Surrogate := TListItemStyleResources.Create(Resources); + try + case FButtonType of + TTextButtonType.Normal: + begin + Surrogate.DefaultTextFont := Resources.ButtonTextFont; + Surrogate.HeaderTextFont := Resources.ButtonTextFont; + + if TStyleResource.TextColor in FStyleValuesNeedUpdate then + begin + if TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.TextColor) then + FTextColor := Resources.ButtonTextColor; + if TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.PressedTextColor) then + FPressedTextColor := Resources.ButtonTextPressedColor; + end; + end; + TTextButtonType.Delete: + begin + Surrogate.DefaultTextFont := Resources.DeleteButtonTextFont; + Surrogate.HeaderTextFont := Resources.DeleteButtonTextFont; + + if TStyleResource.TextColor in FStyleValuesNeedUpdate then + begin + if TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.TextColor) then + FTextColor := Resources.DeleteButtonTextColor; + if TDefaultSettingsHelper.IsDefault(Self, TDrawableElement.PressedTextColor) then + FPressedTextColor := Resources.DeleteButtonTextPressedColor; + end; + end; + end; + + FTextDrawable.DoUpdateValuesFromResources(Surrogate, Purpose); + + FStyleValuesNeedUpdate := FStyleValuesNeedUpdate - TextResources; + + finally + Surrogate.Free; + end; +end; + +procedure TListItemTextButton.SetData(const AValue: TValue); +begin + if AValue.IsEmpty then + Text := '' + else + Text := AValue.ToString; +end; + +procedure TListItemTextButton.SetInvalidateCallback(const Callback: TListItemCallback); +begin + inherited; + FTextDrawable.InvalidateCallback := FCallback; +end; + +function TListItemTextButton.GetRenderPassCount: Integer; +begin + if FTextDrawable.Text.Length > 0 then + Result := 2 + else + Result := inherited; +end; + +function TListItemTextButton.IsClickOpaque: Boolean; +begin + Result := inherited; + + if Result and (FController <> nil) and (FController.EditModeTransitionAlpha > AnimationDeltaEpsilon) then + Result := False; +end; + +procedure TListItemTextButton.Render(const Canvas: TCanvas; const DrawItemIndex: Integer; + const DrawStates: TListItemDrawStates; const Resources: TListItemStyleResources; + const Params: TListItemDrawable.TParams; const SubPassNo: Integer); +var + ReallyPressed: Boolean; + TempOpacity: Single; +begin + TempOpacity := Params.AbsoluteOpacity; + + if not FEnabled then + TempOpacity := TempOpacity * DisabledOpacity; + + if TListItemDrawState.Deleting in DrawStates then + TempOpacity := TempOpacity * Params.DeletingUnwantedOpacity; + + if (TListItemDrawState.EditMode in DrawStates) and (FController <> nil) then + TempOpacity := TempOpacity * (1 - FController.EditModeTransitionAlpha); + + if TempOpacity < OpacityEpsilon then + Exit; + + ReallyPressed := FPressed and FMouseOver; + + // background + if SubPassNo = 0 then + begin +{$IFDEF DRAW_ITEM_MARGINS} + MarginBrush.Color := $50804020; + Canvas.FillRect(LocalRect, 0, 0, AllCorners, 1, MarginBrush); +{$ENDIF} + + if Resources <> nil then + begin + case FButtonType of + TTextButtonType.Normal: + begin + if (not ReallyPressed) and (Resources.ButtonNormalStyleImage.Normal <> nil) then + Resources.ButtonNormalStyleImage.Normal.DrawToCanvas(Canvas, FLocalRect, FTintColor, TempOpacity); + + if ReallyPressed and (Resources.ButtonNormalStyleImage.Pressed <> nil) then + Resources.ButtonNormalStyleImage.Pressed.DrawToCanvas(Canvas, FLocalRect, FTintColor, TempOpacity); + end; + + TTextButtonType.Delete: + begin + if (not ReallyPressed) and (Resources.ButtonDeleteStyleImage.Normal <> nil) then + Resources.ButtonDeleteStyleImage.Normal.DrawToCanvas(Canvas, FLocalRect, FTintColor, TempOpacity); + + if ReallyPressed and (Resources.ButtonDeleteStyleImage.Pressed <> nil) then + Resources.ButtonDeleteStyleImage.Pressed.DrawToCanvas(Canvas, FLocalRect, FTintColor, TempOpacity); + end; + end; + end; + end + else if SubPassNo = 1 then + begin + if ReallyPressed then + begin + FTextDrawable.FTextColor := FPressedTextColor; + FTextDrawable.FSelectedTextColor := FPressedTextColor; + end + else + begin + FTextDrawable.FTextColor := FTextColor; + FTextDrawable.FSelectedTextColor := FTextColor; + end; + FTextDrawable.Render(Canvas, DrawItemIndex, DrawStates, Resources, Params, 0); + end; +end; + +procedure TListItemTextButton.SetButtonType(const Value: TTextButtonType); +begin + if FButtonType <> Value then + begin + FButtonType := Value; + UpdateValuesFromStyle; + Invalidate; + end; +end; + + +function TListItemTextButton.GetTextColor: TAlphaColor; +begin + Result := FTextDrawable.TextColor; +end; + +function TListItemTextButton.GetTextShadowColor: TAlphaColor; +begin + Result := FTextDrawable.TextShadowColor; +end; + +function TListItemTextButton.GetTextShadowOffset: TPosition; +begin + Result := FTextDrawable.TextShadowOffset; +end; + +procedure TListItemTextButton.SetText(const Value: string); +begin + FTextDrawable.FUpdating := FUpdating; + try + FTextDrawable.Text := Value; + if FTextDrawable.NeedRepaint then + Invalidate; + finally + FTextDrawable.FUpdating := 0; + end; +end; + +function TListItemTextButton.GetText: string; +begin + Result := FTextDrawable.Text; +end; + + +procedure TListItemTextButton.SetTextAlign(const Value: TTextAlign); +begin + FTextDrawable.TextAlign := Value; + if FTextDrawable.NeedRepaint then + Invalidate; +end; + +function TListItemTextButton.GetTextAlign: TTextAlign; +begin + Result := FTextDrawable.TextAlign; +end; + +function TListItemTextButton.GetFont: TFont; +begin + Result := FTextDrawable.Font; +end; + +procedure TListItemTextButton.SetTextColor(const Value: TAlphaColor); +begin + if FTextColor <> Value then + begin + FTextColor := Value; + TDefaultSettingsHelper.SetDefault(Self, TDrawableElement.TextColor, Value = TAlphaColorRec.Null); + FTextDrawable.TextColor := Value; + Exclude(FStyleValuesNeedUpdate, TStyleResource.TextColor); + Invalidate; + end; +end; + +procedure TListItemTextButton.SetPressedTextColor(const Value: TAlphaColor); +begin + if FPressedTextColor <> Value then + begin + FPressedTextColor := Value; + TDefaultSettingsHelper.SetDefault(Self, TDrawableElement.PressedTextColor, Value = TAlphaColorRec.Null); + Exclude(FStyleValuesNeedUpdate, TStyleResource.PressedTextColor); + Invalidate; + end; +end; + +procedure TListItemTextButton.SetTextShadowColor(const Value: TAlphaColor); +begin + TDefaultSettingsHelper.SetDefault(Self, TDrawableElement.TextShadowColor, Value = TAlphaColorRec.Null); + FTextDrawable.TextShadowColor := Value; + Exclude(FStyleValuesNeedUpdate, TStyleResource.TextShadowColor); + if FTextDrawable.NeedRepaint then + Invalidate; +end; + +procedure TListItemTextButton.SetTextVertAlign(const Value: TTextAlign); +begin + FTextDrawable.TextVertAlign := Value; + if FTextDrawable.NeedRepaint then + Invalidate; +end; + +function TListItemTextButton.GetTextVertAlign: TTextAlign; +begin + Result := FTextDrawable.TextVertAlign; +end; + +procedure TListItemTextButton.SetTintColor(const Value: TAlphaColor); +begin + if FTintColor <> Value then + begin + FTintColor := Value; + Invalidate; + end; +end; + +procedure TListItemTextButton.SetTrimming(const Value: TTextTrimming); +begin + FTextDrawable.Trimming := Value; + if FTextDrawable.NeedRepaint then + Invalidate; +end; + +function TListItemTextButton.GetTrimming: TTextTrimming; +begin + Result := FTextDrawable.Trimming; +end; + +procedure TListItemTextButton.SetWordWrap(const Value: Boolean); +begin + FTextDrawable.WordWrap := Value; + if FTextDrawable.NeedRepaint then + Invalidate; +end; + +function TListItemTextButton.GetWordWrap: Boolean; +begin + Result := FTextDrawable.WordWrap; +end; + +{$ENDREGION} +{$REGION 'List Item Control Scene'} + +constructor TListItemControlScene.Create(AOwner: TComponent); +begin + inherited; +end; + +destructor TListItemControlScene.Destroy; +begin + inherited; +end; + +procedure TListItemControlScene.DisableUpdating; +begin + Inc(FDisableUpdating); +end; + +procedure TListItemControlScene.SetContainer(const Container: TControl); +begin + FContainer := Container; +end; + +procedure TListItemControlScene.SetOwnerItem(const Item: TListItem); +begin + FOwnerItem := Item; +end; + +procedure TListItemControlScene.SetStyleBook(const Value: TStyleBook); +begin +end; + +procedure TListItemControlScene.DoAddObject(const AObject: TFmxObject); +begin + inherited; + if AObject is TControl then + begin + TControl(AObject).SetNewScene(Self); + TControl(AObject).DisableDisappear := True; + end; + AObject.SetRoot(RealScene.GetObject.Root); +end; + +procedure TListItemControlScene.DoRemoveObject(const AObject: TFmxObject); +begin + inherited; + AObject.SetRoot(nil); + if AObject is TControl then + TControl(AObject).SetNewScene(nil); +end; + +procedure TListItemControlScene.EnableUpdating; +begin + Dec(FDisableUpdating); + if FDisableUpdating < 0 then + raise EInvalidSceneUpdatingPairCall.Create(SInvalidSceneUpdatingPairCall); +end; + +function TListItemControlScene.GetStyleBook: TStyleBook; +begin + Result := nil; +end; + +function TListItemControlScene.GetCanvas: TCanvas; +begin + Result := FCanvas +end; + +function TListItemControlScene.GetRealScene: IScene; +begin + Result := FOwnerItem.Controller.GetScene; +end; + +function TListItemControlScene.GetSceneScale: Single; +begin + Result := RealScene.GetSceneScale; +end; + +function TListItemControlScene.GetObject: TFmxObject; +begin + Result := Self; +end; + +procedure TListItemControlScene.ChangeScrollingState(const AControl: TControl; const Active: Boolean); +begin +end; + +function TListItemControlScene.LocalToScreen(const P: TPointF): TPointF; +begin + Result := RealScene.LocalToScreen(P); +end; + +function TListItemControlScene.ScreenToLocal(const P: TPointF): TPointF; +begin + Result := RealScene.ScreenToLocal(P); +end; + +function TListItemControlScene.GetUpdateRectsCount: Integer; +begin + Result := 1; +end; + +function TListItemControlScene.GetUpdateRect(const Index: Integer): TRectF; +begin + Result := TRectF.Create(0, 0, FLayoutSize.X, FLayoutSize.Y); +end; + +procedure TListItemControlScene.AddUpdateRect(const R: TRectF); +begin + if FDisableUpdating = 0 then + FOwnerItem.Invalidate; +end; + +procedure TListItemControlScene.RepaintScene(const Canvas: TCanvas); +var + Index: Integer; + {$IFDEF CPUARM}[unsafe]{$ENDIF}Control: TOpenControl; +begin + if (not FDrawing) then + begin + FDrawing := True; + FCanvas := Canvas; + try + for Index := 0 to ChildrenCount - 1 do + if (Children[Index] is TControl) and + (TControl(Children[Index]).Visible or ((not TControl(Children[Index]).Visible) and + (csDesigning in ComponentState) and (not TControl(Children[Index]).Locked))) then + begin + Control := TOpenControl(Children[Index]); + TOpenControl(Control).PaintInternal; + end; + finally + FCanvas := nil; + FDrawing := False; + end; + end; +end; + +{$ENDREGION} +{$REGION 'List Item Control Container'} + +constructor TListItemControlContainer.Create(AOwner: TComponent); +begin + inherited; +end; + +destructor TListItemControlContainer.Destroy; +begin + inherited; +end; + +{$ENDREGION} +{$REGION 'List Item Embedded Control'} + +constructor TListItemEmbeddedControl.Create(const AOwner: TListItem); +begin + inherited; + + FScene := TListItemControlScene.Create(nil); + FScene.SetOwnerItem(AOwner); + + FContainer := TListItemControlContainer.Create(nil); + FContainer.Parent := FScene; + FContainer.FItemOwner := Self; + + FScene.SetContainer(FContainer); +end; + +destructor TListItemEmbeddedControl.Destroy; +begin + FContainer.Free; + FScene.Free; + + inherited; +end; + +procedure TListItemEmbeddedControl.Render(const Canvas: TCanvas; const DrawItemIndex: Integer; + const DrawStates: TListItemDrawStates; const Resources: TListItemStyleResources; + const Params: TListItemDrawable.TParams; const SubPassNo: Integer = 0); +var + BoundsPos: TPointF; +begin + if SubPassNo <> 0 then + Exit; + + FScene.FLayoutSize := TPoint.Create(Trunc(Canvas.Width), Trunc(Canvas.Height)); + + BoundsPos := FLocalRect.TopLeft; + BoundsPos := BoundsPos + Params.ParentAbsoluteRect.TopLeft; + + FContainer.SetBounds(BoundsPos.X, BoundsPos.Y, FLocalRect.Width, FLocalRect.Height); + FScene.RepaintScene(Canvas); +end; + +function TListItemEmbeddedControl.ObjectAtPoint(const Point: TPointF): TControl; +var + Control: IControl; +begin + Control := FContainer.ObjectAtPoint(Point); + if (Control <> nil) and (Control.GetObject is TControl) and (Control.GetObject <> FContainer) then + Result := TControl(Control.GetObject) + else + Result := nil; +end; + +{$ENDREGION} +{$REGION 'List Item'} + +constructor TListItem.Create(const AAdapter: IListViewAdapter; + const AController: IListViewController); +begin + inherited Create; + FController := AController; + FAdapter := AAdapter; + FView := ListItemObjectsClass.Create(Self); + FView.Callback := procedure(View: TListItemView; Drawable: TListItemDrawable; Op: TListItemCallbackOp) begin + case Op of + TListItemCallbackOp.CreateDrawables: + CreateObjects; + TListItemCallbackOp.InvalidateOwner: + Invalidate; + TListItemCallbackOp.Click: + FController.ControlClicked(Self, Drawable); + end; + end; + FHeaderRef := -1; + FIndex := -1; + FUpdating := 0; + FNeedRepaint := False; +end; + +procedure TListItem.CreateObjects; +begin +// +end; + +destructor TListItem.Destroy; +begin + FView.Free; + inherited; +end; + +function TListItem.GetController: IListViewController; +begin + Result := FController; +end; + +function TListItem.GetCount: Integer; +begin + Result := FView.Count; +end; + +procedure TListItem.BeginUpdate; +begin + Inc(FUpdating); +end; + +procedure TListItem.EndUpdate; +begin + if FUpdating > 0 then + begin + Dec(FUpdating); + if (FUpdating <= 0) and FNeedRepaint then + begin + FNeedRepaint := False; + Invalidate; + end; + end; +end; + +procedure TListItem.Invalidate; +begin + if FUpdating < 1 then + Repaint + else + FNeedRepaint := True; +end; + +procedure TListItem.SetHeight(const Value: Integer); +var + NewValue: Integer; +begin + NewValue := Value; + if NewValue < 0 then + NewValue := 0; + + if NewValue <> FHeight then + begin + FHeight := NewValue; + InvalidateHeights; + end; +end; + +procedure TListItem.SetIndex(const Value: Integer); +begin + FIndex := Value; +end; + +function TListItem.GetIndex: Integer; +begin + Result := FIndex; +end; + +procedure TListItem.SetPurpose(const AValue: TListItemPurpose); +begin + if AValue <> FPurpose then + begin + FPurpose := AValue; + InvalidateHeights; + end; +end; + +function TListItem.ToString: string; + function GetClassName: string; + begin + if Self <> nil then + Result := ClassName + else + Result := 'TListItem(nil)'; + end; + + function GetPurpose: string; + begin + if Self <> nil then + Result := Purpose.ToString + else + Result := '(unknown)'; + end; + + function GetViewCount: Integer; + begin + if (Self <> nil) and (Self.View <> nil) then + Result := Self.View.Count + else + Result := -1; + end; +begin + Result := Format('%s@%x Purpose=%s |View|=%d', [GetClassName, NativeUInt(Self), GetPurpose, GetViewCount]); +end; + +procedure TListItem.WillBePainted; +begin +end; + +procedure TListItem.InvalidateHeights; +begin + // +end; + +function TListItem.ListItemObjectsClass: TListItemViewType; +begin + Result := TListItemView; +end; + +function TListItem.ObjectAtPoint(const Point: TPointF): TControl; +var + I: Integer; + Control: TControl; +begin + Result := nil; + for I := 0 to FView.Count - 1 do + if FView[I] <> nil then + begin + Control := FView[I].ObjectAtPoint(Point); + if Control <> nil then + Exit(Control); + end; +end; + +procedure TListItem.Repaint; +begin + FController.ItemInvalidated(Self); +end; + +function TListItem.MouseDown(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF): Boolean; +var + I: Integer; +begin + Result := False; + + for I := 0 to FView.Count - 1 do + if (FView[I] <> nil) and FView[I].Visible then + begin + Result := FView[I].MouseDown(Button, Shift, MousePos); + if Result then + Break; + end; +end; + +procedure TListItem.MouseMove(const Shift: TShiftState; const MousePos: TPointF); +var + I: Integer; +begin + for I := 0 to FView.Count - 1 do + if (FView[I] <> nil) and FView[I].Visible then + FView[I].MouseMove(Shift, MousePos); +end; + +procedure TListItem.MouseUp(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF); +var + I: Integer; +begin + for I := 0 to FView.Count - 1 do + if (FView[I] <> nil) and FView[I].Visible then + FView[I].MouseUp(Button, Shift, MousePos); +end; + +procedure TListItem.MouseSelect; +var + I: Integer; +begin + for I := 0 to FView.Count - 1 do + if (FView[I] <> nil) and FView[I].Visible then + FView[I].DoSelect; +end; + +function TListItem.HasClickOnSelectItems: Boolean; +var + I: Integer; +begin + Result := False; + + for I := 0 to FView.Count - 1 do + if (FView[I] <> nil) and FView[I].Visible and (FView[I] is TListItemGlyphButton) then + begin + Result := TListItemGlyphButton(FView[I]).ClickOnSelect; + if Result then + Break; + end; +end; + +{$ENDREGION} +{$REGION 'List Item Objects'} + +constructor TListItemView.Create(const AOwner: TListItem); +begin + inherited Create; + FViewList := TListItemView.TViewList.Create; +end; + +function TListItemView.GetCount: Integer; +begin + FCallback(Self, nil, TListItemCallbackOp.CreateDrawables); + Result := FViewList.Count; +end; + +function TListItemView.GetViewList: TViewList; +begin + Result := FViewList; +end; + +function TListItemView.GetObject(const Index: Integer): TListItemDrawable; +begin + Result := ViewList[Index]; +end; + +function TListItemView.Add(const AItem: TListItemDrawable): Integer; +begin + Result := ViewList.Add(AItem); + FCallback(Self, nil, TListItemCallbackOp.InvalidateOwner); +end; + +procedure TListItemView.Insert(const Index: Integer; const Value: TListItemDrawable); +begin + ViewList.Insert(Index, Value); +end; + +procedure TListItemView.Clear; +begin + ViewList.Clear; +end; + +procedure TListItemView.Delete(Index: Integer); +begin + ViewList.Delete(Index); + FCallback(Self, nil, TListItemCallbackOp.InvalidateOwner); +end; + +destructor TListItemView.Destroy; +begin + FCallback := nil; + FViewList.Free; + inherited; +end; + +procedure TListItemView.Include(const AItem: TListItemDrawable); +begin + if ViewList.IndexOf(AItem) = -1 then + begin + ViewList.Add(AItem); + AItem.InvalidateCallback := FCallback; + FCallback(Self, nil, TListItemCallbackOp.InvalidateOwner); + end; +end; + +procedure TListItemView.Exclude(const AItem: TListItemDrawable); +var + Index: Integer; +begin + Index := ViewList.IndexOf(AItem); + + if (Index <> -1) then + ViewList.Delete(Index); +end; + +function TListItemView.DrawableByName(const AName: string): TListItemDrawable; +begin + Result := FindDrawable(AName); + if Result = nil then + raise EListError.Create(SGenericItemNotFound); +end; + +function TListItemView.ObjectByName(const AName: string): TListItemDrawable; +begin + Exit(DrawableByName(AName)); +end; + +function TListItemView.GetInitialized: Boolean; +begin + Result := FInitialized; +end; + +procedure TListItemView.SetInitialized(const Value: Boolean); +begin + FInitialized := Value; +end; + +function TListItemView.FindDrawable(const AName: string): TListItemDrawable; +var + LObject: TListItemDrawable; + I: Integer; +begin + for I := 0 to ViewList.Count - 1 do + begin + LObject := ViewList[I]; + if LObject.Name = AName then + Exit(LObject); + end; + Result := nil; +end; + +function TListItemView.FindObject(const AName: string): TListItemDrawable; +begin + Exit(FindDrawable(AName)); +end; + +{$ENDREGION} +{$REGION 'List Item Style Objects'} + +{ TListItemStyleObjects } + +constructor TListItemStyleResources.Create; +begin + FOwnsObjects := True; + DefaultTextFont := TFont.Create; + DetailTextFont := TFont.Create; + HeaderTextFont := TFont.Create; + ButtonTextFont := TFont.Create; + DeleteButtonTextFont := TFont.Create; +end; + +constructor TListItemStyleResources.Create(const Source: TListItemStyleResources); +var + I: TAccessoryType; +begin + FOwnsObjects := False; + for I := Low(TAccessoryType) to High(TAccessoryType) do + AccessoryImages[I] := Source.AccessoryImages[I]; + HeaderTextFont := Source.HeaderTextFont; + HeaderTextColor := Source.HeaderTextColor; + HeaderTextShadowColor := Source.HeaderTextShadowColor; + DefaultTextFont := Source.DefaultTextFont; + DefaultTextColor := Source.DefaultTextColor; + DetailTextFont := Source.DetailTextFont; + DetailTextColor := Source.DetailTextColor; + DefaultTextSelectedColor := Source.DefaultTextSelectedColor; + ButtonAddItemStyleImage := Source.ButtonAddItemStyleImage; + ButtonDeleteItemStyleImage := Source.ButtonDeleteItemStyleImage; + ButtonNormalStyleImage := Source.ButtonNormalStyleImage; + ButtonDeleteStyleImage := Source.ButtonDeleteStyleImage; + ButtonCheckboxStyleImage := Source.ButtonCheckboxStyleImage; + ButtonTextFont := Source.ButtonTextFont; + ButtonTextColor := Source.ButtonTextColor; + ButtonTextPressedColor := Source.ButtonTextPressedColor; + DeleteButtonTextFont := Source.DeleteButtonTextFont; + DeleteButtonTextColor := Source.DeleteButtonTextColor; + DeleteButtonTextPressedColor := Source.DeleteButtonTextPressedColor; + ScrollingStretchGlowColor := Source.ScrollingStretchGlowColor; + PullRefreshIndicatorColor := Source.PullRefreshIndicatorColor; + PullRefreshStrokeColor := Source.PullRefreshStrokeColor; +end; + +destructor TListItemStyleResources.Destroy; +begin + if FOwnsObjects then + begin + DefaultTextFont.Free; + DetailTextFont.Free; + HeaderTextFont.Free; + ButtonTextFont.Free; + DeleteButtonTextFont.Free; + end; + inherited; +end; +{$ENDREGION} + +{ TListItemPurposeHelper } + +function TListItemPurposeHelper.ToString: string; +const + PurposeString: array [TListItemPurpose] of string = ('None', 'Header', 'Footer'); +begin + Result := PurposeString[Self]; +end; + +{ TDrawableDefaultSettings } + +class constructor TDefaultSettingsHelper.Create; +begin + FItems := TDictionary.Create; +end; + +class function TDefaultSettingsHelper.IsDefault(const ADrawable: TListItemDrawable; const AElement: TDrawableElement): Boolean; +begin + Result := AElement in FItems[ADrawable]; +end; + +class procedure TDefaultSettingsHelper.SetDefault(const ADrawable: TListItemDrawable; const AElement: TDrawableElement; + const ADefault: Boolean); +var + Elements: TDrawableElements; +begin + Elements := FItems[ADrawable]; + if ADefault then + Include(Elements, AElement) + else + Exclude(Elements, AElement); + FItems[ADrawable] := Elements; +end; + +class procedure TDefaultSettingsHelper.Register(const ADrawable: TListItemDrawable); +begin + FItems.Add(ADrawable, [Low(TDrawableElement)..High(TDrawableElement)]) +end; + +class procedure TDefaultSettingsHelper.Unregister(const ADrawable: TListItemDrawable); +begin + FItems.Remove(ADrawable); +end; + +class destructor TDefaultSettingsHelper.Destroy; +begin + FreeAndNil(FItems); +end; + +end. diff --git a/Alexandria(11.3)/FMX.ListView.pas b/Alexandria(11.3)/FMX.ListView.pas new file mode 100644 index 0000000..58505fa --- /dev/null +++ b/Alexandria(11.3)/FMX.ListView.pas @@ -0,0 +1,7291 @@ +{*******************************************************} +{ } +{ Delphi FireMonkey Platform } +{ } +{ Copyright(c) 2011-2023 Embarcadero Technologies, Inc. } +{ All rights reserved } +{ } +{*******************************************************} + +unit FMX.ListView; + +interface + +{$SCOPEDENUMS ON} + +uses + System.Types, System.UITypes, System.SysUtils, System.Classes, System.Generics.Collections, System.Generics.Defaults, + System.UIConsts, System.ImageList, FMX.Types, FMX.Controls, FMX.InertialMovement, FMX.TextLayout, FMX.ListView.Types, + FMX.ListView.Adapters.Base, FMX.ListView.Appearances, FMX.Styles, FMX.Objects, FMX.StdCtrls, System.Rtti, + FMX.Graphics, FMX.Layouts, FMX.Styles.Objects, FMX.Edit, FMX.Platform, FMX.SearchBox, FMX.ActnList, FMX.ImgList, + FMX.Presentation.Messages, FMX.Controls.Presentation, FMX.ListView.DynamicAppearance; + +type + TSwipeDirection = (ToLeft, ToRight); // ZuBy + + /// List view class that provides members that subclasses may use to + /// communicate with a list view adapter that contains the actual items of the + /// list view. + TAdapterListView = class(TStyledControl) + strict private + FAdapter: IListViewAdapter; + FHeightSumsNeedUpdate: Boolean; + + procedure ItemsMayChange(Sender: TObject); + procedure ItemsCouldHaveChanged(Sender: TObject); + procedure ItemsChange(Sender: TObject); + procedure ItemsResize(Sender: TObject); + procedure ItemsInvalidate(Sender: TObject); + procedure ResetView(Sender: TObject); + protected + /// Called after the adapter had been set, SetAdapter + procedure DoAdapterSet; virtual; + /// Set IListViewAdapter + procedure SetAdapter(Adapter: IListViewAdapter); + /// Request update of item heights + procedure InvalidateHeights; + /// Handler of IListViewAdapter.OnItemsMayChange + procedure DoItemsMayChange; virtual; + /// Handler of + /// IListViewAdapter.OnItemsCouldHaveChanged + procedure DoItemsCouldHaveChanged; virtual; + /// Handler of IListViewAdapter.OnItemsResize + procedure DoItemsResize; virtual; + /// Handler of + /// IListViewAdapter.OnItemsInvalidate + procedure DoItemsInvalidate; virtual; + /// Handler of + /// IListViewAdapter.OnResetView + procedure DoResetView(const Sender: TListItem); virtual; + /// True if update of item height cache is required. Set by InvalidateHeights + property HeightSumsNeedUpdate: Boolean read FHeightSumsNeedUpdate write FHeightSumsNeedUpdate; + public + /// IListViewAdapter providing contents of this TAdapterListView + property Adapter: IListViewAdapter read FAdapter write SetAdapter; + end; + + /// The minimal ListView that's actually a real UI control. It implements all scrolling and drawing + /// functionality. It needs a valid IListViewAdapter to provide item views; + /// see TAdapterListView.Adapter + /// + /// + /// Implements: + /// ISearchResponder enables setting a filter predicate + /// IListItemStyleResources access to style resources + /// IListViewController mediator protocol between TListView and TListItem + /// IGlyph interface to TImageList + /// IMessageSendingCompatible TMessageSender object for FMX.Presentation compatibility + /// + TListViewBase = class(TAdapterListView, ISearchResponder, IListItemStyleResources, IListViewController, IGlyph, + IMessageSendingCompatible, IControlTypeSupportable) + private const + ChangeRepaintedIncidentDelay = 0.1; // seconds + PhysicsProcessingInterval = 8; // 8 ms for ~120 frames per second + RecurrentTimerInterval = 16; // 16 ms for ~60 frames per second + AutoTapScrollingSpeed = 8; // pixels per frame + AutoTapMaxScrollingTime = 1; // seconds + TapSelectWaitTime = 0.25; // seconds + SelectionFadeInTime = 0.125; // seconds + SelectionFadeOutTime = 0.25; // seconds + MinScrollThreshold = 10; + MinSwypeThreshold = 40; + + DefaultDeleteButtonWidth = 72; + + ItemSeparatorTop = 1; + ItemSeparatorBottom = 2; + + EditModeSelectionAlpha = 0.25; // how bright the checked items are in editmode + + EditModeAnimationDuration = 0.1; // in seconds + DeleteModeAnimationDuration = 0.15; // in seconds + DefaultDeleteButtonText = 'Delete'; + + PullRefreshIndicatorStrengthStart = 16; + PullRefreshIndicatorMaxSteps = 12; + + DefaultLeftMargin = 10; + DefaultRightMargin = 11; + + public type + /// Edit mode change event + /// event source (TListViewBase) + /// set to True if event was handled + THandleChangeEvent = procedure(const Sender: TObject; var AHandled: Boolean) of object; + /// Generic TListItem event; used for OnListItemClick, OnItemChange + /// event source (TListViewBase) + /// item being acted upon + TListItemEvent = procedure(const Sender: TObject; const AItem: TListItem) of object; + /// OnItemClickEx event + /// event source + /// index of item + /// click position in item local coordinates + /// TListItemDrawable that received the click + TListItemClickEventEx = procedure(const Sender: TObject; ItemIndex: Integer; const LocalClickPos: TPointF; + const ItemObject: TListItemDrawable) of object; + /// OnUpdateItemView event + TUpdateItemViewEvent = TListItemEvent; + /// OnUpdatingItemView event + TUpdatingItemViewEvent = procedure(const Sender: TObject; const AItem: TListItem; var AHandled: Boolean) of object; + /// OnDeletingItem event + TDeletingItemEvent = procedure(Sender: TObject; AIndex: Integer; var ACanDelete: Boolean) of object; + /// OnDeleteItem event + TDeleteItemEvent = procedure(Sender: TObject; AIndex: Integer) of object; + /// OnDeleteChangeVisible event + TDeleteChangeVisibilityEvent = procedure(Sender: TObject; AValue: Boolean) of object; + + TColumnClick = procedure(const Sender: TObject; const Column: Integer; + const X, Y: Single; const AItem: TListViewItem; const DrawebleName: string) of object; // ZuBy + TScrollEnd = procedure(Sender: TObject) of object; // ZuBy + TSwipeDirectionEvent = procedure(Sender: TObject; const Direction: TSwipeDirection) of object; // ZuBy + + private type + TItemHeightSums = TList; + + TDelayedIncident = (ChangeRepainted, Invalidate, SetItemIndex, ClickEvent); + + TDelayedIncidentEntry = record + Incident: TDelayedIncident; + Triggered: Boolean; + StartTime: Double; + TimeToWait: Double; + CustomData: NativeInt; + end; + + TDelayedIncidents = TList; + + TTransitionType = (None, EditMode, DeleteMode); + + TInternalDragMode = (None, Drag, Swype); + + TItemSelectionAlpha = record + StartTime: Double; + Alpha: Single; + StartAlpha: Single; + + class function Create(const StartTime: Double; const Alpha, StartAlpha: Single): TItemSelectionAlpha; static; inline; + end; + + TItemSelectionAlphas = TDictionary; + TPullRefreshAnimation = (NotPlaying, Playing, Finished); + TStateFlag = (NeedsRebuild, NeedsScrollingLimitsUpdate, Invalid, Painting, ResettingObjects, ScrollingActive, + ScrollingMouseTouch, NeedsScrollBarDisplay); + TStateFlags = set of TStateFlag; + + TEstimatedHeights = record + Item: Single; + Header: Single; + Footer: Single; + end; + + private + FTimerService: IFMXTimerService; + FSystemInformationService: IFMXSystemInformationService; + FListingService: IFMXListingService; + FStateFlags: TStateFlags; + FRecurrentTimerHandle: TFmxHandle; + FDelayedIncidents: TDelayedIncidents; + FSelectionAlphas: TItemSelectionAlphas; + FItemIndex: Integer; + FAniCalc: TAniCalculations; + FScrollViewPos: Single; + FBrush: TBrush; + FStroke: TStrokeBrush; + FMouseDownAt: TPointF; + FMouseClickPrev: TPointF; + FMouseClickDelta: TPointF; + FMouseClicked: Boolean; + FMouseClickIndex: Integer; + FMouseEventIndex: Integer; + FItemSpaces: TBounds; + FMousePrevScrollPos: Single; + FClickEventItemIndex: Integer; + FClickEventMousePos: TPointF; + [Weak] FClickEventControl: TListItemDrawable; + FHeightSums: TItemHeightSums; + FMaxKnownHeight: Integer; + FSideSpace: Integer; + FScrollScale: Single; + FBackgroundStyleColor: TAlphaColor; + FSelectionStyleColor: TAlphaColor; + FItemStyleFillColor: TAlphaColor; + FItemStyleFillAltColor: TAlphaColor; + FItemStyleFrameColor: TAlphaColor; + [Weak] FSelectionStyleImage: TStyleObject; + [Weak] FHeaderStyleImage: TStyleObject; + FTouchAnimationObject: ITouchAnimationObject; + FScrollBar: TScrollBar; + FTransparent: Boolean; + FAllowSelection: Boolean; + FAlternatingColors: Boolean; + FTapSelectItemIndex: Integer; + FTapSelectNewIndexApplied: Integer; + FTapSelectStartTime: Double; + FShowSelection: Boolean; + FOnChange: TNotifyEvent; + FOnChangeRepainted: TNotifyEvent; + FOnItemsChange: TNotifyEvent; + FOnScrollViewChange: TNotifyEvent; + FOnSearchChange: TNotifyEvent; + FOnFilter: TFilterEvent; + FAutoTapScroll: Boolean; + FAutoTapTreshold: Integer; + FAutoTapDistance: Integer; + FOnListItemClick: TListItemEvent; + FOnItemClickEx: TListItemClickEventEx; + FOnItemChange: TListItemEvent; + FOnEditModeChanging: THandleChangeEvent; + FOnEditModeChange: TNotifyEvent; + FOnUpdateItemView: TUpdateItemViewEvent; + FOnUpdatingItemView: TUpdatingItemViewEvent; + FOnDeleteChange: TDeleteChangeVisibilityEvent; + FOnDeletingItem: TDeletingItemEvent; + FOnDeleteItem: TDeleteItemEvent; + FOnPullRefresh: TNotifyEvent; + FDeleteButtonText: string; + FEditMode: Boolean; + FCanSwipeDelete: Boolean; + FDeleteButtonIndex: Integer; + FPrevDeleteButtonIndex: Integer; + FStyleResources: TListItemStyleResources; + FUpdatingStyleResources: Boolean; + FDisableMouseWheel: Boolean; + FTransitionStartTime: Double; + FTransitionType: TTransitionType; + FEditModeTransitionAlpha: Single; + FDeleteModeTransitionAlpha: Single; + FDeleteLayout: TLayout; + FDeleteButton: TSpeedButton; + FDragListMode: TInternalDragMode; + FSearchEdit: TSearchBox; + FSearchVisible: Boolean; + FSearchAlwaysOnTop: Boolean; + FSelectionCrossfade: Boolean; + FPullToRefresh: Boolean; + FPullRefreshWait: Boolean; + FPullRefreshTriggered: Boolean; + FPullRefreshAnimation: TPullRefreshAnimation; + FPullRefreshAnimationStartTime: Double; + FPullRefreshAnimationStopTime: Double; + FScrollStretchStrength: Single; + FControlType: TControlType; + FNativeOptions: TListViewNativeOptions; + FImageLink: TGlyphImageLink; + FMessageSender: TMessageSender; + FItemSelectedBeforeChange: TListItem; + FEstimatedHeights: TEstimatedHeights; + + FMouseClickSwipeEventSend: Boolean; // ZuBy + FHeaderStyleColor: TAlphaColor; + FOnColumnClick: TColumnClick; // ZuBy + FOnScrollEnd: TScrollEnd; // ZuBy + FOnSwipe: TSwipeDirectionEvent; // ZuBy + FTransparentSeparator: Boolean; // ZuBy + FTransparentItems: Boolean; // ZuBy + FTransparentHeaders: Boolean; // ZuBy + FAutoPositionToItem: Boolean; // ZuBy + FTopItemIndex: Integer; // ZuBy + FCanSwipeDirection: Boolean; // ZuBy + FSeparatorLeftOffset: Single; // ZuBy + FSeparatorRightOffset: Single; // ZuBy + FHorizontal: Boolean; // ZuBy + FItemBoxLight: TStyleObject; // ZuBy + FMakeSelectedItemVisible: Boolean; // ZuBy + FShowScrollBar: Boolean; // ZuBy + FColumnWidth: Single; // ZuBy + FTopOffset: Integer; // ZuBy + FBottomOffset: Integer; // ZuBy + FItemBottomOffset: Integer; // ZuBy + FColumns: Integer; // ZuBy + FMarg: Integer; // ZuBy + FAutoColumns: Boolean; // ZuBy + FCanScroll: Boolean; // ZuBy + FShowFirstSeparator: Boolean; // sinuke + FShowLastSeparator: Boolean; // sinuke + + function IsRunningOnDesktop: Boolean; + function HasTouchTracking: Boolean; + function HasSearchFeatures: Boolean; + function HasSearchAsItem: Boolean; + function IsDeleteModeAllowed: Boolean; + function HasStretchyScrolling: Boolean; + function HasPhysicsStretchyScrolling: Boolean; + function HasScrollingStretchGlow: Boolean; + function HasPullRefreshStroke: Boolean; + + function CanDisplaySelectionForItem(const Index: Integer; const Item: TListItem = nil; + const IncludeMultiSelect: Boolean = False; const IncludeCrossFaded: Boolean = False): Boolean; + function GetDefaultSelectionAlpha: Single; + function GetItemSelectionAlpha(const Index: Integer): Single; + procedure DestroyRecurrentTimer; + procedure UpdateRecurrentTimer; + function HasRecurrentTimerEvents: Boolean; + procedure RecurrentTimerEvent; + procedure StartIncident(const Incident: TDelayedIncident; const Triggered: Boolean = True; + const TimeToWait: Single = 0; const CustomData: NativeInt = 0); + procedure ProcessIncident(const Entry: TDelayedIncidentEntry); + procedure TriggerIncidents(const Incident: TDelayedIncident; const ResetStartupTime: Boolean = True); + procedure ProcessDelayedIncidents; + procedure ProcessTransitionAnimation; + procedure ProcessTapSelectItem; + procedure ProcessSelectionAlphas; + procedure InsertItemCrossFade(const Index: Integer; const ShowAnimation: Boolean); + procedure RemoveItemCrossFade(const Index: Integer); + procedure StartPullRefreshAnimation; + procedure ProcessPullRefreshAnimation; + function GetPullRefreshStrength: Single; + function GetPullRefreshIndicatorSteps: Integer; + function GetPullRefreshIndicatorAlpha: Single; + function GetPullRefreshStrokeWidth: Single; + procedure PaintPullRefreshIndicator(const ACanvas: TCanvas; const AStrength, AOpacity: Single); + procedure PaintPullRefreshStroke(const ACanvas: TCanvas; const AStrength, AOpacity: Single); + procedure PaintScrollingStretchGlow(const ACanvas: TCanvas; const AIntensity, AOpacity: Single); + procedure UpdatePullRefreshState; + procedure UpdateScrollStretchStrength(const NewValue: Single); + procedure DeleteButtonClicked(Sender: TObject); + procedure ScrollBarChange(Sender: TObject); + procedure ItemSpacesChange(Sender: TObject); + procedure AniCalcChange(Sender: TObject); + procedure AniCalcStart(Sender: TObject); + procedure AniCalcStop(Sender: TObject); + function GetItemIndex: Integer; + procedure SetItemIndex(const Value: Integer); + procedure SetItemIndexInternal(const Value: Integer; const DisableSelection: Boolean = False; + const DisableCrossfade: Boolean = False); + function GetMaxScrollViewPos: Integer; + procedure UpdateScrollViewPos(const Value: Single); + procedure UpdateSearchEditPos; + procedure SetScrollViewPos(const Value: Single); + procedure UpdateScrollingLimits; + procedure UpdateScrollBar; + procedure GetNumberOfRenderingPasses(const StartItem, EndItem: Integer; var Passes, Subpasses: Integer); + function GetItemHeight(const Index: Integer): Integer; overload; virtual; + function GetItemRelRect(const Index: Integer; const LocRect: TRectF; const SideSpace: Integer = 0): TRectF; inline; + function GetItemGroupSeparators(const Index: Integer): Integer; inline; + function FindLocalItemObjectAtPosition(const ItemIndex: Integer; const Position: TPointF): TListItemDrawable; + + function GetSeparatorLineHeight: Single; + function AlignValueToPixel(const Value: Single): Single; + procedure DrawItemsFill(const StartItem, EndItem: Integer; const LocRect: TRectF; const Opacity: Single; + const HeaderIndex: Integer = -1); + procedure DrawIndexFill(const AIndex: Integer; const LocRect: TRectF; const Opacity: Single); + procedure DrawTouchAnimation(const Index: Integer; const LocRect: TRectF; const Opacity: Single); + + function GetHeaderRelRect(const StartItem, HeaderIndex: Integer; const LocRect: TRectF; + const SideSpace: Integer = 0): TRectF; + procedure DrawHeaderItem(const LocRect: TRectF; const StartItem, HeaderIndex: Integer; const Opacity: Single); + + procedure DrawListItems(const AbsOpacity: Single); + + procedure UpdateItemLookups; + function FindItemAbsoluteAt(const ViewAt: Integer): Integer; + function FindItemAbsoluteAtWithCheck(const ViewAt: Integer): Integer; + procedure SetSideSpace(const Value: Integer); + procedure SetTransparent(const Value: Boolean); + procedure SetAlternatingColors(const Value: Boolean); + procedure SetShowSelection(const Value: Boolean); + procedure RecreateNativePresentation; virtual; + + procedure SetEditMode(const Value: Boolean); + procedure SetCanSwipeDelete(const Value: Boolean); + + procedure SelectItem(const ItemIndex: Integer); virtual; + procedure UnselectItem(const ItemIndex: Integer); virtual; + function GetSelected: TListItem; + procedure SetSelected(const Value: TListItem); + procedure SetNewItemIndex(const NewIndex: Integer); + + procedure SetSearchVisible(const Value: Boolean); + procedure SetSearchAlwaysOnTop(const Value: Boolean); + procedure SetOnFilter(const Value: TFilterEvent); + procedure OnSearchEditResize(Sender: TObject); + procedure OnSearchEditChange(Sender: TObject); + function DeleteButtonTextStored: Boolean; + // ISearchResponder + procedure SetFilterPredicate(const Predicate: TPredicate); + // IMessageSendingCompatible + function GetMessageSender: TMessageSender; + // Custom readers + procedure ReadCanSwipeDelete(Reader: TReader); + procedure ReadIsSearchVisible(Reader: TReader); + procedure ReadIsSearchAlwaysOnTop(Reader: TReader); + procedure ReadEditModeOptions(Reader: TReader); + + function GetItemCount: Integer; + + { IListViewController } + procedure RequestReindexing(const Item: TListItem); + procedure ItemResized(const Item: TListItem); + procedure ItemInvalidated(const Item: TListItem); + procedure ControlClicked(const Item: TListItem; const Control: TListItemDrawable); + procedure CheckStateChanged(const Item: TListItem; const Control: TListItemDrawable); + function GetScene: IScene; + + procedure RecalcTopViewItemIndex; // ZuBy + procedure SetSeparatorLeftOffset(const Value: Single); // ZuBy + procedure SetSeparatorRightOffset(const Value: Single); // ZuBy + procedure SetHorizontal(const Value: Boolean); // ZuBy + procedure SetShowScrollBar(const Value: Boolean); // ZuBy + procedure SetAutoColumns(const Value: Boolean); // ZuBy + procedure SetBottomOffset(const Value: Integer); // ZuBy + procedure SetColumnWidth(const Value: Single); // ZuBy + procedure SetTopOffset(const Value: Integer); + procedure SetItemBottomOffset(const Value: Integer); + procedure SetTransparentSeparator(const Value: Boolean); + procedure SetTransparentItems(const Value: Boolean); // ZuBy + function getTextSize(const aText: string; aFont: TFont; const aWordWrap: Boolean; aWidth, aHeight: Single): TSizeF; // ZuBy + procedure SetTransparentHeader(const Value: Boolean); // ZuBy + procedure SetCanScroll(const Value: Boolean); + procedure SetShowFirstSeparator(const Value: Boolean); + procedure SetShowLastSeparator(const Value: Boolean); + protected + procedure DefineProperties(Filer: TFiler); override; + /// True if in Edit Mode + function IsEditMode: Boolean; virtual; + /// Used internally by presentation hook + procedure DoSetItemIndexInternal(const Value: Integer); virtual; + /// Used internally by presentation hook + procedure DoUpdateScrollViewPos(const Value: Single); virtual; + /// Used internally by presentation hook + procedure DoSetScrollViewPos(const Value: Single); virtual; + /// Invoked when Edit Mode is being changed; if Edit Mode requires a different appearance, this + /// is where update of appearances should be initiated + procedure WillEnterEditMode(const Animated: Boolean); virtual; + /// Used internally by presentation hook + function HasButtonsInCells: Boolean; virtual; + /// Used internally by presentation hook + function HasDeletionEditMode: Boolean; virtual; + /// Used internally by presentation hook + function HasCheckboxMode: Boolean; virtual; + + /// Stop edit mode transition animation + procedure ResetEditModeAnimation; + /// Initialize edit mode transition animation + procedure InitEditModeAnimation; + /// Stop delete mode transition animation + procedure ResetDeleteModeAnimation; + /// Initialize delete mode transition animation + procedure InitDeleteModeAnimation; + /// Update layout to place a Delete button + procedure UpdateDeleteButtonLayout; + /// Perform item deletion + procedure ProceedDeleteItem; + + /// Invokes Pull-to-Refresh when applicable + procedure ScrollStretchChanged; virtual; + /// Scroll stretch threshold value when Pull-to-Refresh is invoked + property ScrollStretchStrength: Single read FScrollStretchStrength; + + procedure SetSelectionCrossfade(const Value: Boolean); + function GetDeleteButtonText: string; + procedure SetDeleteButtonText(const Value: string); + procedure SetPullToRefresh(const Value: Boolean); + { IControlTypeSupportable } + procedure SetControlType(const Value: TControlType); + function GetControlType: TControlType; + procedure SetNativeOptions(const Value: TListViewNativeOptions); + + { IListViewController } + function GetEditModeTransitionAlpha: Single; + function GetDeleteModeTransitionAlpha: Single; + procedure SetDeleteButtonIndex(const NewItemIndex: Integer); + function GetItemEditOffset(const Item: TListItem): Single; + function GetItemDeleteCutoff(const Item: TListItem): Single; + function GetClientMargins: TRectF; + function GetItemCurrentSelectionAlpha(const Item: TListItem): Single; + function IListViewController.GetItemSelectionAlpha = GetItemCurrentSelectionAlpha; + function GetImages: TCustomImageList; + procedure SetImages(const Value: TCustomImageList); + + /// Hook for IListViewController.RequestReindexing + procedure DoRequestReindexing(const Item: TListItem); virtual; + /// Hook for IListViewController.ItemResized + procedure DoItemResized(const Item: TListItem); virtual; + /// Hook for IListViewController.ItemInvalidated + procedure DoItemInvalidated(const Item: TListItem); virtual; + /// Hook for IListViewController.CheckStateChanged + procedure DoCheckStateChanged(const Item: TListItem; const Control: TListItemDrawable); virtual; + /// Hook for IListViewController.ControlClicked + procedure DoControlClicked(const Item: TListItem; const Control: TListItemDrawable); virtual; + + { IGlyph } + function GetImageIndex: TImageIndex; + procedure SetImageIndex(const Value: TImageIndex); + function GetImageList: TBaseImageList; inline; + procedure SetImageList(const Value: TBaseImageList); + function IGlyph.GetImages = GetImageList; + procedure IGlyph.SetImages = SetImageList; + { IListItemStyleResources } + function GetStyleResources: TListItemStyleResources; + function StyleResourcesNeedUpdate: Boolean; + + procedure SetItemSpaces(const Value: TBounds); + function GetItemClientRect(const Index: Integer): TRectF; // part of IListViewPresentationParent + function GetEstimatedItemHeight: Single; // part of IListViewPresentationParent + function GetEstimatedHeaderHeight: Single; // part of IListViewPresentationParent + function GetEstimatedFooterHeight: Single; // part of IListViewPresentationParent + + /// Called when an instance or reference to instance of TBaseImageList or the + /// ImageIndex property is changed. + /// See also FMX.ActnList.IGlyph + procedure ImagesChanged; virtual; + procedure Paint; override; + procedure AfterPaint; override; + procedure Loaded; override; + procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override; + procedure MouseMove(Shift: TShiftState; X, Y: Single); override; + procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override; + procedure MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean); override; + procedure KeyDown(var Key: Word; var KeyChar: System.WideChar; Shift: TShiftState); override; + function ObjectAtPoint(P: TPointF): IControl; override; + procedure DoMouseLeave; override; + procedure Resize; override; + function GetDefaultStyleLookupName: string; override; + procedure ApplyStyle; override; + procedure FreeStyle; override; + procedure Invalidate; + procedure DoRealign; override; + procedure DoExit; override; + /// General state change. Starts TDelayedIncident.ChangeRepainted incident and + /// invokes OnChange + procedure DoChange; virtual; + /// Handle TDelayedIncident.ChangeRepainted incident + procedure DoChangeRepainted; virtual; + /// Invoke OnItemChange handler + procedure DoListItemChange(const AListItem: TListItem); virtual; + /// Invoke OnListItemClick handler + procedure DoListItemClick(const AListItem: TListItem); virtual; + /// Invoke OnEditModeChange handler + procedure DoEditModeChange; virtual; + /// Invoke OnEditModeChanging handler + procedure DoEditModeChanging(var AHandled: Boolean); virtual; + /// Reset edit mode animation + procedure DoResetEditModeAnimation; virtual; + /// Update scrolling limits and animation boundaries + procedure DoUpdateScrollingLimits; virtual; + + // Notifications from IListViewAdapter + procedure DoItemsMayChange; override; + procedure DoItemsCouldHaveChanged; override; + procedure DoItemsInvalidate; override; + /// This virtual method is called immediately after list of items has been changed. + procedure DoItemsChange; override; + procedure DoAdapterSet; override; + /// Deletes an item + /// index of item to be deleted + /// True if deleted succesfully + function DeleteItem(const ItemIndex: Integer): Boolean; + /// Perform actual item deletion. Called from DeleteItem: Boolean + procedure DoDeleteItem(const ItemIndex: Integer); virtual; + /// Get area available to item layout + function GetFinalItemSpaces(const ForceIncludeScrollBar: Boolean = True): TRectF; virtual; + /// Get item size + function GetFinalItemSize(const ForceIncludeScrollBar: Boolean = True): TSizeF; virtual; + function CanObserve(const ID: Integer): Boolean; override; + /// Notify observers about selection change + procedure ObserversBeforeSelection(out LAllowSelection: Boolean); + /// Return True if this item should handle input events + function ShouldHandleEvents: Boolean; virtual; + /// Invoke OnUpdatingItemView handler + procedure DoUpdatingItemView(const AListItem: TListItem; var AHandled: Boolean); virtual; + // Invoke OnUpdateItemView handler + procedure DoUpdateItemView(const AListItem: TListItem); virtual; + /// Get glyph button for item Index + function GetGlyphButton(const Index: Integer): TListItemGlyphButton; + /// Invoked before item view will be updated (before calling ResetObjects) + property OnUpdatingItemView: TUpdatingItemViewEvent read FOnUpdatingItemView write FOnUpdatingItemView; + /// Invoked after item view has been updated (after calling ResetObjects) + property OnUpdateItemView: TUpdateItemViewEvent read FOnUpdateItemView write FOnUpdateItemView; + /// Invoked after EditMode has been changed + property OnEditModeChange: TNotifyEvent read FOnEditModeChange write FOnEditModeChange; + /// Invoked before changing EditMode + property OnEditModeChanging: THandleChangeEvent read FOnEditModeChanging write FOnEditModeChanging; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + class function GetDefaultMargins: TRectF; + procedure EndUpdate; override; + /// When using native presentation, re-creates the list and updates visible item content + procedure RebuildList; virtual; + /// Scrolls the view (instantly) to the desired item placing it within the view + procedure ScrollTo(const AItemIndex: Integer); + /// Index of selected item + property ItemIndex: Integer read GetItemIndex write SetItemIndex default -1; + /// Selected item + property Selected: TListItem read GetSelected write SetSelected; + /// Scroll position in dimension units + property ScrollViewPos: Single read FScrollViewPos write SetScrollViewPos; + /// Get rectangle of item, relative control + function GetItemRect(const AItemIndex: Integer): TRectF; + /// This method should be called when "pull to refresh" mode has been triggered to stop spinning wheel + /// + /// This has only effect in native iOS control and only when PullRefreshWait property is set to True + /// + procedure StopPullRefresh; virtual; + /// Space in logical units around the content of each list item + property ItemSpaces: TBounds read FItemSpaces write SetItemSpaces; + /// The list of images. Can be nil. See also FMX.ActnList.IGlyph + property Images: TCustomImageList read GetImages write SetImages; + /// Space in logical units on all sides around the list box encompassing the items + property SideSpace: Integer read FSideSpace write SetSideSpace default 0; + /// If the control is transparent, it will not draw its background + property Transparent: Boolean read FTransparent write SetTransparent; + /// Determines whether the items are selectable or not. If items are not selectable, user will still be + /// able to click on embedded controls + property AllowSelection: Boolean read FAllowSelection write FAllowSelection default True; + // Enabling this will switch fill colors for odd and even elements + property AlternatingColors: Boolean read FAlternatingColors write SetAlternatingColors default False; + /// Determines whether the selection is visible when selecting items. + /// It may be disabled when using list of checkboxes + property ShowSelection: Boolean read FShowSelection write SetShowSelection default True; + /// Enables swipe delete + property CanSwipeDelete: Boolean read FCanSwipeDelete write SetCanSwipeDelete default True; + /// Enable automatic scrolling to the top when tapped at the top edge + property AutoTapScroll: Boolean read FAutoTapScroll write FAutoTapScroll default False; + /// Threshold distance from the top edge at which the tap would initiate autoscroll to top + property AutoTapTreshold: Integer read FAutoTapTreshold write FAutoTapTreshold default 8; + /// Disables mouse wheel + property DisableMouseWheel: Boolean read FDisableMouseWheel write FDisableMouseWheel default False; + /// Item count + property ItemCount: Integer read GetItemCount; + /// Item click handler + property OnListItemClick: TListItemEvent read FOnListItemClick write FOnListItemClick; + /// Extended item click handler which gets click coordinates and clicked drawable + property OnItemClickEx: TListItemClickEventEx read FOnItemClickEx write FOnItemClickEx; + /// Reserved + property OnItemChange: TListItemEvent read FOnItemChange write FOnItemChange; + /// General OnChange event handler + property OnChange: TNotifyEvent read FOnChange write FOnChange; + /// TDelayedIncident.ChangeRepainted delayed incident handler + property OnChangeRepainted: TNotifyEvent read FOnChangeRepainted write FOnChangeRepainted; + /// This event occurs after list of items has been changed. + property OnItemsChange: TNotifyEvent read FOnItemsChange write FOnItemsChange; + /// Called when ScrollViewPos has changed (manually or in code) + property OnScrollViewChange: TNotifyEvent read FOnScrollViewChange write FOnScrollViewChange; + /// Query before item deletion, see TDeletingItemEvent. + /// Application can veto deletion by returning False in handler Result + /// Deletion is performed by DoDeleteItem + property OnDeletingItem: TDeletingItemEvent read FOnDeletingItem write FOnDeletingItem; + /// Invoked after item has been deleted + property OnDeleteItem: TDeleteItemEvent read FOnDeleteItem write FOnDeleteItem; + /// Invoked when Delete button changes visibility + property OnDeleteChangeVisible: TDeleteChangeVisibilityEvent read FOnDeleteChange write FOnDeleteChange; + /// + property OnSearchChange: TNotifyEvent read FOnSearchChange write FOnSearchChange; + /// Event handler for setting custom filter on text of TListView. + property OnFilter: TFilterEvent read FOnFilter write SetOnFilter; + /// Invoked when pull refresh is triggered + property OnPullRefresh: TNotifyEvent read FOnPullRefresh write FOnPullRefresh; + /// Text to display in Delete button + property DeleteButtonText: string read GetDeleteButtonText write SetDeleteButtonText stored + DeleteButtonTextStored nodefault; + /// Enable/disable Edit Mode + property EditMode: Boolean read FEditMode write SetEditMode default False; + /// True if search bar is visible + property SearchVisible: Boolean read FSearchVisible write SetSearchVisible default False; + /// Always display search bar + property SearchAlwaysOnTop: Boolean read FSearchAlwaysOnTop write SetSearchAlwaysOnTop default True; + /// Enable selection crossfade animation + property SelectionCrossfade: Boolean read FSelectionCrossfade write SetSelectionCrossfade default False; + /// Enable pull to refresh + property PullToRefresh: Boolean read FPullToRefresh write SetPullToRefresh default False; + /// When set to True, the spinning wheel does not disappear automatically and StopPullRefresh method needs + /// to be called after refresh operation is done. If this is set to False (default), then spinning wheel disappears + /// automatically shortly after triggering the effect. This option works only in native iOS control and has no + /// effect otherwise. + property PullRefreshWait: Boolean read FPullRefreshWait write FPullRefreshWait default False; + /// Control type: Styled or Native + property ControlType: TControlType read FControlType write SetControlType default TControlType.Styled; + /// Options for Native control; see ControlType + property NativeOptions: TListViewNativeOptions read FNativeOptions write SetNativeOptions default []; + + procedure RebuildOrientation; // ZuBy + procedure EnableTouchAnimation(Value: Boolean); // ZuBy + // Scroll Width + function GetScrollWidth: Single; // ZuBy + function FindItemByPosition(X, Y: Single): Integer; // ZuBy + // Get Item Height + function getHeightByIndex(Index: Integer): Integer; // ZuBy + function getItemTextHeight(const AItem: TListItemText; const aWidth: Single = 0): Integer; // ZuBy + function getItemTextButtonHeight(const AItem: TListItemTextButton; const aWidth: Single = 0): Integer; // ZuBy + function getItemTextWidth(const AItem: TListItemText; const aHeight: Single = 0): Integer; // ZuBy + function getItemTextButtonWidth(const AItem: TListItemTextButton; const aHeight: Single = 0): Integer; // ZuBy + // Ani Calculaction + function getAniCalc: TAniCalculations; // ZuBy + procedure SearchBoxClear; // ZuBy + property ShowFirstSeparator: Boolean read FShowFirstSeparator write SetShowFirstSeparator default True; + property ShowLastSeparator: Boolean read FShowLastSeparator write SetShowLastSeparator default True; + property TransparentSeparators: Boolean read FTransparentSeparator write SetTransparentSeparator default False; // ZuBy + property TransparentItems: Boolean read FTransparentItems write SetTransparentItems default False; // ZuBy + property TransparentHeaders: Boolean read FTransparentHeaders write SetTransparentHeader default False; // ZuBy + property AutoPositionToItem: Boolean read FAutoPositionToItem write FAutoPositionToItem default False; // ZuBy + // Separator Draw + property SeparatorLeftOffset: Single read FSeparatorLeftOffset write SetSeparatorLeftOffset; // ZuBy + property SeparatorRightOffset: Single read FSeparatorRightOffset write SetSeparatorRightOffset; // ZuBy + property ItemBottomOffset: Integer read FItemBottomOffset write SetItemBottomOffset; // ZuBy + property CanSwipeDirection: Boolean read FCanSwipeDirection write FCanSwipeDirection default False; // ZuBy + property Horizontal: Boolean read FHorizontal write SetHorizontal default False; // ZuBy + property MakeSelectedItemVisible: Boolean read FMakeSelectedItemVisible write FMakeSelectedItemVisible default True; + property OffsetTop: Integer read FTopOffset write SetTopOffset; // ZuBy + property OffsetBottom: Integer read FBottomOffset write SetBottomOffset; + // ZuBy + property AutoColumns: Boolean read FAutoColumns write SetAutoColumns; + // ZuBy + property CanScroll: Boolean read FCanScroll write SetCanScroll default True; + // sinuke + property Columns: Integer read FColumns; // ZuBy + property ColumnOffset: Integer read FMarg; // ZuBy + property ColumnWidth: Single read FColumnWidth write SetColumnWidth; // ZuBy + property ShowScrollBar: Boolean read FShowScrollBar write SetShowScrollBar default True; // ZuBy + property OnColumnClick: TColumnClick read FOnColumnClick write FOnColumnClick; // ZuBy + property OnScrollEnd: TScrollEnd read FOnScrollEnd write FOnScrollEnd; + // ZuBy + property OnSwipeDirection: TSwipeDirectionEvent read FOnSwipe write FOnSwipe; // ZuBy + end; + + /// TListView that supports native presentation + TPresentedListView = class(TListViewBase, IListViewPresentationParent, IListViewDesignPresentationParent) + strict private + FPresentation: IListViewPresentation; + FPresentationLocked: Integer; + FCreatingNativeView: Boolean; + protected + /// Lock presentation and execute P + procedure ExecuteInterlocked(const P: TProc); + /// True if item can be selected + function CanSelectItem(const AItemIndex: Integer): Boolean; + /// True if item can be unselected + function CanUnselectItem(const AItemIndex: Integer): Boolean; + /// Called after item has been selected + procedure DidSelectItem(const AItemIndex: Integer); + /// Called after item has been unselected + procedure DidUnselectItem(const AItemIndex: Integer); + procedure ChangeOrder; override; + procedure ParentChanged; override; + procedure PaintChildren; override; + procedure AncestorVisibleChanged(const Visible: Boolean); override; + procedure DoSetItemIndexInternal(const Value: Integer); override; + procedure DoEditModeChange; override; + procedure DoItemsChange; override; + procedure DoItemsInvalidate; override; + procedure DoItemInvalidated(const Item: TListItem); override; + procedure DoCheckStateChanged(const AItem: TListItem; const Control: TListItemDrawable); override; + procedure DoUpdateScrollViewPos(const Value: Single); override; + procedure DoSetScrollViewPos(const Value: Single); override; + procedure DoDeleteItem(const ItemIndex: Integer); override; + procedure DoResetEditModeAnimation; override; + procedure DoUpdateScrollingLimits; override; + procedure DoAbsoluteChanged; override; + // Presentation + /// Parent control has been loaded + procedure PMAncesstorPresentationLoaded(var AMessage: TDispatchMessageWithValue); message PM_ANCESTOR_PRESENTATION_LOADED; + procedure RecreateNativePresentation; override; + function ShouldHandleEvents: Boolean; override; + // IPresentationParent + function GetRootObject: TObject; + function GetContentFrame: TRect; + function GetControlOpacity: Single; + // IListViewPresentationParent + function GetAdapter: IListViewAdapter; + function GetItemText(const ItemIndex: Integer): string; + function GetItemIndexTitle(const ItemIndex: Integer): string; + procedure ItemButtonClicked(const ItemIndex: Integer); + procedure InvokePullRefresh; + procedure SetSearchFilter(const Filter: string); + function GetTableViewFlags: TListViewModeFlags; + function GetTableViewOptions: TListViewNativeOptions; + function IListViewPresentationParent.GetFlags = GetTableViewFlags; + function IListViewPresentationParent.GetOptions = GetTableViewOptions; + procedure SetCreatingNativeView(const Value: Boolean); + function GetIsTransparent: Boolean; + function GetOpacity: Single; + function GetBackgroundStyleColor: TAlphaColor; + procedure DoItemsResize; override; + // IListViewDesignPresentationParent + function HasDesignPresentationAttached: Boolean; + public + destructor Destroy; override; + procedure BeforeDestruction; override; + procedure RecalcEnabled; override; + procedure Show; override; + procedure Hide; override; + procedure Resize; override; + procedure Paint; override; + procedure RebuildList; override; + procedure StopPullRefresh; override; + procedure RecalcOpacity; override; + end; + + /// TAppearanceListView supports Appearances. Appearances are templates used for dynamic creation + /// of item views. Normally all items of the same purpose in the List View share the same appearance, differing + /// only in data + TAppearanceListView = class(TPresentedListView, IAppearanceItemOwner, IPublishedAppearanceOwner) + public type + /// Generic event invoked on TListViewItem + TItemEvent = procedure(const Sender: TObject; const AItem: TListViewItem) of object; + /// See DoUpdateItemView + TUpdateObjectsEvent = TItemEvent; + /// See DoUpdatingItemView + TUpdatingObjectsEvent = procedure(const Sender: TObject; const AItem: TListViewItem; var AHandled: Boolean) of object; + + strict private + FAppearanceViewItems: TAppearanceListViewItems; + FAppearanceProperties: TPublishedAppearance; + FItemAppearanceObjects: TPublishedObjects; + FItemAppearanceProperties: TItemAppearanceProperties; + FItemEditAppearanceProperties: TItemAppearanceProperties; + FHeaderAppearanceProperties: TItemAppearanceProperties; + FFooterAppearanceProperties: TItemAppearanceProperties; + FUpdatingAppearance: Integer; + FChangedAppearanceObjects: TListItemPurposes; + FChangedAppearanceHeights: TListItemPurposes; + // See also FItemSelectedBeforeChange + FItemSelectedBeforeEdit: TListItem; + FOnButtonClick: TItemControlEvent; + FOnButtonChange: TItemControlEvent; + FAppearanceAllowsCheckboxes: Boolean; + FAppearanceAllowsDeleteMode: Boolean; + FOnItemClick: TItemEvent; + FOnUpdatingObjects: TUpdatingObjectsEvent; + FOnUpdateObjects: TUpdateObjectsEvent; + + function GetFooterAppearanceName: string; + function GetFooterAppearanceClassName: string; + function GetHeaderAppearanceName: string; + function GetHeaderAppearanceClassName: string; + function GetItemAppearanceName: string; + function GetItemEditAppearanceName: string; + function GetItemObjectsClassName: string; + function GetItemEditObjectsClassName: string; + procedure SetFooterAppearanceClassName(const Value: string); + procedure SetHeaderAppearanceClassName(const Value: string); + procedure SetItemObjectsClassName(const Value: string); + procedure SetItemEditObjectsClassName(const Value: string); + procedure SetFooterAppearanceName(const Value: string); + procedure SetHeaderAppearanceName(const Value: string); + procedure SetItemAppearanceName(const Value: string); + procedure SetItemEditAppearanceName(const Value: string); + + procedure SetAppearanceProperties(const Value: TPublishedAppearance); + procedure SetItemAppearanceObjects(const Value: TPublishedObjects); + function GetItemAppearanceObjects: TPublishedObjects; + procedure AppearanceResetObjects(APurposes: TListItemPurposes); + procedure AppearanceResetHeights(APurposes: TListItemPurposes); + + { IPublishedAppearanceOwner } + + function GetFooterAppearanceProperties: TItemAppearanceProperties; + function GetHeaderAppearanceProperties: TItemAppearanceProperties; + function GetItemAppearanceProperties: TItemAppearanceProperties; + function GetItemEditAppearanceProperties: TItemAppearanceProperties; + + procedure EditorBeforeItemAdded(Sender: IListViewEditor); + procedure EditorAfterItemAdded(Sender: IListViewEditor; const Item: TListItem); + procedure EditorBeforeItemDeleted(Sender: IListViewEditor; const Index: Integer); + procedure EditorAfterItemDeleted(Sender: IListViewEditor); + procedure ResetViewAppearance(const AItem: TListViewItem); + + protected + procedure ApplyStyle; override; + /// Handler of + /// TAppearanceListViewItems.OnNotify + procedure ObjectsNotify(Sender: TObject; const Item: TListItem; Action: TCollectionNotification); + /// TAppearanceListView needs adapter to be TAppearanceListViewItems or derivative. + /// If TAppearanceListView is used with a custom adapter, use Items property to set it + /// instead of Adapter property of the base class + procedure SetAppearanceListViewItems(const AItems: TAppearanceListViewItems); + procedure DoResetView(const Item: TListItem); override; + + function HasButtonsInCells: Boolean; override; + function HasDeletionEditMode: Boolean; override; + function HasCheckboxMode: Boolean; override; + procedure SetItemHeight(const Value: Integer); virtual; + procedure SetItemEditHeight(const Value: Integer); virtual; + procedure SetHeaderHeight(const Value: Integer); virtual; + procedure SetFooterHeight(const Value: Integer); virtual; + + function GetAppearanceListViewItem(const Index: Integer): TListViewItem; virtual; + /// Get height of a specific item + function GetItemHeight(const Index: Integer): Integer; overload; override; + // See respective properties + function GetItemHeight: Integer; overload; virtual; + function GetItemEditHeight: Integer; overload; virtual; + function GetHeaderHeight: Integer; overload; virtual; + function GetFooterHeight: Integer; overload; virtual; + + procedure WillEnterEditMode(const Animated: Boolean); override; + procedure DoResetEditModeAnimation; override; + + procedure DoAdapterSet; override; + // hooks from IListViewController + procedure DoRequestReindexing(const Item: TListItem); override; + procedure DoItemResized(const Item: TListItem); override; + procedure DoCheckStateChanged(const AItem: TListItem; const Control: TListItemDrawable); override; + procedure DoControlClicked(const Item: TListItem; const Control: TListItemDrawable); override; + /// Returns an array of 4 elements comprised by + /// ItemEditAppearanceProperties + /// ItemAppearanceProperties + /// HeaderAppearanceProperties + /// FooterAppearanceProperties + function GetAppearanceProperties: TArray; + /// Refresh items with specified purposes; all items if the set is empty; + /// see also IListViewAdapter.ResetViews + procedure RefreshAppearances(const APurposes: TListItemPurposes = []); + /// Same as RefreshAppearances + procedure UpdateAppearanceStyleResources; + /// Invoked when item appearance changes; resets all item views + procedure ItemAppearanceChange(const Sender: TItemAppearanceProperties); + /// Invoked when Appearance Objects (view prototype) change + procedure ItemAppearanceChangeObjects(const Sender: TItemAppearanceProperties); + /// Invoked when appearance height is changed + procedure ItemAppearanceChangeHeight(const Sender: TItemAppearanceProperties); + /// Resets all item views when entering edit mode + procedure EditModeAppearances; + /// Reset appearance. When TAppearanceListView is created, it creates appearances + /// for Item, Edit mode Item, Header, Footer and initializes them by calling this virtual method. + /// By contract it must select TItemAppearanceProperties.AppearanceClass by searching in + /// appearances registry for item purpose specified during TItemAppearanceProperties creation + /// instance of TItemAppearanceProperties + /// See implementation in + /// TListView.InitializeItemAppearance + /// See also TAppearancesRegistry + /// + procedure InitializeItemAppearance(const AAppearance: TItemAppearanceProperties); virtual; + + procedure DoListItemClick(const AItem: TListItem); override; + procedure DoUpdatingItemView(const AListItem: TListItem; var AHandled: Boolean); override; + procedure DoUpdateItemView(const AListItem: TListItem); override; + + // General compatibility properties + /// Item height defined by appearance + property ItemHeight: Integer read GetItemHeight write SetItemHeight; + /// Item height in edit mode defined by appearance + property ItemEditHeight: Integer read GetItemEditHeight write SetItemEditHeight; + /// Header height defined by appearance + property HeaderHeight: Integer read GetHeaderHeight write SetHeaderHeight; + /// Footer height defined by appearance + property FooterHeight: Integer read GetFooterHeight write SetFooterHeight; + + // Appearance related properties + /// Item in edit mode appearance class name + /// Must be loaded prior to other Item, header and footer properties + /// Changing class name will load new appearance and reinitialize all views + /// + property ItemEditAppearanceClassName: string read GetItemEditObjectsClassName write SetItemEditObjectsClassName; + /// Item appearance class name + /// Must be loaded prior to other Item, header and footer properties + /// Changing class name will load new appearance and reinitialize all views + /// + property ItemAppearanceClassName: string read GetItemObjectsClassName write SetItemObjectsClassName; + /// Header appearance class name + /// Must be loaded prior to other Item, header and footer properties + /// Changing class name will load new appearance and reinitialize all views + /// + property HeaderAppearanceClassName: string read GetHeaderAppearanceClassName write SetHeaderAppearanceClassName; + /// Footer appearance class name + /// Must be loaded prior to other Item, header and footer properties + /// Changing class name will load new appearance and reinitialize all views + /// + property FooterAppearanceClassName: string read GetFooterAppearanceClassName write SetFooterAppearanceClassName; + + /// Assign new appearance by name; this will effectively change ItemAppearanceClassName and reload + /// all views + property ItemAppearanceName: string read GetItemAppearanceName write SetItemAppearanceName stored False; + /// Assign new appearance by name; this will effectively change ItemEditAppearanceClassName and reload + /// all views + property ItemEditAppearanceName: string read GetItemEditAppearanceName write SetItemEditAppearanceName stored False; + /// Assign new appearance by name; this will effectively change HeaderAppearanceClassName and reload + /// all views + property HeaderAppearanceName: string read GetHeaderAppearanceName write SetHeaderAppearanceName stored False; + /// Assign new appearance by name; this will effectively change FooterAppearanceClassName and reload + /// all views + property FooterAppearanceName: string read GetFooterAppearanceName write SetFooterAppearanceName stored False; + // TPublishedAppearance represents appearances in the object inspector + property ItemAppearance: TPublishedAppearance read FAppearanceProperties write SetAppearanceProperties; + /// TPublishedObjects represents appearance items (collections of objects comprising appearances) + /// in the object inspector + property ItemAppearanceObjects: TPublishedObjects read GetItemAppearanceObjects write SetItemAppearanceObjects; + /// Invoked on check button state change + property OnButtonChange: TItemControlEvent read FOnButtonChange write FOnButtonChange; + /// Invoked on embedded button click + property OnButtonClick: TItemControlEvent read FOnButtonClick write FOnButtonClick; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + procedure BeginUpdate; override; + procedure EndUpdate; override; + procedure Resize; override; + /// Access to the extended adapter that supports appearances and works with extended + /// items; see TListViewItem + property Items: TAppearanceListViewItems read FAppearanceViewItems write SetAppearanceListViewItems; + /// Item click event handler + property OnItemClick: TItemEvent read FOnItemClick write FOnItemClick; + /// Invoked when view is being created by TAppearanceListView.ResetViewAppearance before + /// calling ResetObjects. If not Handled, the view will be recreated + /// Call sequence: + /// TAppearanceListView.ResetViewAppearance: + /// OnUpdatingObjects -> + /// TItemAppearanceObjects.ResetObjects -> OnUpdateObjects + property OnUpdatingObjects: TUpdatingObjectsEvent read FOnUpdatingObjects write FOnUpdatingObjects; + /// Invoked when view is being created by TAppearanceListView.ResetViewAppearance after + /// calling ResetObjects + /// Call sequence: + /// TAppearanceListView.ResetViewAppearance: + /// OnUpdatingObjects -> + /// TItemAppearanceObjects.ResetObjects -> OnUpdateObjects + property OnUpdateObjects: TUpdateObjectsEvent read FOnUpdateObjects write FOnUpdateObjects; + + // True Items Clear + procedure ItemsClearTrue; // ZuBy + // Scroll + function getFirstVisibleItemIndex: Integer; // ZuBy + function getLastVisibleItemindex: Integer; // ZuBy + function getVisibleCount: Integer; // ZuBy + // Custom Item Draw + procedure SetCustomColorForItem(const ItemIndex: Integer; const aColor: TAlphaColor); // ZuBy + procedure SetDefaultColorForItem(const ItemIndex: Integer); // ZuBy + function IsCustomColorUsed(const ItemIndex: Integer): Boolean; // ZuBy + // Item Draw + procedure SetColorItemSelected(aColor: TAlphaColor); // ZuBy + procedure SetColorBackground(aColor: TAlphaColor); // ZuBy + procedure SetColorItemFill(aColor: TAlphaColor); // ZuBy + procedure SetColorItemFillAlt(aColor: TAlphaColor); // ZuBy + procedure SetColorItemSeparator(aColor: TAlphaColor); // ZuBy + procedure SetColorHeader(aColor: TAlphaColor); // ZuBy + // Text Draw + procedure SetColorText(aColor: TAlphaColor); // ZuBy + procedure SetColorTextSelected(aColor: TAlphaColor); // ZuBy + procedure SetColorTextDetail(aColor: TAlphaColor); // ZuBy + procedure SetColorTextHeader(aColor: TAlphaColor); // ZuBy + procedure SetColorTextHeaderShadow(aColor: TAlphaColor); // ZuBy + procedure SetColorButtonText(aColor: TAlphaColor); // ZuBy + procedure SetColorButtonTextPressed(aColor: TAlphaColor); // ZuBy + procedure SetColorDeleteText(aColor: TAlphaColor); // ZuBy + procedure SetColorDeleteTintColor(aColor: TAlphaColor); // ZuBy + procedure SetColorDeleteTextPressed(aColor: TAlphaColor); // ZuBy + // PullRefresh Draw + procedure SetColorPullRefresh(aColor: TAlphaColor); // ZuBy + procedure SetColorPullRefreshIndicator(aColor: TAlphaColor); // ZuBy + procedure SetColorStretchGlow(aColor: TAlphaColor); // ZuBy + end; + + TCustomListView = class(TAppearanceListView) + end; + + TListView = class(TCustomListView) + protected + procedure InitializeItemAppearance(const AAppearance: TItemAppearanceProperties); override; + public + // Hoist protected appearance properties + property ItemAppearanceName; + property ItemEditAppearanceName; + property HeaderAppearanceName; + property FooterAppearanceName; + published + // Hoist protected appearance properties + property ItemAppearanceClassName; + property ItemEditAppearanceClassName; + property HeaderAppearanceClassName; + property FooterAppearanceClassName; + + property OnUpdatingObjects; + property OnUpdateObjects; + property OnEditModeChange; + property OnEditModeChanging; + property EditMode; + + property Transparent default False; + property AllowSelection; + property AlternatingColors; + property ItemIndex; + property Images; + property ScrollViewPos; + property ItemSpaces; + property SideSpace; + + property Align; + property Anchors; + property CanFocus default True; + property CanParentFocus; + property ClipChildren default True; + property ClipParent default False; + property Cursor default crDefault; + property DisableFocusEffect default True; + property DragMode default TDragMode.dmManual; + property EnableDragHighlight default True; + property Enabled default True; + property Locked default False; + property Height; + property Hint; + property HitTest default True; + property Margins; + property Opacity; + property Padding; + property PopupMenu; + property Position; + property RotationAngle; + property RotationCenter; + property Scale; + property Size; + property TabOrder; + property TabStop; + property Visible default True; + property Width; + property ParentShowHint; + property ShowHint; + + property ShowFirstSeparator; // zubu + property ShowLastSeparator; // zubu + property TransparentSeparators; // zubu + property TransparentItems; // zubu + property AutoPositionToItem; // zubu + property SeparatorLeftOffset; // zubu + property SeparatorRightOffset; // zubu + property ItemBottomOffset; // zubu + property Horizontal; // zubu + + {events} + property OnApplyStyleLookup; + {Drag and Drop events} + property OnDragEnter; + property OnDragLeave; + property OnDragOver; + property OnDragDrop; + property OnDragEnd; + {Keyboard events} + property OnKeyDown; + property OnKeyUp; + {Mouse events} + property OnCanFocus; + + property OnEnter; + property OnExit; + property OnMouseDown; + property OnMouseMove; + property OnMouseUp; + property OnMouseWheel; + property OnMouseEnter; + property OnMouseLeave; + + property OnPainting; + property OnPaint; + property OnResize; + property OnResized; + + property ItemAppearance; + property ItemAppearanceObjects; + + property HelpContext; + property HelpKeyword; + property HelpType; + + property StyleLookup; + property TouchTargetExpansion; + property OnClick; + property OnDblClick; + + { ListView selection events } + property CanSwipeDelete; + + property OnChange; + property OnChangeRepainted; + property OnItemsChange; + property OnScrollViewChange; + property OnItemClick; + property OnItemClickEx; + property OnButtonClick; + property OnButtonChange; + + property OnDeletingItem; + property OnDeleteItem; + property OnDeleteChangeVisible; + property OnSearchChange; + property OnFilter; + property OnPullRefresh; + property DeleteButtonText; + + property AutoTapScroll; + property AutoTapTreshold; + property ShowSelection; + property DisableMouseWheel; + + property SearchVisible; + property SearchAlwaysOnTop; + property SelectionCrossfade; + property PullToRefresh; + property PullRefreshWait; + + property ControlType; + property NativeOptions; + end; +{$ENDREGION} + + EListViewError = class(Exception); + +implementation + +uses + {$IFDEF MACOS}Macapi.CoreFoundation,{$ENDIF} System.SyncObjs, System.Math, System.RTLConsts, System.TypInfo, + System.Math.Vectors, FMX.Consts, FMX.Ani, FMX.Utils, FMX.BehaviorManager, FMX.Forms + {$IFDEF IOS}, FMX.ListView.iOS{$ENDIF} {$IFDEF ANDROID}, FMX.ListView.Android{$ENDIF}; + +var + LVTextLayout: TTextLayout; + +{$REGION 'Types, constants and helper functions'} + +const +{$IFDEF IOS} + DefaultScrollBarWidth = 7; +{$ELSE} +{$IFDEF MACOS} + DefaultScrollBarWidth = 7; +{$ENDIF} +{$ENDIF} +{$IFDEF MSWINDOWS} + DefaultScrollBarWidth = 16; +{$ENDIF} +{$IFDEF ANDROID} + DefaultScrollBarWidth = 7; +{$ENDIF} +{$IFDEF LINUX} + DefaultScrollBarWidth = 16; +{$ENDIF} + +type + TOpenBitmap = class(TBitmap); + TOpenReader = class(TReader); + TEditModeOption = (DisallowSelection, HideSelection, RadioButtonMode, UncheckMode, ModalMode, ClearWhenStart, + MultiSelect); + TEditModeOptions = set of TEditModeOption; + +function RectF(X, Y, Width, Height: Single): TRectF; inline; +begin + Result.Left := X; + Result.Top := Y; + Result.Right := X + Width; + Result.Bottom := Y + Height; +end; + +procedure DisableHitTestForControl(const AControl: TControl); +var + LChild: TFmxObject; +begin + AControl.HitTest := False; + if AControl.Children <> nil then + for LChild in AControl.Children do + if LChild is TControl then + DisableHitTestForControl(TControl(LChild)); +end; + +{$ENDREGION} +{$REGION 'TListViewBase'} + +class function TListViewBase.TItemSelectionAlpha.Create(const StartTime: Double; + const Alpha, StartAlpha: Single): TItemSelectionAlpha; +begin + Result.StartTime := StartTime; + Result.Alpha := Alpha; + Result.StartAlpha := StartAlpha; +end; + +class function TListViewBase.GetDefaultMargins: TRectF; +begin + Result := TRectF.Create(DefaultLeftMargin, 0, DefaultRightMargin, 0); +end; + +constructor TListViewBase.Create(AOwner: TComponent); +begin + inherited; + + if not TPlatformServices.Current.SupportsPlatformService(IFMXTimerService, FTimerService) then + raise EUnsupportedPlatformService.Create('IFMXTimerService'); + + FImageLink := TGlyphImageLink.Create(Self); + FImageLink.IgnoreIndex := True; + TPlatformServices.Current.SupportsPlatformService(IFMXSystemInformationService, FSystemInformationService); + TPlatformServices.Current.SupportsPlatformService(IFMXListingService, FListingService); + + FDragListMode := TInternalDragMode.None; + FDeleteButtonIndex := -1; + FPrevDeleteButtonIndex := -1; + FSearchVisible := False; + FSearchAlwaysOnTop := True; + FCanSwipeDelete := False; + FPullToRefresh := False; + + FDelayedIncidents := TDelayedIncidents.Create; + FSelectionAlphas := TItemSelectionAlphas.Create; + + CanFocus := True; + DisableFocusEffect := True; + AutoCapture := True; + ClipChildren := True; + + FItemBottomOffset := 0; // ZuBy + FHorizontal := False; // ZuBy + FTopOffset := 0; // ZuBy + FBottomOffset := 0; // ZuBy + FAutoColumns := False; // ZuBy + FColumnWidth := 180; // ZuBy + FCanScroll := True; // sinuke + FColumns := 1; // ZuBy + FMarg := 0; // ZuBy + + FScrollBar := TScrollBar.Create(nil); + FScrollBar.Stored := False; + FScrollBar.Orientation := TOrientation.Vertical; + FScrollBar.Align := TAlignLayout.Right; + FScrollBar.Width := DefaultScrollBarWidth; + + FScrollBar.Parent := Self; + + if (not HasTouchTracking) or (csDesigning in ComponentState) then + begin + FScrollBar.Visible := False; + FScrollBar.OnChange := ScrollBarChange; + end; + + if HasTouchTracking then + begin + FAniCalc := TAniCalculations.Create(nil); + FAniCalc.Animation := True; + FAniCalc.OnChanged := AniCalcChange; + FAniCalc.Interval := PhysicsProcessingInterval; + FAniCalc.OnStart := AniCalcStart; + FAniCalc.OnStop := AniCalcStop; + FAniCalc.BoundsAnimation := HasPhysicsStretchyScrolling; + end; + + FItemSpaces := TBounds.Create(GetDefaultMargins); + FItemSpaces.OnChange := ItemSpacesChange; + + FBrush := TBrush.Create(TBrushKind.Solid, $FF000000); + FStroke := TStrokeBrush.Create(TBrushKind.Solid, $FF000000); + + FStyleResources := TListItemStyleResources.Create; + + FHeightSums := TItemHeightSums.Create; + + FItemIndex := -1; + FMouseClickIndex := -1; + FMouseEventIndex := -1; + FTransparent := False; + FAllowSelection := True; + FShowSelection := True; + FAutoTapTreshold := 8; + FTapSelectItemIndex := -1; + FTapSelectNewIndexApplied := -1; + FSelectionCrossfade := False; + FDeleteButtonText := DefaultDeleteButtonText; + + FMaxKnownHeight := 0; + FScrollScale := 1; + + FTransitionType := TTransitionType.None; +end; + +destructor TListViewBase.Destroy; +begin + FMessageSender.Free; + + DestroyRecurrentTimer; + + if FSearchEdit <> nil then + begin + FSearchEdit.Parent := nil; + FreeAndNil(FSearchEdit); + end; + + FHeightSums.Free; + FStroke.Free; + FBrush.Free; + FItemSpaces.Free; + FreeAndNil(FAniCalc); + + FSelectionAlphas.Free; + FDelayedIncidents.Free; + + FStyleResources.Free; + FTimerService := nil; + FListingService := nil; + FImageLink.DisposeOf; + inherited; +end; + +function TListViewBase.IsRunningOnDesktop: Boolean; +begin + Result := TOSVersion.Platform in [pfWindows, pfMacOS, pfLinux]; +end; + +function TListViewBase.HasTouchTracking: Boolean; +begin + // *** ZuBy +{$IFNDEF MSWINDOWS} + Result := True; +{$ELSE} + Result := (FAniCalc <> nil) or ((FSystemInformationService <> nil) and + (TScrollingBehaviour.TouchTracking in FSystemInformationService.GetScrollingBehaviour)); +{$ENDIF} + // *** ZuBy +end; + +function TListViewBase.HasSearchFeatures: Boolean; +begin + Result := ((FListingService <> nil) and (FListingService.GetSearchFeatures <> [])) or + (csDesigning in ComponentState); +end; + +function TListViewBase.HasSearchAsItem: Boolean; +begin + Result := (FSearchVisible and (csDesigning in ComponentState)) or + (FSearchVisible and (FListingService <> nil) and ((not FSearchAlwaysOnTop) or + (not (TListingSearchFeature.StayOnTop in FListingService.GetSearchFeatures))) and + (TListingSearchFeature.AsFirstItem in FListingService.GetSearchFeatures) and ((FSearchEdit = nil) or + (FSearchEdit.Text.Length < 1))); +end; + +function TListViewBase.IsDeleteModeAllowed: Boolean; +begin + Result := TListingEditModeFeature.Delete in FListingService.GetEditModeFeatures; +end; + +function TListViewBase.IsEditMode: Boolean; +begin + Result := FEditMode; +end; + +function TListViewBase.HasStretchyScrolling: Boolean; +begin +{$IFNDEF MSWINDOWS} + Result := True; +{$ELSE} + Result := HasTouchTracking and (FSystemInformationService <> nil) and + (TScrollingBehaviour.BoundsAnimation in FSystemInformationService.GetScrollingBehaviour); +{$ENDIF} +end; + +function TListViewBase.HasButtonsInCells: Boolean; +begin + Result := False; +end; + +function TListViewBase.HasCheckboxMode: Boolean; +begin + Result := False; +end; + +function TListViewBase.HasDeletionEditMode: Boolean; +begin + Result := False; +end; + +function TListViewBase.HasPhysicsStretchyScrolling: Boolean; +begin + Result := HasTouchTracking and (FPullToRefresh or HasStretchyScrolling); +end; + +function TListViewBase.HasScrollingStretchGlow: Boolean; +begin + Result := (FListingService <> nil) and (TListingTransitionFeature.ScrollGlow in FListingService.GetTransitionFeatures); +end; + +function TListViewBase.HasPullRefreshStroke: Boolean; +begin + Result := (FListingService <> nil) and + not (TListingTransitionFeature.PullToRefresh in FListingService.GetTransitionFeatures) and + ((FPullRefreshAnimation = TPullRefreshAnimation.Playing) or (GetPullRefreshStrength > 0)); +end; + +function TListViewBase.CanDisplaySelectionForItem(const Index: Integer; const Item: TListItem; const IncludeMultiSelect, + IncludeCrossFaded: Boolean): Boolean; +var + ItemAlpha: TItemSelectionAlpha; + Checkable: IListViewCheckProvider; + LItem: TListItem; +begin + LItem := Item; + if LItem = nil then + LItem := Adapter[Index]; + Result := ((FItemIndex = Index) and FShowSelection and LItem.View.Initialized and + (LItem.Purpose = TListItemPurpose.None) and (not FEditMode) and (FDeleteButtonIndex = -1)) or + (HasCheckboxMode and IncludeMultiSelect and LItem.View.Initialized and + Supports(Adapter, IListViewCheckProvider, Checkable) and Checkable.Checked[Index]); + + if (not Result) and IncludeCrossFaded and (FSelectionAlphas <> nil) then + if FSelectionAlphas.TryGetValue(Index, ItemAlpha) then + Result := ItemAlpha.Alpha > TEpsilon.Vector; +end; + +function TListViewBase.GetDefaultSelectionAlpha: Single; +begin + if FEditMode then + Result := EditModeSelectionAlpha + else + Result := 1; +end; + +procedure TListViewBase.OnSearchEditResize(Sender: TObject); +begin + InvalidateHeights; + StartIncident(TDelayedIncident.Invalidate); +end; + +procedure TListViewBase.OnSearchEditChange(Sender: TObject); +begin + if Assigned(FOnSearchChange) then + FOnSearchChange(Self); +end; + +function TListViewBase.GetItemCount: Integer; +begin + if Adapter <> nil then + Result := Adapter.Count + else + Result := -1; +end; + +procedure TListViewBase.DoItemsMayChange; +begin + inherited; + FItemSelectedBeforeChange := Selected; +end; + +procedure TListViewBase.DoItemsCouldHaveChanged; +var + SelectionChanged: Boolean; +begin + inherited; + InvalidateHeights; + SelectionChanged := (Selected <> nil) and (FItemSelectedBeforeChange <> nil) and (Selected <> FItemSelectedBeforeChange); + + if (FItemSelectedBeforeChange <> nil) and + (FItemSelectedBeforeChange.Index >= 0) and (FItemSelectedBeforeChange.Index < Adapter.Count) + and (Adapter[FItemSelectedBeforeChange.Index] = FItemSelectedBeforeChange) then + ItemIndex := FItemSelectedBeforeChange.Index + else + ItemIndex := -1; + + if not InRange(FTapSelectItemIndex, -1, ItemCount - 1) then + FTapSelectItemIndex := -1; + + FItemSelectedBeforeChange := nil; + + if SelectionChanged then + begin + TLinkObservers.ListSelectionChanged(Observers); + DoChange; + end; + + Invalidate; + RebuildList; +end; + +procedure TListViewBase.DoItemsInvalidate; +begin + inherited; + Invalidate; +end; + +procedure TListViewBase.ItemSpacesChange(Sender: TObject); +begin + Invalidate; +end; + +procedure TListViewBase.SetSideSpace(const Value: Integer); +var + NewValue: Integer; +begin + NewValue := Max(Value, 0); + + if FSideSpace <> NewValue then + begin + FSideSpace := NewValue; + Invalidate; + end; +end; + +procedure TListViewBase.SetTransparent(const Value: Boolean); +begin + if FTransparent <> Value then +begin + FTransparent := Value; + Invalidate; + end; +end; + +function TListViewBase.ShouldHandleEvents: Boolean; +begin + Result := True; +end; + +procedure TListViewBase.SelectItem(const ItemIndex: Integer); +var + HasChange: Boolean; + NewItemIndex: Integer; + Checkable: IListViewCheckProvider; +begin + NewItemIndex := ItemIndex; + + if (NewItemIndex >= 0) and (NewItemIndex < Adapter.Count) then + begin + if FEditMode and Supports(Adapter, IListViewCheckProvider, Checkable) then + Checkable[NewItemIndex] := True; + end + else + NewItemIndex := -1; + + HasChange := FItemIndex <> NewItemIndex; + + SetItemIndex(NewItemIndex); + + if NewItemIndex <> -1 then + DoListItemClick(Adapter[NewItemIndex]); + + if HasChange then + DoChange; + + Invalidate; + + FClickEventControl := nil; + FClickEventItemIndex := NewItemIndex; + if ShouldHandleEvents then + FClickEventMousePos := TPointF.Zero; + if not FMouseClickSwipeEventSend then // ZuBy + StartIncident(TDelayedIncident.ClickEvent); +end; + +procedure TListViewBase.UnselectItem(const ItemIndex: Integer); +var + Checkable: IListViewCheckProvider; +begin + if (ItemIndex >= 0) and (ItemIndex < Adapter.Count) and + Supports(Adapter, IListViewCheckProvider, Checkable) then + Checkable[ItemIndex] := False; +end; + +procedure TListViewBase.SetAlternatingColors(const Value: Boolean); +begin + if FAlternatingColors <> Value then + begin + FAlternatingColors := Value; + Invalidate; + end; +end; + +procedure TListViewBase.SetItemIndexInternal(const Value: Integer; const DisableSelection, DisableCrossfade: Boolean); +var + NewValue: Integer; +begin + NewValue := Value; + + if (NewValue < 0) or (NewValue > Adapter.Count - 1) then + NewValue := -1; + + if NewValue <> FItemIndex then + begin + if (not FEditMode) and InRange(FItemIndex, 0, Adapter.Count - 1) and not DisableCrossfade + then + InsertItemCrossFade(FItemIndex, False); + + FItemIndex := NewValue; + + // ZuBy *** + if FMakeSelectedItemVisible then + begin + if FItemIndex <> -1 then + ScrollTo(FItemIndex); // Make selected item visible. + end; + // *** ZuBy + + if (not DisableSelection) and (FItemIndex >= 0) and (FItemIndex < Adapter.Count) then + Adapter[FItemIndex].MouseSelect; + + if (not FEditMode) and (FItemIndex <> -1) and not DisableCrossfade then + InsertItemCrossFade(FItemIndex, True); + + DoSetItemIndexInternal(FItemIndex); + Invalidate; + end; +end; + +function TListViewBase.GetItemIndex: Integer; +begin + Result := FItemIndex; +end; + +procedure TListViewBase.SetItemIndex(const Value: Integer); +begin + // The data may not be ready at the moment of component loading, cannot properly set item index at this time. + if (not (csLoading in ComponentState)) or (Value = -1) then + SetItemIndexInternal(Value) + else + StartIncident(TDelayedIncident.SetItemIndex, True, 0, Value); +end; + +procedure TListViewBase.SetEditMode(const Value: Boolean); +var + LHandled: Boolean; + Checkable: IListViewCheckProvider; +begin + if (FEditMode <> Value) and Supports(Adapter, IListViewCheckProvider, Checkable) then + begin + BeginUpdate; + try + Checkable.CheckAll(False); + + if FDeleteButtonIndex <> -1 then + begin + SetDeleteButtonIndex(-1); + ResetDeleteModeAnimation; + end; + + SetItemIndex(-1); + + LHandled := False; + + DoEditModeChanging(LHandled); + + if not LHandled then + begin + FEditMode := Value; + + if FSearchEdit <> nil then + FSearchEdit.Enabled := not FEditMode; + + if (FListingService <> nil) and (TListingTransitionFeature.EditMode in FListingService.GetTransitionFeatures) then + begin // Animated Edit Mode + if EditMode then + WillEnterEditMode(True); //EditModeAppearances; + InitEditModeAnimation; + end + else + begin // Instant Edit Mode + if FEditMode then + FEditModeTransitionAlpha := 1 + else + FEditModeTransitionAlpha := 0; + WillEnterEditMode(False); + Invalidate; + end; + end; + + DoEditModeChange; + finally + EndUpdate; + end; + end; +end; + +procedure TListViewBase.WillEnterEditMode(const Animated: Boolean); +begin +end; + +procedure TListViewBase.SetCanSwipeDelete(const Value: Boolean); +begin + if FCanSwipeDelete <> Value then + FCanSwipeDelete := Value; +end; + +procedure TListViewBase.SetSearchVisible(const Value: Boolean); +begin + if FSearchVisible <> Value then + begin + FSearchVisible := Value; + + if FSearchVisible and HasSearchFeatures and (FSearchEdit = nil) then + begin + FSearchEdit := TSearchBox.Create(Self); + FSearchEdit.Stored := False; + FSearchEdit.Locked := True; + FSearchEdit.Parent := Self; + FSearchEdit.OnResize := OnSearchEditResize; + FSearchEdit.OnChange := OnSearchEditChange; + FSearchEdit.OnFilter := OnFilter; + end; + + if FSearchEdit <> nil then + begin + FSearchEdit.Visible := FSearchVisible; + UpdateSearchEditPos; + end; + + InvalidateHeights; + Invalidate; + RecreateNativePresentation; + end; +end; + +procedure TListViewBase.SetSearchAlwaysOnTop(const Value: Boolean); +begin + if FSearchAlwaysOnTop <> Value then + begin + FSearchAlwaysOnTop := Value; + InvalidateHeights; + Invalidate; + RecreateNativePresentation; + end; +end; + +procedure TListViewBase.SetControlType(const Value: TControlType); +begin + if FControlType <> Value then + begin + FControlType := Value; + if not (csLoading in ComponentState) then + begin + RecreateNativePresentation; + Invalidate; + end; + end; +end; + +function TListViewBase.GetControlType: TControlType; +begin + Result := FControlType; +end; + +procedure TListViewBase.SetNativeOptions(const Value: TListViewNativeOptions); +begin + if FNativeOptions <> Value then + begin + FNativeOptions := Value; + RecreateNativePresentation; + end; +end; + +procedure TListViewBase.SetOnFilter(const Value: TFilterEvent); +begin + FOnFilter := Value; + if FSearchEdit <> nil then + FSearchEdit.OnFilter := OnFilter; +end; + +function TListViewBase.GetEditModeTransitionAlpha: Single; +begin + Result:= FEditModeTransitionAlpha; +end; + +function TListViewBase.GetDeleteModeTransitionAlpha: Single; +begin + Result:= FDeleteModeTransitionAlpha; +end; + +function TListViewBase.GetItemEditOffset(const Item: TListItem): Single; +var + Provider: IListViewGlyphButtonProvider; + Drawable: TListItemGlyphButton; +begin + Result := 0; + if (Item <> nil) and Supports(Adapter, IListViewGlyphButtonProvider, Provider) then + begin + Drawable := Provider.GlyphButtonDrawable[Item.Index]; + if Drawable <> nil then + Result := Drawable.Width; + end; +end; + +function TListViewBase.GetItemDeleteCutoff(const Item: TListItem): Single; +begin + if (Item <> nil) and ((Item.Index = FDeleteButtonIndex) or (Item.Index = FPrevDeleteButtonIndex)) and + (FDeleteLayout <> nil) then + Result := FDeleteLayout.Position.X + else + Result := 0; +end; + +function TListViewBase.GetClientMargins: TRectF; +begin + Result := LocalRect; +end; + +function TListViewBase.GetItemCurrentSelectionAlpha(const Item: TListItem): Single; +begin + if Item <> nil then + Result := Min(GetItemSelectionAlpha(Item.Index) / GetDefaultSelectionAlpha, 1) + else + Result := 0; +end; + +procedure TListViewBase.CheckStateChanged(const Item: TListItem; const Control: TListItemDrawable); +begin + DoCheckStateChanged(Item, Control); +end; + +procedure TListViewBase.ControlClicked(const Item: TListItem; const Control: TListItemDrawable); +begin + DoControlClicked(Item, Control); +end; + +procedure TListViewBase.RequestReindexing(const Item: TListItem); +begin + DoRequestReindexing(Item); +end; + +procedure TListViewBase.ItemInvalidated(const Item: TListItem); +begin + Invalidate; + DoItemInvalidated(Item); +end; + +procedure TListViewBase.ItemResized(const Item: TListItem); +begin + DoItemResized(Item); +end; + +procedure TListViewBase.DoEditModeChanging(var AHandled: Boolean); +begin + if Assigned(FOnEditModeChanging) then + FOnEditModeChanging(Self, AHandled); +end; + +procedure TListViewBase.DoEditModeChange; +begin + if Assigned(FOnEditModeChange) then + FOnEditModeChange(Self); +end; + +procedure TListViewBase.DoItemInvalidated(const Item: TListItem); +begin +end; + +procedure TListViewBase.DoItemResized(const Item: TListItem); +begin +end; + +procedure TListViewBase.DoRequestReindexing(const Item: TListItem); +begin +end; + +procedure TListViewBase.DoCheckStateChanged(const Item: TListItem; const Control: TListItemDrawable); +begin +end; + +procedure TListViewBase.DoControlClicked(const Item: TListItem; const Control: TListItemDrawable); +begin +end; + +procedure TListViewBase.DoItemsChange; +begin + UpdateScrollingLimits; + RebuildList; + + if Assigned(FOnItemsChange) then + FOnItemsChange(Self); +end; + +procedure TListViewBase.DoAdapterSet; +begin + UpdateScrollingLimits; +end; + +procedure TListViewBase.SetFilterPredicate(const Predicate: TPredicate); +var + Filterable: IListViewFilterable; +begin + if Supports(Adapter, IListViewFilterable, Filterable) then + Filterable.Filter := Predicate; +end; + +procedure TListViewBase.RebuildList; +begin +end; + +procedure TListViewBase.StopPullRefresh; +begin +end; + +procedure TListViewBase.SetItemSpaces(const Value: TBounds); +begin + FItemSpaces.Assign(Value); +end; + +function TListViewBase.GetDeleteButtonText: string; +begin + if FDeleteButton <> nil then + Result := FDeleteButton.Text + else + Result := FDeleteButtonText; +end; + +procedure TListViewBase.SetDeleteButtonText(const Value: string); +begin + FDeleteButtonText := Value; + + if FDeleteButton <> nil then + FDeleteButton.Text := Value; +end; + +function TListViewBase.DeleteButtonTextStored: Boolean; +begin + Result := FDeleteButtonText <> DefaultDeleteButtonText; +end; + +function TListViewBase.DeleteItem(const ItemIndex: Integer): Boolean; +begin + Result := (ItemIndex >= 0) and (ItemIndex < Adapter.Count); + + if Result and Assigned(FOnDeletingItem) then + FOnDeletingItem(Self, ItemIndex, Result); + + if Result then + begin + if (FItemIndex <> -1) and (FItemIndex >= ItemIndex) then + SetItemIndex(-1); + + DoDeleteItem(ItemIndex); + + if Assigned(FOnDeleteItem) then + FOnDeleteItem(Self, FDeleteButtonIndex); + end; +end; + +procedure TListViewBase.DoDeleteItem(const ItemIndex: Integer); +var + Editor: IListViewEditor; + + function Purpose(const AIndex: Integer): TListItemPurpose; + begin + Result := TListItemPurpose.None; + if (AIndex >= 0) and (AIndex < Adapter.Count) then + Result := Adapter[AIndex].Purpose; + end; + + procedure DeleteEmptySection(const Index: Integer); + begin + if Purpose(Index - 1) = TListItemPurpose.Header then + begin + if Purpose(Index) = TListItemPurpose.Footer then + Editor.Delete(Index); + if (Index = Adapter.Count) or (Purpose(Index) <> TListItemPurpose.None) then + Editor.Delete(Index - 1); + end; + end; + +begin + if Supports(Adapter, IListViewEditor, Editor) then + begin + Editor.Delete(ItemIndex); + DeleteEmptySection(ItemIndex); + end; +end; + +function TListViewBase.HasRecurrentTimerEvents: Boolean; +begin + Result := + // Delayed Incidents + ((FDelayedIncidents <> nil) and (FDelayedIncidents.Count > 0)) or + // Animation/transition + (FTransitionType <> TTransitionType.None) or + // Tap Selection + (FTapSelectItemIndex <> -1) or + // Selection Crossfading + ((FSelectionAlphas <> nil) and (FSelectionAlphas.Count > 0)) or + // Pull to Refresh animation + (FPullRefreshAnimation = TPullRefreshAnimation.Playing); +end; + +procedure TListViewBase.DestroyRecurrentTimer; +begin + if FRecurrentTimerHandle <> 0 then + begin + FTimerService.DestroyTimer(FRecurrentTimerHandle); + FRecurrentTimerHandle := 0; + end; +end; + +procedure TListViewBase.UpdateRecurrentTimer; +var + HasEvents: Boolean; +begin + HasEvents := HasRecurrentTimerEvents; + + if HasEvents and (FRecurrentTimerHandle = 0) then + FRecurrentTimerHandle := FTimerService.CreateTimer(RecurrentTimerInterval, RecurrentTimerEvent) + else if (not HasEvents) and (FRecurrentTimerHandle <> 0) then + DestroyRecurrentTimer; +end; + +procedure TListViewBase.RecurrentTimerEvent; +begin + if (FDelayedIncidents <> nil) and (FDelayedIncidents.Count > 0) then + ProcessDelayedIncidents; + + if FTransitionType <> TTransitionType.None then + ProcessTransitionAnimation; + + if FTapSelectItemIndex <> -1 then + ProcessTapSelectItem; + + if (FSelectionAlphas <> nil) and (FSelectionAlphas.Count > 0) then + ProcessSelectionAlphas; + + if FPullRefreshAnimation = TPullRefreshAnimation.Playing then + ProcessPullRefreshAnimation; + + UpdateRecurrentTimer; +end; + +procedure TListViewBase.StartIncident(const Incident: TDelayedIncident; const Triggered: Boolean; + const TimeToWait: Single; const CustomData: NativeInt); +var + Entry: TDelayedIncidentEntry; +begin + FillChar(Entry, SizeOf(TDelayedIncidentEntry), 0); + Entry.Incident := Incident; + Entry.Triggered := Triggered; + Entry.StartTime := FTimerService.GetTick; + Entry.TimeToWait := TimeToWait; + Entry.CustomData := CustomData; + + FDelayedIncidents.Add(Entry); + UpdateRecurrentTimer; +end; + +procedure TListViewBase.TriggerIncidents(const Incident: TDelayedIncident; const ResetStartupTime: Boolean); +var + I: Integer; + CurTime: Double; + Entry: TDelayedIncidentEntry; +begin + CurTime := FTimerService.GetTick; + + for I := 0 to FDelayedIncidents.Count - 1 do + begin + Entry := FDelayedIncidents[I]; + + if Entry.Incident = Incident then + begin + Entry.Triggered := True; + + if ResetStartupTime then + Entry.StartTime := CurTime; + end; + + FDelayedIncidents[I] := Entry; + end; +end; + +procedure TListViewBase.ProcessIncident(const Entry: TDelayedIncidentEntry); +var + J, RowColumns: Integer; + pW, ObjX: Single; + iObject: TListItemDrawable; + ObjPoint: TPointF; + DrawebleName: string; +begin + case Entry.Incident of + TDelayedIncident.ChangeRepainted: + DoChangeRepainted; + + TDelayedIncident.Invalidate: + Invalidate; + + TDelayedIncident.SetItemIndex: + SetItemIndexInternal(Entry.CustomData); + + TDelayedIncident.ClickEvent: + begin + if Assigned(FOnItemClickEx) and (not FMouseClickSwipeEventSend) then + FOnItemClickEx(Self, FClickEventItemIndex, FClickEventMousePos, + FClickEventControl); + // ZuBy *** + if (FAutoColumns) + { and (not FCanSwipeDirection) and (Assigned(FOnColumnClick)) } then + begin + pW := 0; + RowColumns := Min(TListViewItem(Adapter[FClickEventItemIndex]).Tag, + FColumns); + for J := 1 to RowColumns do + begin + pW := (J * FColumnWidth) - FColumnWidth; + if InRange(FClickEventMousePos.X, (pW + (J * FMarg)), + (pW + FColumnWidth) + (J * FMarg)) then + break; + end; + if (J > RowColumns) or (J < 0) then + exit; + if Assigned(FOnColumnClick) then + begin + ObjX := FClickEventMousePos.X - (pW + (J * FMarg)); + DrawebleName := ''; + iObject := TListViewItem(Adapter[FClickEventItemIndex]) + .Objects.FindObjectT('bitmap' + J.ToString); + if iObject <> nil then + begin + ObjPoint := iObject.PlaceOffset.Point; + if InRange(FClickEventMousePos.X, ObjPoint.X, + ObjPoint.X + iObject.Width) and InRange(FClickEventMousePos.Y, + ObjPoint.Y, ObjPoint.Y + iObject.Height) then + DrawebleName := iObject.Name; + end; + if DrawebleName.IsEmpty then + begin + iObject := TListViewItem(Adapter[FClickEventItemIndex]) + .Objects.FindObjectT + ('oi_values' + J.ToString); + if iObject <> nil then + begin + if iObject.Visible then + begin + ObjPoint := iObject.PlaceOffset.Point; + if InRange(FClickEventMousePos.X, ObjPoint.X, + ObjPoint.X + iObject.Width) and + InRange(FClickEventMousePos.Y, ObjPoint.Y, + ObjPoint.Y + iObject.Height) then + DrawebleName := iObject.Name; + end; + end; + end; + if DrawebleName.IsEmpty then + begin + iObject := TListViewItem(Adapter[FClickEventItemIndex]) + .Objects.FindObjectT('price' + J.ToString); + if iObject <> nil then + begin + ObjPoint := iObject.PlaceOffset.Point; + if InRange(FClickEventMousePos.X, ObjPoint.X, + ObjPoint.X + iObject.Width) and InRange(FClickEventMousePos.Y, + ObjPoint.Y, ObjPoint.Y + iObject.Height) then + DrawebleName := iObject.Name; + end; + end; + if DrawebleName.IsEmpty then + DrawebleName := 'item'; + FOnColumnClick(Self, J, ObjX, FClickEventMousePos.Y, + TListViewItem(Adapter[FClickEventItemIndex]), DrawebleName); + end; + // *** ZuBy + end; + end; + end; +end; + +procedure TListViewBase.ProcessDelayedIncidents; + + function IsUnsafeIncident(const Incident: TDelayedIncident): Boolean; + begin + Result := Incident in [TDelayedIncident.ChangeRepainted, TDelayedIncident.ClickEvent]; + end; + +var + I: Integer; + CurTime: Double; + Entry: TDelayedIncidentEntry; + UnsafeIncidents: TDelayedIncidents; +begin + UnsafeIncidents := nil; + try + CurTime := FTimerService.GetTick; + + for I := FDelayedIncidents.Count - 1 downto 0 do + begin + Entry := FDelayedIncidents[I]; + + if Entry.Triggered and (Abs(CurTime - Entry.StartTime) >= Entry.TimeToWait) then + begin + if IsUnsafeIncident(Entry.Incident) then + begin + if UnsafeIncidents = nil then + UnsafeIncidents := TDelayedIncidents.Create; + + UnsafeIncidents.Add(Entry); + end + else + ProcessIncident(Entry); + + FDelayedIncidents.Delete(I); + end; + end; + + if UnsafeIncidents <> nil then + for I := 0 to UnsafeIncidents.Count - 1 do + ProcessIncident(UnsafeIncidents[I]); + finally + UnsafeIncidents.Free; + end; +end; + +procedure TListViewBase.ProcessTransitionAnimation; +begin + case FTransitionType of + TTransitionType.EditMode: + if FEditMode then + begin + FEditModeTransitionAlpha := Min(Abs(FTimerService.GetTick - FTransitionStartTime) / + EditModeAnimationDuration, 1); + + if FEditModeTransitionAlpha >= 1 then + ResetEditModeAnimation; + end + else + begin + FEditModeTransitionAlpha := Max(1 - (Abs(FTimerService.GetTick - FTransitionStartTime) / + EditModeAnimationDuration), 0); + + if FEditModeTransitionAlpha <= 0 then + ResetEditModeAnimation; + end; + + TTransitionType.DeleteMode: + begin + if FDeleteButtonIndex <> -1 then + begin + FDeleteModeTransitionAlpha := Min(Abs(FTimerService.GetTick - FTransitionStartTime) / + DeleteModeAnimationDuration, 1); + + if FDeleteModeTransitionAlpha >= 1 then + ResetDeleteModeAnimation; + end + else + begin + FDeleteModeTransitionAlpha := Max(1 - (Abs(FTimerService.GetTick - FTransitionStartTime) / + DeleteModeAnimationDuration), 0); + + if FDeleteModeTransitionAlpha <= 0 then + ResetDeleteModeAnimation; + end; + + UpdateDeleteButtonLayout; + end; + end; + + if FTransitionType <> TTransitionType.None then + Invalidate; +end; + +procedure TListViewBase.ProcessTapSelectItem; +var + Checkable: IListViewCheckProvider; +begin + if Abs(FTimerService.GetTick - FTapSelectStartTime) >= TapSelectWaitTime then + begin + if FAllowSelection then + begin + if Adapter[FTapSelectItemIndex].HasClickOnSelectItems then + FItemIndex := -1; + + SetNewItemIndex(FTapSelectItemIndex); + FTapSelectNewIndexApplied := FTapSelectItemIndex; + + FClickEventItemIndex := FTapSelectItemIndex; + if not FMouseClickSwipeEventSend then // ZuBy + StartIncident(TDelayedIncident.ClickEvent); + end + else if FEditMode and Supports(Adapter, IListViewCheckProvider, Checkable) then + begin + Checkable[FTapSelectItemIndex] := not Checkable[FTapSelectItemIndex]; + FTapSelectNewIndexApplied := FTapSelectItemIndex; + end; + + FTapSelectItemIndex := -1; + end; +end; + +procedure TListViewBase.ProcessSelectionAlphas; +type + TTrashedItems = TList; +var + TrashedItems: TTrashedItems; + ItemAlpha: TItemSelectionAlpha; + Index: Integer; + MaxIndex: Integer; + CurTime: Double; + NewAlpha, Theta, FinalAlpha: Single; + NeedRepaint: Boolean; +begin + CurTime := FTimerService.GetTick; + NeedRepaint := False; + MaxIndex := Adapter.Count - 1; + TrashedItems := TTrashedItems.Create; + try + for Index in FSelectionAlphas.Keys do + begin + if not FSelectionAlphas.TryGetValue(Index, ItemAlpha) or not InRange(Index, 0, MaxIndex) then + begin + TrashedItems.Add(Index); + Continue; + end; + + if CanDisplaySelectionForItem(Index, Adapter[Index], True) then + begin + Theta := Abs(CurTime - ItemAlpha.StartTime) / SelectionFadeInTime; + FinalAlpha := GetDefaultSelectionAlpha; + end + else + begin + Theta := Abs(CurTime - ItemAlpha.StartTime) / SelectionFadeOutTime; + FinalAlpha := 0; + end; + + NewAlpha := ItemAlpha.StartAlpha + (FinalAlpha - ItemAlpha.StartAlpha) * Theta; + if not SameValue(NewAlpha, ItemAlpha.Alpha, TEpsilon.Vector) then + begin + ItemAlpha.Alpha := NewAlpha; + + if Theta >= 1 then + TrashedItems.Add(Index) + else + FSelectionAlphas.AddOrSetValue(Index, ItemAlpha); + + NeedRepaint := True; + end; + end; + + for Index in TrashedItems do + FSelectionAlphas.Remove(Index); + finally + TrashedItems.Free; + end; + + if NeedRepaint then + Invalidate; +end; + +procedure TListViewBase.InsertItemCrossFade(const Index: Integer; const ShowAnimation: Boolean); +var + ItemAlpha, PrevItemAlpha: TItemSelectionAlpha; +begin + if (not FSelectionCrossFade) or (FSelectionAlphas = nil) or (Adapter[Index].Purpose <> TListItemPurpose.None) then + exit; + + if ShowAnimation then + ItemAlpha := TItemSelectionAlpha.Create(FTimerService.GetTick, 0, 0) + else + ItemAlpha := TItemSelectionAlpha.Create(FTimerService.GetTick, GetDefaultSelectionAlpha, GetDefaultSelectionAlpha); + + if FSelectionAlphas.TryGetValue(Index, PrevItemAlpha) then + begin + ItemAlpha.StartAlpha := PrevItemAlpha.Alpha; + ItemAlpha.Alpha := PrevItemAlpha.Alpha; + end; + + FSelectionAlphas.AddOrSetValue(Index, ItemAlpha); + + UpdateRecurrentTimer; +end; + +procedure TListViewBase.RemoveItemCrossFade(const Index: Integer); +begin + if FSelectionAlphas.ContainsKey(Index) then + FSelectionAlphas.Remove(Index); +end; + +function TListViewBase.GetItemSelectionAlpha(const Index: Integer): Single; +var + ItemAlpha: TItemSelectionAlpha; +begin + if (FSelectionAlphas = nil) or (FSelectionAlphas.Count < 1) then + exit(GetDefaultSelectionAlpha); + + if FSelectionAlphas.TryGetValue(Index, ItemAlpha) then + Result := ItemAlpha.Alpha + else + Result := GetDefaultSelectionAlpha; +end; + +procedure TListViewBase.InitEditModeAnimation; +var + Checkable: IListViewCheckProvider; +begin + if Supports(Adapter, IListViewCheckProvider, Checkable) then + begin + if Checkable.FirstChecked(True) <> -1 then + begin + Checkable.CheckAll(False); + SetDeleteButtonIndex(-1); + end; + + UpdateDeleteButtonLayout; + + FTransitionType := TTransitionType.EditMode; + FTransitionStartTime := FTimerService.GetTick; + UpdateRecurrentTimer; + end; +end; + +procedure TListViewBase.ResetEditModeAnimation; +begin + FTransitionType := TTransitionType.None; + UpdateRecurrentTimer; + + if EditMode then + FEditModeTransitionAlpha := 1 + else + FEditModeTransitionAlpha := 0; + + InvalidateHeights; + Invalidate; +end; + +procedure TListViewBase.InitDeleteModeAnimation; +begin + FTransitionType := TTransitionType.DeleteMode; + FTransitionStartTime := FTimerService.GetTick; + UpdateRecurrentTimer; + + if FDeleteLayout = nil then + begin + FDeleteLayout := TLayout.Create(Self); + FDeleteLayout.Stored := False; + FDeleteLayout.Locked := True; + FDeleteLayout.Width := DefaultDeleteButtonWidth; + FDeleteLayout.ClipChildren := True; + FDeleteLayout.Parent := Self; + end; + + if FDeleteButton = nil then + begin + FDeleteButton := TSpeedButton.Create(FDeleteLayout); + FDeleteButton.Stored := False; + FDeleteButton.Locked := True; + FDeleteButton.Align := TAlignLayout.MostRight; + FDeleteButton.Width := DefaultDeleteButtonWidth; + FDeleteButton.StyleLookup := 'listitemdeletebutton'; + FDeleteButton.Text := FDeleteButtonText; + FDeleteButton.OnClick := DeleteButtonClicked; + FDeleteButton.Parent := FDeleteLayout; + end; + + UpdateDeleteButtonLayout; +end; + +{$WARN SYMBOL_DEPRECATED OFF} +procedure TListViewBase.ResetDeleteModeAnimation; +begin + FTransitionType := TTransitionType.None; + UpdateRecurrentTimer; + + FDeleteButton.Visible := Adapter.Count > 0; + Invalidate; + + if FDeleteButtonIndex <> -1 then + FDeleteModeTransitionAlpha := 1 + else + begin + FPrevDeleteButtonIndex := -1; + FDeleteModeTransitionAlpha := 0; + end; + + if FDeleteButtonIndex = -1 then + begin + // This method can be invoked from event handler of these controls, so we have to remove it later after exiting + // from their event handlers + if FDeleteButton <> nil then + begin + FDeleteButton.Release; + FDeleteButton := nil + end; + + if FDeleteLayout <> nil then + begin + FDeleteLayout.Release; + FDeleteLayout := nil + end; + end; +end; +{$WARN SYMBOL_DEPRECATED DEFAULT} + +procedure TListViewBase.StartPullRefreshAnimation; +begin + FPullRefreshAnimation := TPullRefreshAnimation.Playing; + FPullRefreshAnimationStartTime := FTimerService.GetTick; + FPullRefreshAnimationStopTime := FPullRefreshAnimationStartTime; + UpdateRecurrentTimer; +end; + +procedure TListViewBase.ProcessPullRefreshAnimation; +var + EndTrigger: Boolean; +begin + if TListingTransitionFeature.PullToRefresh in FListingService.GetTransitionFeatures then + EndTrigger := GetPullRefreshIndicatorAlpha <= 0 + else + begin + // ZuBy *** + if FHorizontal then + EndTrigger := GetPullRefreshStrokeWidth >= Height + else + EndTrigger := GetPullRefreshStrokeWidth >= Width; + // *** ZuBy + end; + + if EndTrigger then + begin + FPullRefreshAnimation := TPullRefreshAnimation.Finished; + UpdatePullRefreshState; + end; + + Invalidate; +end; + +function TListViewBase.GetPullRefreshStrength: Single; +begin + if FScrollStretchStrength < 0 then + Result := -FScrollStretchStrength + else + Result := 0; +end; + +function TListViewBase.GetPullRefreshIndicatorSteps: Integer; +const + IndicatorStrengthPerStep = 5; +begin + case FPullRefreshAnimation of + TListViewBase.TPullRefreshAnimation.NotPlaying: + Result := EnsureRange(Round((GetPullRefreshStrength - PullRefreshIndicatorStrengthStart) / + IndicatorStrengthPerStep), 0, PullRefreshIndicatorMaxSteps); + + TListViewBase.TPullRefreshAnimation.Playing: + Result := PullRefreshIndicatorMaxSteps; + + else + Result := 0; + end; +end; + +function TListViewBase.GetPullRefreshIndicatorAlpha: Single; +const + IndicatorFadeVelocity = 4; +begin + case FPullRefreshAnimation of + TListViewBase.TPullRefreshAnimation.NotPlaying: + Result := 1; + + TListViewBase.TPullRefreshAnimation.Playing: + if SameValue(FPullRefreshAnimationStartTime, FPullRefreshAnimationStopTime, TEpsilon.Vector) then + Result := 1 + else + Result := Max((1 - Abs(FTimerService.GetTick - FPullRefreshAnimationStopTime) * IndicatorFadeVelocity), 0); + + else + Result := 0; + end; +end; + +procedure TListViewBase.PaintPullRefreshIndicator(const ACanvas: TCanvas; const AStrength, AOpacity: Single); +const + IndicatorMinRadius = 6.5; + IndicatorMaxRadius = 13.5; + IndicatorThickness = 2; + IndicatorRotation = 2; + IndicatorDisappearFraction = 0.7; + PiMulTwo = 2 * Pi; + PiByTwo = Pi / 2; +var + Stroke: TStrokeBrush; + I, LineCount: Integer; + Center, P1, P2: TPointF; + LSin, LCos, LOpacity, Angle, TimeElapsed, ShrinkAlpha: Single; + MinRadius, MaxRadius, Thickness, TopAdjust: Single; +begin + if (FSearchEdit <> nil) and FSearchEdit.Visible and not HasSearchAsItem then + begin + // ZuBy *** + if FHorizontal then + TopAdjust := FSearchEdit.Width + else + TopAdjust := FSearchEdit.Height; + // *** ZuBy + end + else + TopAdjust := 0; + + LineCount := GetPullRefreshIndicatorSteps; + if LineCount < 1 then + exit; + + LOpacity := GetPullRefreshIndicatorAlpha; + if LOpacity <= 0 then + exit; + + // ZuBy *** + if FHorizontal then + begin + Center.X := Height / 2; + Center.Y := TopAdjust + PullRefreshIndicatorStrengthStart + + IndicatorMaxRadius; + end + else + begin + Center.X := Width / 2; + Center.Y := TopAdjust + PullRefreshIndicatorStrengthStart + + IndicatorMaxRadius; + end; + // *** ZuBy + + MinRadius := IndicatorMinRadius; + MaxRadius := IndicatorMaxRadius; + Thickness := IndicatorThickness; + + if FPullRefreshAnimation = TPullRefreshAnimation.Playing then + TimeElapsed := Abs(FTimerService.GetTick - FPullRefreshAnimationStartTime) + else + TimeElapsed := 0; + + if LOpacity <= IndicatorDisappearFraction then + begin + ShrinkAlpha := (1 - IndicatorDisappearFraction) + LOpacity; + MaxRadius := MaxRadius * ShrinkAlpha; + MinRadius := MinRadius * ShrinkAlpha; + Thickness := Thickness * ShrinkAlpha; + end; + + Stroke := TStrokeBrush.Create(TBrushKind.Solid, FStyleResources.PullRefreshIndicatorColor); + try + Stroke.Thickness := Thickness; + + for I := 0 to LineCount - 1 do + begin + Angle := ((I * PiMulTwo) / PullRefreshIndicatorMaxSteps) + TimeElapsed * IndicatorRotation - PiByTwo; + + if not SameValue(FPullRefreshAnimationStartTime, FPullRefreshAnimationStopTime, TEpsilon.Vector) then + Angle := Angle + Abs(FTimerService.GetTick - FPullRefreshAnimationStopTime) * IndicatorRotation * 2; + + SinCos(Angle, LSin, LCos); + P1.X := Center.X + LCos * MinRadius; + P1.Y := Center.Y + LSin * MinRadius; + P2.X := Center.X + LCos * MaxRadius; + P2.Y := Center.Y + LSin * MaxRadius; + // ZuBy *** + if FHorizontal then + ACanvas.DrawLine(P2, P1, AOpacity * LOpacity, Stroke) + else + ACanvas.DrawLine(P1, P2, AOpacity * LOpacity, Stroke); + // *** ZuBy + end; + finally + Stroke.Free; + end; +end; + +function TListViewBase.GetPullRefreshStrokeWidth: Single; +const + StrokeCollapseSpeed1 = 4; + StrokeCollapseSpeed2 = 256; + StrokeCollapsePower = 0.75; + StrokeGrowthSpeed = 0.25; +begin + if FPullRefreshAnimation = TPullRefreshAnimation.Playing then + begin + // ZuBy *** + if FHorizontal then + Result := Min + (Power(Abs(FTimerService.GetTick - FPullRefreshAnimationStartTime) * + StrokeCollapseSpeed1, StrokeCollapsePower) * + StrokeCollapseSpeed2, Height) + else + Result := Min + (Power(Abs(FTimerService.GetTick - FPullRefreshAnimationStartTime) * + StrokeCollapseSpeed1, StrokeCollapsePower) * + StrokeCollapseSpeed2, Width); + // *** ZuBy + end + else + begin + // ZuBy *** + if FHorizontal then + Result := Min + (Sqr(Max(GetPullRefreshStrength - PullRefreshIndicatorStrengthStart, + 0) * StrokeGrowthSpeed), Height) + else + Result := Min + (Sqr(Max(GetPullRefreshStrength - PullRefreshIndicatorStrengthStart, + 0) * StrokeGrowthSpeed), Width); + // *** ZuBy + end; +end; + +procedure TListViewBase.PaintPullRefreshStroke(const ACanvas: TCanvas; const AStrength, AOpacity: Single); +const + DefaultStrokeThickness = 2.5; +var + StrokeBrush: TBrush; + StrokeLength, TopAdjust: Single; +begin + if (FSearchEdit <> nil) and FSearchEdit.Visible and not HasSearchAsItem then + begin + // ZuBy *** + if FHorizontal then + TopAdjust := FSearchEdit.Width + else + TopAdjust := FSearchEdit.Height; + // *** ZuBy + end + else + TopAdjust := 0; + + StrokeBrush := TBrush.Create(TBrushKind.Gradient, FStyleResources.PullRefreshStrokeColor); + try + StrokeBrush.Kind := TBrushKind.Solid; + StrokeLength := GetPullRefreshStrokeWidth; + + if FPullRefreshAnimation = TPullRefreshAnimation.Playing then + begin + // ZuBy *** + if FHorizontal then + begin + ACanvas.FillRect(TRectF.Create(TopAdjust, 0, + TopAdjust + DefaultStrokeThickness, (Height - StrokeLength) / 2), 0, + 0, AllCorners, AOpacity, StrokeBrush); + ACanvas.FillRect(TRectF.Create(TopAdjust, (Height + StrokeLength) / 2, + TopAdjust + DefaultStrokeThickness, Height), 0, 0, AllCorners, + AOpacity, StrokeBrush); + end + else + begin + ACanvas.FillRect(TRectF.Create(0, TopAdjust, (Width - StrokeLength) / 2, + TopAdjust + DefaultStrokeThickness), 0, 0, AllCorners, AOpacity, + StrokeBrush); + ACanvas.FillRect(TRectF.Create((Width + StrokeLength) / 2, TopAdjust, + Width, TopAdjust + DefaultStrokeThickness), 0, 0, AllCorners, + AOpacity, StrokeBrush); + end; + // *** ZuBy + end; + + if FPullRefreshAnimation = TPullRefreshAnimation.NotPlaying then + begin + // ZuBy *** + if FHorizontal then + ACanvas.FillRect(TRectF.Create(TopAdjust, (Height - StrokeLength) / 2, + TopAdjust + DefaultStrokeThickness, (Height + StrokeLength) / 2), 0, + 0, AllCorners, AOpacity, StrokeBrush) + else + ACanvas.FillRect(TRectF.Create((Width - StrokeLength) / 2, TopAdjust, + (Width + StrokeLength) / 2, TopAdjust + DefaultStrokeThickness), 0, 0, + AllCorners, AOpacity, StrokeBrush); + // *** ZuBy + end; + finally + StrokeBrush.Free; + end; +end; + +procedure TListViewBase.PaintScrollingStretchGlow(const ACanvas: TCanvas; const AIntensity, AOpacity: Single); +var + TempPoint: TGradientPoint; + TempColor: TAlphaColor; + GlowBrush: TBrush; + GlowDepth: Single; + GlowRect: TRectF; +begin + GlowBrush := TBrush.Create(TBrushKind.Gradient, FStyleResources.ScrollingStretchGlowColor); + try + GlowBrush.Gradient.Style := TGradientStyle.Radial; + GlowBrush.Gradient.Points.Clear; + TempColor := FStyleResources.ScrollingStretchGlowColor; + + TempPoint := TGradientPoint.Create(GlowBrush.Gradient.Points); + TAlphaColorRec(TempColor).A := 0; + TempPoint.Color := TempColor; + TempPoint.Offset := 0; + + TempPoint := TGradientPoint.Create(GlowBrush.Gradient.Points); + TAlphaColorRec(TempColor).A := 255; + TempPoint.Color := TempColor; + TempPoint.Offset := 1; + + GlowDepth := Max((Sqrt(Abs(AIntensity)) - 3) * 3, 0); + if GlowDepth > TEpsilon.Position then + begin + if AIntensity < 0 then + begin + // ZuBy *** + if FHorizontal then + GlowRect := TRectF.Create(-GlowDepth, -Height / 8, GlowDepth, + Height + Height / 8) + else + GlowRect := TRectF.Create(-Width / 8, -GlowDepth, Width + Width / 8, + GlowDepth); + // *** ZuBy + if (FSearchEdit <> nil) and FSearchEdit.Visible and not HasSearchAsItem + then + GlowRect.Offset(0, FSearchEdit.Height); + end + else + begin + // ZuBy *** + if FHorizontal then + GlowRect := TRectF.Create(Width - GlowDepth, -Height / 8, + Width + GlowDepth, Height + Height / 8) + else + GlowRect := TRectF.Create(-Width / 8, Height - GlowDepth, + Width + Width / 8, Height + GlowDepth); + // *** ZuBy + end; + + ACanvas.FillEllipse(GlowRect, AOpacity, GlowBrush); + end; + finally + GlowBrush.Free; + end; +end; + +procedure TListViewBase.UpdatePullRefreshState; +var + Trigger: Boolean; +begin + if FPullRefreshTriggered and (GetPullRefreshStrength < 1) and + (FPullRefreshAnimation <> TPullRefreshAnimation.Playing) then + begin + FPullRefreshTriggered := False; + FPullRefreshAnimation := TPullRefreshAnimation.NotPlaying; + FPullRefreshAnimationStopTime := FPullRefreshAnimationStartTime; + end + else if not FPullRefreshTriggered then + begin + if TListingTransitionFeature.PullToRefresh in FListingService.GetTransitionFeatures then + Trigger := GetPullRefreshIndicatorSteps >= PullRefreshIndicatorMaxSteps + else + begin + // ZuBy *** + if FHorizontal then + Trigger := GetPullRefreshStrokeWidth >= Height + else + Trigger := GetPullRefreshStrokeWidth >= Width; + // *** ZuBy + end; + + if Trigger then + begin + FPullRefreshTriggered := True; + StartPullRefreshAnimation; + + TThread.Queue(nil, + procedure + begin + if Assigned(FOnPullRefresh) then + FOnPullRefresh(Self); + end); + end; + end; + if FPullRefreshTriggered and (FPullRefreshAnimation = TPullRefreshAnimation.Playing) and + SameValue(FPullRefreshAnimationStartTime, FPullRefreshAnimationStopTime, TEpsilon.Vector) and (FAniCalc <> nil) and + (not FAniCalc.Down) then + FPullRefreshAnimationStopTime := FTimerService.GetTick; +end; + +procedure TListViewBase.UpdateScrollStretchStrength(const NewValue: Single); +begin + if not SameValue(FScrollStretchStrength, NewValue, TEpsilon.Position) then + begin + FScrollStretchStrength := NewValue; + ScrollStretchChanged; + end; +end; + +procedure TListViewBase.ScrollStretchChanged; +begin + if FPullToRefresh then + UpdatePullRefreshState; + + if FPullToRefresh or HasScrollingStretchGlow then + Invalidate; +end; + +procedure TListViewBase.UpdateDeleteButtonLayout; +var + RelRect: TRectF; + LTargetItemIndex: Integer; +begin + if (Adapter.Count < 1) or (FDeleteLayout = nil) or ((FDeleteButtonIndex = -1) and + (FPrevDeleteButtonIndex = -1)) then + exit; + + if (FListingService <> nil) and (TListingTransitionFeature.DeleteButtonSlide in FListingService.GetTransitionFeatures) then + begin + FDeleteLayout.Width := DefaultDeleteButtonWidth * FDeleteModeTransitionAlpha; + FDeleteButton.Opacity := 1; + end + else + begin + if FDeleteModeTransitionAlpha > 0 then + FDeleteLayout.Width := DefaultDeleteButtonWidth + else + FDeleteLayout.Width := 0; + + FDeleteButton.Opacity := 0.5 + (FDeleteModeTransitionAlpha / 2); + end; + + FDeleteLayout.Height := GetItemHeight(FDeleteButtonIndex); + + // ZuBy *** + if FHorizontal then + FDeleteLayout.Position.X := RelRect.Right - FDeleteLayout.Width + else + FDeleteLayout.Position.X := Width - FDeleteLayout.Width; + // *** ZuBy + + if FDeleteButtonIndex = -1 then + LTargetItemIndex := Min(FPrevDeleteButtonIndex, ItemCount - 1) + else + LTargetItemIndex := Min(FDeleteButtonIndex, ItemCount - 1); + + if LTargetItemIndex = -1 then + RelRect := TRectF.Create(LocalRect.TopLeft + TPointF.Create(FSideSpace, FSideSpace - FScrollViewPos), 0, 0) + else + RelRect := GetItemRelRect(LTargetItemIndex, LocalRect); + + FDeleteLayout.Position.Y := + (RelRect.Top + RelRect.Bottom - FDeleteLayout.Height) / 2; +end; + +procedure TListViewBase.DeleteButtonClicked(Sender: TObject); +begin + if DeleteItem(FDeleteButtonIndex) then + begin + FSelectionAlphas.Remove(FDeleteButtonIndex); + SetDeleteButtonIndex(-1); + ResetDeleteModeAnimation; + end; +end; + +procedure TListViewBase.ProceedDeleteItem; +var + Editor: IListViewEditor; +begin + if (FDeleteButtonIndex = -1) or not Supports(Adapter, IListViewEditor, Editor) then + exit; + + if (FItemIndex <> -1) and (FItemIndex >= FDeleteButtonIndex) then + SetItemIndex(-1); + + Editor.Delete(FDeleteButtonIndex); + + if Assigned(FOnDeleteItem) then + FOnDeleteItem(Self, FDeleteButtonIndex); + + SetDeleteButtonIndex(-1); + + ResetDeleteModeAnimation; +end; + +procedure TListViewBase.SetDeleteButtonIndex(const NewItemIndex: Integer); +begin + if FDeleteButtonIndex <> NewItemIndex then + begin + if FTransitionType = TTransitionType.DeleteMode then + ResetDeleteModeAnimation; + + FPrevDeleteButtonIndex := FDeleteButtonIndex; + FDeleteButtonIndex := NewItemIndex; + + InitDeleteModeAnimation; + + if Assigned(FOnDeleteChange) then + FOnDeleteChange(Self, (FDeleteButtonIndex = -1) and (not FEditMode)); + + if FSearchEdit <> nil then + FSearchEdit.Enabled := (FDeleteButtonIndex = -1) and (not FEditMode); + end; +end; + +function TListViewBase.CanObserve(const ID: Integer): Boolean; +begin + Result := False; + if ID = TObserverMapping.EditLinkID then + Result := True + else if ID = TObserverMapping.PositionLinkID then + Result := True + else if ID = TObserverMapping.ControlValueID then + Result := True; +end; + +procedure TListViewBase.ObserversBeforeSelection(out LAllowSelection: Boolean); +begin + LAllowSelection := True; + if Observers.IsObserving(TObserverMapping.EditLinkID) and not TLinkObservers.EditLinkEdit(Observers) then + LAllowSelection := False; + if LAllowSelection then + TLinkObservers.PositionLinkPosChanging(Observers); +end; + +procedure TListViewBase.DoListItemChange(const AListItem: TListItem); +begin + if Assigned(FOnItemChange) then + FOnItemChange(Self, AListItem); +end; + +procedure TListViewBase.DoListItemClick(const AListItem: TListItem); +begin + if Assigned(FOnListItemClick) then + FOnListItemClick(Self, AListItem); +end; + +procedure TListViewBase.DoUpdateItemView(const AListItem: TListItem); +begin + if Assigned(FOnUpdateItemView) then + FOnUpdateItemView(Self, AListItem); +end; + +procedure TListViewBase.DoUpdatingItemView(const AListItem: TListItem; var AHandled: Boolean); +begin + if Assigned(FOnUpdatingItemView) then + FOnUpdatingItemView(Self, AListItem, AHandled); +end; + +procedure TListViewBase.DoUpdateScrollViewPos(const Value: Single); +begin +end; + +procedure TListViewBase.ReadCanSwipeDelete(Reader: TReader); +begin + CanSwipeDelete := Reader.ReadBoolean; +end; + +procedure TListViewBase.ReadIsSearchVisible(Reader: TReader); +begin + SearchVisible := Reader.ReadBoolean; +end; + +procedure TListViewBase.ReadIsSearchAlwaysOnTop(Reader: TReader); +begin + SearchAlwaysOnTop := Reader.ReadBoolean; +end; + +procedure TListViewBase.ReadEditModeOptions(Reader: TReader); +begin + TOpenReader(Reader).ReadSet(TypeInfo(TEditModeOptions)); +end; + +procedure TListViewBase.DefineProperties(Filer: TFiler); +begin + inherited; + + Filer.DefineProperty('CanSwypeDelete', ReadCanSwipeDelete, nil, False); + Filer.DefineProperty('DeleteButtonEnabled', ReadCanSwipeDelete, nil, False); + Filer.DefineProperty('EnabledDeleteButton', ReadCanSwipeDelete, nil, False); + Filer.DefineProperty('IsSearchVisible', ReadIsSearchVisible, nil, False); + Filer.DefineProperty('IsSearchAlwaysOnTop', ReadIsSearchAlwaysOnTop, nil, False); + Filer.DefineProperty('EditModeOptions', ReadEditModeOptions, nil, False); +end; + +procedure TListViewBase.DoRealign; +begin + inherited; + Repaint; +end; + +procedure TListViewBase.DoExit; +begin + inherited; + if Observers.IsObserving(TObserverMapping.EditLinkID) then + if TLinkObservers.EditLinkIsEditing(Observers) then + TLinkObservers.EditLinkUpdate(Observers); + if Observers.IsObserving(TObserverMapping.ControlValueID) then + TLinkObservers.ControlValueUpdate(Observers); +end; + +procedure TListViewBase.UpdateScrollViewPos(const Value: Single); +begin + if (not SameValue(FScrollViewPos, Value, TEpsilon.Vector) and FCanScroll) then + // sinuke + begin + FScrollViewPos := Value; + DoUpdateScrollViewPos(Value); + if Assigned(FOnScrollViewChange) then + FOnScrollViewChange(Self); + // ZuBy *** + if FScrollViewPos >= GetMaxScrollViewPos then + begin + if Assigned(FOnScrollEnd) then + FOnScrollEnd(Self); + end; + // *** ZuBy + end; + RecalcTopViewItemIndex; +end; + +procedure TListViewBase.UpdateSearchEditPos; +var + NewPos: Single; +begin + if FSearchEdit <> nil then + begin + if HasSearchAsItem then + NewPos := Max(0, FSearchEdit.Height - FScrollViewPos) - FSearchEdit.Height + else + NewPos := 0; + + if not SameValue(FSearchEdit.Position.Y, NewPos, TEpsilon.Position) then + begin + FSearchEdit.Position.Y := NewPos; + Invalidate; + end; + end; +end; + +function TListViewBase.GetMaxScrollViewPos: Integer; +begin + // ZuBy *** + if FHorizontal then + Result := Max(Trunc(FSideSpace * 2 + FMaxKnownHeight - LocalRect.Width), 0) + else + Result := Max(Trunc(FSideSpace * 2 + FMaxKnownHeight - + LocalRect.Height), 0); + // *** ZuBy +end; + +function TListViewBase.GetMessageSender: TMessageSender; +begin + if FMessageSender = nil then + begin + FMessageSender := TMessageSender.Create; + FMessageSender.Receiver := Self; + end; + Result := FMessageSender; +end; + +procedure TListViewBase.SetScrollViewPos(const Value: Single); +var + NewValue, MaxValue: Single; +begin + NewValue := Value; + + if NewValue < 0 then + NewValue := 0; + + MaxValue := GetMaxScrollViewPos; + if NewValue > MaxValue then + NewValue := MaxValue; + + UpdateScrollViewPos(NewValue); + DoSetScrollViewPos(NewValue); +end; + +procedure TListViewBase.SetSelected(const Value: TListItem); +begin + if Value = nil then + ItemIndex := -1 + else + ItemIndex := Adapter.IndexOf(Value); +end; + +procedure TListViewBase.SetSelectionCrossfade(const Value: Boolean); +begin + if FSelectionCrossfade <> Value then + begin + FSelectionCrossfade := Value; + Invalidate; + end; +end; + +procedure TListViewBase.SetPullToRefresh(const Value: Boolean); +begin + if FPullToRefresh <> Value then + begin + FPullToRefresh := Value; + if FAniCalc <> nil then + FAniCalc.BoundsAnimation := HasPhysicsStretchyScrolling; + RecreateNativePresentation; + end; +end; + +procedure TListViewBase.SetShowSelection(const Value: Boolean); +begin + if FShowSelection <> Value then + begin + FShowSelection := Value; + Invalidate; + end; +end; + +procedure TListViewBase.ScrollBarChange(Sender: TObject); +begin + UpdateScrollViewPos(FScrollBar.Value); + UpdateSearchEditPos; + UpdateDeleteButtonLayout; +end; + +procedure TListViewBase.AniCalcChange(Sender: TObject); +var + NewViewPos, MaxScrollViewPos: Single; +begin + // ZuBy *** + if FHorizontal then + NewViewPos := FAniCalc.ViewportPosition.X + else + NewViewPos := FAniCalc.ViewportPosition.Y; + // *** ZuBy + MaxScrollViewPos := GetMaxScrollViewPos; + + if NewViewPos < 0 then + UpdateScrollStretchStrength(NewViewPos) + else if NewViewPos > MaxScrollViewPos then + UpdateScrollStretchStrength(NewViewPos - MaxScrollViewPos) + else + UpdateScrollStretchStrength(0); + + if not HasStretchyScrolling then + NewViewPos := EnsureRange(NewViewPos, 0, MaxScrollViewPos); + + if (not SameValue(NewViewPos, FScrollViewPos, TEpsilon.Vector)) and + (TStateFlag.NeedsScrollBarDisplay in FStateFlags) then + begin + FScrollBar.StopPropertyAnimation('Opacity'); + FScrollBar.Opacity := 1; + + Exclude(FStateFlags, TStateFlag.NeedsScrollBarDisplay); + end; + + if TStateFlag.ScrollingActive in FStateFlags then + begin + UpdateScrollViewPos(NewViewPos); + UpdateSearchEditPos; + UpdateDeleteButtonLayout; + UpdateScrollBar; + end; +end; + +procedure TListViewBase.AniCalcStart(Sender: TObject); +begin + if IsRunningOnDesktop then + DisableHitTestForControl(FScrollBar); + + if Scene <> nil then + Scene.ChangeScrollingState(Self, True); + + // ZuBy *** + if FShowScrollBar then + FStateFlags := FStateFlags + [TStateFlag.NeedsScrollBarDisplay, + TStateFlag.ScrollingActive] + else + FStateFlags := FStateFlags + [TStateFlag.ScrollingActive]; + // *** ZuBy +end; + +procedure TListViewBase.AniCalcStop(Sender: TObject); +var + ScrollPixelAlign: Boolean; +begin + ScrollPixelAlign := TStateFlag.ScrollingActive in FStateFlags; + Exclude(FStateFlags, TStateFlag.ScrollingActive); + TAnimator.AnimateFloat(FScrollBar, 'Opacity', 0, 0.2); + + if Scene <> nil then + Scene.ChangeScrollingState(nil, False); + + if ScrollPixelAlign and (FScrollScale > TEpsilon.Scale) then + begin + // ZuBy *** + if FAutoPositionToItem then + ScrollTo(TAppearanceListView(Self).getFirstVisibleItemIndex) + else + SetScrollViewPos(Round(FScrollViewPos * FScrollScale) / FScrollScale); + // *** ZuBy + end; +end; + +procedure TListViewBase.UpdateScrollBar; +var + LocalHeight, ViewSize: Single; +begin + // ZuBy *** + if FHorizontal then + LocalHeight := LocalRect.Width + else + LocalHeight := LocalRect.Height; + // *** ZuBy + + if FScrollViewPos < 0 then + ViewSize := LocalHeight + FScrollViewPos + else if FScrollViewPos > FMaxKnownHeight - LocalHeight then + ViewSize := LocalHeight - (FScrollViewPos - (FMaxKnownHeight - LocalHeight)) + else + ViewSize := LocalHeight; + + FScrollBar.BeginUpdate; + try + FScrollBar.Max := FSideSpace * 2 + FMaxKnownHeight; + FScrollBar.SmallChange := Adapter.GetDefaultViewHeight * 0.5; + if not (csDesigning in ComponentState) then // Don't show at design time + FScrollBar.Visible := ((FMaxKnownHeight > LocalHeight) or + (HasTouchTracking and (TStateFlag.ScrollingActive in FStateFlags))) and + FShowScrollBar // ZuBy + else + FScrollBar.Visible := False; + FScrollBar.Value := FScrollViewPos; + FScrollBar.ViewportSize := ViewSize; + finally + FScrollBar.EndUpdate; + end; + RecalcTopViewItemIndex; // ZuBy +end; + +procedure TListViewBase.UpdateScrollingLimits; +begin + if not IsUpdating then + DoUpdateScrollingLimits + else + Include(FStateFlags, TStateFlag.NeedsScrollingLimitsUpdate); +end; + +procedure TListViewBase.DoUpdateScrollingLimits; +var + Targets: array of TAniCalculations.TTarget; +begin + if FAniCalc <> nil then + begin + SetLength(Targets, 2); + + Targets[0].TargetType := TAniCalculations.TTargetType.Min; + Targets[0].Point := TPointD.Create(0, 0); + Targets[1].TargetType := TAniCalculations.TTargetType.Max; + // ZuBy *** + if FHorizontal then + Targets[1].Point := TPointD.Create + (Max(FSideSpace * 2 + FMaxKnownHeight - LocalRect.Width, 0), 0) + else + Targets[1].Point := TPointD.Create(0, + Max(FSideSpace * 2 + FMaxKnownHeight - LocalRect.Height, 0)); + // *** ZuBy + + FAniCalc.SetTargets(Targets); + end; + + if not HasTouchTracking then + UpdateScrollBar; +end; + +procedure TListViewBase.GetNumberOfRenderingPasses(const StartItem, EndItem: Integer; var Passes, Subpasses: Integer); +var + I, J: Integer; + ListItem: TListItem; +begin + Passes := 0; + Subpasses := 1; + for J := StartItem to EndItem do + begin + ListItem := Adapter[J]; + ListItem.CreateObjects; + Passes := Max(Passes, ListItem.Count); + for I := 0 to ListItem.Count - 1 do + Subpasses := Max(Subpasses, ListItem.View[I].GetRenderPassCount); + end; +end; + +function TListViewBase.GetFinalItemSpaces(const ForceIncludeScrollBar: Boolean): TRectF; +begin + Result := FItemSpaces.Rect; + + // ZuBy *** + if FHorizontal then + begin + if (FScrollBar <> nil) and (not HasTouchTracking) and + (ForceIncludeScrollBar or FScrollBar.Visible) then + Result.Bottom := Result.Bottom - FScrollBar.Height; + + // fix SearchVisible + if (FSearchEdit <> nil) and FSearchEdit.Visible and not HasSearchAsItem then + Result.Top := Result.Top + FSearchEdit.Height; + end + else + begin + if (FScrollBar <> nil) and (not HasTouchTracking) and + (ForceIncludeScrollBar or FScrollBar.Visible) then + Result.Right := Result.Right + FScrollBar.Width; + end; + // *** ZuBy +end; + +function TListViewBase.GetFinalItemSize(const ForceIncludeScrollBar: Boolean): TSizeF; +var + FinalItemSpaces: TRectF; +begin + FinalItemSpaces := GetFinalItemSpaces(ForceIncludeScrollBar); + Result := TSizeF.Create(Width - FinalItemSpaces.Left - FinalItemSpaces.Right, + Height - FinalItemSpaces.Top - FinalItemSpaces.Bottom); +end; + +function TListViewBase.GetItemRelRect(const Index: Integer; const LocRect: TRectF; + const SideSpace: Integer = 0): TRectF; +begin + // ZuBy *** + if FHorizontal then + begin + Result := RectF(LocRect.Left + FSideSpace + FHeightSums[Index] - + FScrollViewPos, LocRect.Top + FSideSpace + SideSpace, + GetItemHeight(Index) - FSideSpace, + LocRect.Height - ((SideSpace + FSideSpace) * 2)); + + if (FScrollBar <> nil) and (not HasTouchTracking) and FScrollBar.Visible + then + Result.Bottom := Result.Bottom - FScrollBar.Height; + + // fix SearchVisible + if (FSearchEdit <> nil) and FSearchEdit.Visible and not HasSearchAsItem then + Result.Top := Result.Top + FSearchEdit.Height; + end + else + begin + Result := RectF(LocRect.Left + FSideSpace + SideSpace, + FItemBottomOffset + LocRect.Top + FSideSpace + FHeightSums[Index] - + FScrollViewPos, LocRect.Width - ((SideSpace + FSideSpace) * 2), + GetItemHeight(Index) - FItemBottomOffset); + + if (FScrollBar <> nil) and (not HasTouchTracking) and FScrollBar.Visible + then + Result.Right := Result.Right - FScrollBar.Width; + end; + // *** ZuBy +end; + +function TListViewBase.GetScene: IScene; +begin + Result := Scene; +end; + +function TListViewBase.GetSelected: TListItem; +begin + if (FItemIndex >= 0) and (FItemIndex < Adapter.Count) then + Result := Adapter[FItemIndex] + else + Result := nil; +end; + +function TListViewBase.GetStyleResources: TListItemStyleResources; +begin + ApplyStyleLookup; + Result := FStyleResources; +end; + +function TListViewBase.StyleResourcesNeedUpdate: Boolean; +begin + Result := FUpdatingStyleResources; +end; + +function TListViewBase.GetItemGroupSeparators(const Index: Integer): Integer; +var + EndIndex: Integer; + Prev, Next: TListItem; +begin + Result := 0; + + EndIndex := Adapter.Count - 1; + if (Index < 0) or (Index > EndIndex) then + exit; + if (Index = 0) and (not FShowFirstSeparator) then + exit; + if (Index = EndIndex) and (not FShowLastSeparator) then + exit; + + Prev := nil; + Next := nil; + if Index > 0 then + Prev := Adapter[Index - 1]; + if Index < EndIndex then + Next := Adapter[Index + 1]; + + if (Prev <> nil) and (Next <> nil) and (Prev.Count > 0) and (Next.Count > 0) then + exit; + + if (Index = 0) or ((Prev.Count < 1) and (Prev.Purpose = TListItemPurpose.None)) then + Result := Result or ItemSeparatorTop; + + if (Index >= EndIndex) or ((Next.Count < 1) and (Next.Purpose = TListItemPurpose.None)) then + Result := Result or ItemSeparatorBottom; +end; + +function TListViewBase.GetItemHeight(const Index: Integer): Integer; +begin + if (Index < 0) or (Index >= Adapter.Count) then + Result := 0 + else + begin + Result := Adapter[Index].Height; + if Result < 1 then + Result := Adapter.GetDefaultViewHeight; + end; +end; + +function TListViewBase.GetSeparatorLineHeight: Single; +begin + if FScrollScale > TEpsilon.Scale then + Result := 1 / FScrollScale + else + Result := 1; + + if FScrollScale >= 2 then + Result := Result * 2; +end; + +function TListViewBase.AlignValueToPixel(const Value: Single): Single; +begin + if FScrollScale > TEpsilon.Scale then + Result := Int(Value * FScrollScale) / FScrollScale + else + Result := Value; +end; + +procedure TListViewBase.DrawItemsFill(const StartItem, EndItem: Integer; const LocRect: TRectF; const Opacity: Single; + const HeaderIndex: Integer); +var + I, Sep, AltIndex: Integer; + DrawRect, DrawSubRect, SepRect: TRectF; + ListItem: TListItem; + SepHeight: Single; + HeaderBefore: Boolean; + DrawPanel: TRectF; + J, RowColumns: Integer; +begin + SepHeight := GetSeparatorLineHeight; + + for I := StartItem to EndItem do + if I <> HeaderIndex then + begin + ListItem := Adapter[I]; + HeaderBefore := (I > 0) and (Adapter[I - 1].Purpose <> TListItemPurpose.None); + if (ListItem <> nil) and ((ListItem.Count > 0) or HeaderBefore) then + begin + DrawRect := GetItemRelRect(I, LocRect); + + if ListItem.Purpose = TListItemPurpose.None then + begin + // ZuBy *** + if FItemBoxLight <> nil then + begin + if FAutoColumns then + begin + RowColumns := Min(TListViewItem(ListItem).Tag, FColumns); + for J := 1 to RowColumns { FColumns } do + begin + DrawPanel := RectF(((J * FColumnWidth) - FColumnWidth) + + (FMarg * J), DrawRect.Top, FColumnWidth, DrawRect.Height); + FItemBoxLight.DrawToCanvas(Canvas, DrawPanel, Opacity); + end; + end + else + FItemBoxLight.DrawToCanvas(Canvas, DrawRect, Opacity); + end + else + begin + // ZuBy *** + if not FTransparentItems then + begin + FBrush.Color := FItemStyleFillColor; + + if ListItem.HeaderRef <> -1 then + AltIndex := Max((I - ListItem.HeaderRef) - 1, 0) + else + AltIndex := I; + + if FAlternatingColors and (AltIndex mod 2 = 1) then + FBrush.Color := FItemStyleFillAltColor; + + // ZuBy *** + if TListViewItem(ListItem).Data['aUseCustomColor'].AsBoolean then + FBrush.Color := TListViewItem(ListItem).Data['aCustomColor'] + .AsInteger; + // *** ZuBy + end + else + FBrush.Kind := TBrushKind.None; + Canvas.FillRect(DrawRect, 0, 0, AllCorners, Opacity, FBrush); + FBrush.Kind := FBrush.DefaultKind; + // *** ZuBy + end; + // *** ZuBy + end; + + Sep := GetItemGroupSeparators(I); + // ZuBy *** + if not FTransparentSeparator then + FBrush.Color := FItemStyleFrameColor + else + FBrush.Kind := TBrushKind.None; + // *** ZuBy + + if (Sep and ItemSeparatorTop > 0) and (ListItem.Purpose = TListItemPurpose.None) then + begin + // ZuBy *** + if FHorizontal then + begin + SepRect.Left := AlignValueToPixel(DrawRect.Left); + SepRect.Right := SepRect.Right + 1; + SepRect.Top := DrawRect.Top + FSeparatorLeftOffset; + SepRect.Bottom := DrawRect.Bottom - FSeparatorRightOffset; + end + else + begin + SepRect.Left := DrawRect.Left + FSeparatorLeftOffset; + SepRect.Right := DrawRect.Right - FSeparatorRightOffset; + SepRect.Top := AlignValueToPixel(DrawRect.Top); + SepRect.Bottom := SepRect.Top + SepHeight; + end; + // *** ZuBy + + Canvas.FillRect(SepRect, 0, 0, AllCorners, Opacity, FBrush); + FBrush.Kind := FBrush.DefaultKind; + end; + end; + + if (not((I >= Adapter.Count - 1) and (not FShowLastSeparator))) and + (ListItem.Purpose = TListItemPurpose.None) and + ((I >= Adapter.Count - 1) or + (Adapter[I + 1].Purpose = TListItemPurpose.None)) then + begin + // ZuBy *** + if not FTransparentSeparator then + FBrush.Color := FItemStyleFrameColor + else + FBrush.Kind := TBrushKind.None; + // *** ZuBy + + // ZuBy *** + if FHorizontal then + begin + SepRect.Left := AlignValueToPixel(DrawRect.Left); + SepRect.Right := SepRect.Left + 1; + SepRect.Top := (DrawRect.Top - 1) + FSeparatorLeftOffset; + SepRect.Bottom := (DrawRect.Bottom + 1) - FSeparatorRightOffset; + end + else + begin + SepRect.Left := (DrawRect.Left - 1) + FSeparatorLeftOffset; + SepRect.Right := (DrawRect.Right + 1) - FSeparatorRightOffset; + SepRect.Top := AlignValueToPixel(DrawRect.Bottom - SepHeight); + SepRect.Bottom := SepRect.Top + SepHeight; + end; + // *** ZuBy + + Canvas.FillRect(SepRect, 0, 0, AllCorners, Opacity, FBrush); + FBrush.Kind := FBrush.DefaultKind; + end; + end; + + for I := StartItem to EndItem do + if I <> HeaderIndex then + begin + ListItem := Adapter[I]; + HeaderBefore := (I > 0) and + (Adapter[I - 1].Purpose <> TListItemPurpose.None); + + if (ListItem <> nil) and ((ListItem.Count > 0) or HeaderBefore) then + begin + DrawRect := GetItemRelRect(I, LocRect); + + if ListItem.Purpose <> TListItemPurpose.None then + begin + DrawSubRect := DrawRect; + + if I = 0 then + DrawSubRect.Top := DrawSubRect.Top + 1; + + // ZuBy *** + if not FTransparentHeaders then + begin + if FHeaderStyleImage <> nil then + FHeaderStyleImage.DrawToCanvas(Canvas, DrawSubRect, Opacity) + else + begin + FBrush.Color := FHeaderStyleColor; + Canvas.FillRect(DrawSubRect, 0, 0, AllCorners, Opacity, FBrush); + end; + end + else + FBrush.Kind := TBrushKind.None; + // *** ZuBy + end; + end; + end; +end; + +procedure TListViewBase.DrawIndexFill(const AIndex: Integer; const LocRect: TRectF; const Opacity: Single); +var + DrawRect, DrawPanel: TRectF; + SepHeight, pW: Single; + J, RowColumns: Integer; +begin + DrawRect := GetItemRelRect(AIndex, LocRect); + SepHeight := GetSeparatorLineHeight; + + if (AIndex >= Adapter.Count - 1) or + (Adapter[AIndex + 1].Purpose = TListItemPurpose.None) then + DrawRect.Bottom := DrawRect.Bottom - SepHeight; + +{$IFDEF MSWINDOWS} + // The selection seems to be broken on Windows (looks ugly, needs fixing). Meanwhile, attempt a temporal fix. + DrawRect.Inflate(-2, -2); +{$ENDIF} + if FSelectionStyleImage <> nil then + begin + DrawRect.Top := AlignValueToPixel(DrawRect.Top - SepHeight) + SepHeight; + DrawRect.Bottom := AlignValueToPixel(DrawRect.Bottom) + 2; + // ZuBy *** + if FItemBoxLight <> nil then + DrawRect.Top := DrawRect.Top + 1; + DrawRect.Bottom := DrawRect.Bottom - 2; + if FAutoColumns then + begin + RowColumns := Min(TListViewItem(Adapter[AIndex]).Tag, FColumns); + for J := 1 to RowColumns do + begin + pW := (J * FColumnWidth) - FColumnWidth; + if InRange(FClickEventMousePos.X, (pW + (J * FMarg)), + (pW + FColumnWidth) + (J * FMarg)) then + break; + end; + + if (J > RowColumns) then + exit; + + DrawPanel := RectF(((J * FColumnWidth) - FColumnWidth) + (FMarg * J), + DrawRect.Top, FColumnWidth, DrawRect.Height); + FSelectionStyleImage.DrawToCanvas(Canvas, DrawPanel, Opacity); + end + else + FSelectionStyleImage.DrawToCanvas(Canvas, DrawRect, Opacity); + // *** ZuBy + end + else + begin + FBrush.Color := FSelectionStyleColor; + // sinuke *** + DrawRect.Top := AlignValueToPixel(DrawRect.Top - SepHeight) + SepHeight; + DrawRect.Bottom := AlignValueToPixel(DrawRect.Bottom) + 2; + + if FItemBoxLight <> nil then + DrawRect.Top := DrawRect.Top + 1; + DrawRect.Bottom := DrawRect.Bottom - 2; + + if FAutoColumns then + begin + RowColumns := Min(TListViewItem(Adapter[AIndex]).Tag, FColumns); + for J := 1 to RowColumns do + begin + pW := (J * FColumnWidth) - FColumnWidth; + if InRange(FClickEventMousePos.X, (pW + (J * FMarg)), + (pW + FColumnWidth) + (J * FMarg)) then + break; + end; + + if (J > RowColumns) then + exit; + + DrawPanel := RectF(((J * FColumnWidth) - FColumnWidth) + (FMarg * J), + DrawRect.Top, FColumnWidth, DrawRect.Height); + Canvas.FillRect(DrawPanel, 0, 0, AllCorners, Opacity, FBrush); + end + else + Canvas.FillRect(DrawRect, 0, 0, AllCorners, Opacity, FBrush); + // *** sinuke + end; +end; + +procedure TListViewBase.DrawTouchAnimation(const Index: Integer; const LocRect: TRectF; const Opacity: Single); +var + R: TRectF; +begin + if (FTouchAnimationObject <> nil) and (ItemIndex >= 0) then + begin + R := GetItemRelRect(Index, LocRect); + FTouchAnimationObject.TouchAnimation.DrawTouchAnimation(Canvas, R); + end; +end; + +function TListViewBase.GetHeaderRelRect(const StartItem, HeaderIndex: Integer; const LocRect: TRectF; + const SideSpace: Integer): TRectF; +var + LimitRect: TRectF; +begin + Result := GetItemRelRect(HeaderIndex, LocRect, SideSpace); + if Result.Top < LocRect.Top then + Result.Offset(0, LocRect.Top - Result.Top); + + if (StartItem < Adapter.Count - 1) and (Adapter[StartItem + 1].HeaderRef <> HeaderIndex) then + begin + LimitRect := GetItemRelRect(StartItem, LocRect, SideSpace); + + if Result.Bottom > LimitRect.Bottom then + Result.Offset(0, LimitRect.Bottom - Result.Bottom); + end; +end; + +procedure TListViewBase.DrawHeaderItem(const LocRect: TRectF; const StartItem, HeaderIndex: Integer; + const Opacity: Single); +begin + // ZuBy *** + if FHeaderStyleImage <> nil then + FHeaderStyleImage.DrawToCanvas(Canvas, GetHeaderRelRect(StartItem, + HeaderIndex, LocRect), Opacity) + else + begin + FBrush.Color := FHeaderStyleColor; + Canvas.FillRect(GetHeaderRelRect(StartItem, HeaderIndex, LocRect), 0, 0, + AllCorners, Opacity, FBrush); + end; + // *** ZuBy +end; + +function TListViewBase.GetItemClientRect(const Index: Integer): TRectF; +var + MarginSize: TPointF; + FinalItemSpaces: TRectF; +begin + FinalItemSpaces := GetFinalItemSpaces(False); + + MarginSize.X := FinalItemSpaces.Left + FinalItemSpaces.Right; + MarginSize.Y := FinalItemSpaces.Top + FinalItemSpaces.Bottom; + + Result.Left := FSideSpace + FinalItemSpaces.Left; + Result.Top := FSideSpace + FinalItemSpaces.Top; + Result.Right := Width - (FSideSpace + FinalItemSpaces.Right); + Result.Bottom := GetItemHeight(Index) - MarginSize.Y; +end; + +function TListViewBase.GetEstimatedItemHeight: Single; +begin + Result := FEstimatedHeights.Item; +end; + +function TListViewBase.GetEstimatedHeaderHeight: Single; +begin + Result := FEstimatedHeights.Header; +end; + +function TListViewBase.GetEstimatedFooterHeight: Single; +begin + Result := FEstimatedHeights.Footer; +end; + +procedure TListViewBase.DrawListItems(const AbsOpacity: Single); + + function GetCleanClipRect: TRectF; + begin + Result := LocalRect; + + if (FSearchEdit <> nil) and FSearchEdit.Visible and not HasSearchAsItem then + Result.Top := Result.Top + FSearchEdit.Height; + end; + +const + DefaultParams: TListItemDrawable.TParams = (AbsoluteOpacity: 1.0; ItemSelectedAlpha: 1.0; + DeletingUnwantedOpacity: 1.0; ParentAbsoluteRect: (Left:0;Top:0;Right:0;Bottom:0); Images: nil); + AnimationDeltaEpsilon = 0.01; +var + I, StartItem, EndItem, MaxHeight, ItemHeaderIndex, SubPassNo: Integer; + VertMarginHeight, SceneScale: Single; + PassNo, NumberOfPasses, TopViewIndex, NumberOfSubPasses: Integer; + MarginSize: TPointF; + BorderRect, RelRect, LocRect, ClipRect: TRectF; + State: TCanvasSaveState; + ListItem: TListItem; + CurDrawable: TListItemDrawable; + DrawStates: TListItemDrawStates; + FinalItemSpaces: TRectF; + NeedPaintPullRefreshStroke, NeedPaintScrollingStretchGlow: Boolean; + PullRefreshStrength: Single; + ItemHeight: Integer; + MaxItemIndex: Integer; + Resources: TListItemStyleResources; + Params: TListItemDrawable.TParams; + Checkable: IListViewCheckProvider; +begin + Adapter.CreateNewViews; + UpdateItemLookups; + + // Precache local rectangle. + LocRect := LocalRect; + ClipRect := LocRect; + + PullRefreshStrength := GetPullRefreshStrength; + Params := DefaultParams; + Params.Images := GetImages; + if GetDeleteModeTransitionAlpha > AnimationDeltaEpsilon then + Params.DeletingUnwantedOpacity := Max(0, 1 - (GetDeleteModeTransitionAlpha * 2)); + Params.ParentAbsoluteRect := AbsoluteRect; + + + if FPullToRefresh and ((PullRefreshStrength > 0) or (FPullRefreshAnimation = TPullRefreshAnimation.Playing)) and + (FListingService <> nil) and (TListingTransitionFeature.PullToRefresh in FListingService.GetTransitionFeatures) then + PaintPullRefreshIndicator(Canvas, PullRefreshStrength, AbsOpacity); + + if FSearchVisible and (FSearchEdit <> nil) and (FListingService <> nil) then + if (FSearchAlwaysOnTop or (not (TListingSearchFeature.AsFirstItem in FListingService.GetSearchFeatures))) and + (TListingSearchFeature.StayOnTop in FListingService.GetSearchFeatures) then + ClipRect.Top := ClipRect.Top + FSearchEdit.Height + else + ClipRect.Top := ClipRect.Top + Max(0, FSearchEdit.Height - FScrollViewPos); + + // Set item clipping. + State := Canvas.SaveState; + Canvas.IntersectClipRect(ClipRect); + + // Calculate item height sums. + // *** ZuBy + if FHorizontal then + MaxHeight := Ceil(LocRect.Width + FScrollViewPos) + else + MaxHeight := Ceil(LocRect.Height + FScrollViewPos); + // ZuBy *** + + // Determine starting and final elements that are currently visible. + TopViewIndex := Trunc(FScrollViewPos - FSideSpace); + MaxItemIndex := Adapter.Count - 1; + StartItem := Min(Max(FindItemAbsoluteAt(TopViewIndex), 0), MaxItemIndex); + EndItem := MaxItemIndex; + + for I := StartItem + 1 to MaxItemIndex - 1 do + if FHeightSums[I + 1] > MaxHeight then + begin + EndItem := I; + break; + end; + + if (FListingService <> nil) and (TListingHeaderBehavior.Sticky in FListingService.GetHeaderBehaviors) then + ItemHeaderIndex := Adapter[StartItem].HeaderRef + else + ItemHeaderIndex := -1; + + FinalItemSpaces := GetFinalItemSpaces(False); + + // *** ZuBy + if FHorizontal then + VertMarginHeight := FinalItemSpaces.Left + FinalItemSpaces.Right + else + VertMarginHeight := FinalItemSpaces.Top + FinalItemSpaces.Bottom; + // ZuBy *** + + if (TAlphaColorRec(FItemStyleFillColor).A > 0) or (FAlternatingColors and + (TAlphaColorRec(FItemStyleFillAltColor).A > 0)) then + begin + if (TAlphaColorRec(FItemStyleFillColor).A >= 255) and (not FAlternatingColors or + (TAlphaColorRec(FItemStyleFillAltColor).A >= 255)) and SameValue(AbsOpacity, 1, TEpsilon.Vector) then + begin + Canvas.Blending := False; + try + DrawItemsFill(StartItem, EndItem, LocRect, 1, ItemHeaderIndex); + finally + Canvas.Blending := True; + end; + end + else + DrawItemsFill(StartItem, EndItem, LocRect, AbsOpacity, ItemHeaderIndex); + end; + + // Draw regular selection + if (not FEditMode) and (FItemIndex >= StartItem) and (FItemIndex <= EndItem) and (FSelectionAlphas.Count < 1) and + CanDisplaySelectionForItem(FItemIndex) then + DrawIndexFill(FItemIndex, LocRect, AbsOpacity); + + // Edit mode has current item and selected items + if FEditMode then + begin + // Highlight current item (e.g. for keyboard navigation) + if (FItemIndex >= StartItem) and (FItemIndex <= EndItem) then + DrawIndexFill(FItemIndex, LocRect, AbsOpacity * 0.5); + + // Highlight items with checkboxes + if Supports(Adapter, IListViewCheckProvider, Checkable) then + for I := StartItem to EndItem do + if Checkable.Checked[I] then + DrawIndexFill(I, LocRect, AbsOpacity * GetItemSelectionAlpha(I)); + end; + + MarginSize.X := FinalItemSpaces.Left + FinalItemSpaces.Right; + MarginSize.Y := FinalItemSpaces.Top + FinalItemSpaces.Bottom; + + BorderRect.Left := LocRect.Left + FSideSpace + FinalItemSpaces.Left; + BorderRect.Top := LocRect.Top + FSideSpace + FinalItemSpaces.Top; + // ZuBy *** + if FHorizontal then + begin + BorderRect.Right := BorderRect.Left + Adapter.GetDefaultViewHeight - + MarginSize.X; + BorderRect.Bottom := LocRect.Bottom + (FSideSpace + FinalItemSpaces.Bottom); + end + else + begin + BorderRect.Right := LocRect.Right - (FSideSpace + FinalItemSpaces.Right); + BorderRect.Bottom := BorderRect.Top + Adapter.GetDefaultViewHeight - + MarginSize.Y; + end; + // *** ZuBy + + SceneScale := Canvas.Scale; + + GetNumberOfRenderingPasses(StartItem, EndItem, NumberOfPasses, NumberOfSubPasses); + + DrawTouchAnimation(ItemIndex, LocRect, AbsOpacity); + + Resources := GetStyleResources; + + for PassNo := 0 to NumberOfPasses - 1 do + for SubPassNo := 0 to NumberOfSubPasses - 1 do + for I := StartItem to EndItem do + if I <> ItemHeaderIndex then + begin + ListItem := Adapter[I]; + + if (ListItem <> nil) and (ListItem.Count > PassNo) then + begin + CurDrawable := ListItem.View[PassNo]; + if (CurDrawable = nil) or (not CurDrawable.Visible) then + Continue; + + DrawStates := []; + + if (FDeleteButtonIndex = I) or (FPrevDeleteButtonIndex = I) then + Include(DrawStates, TListItemDrawState.Deleting); + + if CanDisplaySelectionForItem(I, ListItem, True, True) and (GetItemSelectionAlpha(I) > TEpsilon.Vector) then + Include(DrawStates, TListItemDrawState.Selected); + + if (FEditModeTransitionAlpha > 0) and (ListItem.Purpose = TListItemPurpose.None) then + Include(DrawStates, TListItemDrawState.EditMode); + + // During the first sub-pass the designated areas are calculated. + if SubPassNo = 0 then + begin + if I < MaxItemIndex then + ItemHeight := FHeightSums[I + 1] - FHeightSums[I] + else + ItemHeight := GetItemHeight(I); + ListItem.WillBePainted; + CurDrawable.UpdateValuesFromResources(GetStyleResources, ListItem.Purpose); + + // *** ZuBy + if FHorizontal then + CurDrawable.CalculateLocalRect + ((TRectF.Create(TPointF.Create(BorderRect.Left + FHeightSums + [I] - FScrollViewPos, BorderRect.Top), + ItemHeight - VertMarginHeight, BorderRect.Height)), + SceneScale, DrawStates, ListItem) + else // *** ZuBy + CurDrawable.CalculateLocalRect + (TRectF.Create(TPointF.Create(BorderRect.Left, + BorderRect.Top + FHeightSums[I] - FScrollViewPos), + BorderRect.Width, ItemHeight - VertMarginHeight), SceneScale, + DrawStates, ListItem); + end; + // *** ZuBy + + Params.AbsoluteOpacity := AbsoluteOpacity * CurDrawable.Opacity; + Params.ItemSelectedAlpha := GetItemSelectionAlpha(ListItem.Index); + CurDrawable.Render(Canvas, I, DrawStates, Resources, Params, + SubPassNo); + end; + end; + + if ItemHeaderIndex <> -1 then + begin + DrawHeaderItem(LocRect, StartItem, ItemHeaderIndex, AbsOpacity); + + for PassNo := 0 to NumberOfPasses - 1 do + for SubPassNo := 0 to NumberOfSubPasses - 1 do + begin + ListItem := Adapter[ItemHeaderIndex]; + + if (ListItem <> nil) and (ListItem.Count > PassNo) then + begin + CurDrawable := ListItem.View[PassNo]; + if (CurDrawable = nil) or (not CurDrawable.Visible) then + Continue; + + // During the first sub-pass the designated areas are calculated. + if SubPassNo = 0 then + begin + RelRect := GetHeaderRelRect(StartItem, ItemHeaderIndex, LocRect); + + // *** ZuBy + if FHorizontal then + begin + RelRect.Top := BorderRect.Top; + RelRect.Bottom := BorderRect.Bottom; + end + else + begin + RelRect.Left := BorderRect.Left; + RelRect.Right := BorderRect.Right; + end; // ZuBy *** + CurDrawable.UpdateValuesFromResources(GetStyleResources, + ListItem.Purpose); + CurDrawable.CalculateLocalRect(RelRect, SceneScale, [], ListItem); + end; + + Params.AbsoluteOpacity := AbsoluteOpacity * CurDrawable.Opacity; + Params.ItemSelectedAlpha := GetItemSelectionAlpha(ListItem.Index); + CurDrawable.Render(Canvas, ItemHeaderIndex, [], Resources, Params, SubPassNo); + end; + end; + end; + + // Restore previous clipping rectangle. + Canvas.RestoreState(State); + + if FPullToRefresh then + begin + NeedPaintScrollingStretchGlow := HasScrollingStretchGlow and (Abs(FScrollStretchStrength) > 0); + NeedPaintPullRefreshStroke := HasPullRefreshStroke; + + if NeedPaintScrollingStretchGlow or NeedPaintPullRefreshStroke then + begin + State := Canvas.SaveState; + try + Canvas.IntersectClipRect(GetCleanClipRect); + + if NeedPaintScrollingStretchGlow then + PaintScrollingStretchGlow(Canvas, FScrollStretchStrength, AbsOpacity); + + if NeedPaintPullRefreshStroke then + PaintPullRefreshStroke(Canvas, PullRefreshStrength, AbsOpacity); + finally + Canvas.RestoreState(State); + end; + end; + end; + + TriggerIncidents(TDelayedIncident.ChangeRepainted); +end; + +procedure TListViewBase.Paint; +var + LOpacity: Single; +begin + if not (TStateFlag.Painting in FStateFlags) then + begin + Include(FStateFlags, TStateFlag.Painting); + try + FScrollScale := Canvas.Scale; + LOpacity := GetAbsoluteOpacity; + if not FTransparent then + begin + FBrush.Color := FBackgroundStyleColor; + if SameValue(LOpacity, 1, TEpsilon.Vector) then + begin + Canvas.Blending := False; + try + Canvas.FillRect(LocalRect, 0, 0, AllCorners, 1, FBrush); + finally + Canvas.Blending := True; + end; + end + else + Canvas.FillRect(LocalRect, 0, 0, AllCorners, LOpacity, FBrush); + end; + if Adapter.Count > 0 then + DrawListItems(LOpacity); + finally + Exclude(FStateFlags, TStateFlag.Painting); + end; + end; +end; + +procedure TListViewBase.AfterPaint; +begin + inherited; + Exclude(FStateFlags, TStateFlag.Invalid); +end; + +procedure TListViewBase.RecreateNativePresentation; +begin +end; + +procedure TListViewBase.Loaded; +begin + inherited; + ImagesChanged; + RecreateNativePresentation; +end; + +procedure TListViewBase.Resize; +begin + inherited; + BeginUpdate; + try + Adapter.ResetViews([]); + if FAutoColumns then + begin + FColumns := floor(Width / FColumnWidth); +{$IFDEF MSWINDOWS} // *** sinuke + if FScrollBar.Visible then + FMarg := Trunc(((Width - FScrollBar.Width) - (FColumns * FColumnWidth)) / (FColumns + 1)) + else + FMarg := Trunc((Width - (FColumns * FColumnWidth)) / (FColumns + 1)); +{$ELSE} + FMarg := Trunc((Width - (FColumns * FColumnWidth)) / (FColumns + 1)); +{$ENDIF} // *** sinuke + end; + finally + EndUpdate; + end; + UpdateScrollingLimits; +end; + +procedure TListViewBase.EndUpdate; +begin + inherited; + if not IsUpdating then + begin + if TStateFlag.NeedsScrollingLimitsUpdate in FStateFlags then + UpdateScrollingLimits; + if TStateFlag.NeedsRebuild in FStateFlags then + RebuildList; + FStateFlags := FStateFlags - [TStateFlag.NeedsScrollingLimitsUpdate, TStateFlag.NeedsRebuild]; + end; +end; + +function TListViewBase.ObjectAtPoint(P: TPointF): IControl; +var + LocalPt: TPointF; + ItemAt: Integer; + Control: TControl; + ListItem: TListItem; +begin + if not FMouseClicked then + begin + LocalPt := ScreenToLocal(P); + // *** ZuBy + if FHorizontal then + ItemAt := FindItemAbsoluteAt + (Round(FScrollViewPos + LocalPt.X - (LocalRect.Left + FSideSpace))) + else + ItemAt := FindItemAbsoluteAt + (Round(FScrollViewPos + LocalPt.Y - (LocalRect.Top + FSideSpace))); + // ZuBy *** + if (ItemAt >= 0) and (ItemAt < Adapter.Count) then + begin + ListItem := Adapter[ItemAt]; + if ListItem.View.Initialized then + begin + Control := ListItem.ObjectAtPoint(P); + if Control <> nil then + exit(Control); + end; + end; + end; + Result := inherited ObjectAtPoint(P); +end; + +function TListViewBase.FindLocalItemObjectAtPosition(const ItemIndex: Integer; const Position: TPointF): TListItemDrawable; +var + I: Integer; + Item: TListItem; +begin + if (ItemIndex < 0) or (ItemIndex >= Adapter.Count) then + exit(nil); + + Item := Adapter[ItemIndex]; + + for I := 0 to Item.Count - 1 do + if Item.View[I].InLocalRect(Position) then + exit(Item.View[I]); + + Result := nil; +end; + +procedure TListViewBase.KeyDown(var Key: Word; var KeyChar: System.WideChar; Shift: TShiftState); +var + LFirstVisible, LLastVisible: Integer; + LItemIndex: Integer; + + procedure CalcVisible; + var + I: Integer; + MaxHeight: Integer; + TopViewIndex: Integer; + LocRect: TRectF; + begin + // Precache local rectangle. + LocRect := LocalRect; + + // Calculate item height sums. + // *** ZuBy + if FHorizontal then + MaxHeight := Ceil(LocRect.Width + FScrollViewPos) + else + MaxHeight := Ceil(LocRect.Height + FScrollViewPos); + // ZuBy *** + + // Determine starting and final elements that are currently visible. + TopViewIndex := Trunc(FScrollViewPos - FSideSpace); + + LFirstVisible := Min(Max(FindItemAbsoluteAt(TopViewIndex), 0), Adapter.Count - 1); + LLastVisible := Adapter.Count - 1; + + for I := LFirstVisible + 1 to Adapter.Count - 2 do + if FHeightSums[I + 1] >= MaxHeight then + begin + LLastVisible := I; + break; + end; + end; + + procedure CycleNewIndexUp; + begin + // Cycle through items up until non-header and non-footer is found. + repeat + LItemIndex := Max(LItemIndex - 1, 0); + until (LItemIndex <= 0) or (Adapter[LItemIndex].Purpose = TListItemPurpose.None); + end; + + procedure CycleNewIndexDown; + begin + // Cycle through items down until non-header and non-footer is found. + repeat + LItemIndex := Min(LItemIndex + 1, Adapter.Count - 1); + until (LItemIndex >= ItemCount - 1) or (Adapter[LItemIndex].Purpose = TListItemPurpose.None); + end; + +var + I: Integer; + LChanged: Boolean; + TextProvider: IListViewTextProvider; +begin + LItemIndex := ItemIndex; + if FAllowSelection then + if Observers.IsObserving(TObserverMapping.EditLinkID) then + if (KeyChar > ' ') or (Key in [vkHome, vkEnd, vkUp, vkDown, vkRight, vkLeft]) then + if TLinkObservers.EditLinkIsReadOnly(Observers) then + exit + else if not TLinkObservers.EditLinkEdit(Observers) then + exit; + inherited; + if ItemCount > 0 then + begin + if KeyChar <> #0 then + begin + if Supports(Adapter, IListViewTextProvider, TextProvider) then + for I := 0 to ItemCount - 1 do + if (TextProvider.Text[I] <> '') and (string(TextProvider.Text[I].Chars[0]).ToLower = string(KeyChar).ToLower) then + begin + LItemIndex := I; + break; + end; + + if KeyChar = #32 then + Key := vkSpace; + + KeyChar := #0; + end; + case Key of + vkHome: + begin + LItemIndex := 0; + + if Adapter[LItemIndex].Purpose <> TListItemPurpose.None then + CycleNewIndexDown; + end; + + vkEnd: + begin + LItemIndex := Adapter.Count - 1; + + if Adapter[LItemIndex].Purpose <> TListItemPurpose.None then + CycleNewIndexUp; + end; + + vkUp, + vkLeft: + begin + CycleNewIndexUp; + + if Adapter[LItemIndex].Purpose <> TListItemPurpose.None then + CycleNewIndexDown; + end; + + vkDown, + vkRight: + begin + CycleNewIndexDown; + if Adapter[LItemIndex].Purpose <> TListItemPurpose.None then + CycleNewIndexUp; + end; + vkPrior: + begin + CalcVisible; + LItemIndex := Max(0, LFirstVisible - Max(1, LLastVisible - LFirstVisible + 1)); + if Adapter[LItemIndex].Purpose <> TListItemPurpose.None then + CycleNewIndexDown; + end; + + + + vkNext: + begin + CalcVisible; + LItemIndex := Min(Adapter.Count - 1, LLastVisible + Max(1, LLastVisible - LFirstVisible + 1)); + + if Adapter[LItemIndex].Purpose <> TListItemPurpose.None then + CycleNewIndexUp; + end; + + vkSpace: + begin + SetNewItemIndex(FItemIndex); + if (not HasTouchTracking) and (FItemIndex >= 0) and (FItemIndex < Adapter.Count) then + Adapter[FItemIndex].MouseSelect; + end + else + exit; + end; + LChanged := LItemIndex <> ItemIndex; + if LChanged then + TLinkObservers.PositionLinkPosChanging(Observers); // Validation exception during this call + SetItemIndexInternal(LItemIndex, True); + if LChanged then + begin + TLinkObservers.ListSelectionChanged(Observers); + DoChange; + end; + Key := 0; + end; +end; + +procedure TListViewBase.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); + + function CancelMode: Boolean; + var + FirstIndex: Integer; + Checkable: IListViewCheckProvider; + GlyphButtonDrawable: TListItemGlyphButton; + begin + Result := False; + + if HasTouchTracking then + begin + if (not FEditMode) and (FDeleteButtonIndex <> -1) then + begin + // hide Delete button + SetDeleteButtonIndex(-1); + SetItemIndexInternal(-1); + FDragListMode := TInternalDragMode.None; + FMouseClicked := False; + FTapSelectItemIndex := -1; + FTapSelectNewIndexApplied := -1; + exit(True); + end + else if Supports(Adapter, IListViewCheckProvider, Checkable) and Checkable.AnyChecked(True) then + begin + FirstIndex := Checkable.FirstChecked(True); + GlyphButtonDrawable := GetGlyphButton(FirstIndex); + if (FirstIndex <> -1) and (not (TStateFlag.ScrollingActive in FStateFlags)) and + (GlyphButtonDrawable <> nil) and (GlyphButtonDrawable.ButtonType in [TGlyphButtonType.Delete]) then + begin + Checkable.CheckAll(False); + SetDeleteButtonIndex(-1); + exit(True); + end + end; + end; + end; + +var + ItemAt: Integer; + IsCheckGlyphVisible: Boolean; + Distance: Single; + GlyphButtonDrawable: TListItemGlyphButton; +begin + inherited; + + if not ShouldHandleEvents then + begin + FClickEventMousePos := TPointF.Create(X, Y); + exit; + end; + + FDragListMode := TInternalDragMode.None; + + if (FAniCalc <> nil) and (TStateFlag.ScrollingActive in FStateFlags) and FAniCalc.Animation then + begin + FAniCalc.Averaging := ssTouch in Shift; + FAniCalc.MouseUp(X, Y); + FAniCalc.Animation := False; + end; + + if (FTransitionType <> TTransitionType.None) or CancelMode then + exit; + + if Button = TMouseButton.mbLeft then + begin + FMouseClicked := True; + FMouseClickIndex := -1; + FMouseEventIndex := -1; + + if HasTouchTracking then + begin + StopPropertyAnimation('ScrollViewPos'); + + // *** ZuBy + if FHorizontal then + Distance := X - LocalRect.Left + else + Distance := Y - LocalRect.Top; + // ZuBy *** + + if (not (TStateFlag.ScrollingActive in FStateFlags)) and FAutoTapScroll and (Distance < FAutoTapTreshold) then + FAutoTapDistance := Round(FScrollViewPos) + else + FAutoTapDistance := 0; + end; + + if not (TStateFlag.ScrollingActive in FStateFlags) then + begin + // ZuBy *** + if FHorizontal then + ItemAt := FindItemAbsoluteAtWithCheck + (Round(FScrollViewPos + X - (LocalRect.Left + FSideSpace))) + else + ItemAt := FindItemAbsoluteAtWithCheck + (Round(FScrollViewPos + Y - (LocalRect.Top + FSideSpace))); + // *** ZuBy + if (ItemAt >= 0) and (ItemAt < Adapter.Count) and + (Adapter[ItemAt].Count > 0) then + begin + if Adapter[ItemAt].MouseDown(Button, Shift, PointF(X, Y)) then + begin + FMouseEventIndex := ItemAt; + + FClickEventMousePos := TPointF.Create(X, Y) - GetItemRelRect(ItemAt, LocalRect).TopLeft; + + if FAllowSelection then + SetNewItemIndex(FMouseEventIndex); + + // ZuBy *** + if FAutoColumns then + Invalidate; + // *** ZuBy + end + else + begin + if Adapter[ItemAt].ObjectAtPoint(TPointF.Create(X, Y)) <> nil then + FMouseClickIndex := ItemAt; + + if (FMouseClickIndex <> -1) and FAllowSelection then + SetNewItemIndex(FMouseClickIndex); + + GlyphButtonDrawable := GetGlyphButton(ItemAt); + IsCheckGlyphVisible := FEditMode and (GlyphButtonDrawable <> nil) and + (GlyphButtonDrawable.ButtonType in [TGlyphButtonType.Checkbox]); + if ((FMouseClickIndex = -1) or IsCheckGlyphVisible) and (FDeleteButtonIndex = -1) then + begin + FTapSelectItemIndex := ItemAt; + FTapSelectNewIndexApplied := -1; + FTapSelectStartTime := FTimerService.GetTick; + + FClickEventMousePos := TPointF.Create(X, Y) - GetItemRelRect(ItemAt, LocalRect).TopLeft; + FClickEventControl := FindLocalItemObjectAtPosition(ItemAt, TPointF.Create(X, Y)); + + if FTouchAnimationObject <> nil then + FTouchAnimationObject.TouchAnimation.StartAnimation(Self, TTouchAnimationAdapter.TAnimationKind.Pressed); + + UpdateRecurrentTimer; + + // ZuBy *** + if FAutoColumns then + Invalidate; + // *** ZuBy + end; + end + end; + end + else + Include(FStateFlags, TStateFlag.ScrollingMouseTouch); + + if (FMouseClickIndex = -1) and (FMouseEventIndex = -1) then + begin + if FAniCalc <> nil then + begin + FAniCalc.Averaging := ssTouch in Shift; + FAniCalc.MouseDown(X, Y); + end; + + FMouseDownAt := TPointF.Create(X, Y); + FMouseClickPrev := FMouseDownAt; + FMouseClickDelta := TPointF.Zero; + FMousePrevScrollPos := FScrollViewPos; + FMouseClickSwipeEventSend := False; // ZuBy + end; + end; +end; + +procedure TListViewBase.MouseMove(Shift: TShiftState; X, Y: Single); +var + NewDeleteIndex: Integer; + Checkable: IListViewCheckProvider; + aMinScroll: Boolean; // ZuBy +begin + inherited; + + if (FTransitionType <> TTransitionType.None) or not ShouldHandleEvents then + begin + FClickEventMousePos := TPointF.Create(X, Y); + exit; + end; + + if not Enabled then + begin + FMouseClicked := False; + FTapSelectNewIndexApplied := -1; + FDragListMode := TInternalDragMode.None; + exit; + end; + + if FMouseClicked and (FMouseClickIndex = -1) and (FMouseEventIndex = -1) then + begin + FMouseClickDelta.X := FMouseClickDelta.X + (X - FMouseClickPrev.X); + FMouseClickDelta.Y := FMouseClickDelta.Y + (Y - FMouseClickPrev.Y); + + FMouseClickPrev := TPointF.Create(X, Y); + + if FDragListMode = TInternalDragMode.None then + begin + if HasTouchTracking and (Abs(FMouseClickDelta.X) > MinSwypeThreshold) + and (FCanSwipeDelete or FCanSwipeDirection) and (FTapSelectNewIndexApplied = -1) then + begin + if FCanSwipeDirection then + begin + if Assigned(FOnSwipe) and (not FMouseClickSwipeEventSend) then + begin + FMouseClickSwipeEventSend := True; + if Abs(FMouseDownAt.X) > (X + MinSwypeThreshold) then + FOnSwipe(Self, TSwipeDirection.ToLeft) + else if Abs(FMouseDownAt.X) < (X + MinSwypeThreshold) then + FOnSwipe(Self, TSwipeDirection.ToRight); + end; + end + else + FDragListMode := TInternalDragMode.Swype; + end + else + begin + // ZuBy *** + if FHorizontal then + aMinScroll := Abs(FMouseClickDelta.X) > MinScrollThreshold + else + aMinScroll := Abs(FMouseClickDelta.Y) > MinScrollThreshold; + + if aMinScroll then + begin + FDragListMode := TInternalDragMode.Drag; + + FTapSelectItemIndex := -1; + + if FAniCalc <> nil then + begin + FAniCalc.Averaging := ssTouch in Shift; + FAniCalc.Animation := True; + FAniCalc.MouseDown(FMouseClickPrev.X, FMouseClickPrev.Y); + end; + end; + // *** ZuBy + end; + end; + end; + + if (FTapSelectNewIndexApplied <> -1) and (FDragListMode = TInternalDragMode.Drag) then + begin + if FEditMode and Supports(Adapter, IListViewCheckProvider, Checkable) then + Checkable[FTapSelectNewIndexApplied] := not Checkable[FTapSelectNewIndexApplied]; + + FTapSelectNewIndexApplied := -1; + SetItemIndexInternal(-1); + end; + + if (not FEditMode) and (FDragListMode = TInternalDragMode.Swype) and + (not (TStateFlag.ScrollingMouseTouch in FStateFlags)) and (FDeleteButtonIndex = -1) and HasTouchTracking then + begin + // *** ZuBy + if FHorizontal then + NewDeleteIndex := FindItemAbsoluteAt + (Round(FScrollViewPos + X - (LocalRect.Left + FSideSpace))) + else + NewDeleteIndex := FindItemAbsoluteAt + (Round(FScrollViewPos + Y - (LocalRect.Top + FSideSpace))); + // ZuBy *** + + if (NewDeleteIndex <> -1) and (Adapter[NewDeleteIndex].Purpose <> TListItemPurpose.None) then + NewDeleteIndex := -1; + + if NewDeleteIndex <> -1 then + begin + SetDeleteButtonIndex(NewDeleteIndex); + SetItemIndexInternal(NewDeleteIndex); + FTapSelectItemIndex := -1; + + FDragListMode := TInternalDragMode.None; + FMouseClicked := False; + + if (FAniCalc <> nil) and (TStateFlag.ScrollingActive in FStateFlags) and FAniCalc.Animation then + begin + FAniCalc.Averaging := ssTouch in Shift; + FAniCalc.MouseUp(X, Y); + FAniCalc.Animation := False; + end; + + exit; + end; + end; + + if (FAniCalc <> nil) and FAniCalc.Down and (FDragListMode = TInternalDragMode.Drag) then + FAniCalc.MouseMove(X, Y); + + if (FMouseEventIndex <> -1) and (FDragListMode = TInternalDragMode.Drag) then + Adapter[FMouseEventIndex].MouseMove(Shift, PointF(X, Y)); + + if (FTouchAnimationObject <> nil) and (FAniCalc <> nil) and (TStateFlag.ScrollingActive in FStateFlags) then + FTouchAnimationObject.TouchAnimation.StopAnimation; +end; + +function TListViewBase.GetGlyphButton(const Index: Integer): TListItemGlyphButton; +var + Provider: IListViewGlyphButtonProvider; +begin + Result := nil; + if (Index <> -1) and Supports(Adapter, IListViewGlyphButtonProvider, Provider) then + Result := Provider.GlyphButtonDrawable[Index]; +end; + +procedure TListViewBase.SetNewItemIndex(const NewIndex: Integer); +var + AllowChange: Boolean; + Checkable: IListViewCheckProvider; + GlyphButtonDrawable: TListItemGlyphButton; + OldItemIndex: Integer; +begin + OldItemIndex := ItemIndex; + if NewIndex <> ItemIndex then + begin + AllowChange := True; + ObserversBeforeSelection(AllowChange); + + if AllowChange then + begin + SetItemIndexInternal(NewIndex); + TLinkObservers.ListSelectionChanged(Observers); + if FEditMode and Supports(Adapter, IListViewCheckProvider, Checkable) then + begin + Checkable[NewIndex] := not Checkable[NewIndex]; + GlyphButtonDrawable := GetGlyphButton(NewIndex); + if (not (TStateFlag.ScrollingActive in FStateFlags)) and (GlyphButtonDrawable <> nil) and + (GlyphButtonDrawable.ButtonType in [TGlyphButtonType.Delete]) then + SetDeleteButtonIndex(NewIndex); + end; + end; + end; + + if NewIndex <> -1 then + DoListItemClick(Adapter[NewIndex]); + + if (NewIndex <> OldItemIndex) and AllowChange then + begin + DoChange; + Invalidate; + end; +end; + +procedure TListViewBase.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); +var + NewIndex: Integer; +begin + inherited; + + if (not FEditMode and (FDeleteButtonIndex <> -1)) or not ShouldHandleEvents then + begin + FClickEventMousePos := TPointF.Create(X, Y); + FMouseClickSwipeEventSend := False; + exit; + end; + + if FTapSelectNewIndexApplied <> -1 then + begin + FMouseClicked := False; + FTapSelectNewIndexApplied := -1; + FDragListMode := TInternalDragMode.None; + if FAniCalc <> nil then + FAniCalc.MouseUp(FMouseDownAt.X, FMouseDownAt.Y); + DoChange; + Invalidate; + end; + + if not Enabled then + begin + FMouseClicked := False; + FTapSelectItemIndex := -1; + FTapSelectNewIndexApplied := -1; + FDragListMode := TInternalDragMode.None; + if FAniCalc <> nil then + FAniCalc.MouseUp(X, Y); + exit; + end; + + if (FAniCalc <> nil) and (TStateFlag.ScrollingActive in FStateFlags) then + FAniCalc.MouseUp(X, Y); + + if (FTouchAnimationObject <> nil) and not ((FAniCalc <> nil) and (TStateFlag.ScrollingActive in FStateFlags)) then + FTouchAnimationObject.TouchAnimation.StartAnimation(Self, TTouchAnimationAdapter.TAnimationKind.Unpressed); + + if FMouseClicked then + begin + FTapSelectItemIndex := -1; + + if not (TStateFlag.ScrollingMouseTouch in FStateFlags) then + begin + if FMouseEventIndex <> -1 then + begin + Adapter[FMouseEventIndex].MouseUp(Button, Shift, TPointF.Create(X, Y)); + FMouseEventIndex := -1; + end + else + begin + if (FAutoTapDistance > 0) and (FTapSelectNewIndexApplied = -1) then + begin + TAnimator.AnimateFloat(Self, 'ScrollViewPos', 0, // do not localize + Min(FAutoTapDistance / AutoTapScrollingSpeed, AutoTapMaxScrollingTime), + TAnimationType.Out, TInterpolationType.Sinusoidal); + FAutoTapDistance := 0; + end + else if (FMouseClickIndex = -1) and (FTapSelectNewIndexApplied = -1) then + begin + if FDragListMode = TInternalDragMode.None then + begin + // *** ZuBy + if FHorizontal then + NewIndex := FindItemAbsoluteAtWithCheck + (Round(FScrollViewPos + X - (LocalRect.Left + FSideSpace))) + else + NewIndex := FindItemAbsoluteAtWithCheck + (Round(FScrollViewPos + Y - (LocalRect.Top + FSideSpace))); + // ZuBy *** + + if (NewIndex >= 0) and (NewIndex < Adapter.Count) and (Adapter[NewIndex].Count > 0) and + (Adapter[NewIndex].Purpose = TListItemPurpose.None) then + begin + if FEditMode or FAllowSelection then + begin + if (FMouseClickIndex = -1) and Adapter[NewIndex].HasClickOnSelectItems then + FItemIndex := -1; + SetNewItemIndex(NewIndex); + end; + + FClickEventItemIndex := NewIndex; + FClickEventMousePos := TPointF.Create(X, Y) - GetItemRelRect(NewIndex, LocalRect).TopLeft; + FClickEventControl := FindLocalItemObjectAtPosition(FClickEventItemIndex, TPointF.Create(X, Y)); + + if not FMouseClickSwipeEventSend then + StartIncident(TDelayedIncident.ClickEvent); + // ZuBy *** + if FAutoColumns then + Invalidate; + // *** ZuBy + end; + end; + end + else + FMouseClickIndex := -1; + end; + end + else + Exclude(FStateFlags, TStateFlag.ScrollingMouseTouch); + + FMouseClicked := False; + FTapSelectNewIndexApplied := -1; + FDragListMode := TInternalDragMode.None; + end; + + FMouseClickSwipeEventSend := False; +end; + +procedure TListViewBase.MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean); +var + Offset: Single; +begin + inherited; + + if not ShouldHandleEvents then + exit; + + if (not Handled) and (not DisableMouseWheel) then + begin + if ssHorizontal in Shift then + begin + // ZuBy *** + if FScrollBar <> nil then + Offset := FScrollBar.SmallChange + else + Offset := Width / 5; + Offset := Offset * -1 * (WheelDelta / 120); + SetScrollViewPos(ScrollViewPos + Offset); + Handled := True; + // *** ZuBy + end + else + begin + if FScrollBar <> nil then + Offset := FScrollBar.SmallChange + else + Offset := Height / 5; + Offset := Offset * -1 * (WheelDelta / 120); + SetScrollViewPos(ScrollViewPos + Offset); + Handled := True; + end + end; +end; + +procedure TListViewBase.DoMouseLeave; +begin + inherited; + + if (FAniCalc <> nil) and ShouldHandleEvents then + FAniCalc.MouseLeave; +end; + +procedure TListViewBase.DoResetEditModeAnimation; +begin + InvalidateHeights; +end; + +procedure TListViewBase.DoSetItemIndexInternal(const Value: Integer); +begin +end; + +procedure TListViewBase.DoSetScrollViewPos(const Value: Single); +begin +end; + +procedure TListViewBase.Invalidate; +begin + if not (TStateFlag.Invalid in FStateFlags) then + begin + InvalidateRect(LocalRect); + Include(FStateFlags, TStateFlag.Invalid); + end; +end; + +procedure TListViewBase.DoChange; +begin + StartIncident(TDelayedIncident.ChangeRepainted, False, ChangeRepaintedIncidentDelay); + + if Assigned(FOnChange) then + FOnChange(Self); +end; + +procedure TListViewBase.DoChangeRepainted; +begin + if Assigned(FOnChangeRepainted) then + FOnChangeRepainted(Self); +end; + +procedure TListViewBase.UpdateItemLookups; +const + ZeroHeights: TEstimatedHeights = (Item: 0; Header: 0; Footer: 0); +var + I, TotalHeight, PrevItemHeight, CurHeaderRef: Integer; + ListItem: TListItem; +begin + if HeightSumsNeedUpdate then + begin + BeginUpdate; + try + TotalHeight := 0; + // ZuBy *** + if not FHorizontal then + begin + TotalHeight := FTopOffset; + if (FSearchEdit <> nil) and FSearchEdit.Visible then + TotalHeight := TotalHeight + Ceil(FSearchEdit.Height); + end; + // *** ZuBy + + FEstimatedHeights := ZeroHeights; + + FHeightSums.Clear; + FHeightSums.Add(TotalHeight); + + CurHeaderRef := -1; + + for I := 0 to Adapter.Count - 1 do + begin + if I > 0 then + begin + PrevItemHeight := GetItemHeight(I - 1) + FItemBottomOffset; + Inc(TotalHeight, PrevItemHeight); + + FHeightSums.Add(TotalHeight); + end; + + ListItem := Adapter[I]; + case ListItem.Purpose of + TListItemPurpose.None: + Adapter[I].HeaderRef := CurHeaderRef; + TListItemPurpose.Header: + begin + FEstimatedHeights.Header := GetItemHeight(I); + CurHeaderRef := I; + Adapter[I].HeaderRef := I; + end; + TListItemPurpose.Footer: + FEstimatedHeights.Footer := GetItemHeight(I); + end; + end; + + // ZuBy *** + if not FHorizontal then + Inc(TotalHeight, FBottomOffset); + // *** ZuBy + + if Adapter.Count > 0 then + begin + Inc(TotalHeight, GetItemHeight(Adapter.Count - 1)); + FEstimatedHeights.Item := TotalHeight / Adapter.Count; + end; + if TotalHeight <> FMaxKnownHeight then + begin + FMaxKnownHeight := TotalHeight; + UpdateScrollingLimits; + end; + + HeightSumsNeedUpdate := False; + finally + EndUpdate; + end; + end; +end; + +function TListViewBase.FindItemAbsoluteAt(const ViewAt: Integer): Integer; +var + Left, Right, Pivot, Value: Integer; +begin + UpdateItemLookups; + + if Adapter.Count < 1 then + exit(-1); + if ViewAt < 1 then + exit(0); + if ViewAt >= FHeightSums[FHeightSums.Count - 1] then + exit(FHeightSums.Count - 1); + + Left := 0; + Right := FHeightSums.Count - 1; + Pivot := 0; + + while Left <= Right do + begin + Pivot := (Left + Right) div 2; + + Value := FHeightSums[Pivot]; + if Value = ViewAt then + exit(Pivot); + + if Value > ViewAt then + Right := Pivot - 1 + else + Left := Pivot + 1; + end; + + Result := Pivot; + + if (Result > 0) and (Result < FHeightSums.Count) and (FHeightSums[Result] >= ViewAt) then + Dec(Result); +end; + +function TListViewBase.FindItemAbsoluteAtWithCheck(const ViewAt: Integer): Integer; +begin + Result := -1; + if Adapter.Count > 0 then + begin + UpdateItemLookups; + if ViewAt < FHeightSums[FHeightSums.Count - 1] + GetItemHeight(Adapter.Count - 1) then + Result := FindItemAbsoluteAt(ViewAt); + end; +end; + +function TListViewBase.GetDefaultStyleLookupName: string; +begin + Result := 'listviewstyle'; +end; + +procedure TListViewBase.ApplyStyle; + + function GetColorFromStyle(const ObjectName: string; const DefaultColor: TAlphaColor): TAlphaColor; + var + StyleObject: TFmxObject; + begin + StyleObject := FindStyleResource(ObjectName); + if StyleObject is TColorObject then + Result := TColorObject(StyleObject).Color + else if StyleObject is TText then + Result := TText(StyleObject).Color + else + Result := DefaultColor; + end; + + procedure AssignFontFromStyle(const Font: TFont; const ObjectName: string); + var + StyleObject: TFmxObject; + begin + StyleObject := FindStyleResource(ObjectName); + if StyleObject is TFontObject then + Font.Assign(TFontObject(StyleObject).Font) + else if StyleObject is TText then + Font.Assign(TText(StyleObject).Font); + end; + + function GetStyleObjectFromStyle(const ObjectName: string): TStyleObject; + var + StyleObject: TStyleObject; + begin + if FindStyleResource(ObjectName, StyleObject) then + Result := StyleObject + else + Result := nil; + end; + + procedure UpdateColorsInItemsObjects; + var + I: Integer; + ListItem: TListItem; + J: Integer; + Drawable: TListItemDrawable; + StyleResources: TListItemStyleResources; + begin + StyleResources := GetStyleResources; + + for I := 0 to Adapter.Count - 1 do + begin + ListItem := Adapter[I]; + for J := 0 to ListItem.View.Count - 1 do + begin + Drawable := ListItem.View.Drawables[J]; + Drawable.UpdateValuesFromStyle; + if ControlType = TControlType.Platform then + Drawable.UpdateValuesFromResources(StyleResources, ListItem.Purpose); + end; + end; + end; + +begin + inherited; + + // Control Colors + FBackgroundStyleColor := GetColorFromStyle('background', claWhite); + + FItemStyleFillColor := GetColorFromStyle('itembackground', claWhite); + FItemStyleFillAltColor := GetColorFromStyle('alternatingitembackground', claWhite); + FItemStyleFrameColor := GetColorFromStyle('frame', claBlack); + + // Item Colors + FStyleResources.DefaultTextColor := GetColorFromStyle('foreground', claBlack); + FStyleResources.DefaultTextSelectedColor := GetColorFromStyle('selectiontext', claBlack); + FStyleResources.DetailTextColor := GetColorFromStyle('detailtext', claBlack); + FStyleResources.HeaderTextColor := GetColorFromStyle('headertext', claWhite); + FStyleResources.HeaderTextShadowColor := GetColorFromStyle('headertextshadow', claWhite); + + FStyleResources.ButtonTextColor := GetColorFromStyle('buttontext', claWhite); + FStyleResources.ButtonTextPressedColor := GetColorFromStyle('buttontextpressed', claBlack); + FStyleResources.DeleteButtonTextColor := GetColorFromStyle('deletebuttontext', claWhite); + FStyleResources.DeleteButtonTextPressedColor := GetColorFromStyle('deletebuttontextpressed', claWhite); + + // Fonts + AssignFontFromStyle(FStyleResources.DefaultTextFont, 'font'); + AssignFontFromStyle(FStyleResources.DetailTextFont, 'detailfont'); + AssignFontFromStyle(FStyleResources.HeaderTextFont, 'headerfont'); + AssignFontFromStyle(FStyleResources.ButtonTextFont, 'buttonfont'); + AssignFontFromStyle(FStyleResources.DeleteButtonTextFont, 'deletebuttonfont'); + + // Style Images + // *** ZuBy + FItemBoxLight := GetStyleObjectFromStyle('boxlight'); + if FItemBoxLight <> nil then + begin + FItemStyleFrameColor := TAlphaColorRec.Null; + TAlphaColorRec(FBackgroundStyleColor).R := 238; + TAlphaColorRec(FBackgroundStyleColor).G := 238; + TAlphaColorRec(FBackgroundStyleColor).B := 238; + TAlphaColorRec(FBackgroundStyleColor).A := 255; + end; + + FSelectionStyleImage := GetStyleObjectFromStyle('boxdark'); + if FSelectionStyleImage = nil then + FSelectionStyleImage := GetStyleObjectFromStyle('selection'); + if FSelectionStyleImage = nil then + FSelectionStyleColor := GetColorFromStyle('selection', claBlue); + + FHeaderStyleImage := GetStyleObjectFromStyle('header'); + if FHeaderStyleImage = nil then + FHeaderStyleColor := claWhite; + + EnableTouchAnimation(True); + // ZuBy *** + + FStyleResources.ButtonAddItemStyleImage.Normal := GetStyleObjectFromStyle('additembutton'); + FStyleResources.ButtonAddItemStyleImage.Pressed := GetStyleObjectFromStyle('additembuttonpressed'); + FStyleResources.ButtonDeleteItemStyleImage.Normal := GetStyleObjectFromStyle('deleteitembutton'); + FStyleResources.ButtonDeleteItemStyleImage.Pressed := GetStyleObjectFromStyle('deleteitembuttonglyph'); + FStyleResources.ButtonNormalStyleImage.Normal := GetStyleObjectFromStyle('button'); + FStyleResources.ButtonNormalStyleImage.Pressed := GetStyleObjectFromStyle('buttonpressed'); + FStyleResources.ButtonDeleteStyleImage.Normal := GetStyleObjectFromStyle('deletebutton'); + FStyleResources.ButtonDeleteStyleImage.Pressed := GetStyleObjectFromStyle('deletebuttonpressed'); + FStyleResources.ButtonCheckboxStyleImage.Normal := GetStyleObjectFromStyle('checkboxunchecked'); + FStyleResources.ButtonCheckboxStyleImage.Pressed := GetStyleObjectFromStyle('checkboxchecked'); + + // Acessory Images + FStyleResources.AccessoryImages[TAccessoryType.More].Normal := GetStyleObjectFromStyle('accessorymore'); + FStyleResources.AccessoryImages[TAccessoryType.More].Selected := GetStyleObjectFromStyle('accessorymoreselected'); + FStyleResources.AccessoryImages[TAccessoryType.Checkmark].Normal := GetStyleObjectFromStyle('accessorycheckmark'); + FStyleResources.AccessoryImages[TAccessoryType.Checkmark].Selected := GetStyleObjectFromStyle('accessorycheckmarkselected'); + FStyleResources.AccessoryImages[TAccessoryType.Detail].Normal := GetStyleObjectFromStyle('accessorydetail'); + FStyleResources.AccessoryImages[TAccessoryType.Detail].Selected := GetStyleObjectFromStyle('accessorydetailselected'); + + FStyleResources.ScrollingStretchGlowColor := GetColorFromStyle('glow', $FF87C3DC); + FStyleResources.PullRefreshIndicatorColor := GetColorFromStyle('indicator', $FF686F7B); + FStyleResources.PullRefreshStrokeColor := GetColorFromStyle('pullrefreshstroke', $FF008CBB); + + UpdateColorsInItemsObjects; + // We don't use active notification model for native presentation and colors are set in presentation construction. + // So we recreate native presentation in this case. + if ControlType = TControlType.Platform then + begin + RecreateNativePresentation; + Invalidate; + end; +end; + +procedure TListViewBase.FreeStyle; +var + AccessoryType: TAccessoryType; +begin + // Control Colors + FBackgroundStyleColor := TAlphaColorRec.Null; + FItemStyleFillColor := TAlphaColorRec.Null; + FItemStyleFillAltColor := TAlphaColorRec.Null; + FItemStyleFrameColor := TAlphaColorRec.Null; + + // Item Colors + FStyleResources.DefaultTextColor := TAlphaColorRec.Null; + FStyleResources.DefaultTextSelectedColor := TAlphaColorRec.Null; + FStyleResources.DetailTextColor := TAlphaColorRec.Null; + FStyleResources.HeaderTextColor := TAlphaColorRec.Null; + FStyleResources.HeaderTextShadowColor := TAlphaColorRec.Null; + + FStyleResources.ButtonTextColor := TAlphaColorRec.Null; + FStyleResources.ButtonTextPressedColor := TAlphaColorRec.Null; + FStyleResources.DeleteButtonTextColor := TAlphaColorRec.Null; + FStyleResources.DeleteButtonTextPressedColor := TAlphaColorRec.Null; + + FStyleResources.ButtonAddItemStyleImage.Normal := nil; + FStyleResources.ButtonAddItemStyleImage.Pressed := nil; + FStyleResources.ButtonDeleteItemStyleImage.Normal := nil; + FStyleResources.ButtonDeleteItemStyleImage.Pressed := nil; + FStyleResources.ButtonNormalStyleImage.Normal := nil; + FStyleResources.ButtonNormalStyleImage.Pressed := nil; + FStyleResources.ButtonDeleteStyleImage.Normal := nil; + FStyleResources.ButtonDeleteStyleImage.Pressed := nil; + FStyleResources.ButtonCheckboxStyleImage.Normal := nil; + FStyleResources.ButtonCheckboxStyleImage.Pressed := nil; + + // Acessory Images + for AccessoryType in [Low(TAccessoryType)..High(TAccessoryType)] do + begin + FStyleResources.AccessoryImages[AccessoryType].Normal := nil; + FStyleResources.AccessoryImages[AccessoryType].Selected := nil; + end; + + FStyleResources.ScrollingStretchGlowColor := TAlphaColorRec.Null; + FStyleResources.PullRefreshIndicatorColor := TAlphaColorRec.Null; + FStyleResources.PullRefreshStrokeColor := TAlphaColorRec.Null; + + // Style Images + FSelectionStyleImage := nil; + FHeaderStyleImage := nil; + + // Touch Animation + if FTouchAnimationObject <> nil then + FTouchAnimationObject.TouchAnimation.CustomPaint := nil; + FTouchAnimationObject := nil; + inherited; +end; + +function TListViewBase.GetItemRect(const AItemIndex: Integer): TRectF; +begin + if (AItemIndex < 0) or (AItemIndex >= Adapter.Count) then + exit(TRectF.Create(0, 0, 0, 0)); + UpdateItemLookups; + Result := GetItemRelRect(AItemIndex, LocalRect); +end; + +procedure TListViewBase.ScrollTo(const AItemIndex: Integer); +var + LocRect, ItemRect: TRectF; + NewPos: Integer; +begin + if (AItemIndex < 0) or (AItemIndex >= Adapter.Count) then + exit; + + UpdateItemLookups; + + LocRect := LocalRect; + + ItemRect := GetItemRelRect(AItemIndex, LocRect); + // ZuBy *** + if FHorizontal then + begin + if ItemRect.Left < LocRect.Left then + begin + NewPos := FSideSpace + FHeightSums[AItemIndex]; + + // Take into account sticky header, so it does not clutter the view. + if HasTouchTracking and (Adapter[AItemIndex].HeaderRef <> -1) and + (Adapter[AItemIndex].HeaderRef <> AItemIndex) then + Dec(NewPos, GetItemHeight(Adapter[AItemIndex].HeaderRef)); + + SetScrollViewPos(NewPos); + end + else if ItemRect.Right > LocRect.Right then + SetScrollViewPos(FSideSpace + FHeightSums[AItemIndex] - + (LocRect.Width - GetItemHeight(AItemIndex))); + end + else + begin + if ItemRect.Top < LocRect.Top then + begin + NewPos := FSideSpace + FHeightSums[AItemIndex]; + + // Take into account sticky header, so it does not clutter the view. + if HasTouchTracking and (Adapter[AItemIndex].HeaderRef <> -1) and + (Adapter[AItemIndex].HeaderRef <> AItemIndex) then + Dec(NewPos, GetItemHeight(Adapter[AItemIndex].HeaderRef)); + + SetScrollViewPos(NewPos); + end + else if ItemRect.Bottom > LocRect.Bottom then + SetScrollViewPos(FSideSpace + FHeightSums[AItemIndex] - + (LocRect.Height - GetItemHeight(AItemIndex))); + end; + // *** ZuBy +end; + +procedure TListViewBase.ImagesChanged; +begin + if ([csLoading, csDestroying] * ComponentState = []) and (FImageLink <> nil) then + Invalidate; +end; + +function TListViewBase.GetImages: TCustomImageList; +begin + if FImageLink <> nil then + Result := TCustomImageList(FImageLink.Images) + else + Result := nil; +end; + +procedure TListViewBase.SetImages(const Value: TCustomImageList); +begin + if FImageLink <> nil then + FImageLink.Images := Value; +end; + +function TListViewBase.GetImageIndex: TImageIndex; +begin + Result := -1; +end; + +procedure TListViewBase.SetImageIndex(const Value: TImageIndex); +begin + // none +end; + +function TListViewBase.GetImageList: TBaseImageList; +begin + Result := GetImages; +end; + +procedure TListViewBase.SetImageList(const Value: TBaseImageList); +begin + ValidateInheritance(Value, TCustomImageList); + SetImages(TCustomImageList(Value)); +end; + +function TListViewBase.FindItemByPosition(X, Y: Single): Integer; +begin + // ZuBy *** + if FHorizontal then + Result := FindItemAbsoluteAtWithCheck + (Round(FScrollViewPos + X - (LocalRect.Left + FSideSpace))) + else + Result := FindItemAbsoluteAtWithCheck + (Round(FScrollViewPos + Y - (LocalRect.Top + FSideSpace))); + // *** ZuBy +end; + +procedure TListViewBase.EnableTouchAnimation(Value: Boolean); +var + TouchAnimation: TCustomStyleObject; +begin + if (Value) and (FTouchAnimationObject <> nil) then + exit; + + if Value then + begin + // Touch Animation + if FindStyleResource('touchanimation', TouchAnimation) + then + begin + Supports(TouchAnimation, ITouchAnimationObject, FTouchAnimationObject); + if FTouchAnimationObject <> nil then + FTouchAnimationObject.TouchAnimation.CustomPaint := Repaint; + end; + end + else + begin + if FTouchAnimationObject <> nil then + FTouchAnimationObject.TouchAnimation.CustomPaint := nil; + FTouchAnimationObject := nil; + end; +end; + +procedure TListViewBase.SearchBoxClear; +begin + if FSearchEdit = nil then + exit; + FSearchEdit.Text := ''; +end; + +procedure TListViewBase.RecalcTopViewItemIndex; // ZuBy +var + TopViewIndex: Integer; +begin + TopViewIndex := Trunc(FScrollViewPos - FSideSpace); + FTopItemIndex := Min(Max(FindItemAbsoluteAt(TopViewIndex), 0), Adapter.Count - 1); +end; + +function TListViewBase.getItemTextButtonHeight(const AItem: TListItemTextButton; const aWidth: Single = 0): Integer; +// ZuBy +var + aItemWidth: Single; +begin + if AItem.Text.IsEmpty then + begin + Result := Ceil(AItem.Height); + exit; + end; + + aItemWidth := 0; + if ShowScrollBar then + aItemWidth := DefaultScrollBarWidth; + + if SameValue(0, aWidth) then + aItemWidth := Width - (aItemWidth + ItemSpaces.Left + ItemSpaces.Right + (SideSpace * 2)) + else + aItemWidth := aWidth; + + Result := Ceil(getTextSize(AItem.Text, AItem.Font, AItem.WordWrap, aItemWidth,0).Height); +end; + +function TListViewBase.getItemTextButtonWidth(const AItem: TListItemTextButton; const aHeight: Single): Integer; +begin + if AItem.Text.IsEmpty then + begin + Result := Ceil(AItem.Height); + exit; + end; + + Result := Ceil(getTextSize(AItem.Text, AItem.Font, AItem.WordWrap, 0, 0).Width); +end; + +function TListViewBase.getItemTextHeight(const AItem: TListItemText; const aWidth: Single = 0): Integer; // ZuBy +var + aItemWidth: Single; +begin + if AItem.Text.IsEmpty then + begin + Result := Ceil(AItem.Height); + exit; + end; + + aItemWidth := 0; + if ShowScrollBar then + aItemWidth := DefaultScrollBarWidth; + + if SameValue(0, aWidth) then + aItemWidth := Width - (aItemWidth + ItemSpaces.Left + ItemSpaces.Right + (SideSpace * 2)) + else + aItemWidth := aWidth; + + Result := Ceil(getTextSize(AItem.Text, AItem.Font, AItem.WordWrap, aItemWidth, 0).Height); + +end; + +function TListViewBase.getItemTextWidth(const AItem: TListItemText; const aHeight: Single): Integer; +begin + if AItem.Text.IsEmpty then + begin + Result := Ceil(AItem.Height); + exit; + end; + + Result := Ceil(getTextSize(AItem.Text, AItem.Font, AItem.WordWrap, 0, 0).Width); +end; + +function TAppearanceListView.getLastVisibleItemindex: Integer; // ZuBy +begin + Result := Min(getFirstVisibleItemIndex + getVisibleCount, TListViewBase(Self).Adapter.Count - 1); +end; + +function TListViewBase.getTextSize(const aText: string; aFont: TFont; const aWordWrap: Boolean; aWidth, aHeight: Single): TSizeF; // ZuBy +var + aMaxSize: TPointF; +begin + LVTextLayout.BeginUpdate; + try + LVTextLayout.Text := aText; + + aMaxSize := PointF(aWidth, aHeight); + if aWidth = 0 then + aMaxSize.X := 9999; + if aHeight = 0 then + aMaxSize.Y := 9999; + + LVTextLayout.MaxSize := aMaxSize; + LVTextLayout.Font.Assign(aFont); + LVTextLayout.Font.Size := aFont.Size; + LVTextLayout.WordWrap := aWordWrap; + LVTextLayout.Trimming := TTextTrimming.None; + LVTextLayout.HorizontalAlign := TTextAlign.Leading; + LVTextLayout.VerticalAlign := TTextAlign.Leading; + finally + LVTextLayout.EndUpdate; + end; + + Result.Height := LVTextLayout.TextHeight; + Result.Width := LVTextLayout.TextWidth; +end; + +function TAppearanceListView.getVisibleCount: Integer; // ZuBy +begin + if FHorizontal then + Result := Round(Width / GetItemAppearanceProperties.Height) + else + Result := Round(Height / FItemAppearanceProperties.Height); +end; + +function TListViewBase.GetScrollWidth: Single; // ZuBy +begin + Result := DefaultScrollBarWidth; +end; + +procedure TListViewBase.SetHorizontal(const Value: Boolean); // ZuBy +begin + if FHorizontal <> Value then + begin + FHorizontal := Value; + RebuildOrientation; + UpdateScrollBar; + end; +end; + +procedure TListViewBase.SetAutoColumns(const Value: Boolean); // ZuBy +begin + if FAutoColumns <> Value then + begin + FAutoColumns := Value; + Resize; + end; +end; + +procedure TListViewBase.SetBottomOffset(const Value: Integer); // ZuBy +begin + if FBottomOffset <> Value then + begin + FBottomOffset := Value; + if not FHorizontal then + DoUpdateScrollViewPos(FScrollViewPos + FBottomOffset); // ??? + InvalidateHeights; + UpdateItemLookups; + Invalidate; + end; +end; + +procedure TListViewBase.SetCanScroll(const Value: Boolean); // sinuke +begin + StopPropertyAnimation('ScrollViewPos'); + FCanScroll := Value; +end; + +function TListViewBase.getHeightByIndex(Index: Integer): Integer; // ZuBy +begin + Result := GetItemHeight(index); +end; + +function TListViewBase.getAniCalc: TAniCalculations; // ZuBy +begin + Result := FAniCalc; +end; + +procedure TListViewBase.SetItemBottomOffset(const Value: Integer); +begin + if FItemBottomOffset <> Value then + begin + FItemBottomOffset := Value; + InvalidateHeights; + UpdateItemLookups; + Invalidate; + end; +end; + +procedure TListViewBase.SetSeparatorLeftOffset(const Value: Single); // ZuBy +begin + if FSeparatorLeftOffset <> Value then + begin + FSeparatorLeftOffset := Value; + InvalidateHeights; + UpdateItemLookups; + Invalidate; + end; +end; + +procedure TListViewBase.SetSeparatorRightOffset(const Value: Single); // ZuBy +begin + if FSeparatorRightOffset <> Value then + begin + FSeparatorRightOffset := Value; + InvalidateHeights; + UpdateItemLookups; + Invalidate; + end; +end; + +procedure TListViewBase.SetShowFirstSeparator(const Value: Boolean); +begin + FShowFirstSeparator := Value; +end; + +procedure TListViewBase.SetShowLastSeparator(const Value: Boolean); +begin + FShowLastSeparator := Value; +end; + +procedure TListViewBase.SetShowScrollBar(const Value: Boolean); // ZuBy +begin + if FShowScrollBar <> Value then + begin + FShowScrollBar := Value; + RebuildOrientation; + UpdateScrollBar; + end; +end; + +procedure TListViewBase.SetTopOffset(const Value: Integer); // ZuBy +begin + if FTopOffset <> Value then + begin + FTopOffset := Value; + if not FHorizontal then + DoUpdateScrollViewPos(FScrollViewPos - FTopOffset); + InvalidateHeights; + UpdateItemLookups; + Invalidate; + end; +end; + +procedure TListViewBase.SetTransparentHeader(const Value: Boolean); +begin + FTransparentHeaders := Value; + Invalidate; +end; + +procedure TListViewBase.SetTransparentItems(const Value: Boolean); +begin + FTransparentItems := Value; + Invalidate; +end; + +procedure TListViewBase.SetTransparentSeparator(const Value: Boolean); +begin + FTransparentSeparator := Value; + Invalidate; +end; + +procedure TListViewBase.SetColumnWidth(const Value: Single); // ZuBy +begin + if not SameValue(FColumnWidth, Value, TEpsilon.Position) then + begin + FColumnWidth := Value; + Resize; + end; +end; + +procedure TListViewBase.RebuildOrientation; // ZuBy +begin + if FHorizontal then + begin + FScrollBar.Orientation := TOrientation.Horizontal; + FScrollBar.Align := TAlignLayout.Bottom; + FScrollBar.Height := DefaultScrollBarWidth; + end + else + begin + FScrollBar.Orientation := TOrientation.Vertical; + FScrollBar.Align := TAlignLayout.Right; + FScrollBar.Width := DefaultScrollBarWidth; + end; + Resize; + FScrollBar.Visible := FShowScrollBar; + UpdateItemLookups; +end; + +{$ENDREGION} +{$REGION 'TPresentedListView'} + +procedure TPresentedListView.BeforeDestruction; +var + PresentationService: IFMXListViewPresentationService; +begin + inherited; + if (csDesigning in ComponentState) and + TPlatformServices.Current.SupportsPlatformService(IFMXListViewPresentationService, PresentationService) then + PresentationService.DetachPresentation(Self); +end; + +destructor TPresentedListView.Destroy; +begin + FPresentation := nil; + inherited; +end; + +procedure TPresentedListView.PMAncesstorPresentationLoaded(var AMessage: TDispatchMessageWithValue); +begin + ExecuteInterlocked(procedure begin + FPresentation.ParentChanged; + end); +end; + +procedure TPresentedListView.RecreateNativePresentation; +var + PresentationService: IFMXListViewPresentationService; + LPresentation: IInterface; +begin + if ((FControlType = TControlType.Platform) or (csDesigning in ComponentState)) and + TPlatformServices.Current.SupportsPlatformService(IFMXListViewPresentationService, PresentationService) then + begin + FPresentation := nil; // Make sure that presentation is purged before it's recreated + LPresentation := PresentationService.AttachPresentation(Self); + if Supports(LPresentation, IListViewPresentation, FPresentation) and (FItemIndex <> -1) and not FEditMode then + FPresentation.SetItemSelected(FItemIndex, True); + end + else + FPresentation := nil; +end; + +procedure TPresentedListView.ChangeOrder; +begin + inherited; + ExecuteInterlocked(procedure begin + FPresentation.OrderChanged; + end); +end; + +procedure TPresentedListView.AncestorVisibleChanged; +begin + inherited; + ExecuteInterlocked(procedure begin + FPresentation.AncestorVisibleChanged(Visible); + end); +end; + +procedure TPresentedListView.RecalcOpacity; +begin + inherited; + ExecuteInterlocked(procedure begin + FPresentation.AncestorVisibleChanged(Visible); + end); +end; + +procedure TPresentedListView.Paint; +begin + if FPresentation = nil then + inherited Paint + else + begin + Adapter.CreateNewViews; + UpdateItemLookups; + end; +end; + +procedure TPresentedListView.PaintChildren; +const + LabelMargins = 3; + LabelPadding = 3; + + function GetOverlayIcon: TBitmap; + var + Service: IPresentedControlBehavior; + begin + if TBehaviorServices.Current.SupportsBehaviorService(IPresentedControlBehavior, Service, Self) then + Result := Service.GetOverlayIcon + else + Result := nil; + end; + + procedure PaintDesignTimeCaption; + const + ControlTypeCaption = 'P'; + var + TextWidth: Double; + TextHeight: Double; + TextRect: TRectF; + begin + TextWidth := Canvas.TextWidth(ControlTypeCaption) + 2 * LabelPadding; + TextHeight := Canvas.TextHeight(ControlTypeCaption) + 2 * LabelPadding; + TextRect := TRectF.Create(TPointF.Create(Width - TextWidth - LabelMargins, Height - TextHeight - LabelMargins), + TextWidth, TextHeight); + Canvas.Fill.Color := TAlphaColorRec.Black; + Canvas.FillRect(TextRect, 3, 3, AllCorners, 0.5); + Canvas.Fill.Color := TAlphaColorRec.White; + Canvas.FillText(TextRect, ControlTypeCaption, False, 1, [], TTextAlign.Center, TTextAlign.Center); + end; + + procedure PaintDesignTimeIcon; + var + Icon: TBitmap; + IconRect: TRectF; + DestRect: TRectF; + begin + Icon := GetOverlayIcon; + if Icon <> nil then + begin + IconRect := TRectF.Create(0, 0, Icon.Width, Icon.Height); + DestRect := TRectF.Create(TPointF.Create(Width - LabelPadding - Icon.Width, Height - LabelPadding - Icon.Height), + Icon.Width, Icon.Height); + Canvas.DrawBitmap(Icon, IconRect, DestRect, 0.5); + end + else + PaintDesignTimeCaption; + end; + +begin + inherited; + if (csDesigning in ComponentState) and not Locked and not FInPaintTo and (ControlType = TControlType.Platform) then + begin + Canvas.SetMatrix(AbsoluteMatrix); + PaintDesignTimeIcon; + end; +end; + +procedure TPresentedListView.ParentChanged; +begin + inherited; + ExecuteInterlocked(procedure begin + RecalcAbsolute; // This call is required here because it called later in TControl.DoAddObject + FPresentation.ParentChanged; + end); +end; + +procedure TPresentedListView.RebuildList; +begin + inherited; + if not FCreatingNativeView then + begin + if (FPresentation <> nil) and not (csLoading in ComponentState) then + if IsUpdating then + Include(FStateFlags, TStateFlag.NeedsRebuild) + else + begin + UpdateItemLookups; + FPresentation.ItemsUpdated; + if FItemIndex <> -1 then + FPresentation.SetItemIndex(FItemIndex); + end; + end; +end; + +procedure TPresentedListView.StopPullRefresh; +begin + if FPresentation <> nil then + FPresentation.StopPullRefresh; +end; + +function TPresentedListView.GetRootObject: TObject; +begin + if Root <> nil then + Result := Root.GetObject + else + Result := nil; +end; + +function TPresentedListView.GetAdapter: IListViewAdapter; +begin + Result := Adapter; +end; + +function TPresentedListView.GetContentFrame: TRect; +begin + Result := GetBoundsRect.Round; +end; + +function TPresentedListView.GetControlOpacity: Single; +begin + Result := AbsoluteOpacity; +end; + +function TPresentedListView.GetItemText(const ItemIndex: Integer): string; +var + Provider: IListViewTextProvider; +begin + if Supports(Adapter, IListViewTextProvider, Provider) then + Result := Provider.Text[ItemIndex] + else + Result := string.Empty; +end; + +function TPresentedListView.GetBackgroundStyleColor: TAlphaColor; +begin + Result := FBackgroundStyleColor; +end; + +function TPresentedListView.GetIsTransparent: Boolean; +begin + Result := FTransparent; +end; + +function TPresentedListView.GetOpacity: Single; +begin + Result := FOpacity; +end; + +function TPresentedListView.GetItemIndexTitle(const ItemIndex: Integer): string; +var + Provider: IListViewTextProvider; +begin + Result := string.Empty; + if Supports(Adapter, IListViewTextProvider, Provider) then + Result := Provider.IndexTitle[ItemIndex]; +end; + +function TPresentedListView.CanSelectItem(const AItemIndex: Integer): Boolean; +begin + Result := True; + if ItemIndex <> AItemIndex then + ObserversBeforeSelection(Result); +end; + +procedure TPresentedListView.DidSelectItem(const AItemIndex: Integer); +begin + ExecuteInterlocked(procedure begin + inherited SelectItem(AItemIndex); + end); + TLinkObservers.ListSelectionChanged(Observers); +end; + +function TPresentedListView.CanUnselectItem(const AItemIndex: Integer): Boolean; +begin + ObserversBeforeSelection(Result); +end; + +procedure TPresentedListView.DidUnselectItem(const AItemIndex: Integer); +begin + ExecuteInterlocked(procedure begin + inherited UnselectItem(AItemIndex); + end); + TLinkObservers.ListSelectionChanged(Observers); +end; + +procedure TPresentedListView.DoDeleteItem(const ItemIndex: Integer); +begin + inherited; + if FPresentation <> nil then + FPresentation.ItemsUpdated; +end; + +procedure TPresentedListView.DoEditModeChange; +begin + inherited; + if FPresentation <> nil then + FPresentation.EditModeChanged; +end; + +procedure TPresentedListView.DoResetEditModeAnimation; +begin + inherited; + if FPresentation <> nil then + FPresentation.EditModeChanged; +end; + +procedure TPresentedListView.DoItemInvalidated(const Item: TListItem); +begin + inherited; + if FPresentation <> nil then + FPresentation.ItemInvalidated(Item); +end; + +procedure TPresentedListView.DoItemsChange; +begin + InvalidateHeights; + inherited; + if FPresentation = nil then + Invalidate; +end; + +procedure TPresentedListView.DoItemsInvalidate; +begin + inherited; + if not FCreatingNativeView then + begin + if IsUpdating then + Include(FStateFlags, TStateFlag.NeedsRebuild) + else + ExecuteInterlocked(procedure begin + UpdateItemLookups; + FPresentation.ItemsUpdated; + end); + end; +end; + +procedure TPresentedListView.DoCheckStateChanged(const AItem: TListItem; const Control: TListItemDrawable); +var + Checkable: IListViewCheckProvider; +begin + if Supports(Adapter, IListViewCheckProvider, Checkable) then + begin + ExecuteInterlocked(procedure begin + FPresentation.SetItemSelected(AItem.Index, Checkable[AItem.Index]); + end); + + if FSelectionCrossfade and (FPresentation = nil) then + InsertItemCrossFade(AItem.Index, Checkable[AItem.Index]); + end; + inherited; +end; + +procedure TPresentedListView.DoSetItemIndexInternal(const Value: Integer); +begin + inherited; + ExecuteInterlocked(procedure begin + FPresentation.SetItemIndex(FItemIndex); + end); +end; + +procedure TPresentedListView.DoSetScrollViewPos(const Value: Single); +begin + inherited; + if FPresentation = nil then + begin + if FAniCalc <> nil then + begin + // ZuBy *** + if FHorizontal then + FAniCalc.ViewportPosition := TPointD.Create(FScrollViewPos, 0) + else + FAniCalc.ViewportPosition := TPointD.Create(0, FScrollViewPos); + // *** ZuBy + end; + if not HasTouchTracking then + UpdateScrollBar; + end; +end; + +procedure TPresentedListView.DoUpdateScrollingLimits; +begin + if FPresentation = nil then + inherited; +end; + +procedure TPresentedListView.DoUpdateScrollViewPos(const Value: Single); +begin + if FPresentation = nil then + Invalidate; +end; + +procedure TPresentedListView.ItemButtonClicked(const ItemIndex: Integer); +var + Provider: IListViewTextButtonProvider; +begin + if Supports(Adapter, IListViewTextButtonProvider, Provider) and (Provider.TextButtonDrawable[ItemIndex] <> nil) then + Provider.TextButtonDrawable[ItemIndex].Click; +end; + +procedure TPresentedListView.InvokePullRefresh; +begin + TThread.Queue(nil, + procedure + begin + if Assigned(FOnPullRefresh) then + FOnPullRefresh(Self); + end); +end; + +function TPresentedListView.HasDesignPresentationAttached: Boolean; +begin + Result := (csDesigning in ComponentState) and (FPresentation <> nil); +end; + +procedure TPresentedListView.SetCreatingNativeView(const Value: Boolean); +begin + FCreatingNativeView := Value; +end; + +procedure TPresentedListView.SetSearchFilter(const Filter: string); +var + LText: string; +begin + LText := Filter.Trim.ToLower; + SetFilterPredicate( + function (X: string): Boolean + begin + Result := LText.IsEmpty or X.ToLower.Contains(LText); + end); +end; + +function TPresentedListView.GetTableViewFlags: TListViewModeFlags; +begin + Result := []; + if FEditMode then + Include(Result, TListViewModeFlag.Edit); + if Enabled then + Include(Result, TListViewModeFlag.Enabled); + if Visible then + Include(Result, TListViewModeFlag.Visible); + if HasDeletionEditMode then + Include(Result, TListViewModeFlag.Deletion); + if FPullToRefresh then + Include(Result, TListViewModeFlag.PullRefresh); + if HasButtonsInCells then + Include(Result, TListViewModeFlag.Buttons); + if FSearchVisible then + Include(Result, TListViewModeFlag.Search); + if FSearchAlwaysOnTop then + Include(Result, TListViewModeFlag.SearchOnTop); + if FPullRefreshWait then + Include(Result, TListViewModeFlag.PullRefreshWait); + if FCanSwipeDelete then + Include(Result, TListViewModeFlag.SwipeDelete); +end; + +function TPresentedListView.GetTableViewOptions: TListViewNativeOptions; +begin + Result := FNativeOptions; +end; + +procedure TPresentedListView.RecalcEnabled; +begin + inherited; + if FPresentation <> nil then + FPresentation.StatusChanged; +end; + +function TPresentedListView.ShouldHandleEvents: Boolean; +begin + Result := FPresentation = nil; +end; + +procedure TPresentedListView.Show; +begin + inherited; + if FPresentation <> nil then + FPresentation.StatusChanged; +end; + +procedure TPresentedListView.Hide; +begin + inherited; + if FPresentation <> nil then + FPresentation.StatusChanged; +end; + +procedure TPresentedListView.Resize; +begin + inherited; + + if FPresentation <> nil then + FPresentation.SizeChanged + else if Adapter.Count > 0 then + FScrollViewPos := Min(FScrollViewPos, GetMaxScrollViewPos); +end; + +procedure TPresentedListView.DoItemsResize; +begin + inherited; + if IsUpdating then + Include(FStateFlags, TStateFlag.NeedsRebuild) + else + ExecuteInterlocked(procedure begin + FPresentation.ItemsUpdated; + end); +end; + +procedure TPresentedListView.DoAbsoluteChanged; +begin + inherited; + if FPresentation <> nil then + FPresentation.ParentChanged; +end; + +procedure TPresentedListView.ExecuteInterlocked(const P: TProc); +begin + if (FPresentation <> nil) and (TInterlocked.CompareExchange(FPresentationLocked, 1, 0) = 0) then + try + P; + finally + TInterlocked.Exchange(FPresentationLocked, 0); + end; +end; + +{$ENDREGION} +{$REGION 'TAppearanceListView'} + +constructor TAppearanceListView.Create(AOwner: TComponent); +begin + inherited; + + FAppearanceProperties := TPublishedAppearance.Create(Self); + FItemAppearanceObjects := TPublishedObjects.Create(Self); + + FItemAppearanceProperties := TItemAppearanceProperties.Create(Self, TAppearanceType.Item); + InitializeItemAppearance(FItemAppearanceProperties); + FItemEditAppearanceProperties := TItemAppearanceProperties.Create(Self, TAppearanceType.ItemEdit); + InitializeItemAppearance(FItemEditAppearanceProperties); + FHeaderAppearanceProperties := TItemAppearanceProperties.Create(Self, TAppearanceType.Header); + InitializeItemAppearance(FHeaderAppearanceProperties); + FFooterAppearanceProperties := TItemAppearanceProperties.Create(Self, TAppearanceType.Footer); + InitializeItemAppearance(FFooterAppearanceProperties); + + // Create our own adapter + Items := TAppearanceListViewItems.Create(Self); + + FTransparentSeparator := False; + FTransparentItems := False; + FAutoPositionToItem := False; + FTopItemIndex := -1; // ZuBy + FSeparatorLeftOffset := 0; // ZuBy + FSeparatorRightOffset := 0; // ZuBy + FMakeSelectedItemVisible := True; // ZuBy + FHorizontal := False; // ZuBy + FShowScrollBar := True; // ZuBy +end; + +destructor TAppearanceListView.Destroy; +begin + FAppearanceProperties.Free; + FreeAndNil(FItemAppearanceObjects); + FItemAppearanceProperties.Free; + FItemEditAppearanceProperties.Free; + FHeaderAppearanceProperties.Free; + FFooterAppearanceProperties.Free; + SetAdapter(nil); + FAppearanceViewItems.Free; + inherited; +end; + +procedure TAppearanceListView.SetAppearanceListViewItems(const AItems: TAppearanceListViewItems); +begin + FAppearanceViewItems := AItems; + FAppearanceViewItems.OnNotify := ObjectsNotify; + SetAdapter(AItems as IListViewAdapter); +end; + +procedure TAppearanceListView.DoAdapterSet; +var + Editor: IListViewEditor; +begin + if FAppearanceViewItems = nil then + raise EListViewError.Create(SUseItemsPropertyToSetAdapter); + if Adapter <> nil then + begin + if Supports(Adapter, IListViewEditor, Editor) then + begin + Editor.OnBeforeItemAdded := EditorBeforeItemAdded; + Editor.OnAfterItemAdded := EditorAfterItemAdded; + Editor.OnBeforeItemDeleted := EditorBeforeItemDeleted; + Editor.OnAfterItemDeleted := EditorAfterItemDeleted; + end; + + if Adapter.Count > 0 then + begin + ItemAppearanceChange(FItemAppearanceProperties); + ItemAppearanceChange(FItemEditAppearanceProperties); + ItemAppearanceChange(FHeaderAppearanceProperties); + ItemAppearanceChange(FFooterAppearanceProperties); + end; + end; + DoChange; +end; + +function TAppearanceListView.GetAppearanceListViewItem(const Index: Integer): TListViewItem; +begin + Result := FAppearanceViewItems.AppearanceItem[Index]; +end; + +function TAppearanceListView.GetHeaderAppearanceName: string; +begin + Result := FHeaderAppearanceProperties.Name; +end; + +function TAppearanceListView.GetHeaderAppearanceProperties: TItemAppearanceProperties; +begin + Result := FHeaderAppearanceProperties; +end; + +function TAppearanceListView.GetHeaderAppearanceClassName: string; +begin + Result := FHeaderAppearanceProperties.AppearanceClassName; +end; + +function TAppearanceListView.GetFooterAppearanceName: string; +begin + Result := FFooterAppearanceProperties.Name; +end; + +function TAppearanceListView.GetFooterAppearanceProperties: TItemAppearanceProperties; +begin + Result := FFooterAppearanceProperties; +end; + +function TAppearanceListView.GetFooterAppearanceClassName: string; +begin + Result := FFooterAppearanceProperties.AppearanceClassName; +end; + +function TAppearanceListView.GetItemAppearanceName: string; +begin + Result := FItemAppearanceProperties.Name; +end; + +function TAppearanceListView.GetItemAppearanceObjects: TPublishedObjects; +begin + Result := FItemAppearanceObjects; +end; + +function TAppearanceListView.GetItemEditAppearanceName: string; +begin + Result := FItemEditAppearanceProperties.Name; +end; + +function TAppearanceListView.GetItemAppearanceProperties: TItemAppearanceProperties; +begin + Result := FItemAppearanceProperties; +end; + +function TAppearanceListView.GetItemEditAppearanceProperties: TItemAppearanceProperties; +begin + Result := FItemEditAppearanceProperties; +end; + +function TAppearanceListView.GetItemObjectsClassName: string; +begin + Result := FItemAppearanceProperties.AppearanceClassName; +end; + +function TAppearanceListView.HasButtonsInCells: Boolean; +begin + Result := ItemAppearance.ItemAppearance = TAppearanceNames.ImageListItemRightButton; +end; + +function TAppearanceListView.HasCheckboxMode: Boolean; +begin + Result := FEditMode and (FAppearanceAllowsCheckboxes or ((not IsDeleteModeAllowed) and HasDeletionEditMode)); +end; + +function TAppearanceListView.HasDeletionEditMode: Boolean; +begin + Result := FAppearanceAllowsDeleteMode; +end; + +procedure TAppearanceListView.SetItemHeight(const Value: Integer); +begin + FItemAppearanceProperties.Height := Value; +end; + +procedure TAppearanceListView.SetItemEditHeight(const Value: Integer); +begin + FItemEditAppearanceProperties.Height := Value; +end; + +procedure TAppearanceListView.SetHeaderHeight(const Value: Integer); +begin + FHeaderAppearanceProperties.Height := Value; +end; + +procedure TAppearanceListView.SetFooterHeight(const Value: Integer); +begin + FFooterAppearanceProperties.Height := Value; +end; + +function TAppearanceListView.GetItemEditObjectsClassName: string; +begin + Result := FItemEditAppearanceProperties.AppearanceClassName; +end; + +procedure TAppearanceListView.SetFooterAppearanceClassName(const Value: string); +begin + FFooterAppearanceProperties.AppearanceClassName := Value; +end; + +procedure TAppearanceListView.SetAppearanceProperties(const Value: TPublishedAppearance); +begin + Assert(False); + // Do nothing +end; + +procedure TAppearanceListView.SetItemAppearanceObjects(const Value: TPublishedObjects); +begin + Assert(False); + // Do nothing +end; + +procedure TAppearanceListView.SetItemObjectsClassName(const Value: string); +begin + FItemAppearanceProperties.AppearanceClassName := Value; +end; + +procedure TAppearanceListView.SetItemEditObjectsClassName(const Value: string); +begin + FItemEditAppearanceProperties.AppearanceClassName := Value; +end; + +procedure TAppearanceListView.SetHeaderAppearanceName(const Value: string); +begin + if FHeaderAppearanceProperties.Name <> Value then + begin + FHeaderAppearanceProperties.Name := Value; + RecreateNativePresentation; + end; +end; + +procedure TAppearanceListView.SetHeaderAppearanceClassName(const Value: string); +begin + FHeaderAppearanceProperties.AppearanceClassName := Value; +end; + +procedure TAppearanceListView.SetFooterAppearanceName(const Value: string); +begin + if FFooterAppearanceProperties.Name <> Value then + begin + FFooterAppearanceProperties.Name := Value; + RecreateNativePresentation; + end; +end; + +function TAppearanceListView.GetAppearanceProperties: TArray; +begin + SetLength(Result, 4); + Result[0] := FItemEditAppearanceProperties; + Result[1] := FItemAppearanceProperties; + Result[2] := FHeaderAppearanceProperties; + Result[3] := FFooterAppearanceProperties; +end; + +procedure TAppearanceListView.SetItemAppearanceName(const Value: string); +begin + if FItemAppearanceProperties.Name <> Value then + begin + FItemAppearanceProperties.Name := Value; + RecreateNativePresentation; + end; +end; + +procedure TAppearanceListView.SetItemEditAppearanceName(const Value: string); + + function AllowsCheckboxes(const Name: string): Boolean; + begin + Result := (Name = TAppearanceNames.ListItemRightDetailShowCheck) or + (Name = TAppearanceNames.ImageListItemShowCheck) or + (Name = TAppearanceNames.ImageListItemRightButtonShowCheck) or + (Name = TAppearanceNames.ListItemShowCheck) or + (Name = TAppearanceNames.ImageListItemBottomDetailShowCheck) or + (Name = TAppearanceNames.ImageListItemBottomDetailRightButtonShowCheck); + end; + + function AllowsDeleteMode(const Name: string): Boolean; + begin + Result := (Name = TAppearanceNames.ListItemDelete) or + (Name = TAppearanceNames.ListItemRightDetailDelete) or + (Name = TAppearanceNames.ImageListItemDelete) or + (Name = TAppearanceNames.ImageListItemRightButtonDelete); + end; + +var + Checkable: IListViewCheckProvider; +begin + if FItemEditAppearanceProperties.Name <> Value then + begin + FItemEditAppearanceProperties.Name := Value; + SetDeleteButtonIndex(-1); + FAppearanceAllowsCheckboxes := AllowsCheckboxes(Value); + FAppearanceAllowsDeleteMode := AllowsDeleteMode(Value); + if Supports(Adapter, IListViewCheckProvider, Checkable) then + begin + Checkable.CheckAll(False); + // It's important to reset link on IListViewCheckProvider, because RecreateNativePresentation can recreate Adapter + // and as result release IListViewCheckProvider. + Checkable := nil; + end; + RecreateNativePresentation; + end; +end; + +procedure TAppearanceListView.InitializeItemAppearance(const AAppearance: TItemAppearanceProperties); +begin + AAppearance.AppearanceClass := TNullItemObjects; +end; + +procedure TAppearanceListView.ItemAppearanceChange(const Sender: TItemAppearanceProperties); +var + Purposes: set of TListItemPurpose; +begin + if TStateFlag.ResettingObjects in FStateFlags then + exit; + if Sender = nil then + Purposes := [Low(TListItemPurpose)..High(TListItemPurpose)] + else + Purposes := [Sender.Purpose]; + if FUpdatingAppearance = 0 then + AppearanceResetObjects(Purposes) + else + FChangedAppearanceObjects := FChangedAppearanceObjects + Purposes; +end; + +procedure TAppearanceListView.ItemAppearanceChangeObjects(const Sender: TItemAppearanceProperties); +begin + ItemAppearanceChange(Sender); +end; + +procedure TAppearanceListView.ObjectsNotify(Sender: TObject; const Item: TListItem; Action: TCollectionNotification); +begin + if Action = TCollectionNotification.cnRemoved then + begin + FItemSelectedBeforeChange := nil; + FItemSelectedBeforeEdit := nil; + end; +end; + +procedure TAppearanceListView.AppearanceResetObjects(APurposes: TListItemPurposes); +var + LPurposes: TListItemPurposes; + LProperties: TItemAppearanceProperties; +begin + if Adapter <> nil then + begin + LPurposes := []; + for LProperties in GetAppearanceProperties do + if LProperties.Active then + Include(LPurposes, LProperties.Purpose); + LPurposes := LPurposes * APurposes; + if LPurposes <> [] then + begin + Adapter.ResetViews(LPurposes); + InvalidateHeights; + UpdateScrollingLimits; + Invalidate; + end; + end; +end; + +procedure TAppearanceListView.ApplyStyle; +begin + inherited; + UpdateAppearanceStyleResources; +end; + +procedure TAppearanceListView.AppearanceResetHeights(APurposes: TListItemPurposes); +var + LPurposes: TListItemPurposes; + LProperties: TItemAppearanceProperties; +begin + if Adapter <> nil then + begin + LPurposes := []; + for LProperties in GetAppearanceProperties do + if LProperties.Active then + Include(LPurposes, LProperties.Purpose); + LPurposes := LPurposes * APurposes; + if LPurposes <> [] then + begin + InvalidateHeights; + UpdateScrollingLimits; + Invalidate; + end; + end; +end; + +procedure TAppearanceListView.DoResetView(const Item: TListItem); +begin + inherited; + ResetViewAppearance(TListViewItem(Item)); +end; + +procedure TAppearanceListView.ResetViewAppearance(const AItem: TListViewItem); +var + LHandled: Boolean; + LItemObjects: TItemAppearanceObjects; + LPrevItemIndex: Integer; + LDoUpdatingItemViewP: procedure (const AListItem: TListItem; var AHandled: Boolean) of object; + LDoUpdateItemViewP: procedure (const AListItem: TListItem) of object; + LSync: Boolean; + + function IsIndexValid(AIndex: Integer): Boolean; + begin + Result := (AIndex >= 0) and (AIndex < Adapter.Count); + end; + + function GetSelectableIndex(AIndex: Integer): Integer; + var + LItem: TListItem; + begin + Result := AIndex; + if not IsIndexValid(AIndex) then + Exit; + LItem := Adapter[AIndex]; + case LItem.Purpose of + TListItemPurpose.Header: + while (Result < Adapter.Count) and (Adapter[Result].Purpose <> TListItemPurpose.None) do + Inc(Result); + TListItemPurpose.Footer: + while (Result >= 0) and (Adapter[Result].Purpose <> TListItemPurpose.None) do + Dec(Result); + end; + end; + +begin + if TStateFlag.ResettingObjects in FStateFlags then + Exit; + Include(FStateFlags, TStateFlag.ResettingObjects); + try + LItemObjects := nil; + case AItem.Purpose of + TListItemPurpose.None: + if FItemEditAppearanceProperties.Active then + LItemObjects := FItemEditAppearanceProperties.Objects + else if FItemAppearanceProperties.Active then + LItemObjects := FItemAppearanceProperties.Objects; + TListItemPurpose.Header: + if FHeaderAppearanceProperties.Active then + LItemObjects := FHeaderAppearanceProperties.Objects; + TListItemPurpose.Footer: + if FFooterAppearanceProperties.Active then + LItemObjects := FFooterAppearanceProperties.Objects; + else + Assert(False); + end; + if (LItemObjects <> nil) and not (LItemObjects is TNullItemObjects) then + begin + LHandled := False; + LPrevItemIndex := FItemIndex; + LDoUpdatingItemViewP := DoUpdatingItemView; + LDoUpdateItemViewP := DoUpdateItemView; + LSync := IsIndexValid(AItem.Index) and Observers.IsObserving(TObserverMapping.PositionLinkID) and ( + (TMethod(LDoUpdatingItemViewP).Code <> @TAppearanceListView.DoUpdatingItemView) or + (TMethod(LDoUpdateItemViewP).Code <> @TAppearanceListView.DoUpdateItemView) or + Assigned(OnUpdatingItemView) or Assigned(OnUpdatingObjects) or + Assigned(OnUpdateItemView) or Assigned(OnUpdateObjects)); + try + if LSync then + begin + FItemIndex := GetSelectableIndex(AItem.Index); + TLinkObservers.PositionLinkPosChanged(Observers); + end; + DoUpdatingItemView(AItem, LHandled); + if not LHandled then + LItemObjects.ResetObjects(AItem, GetFinalItemSize); + DoUpdateItemView(AItem); + finally + if LSync then + begin + FItemIndex := LPrevItemIndex; + TLinkObservers.PositionLinkPosChanged(Observers); + end; + end; + end; + finally + Exclude(FStateFlags, TStateFlag.ResettingObjects); + end; +end; + +procedure TAppearanceListView.RefreshAppearances(const APurposes: TListItemPurposes); +begin + Adapter.ResetViews(APurposes); + InvalidateHeights; // Object heights may have changed +end; + +procedure TAppearanceListView.UpdateAppearanceStyleResources; +begin + Assert(not FUpdatingStyleResources); + TNonReentrantHelper.Execute(FUpdatingStyleResources, + procedure begin + RefreshAppearances; + end); +end; + +procedure TAppearanceListView.WillEnterEditMode(const Animated: Boolean); +begin + if Animated then + EditModeAppearances + else + RefreshAppearances([TListItemPurpose.None]); +end; + +procedure TAppearanceListView.ItemAppearanceChangeHeight(const Sender: TItemAppearanceProperties); +begin + Assert(Sender <> nil); + if Sender <> nil then + if FUpdatingAppearance = 0 then + AppearanceResetHeights([Sender.Purpose]) + else + Include(FChangedAppearanceHeights, Sender.Purpose); +end; + +procedure TAppearanceListView.DoResetEditModeAnimation; +begin + inherited; +end; + +procedure TAppearanceListView.BeginUpdate; +begin + inherited; + Inc(FUpdatingAppearance); + if FUpdatingAppearance = 1 then + begin + FChangedAppearanceObjects := []; + FChangedAppearanceHeights := []; + end; + if FAppearanceViewItems <> nil then + FAppearanceViewItems.BeginUpdate; +end; + +procedure TAppearanceListView.EndUpdate; +begin + inherited; + if FUpdatingAppearance > 0 then + begin + Dec(FUpdatingAppearance); + if FUpdatingAppearance = 0 then + begin + AppearanceResetObjects(FChangedAppearanceObjects); + AppearanceResetHeights(FChangedAppearanceHeights - FChangedAppearanceObjects); + end; + end; + if FAppearanceViewItems <> nil then + FAppearanceViewItems.EndUpdate; +end; + +procedure TAppearanceListView.EditModeAppearances; + function CanResetObjects: Boolean; + var + LProperties: TItemAppearanceProperties; + begin + Result := False; + for LProperties in GetAppearanceProperties do + if LProperties.Active and not (LProperties.Objects is TNullItemObjects) then + exit(True); + end; + +var + Filter: IListViewFilterable; + Item: TListItem; +begin + if Supports(Adapter, IListViewFilterable, Filter) and CanResetObjects then + begin + BeginUpdate; + try + for Item in Filter.UnfilteredItems do + if Item is TListViewItem and (Item.Purpose = TListItemPurpose.None) then + Item.View.Initialized := False; + finally + EndUpdate; + end; + end; +end; + +function TAppearanceListView.GetItemHeight(const Index: Integer): Integer; +var + Item: TListItem; +begin + if (Index < 0) or (Index >= Adapter.Count) then + exit(0); + Item := Adapter[Index]; + Result := Item.Height; + if Result < 1 then + case Item.Purpose of + TListItemPurpose.None: + if EditMode and FItemEditAppearanceProperties.Active then + Result := ItemEditHeight + else + Result := ItemHeight; + TListItemPurpose.Header: + Result := HeaderHeight; + TListItemPurpose.Footer: + Result := FooterHeight; + else + Assert(False); + end; +end; + +function TAppearanceListView.GetItemHeight: Integer; +begin + Result := FItemAppearanceProperties.Height; +end; + +function TAppearanceListView.GetItemEditHeight: Integer; +begin + Result := FItemEditAppearanceProperties.Height; +end; + +function TAppearanceListView.GetHeaderHeight: Integer; +begin + Result := FHeaderAppearanceProperties.Height; +end; + +function TAppearanceListView.GetFooterHeight: Integer; +begin + Result := FFooterAppearanceProperties.Height; +end; + +procedure TAppearanceListView.Resize; +begin + inherited; +end; + +procedure TAppearanceListView.DoRequestReindexing(const Item: TListItem); +begin + FAppearanceViewItems.ReindexAndFindItem(TListViewItem(Item)); +end; + +procedure TAppearanceListView.DoCheckStateChanged(const AItem: TListItem; const Control: TListItemDrawable); +begin + inherited; + if Assigned(FOnButtonChange) and (Control is TListItemSimpleControl) then + FOnButtonChange(Self, AItem, TListItemSimpleControl(Control)); +end; + +procedure TAppearanceListView.DoControlClicked(const Item: TListItem; const Control: TListItemDrawable); +begin + inherited; + if Control is TListItemSimpleControl then + begin + if Assigned(FOnButtonClick) then + FOnButtonClick(Self, Item, TListItemSimpleControl(Control)); + FClickEventControl := Control; + + if not FMouseClickSwipeEventSend then // ZuBy + StartIncident(TDelayedIncident.ClickEvent); + end; +end; + +procedure TAppearanceListView.DoItemResized(const Item: TListItem); +begin + FAppearanceViewItems.ItemsResize; +end; + +procedure TAppearanceListView.DoListItemClick(const AItem: TListItem); +begin + inherited; + if Assigned(FOnItemClick) and (AItem is TListViewItem) and + (not FMouseClickSwipeEventSend) then + FOnItemClick(Self, TListViewItem(AItem)); +end; + +procedure TAppearanceListView.DoUpdateItemView(const AListItem: TListItem); +begin + inherited; + if Assigned(FOnUpdateObjects) and (AListItem is TListViewItem) then + FOnUpdateObjects(Self, TListViewItem(AListItem)); +end; + +procedure TAppearanceListView.DoUpdatingItemView(const AListItem: TListItem; var AHandled: Boolean); +begin + inherited; + if Assigned(FOnUpdatingObjects) and (AListItem is TListViewItem) then + FOnUpdatingObjects(Self, TListViewItem(AListItem), AHandled); +end; + +function TAppearanceListView.getFirstVisibleItemIndex: Integer; // ZuBy +begin + Result := FTopItemIndex; +end; + +procedure TAppearanceListView.SetColorBackground(aColor: TAlphaColor); // ZuBy +begin + FBackgroundStyleColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorButtonText(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.ButtonNormalStyleImage.Normal := nil; + FStyleResources.ButtonNormalStyleImage.Pressed := nil; + FStyleResources.ButtonTextColor := aColor; + FItemAppearanceObjects.ItemObjects.TextButton.TextColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorButtonTextPressed(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.ButtonTextPressedColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorDeleteText(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.DeleteButtonTextColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorDeleteTextPressed(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.DeleteButtonTextPressedColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorDeleteTintColor(aColor: TAlphaColor); // ZuBy +begin + FDeleteButton.TintColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorHeader(aColor: TAlphaColor); // ZuBy +begin + FHeaderStyleImage := nil; + FHeaderStyleColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorItemFill(aColor: TAlphaColor); // ZuBy +begin + FItemStyleFillColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorItemFillAlt(aColor: TAlphaColor); // ZuBy +begin + FItemStyleFillAltColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorItemSelected(aColor: TAlphaColor); // ZuBy +begin + FSelectionStyleImage := nil; + FSelectionStyleColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorItemSeparator(aColor: TAlphaColor); // ZuBy +begin + FItemStyleFrameColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorText(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.DefaultTextColor := aColor; + ItemAppearanceObjects.ItemObjects.Text.TextColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorTextDetail(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.DetailTextColor := aColor; + ItemAppearanceObjects.ItemObjects.Detail.TextColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorTextHeader(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.HeaderTextColor := aColor; + ItemAppearanceObjects.HeaderObjects.Text.TextColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorTextHeaderShadow(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.HeaderTextShadowColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorTextSelected(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.DefaultTextSelectedColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.ItemsClearTrue; // ZuBy +begin + ScrollViewPos := 0; + while Items.Count > 0 do + Items.Delete(0); +end; + +function TAppearanceListView.IsCustomColorUsed(const ItemIndex: Integer): Boolean; // ZuBy +begin + Result := Items[ItemIndex].Data['aUseCustomColor'].AsBoolean; +end; + + +procedure TAppearanceListView.SetCustomColorForItem(const ItemIndex: Integer; const aColor: TAlphaColor); // ZuBy +begin + with Items[ItemIndex] do + begin + Data['aUseCustomColor'] := True; + Data['aCustomColor'] := aColor; + end; + Repaint; +end; + +procedure TAppearanceListView.SetDefaultColorForItem(const ItemIndex: Integer); // ZuBy +begin + Items[ItemIndex].Data['aUseCustomColor'] := False; + Repaint; +end; + +procedure TAppearanceListView.SetColorPullRefresh(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.PullRefreshStrokeColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorPullRefreshIndicator(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.PullRefreshIndicatorColor := aColor; + Repaint; +end; + +procedure TAppearanceListView.SetColorStretchGlow(aColor: TAlphaColor); // ZuBy +begin + FStyleResources.ScrollingStretchGlowColor := aColor; + Repaint; +end; + +{$ENDREGION} +{$REGION 'TListView'} +procedure TListView.InitializeItemAppearance(const AAppearance: TItemAppearanceProperties); +begin + case AAppearance.AppearanceType of + TAppearanceType.Item: + AAppearance.AppearanceClass := TAppearancesRegistry.FindItemAppearanceObjectsClassByOption( + TRegisterAppearanceOption.DefaultItem); + TAppearanceType.ItemEdit: + AAppearance.AppearanceClass := TAppearancesRegistry.FindItemAppearanceObjectsClassByOption( + TRegisterAppearanceOption.DefaultItemEdit); + TAppearanceType.Header: + AAppearance.AppearanceClass := TAppearancesRegistry.FindItemAppearanceObjectsClassByOption( + TRegisterAppearanceOption.DefaultHeader); + TAppearanceType.Footer: + AAppearance.AppearanceClass := TAppearancesRegistry.FindItemAppearanceObjectsClassByOption( + TRegisterAppearanceOption.DefaultFooter); + else + Assert(False); + end; +end; + +procedure TAppearanceListView.EditorBeforeItemAdded(Sender: IListViewEditor); +begin + FItemSelectedBeforeEdit := Selected; +end; + +procedure TAppearanceListView.EditorAfterItemAdded(Sender: IListViewEditor; const Item: TListItem); +begin + if (FItemSelectedBeforeEdit <> nil) and (ItemIndex <> FItemSelectedBeforeEdit.Index) then + SetItemIndexInternal(FItemSelectedBeforeEdit.Index, True, True); + FItemSelectedBeforeEdit := nil; +end; + +procedure TAppearanceListView.EditorBeforeItemDeleted(Sender: IListViewEditor; const Index: Integer); +begin + if ItemIndex >= Adapter.Count - 1 then + ItemIndex := -1; + RemoveItemCrossFade(Index); + FItemSelectedBeforeEdit := Selected; + if (FItemSelectedBeforeEdit <> nil) and (FItemSelectedBeforeEdit.Index = Index) then + FItemSelectedBeforeEdit := nil; +end; + +procedure TAppearanceListView.EditorAfterItemDeleted(Sender: IListViewEditor); +begin + if (FItemSelectedBeforeEdit <> nil) and (ItemIndex <> FItemSelectedBeforeEdit.Index) then + SetItemIndexInternal(FItemSelectedBeforeEdit.Index, True, True); + FItemSelectedBeforeEdit := nil; +end; + +{$ENDREGION} +{$REGION 'TAdapterListView'} + +procedure TAdapterListView.SetAdapter(Adapter: IListViewAdapter); +begin + FAdapter := Adapter; + HeightSumsNeedUpdate := True; + if Adapter <> nil then + begin + FAdapter.OnItemsMayChange := ItemsMayChange; + FAdapter.OnItemsCouldHaveChanged := ItemsCouldHaveChanged; + FAdapter.OnChanged := ItemsChange; + FAdapter.OnItemsInvalidate := ItemsInvalidate; + FAdapter.OnItemsResize := ItemsResize; + FAdapter.OnResetView := ResetView; + DoAdapterSet; + end; +end; + +procedure TAdapterListView.ItemsChange(Sender: TObject); +begin + DoItemsChange; +end; + +procedure TAdapterListView.ItemsCouldHaveChanged(Sender: TObject); +begin + DoItemsCouldHaveChanged; +end; + +procedure TAdapterListView.ItemsMayChange(Sender: TObject); +begin + DoItemsMayChange; +end; + +procedure TAdapterListView.ItemsInvalidate(Sender: TObject); +begin + DoItemsInvalidate; +end; + +procedure TAdapterListView.ResetView(Sender: TObject); +begin + DoResetView(Sender as TListItem); +end; + +procedure TAdapterListView.ItemsResize(Sender: TObject); +begin + DoItemsResize; +end; + +procedure TAdapterListView.DoAdapterSet; +begin +end; + +procedure TAdapterListView.DoItemsChange; +begin +end; + +procedure TAdapterListView.DoItemsCouldHaveChanged; +begin +end; + +procedure TAdapterListView.DoItemsMayChange; +begin +end; + +procedure TAdapterListView.DoItemsInvalidate; +begin +end; + +procedure TAdapterListView.DoResetView(const Sender: TListItem); +begin +end; + +procedure TAdapterListView.DoItemsResize; +begin + InvalidateHeights; +end; + +procedure TAdapterListView.InvalidateHeights; +begin + FHeightSumsNeedUpdate := True; +end; +{$ENDREGION} + +initialization + + LVTextLayout := TTextLayoutManager.DefaultTextLayout.Create; + RegisterFmxClasses([TCustomListView, TListView]); +finalization + LVTextLayout.Free; +end.