Trade Menu Spreadsheet

Implemented generic spreadsheet component for the Trade menu with sortable and resizable data columns
This commit is contained in:
Reinier Vens 2021-01-14 03:20:27 +00:00
commit 8036585ebc
46 changed files with 21429 additions and 17151 deletions

View file

@ -58,6 +58,10 @@ public class ActionGameManager : MonoBehaviour
private (TargetLock targetLock, PlaceUIElementWorldspace indicator, Rotate spin)[] LockingIndicators;
public List<Ship> PlayerShips { get; } = new List<Ship>();
public Ship CurrentShip
{
get => _currentShip;
}
public DatabaseCache ItemData { get; private set; }
public ItemManager ItemManager { get; private set; }
public Zone Zone { get; private set; }

View file

@ -0,0 +1,35 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using TMPro;
using UnityEngine;
public class EventLog : MonoBehaviour
{
public TextMeshProUGUI Log;
public int LineCount = 16;
private SizedQueue<string> LogMessages;
private StringBuilder LogText;
private void Start()
{
LogMessages = new SizedQueue<string>(LineCount);
LogText = new StringBuilder();
Log.text = LogText.ToString();
}
public void LogMessage(string text)
{
LogMessage(text, Color.white);
}
public void LogMessage(string text, Color color)
{
LogMessages.Enqueue($"<color=#{ColorUtility.ToHtmlStringRGB(color)}>{text}");
LogText.Clear();
foreach (var s in LogMessages) LogText.AppendLine(s);
Log.text = LogText.ToString();
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3210caac18cbfa04dad241990777a2a0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -9,6 +9,7 @@ using UnityEngine;
public class MenuPanel : MonoBehaviour
{
public ActionGameManager GameManager;
public RectTransform TabButtons;
public Color ActiveTabColor;
public Color InactiveTabColor;
@ -40,7 +41,15 @@ public class MenuPanel : MonoBehaviour
TabChanged?.Invoke(tab);
}
private void OnEnable()
{
foreach (var tabButton in _tabs.Values)
{
tabButton.gameObject.SetActive(!tabButton.RequireParent || GameManager.CurrentShip.Parent != null);
}
}
void Awake()
{
foreach (var tabButton in TabButtons.GetComponentsInChildren<MenuTabButton>())
@ -57,5 +66,6 @@ public class MenuPanel : MonoBehaviour
public enum MenuTab
{
Map,
Inventory
Inventory,
Trade
}

View file

@ -15,4 +15,5 @@ public class MenuTabButton : MonoBehaviour
public Button Button;
public MenuTab Tab;
public TextMeshProUGUI Text;
public bool RequireParent;
}

View file

@ -23,6 +23,11 @@ namespace UniRx.Triggers
}
private void OnDisable()
{
Reset();
}
public void Reset()
{
onPointerClick = null;
}

View file

@ -0,0 +1,12 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PrototypeParticleReturn : MonoBehaviour
{
private void OnParticleSystemStopped()
{
GetComponent<Prototype>().ReturnToPool();
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: db021a309de3d324588c5603de40c29f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,17 @@
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class SizeFilter : MonoBehaviour
{
public TMP_InputField Width;
public TMP_InputField Height;
public Button DisableButton;
void Start()
{
DisableButton.onClick.AddListener(() => gameObject.SetActive(false));
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8417880965a8e3a4e8d2586ab3ab0367
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,199 @@
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;
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ed0409b21bd8a5b4dae4c406c4a88134
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,30 @@
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UniRx.Triggers;
using UnityEngine;
using UnityEngine.UI;
public class SpreadsheetColumnHeader : MonoBehaviour
{
public TextMeshProUGUI Title;
public RectTransform Rect;
public Button Button;
public Image SortIcon;
public ObservableBeginDragTrigger ResizeBeginDragTrigger;
public ObservableDragTrigger ResizeDragTrigger;
public ObservableEndDragTrigger ResizeEndDragTrigger;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c76217e77ad445d4fbdbdc7241eabbc2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,40 @@
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UniRx.Triggers;
using UnityEngine;
using UnityEngine.UI;
public class SpreadsheetRow : MonoBehaviour
{
public Prototype Column;
public Image Background;
public ObservablePointerClickTrigger ClickTrigger;
private List<RectTransform> _entries = new List<RectTransform>();
public void ShowData(SpreadsheetEntryRow data)
{
foreach(var entry in _entries)
entry.GetComponent<Prototype>().ReturnToPool();
_entries.Clear();
foreach (var column in data.Columns)
{
var entry = Column.Instantiate<RectTransform>();
entry.GetComponent<TextMeshProUGUI>().text = column.Output();
_entries.Add(entry);
}
}
public void ApplyColumnSizes(int[] sizes)
{
var distance = 0;
for (int i = 0; i < sizes.Length; i++)
{
_entries[i].anchoredPosition = Vector2.right * distance;
_entries[i].sizeDelta = Vector2.right * sizes[i];
distance += sizes[i];
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ffbf236e994f734449d5eec120c36177
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,57 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
public class TradeMenu : MonoBehaviour
{
public ActionGameManager GameManager;
public DropdownMenu Dropdown;
public Button NewFilterButton;
public SizeFilter MinimumSizeFilter;
public SizeFilter MaximumSizeFilter;
public RectTransform ItemList;
public PropertiesPanel Properties;
public Spreadsheet Spreadsheet;
private void OnEnable()
{
Properties.Context = GameManager.ItemManager;
Spreadsheet.ShowData(
new[] {"Name", "Mass"},
new[] {2, 1},
GameManager.ItemManager.ItemData.GetAll<ItemData>().Select(i => new SpreadsheetEntryRow
{
Columns = new[]
{
new SpreadsheetEntryColumn
{
Output = () => i.Name,
SortKey = i.Name
},
new SpreadsheetEntryColumn
{
Output = () => i.Mass.SignificantDigits(3),
SortKey = i.Mass
},
},
OnClick = () =>
{
Properties.Clear();
Properties.AddItemDataProperties(i);
}
}));
}
void Start()
{
}
void Update()
{
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 674196ee85a8a424ab8d3f4d0101ada7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -394,11 +394,43 @@ public class PropertiesPanel : MonoBehaviour
}
}
public void AddItemDataProperties(ItemData data)
{
AddProperty("Type", () => data.Name);
AddProperty(data.Description).Label.fontStyle = FontStyles.Normal;
if (data is EquippableItemData gearData)
{
AddProperty("Durability", () => gearData.Durability.SignificantDigits(Context.GameplaySettings.SignificantDigits));
foreach (var behavior in gearData.Behaviors)
{
var type = behavior.GetType();
if (type.GetCustomAttribute(typeof(RuntimeInspectable)) != null)
{
foreach (var field in type.GetFields().Where(f => f.GetCustomAttribute<RuntimeInspectable>() != null))
{
var fieldType = field.FieldType;
if (fieldType == typeof(float))
AddProperty(field.Name, () => $"{((float) field.GetValue(behavior)).SignificantDigits(Context.GameplaySettings.SignificantDigits)}");
else if (fieldType == typeof(int))
AddProperty(field.Name, () => $"{(int) field.GetValue(behavior)}");
else if (fieldType == typeof(PerformanceStat))
{
var stat = (PerformanceStat) field.GetValue(behavior);
if(Math.Abs(stat.Min - stat.Max) < .001f)
AddProperty(field.Name, () => $"{stat.Min.SignificantDigits(Context.GameplaySettings.SignificantDigits)}");
else
AddProperty(field.Name, () => $"{stat.Min.SignificantDigits(Context.GameplaySettings.SignificantDigits)}-{stat.Max.SignificantDigits(Context.GameplaySettings.SignificantDigits)}");
}
}
}
}
}
}
public void AddItemProperties(ItemInstance item)
{
var data = Context.ItemData.Get<ItemData>(item.Data);
AddProperty("Type", () => data.Name);
AddProperty(data.Description).Label.fontStyle = FontStyles.Normal;
AddItemDataProperties(data);
// if (item is CraftedItemInstance craftedItemInstance)
// {
// var sourceEntity = Context.ItemData.Get<Entity>(craftedItemInstance.SourceEntity);
@ -421,26 +453,7 @@ public class PropertiesPanel : MonoBehaviour
var gearData = Context.GetData(gear);
AddProperty("Durability", () =>
$"{gear.Durability.SignificantDigits(Context.GameplaySettings.SignificantDigits)}/{gearData.Durability.SignificantDigits(Context.GameplaySettings.SignificantDigits)}");
foreach (var behavior in gearData.Behaviors)
{
var type = behavior.GetType();
if (type.GetCustomAttribute(typeof(RuntimeInspectable)) != null)
{
foreach (var field in type.GetFields().Where(f => f.GetCustomAttribute<RuntimeInspectable>() != null))
{
var fieldType = field.FieldType;
if (fieldType == typeof(float))
AddProperty(field.Name, () => $"{((float) field.GetValue(behavior)).SignificantDigits(Context.GameplaySettings.SignificantDigits)}");
else if (fieldType == typeof(int))
AddProperty(field.Name, () => $"{(int) field.GetValue(behavior)}");
else if (fieldType == typeof(PerformanceStat))
{
var stat = (PerformanceStat) field.GetValue(behavior);
AddProperty(field.Name, () => $"{Context.Evaluate(stat, gear).SignificantDigits(Context.GameplaySettings.SignificantDigits)}");
}
}
}
}
}
}