This commit is contained in:
2025-08-18 09:22:24 +08:00
commit cef5623ab0
1333 changed files with 305844 additions and 0 deletions
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: de71ef25b2d6216429424ec0c68a28b8
folderAsset: yes
timeCreated: 1553129474
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,102 @@
using System;
using UnityEngine;
using UnityEngine.UI;
using Com.ForbiddenByte.OSA.Core;
using Com.ForbiddenByte.OSA.CustomParams;
namespace Com.ForbiddenByte.OSA.CustomAdapters.DateTimePicker
{
/// <summary>Implementing multiple adapters to get a generic picker which returns a <see cref="DateTime"/> object</summary>
public class DateTimePickerAdapter : OSA<MyParams, MyItemViewsHolder>
{
public int SelectedValue { get; private set; }
public event Action<int> OnSelectedValueChanged;
#region OSA implementation
/// <inheritdoc/>
protected override void Start()
{
base.Start();
}
/// <inheritdoc/>
protected override void Update()
{
base.Update();
if (!IsInitialized)
return;
if (VisibleItemsCount == 0)
return;
int middleVHIndex = VisibleItemsCount / 2;
var middleVH = GetItemViewsHolder(middleVHIndex);
var prevValue = SelectedValue;
SelectedValue = _Params.GetItemValueAtIndex(middleVH.ItemIndex);
middleVH.background.CrossFadeColor(_Params.selectedColor, .1f, false, false);
for (int i = 0; i < VisibleItemsCount; ++i)
{
if (i != middleVHIndex)
GetItemViewsHolder(i).background.CrossFadeColor(_Params.nonSelectedColor, .1f, false, false);
}
if (prevValue != SelectedValue && OnSelectedValueChanged != null)
OnSelectedValueChanged(SelectedValue);
}
/// <inheritdoc/>
protected override MyItemViewsHolder CreateViewsHolder(int itemIndex)
{
var instance = new MyItemViewsHolder();
instance.Init(_Params.ItemPrefab, _Params.Content, itemIndex);
return instance;
}
/// <inheritdoc/>
protected override void UpdateViewsHolder(MyItemViewsHolder newOrRecycled) { newOrRecycled.titleText.text = _Params.GetItemValueAtIndex(newOrRecycled.ItemIndex) + ""; }
#endregion
void ChangeItemsCountWithChecks(int newCount)
{
int min = 4;
if (newCount < min)
newCount = min;
ResetItems(newCount);
}
}
[Serializable] // serializable, so it can be shown in inspector
public class MyParams : BaseParamsWithPrefab
{
public int startItemNumber = 0;
public int increment = 1;
public Color selectedColor, nonSelectedColor;
/// <summary>The value of each item is calculated dynamically using its <paramref name="index"/>, <see cref="startItemNumber"/> and the <see cref="increment"/></summary>
/// <returns>The item's value (the displayed number)</returns>
public int GetItemValueAtIndex(int index) { return startItemNumber + increment * index; }
}
public class MyItemViewsHolder : BaseItemViewsHolder
{
public Image background;
public Text titleText;
/// <inheritdoc/>
public override void CollectViews()
{
base.CollectViews();
background = root.GetComponent<Image>();
titleText = root.GetComponentInChildren<Text>();
}
}
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f1cc6a6937df8c046943043e53538154
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
@@ -0,0 +1,309 @@
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using frame8.Logic.Misc.Other.Extensions;
using Com.ForbiddenByte.OSA.Core;
namespace Com.ForbiddenByte.OSA.CustomAdapters.DateTimePicker
{
/// <summary>
/// Implementing multiple adapters to get a generic picker which returns a <see cref="DateTime"/> object.
/// There are 2 ways of using this script: either simply call <see cref="Show(Action{DateTime})"/>
/// or drag and drop the prefab from /Resources/Com.ForbiddenByte.OSA/DateTimePickerDialog into your scene and subscribe to <see cref="OnDateSelected"/>
/// </summary>
public class DateTimePickerDialog : MonoBehaviour
{
[SerializeField]
bool _AutoInit = false;
[SerializeField]
bool _DisplaySelectedDateAsShort = false;
[SerializeField]
bool _DisplaySelectedTimeAsShort = false;
[Tooltip("If true, animations won't be affected by Time.timeScale")]
[SerializeField]
bool _UseUnscaledTime = true;
public event Action<DateTime> OnDateSelected;
public DateTimePickerAdapter DayAdapter { get; private set; }
public DateTimePickerAdapter MonthAdapter { get; private set; }
public DateTimePickerAdapter YearAdapter { get; private set; }
public DateTimePickerAdapter HourAdapter { get; private set; }
public DateTimePickerAdapter MinuteAdapter { get; private set; }
public DateTimePickerAdapter SecondAdapter { get; private set; }
public DateTime SelectedValue
{
get
{
return new DateTime(
YearAdapter.SelectedValue, MonthAdapter.SelectedValue, DayAdapter.SelectedValue,
HourAdapter.SelectedValue, MinuteAdapter.SelectedValue, SecondAdapter.SelectedValue
);
}
}
const float SCROLL_DURATION1 = .2f;
const float SCROLL_DURATION2 = .35f;
const float SCROLL_DURATION3 = .5f;
const float ANIM_DURATION = .25f;
const float DEFAULT_WIDTH = 660f, DEFAULT_HEIGHT = DEFAULT_WIDTH / 2;
float AnimElapsedTime01 { get { float t = Mathf.Clamp01((Time - _AnimStartTime) / ANIM_DURATION); return t * t * t * t; } }
//Vector3 AnimCurrentScale { get { return Vector3.Lerp(_AnimStart, _AnimEnd, AnimElapsedTime01); } }
float AnimCurrentFloat { get { return Mathf.Lerp(_AnimStart, _AnimEnd, AnimElapsedTime01); } }
float Time { get { return _UseUnscaledTime ? UnityEngine.Time.unscaledTime : UnityEngine.Time.time; } }
Transform _DatePanel, _TimePanel;
Text _SelectedDateText, _SelectedTimeText;
bool _Initialized;
DateTime? _DateToInitWith;
bool _Animating;
//Vector3 _AnimStart, _AnimEnd;
float _AnimStart, _AnimEnd;
float _AnimStartTime;
Action _ActionOnAnimDone;
CanvasGroup _CanvasGroup;
DateTimePickerAdapter[] _AllAdapters = new DateTimePickerAdapter[6];
public static DateTimePickerDialog Show(Action<DateTime> onSelected)
{
return Show(DateTime.Now, onSelected);
}
public static DateTimePickerDialog Show(Action<DateTime> onSelected, string prefabPathInResources)
{
return Show(DateTime.Now, onSelected, prefabPathInResources);
}
public static DateTimePickerDialog Show(DateTime startingDate, Action<DateTime> onSelected)
{
return Show(startingDate, onSelected, DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
public static DateTimePickerDialog Show(DateTime startingDate, Action<DateTime> onSelected, string prefabPathInResources)
{
return Show(startingDate, onSelected, DEFAULT_WIDTH, DEFAULT_HEIGHT, prefabPathInResources);
}
public static DateTimePickerDialog Show(DateTime startingDate, Action<DateTime> onSelected, float width, float height)
{
var prefabPathInResources = OSAConst.OSA_PATH_IN_RESOURCES + "/" + typeof(DateTimePickerDialog).Name;
return Show(startingDate, onSelected, width, height, prefabPathInResources);
}
public static DateTimePickerDialog Show(DateTime startingDate, Action<DateTime> onSelected, float width, float height, string prefabPathInResources)
{
var go = Resources.Load<GameObject>(prefabPathInResources);
var picker = (Instantiate(go) as GameObject).GetComponent<DateTimePickerDialog>();
var c = FindObjectOfType<Canvas>();
if (!c)
throw new OSAException(typeof(DateTimePickerDialog).Name + ": no Canvas was found in the scene");
var canvasRT = c.transform as RectTransform;
var rt = (picker.transform as RectTransform);
rt.SetParent(canvasRT, false);
rt.SetAsLastSibling();
picker._DateToInitWith = startingDate;
if (onSelected != null)
picker.OnDateSelected += onSelected;
rt.SetInsetAndSizeFromParentEdgeWithCurrentAnchors(RectTransform.Edge.Left, (canvasRT.rect.width - width) / 2, width);
rt.SetInsetAndSizeFromParentEdgeWithCurrentAnchors(RectTransform.Edge.Top, (canvasRT.rect.height - height) / 2, height);
return picker;
}
void Awake()
{
_CanvasGroup = GetComponent<CanvasGroup>();
_CanvasGroup.alpha = 0f;
_AnimEnd = 1f;
_AnimStartTime = Time;
_Animating = true;
}
void Start()
{
var adaptersTR = transform.Find("Adapters");
_DatePanel = adaptersTR.Find("Date");
_DatePanel.GetComponentAtPath("SelectedIndicatorText", out _SelectedDateText);
int i = 0;
_AllAdapters[i++] = DayAdapter = _DatePanel.GetComponentAtPath<DateTimePickerAdapter>("Panel/Day");
_AllAdapters[i++] = MonthAdapter = _DatePanel.GetComponentAtPath<DateTimePickerAdapter>("Panel/Month");
_AllAdapters[i++] = YearAdapter = _DatePanel.GetComponentAtPath<DateTimePickerAdapter>("Panel/Year");
_TimePanel = adaptersTR.Find("Time");
_TimePanel.GetComponentAtPath("SelectedIndicatorText", out _SelectedTimeText);
_AllAdapters[i++] = HourAdapter = _TimePanel.GetComponentAtPath<DateTimePickerAdapter>("Panel/Hour");
_AllAdapters[i++] = MinuteAdapter = _TimePanel.GetComponentAtPath<DateTimePickerAdapter>("Panel/Minute");
_AllAdapters[i++] = SecondAdapter = _TimePanel.GetComponentAtPath<DateTimePickerAdapter>("Panel/Second");
for (i = 0; i < _AllAdapters.Length; ++i)
_AllAdapters[i].Parameters.UseUnscaledTime = _UseUnscaledTime;
if (_AutoInit)
ExecuteAfter(.2f, AutoInit);
}
void Update()
{
if (_Animating)
{
//transform.localScale = AnimCurrentFloat;
_CanvasGroup.alpha = AnimCurrentFloat;
if (AnimElapsedTime01 == 1f)
{
_Animating = false;
if (_ActionOnAnimDone != null)
_ActionOnAnimDone();
}
return;
}
if (!_Initialized)
return;
try
{
var current = SelectedValue;
_SelectedDateText.text = _DisplaySelectedDateAsShort ? current.ToShortDateString() : current.ToLongDateString();
_SelectedTimeText.text = _DisplaySelectedTimeAsShort ? current.ToShortTimeString() : current.ToLongTimeString();
}
catch /*(Exception e)*/{
//Debug.Log(e + "\n"+YearAdapter.SelectedValue + "," + MonthAdapter.SelectedValue + "," + DayAdapter.SelectedValue + "," +
//HourAdapter.SelectedValue + "," + MinuteAdapter.SelectedValue + "," + SecondAdapter.SelectedValue);
}
}
public void InitWithNow() { InitWithDate(DateTime.Now); }
public void InitWithDate(DateTime dateTime)
{
StopAnimations();
_DateToInitWith = null;
UnregisterAutoCorrection();
int doneNum = 0;
int targetDone = 2;
Action onDone = () =>
{
if (++doneNum == targetDone)
{
_Initialized = true;
RegisterAutoCorrection();
}
};
//Func<float, bool> onProgress = p01 =>
//{
// if (p01 == 1f)
// {
// if (++doneNum == targetDone)
// {
// _Initialized = true;
// RegisterAutoCorrection();
// }
// }
// return true;
//};
YearAdapter.ResetItems(3000);
YearAdapter.SmoothScrollTo(dateTime.Year - 1, SCROLL_DURATION1, .5f, .5f);
MonthAdapter.ResetItems(12);
MonthAdapter.SmoothScrollTo(dateTime.Month - 1, SCROLL_DURATION2, .5f, .5f);
DayAdapter.ResetItems(DateTime.DaysInMonth(dateTime.Year, dateTime.Month));
//DayAdapter.SmoothScrollTo(dateTime.Day - 1, SCROLL_DURATION3, .5f, .5f, onProgress, null, true);
DayAdapter.SmoothScrollTo(dateTime.Day - 1, SCROLL_DURATION3, .5f, .5f, null, onDone, true);
SecondAdapter.ResetItems(60);
SecondAdapter.SmoothScrollTo(dateTime.Second, SCROLL_DURATION1, .5f, .5f);
MinuteAdapter.ResetItems(60);
MinuteAdapter.SmoothScrollTo(dateTime.Minute, SCROLL_DURATION2, .5f, .5f);
HourAdapter.ResetItems(24);
//HourAdapter.SmoothScrollTo(dateTime.Hour, SCROLL_DURATION3, .5f, .5f, onProgress, true);
HourAdapter.SmoothScrollTo(dateTime.Hour, SCROLL_DURATION3, .5f, .5f, null, onDone, true);
}
public void ReturnCurrent()
{
//_AnimStart = Vector3.one;
//_AnimEnd = Vector3.zero;
_AnimStart = _CanvasGroup.alpha;
_AnimEnd = 0f;
_AnimStartTime = Time;
_Animating = true;
_ActionOnAnimDone = () =>
{
_ActionOnAnimDone = null;
if (OnDateSelected != null)
OnDateSelected(SelectedValue);
Destroy(gameObject);
};
}
void AutoInit()
{
_DateToInitWith = _DateToInitWith ?? DateTime.Now;
InitWithDate(_DateToInitWith.Value);
}
void UnregisterAutoCorrection()
{
YearAdapter.OnSelectedValueChanged -= OnYearChanged;
MonthAdapter.OnSelectedValueChanged -= OnMonthChanged;
}
void RegisterAutoCorrection()
{
YearAdapter.OnSelectedValueChanged += OnYearChanged;
MonthAdapter.OnSelectedValueChanged += OnMonthChanged;
}
void OnYearChanged(int year) { OnMonthChanged(MonthAdapter.SelectedValue); }
void OnMonthChanged(int month)
{
var selectedDay = DayAdapter.SelectedValue;
int newDaysInMonth = DateTime.DaysInMonth(YearAdapter.SelectedValue, month);
if (newDaysInMonth == DayAdapter.GetItemsCount())
return;
DayAdapter.ResetItems(newDaysInMonth);
DayAdapter.ScrollTo(Math.Min(newDaysInMonth, selectedDay) - 1, .5f, .5f);
}
void StopAnimations()
{
foreach (var adapter in _AllAdapters)
{
if (adapter)
adapter.CancelAllAnimations();
}
}
void ExecuteAfter(float seconds, Action action) { StartCoroutine(ExecuteAfterCoroutine(seconds, action)); }
IEnumerator ExecuteAfterCoroutine(float seconds, Action action)
{
if (seconds > 0f)
{
yield return null;
yield return null;
}
if (_UseUnscaledTime)
yield return new WaitForSecondsRealtime(seconds);
else
yield return new WaitForSeconds(seconds);
action();
}
}
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 67c05838ffd5f8849ad61288be5e976f
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 7e92a4bb33aadd94797256497c81c4c3
folderAsset: yes
timeCreated: 1638561458
licenseType: Store
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 41e1a65c7b96d03498c2b6c910ff6748
folderAsset: yes
timeCreated: 1606210561
licenseType: Store
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 57ccde6a849ccca4188519c929e776d2
folderAsset: yes
timeCreated: 1606210561
licenseType: Store
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
namespace Com.ForbiddenByte.OSA.CustomAdapters.GridView.Specialized.GridWithCategories
{
public enum CellType
{
VALID,
FOR_ROW_COMPLETION,
IN_ROW_SEPARATING_CATEGORIES
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: dae9c5a81d044ef408cbf2496982fed3
timeCreated: 1606210561
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
namespace Com.ForbiddenByte.OSA.CustomAdapters.GridView.Specialized.GridWithCategories
{
public class GridWithCategoriesUtil
{
static T CreateItemModelForRowCompletion<T>(ICategoryModel parentCategory) where T : ICellModel, new()
{
return new T
{
ParentCategory = parentCategory,
Id = -1,
Type = CellType.FOR_ROW_COMPLETION
};
}
static T CreateItemModelInRowSeparatingCategories<T>(ICategoryModel parentCategory) where T : ICellModel, new()
{
return new T
{
ParentCategory = parentCategory,
Id = -1,
Type = CellType.IN_ROW_SEPARATING_CATEGORIES
};
}
/// <summary>
/// Converts your user-level data structure to an OSA Grid-compatible list of items, which will also include "filler" items for the spaces between the categories and empty spaces.
/// This approach has the advantage of reusing the OSA's GridAdapter completely, at the expense of creating/managing few additional items at the user-level
/// </summary>
public static void ConvertCategoriesToListOfItemModels<TCategory, TCell>(int itemSlotsPerRow, List<TCategory> categories, out List<TCell> cells)
where TCategory : ICategoryModel
where TCell : ICellModel, new()
{
cells = new List<TCell>();
for (int i = 0; i < categories.Count; i++)
{
var cat = categories[i];
// Insert an empty row of items to make room for the category's header
for (int j = 0; j < itemSlotsPerRow; j++)
{
var m = CreateItemModelInRowSeparatingCategories<TCell>(cat);
cells.Add(m);
}
// Add the actual cells
for (int j = 0; j < cat.Count; j++)
cells.Add((TCell)cat[j]);
// If the category's last row is not full, fill it with empty slots, so they won't be occupied with the ones from the next category
while (cells.Count % itemSlotsPerRow != 0)
cells.Add(CreateItemModelForRowCompletion<TCell>(cat));
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 3015ac1d7f1fd374ca2280b1b3196003
timeCreated: 1606210561
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
namespace Com.ForbiddenByte.OSA.CustomAdapters.GridView.Specialized.GridWithCategories
{
public interface ICategoryModel
{
int Count { get; }
ICellModel this[int index] { get; }
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 486ee90ba4b16014ebe95094ecde8c98
timeCreated: 1606210561
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
namespace Com.ForbiddenByte.OSA.CustomAdapters.GridView.Specialized.GridWithCategories
{
public interface ICellModel
{
ICategoryModel ParentCategory { get; set; }
int Id { get; set; }
CellType Type { get; set; }
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 0167959a452670147a41bbac4d2b6cc7
timeCreated: 1606210561
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: ab806c65fbd4e8e48aca194d97e8bf4f
folderAsset: yes
timeCreated: 1562924181
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 02d3576c3b8e35f4eab016ed74dc01b2
folderAsset: yes
timeCreated: 1562924181
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,55 @@
using System;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Basic
{
public class BasicColumnInfo : IColumnInfo
{
public string Name
{
get { return _Name; }
set
{
if (_Name == value)
return;
_Name = value;
ReconstructDisplayName();
}
}
public string DisplayName { get; set; }
public TableValueType ValueType { get; private set; }
public Type EnumValueType { get; private set; }
public float Size { get { return _Size; } }
string _Name;
float _Size = -1;
public BasicColumnInfo(string name, TableValueType valueType, Type enumValueType = null, float? customSize = null)
{
ValueType = valueType;
EnumValueType = enumValueType;
if (customSize != null)
_Size = customSize.Value;
// Setting it last, so the display name will be reconstructed using the other properties
Name = name;
}
void ReconstructDisplayName()
{
DisplayName = ConstructColumnDisplayName(Name, ValueType, EnumValueType);
}
public static string ConstructColumnDisplayName(string name, TableValueType valueType, Type enumValueType = null)
{
string innerStr;
if (valueType == TableValueType.ENUMERATION)
innerStr = "ENUM <i>" + (enumValueType == null ? "<Unknown>" : enumValueType.Name) + "</i>";
else
innerStr = valueType.ToString();
return name + "\n<color=#00000070><size=12>" + innerStr + "</size></color>";
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4dacb2671ec142e4698dd1539eaeceb4
timeCreated: 1563189913
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,21 @@
using System;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Basic
{
public class BasicColumnState : IColumnState
{
public IColumnInfo Info { get; private set; }
public TableValueSortType CurrentSortingType { get; set; }
public bool CurrentlyReadOnly { get; set; }
public float CurrentSize { get; set; }
public BasicColumnState(IColumnInfo info, bool readonlyByDefault)
{
Info = info;
CurrentSortingType = TableValueSortType.NONE;
CurrentlyReadOnly = readonlyByDefault;
CurrentSize = info.Size;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: bbc77a9af44c03a46993cacfdae1d85b
timeCreated: 1563189913
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,65 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Basic
{
public class BasicTableColumns : ITuple, ITableColumns
{
IList<BasicColumnState> _ColumnStates;
public int ColumnsCount { get { return _ColumnStates.Count; } }
int ITuple.Length { get { return ColumnsCount; } }
public BasicTableColumns(IList<IColumnInfo> columnInfos)
{
_ColumnStates = new List<BasicColumnState>(columnInfos.Count);
for (int i = 0; i < columnInfos.Count; i++)
_ColumnStates.Add(new BasicColumnState(columnInfos[i], false));
}
public BasicTableColumns(IList<BasicColumnInfo> columnInfos)
{
_ColumnStates = new List<BasicColumnState>(columnInfos.Count);
for (int i = 0; i < columnInfos.Count; i++)
_ColumnStates.Add(new BasicColumnState(columnInfos[i], false));
}
public IColumnState GetColumnState(int index)
{
return _ColumnStates[index];
}
public ITuple GetColumnsAsTuple()
{
return this;
}
/// <summary>Gets the title of a column</summary>
object ITuple.GetValue(int index)
{
return _ColumnStates[index].Info.DisplayName;
}
/// <summary>Sets the title of a column</summary>
void ITuple.SetValue(int index, object value)
{
_ColumnStates[index].Info.Name = value == null ? "" : value.ToString();
}
/// <summary>
/// Sets the titles of all columns. <paramref name="newValues"/> should be of the same length of the current list,
/// i.e. only an existing list of columns can have its names modified
/// </summary>
void ITuple.ResetValues(IList newValues, bool cloneList)
{
if (_ColumnStates == null || newValues.Count != _ColumnStates.Count)
throw new InvalidOperationException("Not supported for " + typeof(BasicTableColumns).Name + " if the count is different");
for (int i = 0; i < newValues.Count; i++)
(this as ITuple).SetValue(i, newValues[i]);
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: d3c0e28f9a322c44b98000d7b35cbc35
timeCreated: 1562942182
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,46 @@
using System;
using System.Collections;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Basic
{
/// <summary>
/// A table data that's fetched all at once.
/// </summary>
public class BasicTableData : TableData
{
public override int Count { get { return _RowTuples.Count; } }
public override bool ColumnClearingSupported { get { return Count < TableViewConst.MAX_TABLE_ENTRIES_FOR_ACCEPTABLE_COLUMN_ITERATION_TIME; } }
IList _RowTuples;
/// <summary>
/// <paramref name="rowTuples"/> is the list of all the rows in the table, as tuples implementing <see cref="ITuple"/>
/// </summary>
public BasicTableData(ITableColumns columnsProvider, IList rowTuples, bool columnSortingSupported)
{
_RowTuples = rowTuples;
Init(columnsProvider, columnSortingSupported);
}
public override ITuple GetTuple(int index) { return _RowTuples[index] as ITuple; }
protected override bool ReverseTuplesListIfSupported()
{
// The ArrayList.Adapter() is an O(1) operation
var adapter = ArrayList.Adapter(_RowTuples);
adapter.Reverse();
return true;
}
protected override bool SortTuplesListIfSupported(IComparer comparerToUse)
{
// The ArrayList.Adapter() is an O(1) operation
var adapter = ArrayList.Adapter(_RowTuples);
adapter.Sort(comparerToUse);
return true;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 6f2cf5322da9e7943b78d66a6ba991dd
timeCreated: 1563565624
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,90 @@
using System;
using UnityEngine;
using UnityEngine.UI;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Basic
{
public class BasicTableViewOptionsPanel : MonoBehaviour, ITableViewOptionsPanel
{
[SerializeField]
Button _EnterClearingStateButton = null;
[SerializeField]
Button _ExitClearingStateButton = null;
[SerializeField]
GameObject _ClearingStateShownObject = null;
[SerializeField]
GameObject _NoneStateShownObject = null;
[SerializeField]
GameObject _LoadingGameObject = null;
[SerializeField]
CanvasGroup _CanvasGroupToDisableOnLoad = null;
public bool IsClearing { get { return _IsClearing; } set { SetIsClearing(value); } }
public bool IsLoading { get { return _IsLoading; } set { SetIsLoading(value); } }
bool _IsClearing;
bool _IsLoading;
void Start()
{
if (_EnterClearingStateButton)
_EnterClearingStateButton.onClick.AddListener(SetClearing);
if (_ExitClearingStateButton)
_ExitClearingStateButton.onClick.AddListener(SetNoClearing);
IsClearing = false;
IsLoading = false;
}
void Update()
{
if (_IsLoading)
{
if (_LoadingGameObject)
{
_LoadingGameObject.transform.Rotate(Vector3.forward, -270f * Time.deltaTime, Space.Self);
}
}
}
void SetNoClearing()
{
IsClearing = false;
}
void SetClearing()
{
IsClearing = true;
}
void SetIsClearing(bool isClearing)
{
if (_EnterClearingStateButton)
_EnterClearingStateButton.gameObject.SetActive(!isClearing);
if (_ExitClearingStateButton)
_ExitClearingStateButton.gameObject.SetActive(isClearing);
if (_ClearingStateShownObject)
_ClearingStateShownObject.gameObject.SetActive(isClearing);
if (_NoneStateShownObject)
_NoneStateShownObject.gameObject.SetActive(!isClearing);
_IsClearing = isClearing;
}
void SetIsLoading(bool isLoading)
{
_IsLoading = isLoading;
if (_LoadingGameObject)
_LoadingGameObject.SetActive(_IsLoading);
if (_CanvasGroupToDisableOnLoad)
_CanvasGroupToDisableOnLoad.blocksRaycasts = !_IsLoading;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: e313db585f39b2b4aa28a3dd276b0585
timeCreated: 1563526496
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,49 @@
using System.Collections;
using System.Collections.Generic;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Basic
{
public class BasicTuple : ITuple
{
public int Length { get { return _Values.Count; } }
IList _Values;
/// <summary>See <see cref="ResetValues(IList, bool)"/></summary>
public BasicTuple() { }
/// <summary>See <see cref="ResetValues(IList, bool)"/></summary>
public BasicTuple(IList values, bool cloneList = false)
{
ResetValues(values, cloneList);
}
public object GetValue(int index)
{
return _Values[index];
}
public void SetValue(int index, object value)
{
_Values[index] = value;
}
/// <summary>
/// Passing <paramref name="cloneList"/>=true, will clone the list of values. Otherwise, will keep a reference to the list
/// and thus will be affected by external changes to it
/// </summary>
public void ResetValues(IList newValues, bool cloneList)
{
if (cloneList)
{
_Values = new object[newValues.Count];
for (int i = 0; i < newValues.Count; i++)
_Values[i] = newValues[i];
}
else
_Values = newValues;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 33caa64ff4011084b91034f3ba113ca7
timeCreated: 1562924181
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: f37b5566452a438468329b3ad4072d24
folderAsset: yes
timeCreated: 1563565624
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,47 @@
using System;
using System.Collections;
using Com.ForbiddenByte.OSA.DataHelpers;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Extra
{
/// <summary>
/// Class for loading chunks of data asynchronously into a <see cref="BufferredTableData"/> and notifying a <see cref="TableAdapter{TParams, TTupleViewsHolder, THeaderTupleViewsHolder}"/>
/// about changes, while staying aware of its lifecycle events like <see cref="TableAdapter{TParams, TTupleViewsHolder, THeaderTupleViewsHolder}.ChangeItemsCount(Core.ItemCountChangeMode, int, int, bool, bool)"/>
/// and invalidating any existing loading tasks when needed
/// </summary>
public class AsyncBufferredTableData<TTuple> : ITupleProvider
where TTuple : ITuple, new()
{
public ITableColumns Columns { get; private set; }
public int Count { get { return _DataSource.Count; } }
public bool ColumnClearingSupported { get { return false; } }
public bool ColumnSortingSupported { get { return false; } }
public AsyncBufferredDataSource<TTuple> Source { get { return _DataSource; } }
AsyncBufferredDataSource<TTuple> _DataSource;
/// <summary>
/// See <see cref="AsyncBufferredDataSource{T}"/>
/// </summary>
public AsyncBufferredTableData(ITableColumns columns, int tuplesCount, int chunkBufferSize, AsyncBufferredDataSource<TTuple>.Loader loader)
{
_DataSource = new AsyncBufferredDataSource<TTuple>(tuplesCount, chunkBufferSize, loader);
Columns = columns;
}
public ITuple GetTuple(int index) { return _DataSource.GetValue(index); }
//TTuple CreateEmptyTuple()
//{
// return TableViewUtil.CreateTupleWithEmptyValues<TTuple>(Columns.ColumnsCount);
//}
public bool ChangeColumnSortType(int columnIndex, TableValueType columnType, TableValueSortType currentSorting, TableValueSortType nextSorting)
{ throw new NotSupportedException(); }
public void SetAllValuesOnColumn(int columnIndex, object sameColumnValueInAllTuples)
{ throw new NotSupportedException(); }
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1eccc520d353e75408cd8e73a89d6fa2
timeCreated: 1563617598
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,122 @@
using System;
using UnityEngine;
using Com.ForbiddenByte.OSA.DataHelpers;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Extra
{
/// <summary>
/// Class for loading data via a <see cref="AsyncBufferredTableData{TTuple}"/> and notifying a <see cref="TableAdapter{TParams, TTupleViewsHolder, THeaderTupleViewsHolder}"/>
/// about changes, while staying aware of its lifecycle events like <see cref="TableAdapter{TParams, TTupleViewsHolder, THeaderTupleViewsHolder}.ChangeItemsCount(Core.ItemCountChangeMode, int, int, bool, bool)"/>
/// and invalidating any existing loading tasks when needed.
/// It'll dispose everything on first count change.
/// </summary>
public class AsyncLoadingUIController<TTuple>
where TTuple : ITuple, new()
{
ITableAdapter _Adapter;
AsyncBufferredTableData<TTuple> _AsyncData;
static int _NumInstances; // just for debug purposes
int _ID = _NumInstances++;
public AsyncLoadingUIController(ITableAdapter tableAdapter, AsyncBufferredTableData<TTuple> data)
{
_Adapter = tableAdapter;
_AsyncData = data;
_AsyncData.Source.LoadingSessionStarted += OnAsyncDataLoadingStarted;
_AsyncData.Source.SingleTaskFinished += OnAsyncDataSingleTaskFinished;
_AsyncData.Source.LoadingSessionEnded += OnAsyncDataLoadingSessionEnded;
}
public void BeginListeningForSelfDisposal()
{
_Adapter.ItemsRefreshed += OnAdapterItemsRefreshed;
}
void OnAsyncDataLoadingStarted()
{
if (_Adapter.Options != null)
_Adapter.Options.IsLoading = true;
}
void OnAsyncDataSingleTaskFinished(AsyncBufferredDataSource<TTuple>.LoadingTask task)
{
// Make sure the adapter wasn't disposed meanwhile
if (_Adapter == null || !_Adapter.IsInitialized)
{
Dispose();
return;
}
_Adapter.RefreshRange(task.FirstItemIndex, task.CountToRead);
}
void OnAsyncDataLoadingSessionEnded()
{
// Make sure the adapter wasn't disposed meanwhile
if (_Adapter == null || !_Adapter.IsInitialized)
{
Dispose();
return;
}
if (_Adapter.Options != null)
_Adapter.Options.IsLoading = false;
}
// If the adapter refreshes its items while one or more tasks are in progress,
// make sure to invalidate them so they'll be ignored when they'll fire OnFinishedOneTask
void OnAdapterItemsRefreshed(int prevCount, int newCount)
{
// When the adapter's items count changes or the views are fully refreshed (for example, as a result
// of resizing the ScrollView), it fires the ItemsRefreshed.
// Check whether that was a result of a simple Refresh (which is done by
// OSA and thus preserves the data) or an external ResetTable call, which changes the data and thus
// requires this uiController to self-dispose.
bool sameDataReferences = _Adapter.Tuples == _AsyncData && _Adapter.Columns == _AsyncData.Columns;
if (sameDataReferences)
return;
int numRunning = _AsyncData.Source.CurrentlyLoadingTasksCount;
if (numRunning > 0)
{
//if (_AsyncData.ShowLogs)
// Debug.Log("OnAdapterItemsRefreshed(count " + prevCount + " -> " + newCount + ") : Clearing all " + numRunning + " active tasks");
// Update: this overrides an existing loading task, so it's left to the adapter's will to disable the loading state
//if (_Adapter.Options != null)
// _Adapter.Options.IsLoading = false;
_AsyncData.Source.ClearAllRunningTasks();
}
if (_AsyncData.Source.ShowLogs)
Debug.Log(
"AsyncLoadingUIController #"+ _ID +
": OnAdapterItemsRefreshed(count " + prevCount + " -> " + newCount +
") : Clearing all " + numRunning + " active tasks and disposing self"
);
Dispose();
}
void Dispose()
{
// Unsubscribing from events makes this object available for GC
if (_AsyncData != null && _AsyncData.Source != null)
{
_AsyncData.Source.LoadingSessionStarted -= OnAsyncDataLoadingStarted;
_AsyncData.Source.SingleTaskFinished -= OnAsyncDataSingleTaskFinished;
_AsyncData.Source.LoadingSessionEnded -= OnAsyncDataLoadingSessionEnded;
}
if (_Adapter != null)
_Adapter.ItemsRefreshed -= OnAdapterItemsRefreshed;
_Adapter = null;
_AsyncData = null;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 564a64a4bd5e3384580f504309027fe3
timeCreated: 1563621284
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,65 @@
using System;
using System.Collections;
using Com.ForbiddenByte.OSA.DataHelpers;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Extra
{
/// <summary>
/// A table data that's not fetched all at once, but rather on demand, in chunks.
/// </summary>
public class BufferredTableData : TableData
{
/// <summary>
/// </summary>
/// <param name="into">Array to read data into, starting at 0, and ending at countToRead-1</param>
/// <param name="firstItemIndex">This is not the index at which to insert the item, but rather the index of the item in your table</param>
public delegate void TuplesChunkReader(ITuple[] into, int firstItemIndex, int countToRead);
public override int Count { get { return _DataSource.Count; } }
public override bool ColumnClearingSupported { get { return false; } }
BufferredDataSource<ITuple> _DataSource;
TuplesChunkReader _ChunkReader;
/// <summary>
/// Set <paramref name="chunkBufferSize"/> to a smaller value (like 10) if big jumps in the scrolling position are very frequent
/// and your data source allows retrieving a few values, but frequently. Default is <see cref="BufferredDataSource{T}.BUFFER_MAX_SIZE_DEFAULT"/>.
/// <para>Though the best way is to find it by manually testing different values</para>
/// </summary>
public BufferredTableData(ITableColumns columnsProvider, int tuplesCount, TuplesChunkReader tuplesChunkReader, int chunkBufferSize, bool columnSortingSupported)
{
_ChunkReader = tuplesChunkReader;
_DataSource = new BufferredDataSource<ITuple>(tuplesCount, ReadTuplesChunk, chunkBufferSize, false /*items will be created directly*/);
Init(columnsProvider, columnSortingSupported);
}
void ReadTuplesChunk(ITuple[] into, int firstItemIndex, int countToRead)
{
_ChunkReader(into, firstItemIndex, countToRead);
}
/// <summary>
/// Because this is backed by a <see cref="LazyList{T}"/>, the value is either returned (if exists) or created and then returned
/// </summary>
public override ITuple GetTuple(int index) { return _DataSource[index]; }
/// <summary><see cref="BufferredDataSource{T}.TryGetCachedValue(int, out T)"/></summary>
public bool TryGetCachedTuple(int index, out ITuple tuple) { return _DataSource.TryGetCachedValue(index, out tuple); }
/// <summary>See <see cref="BufferredDataSource{T}.GetValueUnchecked(int)"/></summary>
public ITuple GetExistingTuple(int index) { return _DataSource.GetValueUnchecked(index); }
protected override bool SortTuplesListIfSupported(IComparer comparerToUse)
{
// No sorting for now, but can be done for smaller data sets. Will probably be implemented in a future version
return false;
}
protected override bool ReverseTuplesListIfSupported()
{
// Also no reversing for now
return false;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: aa5c2e3f1c162cf4fb290597ff11f3ec
timeCreated: 1563565624
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,25 @@
using System;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView
{
public interface IColumnInfo
{
/// <summary>
/// The column's simple name
/// </summary>
string Name { get; set; }
/// <summary>
/// The name to actually display, which can be different from <see cref="Name"/>
/// </summary>
string DisplayName { get; set; }
TableValueType ValueType { get; }
/// <summary>Only applicable to columns for which <see cref="ValueType"/> is <see cref="TableValueType.ENUMERATION"/>, in which case it becomes required/ </summary>
Type EnumValueType { get; }
/// <summary>The width, for vertical TableViews. -1 to use the prefab's size</summary>
float Size { get; }
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: eb0333f852ffea1418191456f3d9c36e
timeCreated: 1563189913
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
using System;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView
{
public interface IColumnState
{
IColumnInfo Info { get; }
TableValueSortType CurrentSortingType { get; set; }
bool CurrentlyReadOnly { get; set; }
/// <summary>
/// Set to <see cref="IColumnInfo.Size"/> on initialization. Can be changed after.
/// </summary>
float CurrentSize { get; set; }
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 405d849c2add9a845a24b0a4962c572b
timeCreated: 1563189913
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,20 @@
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Serialization;
using Com.ForbiddenByte.OSA.Core;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView
{
/// <summary>
/// non-generic interface to access a <see cref="TableAdapter{TParams, TTupleViewsHolder, THeaderTupleViewsHolder}"/>
/// </summary>
public interface ITableAdapter : IOSA
{
TableParams TableParameters { get; }
ITableViewOptionsPanel Options { get; }
ITupleProvider Tuples { get; }
ITableColumns Columns { get; }
void RefreshRange(int firstIndex, int count);
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: a615c6f4d3735da4d9de37275bc872b1
timeCreated: 1563617598
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,19 @@
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Serialization;
using Com.ForbiddenByte.OSA.Core;
using Com.ForbiddenByte.OSA.CustomAdapters.TableView.Input;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView
{
/// <summary>
/// Stores both the configuration and some view state related info, notably the current sorting of each column
/// </summary>
public interface ITableColumns
{
int ColumnsCount { get; }
ITuple GetColumnsAsTuple();
IColumnState GetColumnState(int index);
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4b08e9514076fe044a499fd71e89c3d3
timeCreated: 1563003860
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView
{
/// <summary>
/// <para>
/// If you want to support clearing values, i.e. setting cells or entire columns to null,
/// or the other functionalities exposed by <see cref="ITableViewOptionsPanel"/>, implement this, attach it to a game object and assign it to TableAdapter's params.
/// </para>
/// The <see cref="IsClearing"/> and <see cref="IsLoading"/> are not mutually-exclusive
/// </summary>
public interface ITableViewOptionsPanel
{
bool IsClearing { get; set; }
bool IsLoading { get; set; }
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 729782eb43241774c94292fba64f0f0d
timeCreated: 1563525110
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,28 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView
{
public interface ITuple
{
int Length { get; }
object GetValue(int index);
void SetValue(int index, object value);
void ResetValues(IList newValues, bool cloneList);
}
public static class ITupleExt
{
public static void CopyFrom(this ITuple tuple, IEnumerable list)
{
int i = 0;
foreach (var item in list)
{
tuple.SetValue(i, item);
++i;
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1344815b8b8bed942bd8835ebfa02585
timeCreated: 1562924181
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,24 @@
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Serialization;
using Com.ForbiddenByte.OSA.Core;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView
{
/// <summary>
/// This provides tuples (a tuple represents a row in a table)
/// </summary>
public interface ITupleProvider
{
int Count { get; }
/// <summary>
/// Whether the user can clear all values for a column when it clicks it while in <see cref="ITableAdapter.Options"/> has IsClearing true
/// </summary>
bool ColumnClearingSupported { get; }
bool ColumnSortingSupported { get; }
ITuple GetTuple(int index);
bool ChangeColumnSortType(int columnIndex, TableValueType columnType, TableValueSortType currentSorting, TableValueSortType nextSorting);
void SetAllValuesOnColumn(int columnIndex, object sameColumnValueInAllTuples);
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1c27b10fff9908548bd8b14fb04b5045
timeCreated: 1563003860
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 715e89d4186da85438ead81f5a10c0fd
folderAsset: yes
timeCreated: 1563036279
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using UnityEngine.Events;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Input
{
public interface ITableViewFloatingDropdown
{
event Action Closed;
UnityEvent<int> onValueChanged { get; }
int value { get; set; }
int OptionsCount { get; }
void ClearOptions();
void AddOptions(List<string> options);
void Show();
void Hide();
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 70b3d39086f96604d96d4d086614dd6b
timeCreated: 1563651051
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Serialization;
using Com.ForbiddenByte.OSA.Core;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Input
{
public static class InputValidators
{
public delegate char StringValidator(string text, int charIndex, char addedChar);
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2047647c06cf33741bd4ccb813e90e9e
timeCreated: 1563036279
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,30 @@
using System;
using UnityEngine;
using UnityEngine.Events;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Input
{
public class TableViewFloatingDropdown : UnityEngine.UI.Dropdown, ITableViewFloatingDropdown
{
public event Action Closed;
public int OptionsCount { get { return options.Count; } }
UnityEvent<int> ITableViewFloatingDropdown.onValueChanged { get { return base.onValueChanged; } }
public new DropdownEvent onValueChanged
{
get { throw new InvalidOperationException("FloatingDropdown.onValueChanged: Not available for this class"); }
set { throw new InvalidOperationException("FloatingDropdown.onValueChanged: Not available for this class"); }
}
public new void Show() { throw new InvalidOperationException("FloatingDropdown.Show() Not available for this class "); }
void ITableViewFloatingDropdown.Show() { base.Show(); }
protected override void DestroyDropdownList(GameObject dropdownList)
{
base.DestroyDropdownList(dropdownList);
if (Closed != null)
Closed();
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: a51d16c508165c045be9b90f00319372
timeCreated: 1563190545
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using frame8.Logic.Misc.Other.Extensions;
using UnityEngine;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Input
{
public class TableViewFloatingDropdownController : MonoBehaviour
{
ITableViewFloatingDropdown _Dropdown;
Action<int> _CurrentCallback;
int _ValueToReturn = -1;
//Vector3 _PosToReset;
List<object> _Values = new List<object>();
// Excluding the Close option
List<string> _ValueNames = new List<string>();
protected void Awake()
{
(transform as RectTransform).pivot = new Vector2(0f, 1f);
_Dropdown = GetComponent(typeof(ITableViewFloatingDropdown)) as ITableViewFloatingDropdown;
_Dropdown.Closed += OnDropdownClosed;
}
public void InitWithEnum(Type enumType, bool sortByEnumNameInsteadOfValue = false, Action<List<object>> valuesFilter = null)
{
if (enumType == null || !enumType.IsEnum)
{
enumType = null;
ClearOptionsAndTypes();
return;
}
var values = Enum.GetValues(enumType);
var list = new List<object>();
var names = new List<string>();
for (int i = 0; i < values.Length; i++)
{
var value = (int)values.GetValue(i);
list.Add(value);
names.Add(Enum.GetName(enumType, value));
}
if (valuesFilter != null)
valuesFilter(list);
if (sortByEnumNameInsteadOfValue)
list.Sort((a, b) => Enum.GetName(enumType, a).CompareTo(Enum.GetName(enumType, b)));
else
list.Sort();
InitWithValues(list, names);
}
public void ShowFloating(RectTransform atParent, Action<object> onValueSelected, object invalidValue)
{
Action<int> onSelected = i =>
{
if (onValueSelected == null || _Values == null || _Values.Count <= i)
return;
var val = i == -1 ? invalidValue : _Values[i];
onValueSelected(val);
};
ShowFloating(atParent, onSelected);
}
public void ClearOptionsAndTypes()
{
_Values.Clear();
_ValueNames.Clear();
_Dropdown.ClearOptions();
}
public void Hide()
{
_Dropdown.Hide();
}
void InitWithValues(IList<object> values, IList<string> names)
{
ClearOptionsAndTypes();
_Values.AddRange(values);
_ValueNames.AddRange(names);
}
void ShowFloating(RectTransform atParent, Action<int> onSelected)
{
_CurrentCallback = onSelected;
_ValueToReturn = -1;
_Dropdown.onValueChanged.RemoveListener(OnValueChanged);
gameObject.SetActive(true);
transform.position = atParent.position;
_Dropdown.ClearOptions();
var options = new List<string>(_ValueNames); // modifying a copy
options.Add("<Close>");
_Dropdown.AddOptions(options);
_Dropdown.value = options.Count - 1; // selecting an invalid value by default
//RefreshShownValue();
_Dropdown.Show();
_Dropdown.onValueChanged.AddListener(OnValueChanged);
//_PosToReset = transform.localPosition;
var asRT = (transform as RectTransform);
asRT.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, atParent.rect.width);
asRT.TryClampPositionToParentBoundary();
}
void OnValueChanged(int value)
{
// The invalid value is returned as -1
_ValueToReturn = value == _Dropdown.OptionsCount - 1 ? -1 : value;
_Dropdown.onValueChanged.RemoveListener(OnValueChanged);
_Dropdown.Hide();
}
void OnDropdownClosed()
{
gameObject.SetActive(false);
if (_CurrentCallback != null)
{
var callback = _CurrentCallback;
_CurrentCallback = null;
callback(_ValueToReturn);
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 6b8d32910814fa14fa9f66f6d4140c3e
timeCreated: 1563649730
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
using System;
using UnityEngine;
using UnityEngine.Events;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Input
{
#if OSA_TV_TMPRO
public class TableViewFloatingDropdownTMPro : TMPro.TMP_Dropdown, ITableViewFloatingDropdown
{
public event Action Closed;
public int OptionsCount { get { return options.Count; } }
UnityEvent<int> ITableViewFloatingDropdown.onValueChanged { get { return base.onValueChanged; } }
public new DropdownEvent onValueChanged
{
get { throw new InvalidOperationException("FloatingDropdown.onValueChanged: Not available for this class"); }
set { throw new InvalidOperationException("FloatingDropdown.onValueChanged: Not available for this class"); }
}
public new void Show() { throw new InvalidOperationException("FloatingDropdown.Show() Not available for this class "); }
void ITableViewFloatingDropdown.Show() { base.Show(); }
protected override void DestroyDropdownList(GameObject dropdownList)
{
base.DestroyDropdownList(dropdownList);
if (Closed != null)
Closed();
}
}
#endif
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 6466b4c288bd80845849759bab8c0ec9
timeCreated: 1563649730
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,181 @@
using System;
using UnityEngine;
using Com.ForbiddenByte.OSA.Core;
#if OSA_TV_TMPRO
using TText = TMPro.TextMeshProUGUI;
#else
using TText = UnityEngine.UI.Text;
#endif
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Input
{
/// <summary>
/// Wrapper around a Text or a TMPro.TextMeshProUGUI.
/// It expects a Text component attached.
/// Or its TMPro counterpart if OSA_TV_TMPRO is defined
/// </summary>
public class TableViewText : MonoBehaviour
{
[Tooltip(
"Only needed if you want to display something like an ellipsis when the text shown isn't the entire text. " +
"If you're using TMPro, you won't need this, as TMPro already has an ellipsis adding mechanism built-in")]
[SerializeField]
Transform _ObjectToActivateOnOverflow = null;
/// <summary>
/// Keeping the same property name as the Unity's Text component
/// </summary>
public string text
{
get { return _Text.text; }
set { _Text.text = value; }
}
/// <summary>
/// Keeping the same property name as the Unity's Text component
/// </summary>
public bool supportRichText
{
#if OSA_TV_TMPRO
get { return _Text.richText; }
set { _Text.richText = value; }
#else
get { return _Text.supportRichText; }
set { _Text.supportRichText = value; }
#endif
}
/// <summary>
/// Keeping the same property name as the Unity's Text component
/// </summary>
public int fontSize
{
#if OSA_TV_TMPRO
get { return (int)(_Text.fontSize + .5f); /*rounding to nearest int*/ }
#else
get { return _Text.fontSize; }
#endif
set { _Text.fontSize = value; }
}
/// <summary>
/// Keeping the same property name as the Unity's Text component
/// </summary>
public Color color
{
get { return _Text.color; }
set { _Text.color = value; }
}
public RectTransform RT
{
get
{
if (!_RetrievedRT)
{
_RetrievedRT = true;
_RT = transform as RectTransform;
}
return _RT;
}
}
bool _RetrievedRT;
RectTransform _RT;
TText _Text;
//float _SavedAlpha;
void OnEnable()
{
if (_Text)
_Text.enabled = true;
}
void Awake()
{
_Text = GetComponent<TText>();
if (!_Text)
throw new OSAException("TableViewText: no " + typeof(TText).Name + " component found (expecting it because OSA_TV_TMPRO scripting symbol is defined)");
// // The builtin Text will disappear sometimes if verticalOverflow is not set to VerticalWrapMode.Overflow
//#if OSA_TV_TMPRO
// _Text.overflowMode = TMPro.TextOverflowModes.Ellipsis;
//#else
// //_Text.verticalOverflow = VerticalWrapMode.Overflow;
// if (_ObjectToActivateOnOverflow)
// SetOverflowActive(false);
//#endif
if (_ObjectToActivateOnOverflow)
SetOverflowActive(false);
}
// Manually add an Ellipsis if the text overflows, when using the built-in Text component
void Update()
{
// Update at larger intervals, for better performance
if (Time.frameCount % 10 == 0)
CheckOverflow();
}
void CheckOverflow()
{
if (!_Text || !_ObjectToActivateOnOverflow)
return;
bool active = false;
#if OSA_TV_TMPRO
active = _Text.isTextOverflowing;
#else
var textGen = _Text.cachedTextGenerator;
if (textGen != null)
active = textGen.characterCountVisible != _Text.text.Length;
#endif
SetOverflowActive(active);
}
void SetOverflowActive(bool overFlowActive)
{
//float scaleToSet = overFlowActive ? 1f : 0f;
//if (_ObjectToActivateOnOverflow.localScale.x == scaleToSet)
// return;
//var l = _ObjectToActivateOnOverflow.localScale;
//l.x = scaleToSet;
//_ObjectToActivateOnOverflow.localScale = l;
_ObjectToActivateOnOverflow.gameObject.SetActive(overFlowActive);
}
void OnDisable()
{
if (_Text)
_Text.enabled = false;
}
//public void SetEnabledByScalingGameObject(bool enabled)
//{
// transform.localScale = enabled ? Vector3.one : Vector3.zero;
//}
/// <summary>Returns the previous alpha</summary>
public float SetAlpha(float alpha)
{
float prevAlpha;
if (_Text)
{
var c = _Text.color;
prevAlpha = c.a;
c.a = alpha;
_Text.color = c;
}
else
prevAlpha = 0f;
return prevAlpha;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2025fbef5c26c1446aaaad9489fe362b
timeCreated: 1563447195
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,223 @@
//#define OSA_TV_TMPRO
using System;
using UnityEngine;
using Com.ForbiddenByte.OSA.Core;
using frame8.Logic.Misc.Other.Extensions;
using UnityEngine.UI;
#if OSA_TV_TMPRO
using TInputField = TMPro.TMP_InputField;
#else
using TInputField = UnityEngine.UI.InputField;
#endif
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Input
{
/// <summary>
/// It expects an InputField in the direct children, and a Text.
/// Or their TMPro counterparts if OSA_TV_TMPRO is defined
/// </summary>
public class TableViewTextInputController : MonoBehaviour
{
string text
{
get { return _InputField.text; }
set
{
_InputField.text = value;
UpdateSizeControllerText(_InputField.text);
}
}
/// <summary>
/// Keeping the same property name as the Unity's Text component
/// </summary>
public int fontSize
{
get { return _Text.fontSize; }
set
{
_Text.fontSize = value;
_InputField.textComponent.fontSize = value;
}
}
bool interactable { get { return _InputField.interactable; } set { _InputField.interactable = value; } }
//public TText textComponent { get { return _InputField.textComponent; } /*set { _InputField.textComponent = value; }*/ }
//public Image image { get { return _InputField.image; } /*set { _InputField.image = value; }*/ }
bool MultiLine
{
get { return _InputField.multiLine; }
set
{
_InputField.lineType = value ? TInputField.LineType.MultiLineNewline : TInputField.LineType.SingleLine;
}
}
//public bool CanAcceptInput { get { return isActiveAndEnabled && interactable && _InputField.isActiveAndEnabled; } }
RectTransform _RT;
TInputField _InputField;
TableViewText _Text;
LayoutElement _TextLayoutElement;
//LayoutElement _MyLayoutElement;
Action<string> _CurrentEndEditCallback;
Action _CurrentCancelCallback;
//void OnEnable()
//{
// if (_InputField)
// _InputField.enabled = true;
// if (_Text)
// _Text.enabled = true;
//}
void Awake()
{
_RT = transform as RectTransform;
_RT.pivot = new Vector2(0f, 1f); // top-left
for (int i = 0; i < transform.childCount; i++)
{
var ch = transform.GetChild(i);
if (!_InputField)
_InputField = ch.GetComponent<TInputField>();
if (!_Text)
_Text = ch.GetComponent<TableViewText>();
}
if (!_InputField)
throw new OSAException("TableView: no " + typeof(TInputField).Name + " component found in direct children");
if (!_Text)
throw new OSAException("TableView: no " + typeof(TableViewText).Name + " component field found in direct children");
if (!_InputField.textComponent)
throw new OSAException("TableView: the " + typeof(TInputField).Name + " has no text component specified");
var layEl = _InputField.GetComponent<LayoutElement>();
if (!layEl)
layEl = _InputField.gameObject.AddComponent<LayoutElement>();
layEl.ignoreLayout = true;
var rt = layEl.transform as RectTransform;
rt.MatchParentSize(true);
_TextLayoutElement = _Text.GetComponent<LayoutElement>();
if (!_TextLayoutElement)
_TextLayoutElement = _Text.gameObject.AddComponent<LayoutElement>();
_TextLayoutElement.preferredHeight = _TextLayoutElement.preferredWidth = -1f;
_TextLayoutElement.flexibleHeight = _TextLayoutElement.flexibleWidth = -1f;
//layEl.flexibleHeight = _FlexibleHeight;
//layEl.flexibleWidth = _FlexibleWidth;
var group = GetComponent<HorizontalLayoutGroup>();
if (!group)
group = gameObject.AddComponent<HorizontalLayoutGroup>();
group.childForceExpandHeight = group.childForceExpandWidth = false;
group.childControlHeight = group.childControlWidth = true;
//_MyLayoutElement = GetComponent<LayoutElement>();
//if (!_MyLayoutElement)
// _MyLayoutElement = gameObject.AddComponent<LayoutElement>();
var csf = GetComponent<ContentSizeFitter>();
if (!csf)
csf = gameObject.AddComponent<ContentSizeFitter>();
csf.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
csf.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
_InputField.onValueChanged.AddListener(UpdateSizeControllerText);
_InputField.onEndEdit.AddListener(OnEndEdit);
}
//void OnDisable()
//{
// if (_InputField)
// _InputField.enabled = false;
// if (_Text)
// _Text.enabled = false;
//}
void OnDestroy()
{
if (_InputField)
_InputField.onValueChanged.RemoveListener(UpdateSizeControllerText);
}
public void ShowFloating(RectTransform atParent, string initialText, bool multiLine, Action<string> onEndEdit, Action onCancel)
{
_CurrentEndEditCallback = null;
_CurrentCancelCallback = null;
gameObject.SetActive(true);
_CurrentEndEditCallback = onEndEdit;
_CurrentCancelCallback = onCancel;
var parRect = atParent.rect;
_RT.position = atParent.position;
_TextLayoutElement.minWidth = _TextLayoutElement.preferredWidth = parRect.width;
_TextLayoutElement.minHeight = _TextLayoutElement.preferredHeight = parRect.height;
//_RT.SetSizeFromParentEdgeWithCurrentAnchors(_RT.parent as RectTransform, RectTransform.Edge.Left, parRect.width);
_RT.TryClampPositionToParentBoundary();
MultiLine = multiLine;
ActivateInputField();
text = initialText;
}
public void Hide()
{
_CurrentEndEditCallback = null;
var cancelCallback = _CurrentCancelCallback;
bool callCancel = false;
_CurrentCancelCallback = null;
if (_InputField && _InputField.isActiveAndEnabled && _InputField.isFocused)
{
DeactivateInputField();
callCancel = true;
}
gameObject.SetActive(false);
if (callCancel && cancelCallback != null)
cancelCallback();
}
void ActivateInputField()
{
_InputField.ActivateInputField();
}
void DeactivateInputField()
{
_InputField.DeactivateInputField();
}
void UpdateSizeControllerText(string _)
{
_Text.text = _InputField.text;
}
void OnEndEdit(string text)
{
var c = _CurrentEndEditCallback;
Hide();
if (c != null)
c(text);
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 6cd1979504c51a44abb45d3367e45ae6
timeCreated: 1563190545
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 3443d754cee74af49bc0e8304ffcffd7
timeCreated: 1562924181
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,177 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView
{
/// <summary>
/// A convenience base class for a table's data used to store information for both the columns and tuples (aka 'rows').
/// Column sorting is not supported if items count exceeds <see cref="TableViewConst.MAX_TABLE_ENTRIES_FOR_ACCEPTABLE_COLUMN_ITERATION_TIME"/>
/// </summary>
public abstract class TableData : ITupleProvider
{
public ITableColumns Columns { get; protected set; }
public abstract int Count { get; }
public abstract bool ColumnClearingSupported { get; }
public bool ColumnSortingSupported { get { return _ColumnSortingSupported; } protected set { _ColumnSortingSupported = value; } }
bool _ColumnSortingSupported;
Dictionary<TableValueType, IComparer> _MapValueTypeToComparer;
/// <summary>
/// <paramref name="rowTuples"/> is the list of all the rows in the table, as tuples implementing <see cref="ITuple"/>
/// </summary>
public TableData(ITableColumns columnsProvider, bool columnSortingSupported)
{
Init(columnsProvider, _ColumnSortingSupported);
}
protected TableData()
{
}
/// <summary>Initialization method provided for inheritors, if needed</summary>
protected void Init(ITableColumns columns, bool columnSortingSupported)
{
Columns = columns;
int maxCountForSorting = TableViewConst.MAX_TABLE_ENTRIES_FOR_ACCEPTABLE_COLUMN_ITERATION_TIME;
if (Count > maxCountForSorting)
{
if (columnSortingSupported)
{
Debug.Log(typeof(TableData).Name +
": columnSortingSupported is true, but the count exceeds MAX_TABLE_ENTRIES_FOR_ACCEPTABLE_COLUMN_SORTING_TIME " +
"(" + Count + " > " + maxCountForSorting + "). Setting columnSortingSupported=false");
columnSortingSupported = false;
}
}
_ColumnSortingSupported = columnSortingSupported;
if (_ColumnSortingSupported)
{
_MapValueTypeToComparer = new Dictionary<TableValueType, IComparer>(9);
var rawComparerForNull = new ObjectComparerForNull();
_MapValueTypeToComparer[TableValueType.RAW] = rawComparerForNull;
_MapValueTypeToComparer[TableValueType.STRING] = StringComparer.OrdinalIgnoreCase;
_MapValueTypeToComparer[TableValueType.INT] = Comparer<int>.Default;
_MapValueTypeToComparer[TableValueType.LONG_INT] = Comparer<long>.Default;
_MapValueTypeToComparer[TableValueType.FLOAT] = Comparer<float>.Default;
_MapValueTypeToComparer[TableValueType.DOUBLE] = Comparer<double>.Default;
_MapValueTypeToComparer[TableValueType.ENUMERATION] = new EnumComparerSupportingNull();
_MapValueTypeToComparer[TableValueType.BOOL] = Comparer<bool>.Default;
_MapValueTypeToComparer[TableValueType.TEXTURE] = rawComparerForNull;
}
}
#region ITupleProvider
public abstract ITuple GetTuple(int index);
/// <summary>Expensive operation, if the table contains a lot of entries</summary>
public bool ChangeColumnSortType(int columnIndex, TableValueType columnType, TableValueSortType currentSorting, TableValueSortType nextSorting)
{
if (!_ColumnSortingSupported)
throw new InvalidOperationException("Cannot sort this table model because it was constructed with columnSortingSupported = false");
// No comparer means changing sort type is not possible
IComparer comparer;
if (!_MapValueTypeToComparer.TryGetValue(columnType, out comparer))
return false;
// Sort them
if (currentSorting == TableValueSortType.NONE)
{
bool asc = nextSorting == TableValueSortType.ASCENDING;
SortTuplesListIfSupported(new TupleComparerWrapper(comparer, asc, columnIndex));
}
else
// No sorting needed, just reversing the list, which is faster
ReverseTuplesListIfSupported();
return true;
}
/// <summary>Expensive operation, if the table contains a lot of entries</summary>
public void SetAllValuesOnColumn(int columnIndex, object sameColumnValueInAllTuples)
{
for (int i = 0; i < Count; i++)
{
GetTuple(i).SetValue(columnIndex, sameColumnValueInAllTuples);
}
}
#endregion
protected abstract bool SortTuplesListIfSupported(IComparer comparerToUse);
protected abstract bool ReverseTuplesListIfSupported();
protected class ObjectComparerForNull : IComparer
{
public int Compare(object x, object y)
{
if (x == null)
{
if (y != null)
return -1;
}
else if (y == null)
return 1;
return 0;
}
}
protected class EnumComparerSupportingNull : ObjectComparerForNull
{
Comparer<Enum> _SystemComparer = Comparer<Enum>.Default;
public int Compare(Enum x, Enum y)
{
if (x == null)
{
if (y != null)
return -1;
return 0; // both null => equal
}
if (y == null)
return 1;
return _SystemComparer.Compare(x, y);
}
}
protected class TupleComparerWrapper : IComparer
{
IComparer _Comparer;
readonly int _ColumnIndex;
int _Sign;
public TupleComparerWrapper(IComparer comparer, bool asc, int columnIndex)
{
_Comparer = comparer;
_ColumnIndex = columnIndex;
_Sign = asc ? 1 : -1;
}
int IComparer.Compare(object a, object b)
{
return _Sign * _Comparer.Compare(
(a as ITuple).GetValue(_ColumnIndex),
(b as ITuple).GetValue(_ColumnIndex)
);
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: ddd05b2504d685a4297c4bcc8aa4f12d
timeCreated: 1563565624
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,135 @@
using System;
using UnityEngine;
using UnityEngine.UI;
using Com.ForbiddenByte.OSA.Core;
using Com.ForbiddenByte.OSA.CustomAdapters.TableView.Input;
using UnityEngine.Serialization;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView
{
/// <summary>Base class for params to be used with a <see cref="GridAdapter{TParams, TCellVH}"/></summary>
[Serializable] // serializable, so it can be shown in inspector
public class TableParams : BaseParams
{
#region Configuration
[SerializeField]
TableConfig _Table = new TableConfig();
public TableConfig Table { get { return _Table; } set { _Table = value; } }
#endregion
/// <inheritdoc/>
public override void InitIfNeeded(IOSA iAdapter)
{
base.InitIfNeeded(iAdapter);
if (optimization.ScaleToZeroInsteadOfDisable)
{
Debug.Log(typeof(TableParams).Name + ": optimization.ScaleToZeroInsteadOfDisable is true, but this is not supported with a TableView. Setting back to false...");
optimization.ScaleToZeroInsteadOfDisable = false;
}
if (Navigation.Enabled)
{
Debug.Log(typeof(TableParams).Name + ": Navigation.Enabled is true, but this is not yet supported with a TableView. Setting back to false...");
Navigation.Enabled = false;
}
Table.InitIfNeeded(iAdapter);
DefaultItemSize = Table.TuplePrefabSize;
}
[Serializable]
public class TableConfig
{
[SerializeField]
RectTransform _TuplePrefab = null;
/// <summary>The prefab to use for each tuple (aka row in database)</summary>
public RectTransform TuplePrefab { get { return _TuplePrefab; } set { _TuplePrefab = value; } }
[SerializeField]
[Tooltip("The prefab to use for the columns header. Can be the same as TuplePrefab")]
RectTransform _ColumnsTuplePrefab = null;
/// <summary>The prefab to use for the columns header. Can be the same as <see cref="TuplePrefab"/></summary>
public RectTransform ColumnsTuplePrefab { get { return _ColumnsTuplePrefab; } set { _ColumnsTuplePrefab = value; } }
[SerializeField]
[FormerlySerializedAs("_ColumnsHeaderSize")]
[Tooltip("Size (height for vertical ScrollViews, width otherwise) of the header containing the columns. Leave to -1 to use the prefab's one")]
float _ColumnsTupleSize = -1f;
/// <summary>Size (height for vertical ScrollViews, width otherwise) of the header containing the columns. Leave to -1 to use the prefab's one</summary>
public float ColumnsTupleSize { get { return _ColumnsTupleSize; } set { _ColumnsTupleSize = value; } }
[SerializeField]
[FormerlySerializedAs("_ColumnsHeaderSpacing")]
[Tooltip("Additional space between the header and the actual content")]
float _ColumnsTupleSpacing = 0f;
/// <summary>Additional space between the header and the actual content</summary>
public float ColumnsTupleSpacing { get { return _ColumnsTupleSpacing; } set { _ColumnsTupleSpacing = value; } }
[SerializeField]
Scrollbar _ColumnsScrollbar = null;
public Scrollbar ColumnsScrollbar { get { return _ColumnsScrollbar; } set { _ColumnsScrollbar = value; } }
[Tooltip("A GameObject having a component that implements ITableViewFloatingDropdown")]
[SerializeField]
RectTransform _FloatingDropdownPrefab = null;
/// <summary>A GameObject having a component that implements <see cref="Input.ITableViewFloatingDropdown"/></summary>
public RectTransform FloatingDropdownPrefab { get { return _FloatingDropdownPrefab; } set { _FloatingDropdownPrefab = value; } }
[Tooltip("Used for text input")]
[SerializeField]
TableViewTextInputController _TextInputControllerPrefab = null;
/// <summary>Used for text input. See <see cref="Input.TableViewTextInputController"/></summary>
public TableViewTextInputController TextInputControllerPrefab { get { return _TextInputControllerPrefab; } set { _TextInputControllerPrefab = value; } }
[Tooltip("A GameObject having a component that implements ITableViewOptionsPanel")]
[SerializeField]
RectTransform _OptionsPanel = null;
/// <summary>A GameObject having a component that implements <see cref="ITableViewOptionsPanel"/></summary>
public RectTransform OptionsPanel { get { return _OptionsPanel; } set { _OptionsPanel = value; } }
public float TuplePrefabSize
{
get
{
if (!TuplePrefab)
throw new OSAException(typeof(TableParams).Name + ": the TuplePrefab was not set. Please set it through inspector or in code");
if (_TuplePrefabSize == -1f)
_TuplePrefabSize = _IsHorizontal ? TuplePrefab.rect.width : TuplePrefab.rect.height;
return _TuplePrefabSize;
}
}
float _TuplePrefabSize = -1f;
bool _IsHorizontal;
public void InitIfNeeded(IOSA iAdapter)
{
string sceneObjectErrSuffix = " should be non-null, and a scene object that's active in hierarchy (i.e. not directly assigned from project view)";
if (TuplePrefab == null || !TuplePrefab.gameObject.activeInHierarchy)
throw new OSAException("TuplePrefab" + sceneObjectErrSuffix);
if (ColumnsTuplePrefab == null || !ColumnsTuplePrefab.gameObject.activeInHierarchy)
throw new OSAException("ColumnsTuplePrefab" + sceneObjectErrSuffix);
_IsHorizontal = iAdapter.IsHorizontal;
if (_ColumnsTupleSize == -1f)
_ColumnsTupleSize = _TuplePrefab.rect.size[_IsHorizontal ? 0 : 1];
var adapterParams = iAdapter.BaseParameters;
if (TuplePrefab.parent != adapterParams.ScrollViewRT)
LayoutRebuilder.ForceRebuildLayoutImmediate(TuplePrefab.parent as RectTransform);
else
LayoutRebuilder.ForceRebuildLayoutImmediate(TuplePrefab);
adapterParams.AssertValidWidthHeight(TuplePrefab);
_TuplePrefabSize = -1f; // so the prefab's size will be recalculated next time is accessed
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 8021a0e05fa76f44183f1c3f85e3ac7b
timeCreated: 1562924181
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,17 @@
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView
{
public enum TableResizingMode
{
NONE,
AUTO_FIT_TUPLE_CONTENT,
/// <summary>Not implemented yet</summary>
MANUAL_COLUMNS,
MANUAL_TUPLES,
/// <summary>Not fully implemented yet. Behaves exactly like <see cref="MANUAL_TUPLES"/></summary>
MANUAL_COLUMNS_AND_TUPLES,
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 19186eb5a20b198488699d59403cd557
timeCreated: 1563262587
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,19 @@
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Serialization;
using Com.ForbiddenByte.OSA.Core;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView
{
public enum TableValueSortType
{
/// <summary>
/// The default order in the provided data. Once changed, it'll only have one of <see cref="ASCENDING"/> or <see cref="DESCENDING"/>
/// </summary>
NONE,
ASCENDING,
DESCENDING
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: e277c8b98e2bce54a9ed2e7560313fa4
timeCreated: 1563003860
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,38 @@
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Serialization;
using Com.ForbiddenByte.OSA.Core;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView
{
public enum TableValueType
{
/// <summary>
/// Will try to call <see cref="String.ToString"/> on the value
/// </summary>
RAW,
STRING,
INT,
LONG_INT,
FLOAT,
DOUBLE,
/// <summary>
/// Will try to cast the value to an integer, then retrieve its enum value using the <see cref="IColumnInfo.EnumValueType"/>.
/// If not successful, the raw integer will be shown
/// </summary>
ENUMERATION,
BOOL,
TEXTURE,
// TBA
//ARRAY
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 9e0b31d7576be5d488cc7772275878a4
timeCreated: 1562942182
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView
{
public static class TableViewConst
{
public const int MAX_TABLE_ENTRIES_FOR_ACCEPTABLE_COLUMN_ITERATION_TIME = 50 * 1000;
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 584ba99a42277a046b65fb4239aaf352
timeCreated: 1563565624
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,22 @@
using System.Collections.Generic;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView
{
public static class TableViewUtil
{
public static TTuple CreateTupleWithEmptyValues<TTuple>(int length)
where TTuple : ITuple, new()
{
var emptyValues = new List<object>(length);
for (int i = 0; i < length; i++)
emptyValues.Add(null);
var t = new TTuple();
t.ResetValues(emptyValues, false);
return t;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: ff4f71327f0a33e4fbc011d857c02a5f
timeCreated: 1563364766
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 03800226adeeeee41b6472d30ca2e24a
folderAsset: yes
timeCreated: 1562924181
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: db67da17f13a51044a296400e50b83fa
folderAsset: yes
timeCreated: 1562924181
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,11 @@
using System;
using UnityEngine;
using Com.ForbiddenByte.OSA.Core;
using frame8.Logic.Misc.Other.Extensions;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Tuple.Basic
{
public class BasicHeaderTupleAdapter : TupleAdapter<TupleParams, BasicHeaderValueViewsHolder>
{
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 59d0576dc4bcf2b4db93b01ab0ad8ef5
timeCreated: 1563014573
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,66 @@
using Com.ForbiddenByte.OSA.Core;
using Com.ForbiddenByte.OSA.CustomAdapters.TableView.Input;
using frame8.Logic.Misc.Other.Extensions;
using UnityEngine;
using UnityEngine.UI;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Tuple.Basic
{
/// <summary>
/// A views holder for a value inside the header.
/// See <see cref="TupleValueViewsHolder"/>
/// </summary>
public class BasicHeaderValueViewsHolder : TupleValueViewsHolder
{
RectTransform _ArrowRT;
public override void CollectViews()
{
base.CollectViews();
_ArrowRT = root.GetComponentAtPath<RectTransform>("SortArrow");
}
public override void UpdateViews(object value, ITableColumns columnsProvider)
{
string asStr = (string)value;
if (columnsProvider.GetColumnState(ItemIndex).CurrentlyReadOnly)
{
asStr += "\n<color=#00000030><size=10>Read-only</size></color>";
}
TextComponent.text = asStr;
var sortType = columnsProvider.GetColumnState(ItemIndex).CurrentSortingType;
UpdateArrowFromSortType(sortType);
}
void UpdateArrowFromSortType(TableValueSortType type)
{
if (!_ArrowRT)
return;
if (_ArrowRT)
{
bool valid = type != TableValueSortType.NONE;
float scale;
float zRotation;
if (valid)
{
scale = 1f;
zRotation = 90f * (type == TableValueSortType.ASCENDING ? 1f : -1f);
}
else
{
scale = .5f;
zRotation = 0f;
}
_ArrowRT.localScale = Vector3.one * scale;
var euler = _ArrowRT.localRotation.eulerAngles;
euler.z = zRotation;
_ArrowRT.localRotation = Quaternion.Euler(euler);
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 031ad4cc2148ba9459c83d86713dd18f
timeCreated: 1563014573
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,11 @@
using System;
using UnityEngine;
using Com.ForbiddenByte.OSA.Core;
using frame8.Logic.Misc.Other.Extensions;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Tuple.Basic
{
public class BasicTupleAdapter : TupleAdapter<TupleParams, BasicTupleValueViewsHolder>
{
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: e25d0e466dcb6f2428f2be388929b642
timeCreated: 1562924181
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,270 @@
using System;
using UnityEngine;
using UnityEngine.UI;
using frame8.Logic.Misc.Other.Extensions;
using Com.ForbiddenByte.OSA.Core;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Tuple.Basic
{
/// <summary>
/// A views holder for a value inside a row from a database, which casts the value passed to it to a
/// string or Texture and binds it to a Text or RawImage component, respectively
/// </summary>
public class BasicTupleValueViewsHolder : TupleValueViewsHolder
{
RectTransform _ImagePanel;
RawImage _Image;
RectTransform _TextPanel;
RectTransform _TogglePanel;
Toggle _Toggle;
bool _ForwardValueChanges = true;
RectTransform _InputAvailableDot;
//bool _IsCurrentlyReadonly;
//LayoutElement _ImagePanelLE;
//LayoutElement _TextPanelLE;
//LayoutElement _TogglePanelLE;
public override void CollectViews()
{
base.CollectViews();
root.GetComponentAtPath("ImagePanel", out _ImagePanel);
_ImagePanel.GetComponentAtPath("Image", out _Image);
root.GetComponentAtPath("TextPanel", out _TextPanel);
//root.GetComponentAtPath("TogglePanel", out _TogglePanel);
//_TogglePanel.GetComponentAtPath("Toggle", out _Toggle);
root.GetComponentAtPath("Toggle", out _TogglePanel);
_Toggle = _TogglePanel.GetComponent<Toggle>();
root.GetComponentAtPath("InputAvailableDot", out _InputAvailableDot);
_Toggle.onValueChanged.AddListener(OnToggleValueChanged);
//_ImagePanelLE = _ImagePanel.GetComponent<LayoutElement>();
//if (!_ImagePanelLE)
// _ImagePanelLE = _ImagePanel.gameObject.AddComponent<LayoutElement>();
//_ImagePanelLE.ignoreLayout = true;
//_TextPanelLE = _TextPanel.GetComponent<LayoutElement>();
//if (!_TextPanelLE)
// _TextPanelLE = _TextPanel.gameObject.AddComponent<LayoutElement>();
//_TextPanelLE.ignoreLayout = true;
//_TogglePanelLE = _TogglePanel.GetComponent<LayoutElement>();
//if (!_TogglePanelLE)
// _TogglePanelLE = _TogglePanel.gameObject.AddComponent<LayoutElement>();
//_TogglePanelLE.ignoreLayout = true;
}
public override void UpdateViews(object value, ITableColumns columnsProvider)
{
bool isNull = value == null;
var column = columnsProvider.GetColumnState(ItemIndex);
if (isNull)
{
UpdateAsNullText(column);
return;
}
UpdateViews(value, column);
}
void UpdateAsNullText(IColumnState column)
{
UpdateAsText("<color=#88444499>NULL</color>", false);
SetInputAvailable(!column.CurrentlyReadOnly && IsStringInputType(column.Info.ValueType));
}
void UpdateIntOrLong(string asStr, bool canChangeValue)
{
bool updatedSuccessfully = UpdateAsText(asStr, canChangeValue);
SetInputAvailable(canChangeValue && updatedSuccessfully);
}
void UpdateFloatOrDouble(string asStr, bool canChangeValue)
{
bool updatedSuccessfully = UpdateAsText(asStr, canChangeValue);
SetInputAvailable(canChangeValue && updatedSuccessfully);
}
/// <summary>
/// Expecting value to be non-null
/// </summary>
void UpdateViews(object value, IColumnState column)
{
bool? textInputAvailable = null;
try
{
bool canChangeValue = !column.CurrentlyReadOnly;
bool updatedSuccessfully;
switch (column.Info.ValueType)
{
case TableValueType.RAW:
UpdateAsText("<color=#22552266>" + value.GetType().Name + "</color> " + value.ToString(), false);
textInputAvailable = false;
break;
case TableValueType.STRING:
updatedSuccessfully = UpdateAsText((string)value, canChangeValue);
textInputAvailable = canChangeValue && updatedSuccessfully;
break;
case TableValueType.INT:
UpdateIntOrLong(((int)value).ToString(), canChangeValue);
break;
case TableValueType.LONG_INT:
UpdateIntOrLong(((long)value).ToString(), canChangeValue);
break;
case TableValueType.FLOAT:
float fl = (float)value;
UpdateFloatOrDouble(fl.ToString(OSAConst.FLOAT_TO_STRING_CONVERSION_SPECIFIER_PRESERVE_PRECISION), canChangeValue);
break;
case TableValueType.DOUBLE:
double db = (double)value;
//string text = val.ToString();
// Spent like 2 hours to find out C# double doesn't always convert successfully to string by default without losing precision, smh.
// https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#RFormatString
// Furthermore, they have a useless R specifier that is more annoying because it doesn't always work. Probably there for historical reasons.
// The odd "G17" should be used for making sure a string that doesn't lose precision is output
UpdateFloatOrDouble(db.ToString(OSAConst.DOUBLE_TO_STRING_CONVERSION_SPECIFIER_PRESERVE_PRECISION), canChangeValue);
break;
case TableValueType.ENUMERATION:
string textToSet = null;
if (column.Info.EnumValueType != null && column.Info.EnumValueType.IsEnum)
try { textToSet = Enum.GetName(column.Info.EnumValueType, value); } catch { }
bool validEnum = !string.IsNullOrEmpty(textToSet);
if (!validEnum)
textToSet = value.ToString();
updatedSuccessfully = UpdateAsText(textToSet, false /*enum text is changed by other means, not direct text editing*/);
textInputAvailable = false;
break;
case TableValueType.BOOL:
updatedSuccessfully = UpdateAsCheckbox((bool)value, canChangeValue);
break;
case TableValueType.TEXTURE:
UpdateAsImage((Texture)value);
break;
}
if (textInputAvailable != null)
SetInputAvailable(textInputAvailable.Value);
}
catch (Exception e)
{
Debug.LogError("Exception pre-details: " + column.Info.ValueType + ", value " + (value == null ? "NULL" : value));
throw e;
}
}
protected void UpdateAsImage(Texture texture)
{
if (ActivatePanelOnlyFor(_Image))
{
_Image.texture = texture;
}
SetInputAvailable(false);
}
protected bool UpdateAsText(string text, bool editable)
{
if (TextComponent)
TextComponent.supportRichText = !editable;
if (ActivatePanelOnlyFor(TextComponent))
{
// Don't forward changes that are done from the model
_ForwardValueChanges = false;
HasPendingTransversalSizeChanges = TextComponent.text != text;
if (HasPendingTransversalSizeChanges)
{
TextComponent.text = text;
}
_ForwardValueChanges = true;
return true;
}
return false;
}
protected bool UpdateAsCheckbox(bool value, bool editable)
{
if (_Toggle)
_Toggle.interactable = editable;
SetInputAvailable(false);
if (ActivatePanelOnlyFor(_Toggle))
{
// Don't forward changes that are done from the model
_ForwardValueChanges = false;
_Toggle.isOn = value;
_ForwardValueChanges = true;
return true;
}
return false;
}
bool ActivatePanelOnlyFor(UnityEngine.MonoBehaviour uiElement)
{
bool result = false;
if (_Image)
{
bool act = _Image == uiElement;
_ImagePanel.gameObject.SetActive(act);
result = result || act;
}
if (TextComponent)
{
bool act = TextComponent == uiElement;
_TextPanel.gameObject.SetActive(act);
result = result || act;
}
if (_Toggle)
{
bool act = _Toggle == uiElement;
_TogglePanel.gameObject.SetActive(act);
result = result || act;
}
return result;
}
void SetInputAvailable(bool available)
{
//_InputAvailableDot.localScale = available ? Vector3.one : Vector3.zero;
_InputAvailableDot.gameObject.SetActive(available);
}
void OnToggleValueChanged(bool newValue)
{
if (_ForwardValueChanges && _TogglePanel.gameObject.activeSelf) // just a sanity check
NotifyValueChangedFromInput(newValue);
}
bool IsStringInputType(TableValueType type)
{
return type == TableValueType.STRING
|| type == TableValueType.INT
|| type == TableValueType.LONG_INT
|| type == TableValueType.FLOAT
|| type == TableValueType.DOUBLE;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: d4013a7516d696f45945508fbdc5f77b
timeCreated: 1562924181
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,21 @@
using System;
using UnityEngine;
using Com.ForbiddenByte.OSA.Core;
using frame8.Logic.Misc.Other.Extensions;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Tuple
{
public interface ITupleAdapter : IOSA
{
event Action<TupleValueViewsHolder> ValueClicked;
event Action<TupleValueViewsHolder, object> ValueChangedFromInput;
TupleParams TupleParameters { get; }
ITupleAdapterSizeHandler SizeHandler { get; set; }
void ResetWithTuple(ITuple tupleModel, ITableColumns columnsProvider);
void ForceUpdateValueViewsHolderIfVisible(int withItemIndex);
void ForceUpdateValueViewsHolder(TupleValueViewsHolder vh);
void OnWillBeRecycled(float newSize);
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: ed74f9cf454c5ef4085f35804d8b3654
timeCreated: 1562927162
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,354 @@
using System;
using UnityEngine;
using Com.ForbiddenByte.OSA.Core;
using frame8.Logic.Misc.Other.Extensions;
using System.Collections.Generic;
using UnityEngine.UI;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Tuple
{
public abstract class TupleAdapter<TParams, TTupleValueViewsHolder> : OSA<TParams, TTupleValueViewsHolder>, ITupleAdapter
where TParams : TupleParams, new()
where TTupleValueViewsHolder : TupleValueViewsHolder, new()
{
public event Action<TupleValueViewsHolder> ValueClicked;
public event Action<TupleValueViewsHolder, object> ValueChangedFromInput;
public TupleParams TupleParameters { get { return _Params; } }
public ITupleAdapterSizeHandler SizeHandler { get; set; }
public RectTransform RTransform
{
get
{
if (_RectTransform == null)
_RectTransform = transform as RectTransform;
return _RectTransform;
}
}
// Can be null (for example, when data is not available and will be provided later)
protected ITuple _CurrentTuple;
protected ITableColumns _ColumnsProvider;
RectTransform _RectTransform;
float _MyPrevKnownTransvSize;
public void ResetWithTuple(ITuple tuple, ITableColumns columnsProvider)
{
if (!IsInitialized)
Init();
_CurrentTuple = tuple;
_ColumnsProvider = columnsProvider;
int columnsCount = _ColumnsProvider.ColumnsCount;
if (GetItemsCount() == columnsCount)
{
// Save massive amounts of performance by just updating the existing views holders rather than resetting the view.
// Same count means existing items don't need to be enabled/disabled/destroyed, because their position won't change
var thisAsITupleAdapter = this as ITupleAdapter;
for (int i = 0; i < VisibleItemsCount; i++)
{
var vh = GetItemViewsHolder(i);
thisAsITupleAdapter.ForceUpdateValueViewsHolder(vh);
}
// Force a ComputeVisibility pass, if needed
//SetNormalizedPosition(GetNormalizedPosition());
}
else
ResetItems(columnsCount);
}
/// <summary>
/// Start was overridden so that Init is not called automatically (see base.Start()), because this is done manually in the first call of ResetWithTuple().
/// <para>See <see cref="OSA{TParams, TItemViewsHolder}.Start"/></para>
/// </summary>
protected sealed override void Start()
{}
protected override void Update()
{
base.Update();
if (!IsInitialized)
return;
CheckResizing();
}
protected override void OnInitialized()
{
_MyPrevKnownTransvSize = GetMyCurrentTransversalSize();
base.OnInitialized();
}
protected override void CollectItemsSizes(ItemCountChangeMode changeMode, int count, int indexIfInsertingOrRemoving, ItemsDescriptor itemsDesc)
{
base.CollectItemsSizes(changeMode, count, indexIfInsertingOrRemoving, itemsDesc);
if (changeMode == ItemCountChangeMode.REMOVE || count == 0)
return;
int indexOfFirstItemThatWillChangeSize;
if (changeMode == ItemCountChangeMode.RESET)
indexOfFirstItemThatWillChangeSize = 0;
else
indexOfFirstItemThatWillChangeSize = indexIfInsertingOrRemoving;
int end = indexOfFirstItemThatWillChangeSize + count;
itemsDesc.BeginChangingItemsSizes(indexOfFirstItemThatWillChangeSize);
for (int i = indexOfFirstItemThatWillChangeSize; i < end; i++)
{
var size = _ColumnsProvider.GetColumnState(i).CurrentSize;
bool useDefault = size == -1;
if (useDefault)
size = Parameters.DefaultItemSize;
itemsDesc[i] = size;
}
itemsDesc.EndChangingItemsSizes();
}
protected override TTupleValueViewsHolder CreateViewsHolder(int itemIndex)
{
var vh = new TTupleValueViewsHolder();
vh.Init(_Params.ItemPrefab, _Params.Content, itemIndex);
vh.SetClickListener(() => OnValueClicked(vh));
vh.SetValueChangedFromInputListener(value => OnValueChangedFromInput(vh, value));
// Fixing text that disappears because all layout elements and groups are disabled on the prefab when resize mode is none, for optimization purposes
if (_Params.ResizingMode == TableResizingMode.NONE)
{
vh.TextComponent.RT.MatchParentSize(true);
}
return vh;
}
protected override void UpdateViewsHolder(TTupleValueViewsHolder newOrRecycled)
{
object value;
if (_CurrentTuple == null) // data pending
value = null;
else
value = _CurrentTuple.GetValue(newOrRecycled.ItemIndex);
newOrRecycled.UpdateViews(value, _ColumnsProvider);
}
protected override void OnBeforeDestroyViewsHolder(TTupleValueViewsHolder vh, bool isActive)
{
vh.SetValueChangedFromInputListener(null);
base.OnBeforeDestroyViewsHolder(vh, isActive);
}
protected override void OnBeforeRecycleOrDisableViewsHolder(TTupleValueViewsHolder inRecycleBinOrVisible, int newItemIndex)
{
base.OnBeforeRecycleOrDisableViewsHolder(inRecycleBinOrVisible, newItemIndex);
if (_Params.ResizingMode == TableResizingMode.AUTO_FIT_TUPLE_CONTENT)
{
// Make sure items that will just become visible will be rebuilt shortly
if (newItemIndex >= 0)
inRecycleBinOrVisible.HasPendingTransversalSizeChanges = true;
else
inRecycleBinOrVisible.HasPendingTransversalSizeChanges = false;
}
}
protected virtual void OnValueClicked(TTupleValueViewsHolder vh)
{
if (ValueClicked != null)
ValueClicked(vh);
}
protected virtual void OnValueChangedFromInput(TTupleValueViewsHolder vh, object newValue)
{
if (ValueChangedFromInput != null)
ValueChangedFromInput(vh, newValue);
}
void ITupleAdapter.ForceUpdateValueViewsHolderIfVisible(int withItemIndex)
{
var vh = GetItemViewsHolderIfVisible(withItemIndex);
if (vh != null)
UpdateViewsHolder(vh);
}
void ITupleAdapter.ForceUpdateValueViewsHolder(TupleValueViewsHolder vh)
{
UpdateViewsHolder(vh as TTupleValueViewsHolder);
}
void ITupleAdapter.OnWillBeRecycled(float newSize)
{
float transvPad = (float)_InternalState.layoutInfo.transversalPaddingStartPlusEnd;
bool autoFitEnabled = _Params.ResizingMode == TableResizingMode.AUTO_FIT_TUPLE_CONTENT;
var axis = (RectTransform.Axis)(1 - _InternalState.hor0_vert1);
// When this entire tuple will be recycled, reset every vh, visible or in recycle cache
for (int i = 0; i < VisibleItemsCount; i++)
{
var vh = GetItemViewsHolder(i);
OnBeforeRecycleOrDisableViewsHolder(vh, -1);
if (autoFitEnabled)
{
float valueItemSize = newSize - transvPad;
vh.root.SetSizeFromParentEdgeWithCurrentAnchors(_Params.Content, _InternalState.transvStartEdge, valueItemSize);
}
}
if (autoFitEnabled)
{
for (int i = 0; i < RecyclableItemsCount; i++)
{
var vh = _RecyclableItems[i];
if (autoFitEnabled)
{
float valueItemSize = newSize - transvPad;
// SetSizeWithCurrentAnchors is more efficient when positioning is not important
vh.root.SetSizeWithCurrentAnchors(axis, valueItemSize);
}
}
for (int i = 0; i < BufferedRecyclableItemsCount; i++)
{
var vh = _BufferredRecyclableItems[i];
if (autoFitEnabled)
{
float valueItemSize = newSize - transvPad;
// SetSizeWithCurrentAnchors is more efficient when positioning is not important
vh.root.SetSizeWithCurrentAnchors(axis, valueItemSize);
}
}
}
}
float GetMyCurrentTransversalSize()
{
return _Params.ScrollViewRT.rect.size[1 - _InternalState.hor0_vert1];
}
// When a children's size exceeds this adapter's size or all
// children become smaller than this adapter (meaning the adapter should be shrunk by the parent)
void CheckResizing()
{
float myTransvSize = GetMyCurrentTransversalSize();
float biggestSize = myTransvSize;
float biggestItemTransvSizePlusTransvPadding = 0f;
bool resizeNeeded = false;
if (myTransvSize != _MyPrevKnownTransvSize)
{
_MyPrevKnownTransvSize = myTransvSize;
resizeNeeded = true;
}
// Check if any item has an even bigger size
int indexOfBiggestItem = -1;
float transvPaddingStart = (float)_InternalState.layoutInfo.transversalPaddingContentStart;
float transvPaddingStartPlusEnd = (float)_InternalState.layoutInfo.transversalPaddingStartPlusEnd;
bool foundItemBiggerThanMe = false;
for (int i = 0; i < VisibleItemsCount; i++)
{
var vh = GetItemViewsHolder(i);
RebuildVHIfNeeded(vh);
float transvSize = vh.root.rect.size[1 - _InternalState.hor0_vert1];
float transvSizePlusTransvPadding = transvSize + transvPaddingStartPlusEnd;
if (transvSizePlusTransvPadding > biggestSize)
{
biggestSize = transvSizePlusTransvPadding;
foundItemBiggerThanMe = true;
indexOfBiggestItem = i;
}
if (transvSizePlusTransvPadding > biggestItemTransvSizePlusTransvPadding)
biggestItemTransvSizePlusTransvPadding = transvSizePlusTransvPadding;
}
var vhsSmallerThanBiggestSizeMinusPadding = new List<TTupleValueViewsHolder>();
for (int i = 0; i < VisibleItemsCount; i++)
{
var vh = GetItemViewsHolder(i);
float transvSize = vh.root.rect.size[1 - _InternalState.hor0_vert1];
float transvSizePlusTransvPadding = transvSize + transvPaddingStartPlusEnd;
if (transvSizePlusTransvPadding < biggestSize)
{
//Debug.Log(i + ", " + (biggestSize - transvSizePlusTransvPadding) + ", " + transvPaddingStartPlusEnd);
vhsSmallerThanBiggestSizeMinusPadding.Add(vh);
//vhsSmallerThanMeSizes.Add(transvSize);
}
}
if (foundItemBiggerThanMe)
{
resizeNeeded = true;
}
float sizeToSet = biggestSize;
if (!resizeNeeded)
{
// All items are smaller and also this adapter's size didn't change => consider shrinking
if (biggestItemTransvSizePlusTransvPadding > 0f)
{
// Only if it's a significant drop in size
if (myTransvSize - biggestItemTransvSizePlusTransvPadding > 1f)
{
resizeNeeded = true;
sizeToSet = biggestItemTransvSizePlusTransvPadding;
}
}
}
if (_Params.ResizingMode == TableResizingMode.AUTO_FIT_TUPLE_CONTENT)
{
float itemsSizeToSet = sizeToSet - transvPaddingStartPlusEnd;
// Resize smaller items to fill the empty space
for (int i = 0; i < vhsSmallerThanBiggestSizeMinusPadding.Count; i++)
{
// The biggest item is already sized correctly, is indexOfBiggestItem is not -1
if (i == indexOfBiggestItem)
continue;
var vh = vhsSmallerThanBiggestSizeMinusPadding[i];
//Debug.Log(i + ": " + indexOfBiggestItem + ", " + itemsSizeToSet + ", vh " + vh.root.rect.height);
//_Params.SetPaddingTransvEndToAchieveTansvSizeFor(vh.root, vh.LayoutGroup, itemsSizeToSet);
vh.root.SetInsetAndSizeFromParentEdgeWithCurrentAnchors(_InternalState.layoutInfo.transvStartEdge, transvPaddingStart, itemsSizeToSet);
}
}
if (resizeNeeded)
{
//Debug.Log(resizeNeeded);
//if (indexOfBiggestItem != -1)
// Debug.Log(", 1 " + biggestItemTransvSizePlusTransvPadding + ", b " + myTransvSize + ", c " + biggestSize + ", d " + indexOfBiggestItem, gameObject);
if (SizeHandler != null)
SizeHandler.RequestChangeTransversalSize(this, sizeToSet);
}
}
void RebuildVHIfNeeded(TTupleValueViewsHolder vh)
{
if (vh.HasPendingTransversalSizeChanges)
{
if (_Params.ResizingMode == TableResizingMode.AUTO_FIT_TUPLE_CONTENT)
{
// Only rebuild strings
if (_ColumnsProvider.GetColumnState(vh.ItemIndex).Info.ValueType == TableValueType.STRING)
{
if (vh.CSF)
ForceRebuildViewsHolder(vh);
}
}
vh.HasPendingTransversalSizeChanges = false;
}
}
}
public interface ITupleAdapterSizeHandler
{
void RequestChangeTransversalSize(ITupleAdapter adapter, double size);
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 100d5263147813041bd603352d25aee0
timeCreated: 1562924181
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,223 @@
using System;
using Com.ForbiddenByte.OSA.Core;
using Com.ForbiddenByte.OSA.CustomParams;
using UnityEngine;
using UnityEngine.UI;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Tuple
{
[Serializable]
public class TupleParams : BaseParamsWithPrefab
{
[SerializeField]
RectTransform _EdgeDragger = null;
public RectTransform EdgeDragger { get { return _EdgeDragger; } set { _EdgeDragger = value; } }
[SerializeField]
TableResizingMode _ResizingMode = TableResizingMode.NONE;
public TableResizingMode ResizingMode { get { return _ResizingMode; } set { _ResizingMode = value; } }
public override void InitIfNeeded(IOSA iAdapter)
{
if (ItemPrefab == null || !ItemPrefab.gameObject.activeInHierarchy)
throw new OSAException(
"A Tuple's value prefab should be non-null, and a scene object (i.e. not directly assigned from project view) active in hierarchy." +
"This error may be caused by the Tuple prefab itself not being an object present in scene");
base.InitIfNeeded(iAdapter);
var tupleAdapter = iAdapter as ITupleAdapter;
if (tupleAdapter == null)
throw new OSAException(typeof(TupleParams).Name + ": No script implementing " + typeof(ITupleAdapter).Name + " found on TuplePrefab");
bool prefabRebuildNeeded = InitResizingNeeded(tupleAdapter);
if (prefabRebuildNeeded)
InitItemPrefab();
else
AssertValidWidthHeight(ItemPrefab);
if (ContentPadding.left == -1 || ContentPadding.right == -1 || ContentPadding.top == -1 || ContentPadding.bottom == -1)
throw new OSAException(typeof(TupleParams).Name + ": A Tuple's ContentPadding isn't allowed to have negative components in any direction. Set them to zero or positive values");
if (Navigation.Enabled)
{
Debug.Log(typeof(TupleParams).Name + ": Navigation.Enabled is true, but this is not yet supported with a TupleAdapter. Setting back to false...");
Navigation.Enabled = false;
}
}
// Returns if prefab needs to be rebuilt
bool InitResizingNeeded(ITupleAdapter tupleAdapter)
{
var tupleParams = tupleAdapter.TupleParameters;
var valuePrefab = tupleParams.ItemPrefab;
var csf = valuePrefab.GetComponent<ContentSizeFitter>();
var layoutGroup = valuePrefab.GetComponent<LayoutGroup>();
if (ResizingMode == TableResizingMode.AUTO_FIT_TUPLE_CONTENT)
{
if (EdgeDragger && EdgeDragger.gameObject.activeSelf)
EdgeDragger.gameObject.SetActive(false);
if (tupleParams.ItemTransversalSize != -1f)
{
Debug.Log(
typeof(TupleParams).Name + ": ItemTransversalSize needs to be -1, " +
"because ResizingMode is " + ResizingMode +
". To avoid this message, manually set it in inspector"
);
tupleParams.ItemTransversalSize = -1f;
}
if (!csf)
{
//Debug.Log(
// typeof(TupleParams).Name + ": ResizingMode is " + TableResizingMode.AUTO_FIT_TUPLE_CONTENT +
// ", but no ContentSizeFitter found on tuple's value prefab. Adding one..."
//);
csf = valuePrefab.gameObject.AddComponent<ContentSizeFitter>();
}
var prefr = ContentSizeFitter.FitMode.PreferredSize;
var unconstr = ContentSizeFitter.FitMode.Unconstrained;
csf.horizontalFit = tupleAdapter.IsHorizontal ? unconstr : prefr;
csf.verticalFit = tupleAdapter.IsHorizontal ? prefr : unconstr;
// Update: will be manually enabled when needed;
//csf.enabled = true;
csf.enabled = false;
//var valPrefabLayG = valuePrefab.GetComponent<LayoutGroup>();
//HorizontalLayoutGroup valPrefabLayGHor;
//if (valPrefabLayG)
//{
// valPrefabLayGHor = valPrefabLayG as HorizontalLayoutGroup;
// if (!valPrefabLayGHor)
// throw new OSAException(
// typeof(TupleParams).Name + ": Only " + typeof(HorizontalLayoutGroup).Name +
// " is allowed on value prefab ATM when using ResizingMode " + TableResizingMode.AUTO_FIT_TUPLE_CONTENT
// );
//}
//else
// valPrefabLayGHor = valuePrefab.gameObject.AddComponent<HorizontalLayoutGroup>();
//if (IsHorizontal)
// _PrefabStandardPaddingTransvEnd = valPrefabLayGHor.padding.bottom;
//else
// _PrefabStandardPaddingTransvEnd = valPrefabLayGHor.padding.right;
//valPrefabLayG.childAlignment = TextAnchor.UpperLeft;
//valPrefabLayGHor.childControlHeight = valPrefabLayGHor.childControlWidth = true;
//valPrefabLayGHor.childForceExpandHeight = valPrefabLayGHor.childForceExpandWidth = false;
//// Make sure the value prefab has the same size as the tuple
//SetPaddingTransvEndToAchieveTansvSizeFor(valuePrefab, valPrefabLayG, ScrollViewRT.rect.size[IsHorizontal ? 1 : 0]);
//return true;
}
else
{
if (EdgeDragger && !EdgeDragger.gameObject.activeSelf)
EdgeDragger.gameObject.SetActive(true);
if (tupleParams.ItemTransversalSize == -1f)
{
Debug.Log(
typeof(TupleParams).Name + ": ItemTransversalSize set to -1 is not supported, " +
"because ResizingMode is not " + TableResizingMode.AUTO_FIT_TUPLE_CONTENT +
". To avoid this message, manually set it to something else in inspector"
);
tupleParams.ItemTransversalSize = 0f;
}
if (csf && csf.enabled)
{
Debug.Log(typeof(TupleParams).Name + ": Found enabled ContentSizeFitter on tuple's value prefab, but ResizingMode is not "
+ TableResizingMode.AUTO_FIT_TUPLE_CONTENT + ". Disabling ContentSizeFitter...");
csf.enabled = false;
}
if (ResizingMode == TableResizingMode.NONE)
{
}
else
{
}
}
// Layout components are disabled when no resizing is available. You
// should rely on anchoring to properly size the views in this case
if (ResizingMode == TableResizingMode.NONE)
{
if (layoutGroup && layoutGroup.enabled)
{
layoutGroup.enabled = false;
Debug.Log(typeof(TupleParams).Name + ": Found enabled LayoutGroup on tuple's value prefab, but ResizingMode is " + ResizingMode +
". Disabling LayoutGroup...");
}
foreach (RectTransform rt in valuePrefab)
{
var l = rt.GetComponent<LayoutGroup>();
if (l)
l.enabled = false;
var le = rt.GetComponent<LayoutElement>();
if (le)
le.enabled = false;
}
}
else
{
if (layoutGroup && !layoutGroup.enabled)
layoutGroup.enabled = true;
foreach (RectTransform rt in valuePrefab)
{
var l = rt.GetComponent<LayoutGroup>();
if (l)
l.enabled = true;
var le = rt.GetComponent<LayoutElement>();
if (le)
le.enabled = true;
}
}
return false;
}
//public void SetPaddingTransvEndToAchieveTansvSizeFor(RectTransform rt, LayoutGroup layoutGroup, double targetTransvSize)
//{
// double vhTransvSize = rt.rect.size[IsHorizontal ? 1 : 0];
// double vhTransvSizeMinusCurrentPaddingBottom = vhTransvSize - layoutGroup.padding.bottom;
// double padTransvEnd = targetTransvSize - vhTransvSizeMinusCurrentPaddingBottom;
// //if (padTransvEnd < 0d)
// // throw new Exception(targetTransvSize + ", " + vhTransvSizeMinusCurrentPaddingBottom);
// if (padTransvEnd < _PrefabStandardPaddingTransvEnd)
// padTransvEnd = _PrefabStandardPaddingTransvEnd;
// SetPaddingTransvEndFor(rt, layoutGroup, padTransvEnd);
//}
//public void SetStandardTransvPaddingFor(RectTransform rt, LayoutGroup layoutGroup)
//{
// SetPaddingTransvEndFor(rt, layoutGroup, _PrefabStandardPaddingTransvEnd);
//}
//void SetPaddingTransvEndFor(RectTransform rt, LayoutGroup layGroup, double paddingTransvEnd)
//{
// int padEnd = (int)paddingTransvEnd;
// if (IsHorizontal)
// layGroup.padding.bottom = padEnd;
// else
// layGroup.padding.right = padEnd;
// LayoutRebuilder.MarkLayoutForRebuild(rt);
//}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 0a445190494cb1e4299f52634fbf1ef8
timeCreated: 1562924181
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,84 @@
using Com.ForbiddenByte.OSA.Core;
using Com.ForbiddenByte.OSA.CustomAdapters.TableView.Input;
using frame8.Logic.Misc.Other.Extensions;
using System;
using UnityEngine.Events;
using UnityEngine.UI;
namespace Com.ForbiddenByte.OSA.CustomAdapters.TableView.Tuple
{
public abstract class TupleValueViewsHolder : BaseItemViewsHolder
{
public bool HasPendingTransversalSizeChanges { get; set; }
public ContentSizeFitter CSF { get { return _CSF; } }
//public LayoutGroup LayoutGroup { get { return _LayoutGroup; } }
public TableViewText TextComponent { get { return _TextComponent; } }
TableViewText _TextComponent;
Button _Button;
ContentSizeFitter _CSF;
//LayoutGroup _LayoutGroup;
UnityAction<object> _ValueChangedFromInput;
public override void CollectViews()
{
base.CollectViews();
_Button = root.GetComponent<Button>();
_CSF = root.GetComponent<UnityEngine.UI.ContentSizeFitter>();
//_LayoutGroup = root.GetComponent<UnityEngine.UI.LayoutGroup>();
root.GetComponentAtPath("TextPanel/Text", out _TextComponent);
}
public virtual void SetClickListener(UnityAction action)
{
if (_Button)
{
if (action == null)
_Button.onClick.RemoveAllListeners();
else
_Button.onClick.AddListener(action);
}
}
public virtual void SetValueChangedFromInputListener(UnityAction<object> action)
{
_ValueChangedFromInput = action;
}
public abstract void UpdateViews(object value, ITableColumns columnsProvider);
/// <summary>
/// Called by the controller of this Views Holder, when a click is not handled by it and should be processed by this Views Holder itself
/// </summary>
public virtual void ProcessUnhandledClick()
{
}
public override void MarkForRebuild()
{
// Don't LayoutRebuilder.MarkLayoutForRebuild(), because the tuples in a TableView are rebuilt
// via LayoutRebuilder.ForceRebuildLayoutImmediate() by the TupleAdapter itself
//base.MarkForRebuild();
if (CSF)
CSF.enabled = true;
}
public override void UnmarkForRebuild()
{
if (CSF)
CSF.enabled = false;
base.UnmarkForRebuild();
}
protected void NotifyValueChangedFromInput(object newValue)
{
if (_ValueChangedFromInput != null)
_ValueChangedFromInput(newValue);
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: eca5655e0a98de448b20d12706b0efd2
timeCreated: 1562924181
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More