2017-07-26 01:54:00 +08:00
/// Credit David Gileadi
/// Sourced from - https://bitbucket.org/UnityUIExtensions/unity-ui-extensions/pull-requests/11
using System ;
2017-09-01 00:29:16 +08:00
using System.Collections ;
2017-07-26 01:54:00 +08:00
using UnityEngine.Events ;
using UnityEngine.EventSystems ;
namespace UnityEngine.UI.Extensions
{
// Stepper control
[AddComponentMenu("UI/Extensions/Stepper")]
[RequireComponent(typeof(RectTransform))]
public class Stepper : UIBehaviour
{
2017-07-27 05:32:48 +08:00
private Selectable [ ] _sides ;
2017-07-26 01:54:00 +08:00
[SerializeField]
2017-07-26 02:54:07 +08:00
[Tooltip("The current step value of the control")]
2017-07-26 01:54:00 +08:00
private int _value = 0 ;
[SerializeField]
2017-07-26 02:54:07 +08:00
[Tooltip("The minimum step value allowed by the control. When reached it will disable the '-' button")]
2017-07-26 01:54:00 +08:00
private int _minimum = 0 ;
[SerializeField]
2017-07-26 02:54:07 +08:00
[Tooltip("The maximum step value allowed by the control. When reached it will disable the '+' button")]
2017-07-26 01:54:00 +08:00
private int _maximum = 100 ;
[SerializeField]
2017-07-26 02:54:07 +08:00
[Tooltip("The step increment used to increment / decrement the step value")]
2017-07-26 01:54:00 +08:00
private int _step = 1 ;
[SerializeField]
2017-07-26 02:54:07 +08:00
[Tooltip("Does the step value loop around from end to end")]
2017-07-26 01:54:00 +08:00
private bool _wrap = false ;
[SerializeField]
2017-07-26 02:54:07 +08:00
[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")]
2017-07-26 01:54:00 +08:00
private Graphic _separator ;
private float _separatorWidth = 0 ;
2017-07-26 02:54:07 +08:00
2017-07-26 01:54:00 +08:00
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 ( ) ;
2017-07-26 02:54:07 +08:00
[Serializable]
public class StepperValueChangedEvent : UnityEvent < int > { }
2017-07-27 05:32:48 +08:00
public Selectable [ ] sides
2017-07-26 02:54:07 +08:00
{
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 ) ; } }
2017-07-26 01:54:00 +08:00
public StepperValueChangedEvent onValueChanged
{
get { return _onValueChanged ; }
set { _onValueChanged = value ; }
}
protected Stepper ( )
{ }
#if UNITY_EDITOR
protected override void OnValidate ( )
{
base . OnValidate ( ) ;
2017-08-13 07:19:19 +08:00
RecreateSprites ( sides ) ;
2017-07-26 01:54:00 +08:00
if ( separator )
LayoutSides ( ) ;
if ( ! wrap )
{
DisableAtExtremes ( sides ) ;
}
}
# endif
2017-08-20 12:57:27 +08:00
protected override void Start ( )
{
2017-09-01 00:29:16 +08:00
if ( isActiveAndEnabled )
StartCoroutine ( DelayedInit ( ) ) ;
}
protected override void OnEnable ( )
{
StartCoroutine ( DelayedInit ( ) ) ;
}
IEnumerator DelayedInit ( )
{
yield return null ;
2017-08-20 12:57:27 +08:00
RecreateSprites ( sides ) ;
}
2017-07-27 05:32:48 +08:00
private Selectable [ ] GetSides ( )
2017-07-26 01:54:00 +08:00
{
2017-07-27 05:32:48 +08:00
var buttons = GetComponentsInChildren < Selectable > ( ) ;
2017-07-26 01:54:00 +08:00
if ( buttons . Length ! = 2 )
{
throw new InvalidOperationException ( "A stepper must have two Button children" ) ;
}
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 ) ;
}
2017-07-27 05:32:48 +08:00
private void DisableAtExtremes ( Selectable [ ] sides )
2017-07-26 01:54:00 +08:00
{
sides [ 0 ] . interactable = wrap | | value > minimum ;
sides [ 1 ] . interactable = wrap | | value < maximum ;
}
2017-07-27 05:32:48 +08:00
private void RecreateSprites ( Selectable [ ] sides )
2017-07-26 01:54:00 +08:00
{
for ( int i = 0 ; i < 2 ; i + + )
{
if ( sides [ i ] . image = = null )
continue ;
2017-08-13 07:19:19 +08:00
var sprite = CutSprite ( sides [ i ] . image . sprite , i = = 0 ) ;
2017-08-13 07:56:22 +08:00
var side = sides [ i ] . GetComponent < StepperSide > ( ) ;
if ( side )
{
side . cutSprite = sprite ;
}
2017-08-13 07:19:19 +08:00
sides [ i ] . image . overrideSprite = sprite ;
}
}
2017-07-26 01:54:00 +08:00
2017-08-13 07:19:19 +08:00
static internal Sprite CutSprite ( Sprite sprite , bool leftmost )
{
if ( sprite . border . x = = 0 | | sprite . border . z = = 0 )
return sprite ;
2017-07-26 01:54:00 +08:00
2017-08-13 07:19:19 +08:00
var rect = sprite . rect ;
var border = sprite . border ;
2017-07-26 01:54:00 +08:00
2017-08-13 07:19:19 +08:00
if ( leftmost )
{
rect . xMax = border . z ;
border . z = 0 ;
2017-07-26 01:54:00 +08:00
}
2017-08-13 07:19:19 +08:00
else
{
rect . xMin = border . x ;
border . x = 0 ;
}
return Sprite . Create ( sprite . texture , rect , sprite . pivot , sprite . pixelsPerUnit , 0 , SpriteMeshType . FullRect , border ) ;
2017-07-26 01:54:00 +08:00
}
2017-07-27 05:32:48 +08:00
public void LayoutSides ( Selectable [ ] sides = null )
2017-07-26 01:54:00 +08:00
{
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 ) ;
}
}
}
}