com.unity.uiextensions/Scripts/Controls/Stepper.cs

333 lines
10 KiB
C#

/// Credit David Gileadi
/// Sourced from - https://bitbucket.org/UnityUIExtensions/unity-ui-extensions/pull-requests/11
using System;
using UnityEngine.Events;
using UnityEngine.EventSystems;
namespace UnityEngine.UI.Extensions
{
// Stepper control
[AddComponentMenu("UI/Extensions/Stepper")]
[RequireComponent(typeof(RectTransform))]
public class Stepper : UIBehaviour
{
private Selectable[] _sides;
[SerializeField]
[Tooltip("The current step value of the control")]
private int _value = 0;
[SerializeField]
[Tooltip("The minimum step value allowed by the control. When reached it will disable the '-' button")]
private int _minimum = 0;
[SerializeField]
[Tooltip("The maximum step value allowed by the control. When reached it will disable the '+' button")]
private int _maximum = 100;
[SerializeField]
[Tooltip("The step increment used to increment / decrement the step value")]
private int _step = 1;
[SerializeField]
[Tooltip("Does the step value loop around from end to end")]
private bool _wrap = false;
[SerializeField]
[Tooltip("A GameObject with an Image to use as a separator between segments. Size of the RectTransform will determine the size of the separator used.\nNote, make sure to disable the separator GO so that it does not affect the scene")]
private Graphic _separator;
private float _separatorWidth = 0;
private float separatorWidth
{
get
{
if (_separatorWidth == 0 && separator)
{
_separatorWidth = separator.rectTransform.rect.width;
var image = separator.GetComponent<Image>();
if (image)
_separatorWidth /= image.pixelsPerUnit;
}
return _separatorWidth;
}
}
// Event delegates triggered on click.
[SerializeField]
private StepperValueChangedEvent _onValueChanged = new StepperValueChangedEvent();
[Serializable]
public class StepperValueChangedEvent : UnityEvent<int> { }
public Selectable[] sides
{
get
{
if (_sides == null || _sides.Length == 0)
{
_sides = GetSides();
}
return _sides;
}
}
public int value { get { return _value; } set { _value = value; } }
public int minimum { get { return _minimum; } set { _minimum = value; } }
public int maximum { get { return _maximum; } set { _maximum = value; } }
public int step { get { return _step; } set { _step = value; } }
public bool wrap { get { return _wrap; } set { _wrap = value; } }
public Graphic separator { get { return _separator; } set { _separator = value; _separatorWidth = 0; LayoutSides(sides); } }
public StepperValueChangedEvent onValueChanged
{
get { return _onValueChanged; }
set { _onValueChanged = value; }
}
protected Stepper()
{ }
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
RecreateSprites(sides);
if (separator)
LayoutSides();
if (!wrap)
{
DisableAtExtremes(sides);
}
}
#endif
private Selectable[] GetSides()
{
var buttons = GetComponentsInChildren<Selectable>();
if (buttons.Length != 2)
{
throw new InvalidOperationException("A stepper must have two Button children");
}
for (int i = 0; i < 2; i++)
{
var side = buttons[i].GetComponent<StepperSide>();
if (side == null)
{
side = buttons[i].gameObject.AddComponent<StepperSide>();
}
}
if (!wrap)
{
DisableAtExtremes(buttons);
}
LayoutSides(buttons);
return buttons;
}
public void StepUp()
{
Step(step);
}
public void StepDown()
{
Step(-step);
}
private void Step(int amount)
{
value += amount;
if (wrap)
{
if (value > maximum) value = minimum;
if (value < minimum) value = maximum;
}
else
{
value = Math.Max(minimum, value);
value = Math.Min(maximum, value);
DisableAtExtremes(sides);
}
_onValueChanged.Invoke(value);
}
private void DisableAtExtremes(Selectable[] sides)
{
sides[0].interactable = wrap || value > minimum;
sides[1].interactable = wrap || value < maximum;
}
private void RecreateSprites(Selectable[] sides)
{
for (int i = 0; i < 2; i++)
{
if (sides[i].image == null)
continue;
var sprite = CutSprite(sides[i].image.sprite, i == 0);
sides[i].GetComponent<StepperSide>().cutSprite = sprite;
sides[i].image.overrideSprite = sprite;
}
}
static internal Sprite CutSprite(Sprite sprite, bool leftmost)
{
if (sprite.border.x == 0 || sprite.border.z == 0)
return sprite;
var rect = sprite.rect;
var border = sprite.border;
if (leftmost)
{
rect.xMax = border.z;
border.z = 0;
}
else
{
rect.xMin = border.x;
border.x = 0;
}
return Sprite.Create(sprite.texture, rect, sprite.pivot, sprite.pixelsPerUnit, 0, SpriteMeshType.FullRect, border);
}
public void LayoutSides(Selectable[] sides = null)
{
sides = sides ?? this.sides;
RecreateSprites(sides);
RectTransform transform = this.transform as RectTransform;
float width = (transform.rect.width / 2) - separatorWidth;
for (int i = 0; i < 2; i++)
{
float insetX = i == 0 ? 0 : width + separatorWidth;
var rectTransform = sides[i].GetComponent<RectTransform>();
rectTransform.anchorMin = Vector2.zero;
rectTransform.anchorMax = Vector2.zero;
rectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Left, insetX, width);
rectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Top, 0, transform.rect.height);
// TODO: maybe adjust text position
}
if (separator)
{
var sepTransform = gameObject.transform.Find("Separator");
Graphic sep = (sepTransform != null) ? sepTransform.GetComponent<Graphic>() : (GameObject.Instantiate(separator.gameObject) as GameObject).GetComponent<Graphic>();
sep.gameObject.name = "Separator";
sep.gameObject.SetActive(true);
sep.rectTransform.SetParent(this.transform, false);
sep.rectTransform.anchorMin = Vector2.zero;
sep.rectTransform.anchorMax = Vector2.zero;
sep.rectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Left, width, separatorWidth);
sep.rectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Top, 0, transform.rect.height);
}
}
}
[RequireComponent(typeof(Selectable))]
public class StepperSide :
UIBehaviour,
IPointerClickHandler,
ISubmitHandler,
IPointerEnterHandler, IPointerExitHandler,
IPointerDownHandler, IPointerUpHandler,
ISelectHandler, IDeselectHandler
{
Selectable button { get { return GetComponent<Selectable>(); } }
Stepper stepper { get { return GetComponentInParent<Stepper>(); } }
bool leftmost { get { return button == stepper.sides[0]; } }
internal Sprite cutSprite;
protected StepperSide()
{ }
public virtual void OnPointerClick(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
Press();
AdjustSprite(false);
}
public virtual void OnSubmit(BaseEventData eventData)
{
Press();
AdjustSprite(true);
}
public virtual void OnPointerEnter(PointerEventData eventData)
{
AdjustSprite(false);
}
public virtual void OnPointerExit(PointerEventData eventData)
{
AdjustSprite(true);
}
public virtual void OnPointerDown(PointerEventData eventData)
{
AdjustSprite(false);
}
public virtual void OnPointerUp(PointerEventData eventData)
{
AdjustSprite(false);
}
public virtual void OnSelect(BaseEventData eventData)
{
AdjustSprite(false);
}
public virtual void OnDeselect(BaseEventData eventData)
{
AdjustSprite(true);
}
private void Press()
{
if (!button.IsActive() || !button.IsInteractable())
return;
if (leftmost)
{
stepper.StepDown();
}
else
{
stepper.StepUp();
}
}
private void AdjustSprite(bool restore)
{
var image = button.image;
if (!image || image.overrideSprite == cutSprite)
return;
if (restore)
image.overrideSprite = cutSprite;
else
image.overrideSprite = Stepper.CutSprite(image.overrideSprite, leftmost);
}
}
}