Merged in david_gileadi/unity-ui-extensions/segmented-control (pull request #12)
Thanks, looks good Segmented Controlpull/413/head
commit
a56a9a9084
|
@ -1652,6 +1652,32 @@ namespace UnityEditor.UI
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Segmented Control
|
||||||
|
[MenuItem("GameObject/UI/Extensions/Segmented Control", false)]
|
||||||
|
static public void AddSegmentedControl(MenuCommand menuCommand)
|
||||||
|
{
|
||||||
|
GameObject go = CreateUIElementRoot("Segmented Control", menuCommand, s_ThinGUIElementSize);
|
||||||
|
SegmentedControl control = go.AddComponent<SegmentedControl>();
|
||||||
|
|
||||||
|
Color selectedColor = new Color(0f, 0.455f, 0.894f);
|
||||||
|
control.selectedColor = selectedColor;
|
||||||
|
|
||||||
|
var labels = new string[] { "This", "That", "Other" };
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
var button = AddButtonAsChild(go);
|
||||||
|
button.name = "Segment " + (i + 1);
|
||||||
|
var text = button.GetComponentInChildren<Text>();
|
||||||
|
text.text = labels[i];
|
||||||
|
text.color = selectedColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
control.LayoutSegments();
|
||||||
|
|
||||||
|
Selection.activeGameObject = go;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region UI Knob
|
#region UI Knob
|
||||||
[MenuItem("GameObject/UI/Extensions/UI Knob", false)]
|
[MenuItem("GameObject/UI/Extensions/UI Knob", false)]
|
||||||
static public void AddUIKnob(MenuCommand menuCommand)
|
static public void AddUIKnob(MenuCommand menuCommand)
|
||||||
|
|
|
@ -0,0 +1,422 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine.Events;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
using UnityEngine.Serialization;
|
||||||
|
|
||||||
|
namespace UnityEngine.UI.Extensions
|
||||||
|
{
|
||||||
|
// Segmented control, like a group of buttons
|
||||||
|
[AddComponentMenu("UI/Extensions/Segmented Control")]
|
||||||
|
[RequireComponent(typeof(RectTransform))]
|
||||||
|
public class SegmentedControl : UIBehaviour
|
||||||
|
{
|
||||||
|
public Button[] segments
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_segments == null || _segments.Length == 0)
|
||||||
|
{
|
||||||
|
_segments = GetChildSegments();
|
||||||
|
}
|
||||||
|
return _segments;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private Button[] _segments;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
public Color selectedColor;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private Graphic _separator;
|
||||||
|
public Graphic separator { get { return _separator; } set { _separator = value; _separatorWidth = 0; LayoutSegments(); } }
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField] private bool _allowSwitchingOff = false;
|
||||||
|
public bool allowSwitchingOff { get { return _allowSwitchingOff; } set { _allowSwitchingOff = value; } }
|
||||||
|
|
||||||
|
protected internal Button selectedSegment;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private int _selectedSegmentIndex = -1;
|
||||||
|
public int selectedSegmentIndex
|
||||||
|
{
|
||||||
|
get { return Array.IndexOf(segments, selectedSegment); }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
value = Math.Max(value, -1);
|
||||||
|
value = Math.Min(value, segments.Length - 1);
|
||||||
|
_selectedSegmentIndex = value;
|
||||||
|
if (value == -1)
|
||||||
|
{
|
||||||
|
if (selectedSegment)
|
||||||
|
{
|
||||||
|
selectedSegment.GetComponent<Segment>().selected = false;
|
||||||
|
selectedSegment = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
segments[value].GetComponent<Segment>().StoreTextColor();
|
||||||
|
#endif
|
||||||
|
segments[value].GetComponent<Segment>().selected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class SegmentSelectedEvent : UnityEvent<int> { }
|
||||||
|
|
||||||
|
// Event delegates triggered on click.
|
||||||
|
[SerializeField]
|
||||||
|
private SegmentSelectedEvent _onValueChanged = new SegmentSelectedEvent();
|
||||||
|
|
||||||
|
public SegmentSelectedEvent onValueChanged
|
||||||
|
{
|
||||||
|
get { return _onValueChanged; }
|
||||||
|
set { _onValueChanged = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SegmentedControl()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected override void Start()
|
||||||
|
{
|
||||||
|
base.Start();
|
||||||
|
|
||||||
|
LayoutSegments();
|
||||||
|
|
||||||
|
if (_selectedSegmentIndex != -1)
|
||||||
|
selectedSegmentIndex = _selectedSegmentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
protected override void OnValidate()
|
||||||
|
{
|
||||||
|
base.OnValidate();
|
||||||
|
|
||||||
|
if (separator)
|
||||||
|
LayoutSegments();
|
||||||
|
|
||||||
|
if (_selectedSegmentIndex != -1)
|
||||||
|
selectedSegmentIndex = _selectedSegmentIndex;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private Button[] GetChildSegments()
|
||||||
|
{
|
||||||
|
var buttons = GetComponentsInChildren<Button>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetAllSegmentsOff()
|
||||||
|
{
|
||||||
|
selectedSegment = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecreateSprites()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < segments.Length; i++)
|
||||||
|
{
|
||||||
|
if (segments[i].image == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var sprite = segments[i].image.sprite;
|
||||||
|
if (sprite.border.x == 0 || sprite.border.z == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var rect = sprite.rect;
|
||||||
|
var border = sprite.border;
|
||||||
|
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
rect.xMin = border.x;
|
||||||
|
border.x = 0;
|
||||||
|
}
|
||||||
|
if (i < segments.Length - 1)
|
||||||
|
{
|
||||||
|
rect.xMax = border.z;
|
||||||
|
border.z = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
segments[i].image.sprite = Sprite.Create(sprite.texture, rect, sprite.pivot, sprite.pixelsPerUnit, 0, SpriteMeshType.FullRect, border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LayoutSegments()
|
||||||
|
{
|
||||||
|
RecreateSprites();
|
||||||
|
|
||||||
|
RectTransform transform = this.transform as RectTransform;
|
||||||
|
float width = (transform.rect.width / segments.Length) - (separatorWidth * (segments.Length - 1));
|
||||||
|
|
||||||
|
for (int i = 0; i < segments.Length; i++)
|
||||||
|
{
|
||||||
|
float insetX = ((width + separatorWidth) * i);
|
||||||
|
|
||||||
|
var rectTransform = segments[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);
|
||||||
|
|
||||||
|
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;
|
||||||
|
sep.rectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Left, insetX - separatorWidth, separatorWidth);
|
||||||
|
sep.rectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Top, 0, transform.rect.height);
|
||||||
|
}
|
||||||
|
// TODO: maybe adjust text position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RequireComponent(typeof(Button))]
|
||||||
|
public class Segment :
|
||||||
|
UIBehaviour,
|
||||||
|
IPointerClickHandler,
|
||||||
|
ISubmitHandler,
|
||||||
|
IPointerEnterHandler, IPointerExitHandler,
|
||||||
|
IPointerDownHandler, IPointerUpHandler,
|
||||||
|
ISelectHandler, IDeselectHandler
|
||||||
|
{
|
||||||
|
internal int index;
|
||||||
|
|
||||||
|
internal bool leftmost
|
||||||
|
{
|
||||||
|
get { return index == 0; }
|
||||||
|
}
|
||||||
|
internal bool rightmost
|
||||||
|
{
|
||||||
|
get { return index == segmentControl.segments.Length - 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool selected
|
||||||
|
{
|
||||||
|
get { return segmentControl.selectedSegment == this.button; }
|
||||||
|
set { SetSelected(value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal SegmentedControl segmentControl
|
||||||
|
{
|
||||||
|
get { return GetComponentInParent<SegmentedControl>(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Button button
|
||||||
|
{
|
||||||
|
get { return GetComponent<Button>(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
Color textColor;
|
||||||
|
|
||||||
|
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())
|
||||||
|
{
|
||||||
|
if (segmentControl.selectedSegment == this.button)
|
||||||
|
{
|
||||||
|
if (segmentControl.allowSwitchingOff)
|
||||||
|
{
|
||||||
|
Deselect();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MaintainSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (segmentControl.selectedSegment)
|
||||||
|
{
|
||||||
|
var segment = segmentControl.selectedSegment.GetComponent<Segment>();
|
||||||
|
segmentControl.selectedSegment = null;
|
||||||
|
segment.TransitionButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
segmentControl.selectedSegment = this.button;
|
||||||
|
StoreTextColor();
|
||||||
|
TransitionButton();
|
||||||
|
segmentControl.onValueChanged.Invoke(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (segmentControl.selectedSegment == this.button)
|
||||||
|
{
|
||||||
|
Deselect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Deselect()
|
||||||
|
{
|
||||||
|
segmentControl.selectedSegment = null;
|
||||||
|
TransitionButton();
|
||||||
|
segmentControl.onValueChanged.Invoke(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaintainSelection()
|
||||||
|
{
|
||||||
|
if (button != segmentControl.selectedSegment)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TransitionButton(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void TransitionButton()
|
||||||
|
{
|
||||||
|
TransitionButton(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void TransitionButton(bool instant)
|
||||||
|
{
|
||||||
|
Color tintColor = selected ? segmentControl.selectedColor : button.colors.normalColor;
|
||||||
|
Color textColor = selected ? button.colors.normalColor : this.textColor;
|
||||||
|
Sprite transitionSprite = selected ? button.spriteState.pressedSprite : null;
|
||||||
|
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:
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 439b3dcc2b7714c1e91776a9d161f6ee
|
||||||
|
timeCreated: 1498517608
|
||||||
|
licenseType: Free
|
||||||
|
MonoImporter:
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
Loading…
Reference in New Issue