2017-07-26 02:54:07 +08:00
/// Credit David Gileadi
/// Sourced from - https://bitbucket.org/UnityUIExtensions/unity-ui-extensions/pull-requests/12
2017-07-24 12:48:38 +08:00
using System ;
using UnityEngine.Events ;
using UnityEngine.EventSystems ;
namespace UnityEngine.UI.Extensions
{
// Segmented control, like a group of buttons
[AddComponentMenu("UI/Extensions/Segmented Control")]
[RequireComponent(typeof(RectTransform))]
public class SegmentedControl : UIBehaviour
{
2017-07-27 05:32:48 +08:00
private Selectable [ ] m_segments ;
2017-07-26 02:54:07 +08:00
[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 m_separator ;
private float m_separatorWidth = 0 ;
[SerializeField]
[Tooltip("When True, it allows each button to be toggled on/off")]
private bool m_allowSwitchingOff = false ;
[SerializeField]
[Tooltip("The selected default for the control (zero indexed array)")]
private int m_selectedSegmentIndex = - 1 ;
// Event delegates triggered on click.
[SerializeField]
[Tooltip("Event to fire once the selection has been changed")]
private SegmentSelectedEvent m_onValueChanged = new SegmentSelectedEvent ( ) ;
2017-07-27 05:32:48 +08:00
protected internal Selectable selectedSegment ;
2017-07-26 02:54:07 +08:00
protected float SeparatorWidth
2017-07-24 12:48:38 +08:00
{
get
{
2017-07-26 02:54:07 +08:00
if ( m_separatorWidth = = 0 & & separator )
2017-07-24 12:48:38 +08:00
{
2017-07-26 02:54:07 +08:00
m_separatorWidth = separator . rectTransform . rect . width ;
var image = separator . GetComponent < Image > ( ) ;
if ( image )
m_separatorWidth / = image . pixelsPerUnit ;
2017-07-24 12:48:38 +08:00
}
2017-07-26 02:54:07 +08:00
return m_separatorWidth ;
2017-07-24 12:48:38 +08:00
}
}
2017-07-26 02:54:07 +08:00
[Serializable]
public class SegmentSelectedEvent : UnityEvent < int > { }
2017-07-24 12:48:38 +08:00
2017-07-27 05:32:48 +08:00
public Selectable [ ] segments
2017-07-24 12:48:38 +08:00
{
get
{
2017-07-26 02:54:07 +08:00
if ( m_segments = = null | | m_segments . Length = = 0 )
2017-07-24 12:48:38 +08:00
{
2017-07-26 02:54:07 +08:00
m_segments = GetChildSegments ( ) ;
2017-07-24 12:48:38 +08:00
}
2017-07-26 02:54:07 +08:00
return m_segments ;
2017-07-24 12:48:38 +08:00
}
}
2017-07-26 02:54:07 +08:00
[SerializeField]
public Color selectedColor ;
2017-07-24 12:48:38 +08:00
2017-07-26 02:54:07 +08:00
public Graphic separator { get { return m_separator ; } set { m_separator = value ; m_separatorWidth = 0 ; LayoutSegments ( ) ; } }
public bool allowSwitchingOff { get { return m_allowSwitchingOff ; } set { m_allowSwitchingOff = value ; } }
2017-07-24 12:48:38 +08:00
public int selectedSegmentIndex
{
get { return Array . IndexOf ( segments , selectedSegment ) ; }
set
{
value = Math . Max ( value , - 1 ) ;
value = Math . Min ( value , segments . Length - 1 ) ;
2017-07-26 02:54:07 +08:00
m_selectedSegmentIndex = value ;
2017-07-24 12:48:38 +08:00
if ( value = = - 1 )
{
if ( selectedSegment )
{
2017-08-13 07:56:22 +08:00
var segment = selectedSegment . GetComponent < Segment > ( ) ;
if ( segment )
{
segment . selected = false ;
}
2017-07-24 12:48:38 +08:00
selectedSegment = null ;
}
}
else
{
2017-08-13 07:56:22 +08:00
var segment = selectedSegment . GetComponent < Segment > ( ) ;
if ( segment )
{
2017-07-24 12:48:38 +08:00
#if UNITY_EDITOR
2017-08-13 07:56:22 +08:00
segment . StoreTextColor ( ) ;
2017-07-24 12:48:38 +08:00
# endif
2017-08-13 07:56:22 +08:00
segment . selected = true ;
}
2017-07-24 12:48:38 +08:00
}
}
}
public SegmentSelectedEvent onValueChanged
{
2017-07-26 02:54:07 +08:00
get { return m_onValueChanged ; }
set { m_onValueChanged = value ; }
2017-07-24 12:48:38 +08:00
}
protected SegmentedControl ( )
{ }
protected override void Start ( )
{
base . Start ( ) ;
LayoutSegments ( ) ;
2017-07-26 02:54:07 +08:00
if ( m_selectedSegmentIndex ! = - 1 )
selectedSegmentIndex = m_selectedSegmentIndex ;
2017-07-24 12:48:38 +08:00
}
#if UNITY_EDITOR
protected override void OnValidate ( )
{
base . OnValidate ( ) ;
2017-08-13 07:19:19 +08:00
LayoutSegments ( ) ;
2017-07-24 12:48:38 +08:00
2017-07-26 02:54:07 +08:00
if ( m_selectedSegmentIndex ! = - 1 )
selectedSegmentIndex = m_selectedSegmentIndex ;
if ( m_selectedSegmentIndex > transform . childCount )
{
selectedSegmentIndex = transform . childCount - 1 ;
}
if ( selectedColor = = new Color ( 0 , 0 , 0 , 0 ) )
{
selectedColor = new Color ( 0f , 0.455f , 0.894f ) ;
}
2017-07-24 12:48:38 +08:00
}
# endif
2017-07-27 05:32:48 +08:00
private Selectable [ ] GetChildSegments ( )
2017-07-24 12:48:38 +08:00
{
2017-07-27 05:32:48 +08:00
var buttons = GetComponentsInChildren < Selectable > ( ) ;
2017-07-24 12:48:38 +08:00
if ( buttons . Length < 2 )
{
throw new InvalidOperationException ( "A segmented control must have at least two Button children" ) ;
}
for ( int i = 0 ; i < buttons . Length ; i + + )
{
var segment = buttons [ i ] . GetComponent < Segment > ( ) ;
if ( segment = = null )
{
segment = buttons [ i ] . gameObject . AddComponent < Segment > ( ) ;
}
segment . index = i ;
2017-08-13 07:19:19 +08:00
segment . segmentedControl = this ;
2017-07-24 12:48:38 +08:00
}
return buttons ;
}
2017-07-24 13:42:17 +08:00
private void RecreateSprites ( )
{
for ( int i = 0 ; i < segments . Length ; i + + )
{
if ( segments [ i ] . image = = null )
continue ;
2017-08-13 07:19:19 +08:00
var sprite = CutSprite ( segments [ i ] . image . sprite , i = = 0 , i = = segments . Length - 1 ) ;
2017-08-13 07:56:22 +08:00
var segment = segments [ i ] . GetComponent < Segment > ( ) ;
if ( segment )
{
segment . cutSprite = sprite ;
}
2017-08-13 07:19:19 +08:00
segments [ i ] . image . overrideSprite = sprite ;
}
}
2017-07-24 13:42:17 +08:00
2017-08-13 07:19:19 +08:00
static internal Sprite CutSprite ( Sprite sprite , bool leftmost , bool rightmost )
{
if ( sprite . border . x = = 0 | | sprite . border . z = = 0 )
return sprite ;
2017-07-24 13:42:17 +08:00
2017-08-13 07:19:19 +08:00
var rect = sprite . rect ;
var border = sprite . border ;
2017-07-24 13:42:17 +08:00
2017-08-13 07:19:19 +08:00
if ( ! leftmost )
{
rect . xMin = border . x ;
border . x = 0 ;
}
if ( ! rightmost )
{
rect . xMax = border . z ;
border . z = 0 ;
2017-07-24 13:42:17 +08:00
}
2017-08-13 07:19:19 +08:00
return Sprite . Create ( sprite . texture , rect , sprite . pivot , sprite . pixelsPerUnit , 0 , SpriteMeshType . FullRect , border ) ;
2017-07-24 13:42:17 +08:00
}
2017-07-24 12:48:38 +08:00
public void LayoutSegments ( )
{
2017-07-24 13:42:17 +08:00
RecreateSprites ( ) ;
2017-07-24 12:48:38 +08:00
RectTransform transform = this . transform as RectTransform ;
2017-07-26 02:54:07 +08:00
float width = ( transform . rect . width / segments . Length ) - ( SeparatorWidth * ( segments . Length - 1 ) ) ;
2017-07-24 12:48:38 +08:00
for ( int i = 0 ; i < segments . Length ; i + + )
{
2017-07-26 02:54:07 +08:00
float insetX = ( ( width + SeparatorWidth ) * i ) ;
2017-07-24 12:48:38 +08:00
var rectTransform = segments [ i ] . GetComponent < RectTransform > ( ) ;
rectTransform . anchorMin = Vector2 . zero ;
rectTransform . anchorMax = Vector2 . zero ;
2017-07-24 13:42:17 +08:00
rectTransform . SetInsetAndSizeFromParentEdge ( RectTransform . Edge . Left , insetX , width ) ;
2017-07-24 12:48:38 +08:00
rectTransform . SetInsetAndSizeFromParentEdge ( RectTransform . Edge . Top , 0 , transform . rect . height ) ;
if ( separator & & i > 0 )
{
var sepTransform = gameObject . transform . Find ( "Separator " + i ) ;
Graphic sep = ( sepTransform ! = null ) ? sepTransform . GetComponent < Graphic > ( ) : ( GameObject . Instantiate ( separator . gameObject ) as GameObject ) . GetComponent < Graphic > ( ) ;
sep . gameObject . name = "Separator " + i ;
sep . gameObject . SetActive ( true ) ;
sep . rectTransform . SetParent ( this . transform , false ) ;
sep . rectTransform . anchorMin = Vector2 . zero ;
sep . rectTransform . anchorMax = Vector2 . zero ;
2017-07-26 02:54:07 +08:00
sep . rectTransform . SetInsetAndSizeFromParentEdge ( RectTransform . Edge . Left , insetX - SeparatorWidth , SeparatorWidth ) ;
2017-07-24 12:48:38 +08:00
sep . rectTransform . SetInsetAndSizeFromParentEdge ( RectTransform . Edge . Top , 0 , transform . rect . height ) ;
}
// TODO: maybe adjust text position
}
}
}
2017-07-27 05:32:48 +08:00
[RequireComponent(typeof(Selectable))]
2017-07-24 12:48:38 +08:00
public class Segment :
UIBehaviour ,
IPointerClickHandler ,
ISubmitHandler ,
IPointerEnterHandler , IPointerExitHandler ,
IPointerDownHandler , IPointerUpHandler ,
ISelectHandler , IDeselectHandler
{
internal int index ;
2017-08-13 07:19:19 +08:00
internal SegmentedControl segmentedControl ;
2017-07-24 12:48:38 +08:00
internal bool leftmost
{
get { return index = = 0 ; }
}
internal bool rightmost
{
2017-08-13 07:19:19 +08:00
get { return index = = segmentedControl . segments . Length - 1 ; }
2017-07-24 12:48:38 +08:00
}
public bool selected
{
2017-08-13 07:19:19 +08:00
get { return segmentedControl . selectedSegment = = this . button ; }
2017-07-24 12:48:38 +08:00
set { SetSelected ( value ) ; }
}
2017-07-27 05:32:48 +08:00
internal Selectable button
2017-07-24 12:48:38 +08:00
{
2017-07-27 05:32:48 +08:00
get { return GetComponent < Selectable > ( ) ; }
2017-07-24 12:48:38 +08:00
}
[SerializeField]
Color textColor ;
2017-08-13 07:19:19 +08:00
internal Sprite cutSprite ;
2017-07-24 12:48:38 +08:00
protected Segment ( )
{ }
public virtual void OnPointerClick ( PointerEventData eventData )
{
if ( eventData . button ! = PointerEventData . InputButton . Left )
return ;
selected = true ;
}
public virtual void OnPointerEnter ( PointerEventData eventData )
{
MaintainSelection ( ) ;
}
public virtual void OnPointerExit ( PointerEventData eventData )
{
MaintainSelection ( ) ;
}
public virtual void OnPointerDown ( PointerEventData eventData )
{
MaintainSelection ( ) ;
}
public virtual void OnPointerUp ( PointerEventData eventData )
{
MaintainSelection ( ) ;
}
public virtual void OnSelect ( BaseEventData eventData )
{
MaintainSelection ( ) ;
}
public virtual void OnDeselect ( BaseEventData eventData )
{
MaintainSelection ( ) ;
}
public virtual void OnSubmit ( BaseEventData eventData )
{
selected = true ;
}
private void SetSelected ( bool value )
{
if ( value & & button . IsActive ( ) & & button . IsInteractable ( ) )
{
2017-08-13 07:19:19 +08:00
if ( segmentedControl . selectedSegment = = this . button )
2017-07-24 12:48:38 +08:00
{
2017-08-13 07:19:19 +08:00
if ( segmentedControl . allowSwitchingOff )
2017-07-24 12:48:38 +08:00
{
Deselect ( ) ;
}
else
{
MaintainSelection ( ) ;
}
}
else
{
2017-08-13 07:19:19 +08:00
if ( segmentedControl . selectedSegment )
2017-07-24 12:48:38 +08:00
{
2017-08-13 07:19:19 +08:00
var segment = segmentedControl . selectedSegment . GetComponent < Segment > ( ) ;
segmentedControl . selectedSegment = null ;
2017-08-13 07:56:22 +08:00
if ( segment )
{
segment . TransitionButton ( ) ;
}
2017-07-24 12:48:38 +08:00
}
2017-08-13 07:19:19 +08:00
segmentedControl . selectedSegment = this . button ;
2017-07-24 12:48:38 +08:00
StoreTextColor ( ) ;
TransitionButton ( ) ;
2017-08-13 07:19:19 +08:00
segmentedControl . onValueChanged . Invoke ( index ) ;
2017-07-24 12:48:38 +08:00
}
}
2017-08-13 07:19:19 +08:00
else if ( segmentedControl . selectedSegment = = this . button )
2017-07-24 12:48:38 +08:00
{
Deselect ( ) ;
}
}
private void Deselect ( )
{
2017-08-13 07:19:19 +08:00
segmentedControl . selectedSegment = null ;
2017-07-24 12:48:38 +08:00
TransitionButton ( ) ;
2017-08-13 07:19:19 +08:00
segmentedControl . onValueChanged . Invoke ( - 1 ) ;
2017-07-24 12:48:38 +08:00
}
void MaintainSelection ( )
{
2017-08-13 07:19:19 +08:00
if ( button ! = segmentedControl . selectedSegment )
2017-07-24 12:48:38 +08:00
return ;
TransitionButton ( true ) ;
}
internal void TransitionButton ( )
{
TransitionButton ( false ) ;
}
internal void TransitionButton ( bool instant )
{
2017-08-13 07:19:19 +08:00
Color tintColor = selected ? segmentedControl . selectedColor : button . colors . normalColor ;
2017-07-24 12:48:38 +08:00
Color textColor = selected ? button . colors . normalColor : this . textColor ;
2017-08-13 07:19:19 +08:00
Sprite transitionSprite = selected ? button . spriteState . pressedSprite : cutSprite ;
2017-07-24 12:48:38 +08:00
string triggerName = selected ? button . animationTriggers . pressedTrigger : button . animationTriggers . normalTrigger ;
switch ( button . transition )
{
case Selectable . Transition . ColorTint :
StartColorTween ( tintColor * button . colors . colorMultiplier , instant ) ;
ChangeTextColor ( textColor * button . colors . colorMultiplier ) ;
break ;
case Selectable . Transition . SpriteSwap :
2017-08-13 07:19:19 +08:00
if ( transitionSprite ! = cutSprite )
transitionSprite = SegmentedControl . CutSprite ( transitionSprite , leftmost , rightmost ) ;
2017-07-24 12:48:38 +08:00
DoSpriteSwap ( transitionSprite ) ;
break ;
case Selectable . Transition . Animation :
TriggerAnimation ( triggerName ) ;
break ;
}
}
void StartColorTween ( Color targetColor , bool instant )
{
if ( button . targetGraphic = = null )
return ;
button . targetGraphic . CrossFadeColor ( targetColor , instant ? 0f : button . colors . fadeDuration , true , true ) ;
}
internal void StoreTextColor ( )
{
var text = GetComponentInChildren < Text > ( ) ;
if ( ! text )
return ;
textColor = text . color ;
}
void ChangeTextColor ( Color targetColor )
{
var text = GetComponentInChildren < Text > ( ) ;
if ( ! text )
return ;
text . color = targetColor ;
}
void DoSpriteSwap ( Sprite newSprite )
{
if ( button . image = = null )
return ;
button . image . overrideSprite = newSprite ;
}
void TriggerAnimation ( string triggername )
{
if ( button . animator = = null | | ! button . animator . isActiveAndEnabled | | ! button . animator . hasBoundPlayables | | string . IsNullOrEmpty ( triggername ) )
return ;
button . animator . ResetTrigger ( button . animationTriggers . normalTrigger ) ;
button . animator . ResetTrigger ( button . animationTriggers . pressedTrigger ) ;
button . animator . ResetTrigger ( button . animationTriggers . highlightedTrigger ) ;
button . animator . ResetTrigger ( button . animationTriggers . disabledTrigger ) ;
button . animator . SetTrigger ( triggername ) ;
}
}
}