Aetheria/Assets/Scripts/Spreadsheet.cs
Reinier Vens 8036585ebc Trade Menu Spreadsheet
Implemented generic spreadsheet component for the Trade menu with sortable and resizable data columns
2021-01-14 03:20:27 +00:00

199 lines
7.5 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UniRx;
using UnityEngine;
using UnityEngine.EventSystems;
using Random = UnityEngine.Random;
public class Spreadsheet : MonoBehaviour
{
public Prototype ColumnDividerPrototype;
public Prototype ColumnHeaderPrototype;
public Prototype RowPrototype;
public Color OddRowBackground;
public Color EvenRowBackground;
public int MinimumColumnSize = 50;
private List<RectTransform> _columnDividerInstances = new List<RectTransform>();
private List<SpreadsheetRow> _rowInstances = new List<SpreadsheetRow>();
private List<SpreadsheetColumnHeader> _columnInstances = new List<SpreadsheetColumnHeader>();
private List<SpreadsheetEntryRow> _data = new List<SpreadsheetEntryRow>();
private int[] _columnSizes;
private int _sortColumn = -1;
private bool _sortAscending = false;
private Vector2 _dragStartPosition;
private int _dragStartColumnSize;
private int _dragStartNextColumnSize;
public void ShowData(string[] columnNames, int[] columnSizePriorities, IEnumerable<SpreadsheetEntryRow> data)
{
foreach(var col in _columnInstances)
{
col.Button.onClick.RemoveAllListeners();
col.GetComponent<Prototype>().ReturnToPool();
}
_columnInstances.Clear();
foreach(var row in _rowInstances)
row.GetComponent<Prototype>().ReturnToPool();
_rowInstances.Clear();
foreach(var div in _columnDividerInstances)
div.GetComponent<Prototype>().ReturnToPool();
_columnDividerInstances.Clear();
_data.Clear();
_data.AddRange(data);
_sortColumn = -1;
if(columnNames.Length != columnSizePriorities.Length)
throw new ArgumentException("Spreadsheet column names and sizes do not match!");
foreach(var d in _data)
if(columnNames.Length!=d.Columns.Length)
throw new ArgumentException("Spreadsheet column names and row data do not match!");
var totalPriority = columnSizePriorities.Sum();
var totalSize = Mathf.RoundToInt(GetComponent<RectTransform>().rect.width);
_columnSizes = columnSizePriorities.Select(p => (int)((float) p / totalPriority * totalSize)).ToArray();
for (var i = 0; i < columnNames.Length; i++)
{
var columnHeader = ColumnHeaderPrototype.Instantiate<SpreadsheetColumnHeader>();
columnHeader.SortIcon.gameObject.SetActive(false);
columnHeader.Title.text = columnNames[i];
var columnIndex = i;
columnHeader.Button.onClick.AddListener(() =>
{
if (_sortColumn != -1)
{
_columnInstances[_sortColumn].SortIcon.gameObject.SetActive(false);
}
_sortAscending = _sortColumn == columnIndex && !_sortAscending;
if(_sortAscending)
_data.Sort((row1, row2) => row2.Columns[columnIndex].SortKey.CompareTo(row1.Columns[columnIndex].SortKey));
else _data.Sort((row1, row2) => row1.Columns[columnIndex].SortKey.CompareTo(row2.Columns[columnIndex].SortKey));
_sortColumn = columnIndex;
_columnInstances[_sortColumn].SortIcon.gameObject.SetActive(true);
_columnInstances[_sortColumn].SortIcon.rectTransform.rotation = Quaternion.Euler(0,0, _sortAscending ? -90 : 90);
RefreshData();
RepositionColumns();
});
columnHeader.ResizeBeginDragTrigger.OnBeginDragAsObservable().Subscribe(e =>
{
_dragStartPosition = e.position;
_dragStartColumnSize = _columnSizes[columnIndex];
_dragStartNextColumnSize = columnIndex != columnNames.Length-1 ? _columnSizes[columnIndex + 1] : 0;
});
columnHeader.ResizeDragTrigger.OnDragAsObservable().Subscribe(e =>
{
var delta = e.position.x - _dragStartPosition.x;
if (_dragStartColumnSize + (int) delta < MinimumColumnSize ||
columnIndex != columnNames.Length - 1 && _dragStartNextColumnSize - (int) delta < MinimumColumnSize)
return;
_columnSizes[columnIndex] = _dragStartColumnSize + (int) delta;
if(columnIndex != columnNames.Length-1)
_columnSizes[columnIndex+1] = _dragStartNextColumnSize - (int) delta;
RepositionColumns();
});
_columnInstances.Add(columnHeader);
if (i != columnNames.Length-1)
{
var columnDivider = ColumnDividerPrototype.Instantiate<RectTransform>();
_columnDividerInstances.Add(columnDivider);
}
}
for (var i = 0; i < _data.Count; i++)
{
var rowInstance = RowPrototype.Instantiate<SpreadsheetRow>();
rowInstance.Background.color = i % 2 == 0 ? EvenRowBackground : OddRowBackground;
_rowInstances.Add(rowInstance);
}
RefreshData();
RepositionColumns();
}
private void RefreshData()
{
for (var i = 0; i < _data.Count; i++)
{
var rowIndex = i;
_rowInstances[i].ShowData(_data[i]);
_rowInstances[i].ClickTrigger.Reset();
_rowInstances[i].ClickTrigger.OnPointerClickAsObservable().Subscribe(e =>
{
if (e.button == PointerEventData.InputButton.Right)
_data[rowIndex].OnRightClick?.Invoke();
else
{
if (e.clickCount == 2)
{
_data[rowIndex].OnDoubleClick?.Invoke();
}
else _data[rowIndex].OnClick?.Invoke();
}
});
}
}
private void RepositionColumns()
{
var distance = 0;
for (var i = 0; i < _columnSizes.Length; i++)
{
_columnInstances[i].Rect.anchoredPosition = Vector2.right * distance;
_columnInstances[i].Rect.sizeDelta = Vector2.right * _columnSizes[i];
distance += _columnSizes[i];
if (i != _columnSizes.Length-1)
_columnDividerInstances[i].anchoredPosition = Vector2.right * distance;
}
foreach(var row in _rowInstances)
row.ApplyColumnSizes(_columnSizes);
}
void Start()
{
// ShowData(
// new []{"Column 1", "Column 2", "Column 3", "Column 4", "Column 5"},
// new []{2,1,1,1,1},
// Enumerable.Range(0,10)
// .Select(i=>new SpreadsheetEntryRow
// {
// Columns = Enumerable.Range(0,5).Select(j=>
// {
// var value = Random.Range(0, 10000);
// return new SpreadsheetEntryColumn
// {
// Output = () => $"c{(j+1).ToString()}:{value.ToString()}",
// SortKey = value
// };
// }).ToArray()
// }).ToArray()
// );
}
void Update()
{
}
}
public class SpreadsheetEntryRow
{
public SpreadsheetEntryColumn[] Columns;
public Action OnClick;
public Action OnDoubleClick;
public Action OnRightClick;
}
public class SpreadsheetEntryColumn
{
public IComparable SortKey;
public Func<string> Output;
}