@@ -0,0 +1,252 @@
using System.Collections.Generic ;
using UnityEngine ;
using UnityEngine.EventSystems ;
using UnityEngine.UI ;
namespace SimpleFileBrowser
{
[RequireComponent( typeof( ScrollRect ) )]
public class RecycledListView : MonoBehaviour
#if UNITY_EDITOR | | UNITY_STANDALONE | | UNITY_WSA | | UNITY_WSA_10_0
, IPointerClickHandler
#endif
{
#pragma warning disable 0649
#if UNITY_EDITOR | | UNITY_STANDALONE | | UNITY_WSA | | UNITY_WSA_10_0
[SerializeField]
private FileBrowser fileBrowser ;
#endif
// Cached components
[SerializeField]
private RectTransform viewportTransform ;
[SerializeField]
private RectTransform contentTransform ;
#pragma warning restore 0649
private float itemHeight , _1OverItemHeight ;
private float viewportHeight ;
private readonly Dictionary < int , ListItem > items = new Dictionary < int , ListItem > ( ) ;
private readonly Stack < ListItem > pooledItems = new Stack < ListItem > ( ) ;
IListViewAdapter adapter = null ;
// Current indices of items shown on screen
private int currentTopIndex = - 1 , currentBottomIndex = - 1 ;
private void Start ( )
{
viewportHeight = viewportTransform . rect . height ;
GetComponent < ScrollRect > ( ) . onValueChanged . AddListener ( ( pos ) = > UpdateItemsInTheList ( ) ) ;
}
public void SetAdapter ( IListViewAdapter adapter )
{
this . adapter = adapter ;
itemHeight = adapter . ItemHeight ;
_1OverItemHeight = 1f / itemHeight ;
}
public void OnSkinRefreshed ( )
{
if ( currentTopIndex > = 0 )
{
DestroyItemsBetweenIndices ( currentTopIndex , currentBottomIndex ) ;
currentTopIndex = currentBottomIndex = - 1 ;
}
itemHeight = adapter . ItemHeight ;
_1OverItemHeight = 1f / itemHeight ;
UpdateList ( ) ;
}
// Update the list
public void UpdateList ( )
{
float newHeight = Mathf . Max ( 1f , adapter . Count * itemHeight ) ;
contentTransform . sizeDelta = new Vector2 ( 0f , newHeight ) ;
viewportHeight = viewportTransform . rect . height ;
UpdateItemsInTheList ( true ) ;
}
// Window is resized, update the list
public void OnViewportDimensionsChanged ( )
{
viewportHeight = viewportTransform . rect . height ;
UpdateItemsInTheList ( ) ;
}
// Calculate the indices of items to show
private void UpdateItemsInTheList ( bool updateAllVisibleItems = false )
{
// If there is at least one item to show
if ( adapter . Count > 0 )
{
float contentPos = contentTransform . anchoredPosition . y - 1f ;
int newTopIndex = ( int ) ( contentPos * _1OverItemHeight ) ;
int newBottomIndex = ( int ) ( ( contentPos + viewportHeight + 2f ) * _1OverItemHeight ) ;
if ( newTopIndex < 0 )
newTopIndex = 0 ;
if ( newBottomIndex > adapter . Count - 1 )
newBottomIndex = adapter . Count - 1 ;
if ( currentTopIndex = = - 1 )
{
// There are no active items
updateAllVisibleItems = true ;
currentTopIndex = newTopIndex ;
currentBottomIndex = newBottomIndex ;
CreateItemsBetweenIndices ( newTopIndex , newBottomIndex ) ;
}
else
{
// There are some active items
if ( newBottomIndex < currentTopIndex | | newTopIndex > currentBottomIndex )
{
// If user scrolled a lot such that, none of the items are now within
// the bounds of the scroll view, pool all the previous items and create
// new items for the new list of visible entries
updateAllVisibleItems = true ;
DestroyItemsBetweenIndices ( currentTopIndex , currentBottomIndex ) ;
CreateItemsBetweenIndices ( newTopIndex , newBottomIndex ) ;
}
else
{
// User did not scroll a lot such that, some items are are still within
// the bounds of the scroll view. Don't destroy them but update their content,
// if necessary
if ( newTopIndex > currentTopIndex )
{
DestroyItemsBetweenIndices ( currentTopIndex , newTopIndex - 1 ) ;
}
if ( newBottomIndex < currentBottomIndex )
{
DestroyItemsBetweenIndices ( newBottomIndex + 1 , currentBottomIndex ) ;
}
if ( newTopIndex < currentTopIndex )
{
CreateItemsBetweenIndices ( newTopIndex , currentTopIndex - 1 ) ;
// If it is not necessary to update all the items,
// then just update the newly created items. Otherwise,
// wait for the major update
if ( ! updateAllVisibleItems )
{
UpdateItemContentsBetweenIndices ( newTopIndex , currentTopIndex - 1 ) ;
}
}
if ( newBottomIndex > currentBottomIndex )
{
CreateItemsBetweenIndices ( currentBottomIndex + 1 , newBottomIndex ) ;
// If it is not necessary to update all the items,
// then just update the newly created items. Otherwise,
// wait for the major update
if ( ! updateAllVisibleItems )
{
UpdateItemContentsBetweenIndices ( currentBottomIndex + 1 , newBottomIndex ) ;
}
}
}
currentTopIndex = newTopIndex ;
currentBottomIndex = newBottomIndex ;
}
if ( updateAllVisibleItems )
{
// Update all the items
UpdateItemContentsBetweenIndices ( currentTopIndex , currentBottomIndex ) ;
}
}
else if ( currentTopIndex ! = - 1 )
{
// There is nothing to show but some items are still visible; pool them
DestroyItemsBetweenIndices ( currentTopIndex , currentBottomIndex ) ;
currentTopIndex = - 1 ;
}
}
private void CreateItemsBetweenIndices ( int topIndex , int bottomIndex )
{
for ( int i = topIndex ; i < = bottomIndex ; i + + )
{
CreateItemAtIndex ( i ) ;
}
}
// Create (or unpool) an item
private void CreateItemAtIndex ( int index )
{
ListItem item ;
if ( pooledItems . Count > 0 )
{
item = pooledItems . Pop ( ) ;
item . gameObject . SetActive ( true ) ;
}
else
{
item = adapter . CreateItem ( ) ;
item . transform . SetParent ( contentTransform , false ) ;
item . SetAdapter ( adapter ) ;
}
// Reposition the item
( ( RectTransform ) item . transform ) . anchoredPosition = new Vector2 ( 1f , - index * itemHeight ) ;
// To access this item easily in the future, add it to the dictionary
items [ index ] = item ;
}
private void DestroyItemsBetweenIndices ( int topIndex , int bottomIndex )
{
for ( int i = topIndex ; i < = bottomIndex ; i + + )
{
ListItem item = items [ i ] ;
item . gameObject . SetActive ( false ) ;
pooledItems . Push ( item ) ;
}
}
private void UpdateItemContentsBetweenIndices ( int topIndex , int bottomIndex )
{
for ( int i = topIndex ; i < = bottomIndex ; i + + )
{
ListItem item = items [ i ] ;
item . Position = i ;
adapter . SetItemContent ( item ) ;
}
}
#if UNITY_EDITOR | | UNITY_STANDALONE | | UNITY_WSA | | UNITY_WSA_10_0
// When free space inside ScrollRect is clicked:
// Left click: deselect selected file(s)
// Right click: show context menu
public void OnPointerClick ( PointerEventData eventData )
{
if ( eventData . button = = PointerEventData . InputButton . Left )
fileBrowser . DeselectAllFiles ( ) ;
else if ( eventData . button = = PointerEventData . InputButton . Right )
fileBrowser . OnContextMenuTriggered ( eventData . position ) ;
}
#endif
}
}