123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
- #if UNITY_EDITOR
- using System;
- using UnityEditor;
- using UnityEngine;
- using static Animancer.Editor.AnimancerGUI;
- namespace Animancer.Editor
- {
- /// <summary>[Editor-Only] Utility for drawing tables.</summary>
- /// https://kybernetik.com.au/animancer/api/Animancer.Editor/TableGUI
- [Serializable]
- public class TableGUI
- {
- /************************************************************************************************************************/
- /// <summary>The pixel spacing between cells.</summary>
- public static float Padding
- => 0;// StandardSpacing;
- /// <summary>The style for the horizontal scroll bar.</summary>
- public static GUIStyle HorizontalScrollBar
- => GUI.skin.horizontalScrollbar;
- /// <summary>The style for the vertical scroll bar.</summary>
- public static GUIStyle VerticalScrollBar
- => GUI.skin.verticalScrollbar;
- /************************************************************************************************************************/
- /// <summary>Draws the GUI for a specified cell.</summary>
- /// <remarks>`column` and `row` are given <c>-1</c> for the labels.</remarks>
- public delegate void CellGUIDelegate(Rect area, int column, int row);
- /// <summary>Draws the GUI for a specified cell.</summary>
- /// <remarks>`column` and `row` are given <c>-1</c> for the labels.</remarks>
- public CellGUIDelegate DoCellGUI;
- [NonSerialized] private Vector2 _MinCellSize;// Pixels.
- [NonSerialized] private Vector2 _MaxCellSize;// Pixels.
- [SerializeField] private Vector2 _LabelSize = new(0.25f, 0.25f);// Normalized.
- [SerializeField] private Vector2 _ScrollPosition;
- /// <summary>The minimum pixel size of each cell.</summary>
- public ref Vector2 MinCellSize
- => ref _MinCellSize;
- /// <summary>The maximum pixel size of each cell.</summary>
- public ref Vector2 MaxCellSize
- => ref _MaxCellSize;
- /// <summary>[<see cref="SerializeField"/>] The normalized size of the header labels.</summary>
- public ref Vector2 LabelSize
- => ref _LabelSize;
- /// <summary>[<see cref="SerializeField"/>] The position the table is currently scrolled to.</summary>
- public ref Vector2 ScrollPosition
- => ref _ScrollPosition;
- /************************************************************************************************************************/
- /// <summary>Draws this table.</summary>
- public void DoTableGUI(
- Rect area,
- int columns,
- int rows)
- {
- HandleInput(area);
- var scrollBarSize = new Vector2(
- VerticalScrollBar.fixedWidth + Padding,
- HorizontalScrollBar.fixedHeight + Padding);
- area.size -= scrollBarSize;
- CalculateSizes(area, columns, rows, out var labelSize, out var cellSize);
- area.size += scrollBarSize;
- var cornerArea = new Rect(area.position, labelSize + scrollBarSize);
- DoCellGUI(cornerArea, -1, -1);
- var labelResizerArea = cornerArea;
- labelResizerArea.xMin = labelResizerArea.xMax - scrollBarSize.x;
- labelResizerArea.yMin = labelResizerArea.yMax - scrollBarSize.y;
- DoLabelResizerGUI(labelResizerArea, area);
- var columnLabelArea = new Rect(
- area.x + scrollBarSize.x + labelSize.x + Padding,
- area.y,
- cellSize.x,
- labelSize.y);
- var rowLabelArea = new Rect(
- area.x,
- area.y + scrollBarSize.y + labelSize.y + Padding,
- labelSize.x,
- cellSize.y);
- area.xMin += labelSize.x + Padding + scrollBarSize.x;
- area.yMin += labelSize.y + Padding + scrollBarSize.y;
- GUI.BeginClip(area);
- for (int x = 0; x < columns; x++)
- {
- for (int y = 0; y < rows; y++)
- {
- var cellArea = new Rect(
- (cellSize.x + Padding) * x - _ScrollPosition.x,
- (cellSize.y + Padding) * y - _ScrollPosition.y,
- cellSize.x,
- cellSize.y);
- DoCellGUI(cellArea, x, y);
- }
- }
- GUI.EndClip();
- DrawColumnLabels(columnLabelArea, columns, area.width, scrollBarSize.y);
- DrawRowLabels(rowLabelArea, rows, area.height, scrollBarSize.x);
- }
- /************************************************************************************************************************/
- private static readonly int LabelResizerHint = "LabelResizer".GetHashCode();
- private static GUIContent _LabelResizerIcon;
- private void DoLabelResizerGUI(
- Rect resizerArea,
- Rect tableArea)
- {
- var control = new GUIControl(resizerArea, LabelResizerHint);
- switch (control.EventType)
- {
- case EventType.MouseDown:
- if (control.Event.button == 0 &&
- control.TryUseMouseDown())
- {
- if (control.Event.clickCount == 2)
- {
- AutoSizeLabels(tableArea);
- GUIUtility.hotControl = 0;
- }
- }
- break;
- case EventType.MouseUp:
- control.TryUseMouseUp();
- break;
- case EventType.MouseDrag:
- if (control.TryUseHotControl())
- {
- var offset = control.Event.mousePosition - tableArea.position;
- LabelSize = new(
- offset.x / tableArea.width,
- offset.y / tableArea.height);
- }
- break;
- case EventType.KeyDown:
- if (control.TryUseKey(KeyCode.Escape))
- Deselect();
- break;
- case EventType.Repaint:
- EditorGUIUtility.AddCursorRect(resizerArea, MouseCursor.ResizeUpLeft);
- AnimancerIcons.IconContent(ref _LabelResizerIcon, "MoveTool");
- if (_LabelResizerIcon != null)
- GUI.DrawTexture(resizerArea, _LabelResizerIcon.image);
- break;
- }
- }
- /************************************************************************************************************************/
- private static readonly Matrix4x4
- Rotate90LeftMatrix = Matrix4x4.Rotate(Quaternion.Euler(0, 0, -90));
- private void DrawColumnLabels(Rect area, int count, float availableSize, float scrollSize)
- {
- var previousClip = GetGUIClipRect();
- GUI.EndClip();
- var totalSize =
- area.width * count +
- Padding * (count - 1);
- var totalArea = new Rect(
- area.x,
- area.y + area.height + Padding,
- availableSize,
- scrollSize);
- var translation = previousClip.position + area.position;
- translation = -translation.GetPerpendicular();
- translation.x -= area.height;
- area.x = 0;
- area.y = -_ScrollPosition.x;
- (area.width, area.height) = (area.height, area.width);
- GUI.BeginClip(new(0, 0, area.width, availableSize));
- var matrix = GUI.matrix;
- GUI.matrix =
- Rotate90LeftMatrix *
- Matrix4x4.Translate(translation);
- for (int i = 0; i < count; i++)
- {
- DoCellGUI(area, i, -1);
- area.y += area.height + Padding;
- }
- GUI.matrix = matrix;
- GUI.EndClip();
- GUI.BeginClip(previousClip);
- var enabled = GUI.enabled;
- GUI.enabled = availableSize < totalSize;
- _ScrollPosition.x = GUI.HorizontalScrollbar(
- totalArea,
- _ScrollPosition.x,
- availableSize,
- 0,
- totalSize);
- GUI.enabled = enabled;
- }
- /************************************************************************************************************************/
- private void DrawRowLabels(Rect area, int count, float availableSize, float scrollSize)
- {
- var totalArea = area;
- totalArea.height = availableSize;
- GUI.BeginClip(totalArea);
- area.x = 0;
- area.y = -_ScrollPosition.y;
- for (int i = 0; i < count; i++)
- {
- DoCellGUI(area, -1, i);
- area.y += area.height + Padding;
- }
- GUI.EndClip();
- var totalSize =
- area.height * count +
- Padding * (count - 1);
- totalArea.x += totalArea.width + Padding;
- totalArea.width = scrollSize;
- var enabled = GUI.enabled;
- GUI.enabled = availableSize < totalSize;
- _ScrollPosition.y = GUI.VerticalScrollbar(
- totalArea,
- _ScrollPosition.y,
- availableSize,
- 0,
- totalSize);
- GUI.enabled = enabled;
- }
- /************************************************************************************************************************/
- private static readonly int ControlHint = nameof(TableGUI).GetHashCode();
- private void HandleInput(Rect area)
- {
- var control = new GUIControl(area, ControlHint);
- switch (control.EventType)
- {
- case EventType.ScrollWheel:
- if (control.ContainsMousePosition)
- {
- var delta = control.Event.delta * 5;
- if (control.Event.shift)
- delta = delta.GetPerpendicular();
- _ScrollPosition += delta;
- control.Event.Use();
- }
- break;
- case EventType.MouseDown:
- if (control.Event.IsMiddleClick())
- control.TryUseMouseDown();
- break;
- case EventType.MouseUp:
- control.TryUseMouseUp();
- break;
- case EventType.MouseDrag:
- if (control.TryUseHotControl())
- _ScrollPosition -= control.Event.delta;
- break;
- }
- }
- /************************************************************************************************************************/
- #region Size Calculation
- /************************************************************************************************************************/
- /// <summary>Calculates the current label and cell sizes for the given `area`.</summary>
- public void CalculateSizes(
- Rect area,
- int columns,
- int rows,
- out Vector2 labelSize,
- out Vector2 cellSize)
- {
- // Min cell size.
- cellSize = _MinCellSize;
- if (cellSize.x < 1)
- cellSize.x = LineHeight;
- if (cellSize.y < 1)
- cellSize.y = LineHeight;
- // Min label size.
- labelSize.x = Mathf.Clamp(_LabelSize.x, 0, 0.9f);
- labelSize.y = Mathf.Clamp(_LabelSize.y, 0, 0.9f);
- labelSize = Vector2.Scale(area.size, labelSize);
- if (labelSize == default)
- labelSize = cellSize;
- // Expand cells if there is more area available, up to the max.
- var availableSize = area.size - labelSize;
- cellSize.x = StretchCellSize(availableSize.x, cellSize.x, _MaxCellSize.x, columns);
- cellSize.y = StretchCellSize(availableSize.y, cellSize.y, _MaxCellSize.y, rows);
- // Expand labels if there is more area available.
- labelSize.x = StretchLabelSize(area.width, labelSize.x, cellSize.x, columns);
- labelSize.y = StretchLabelSize(area.height, labelSize.y, cellSize.y, rows);
- }
- /************************************************************************************************************************/
- private static float StretchCellSize(
- float availableSize,
- float cellSize,
- float maxCellSize,
- int cellCount)
- {
- if (cellSize < maxCellSize)
- {
- availableSize -= Padding * (cellCount - 1);
- if (availableSize > cellSize * cellCount)
- cellSize = Math.Min(availableSize / cellCount, maxCellSize);
- }
- return cellSize;
- }
- /************************************************************************************************************************/
- private static float StretchLabelSize(
- float availableSize,
- float labelSize,
- float cellSize,
- int cellCount)
- {
- labelSize = Math.Max(labelSize, availableSize - (cellSize + Padding) * cellCount);
- labelSize = Math.Max(labelSize, cellSize);
- return labelSize;
- }
- /************************************************************************************************************************/
- /// <summary>A delegate to calculate the largest pixel width of the header labels.</summary>
- public Func<float> CalculateWidestLabel { get; set; }
- private void AutoSizeLabels(Rect tableArea)
- {
- if (CalculateWidestLabel == null)
- return;
- var targetLabelSize = CalculateWidestLabel();
- _LabelSize.x = targetLabelSize / tableArea.width;
- _LabelSize.y = targetLabelSize / tableArea.height;
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- }
- }
- #endif
|