diff options
author | chai <chaifix@163.com> | 2021-05-08 23:15:13 +0800 |
---|---|---|
committer | chai <chaifix@163.com> | 2021-05-08 23:15:13 +0800 |
commit | d07e14add74e017b52ab2371efeea1aa4ea10ced (patch) | |
tree | efd07869326e4c428f5bfe43fad0c2583d32a401 /Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls |
+init
Diffstat (limited to 'Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls')
26 files changed, 6877 insertions, 0 deletions
diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/AnimationTriggers.cs b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/AnimationTriggers.cs new file mode 100644 index 0000000..e42871b --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/AnimationTriggers.cs @@ -0,0 +1,36 @@ +using System; +using UnityEngine.Serialization; + +namespace UnityEngine.UI +{ + [Serializable] + public class AnimationTriggers + { + private const string kDefaultNormalAnimName = "Normal"; + private const string kDefaultSelectedAnimName = "Highlighted"; + private const string kDefaultPressedAnimName = "Pressed"; + private const string kDefaultDisabledAnimName = "Disabled"; + + [FormerlySerializedAs("normalTrigger")] + [SerializeField] + private string m_NormalTrigger = kDefaultNormalAnimName; + + [FormerlySerializedAs("highlightedTrigger")] + [FormerlySerializedAs("m_SelectedTrigger")] + [SerializeField] + private string m_HighlightedTrigger = kDefaultSelectedAnimName; + + [FormerlySerializedAs("pressedTrigger")] + [SerializeField] + private string m_PressedTrigger = kDefaultPressedAnimName; + + [FormerlySerializedAs("disabledTrigger")] + [SerializeField] + private string m_DisabledTrigger = kDefaultDisabledAnimName; + + public string normalTrigger { get { return m_NormalTrigger; } set { m_NormalTrigger = value; } } + public string highlightedTrigger { get { return m_HighlightedTrigger; } set { m_HighlightedTrigger = value; } } + public string pressedTrigger { get { return m_PressedTrigger; } set { m_PressedTrigger = value; } } + public string disabledTrigger { get { return m_DisabledTrigger; } set { m_DisabledTrigger = value; } } + } +} diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/AnimationTriggers.cs.meta b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/AnimationTriggers.cs.meta new file mode 100644 index 0000000..52f2ca0 --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/AnimationTriggers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd02e9c578e561646b10984381250bf5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Button.cs b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Button.cs new file mode 100644 index 0000000..d0c9ae3 --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Button.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine.Events; +using UnityEngine.EventSystems; +using UnityEngine.Serialization; + +namespace UnityEngine.UI +{ + // Button that's meant to work with mouse or touch-based devices. + [AddComponentMenu("UI/Button", 30)] + public class Button + : Selectable + , IPointerClickHandler // 鼠标点击\触摸 + , ISubmitHandler // Input>Submit触发,比如手柄、键盘某个按键按下 + { + [Serializable] + public class ButtonClickedEvent : UnityEvent {} + + // Event delegates triggered on click. + [FormerlySerializedAs("onClick")] + [SerializeField] + private ButtonClickedEvent m_OnClick = new ButtonClickedEvent(); + + protected Button() + {} + + public ButtonClickedEvent onClick + { + get { return m_OnClick; } + set { m_OnClick = value; } + } + + // 调回调 + private void Press() + { + if (!IsActive() || !IsInteractable()) + return; + + UISystemProfilerApi.AddMarker("Button.onClick", this); + m_OnClick.Invoke(); + } + + // Trigger all registered callbacks. + public virtual void OnPointerClick(PointerEventData eventData) + { + if (eventData.button != PointerEventData.InputButton.Left) + return; + + Press(); + } + + public virtual void OnSubmit(BaseEventData eventData) + { + LogHelper.Log("OnSubmit() " + gameObject.name); + + Press(); + + // if we get set disabled during the press + // don't run the coroutine. + if (!IsActive() || !IsInteractable()) + return; + + DoStateTransition(SelectionState.Pressed, false); + StartCoroutine(OnFinishSubmit()); + } + + private IEnumerator OnFinishSubmit() + { + var fadeTime = colors.fadeDuration; + var elapsedTime = 0f; + + while (elapsedTime < fadeTime) + { + elapsedTime += Time.unscaledDeltaTime; + yield return null; + } + + DoStateTransition(currentSelectionState, false); + } + + } +} diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Button.cs.meta b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Button.cs.meta new file mode 100644 index 0000000..cbdb3a1 --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Button.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a754c749030e1d848bed7a3cb2e5520f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ColorBlock.cs b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ColorBlock.cs new file mode 100644 index 0000000..c78d617 --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ColorBlock.cs @@ -0,0 +1,91 @@ +using System; +using UnityEngine.Serialization; + +namespace UnityEngine.UI +{ + [Serializable] + public struct ColorBlock : IEquatable<ColorBlock> + { + [FormerlySerializedAs("normalColor")] + [SerializeField] + private Color m_NormalColor; + + [FormerlySerializedAs("highlightedColor")] + [FormerlySerializedAs("m_SelectedColor")] + [SerializeField] + private Color m_HighlightedColor; + + [FormerlySerializedAs("pressedColor")] + [SerializeField] + private Color m_PressedColor; + + [FormerlySerializedAs("disabledColor")] + [SerializeField] + private Color m_DisabledColor; + + [Range(1, 5)] + [SerializeField] + private float m_ColorMultiplier; + + [FormerlySerializedAs("fadeDuration")] + [SerializeField] + private float m_FadeDuration; + + public Color normalColor { get { return m_NormalColor; } set { m_NormalColor = value; } } + public Color highlightedColor { get { return m_HighlightedColor; } set { m_HighlightedColor = value; } } + public Color pressedColor { get { return m_PressedColor; } set { m_PressedColor = value; } } + public Color disabledColor { get { return m_DisabledColor; } set { m_DisabledColor = value; } } + public float colorMultiplier { get { return m_ColorMultiplier; } set { m_ColorMultiplier = value; } } + public float fadeDuration { get { return m_FadeDuration; } set { m_FadeDuration = value; } } + + public static ColorBlock defaultColorBlock + { + get + { + var c = new ColorBlock + { + m_NormalColor = new Color32(255, 255, 255, 255), + m_HighlightedColor = new Color32(245, 245, 245, 255), + m_PressedColor = new Color32(200, 200, 200, 255), + m_DisabledColor = new Color32(200, 200, 200, 128), + colorMultiplier = 1.0f, + fadeDuration = 0.1f + }; + return c; + } + } + + public override bool Equals(object obj) + { + if (!(obj is ColorBlock)) + return false; + + return Equals((ColorBlock)obj); + } + + public bool Equals(ColorBlock other) + { + return normalColor == other.normalColor && + highlightedColor == other.highlightedColor && + pressedColor == other.pressedColor && + disabledColor == other.disabledColor && + colorMultiplier == other.colorMultiplier && + fadeDuration == other.fadeDuration; + } + + public static bool operator==(ColorBlock point1, ColorBlock point2) + { + return point1.Equals(point2); + } + + public static bool operator!=(ColorBlock point1, ColorBlock point2) + { + return !point1.Equals(point2); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + } +} diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ColorBlock.cs.meta b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ColorBlock.cs.meta new file mode 100644 index 0000000..8680539 --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ColorBlock.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f7d25b8a109dd134fa40749f78bbd907 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/DefaultControls.cs b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/DefaultControls.cs new file mode 100644 index 0000000..a22ff9a --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/DefaultControls.cs @@ -0,0 +1,581 @@ +using UnityEngine; +using System.Collections.Generic; + +namespace UnityEngine.UI +{ + public static class DefaultControls + { + public struct Resources + { + public Sprite standard; + public Sprite background; + public Sprite inputField; + public Sprite knob; + public Sprite checkmark; + public Sprite dropdown; + public Sprite mask; + } + + private const float kWidth = 160f; + private const float kThickHeight = 30f; + private const float kThinHeight = 20f; + private static Vector2 s_ThickElementSize = new Vector2(kWidth, kThickHeight); + private static Vector2 s_ThinElementSize = new Vector2(kWidth, kThinHeight); + private static Vector2 s_ImageElementSize = new Vector2(100f, 100f); + private static Color s_DefaultSelectableColor = new Color(1f, 1f, 1f, 1f); + private static Color s_PanelColor = new Color(1f, 1f, 1f, 0.392f); + private static Color s_TextColor = new Color(50f / 255f, 50f / 255f, 50f / 255f, 1f); + + // Helper methods at top + + private static GameObject CreateUIElementRoot(string name, Vector2 size) + { + GameObject child = new GameObject(name); + RectTransform rectTransform = child.AddComponent<RectTransform>(); + rectTransform.sizeDelta = size; + return child; + } + + static GameObject CreateUIObject(string name, GameObject parent) + { + GameObject go = new GameObject(name); + go.AddComponent<RectTransform>(); + SetParentAndAlign(go, parent); + return go; + } + + private static void SetDefaultTextValues(Text lbl) + { + // Set text values we want across UI elements in default controls. + // Don't set values which are the same as the default values for the Text component, + // since there's no point in that, and it's good to keep them as consistent as possible. + lbl.color = s_TextColor; + + // Reset() is not called when playing. We still want the default font to be assigned + lbl.AssignDefaultFont(); + } + + private static void SetDefaultColorTransitionValues(Selectable slider) + { + ColorBlock colors = slider.colors; + colors.highlightedColor = new Color(0.882f, 0.882f, 0.882f); + colors.pressedColor = new Color(0.698f, 0.698f, 0.698f); + colors.disabledColor = new Color(0.521f, 0.521f, 0.521f); + } + + private static void SetParentAndAlign(GameObject child, GameObject parent) + { + if (parent == null) + return; + + child.transform.SetParent(parent.transform, false); + SetLayerRecursively(child, parent.layer); + } + + private static void SetLayerRecursively(GameObject go, int layer) + { + go.layer = layer; + Transform t = go.transform; + for (int i = 0; i < t.childCount; i++) + SetLayerRecursively(t.GetChild(i).gameObject, layer); + } + + // Actual controls + + public static GameObject CreatePanel(Resources resources) + { + GameObject panelRoot = CreateUIElementRoot("Panel", s_ThickElementSize); + + // Set RectTransform to stretch + RectTransform rectTransform = panelRoot.GetComponent<RectTransform>(); + rectTransform.anchorMin = Vector2.zero; + rectTransform.anchorMax = Vector2.one; + rectTransform.anchoredPosition = Vector2.zero; + rectTransform.sizeDelta = Vector2.zero; + + Image image = panelRoot.AddComponent<Image>(); + image.sprite = resources.background; + image.type = Image.Type.Sliced; + image.color = s_PanelColor; + + return panelRoot; + } + + public static GameObject CreateButton(Resources resources) + { + GameObject buttonRoot = CreateUIElementRoot("Button", s_ThickElementSize); + + GameObject childText = new GameObject("Text"); + childText.AddComponent<RectTransform>(); + SetParentAndAlign(childText, buttonRoot); + + Image image = buttonRoot.AddComponent<Image>(); + image.sprite = resources.standard; + image.type = Image.Type.Sliced; + image.color = s_DefaultSelectableColor; + + Button bt = buttonRoot.AddComponent<Button>(); + SetDefaultColorTransitionValues(bt); + + Text text = childText.AddComponent<Text>(); + text.text = "Button"; + text.alignment = TextAnchor.MiddleCenter; + SetDefaultTextValues(text); + + RectTransform textRectTransform = childText.GetComponent<RectTransform>(); + textRectTransform.anchorMin = Vector2.zero; + textRectTransform.anchorMax = Vector2.one; + textRectTransform.sizeDelta = Vector2.zero; + + return buttonRoot; + } + + public static GameObject CreateText(Resources resources) + { + GameObject go = CreateUIElementRoot("Text", s_ThickElementSize); + + Text lbl = go.AddComponent<Text>(); + lbl.text = "New Text"; + SetDefaultTextValues(lbl); + + return go; + } + + public static GameObject CreateImage(Resources resources) + { + GameObject go = CreateUIElementRoot("Image", s_ImageElementSize); + go.AddComponent<Image>(); + return go; + } + + public static GameObject CreateRawImage(Resources resources) + { + GameObject go = CreateUIElementRoot("RawImage", s_ImageElementSize); + go.AddComponent<RawImage>(); + return go; + } + + public static GameObject CreateSlider(Resources resources) + { + // Create GOs Hierarchy + GameObject root = CreateUIElementRoot("Slider", s_ThinElementSize); + + GameObject background = CreateUIObject("Background", root); + GameObject fillArea = CreateUIObject("Fill Area", root); + GameObject fill = CreateUIObject("Fill", fillArea); + GameObject handleArea = CreateUIObject("Handle Slide Area", root); + GameObject handle = CreateUIObject("Handle", handleArea); + + // Background + Image backgroundImage = background.AddComponent<Image>(); + backgroundImage.sprite = resources.background; + backgroundImage.type = Image.Type.Sliced; + backgroundImage.color = s_DefaultSelectableColor; + RectTransform backgroundRect = background.GetComponent<RectTransform>(); + backgroundRect.anchorMin = new Vector2(0, 0.25f); + backgroundRect.anchorMax = new Vector2(1, 0.75f); + backgroundRect.sizeDelta = new Vector2(0, 0); + + // Fill Area + RectTransform fillAreaRect = fillArea.GetComponent<RectTransform>(); + fillAreaRect.anchorMin = new Vector2(0, 0.25f); + fillAreaRect.anchorMax = new Vector2(1, 0.75f); + fillAreaRect.anchoredPosition = new Vector2(-5, 0); + fillAreaRect.sizeDelta = new Vector2(-20, 0); + + // Fill + Image fillImage = fill.AddComponent<Image>(); + fillImage.sprite = resources.standard; + fillImage.type = Image.Type.Sliced; + fillImage.color = s_DefaultSelectableColor; + + RectTransform fillRect = fill.GetComponent<RectTransform>(); + fillRect.sizeDelta = new Vector2(10, 0); + + // Handle Area + RectTransform handleAreaRect = handleArea.GetComponent<RectTransform>(); + handleAreaRect.sizeDelta = new Vector2(-20, 0); + handleAreaRect.anchorMin = new Vector2(0, 0); + handleAreaRect.anchorMax = new Vector2(1, 1); + + // Handle + Image handleImage = handle.AddComponent<Image>(); + handleImage.sprite = resources.knob; + handleImage.color = s_DefaultSelectableColor; + + RectTransform handleRect = handle.GetComponent<RectTransform>(); + handleRect.sizeDelta = new Vector2(20, 0); + + // Setup slider component + Slider slider = root.AddComponent<Slider>(); + slider.fillRect = fill.GetComponent<RectTransform>(); + slider.handleRect = handle.GetComponent<RectTransform>(); + slider.targetGraphic = handleImage; + slider.direction = Slider.Direction.LeftToRight; + SetDefaultColorTransitionValues(slider); + + return root; + } + + public static GameObject CreateScrollbar(Resources resources) + { + // Create GOs Hierarchy + GameObject scrollbarRoot = CreateUIElementRoot("Scrollbar", s_ThinElementSize); + + GameObject sliderArea = CreateUIObject("Sliding Area", scrollbarRoot); + GameObject handle = CreateUIObject("Handle", sliderArea); + + Image bgImage = scrollbarRoot.AddComponent<Image>(); + bgImage.sprite = resources.background; + bgImage.type = Image.Type.Sliced; + bgImage.color = s_DefaultSelectableColor; + + Image handleImage = handle.AddComponent<Image>(); + handleImage.sprite = resources.standard; + handleImage.type = Image.Type.Sliced; + handleImage.color = s_DefaultSelectableColor; + + RectTransform sliderAreaRect = sliderArea.GetComponent<RectTransform>(); + sliderAreaRect.sizeDelta = new Vector2(-20, -20); + sliderAreaRect.anchorMin = Vector2.zero; + sliderAreaRect.anchorMax = Vector2.one; + + RectTransform handleRect = handle.GetComponent<RectTransform>(); + handleRect.sizeDelta = new Vector2(20, 20); + + Scrollbar scrollbar = scrollbarRoot.AddComponent<Scrollbar>(); + scrollbar.handleRect = handleRect; + scrollbar.targetGraphic = handleImage; + SetDefaultColorTransitionValues(scrollbar); + + return scrollbarRoot; + } + + public static GameObject CreateToggle(Resources resources) + { + // Set up hierarchy + GameObject toggleRoot = CreateUIElementRoot("Toggle", s_ThinElementSize); + + GameObject background = CreateUIObject("Background", toggleRoot); + GameObject checkmark = CreateUIObject("Checkmark", background); + GameObject childLabel = CreateUIObject("Label", toggleRoot); + + // Set up components + Toggle toggle = toggleRoot.AddComponent<Toggle>(); + toggle.isOn = true; + + Image bgImage = background.AddComponent<Image>(); + bgImage.sprite = resources.standard; + bgImage.type = Image.Type.Sliced; + bgImage.color = s_DefaultSelectableColor; + + Image checkmarkImage = checkmark.AddComponent<Image>(); + checkmarkImage.sprite = resources.checkmark; + + Text label = childLabel.AddComponent<Text>(); + label.text = "Toggle"; + SetDefaultTextValues(label); + + toggle.graphic = checkmarkImage; + toggle.targetGraphic = bgImage; + SetDefaultColorTransitionValues(toggle); + + RectTransform bgRect = background.GetComponent<RectTransform>(); + bgRect.anchorMin = new Vector2(0f, 1f); + bgRect.anchorMax = new Vector2(0f, 1f); + bgRect.anchoredPosition = new Vector2(10f, -10f); + bgRect.sizeDelta = new Vector2(kThinHeight, kThinHeight); + + RectTransform checkmarkRect = checkmark.GetComponent<RectTransform>(); + checkmarkRect.anchorMin = new Vector2(0.5f, 0.5f); + checkmarkRect.anchorMax = new Vector2(0.5f, 0.5f); + checkmarkRect.anchoredPosition = Vector2.zero; + checkmarkRect.sizeDelta = new Vector2(20f, 20f); + + RectTransform labelRect = childLabel.GetComponent<RectTransform>(); + labelRect.anchorMin = new Vector2(0f, 0f); + labelRect.anchorMax = new Vector2(1f, 1f); + labelRect.offsetMin = new Vector2(23f, 1f); + labelRect.offsetMax = new Vector2(-5f, -2f); + + return toggleRoot; + } + + public static GameObject CreateInputField(Resources resources) + { + GameObject root = CreateUIElementRoot("InputField", s_ThickElementSize); + + GameObject childPlaceholder = CreateUIObject("Placeholder", root); + GameObject childText = CreateUIObject("Text", root); + + Image image = root.AddComponent<Image>(); + image.sprite = resources.inputField; + image.type = Image.Type.Sliced; + image.color = s_DefaultSelectableColor; + + InputField inputField = root.AddComponent<InputField>(); + SetDefaultColorTransitionValues(inputField); + + Text text = childText.AddComponent<Text>(); + text.text = ""; + text.supportRichText = false; + SetDefaultTextValues(text); + + Text placeholder = childPlaceholder.AddComponent<Text>(); + placeholder.text = "Enter text..."; + placeholder.fontStyle = FontStyle.Italic; + // Make placeholder color half as opaque as normal text color. + Color placeholderColor = text.color; + placeholderColor.a *= 0.5f; + placeholder.color = placeholderColor; + + RectTransform textRectTransform = childText.GetComponent<RectTransform>(); + textRectTransform.anchorMin = Vector2.zero; + textRectTransform.anchorMax = Vector2.one; + textRectTransform.sizeDelta = Vector2.zero; + textRectTransform.offsetMin = new Vector2(10, 6); + textRectTransform.offsetMax = new Vector2(-10, -7); + + RectTransform placeholderRectTransform = childPlaceholder.GetComponent<RectTransform>(); + placeholderRectTransform.anchorMin = Vector2.zero; + placeholderRectTransform.anchorMax = Vector2.one; + placeholderRectTransform.sizeDelta = Vector2.zero; + placeholderRectTransform.offsetMin = new Vector2(10, 6); + placeholderRectTransform.offsetMax = new Vector2(-10, -7); + + inputField.textComponent = text; + inputField.placeholder = placeholder; + + return root; + } + + public static GameObject CreateDropdown(Resources resources) + { + GameObject root = CreateUIElementRoot("Dropdown", s_ThickElementSize); + + GameObject label = CreateUIObject("Label", root); + GameObject arrow = CreateUIObject("Arrow", root); + GameObject template = CreateUIObject("Template", root); + GameObject viewport = CreateUIObject("Viewport", template); + GameObject content = CreateUIObject("Content", viewport); + GameObject item = CreateUIObject("Item", content); + GameObject itemBackground = CreateUIObject("Item Background", item); + GameObject itemCheckmark = CreateUIObject("Item Checkmark", item); + GameObject itemLabel = CreateUIObject("Item Label", item); + + // Sub controls. + + GameObject scrollbar = CreateScrollbar(resources); + scrollbar.name = "Scrollbar"; + SetParentAndAlign(scrollbar, template); + + Scrollbar scrollbarScrollbar = scrollbar.GetComponent<Scrollbar>(); + scrollbarScrollbar.SetDirection(Scrollbar.Direction.BottomToTop, true); + + RectTransform vScrollbarRT = scrollbar.GetComponent<RectTransform>(); + vScrollbarRT.anchorMin = Vector2.right; + vScrollbarRT.anchorMax = Vector2.one; + vScrollbarRT.pivot = Vector2.one; + vScrollbarRT.sizeDelta = new Vector2(vScrollbarRT.sizeDelta.x, 0); + + // Setup item UI components. + + Text itemLabelText = itemLabel.AddComponent<Text>(); + SetDefaultTextValues(itemLabelText); + itemLabelText.alignment = TextAnchor.MiddleLeft; + + Image itemBackgroundImage = itemBackground.AddComponent<Image>(); + itemBackgroundImage.color = new Color32(245, 245, 245, 255); + + Image itemCheckmarkImage = itemCheckmark.AddComponent<Image>(); + itemCheckmarkImage.sprite = resources.checkmark; + + Toggle itemToggle = item.AddComponent<Toggle>(); + itemToggle.targetGraphic = itemBackgroundImage; + itemToggle.graphic = itemCheckmarkImage; + itemToggle.isOn = true; + + // Setup template UI components. + + Image templateImage = template.AddComponent<Image>(); + templateImage.sprite = resources.standard; + templateImage.type = Image.Type.Sliced; + + ScrollRect templateScrollRect = template.AddComponent<ScrollRect>(); + templateScrollRect.content = (RectTransform)content.transform; + templateScrollRect.viewport = (RectTransform)viewport.transform; + templateScrollRect.horizontal = false; + templateScrollRect.movementType = ScrollRect.MovementType.Clamped; + templateScrollRect.verticalScrollbar = scrollbarScrollbar; + templateScrollRect.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHideAndExpandViewport; + templateScrollRect.verticalScrollbarSpacing = -3; + + Mask scrollRectMask = viewport.AddComponent<Mask>(); + scrollRectMask.showMaskGraphic = false; + + Image viewportImage = viewport.AddComponent<Image>(); + viewportImage.sprite = resources.mask; + viewportImage.type = Image.Type.Sliced; + + // Setup dropdown UI components. + + Text labelText = label.AddComponent<Text>(); + SetDefaultTextValues(labelText); + labelText.alignment = TextAnchor.MiddleLeft; + + Image arrowImage = arrow.AddComponent<Image>(); + arrowImage.sprite = resources.dropdown; + + Image backgroundImage = root.AddComponent<Image>(); + backgroundImage.sprite = resources.standard; + backgroundImage.color = s_DefaultSelectableColor; + backgroundImage.type = Image.Type.Sliced; + + Dropdown dropdown = root.AddComponent<Dropdown>(); + dropdown.targetGraphic = backgroundImage; + SetDefaultColorTransitionValues(dropdown); + dropdown.template = template.GetComponent<RectTransform>(); + dropdown.captionText = labelText; + dropdown.itemText = itemLabelText; + + // Setting default Item list. + itemLabelText.text = "Option A"; + dropdown.options.Add(new Dropdown.OptionData {text = "Option A"}); + dropdown.options.Add(new Dropdown.OptionData {text = "Option B"}); + dropdown.options.Add(new Dropdown.OptionData {text = "Option C"}); + dropdown.RefreshShownValue(); + + // Set up RectTransforms. + + RectTransform labelRT = label.GetComponent<RectTransform>(); + labelRT.anchorMin = Vector2.zero; + labelRT.anchorMax = Vector2.one; + labelRT.offsetMin = new Vector2(10, 6); + labelRT.offsetMax = new Vector2(-25, -7); + + RectTransform arrowRT = arrow.GetComponent<RectTransform>(); + arrowRT.anchorMin = new Vector2(1, 0.5f); + arrowRT.anchorMax = new Vector2(1, 0.5f); + arrowRT.sizeDelta = new Vector2(20, 20); + arrowRT.anchoredPosition = new Vector2(-15, 0); + + RectTransform templateRT = template.GetComponent<RectTransform>(); + templateRT.anchorMin = new Vector2(0, 0); + templateRT.anchorMax = new Vector2(1, 0); + templateRT.pivot = new Vector2(0.5f, 1); + templateRT.anchoredPosition = new Vector2(0, 2); + templateRT.sizeDelta = new Vector2(0, 150); + + RectTransform viewportRT = viewport.GetComponent<RectTransform>(); + viewportRT.anchorMin = new Vector2(0, 0); + viewportRT.anchorMax = new Vector2(1, 1); + viewportRT.sizeDelta = new Vector2(-18, 0); + viewportRT.pivot = new Vector2(0, 1); + + RectTransform contentRT = content.GetComponent<RectTransform>(); + contentRT.anchorMin = new Vector2(0f, 1); + contentRT.anchorMax = new Vector2(1f, 1); + contentRT.pivot = new Vector2(0.5f, 1); + contentRT.anchoredPosition = new Vector2(0, 0); + contentRT.sizeDelta = new Vector2(0, 28); + + RectTransform itemRT = item.GetComponent<RectTransform>(); + itemRT.anchorMin = new Vector2(0, 0.5f); + itemRT.anchorMax = new Vector2(1, 0.5f); + itemRT.sizeDelta = new Vector2(0, 20); + + RectTransform itemBackgroundRT = itemBackground.GetComponent<RectTransform>(); + itemBackgroundRT.anchorMin = Vector2.zero; + itemBackgroundRT.anchorMax = Vector2.one; + itemBackgroundRT.sizeDelta = Vector2.zero; + + RectTransform itemCheckmarkRT = itemCheckmark.GetComponent<RectTransform>(); + itemCheckmarkRT.anchorMin = new Vector2(0, 0.5f); + itemCheckmarkRT.anchorMax = new Vector2(0, 0.5f); + itemCheckmarkRT.sizeDelta = new Vector2(20, 20); + itemCheckmarkRT.anchoredPosition = new Vector2(10, 0); + + RectTransform itemLabelRT = itemLabel.GetComponent<RectTransform>(); + itemLabelRT.anchorMin = Vector2.zero; + itemLabelRT.anchorMax = Vector2.one; + itemLabelRT.offsetMin = new Vector2(20, 1); + itemLabelRT.offsetMax = new Vector2(-10, -2); + + template.SetActive(false); + + return root; + } + + public static GameObject CreateScrollView(Resources resources) + { + GameObject root = CreateUIElementRoot("Scroll View", new Vector2(200, 200)); + + GameObject viewport = CreateUIObject("Viewport", root); + GameObject content = CreateUIObject("Content", viewport); + + // Sub controls. + + GameObject hScrollbar = CreateScrollbar(resources); + hScrollbar.name = "Scrollbar Horizontal"; + SetParentAndAlign(hScrollbar, root); + RectTransform hScrollbarRT = hScrollbar.GetComponent<RectTransform>(); + hScrollbarRT.anchorMin = Vector2.zero; + hScrollbarRT.anchorMax = Vector2.right; + hScrollbarRT.pivot = Vector2.zero; + hScrollbarRT.sizeDelta = new Vector2(0, hScrollbarRT.sizeDelta.y); + + GameObject vScrollbar = CreateScrollbar(resources); + vScrollbar.name = "Scrollbar Vertical"; + SetParentAndAlign(vScrollbar, root); + vScrollbar.GetComponent<Scrollbar>().SetDirection(Scrollbar.Direction.BottomToTop, true); + RectTransform vScrollbarRT = vScrollbar.GetComponent<RectTransform>(); + vScrollbarRT.anchorMin = Vector2.right; + vScrollbarRT.anchorMax = Vector2.one; + vScrollbarRT.pivot = Vector2.one; + vScrollbarRT.sizeDelta = new Vector2(vScrollbarRT.sizeDelta.x, 0); + + // Setup RectTransforms. + + // Make viewport fill entire scroll view. + RectTransform viewportRT = viewport.GetComponent<RectTransform>(); + viewportRT.anchorMin = Vector2.zero; + viewportRT.anchorMax = Vector2.one; + viewportRT.sizeDelta = Vector2.zero; + viewportRT.pivot = Vector2.up; + + // Make context match viewpoprt width and be somewhat taller. + // This will show the vertical scrollbar and not the horizontal one. + RectTransform contentRT = content.GetComponent<RectTransform>(); + contentRT.anchorMin = Vector2.up; + contentRT.anchorMax = Vector2.one; + contentRT.sizeDelta = new Vector2(0, 300); + contentRT.pivot = Vector2.up; + + // Setup UI components. + + ScrollRect scrollRect = root.AddComponent<ScrollRect>(); + scrollRect.content = contentRT; + scrollRect.viewport = viewportRT; + scrollRect.horizontalScrollbar = hScrollbar.GetComponent<Scrollbar>(); + scrollRect.verticalScrollbar = vScrollbar.GetComponent<Scrollbar>(); + scrollRect.horizontalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHideAndExpandViewport; + scrollRect.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHideAndExpandViewport; + scrollRect.horizontalScrollbarSpacing = -3; + scrollRect.verticalScrollbarSpacing = -3; + + Image rootImage = root.AddComponent<Image>(); + rootImage.sprite = resources.background; + rootImage.type = Image.Type.Sliced; + rootImage.color = s_PanelColor; + + Mask viewportMask = viewport.AddComponent<Mask>(); + viewportMask.showMaskGraphic = false; + + Image viewportImage = viewport.AddComponent<Image>(); + viewportImage.sprite = resources.mask; + viewportImage.type = Image.Type.Sliced; + + return root; + } + } +} diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/DefaultControls.cs.meta b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/DefaultControls.cs.meta new file mode 100644 index 0000000..4365d7a --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/DefaultControls.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ca969270ec2be6848bd9453f8c69a4b3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Dropdown.cs b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Dropdown.cs new file mode 100644 index 0000000..86bed37 --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Dropdown.cs @@ -0,0 +1,649 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine.Events; +using UnityEngine.EventSystems; +using UnityEngine.UI.CoroutineTween; + +namespace UnityEngine.UI +{ + [AddComponentMenu("UI/Dropdown", 35)] + [RequireComponent(typeof(RectTransform))] + public class Dropdown : Selectable, IPointerClickHandler, ISubmitHandler, ICancelHandler + { + protected internal class DropdownItem : MonoBehaviour, IPointerEnterHandler, ICancelHandler + { + [SerializeField] + private Text m_Text; + [SerializeField] + private Image m_Image; + [SerializeField] + private RectTransform m_RectTransform; + [SerializeField] + private Toggle m_Toggle; + + public Text text { get { return m_Text; } set { m_Text = value; } } + public Image image { get { return m_Image; } set { m_Image = value; } } + public RectTransform rectTransform { get { return m_RectTransform; } set { m_RectTransform = value; } } + public Toggle toggle { get { return m_Toggle; } set { m_Toggle = value; } } + + public virtual void OnPointerEnter(PointerEventData eventData) + { + EventSystem.current.SetSelectedGameObject(gameObject); + } + + public virtual void OnCancel(BaseEventData eventData) + { + Dropdown dropdown = GetComponentInParent<Dropdown>(); + if (dropdown) + dropdown.Hide(); + } + } + + [Serializable] + public class OptionData + { + [SerializeField] + private string m_Text; + [SerializeField] + private Sprite m_Image; + + public string text { get { return m_Text; } set { m_Text = value; } } + public Sprite image { get { return m_Image; } set { m_Image = value; } } + + public OptionData() + { + } + + public OptionData(string text) + { + this.text = text; + } + + public OptionData(Sprite image) + { + this.image = image; + } + + public OptionData(string text, Sprite image) + { + this.text = text; + this.image = image; + } + } + + [Serializable] + public class OptionDataList + { + [SerializeField] + private List<OptionData> m_Options; + public List<OptionData> options { get { return m_Options; } set { m_Options = value; } } + + + public OptionDataList() + { + options = new List<OptionData>(); + } + } + + [Serializable] + public class DropdownEvent : UnityEvent<int> {} + + // Template used to create the dropdown. + [SerializeField] + private RectTransform m_Template; + public RectTransform template { get { return m_Template; } set { m_Template = value; RefreshShownValue(); } } + + // Text to be used as a caption for the current value. It's not required, but it's kept here for convenience. + [SerializeField] + private Text m_CaptionText; + public Text captionText { get { return m_CaptionText; } set { m_CaptionText = value; RefreshShownValue(); } } + + [SerializeField] + private Image m_CaptionImage; + public Image captionImage { get { return m_CaptionImage; } set { m_CaptionImage = value; RefreshShownValue(); } } + + [Space] + + [SerializeField] + private Text m_ItemText; + public Text itemText { get { return m_ItemText; } set { m_ItemText = value; RefreshShownValue(); } } + + [SerializeField] + private Image m_ItemImage; + public Image itemImage { get { return m_ItemImage; } set { m_ItemImage = value; RefreshShownValue(); } } + + [Space] + + [SerializeField] + private int m_Value; + + [Space] + + // Items that will be visible when the dropdown is shown. + // We box this into its own class so we can use a Property Drawer for it. + [SerializeField] + private OptionDataList m_Options = new OptionDataList(); + public List<OptionData> options + { + get { return m_Options.options; } + set { m_Options.options = value; RefreshShownValue(); } + } + + [Space] + + // Notification triggered when the dropdown changes. + [SerializeField] + private DropdownEvent m_OnValueChanged = new DropdownEvent(); + public DropdownEvent onValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } } + + private GameObject m_Dropdown; + private GameObject m_Blocker; + private List<DropdownItem> m_Items = new List<DropdownItem>(); + private TweenRunner<FloatTween> m_AlphaTweenRunner; + private bool validTemplate = false; + + private static OptionData s_NoOptionData = new OptionData(); + + // Current value. + public int value + { + get + { + return m_Value; + } + set + { + if (Application.isPlaying && (value == m_Value || options.Count == 0)) + return; + + m_Value = Mathf.Clamp(value, 0, options.Count - 1); + RefreshShownValue(); + + // Notify all listeners + UISystemProfilerApi.AddMarker("Dropdown.value", this); + m_OnValueChanged.Invoke(m_Value); + } + } + + protected Dropdown() + {} + + protected override void Awake() + { + #if UNITY_EDITOR + if (!Application.isPlaying) + return; + #endif + + m_AlphaTweenRunner = new TweenRunner<FloatTween>(); + m_AlphaTweenRunner.Init(this); + + if (m_CaptionImage) + m_CaptionImage.enabled = (m_CaptionImage.sprite != null); + + if (m_Template) + m_Template.gameObject.SetActive(false); + } + + #if UNITY_EDITOR + protected override void OnValidate() + { + base.OnValidate(); + + if (!IsActive()) + return; + + RefreshShownValue(); + } + + #endif + + public void RefreshShownValue() + { + OptionData data = s_NoOptionData; + + if (options.Count > 0) + data = options[Mathf.Clamp(m_Value, 0, options.Count - 1)]; + + if (m_CaptionText) + { + if (data != null && data.text != null) + m_CaptionText.text = data.text; + else + m_CaptionText.text = ""; + } + + if (m_CaptionImage) + { + if (data != null) + m_CaptionImage.sprite = data.image; + else + m_CaptionImage.sprite = null; + m_CaptionImage.enabled = (m_CaptionImage.sprite != null); + } + } + + public void AddOptions(List<OptionData> options) + { + this.options.AddRange(options); + RefreshShownValue(); + } + + public void AddOptions(List<string> options) + { + for (int i = 0; i < options.Count; i++) + this.options.Add(new OptionData(options[i])); + RefreshShownValue(); + } + + public void AddOptions(List<Sprite> options) + { + for (int i = 0; i < options.Count; i++) + this.options.Add(new OptionData(options[i])); + RefreshShownValue(); + } + + public void ClearOptions() + { + options.Clear(); + RefreshShownValue(); + } + + private void SetupTemplate() + { + validTemplate = false; + + if (!m_Template) + { + Debug.LogError("The dropdown template is not assigned. The template needs to be assigned and must have a child GameObject with a Toggle component serving as the item.", this); + return; + } + + GameObject templateGo = m_Template.gameObject; + templateGo.SetActive(true); + Toggle itemToggle = m_Template.GetComponentInChildren<Toggle>(); + + validTemplate = true; + if (!itemToggle || itemToggle.transform == template) + { + validTemplate = false; + Debug.LogError("The dropdown template is not valid. The template must have a child GameObject with a Toggle component serving as the item.", template); + } + else if (!(itemToggle.transform.parent is RectTransform)) + { + validTemplate = false; + Debug.LogError("The dropdown template is not valid. The child GameObject with a Toggle component (the item) must have a RectTransform on its parent.", template); + } + else if (itemText != null && !itemText.transform.IsChildOf(itemToggle.transform)) + { + validTemplate = false; + Debug.LogError("The dropdown template is not valid. The Item Text must be on the item GameObject or children of it.", template); + } + else if (itemImage != null && !itemImage.transform.IsChildOf(itemToggle.transform)) + { + validTemplate = false; + Debug.LogError("The dropdown template is not valid. The Item Image must be on the item GameObject or children of it.", template); + } + + if (!validTemplate) + { + templateGo.SetActive(false); + return; + } + + DropdownItem item = itemToggle.gameObject.AddComponent<DropdownItem>(); + item.text = m_ItemText; + item.image = m_ItemImage; + item.toggle = itemToggle; + item.rectTransform = (RectTransform)itemToggle.transform; + + Canvas popupCanvas = GetOrAddComponent<Canvas>(templateGo); + popupCanvas.overrideSorting = true; + popupCanvas.sortingOrder = 30000; + + GetOrAddComponent<GraphicRaycaster>(templateGo); + GetOrAddComponent<CanvasGroup>(templateGo); + templateGo.SetActive(false); + + validTemplate = true; + } + + private static T GetOrAddComponent<T>(GameObject go) where T : Component + { + T comp = go.GetComponent<T>(); + if (!comp) + comp = go.AddComponent<T>(); + return comp; + } + + public virtual void OnPointerClick(PointerEventData eventData) + { + Show(); + } + + public virtual void OnSubmit(BaseEventData eventData) + { + Show(); + } + + public virtual void OnCancel(BaseEventData eventData) + { + Hide(); + } + + // Show the dropdown. + // + // Plan for dropdown scrolling to ensure dropdown is contained within screen. + // + // We assume the Canvas is the screen that the dropdown must be kept inside. + // This is always valid for screen space canvas modes. + // For world space canvases we don't know how it's used, but it could be e.g. for an in-game monitor. + // We consider it a fair constraint that the canvas must be big enough to contains dropdowns. + public void Show() + { + if (!IsActive() || !IsInteractable() || m_Dropdown != null) + return; + + if (!validTemplate) + { + SetupTemplate(); + if (!validTemplate) + return; + } + + // Get root Canvas. + var list = ListPool<Canvas>.Get(); + gameObject.GetComponentsInParent(false, list); + if (list.Count == 0) + return; + Canvas rootCanvas = list[0]; + ListPool<Canvas>.Release(list); + + m_Template.gameObject.SetActive(true); + + // Instantiate the drop-down template + m_Dropdown = CreateDropdownList(m_Template.gameObject); + m_Dropdown.name = "Dropdown List"; + m_Dropdown.SetActive(true); + + // Make drop-down RectTransform have same values as original. + RectTransform dropdownRectTransform = m_Dropdown.transform as RectTransform; + dropdownRectTransform.SetParent(m_Template.transform.parent, false); + + // Instantiate the drop-down list items + + // Find the dropdown item and disable it. + DropdownItem itemTemplate = m_Dropdown.GetComponentInChildren<DropdownItem>(); + + GameObject content = itemTemplate.rectTransform.parent.gameObject; + RectTransform contentRectTransform = content.transform as RectTransform; + itemTemplate.rectTransform.gameObject.SetActive(true); + + // Get the rects of the dropdown and item + Rect dropdownContentRect = contentRectTransform.rect; + Rect itemTemplateRect = itemTemplate.rectTransform.rect; + + // Calculate the visual offset between the item's edges and the background's edges + Vector2 offsetMin = itemTemplateRect.min - dropdownContentRect.min + (Vector2)itemTemplate.rectTransform.localPosition; + Vector2 offsetMax = itemTemplateRect.max - dropdownContentRect.max + (Vector2)itemTemplate.rectTransform.localPosition; + Vector2 itemSize = itemTemplateRect.size; + + m_Items.Clear(); + + Toggle prev = null; + for (int i = 0; i < options.Count; ++i) + { + OptionData data = options[i]; + DropdownItem item = AddItem(data, value == i, itemTemplate, m_Items); + if (item == null) + continue; + + // Automatically set up a toggle state change listener + item.toggle.isOn = value == i; + item.toggle.onValueChanged.AddListener(x => OnSelectItem(item.toggle)); + + // Select current option + if (item.toggle.isOn) + item.toggle.Select(); + + // Automatically set up explicit navigation + if (prev != null) + { + Navigation prevNav = prev.navigation; + Navigation toggleNav = item.toggle.navigation; + prevNav.mode = Navigation.Mode.Explicit; + toggleNav.mode = Navigation.Mode.Explicit; + + prevNav.selectOnDown = item.toggle; + prevNav.selectOnRight = item.toggle; + toggleNav.selectOnLeft = prev; + toggleNav.selectOnUp = prev; + + prev.navigation = prevNav; + item.toggle.navigation = toggleNav; + } + prev = item.toggle; + } + + // Reposition all items now that all of them have been added + Vector2 sizeDelta = contentRectTransform.sizeDelta; + sizeDelta.y = itemSize.y * m_Items.Count + offsetMin.y - offsetMax.y; + contentRectTransform.sizeDelta = sizeDelta; + + float extraSpace = dropdownRectTransform.rect.height - contentRectTransform.rect.height; + if (extraSpace > 0) + dropdownRectTransform.sizeDelta = new Vector2(dropdownRectTransform.sizeDelta.x, dropdownRectTransform.sizeDelta.y - extraSpace); + + // Invert anchoring and position if dropdown is partially or fully outside of canvas rect. + // Typically this will have the effect of placing the dropdown above the button instead of below, + // but it works as inversion regardless of initial setup. + Vector3[] corners = new Vector3[4]; + dropdownRectTransform.GetWorldCorners(corners); + + RectTransform rootCanvasRectTransform = rootCanvas.transform as RectTransform; + Rect rootCanvasRect = rootCanvasRectTransform.rect; + for (int axis = 0; axis < 2; axis++) + { + bool outside = false; + for (int i = 0; i < 4; i++) + { + Vector3 corner = rootCanvasRectTransform.InverseTransformPoint(corners[i]); + if (corner[axis] < rootCanvasRect.min[axis] || corner[axis] > rootCanvasRect.max[axis]) + { + outside = true; + break; + } + } + if (outside) + RectTransformUtility.FlipLayoutOnAxis(dropdownRectTransform, axis, false, false); + } + + for (int i = 0; i < m_Items.Count; i++) + { + RectTransform itemRect = m_Items[i].rectTransform; + itemRect.anchorMin = new Vector2(itemRect.anchorMin.x, 0); + itemRect.anchorMax = new Vector2(itemRect.anchorMax.x, 0); + itemRect.anchoredPosition = new Vector2(itemRect.anchoredPosition.x, offsetMin.y + itemSize.y * (m_Items.Count - 1 - i) + itemSize.y * itemRect.pivot.y); + itemRect.sizeDelta = new Vector2(itemRect.sizeDelta.x, itemSize.y); + } + + // Fade in the popup + AlphaFadeList(0.15f, 0f, 1f); + + // Make drop-down template and item template inactive + m_Template.gameObject.SetActive(false); + itemTemplate.gameObject.SetActive(false); + + m_Blocker = CreateBlocker(rootCanvas); + } + + protected virtual GameObject CreateBlocker(Canvas rootCanvas) + { + // Create blocker GameObject. + GameObject blocker = new GameObject("Blocker"); + + // Setup blocker RectTransform to cover entire root canvas area. + RectTransform blockerRect = blocker.AddComponent<RectTransform>(); + blockerRect.SetParent(rootCanvas.transform, false); + blockerRect.anchorMin = Vector3.zero; + blockerRect.anchorMax = Vector3.one; + blockerRect.sizeDelta = Vector2.zero; + + // Make blocker be in separate canvas in same layer as dropdown and in layer just below it. + Canvas blockerCanvas = blocker.AddComponent<Canvas>(); + blockerCanvas.overrideSorting = true; + Canvas dropdownCanvas = m_Dropdown.GetComponent<Canvas>(); + blockerCanvas.sortingLayerID = dropdownCanvas.sortingLayerID; + blockerCanvas.sortingOrder = dropdownCanvas.sortingOrder - 1; + + // Add raycaster since it's needed to block. + blocker.AddComponent<GraphicRaycaster>(); + + // Add image since it's needed to block, but make it clear. + Image blockerImage = blocker.AddComponent<Image>(); + blockerImage.color = Color.clear; + + // Add button since it's needed to block, and to close the dropdown when blocking area is clicked. + Button blockerButton = blocker.AddComponent<Button>(); + blockerButton.onClick.AddListener(Hide); + + return blocker; + } + + protected virtual void DestroyBlocker(GameObject blocker) + { + Destroy(blocker); + } + + protected virtual GameObject CreateDropdownList(GameObject template) + { + return (GameObject)Instantiate(template); + } + + protected virtual void DestroyDropdownList(GameObject dropdownList) + { + Destroy(dropdownList); + } + + protected virtual DropdownItem CreateItem(DropdownItem itemTemplate) + { + return (DropdownItem)Instantiate(itemTemplate); + } + + protected virtual void DestroyItem(DropdownItem item) + { + // No action needed since destroying the dropdown list destroys all contained items as well. + } + + // Add a new drop-down list item with the specified values. + private DropdownItem AddItem(OptionData data, bool selected, DropdownItem itemTemplate, List<DropdownItem> items) + { + // Add a new item to the dropdown. + DropdownItem item = CreateItem(itemTemplate); + item.rectTransform.SetParent(itemTemplate.rectTransform.parent, false); + + item.gameObject.SetActive(true); + item.gameObject.name = "Item " + items.Count + (data.text != null ? ": " + data.text : ""); + + if (item.toggle != null) + { + item.toggle.isOn = false; + } + + // Set the item's data + if (item.text) + item.text.text = data.text; + if (item.image) + { + item.image.sprite = data.image; + item.image.enabled = (item.image.sprite != null); + } + + items.Add(item); + return item; + } + + private void AlphaFadeList(float duration, float alpha) + { + CanvasGroup group = m_Dropdown.GetComponent<CanvasGroup>(); + AlphaFadeList(duration, group.alpha, alpha); + } + + private void AlphaFadeList(float duration, float start, float end) + { + if (end.Equals(start)) + return; + + FloatTween tween = new FloatTween {duration = duration, startValue = start, targetValue = end}; + tween.AddOnChangedCallback(SetAlpha); + tween.ignoreTimeScale = true; + m_AlphaTweenRunner.StartTween(tween); + } + + private void SetAlpha(float alpha) + { + if (!m_Dropdown) + return; + CanvasGroup group = m_Dropdown.GetComponent<CanvasGroup>(); + group.alpha = alpha; + } + + // Hide the dropdown. + public void Hide() + { + if (m_Dropdown != null) + { + AlphaFadeList(0.15f, 0f); + + // User could have disabled the dropdown during the OnValueChanged call. + if (IsActive()) + StartCoroutine(DelayedDestroyDropdownList(0.15f)); + } + if (m_Blocker != null) + DestroyBlocker(m_Blocker); + m_Blocker = null; + Select(); + } + + private IEnumerator DelayedDestroyDropdownList(float delay) + { + yield return new WaitForSecondsRealtime(delay); + for (int i = 0; i < m_Items.Count; i++) + { + if (m_Items[i] != null) + DestroyItem(m_Items[i]); + } + m_Items.Clear(); + if (m_Dropdown != null) + DestroyDropdownList(m_Dropdown); + m_Dropdown = null; + } + + // Change the value and hide the dropdown. + private void OnSelectItem(Toggle toggle) + { + if (!toggle.isOn) + toggle.isOn = true; + + int selectedIndex = -1; + Transform tr = toggle.transform; + Transform parent = tr.parent; + for (int i = 0; i < parent.childCount; i++) + { + if (parent.GetChild(i) == tr) + { + // Subtract one to account for template child. + selectedIndex = i - 1; + break; + } + } + + if (selectedIndex < 0) + return; + + value = selectedIndex; + Hide(); + } + } +} diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Dropdown.cs.meta b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Dropdown.cs.meta new file mode 100644 index 0000000..a80360c --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Dropdown.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bfed003f40ad7ff4ba0ba7ccf49021aa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/InputField.cs b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/InputField.cs new file mode 100644 index 0000000..1f3267a --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/InputField.cs @@ -0,0 +1,2484 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using UnityEngine.Events; +using UnityEngine.EventSystems; +using UnityEngine.Serialization; +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace UnityEngine.UI +{ + /// <summary> + /// Editable text input field. + /// </summary> + + [AddComponentMenu("UI/Input Field", 31)] + public class InputField + : Selectable, + IUpdateSelectedHandler, + IBeginDragHandler, + IDragHandler, + IEndDragHandler, + IPointerClickHandler, + ISubmitHandler, + ICanvasElement, + ILayoutElement + { + // Setting the content type acts as a shortcut for setting a combination of InputType, CharacterValidation, LineType, and TouchScreenKeyboardType + public enum ContentType + { + Standard, + Autocorrected, + IntegerNumber, + DecimalNumber, + Alphanumeric, + Name, + EmailAddress, + Password, + Pin, + Custom + } + + public enum InputType + { + Standard, + AutoCorrect, + Password, + } + + public enum CharacterValidation + { + None, + Integer, + Decimal, + Alphanumeric, + Name, + EmailAddress + } + + public enum LineType + { + SingleLine, + MultiLineSubmit, + MultiLineNewline + } + + public delegate char OnValidateInput(string text, int charIndex, char addedChar); + + [Serializable] + public class SubmitEvent : UnityEvent<string> {} + + [Serializable] + public class OnChangeEvent : UnityEvent<string> {} + + protected TouchScreenKeyboard m_Keyboard; + static private readonly char[] kSeparators = { ' ', '.', ',', '\t', '\r', '\n' }; + + /// <summary> + /// Text Text used to display the input's value. + /// </summary> + + [SerializeField] + [FormerlySerializedAs("text")] + protected Text m_TextComponent; + + [SerializeField] + protected Graphic m_Placeholder; + + [SerializeField] + private ContentType m_ContentType = ContentType.Standard; + + /// <summary> + /// Type of data expected by the input field. + /// </summary> + [FormerlySerializedAs("inputType")] + [SerializeField] + private InputType m_InputType = InputType.Standard; + + /// <summary> + /// The character used to hide text in password field. + /// </summary> + [FormerlySerializedAs("asteriskChar")] + [SerializeField] + private char m_AsteriskChar = '*'; + + /// <summary> + /// Keyboard type applies to mobile keyboards that get shown. + /// </summary> + [FormerlySerializedAs("keyboardType")] + [SerializeField] + private TouchScreenKeyboardType m_KeyboardType = TouchScreenKeyboardType.Default; + + [SerializeField] + private LineType m_LineType = LineType.SingleLine; + + /// <summary> + /// Should hide mobile input. + /// </summary> + + [FormerlySerializedAs("hideMobileInput")] + [SerializeField] + private bool m_HideMobileInput = false; + + /// <summary> + /// What kind of validation to use with the input field's data. + /// </summary> + [FormerlySerializedAs("validation")] + [SerializeField] + private CharacterValidation m_CharacterValidation = CharacterValidation.None; + + /// <summary> + /// Maximum number of characters allowed before input no longer works. + /// </summary> + [FormerlySerializedAs("characterLimit")] + [SerializeField] + private int m_CharacterLimit = 0; + + /// <summary> + /// Event delegates triggered when the input field submits its data. + /// </summary> + [FormerlySerializedAs("onSubmit")] + [FormerlySerializedAs("m_OnSubmit")] + [FormerlySerializedAs("m_EndEdit")] + [SerializeField] + private SubmitEvent m_OnEndEdit = new SubmitEvent(); + + /// <summary> + /// Event delegates triggered when the input field changes its data. + /// </summary> + [FormerlySerializedAs("onValueChange")] + [FormerlySerializedAs("m_OnValueChange")] + [SerializeField] + private OnChangeEvent m_OnValueChanged = new OnChangeEvent(); + + /// <summary> + /// Custom validation callback. + /// </summary> + [FormerlySerializedAs("onValidateInput")] + [SerializeField] + private OnValidateInput m_OnValidateInput; + + [SerializeField] + private Color m_CaretColor = new Color(50f / 255f, 50f / 255f, 50f / 255f, 1f); + + [SerializeField] + private bool m_CustomCaretColor = false; + + [FormerlySerializedAs("selectionColor")] + [SerializeField] + private Color m_SelectionColor = new Color(168f / 255f, 206f / 255f, 255f / 255f, 192f / 255f); + + /// <summary> + /// Input field's value. + /// </summary> + + [SerializeField] + [FormerlySerializedAs("mValue")] + protected string m_Text = string.Empty; + + [SerializeField] + [Range(0f, 4f)] + private float m_CaretBlinkRate = 0.85f; + + [SerializeField] + [Range(1, 5)] + private int m_CaretWidth = 1; + + [SerializeField] + private bool m_ReadOnly = false; + + protected int m_CaretPosition = 0; + protected int m_CaretSelectPosition = 0; + private RectTransform caretRectTrans = null; + protected UIVertex[] m_CursorVerts = null; + private TextGenerator m_InputTextCache; + private CanvasRenderer m_CachedInputRenderer; + private bool m_PreventFontCallback = false; + [NonSerialized] protected Mesh m_Mesh; + private bool m_AllowInput = false; + private bool m_ShouldActivateNextUpdate = false; + private bool m_UpdateDrag = false; + private bool m_DragPositionOutOfBounds = false; + private const float kHScrollSpeed = 0.05f; + private const float kVScrollSpeed = 0.10f; + protected bool m_CaretVisible; + private Coroutine m_BlinkCoroutine = null; + private float m_BlinkStartTime = 0.0f; + protected int m_DrawStart = 0; + protected int m_DrawEnd = 0; + private Coroutine m_DragCoroutine = null; + private string m_OriginalText = ""; + private bool m_WasCanceled = false; + private bool m_HasDoneFocusTransition = false; + + private BaseInput input + { + get + { + if (EventSystem.current && EventSystem.current.currentInputModule) + return EventSystem.current.currentInputModule.input; + return null; + } + } + + private string compositionString + { + get { return input != null ? input.compositionString : Input.compositionString; } + } + + // Doesn't include dot and @ on purpose! See usage for details. + const string kEmailSpecialCharacters = "!#$%&'*+-/=?^_`{|}~"; + + protected InputField() + { + EnforceTextHOverflow(); + } + + protected Mesh mesh + { + get + { + if (m_Mesh == null) + m_Mesh = new Mesh(); + return m_Mesh; + } + } + + protected TextGenerator cachedInputTextGenerator + { + get + { + if (m_InputTextCache == null) + m_InputTextCache = new TextGenerator(); + + return m_InputTextCache; + } + } + + /// <summary> + /// Should the mobile keyboard input be hidden. + /// </summary> + + public bool shouldHideMobileInput + { + set + { + SetPropertyUtility.SetStruct(ref m_HideMobileInput, value); + } + get + { + switch (Application.platform) + { + case RuntimePlatform.Android: + case RuntimePlatform.IPhonePlayer: + case RuntimePlatform.TizenPlayer: + case RuntimePlatform.tvOS: + return m_HideMobileInput; + } + + return true; + } + } + + bool shouldActivateOnSelect + { + get + { + return Application.platform != RuntimePlatform.tvOS; + } + } + + /// <summary> + /// Input field's current text value. + /// </summary> + + public string text + { + get + { + return m_Text; + } + set + { + if (this.text == value) + return; + if (value == null) + value = ""; + value = value.Replace("\0", string.Empty); // remove embedded nulls + if (m_LineType == LineType.SingleLine) + value = value.Replace("\n", "").Replace("\t", ""); + + // If we have an input validator, validate the input and apply the character limit at the same time. + if (onValidateInput != null || characterValidation != CharacterValidation.None) + { + m_Text = ""; + OnValidateInput validatorMethod = onValidateInput ?? Validate; + m_CaretPosition = m_CaretSelectPosition = value.Length; + int charactersToCheck = characterLimit > 0 ? Math.Min(characterLimit, value.Length) : value.Length; + for (int i = 0; i < charactersToCheck; ++i) + { + char c = validatorMethod(m_Text, m_Text.Length, value[i]); + if (c != 0) + m_Text += c; + } + } + else + { + m_Text = characterLimit > 0 && value.Length > characterLimit ? value.Substring(0, characterLimit) : value; + } + +#if UNITY_EDITOR + if (!Application.isPlaying) + { + SendOnValueChangedAndUpdateLabel(); + return; + } +#endif + + if (m_Keyboard != null) + m_Keyboard.text = m_Text; + + if (m_CaretPosition > m_Text.Length) + m_CaretPosition = m_CaretSelectPosition = m_Text.Length; + else if (m_CaretSelectPosition > m_Text.Length) + m_CaretSelectPosition = m_Text.Length; + SendOnValueChangedAndUpdateLabel(); + } + } + + public bool isFocused + { + get { return m_AllowInput; } + } + + public float caretBlinkRate + { + get { return m_CaretBlinkRate; } + set + { + if (SetPropertyUtility.SetStruct(ref m_CaretBlinkRate, value)) + { + if (m_AllowInput) + SetCaretActive(); + } + } + } + + public int caretWidth { get { return m_CaretWidth; } set { if (SetPropertyUtility.SetStruct(ref m_CaretWidth, value)) MarkGeometryAsDirty(); } } + + public Text textComponent + { + get { return m_TextComponent; } + set + { + if (m_TextComponent != null) + { + m_TextComponent.UnregisterDirtyVerticesCallback(MarkGeometryAsDirty); + m_TextComponent.UnregisterDirtyVerticesCallback(UpdateLabel); + m_TextComponent.UnregisterDirtyMaterialCallback(UpdateCaretMaterial); + } + + if (SetPropertyUtility.SetClass(ref m_TextComponent, value)) + { + EnforceTextHOverflow(); + if (m_TextComponent != null) + { + m_TextComponent.RegisterDirtyVerticesCallback(MarkGeometryAsDirty); + m_TextComponent.RegisterDirtyVerticesCallback(UpdateLabel); + m_TextComponent.RegisterDirtyMaterialCallback(UpdateCaretMaterial); + } + } + } + } + + public Graphic placeholder { get { return m_Placeholder; } set { SetPropertyUtility.SetClass(ref m_Placeholder, value); } } + + public Color caretColor { get { return customCaretColor ? m_CaretColor : textComponent.color; } set { if (SetPropertyUtility.SetColor(ref m_CaretColor, value)) MarkGeometryAsDirty(); } } + + public bool customCaretColor { get { return m_CustomCaretColor; } set { if (m_CustomCaretColor != value) { m_CustomCaretColor = value; MarkGeometryAsDirty(); } } } + + public Color selectionColor { get { return m_SelectionColor; } set { if (SetPropertyUtility.SetColor(ref m_SelectionColor, value)) MarkGeometryAsDirty(); } } + + public SubmitEvent onEndEdit { get { return m_OnEndEdit; } set { SetPropertyUtility.SetClass(ref m_OnEndEdit, value); } } + + [Obsolete("onValueChange has been renamed to onValueChanged")] + public OnChangeEvent onValueChange { get { return onValueChanged; } set { onValueChanged = value; } } + + public OnChangeEvent onValueChanged { get { return m_OnValueChanged; } set { SetPropertyUtility.SetClass(ref m_OnValueChanged, value); } } + + public OnValidateInput onValidateInput { get { return m_OnValidateInput; } set { SetPropertyUtility.SetClass(ref m_OnValidateInput, value); } } + + public int characterLimit { get { return m_CharacterLimit; } set { if (SetPropertyUtility.SetStruct(ref m_CharacterLimit, Math.Max(0, value))) UpdateLabel(); } } + + // Content Type related + + public ContentType contentType { get { return m_ContentType; } set { if (SetPropertyUtility.SetStruct(ref m_ContentType, value)) EnforceContentType(); } } + + public LineType lineType + { + get { return m_LineType; } + set + { + if (SetPropertyUtility.SetStruct(ref m_LineType, value)) + { + SetToCustomIfContentTypeIsNot(ContentType.Standard, ContentType.Autocorrected); + EnforceTextHOverflow(); + } + } + } + + public InputType inputType { get { return m_InputType; } set { if (SetPropertyUtility.SetStruct(ref m_InputType, value)) SetToCustom(); } } + + public TouchScreenKeyboardType keyboardType + { + get { return m_KeyboardType; } + set + { +#if UNITY_EDITOR + if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.WiiU) + { + if (value == TouchScreenKeyboardType.NintendoNetworkAccount) + Debug.LogWarning("Invalid InputField.keyboardType value set. TouchScreenKeyboardType.NintendoNetworkAccount only applies to the Wii U. InputField.keyboardType will default to TouchScreenKeyboardType.Default ."); + } +#elif !UNITY_WIIU + if (value == TouchScreenKeyboardType.NintendoNetworkAccount) + Debug.LogWarning("Invalid InputField.keyboardType value set. TouchScreenKeyboardType.NintendoNetworkAccount only applies to the Wii U. InputField.keyboardType will default to TouchScreenKeyboardType.Default ."); +#endif + if (SetPropertyUtility.SetStruct(ref m_KeyboardType, value)) + SetToCustom(); + } + } + + public CharacterValidation characterValidation { get { return m_CharacterValidation; } set { if (SetPropertyUtility.SetStruct(ref m_CharacterValidation, value)) SetToCustom(); } } + + public bool readOnly { get { return m_ReadOnly; } set { m_ReadOnly = value; } } + + // Derived property + public bool multiLine { get { return m_LineType == LineType.MultiLineNewline || lineType == LineType.MultiLineSubmit; } } + // Not shown in Inspector. + public char asteriskChar { get { return m_AsteriskChar; } set { if (SetPropertyUtility.SetStruct(ref m_AsteriskChar, value)) UpdateLabel(); } } + public bool wasCanceled { get { return m_WasCanceled; } } + + protected void ClampPos(ref int pos) + { + if (pos < 0) pos = 0; + else if (pos > text.Length) pos = text.Length; + } + + /// <summary> + /// Current position of the cursor. + /// Getters are public Setters are protected + /// </summary> + + protected int caretPositionInternal { get { return m_CaretPosition + compositionString.Length; } set { m_CaretPosition = value; ClampPos(ref m_CaretPosition); } } + protected int caretSelectPositionInternal { get { return m_CaretSelectPosition + compositionString.Length; } set { m_CaretSelectPosition = value; ClampPos(ref m_CaretSelectPosition); } } + private bool hasSelection { get { return caretPositionInternal != caretSelectPositionInternal; } } + +#if UNITY_EDITOR + [Obsolete("caretSelectPosition has been deprecated. Use selectionFocusPosition instead (UnityUpgradable) -> selectionFocusPosition", true)] + public int caretSelectPosition { get { return selectionFocusPosition; } protected set { selectionFocusPosition = value; } } +#endif + + /// <summary> + /// Get: Returns the focus position as thats the position that moves around even during selection. + /// Set: Set both the anchor and focus position such that a selection doesn't happen + /// </summary> + + public int caretPosition + { + get { return m_CaretSelectPosition + compositionString.Length; } + set { selectionAnchorPosition = value; selectionFocusPosition = value; } + } + + /// <summary> + /// Get: Returns the fixed position of selection + /// Set: If Input.compositionString is 0 set the fixed position + /// </summary> + + public int selectionAnchorPosition + { + get { return m_CaretPosition + compositionString.Length; } + set + { + if (compositionString.Length != 0) + return; + + m_CaretPosition = value; + ClampPos(ref m_CaretPosition); + } + } + + /// <summary> + /// Get: Returns the variable position of selection + /// Set: If Input.compositionString is 0 set the variable position + /// </summary> + + public int selectionFocusPosition + { + get { return m_CaretSelectPosition + compositionString.Length; } + set + { + if (compositionString.Length != 0) + return; + + m_CaretSelectPosition = value; + ClampPos(ref m_CaretSelectPosition); + } + } + + #if UNITY_EDITOR + // Remember: This is NOT related to text validation! + // This is Unity's own OnValidate method which is invoked when changing values in the Inspector. + protected override void OnValidate() + { + base.OnValidate(); + EnforceContentType(); + EnforceTextHOverflow(); + + m_CharacterLimit = Math.Max(0, m_CharacterLimit); + + //This can be invoked before OnEnabled is called. So we shouldn't be accessing other objects, before OnEnable is called. + if (!IsActive()) + return; + + UpdateLabel(); + if (m_AllowInput) + SetCaretActive(); + } + + #endif // if UNITY_EDITOR + + protected override void OnEnable() + { + base.OnEnable(); + if (m_Text == null) + m_Text = string.Empty; + m_DrawStart = 0; + m_DrawEnd = m_Text.Length; + + // If we have a cached renderer then we had OnDisable called so just restore the material. + if (m_CachedInputRenderer != null) + m_CachedInputRenderer.SetMaterial(m_TextComponent.GetModifiedMaterial(Graphic.defaultGraphicMaterial), Texture2D.whiteTexture); + + if (m_TextComponent != null) + { + m_TextComponent.RegisterDirtyVerticesCallback(MarkGeometryAsDirty); + m_TextComponent.RegisterDirtyVerticesCallback(UpdateLabel); + m_TextComponent.RegisterDirtyMaterialCallback(UpdateCaretMaterial); + UpdateLabel(); + } + } + + protected override void OnDisable() + { + // the coroutine will be terminated, so this will ensure it restarts when we are next activated + m_BlinkCoroutine = null; + + DeactivateInputField(); + if (m_TextComponent != null) + { + m_TextComponent.UnregisterDirtyVerticesCallback(MarkGeometryAsDirty); + m_TextComponent.UnregisterDirtyVerticesCallback(UpdateLabel); + m_TextComponent.UnregisterDirtyMaterialCallback(UpdateCaretMaterial); + } + CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild(this); + + // Clear needs to be called otherwise sync never happens as the object is disabled. + if (m_CachedInputRenderer != null) + m_CachedInputRenderer.Clear(); + + if (m_Mesh != null) + DestroyImmediate(m_Mesh); + m_Mesh = null; + + base.OnDisable(); + } + + IEnumerator CaretBlink() + { + // Always ensure caret is initially visible since it can otherwise be confusing for a moment. + m_CaretVisible = true; + yield return null; + + while (isFocused && m_CaretBlinkRate > 0) + { + // the blink rate is expressed as a frequency + float blinkPeriod = 1f / m_CaretBlinkRate; + + // the caret should be ON if we are in the first half of the blink period + bool blinkState = (Time.unscaledTime - m_BlinkStartTime) % blinkPeriod < blinkPeriod / 2; + if (m_CaretVisible != blinkState) + { + m_CaretVisible = blinkState; + if (!hasSelection) + MarkGeometryAsDirty(); + } + + // Then wait again. + yield return null; + } + m_BlinkCoroutine = null; + } + + void SetCaretVisible() + { + if (!m_AllowInput) + return; + + m_CaretVisible = true; + m_BlinkStartTime = Time.unscaledTime; + SetCaretActive(); + } + + // SetCaretActive will not set the caret immediately visible - it will wait for the next time to blink. + // However, it will handle things correctly if the blink speed changed from zero to non-zero or non-zero to zero. + void SetCaretActive() + { + if (!m_AllowInput) + return; + + if (m_CaretBlinkRate > 0.0f) + { + if (m_BlinkCoroutine == null) + m_BlinkCoroutine = StartCoroutine(CaretBlink()); + } + else + { + m_CaretVisible = true; + } + } + + private void UpdateCaretMaterial() + { + if (m_TextComponent != null && m_CachedInputRenderer != null) + m_CachedInputRenderer.SetMaterial(m_TextComponent.GetModifiedMaterial(Graphic.defaultGraphicMaterial), Texture2D.whiteTexture); + } + + protected void OnFocus() + { + SelectAll(); + } + + protected void SelectAll() + { + caretPositionInternal = text.Length; + caretSelectPositionInternal = 0; + } + + public void MoveTextEnd(bool shift) + { + int position = text.Length; + + if (shift) + { + caretSelectPositionInternal = position; + } + else + { + caretPositionInternal = position; + caretSelectPositionInternal = caretPositionInternal; + } + UpdateLabel(); + } + + public void MoveTextStart(bool shift) + { + int position = 0; + + if (shift) + { + caretSelectPositionInternal = position; + } + else + { + caretPositionInternal = position; + caretSelectPositionInternal = caretPositionInternal; + } + + UpdateLabel(); + } + + static string clipboard + { + get + { + return GUIUtility.systemCopyBuffer; + } + set + { + GUIUtility.systemCopyBuffer = value; + } + } + + private bool InPlaceEditing() + { + return !TouchScreenKeyboard.isSupported; + } + + void UpdateCaretFromKeyboard() + { + var selectionRange = m_Keyboard.selection; + + var selectionStart = selectionRange.start; + var selectionEnd = selectionRange.end; + + var caretChanged = false; + + if (caretPositionInternal != selectionStart) + { + caretChanged = true; + caretPositionInternal = selectionStart; + } + + if (caretSelectPositionInternal != selectionEnd) + { + caretSelectPositionInternal = selectionEnd; + caretChanged = true; + } + + if (caretChanged) + { + m_BlinkStartTime = Time.unscaledTime; + + UpdateLabel(); + } + } + + /// <summary> + /// Update the text based on input. + /// </summary> + // TODO: Make LateUpdate a coroutine instead. Allows us to control the update to only be when the field is active. + protected virtual void LateUpdate() + { + // Only activate if we are not already activated. + if (m_ShouldActivateNextUpdate) + { + if (!isFocused) + { + ActivateInputFieldInternal(); + m_ShouldActivateNextUpdate = false; + return; + } + + // Reset as we are already activated. + m_ShouldActivateNextUpdate = false; + } + + if (InPlaceEditing() || !isFocused) + return; + + AssignPositioningIfNeeded(); + + if (m_Keyboard == null || m_Keyboard.done) + { + if (m_Keyboard != null) + { + if (!m_ReadOnly) + text = m_Keyboard.text; + + if (m_Keyboard.wasCanceled) + m_WasCanceled = true; + } + + OnDeselect(null); + return; + } + + string val = m_Keyboard.text; + + if (m_Text != val) + { + if (m_ReadOnly) + { + m_Keyboard.text = m_Text; + } + else + { + m_Text = ""; + + for (int i = 0; i < val.Length; ++i) + { + char c = val[i]; + + if (c == '\r' || (int)c == 3) + c = '\n'; + + if (onValidateInput != null) + c = onValidateInput(m_Text, m_Text.Length, c); + else if (characterValidation != CharacterValidation.None) + c = Validate(m_Text, m_Text.Length, c); + + if (lineType == LineType.MultiLineSubmit && c == '\n') + { + m_Keyboard.text = m_Text; + + OnDeselect(null); + return; + } + + if (c != 0) + m_Text += c; + } + + if (characterLimit > 0 && m_Text.Length > characterLimit) + m_Text = m_Text.Substring(0, characterLimit); + + if (m_Keyboard.canGetSelection) + { + UpdateCaretFromKeyboard(); + } + else + { + caretPositionInternal = caretSelectPositionInternal = m_Text.Length; + } + + // Set keyboard text before updating label, as we might have changed it with validation + // and update label will take the old value from keyboard if we don't change it here + if (m_Text != val) + m_Keyboard.text = m_Text; + + SendOnValueChangedAndUpdateLabel(); + } + } + else if (m_Keyboard.canGetSelection) + { + UpdateCaretFromKeyboard(); + } + + + if (m_Keyboard.done) + { + if (m_Keyboard.wasCanceled) + m_WasCanceled = true; + + OnDeselect(null); + } + } + + [Obsolete("This function is no longer used. Please use RectTransformUtility.ScreenPointToLocalPointInRectangle() instead.")] + public Vector2 ScreenToLocal(Vector2 screen) + { + var theCanvas = m_TextComponent.canvas; + if (theCanvas == null) + return screen; + + Vector3 pos = Vector3.zero; + if (theCanvas.renderMode == RenderMode.ScreenSpaceOverlay) + { + pos = m_TextComponent.transform.InverseTransformPoint(screen); + } + else if (theCanvas.worldCamera != null) + { + Ray mouseRay = theCanvas.worldCamera.ScreenPointToRay(screen); + float dist; + Plane plane = new Plane(m_TextComponent.transform.forward, m_TextComponent.transform.position); + plane.Raycast(mouseRay, out dist); + pos = m_TextComponent.transform.InverseTransformPoint(mouseRay.GetPoint(dist)); + } + return new Vector2(pos.x, pos.y); + } + + private int GetUnclampedCharacterLineFromPosition(Vector2 pos, TextGenerator generator) + { + if (!multiLine) + return 0; + + // transform y to local scale + float y = pos.y * m_TextComponent.pixelsPerUnit; + float lastBottomY = 0.0f; + + for (int i = 0; i < generator.lineCount; ++i) + { + float topY = generator.lines[i].topY; + float bottomY = topY - generator.lines[i].height; + + // pos is somewhere in the leading above this line + if (y > topY) + { + // determine which line we're closer to + float leading = topY - lastBottomY; + if (y > topY - 0.5f * leading) + return i - 1; + else + return i; + } + + if (y > bottomY) + return i; + + lastBottomY = bottomY; + } + + // Position is after last line. + return generator.lineCount; + } + + /// <summary> + /// Given an input position in local space on the Text return the index for the selection cursor at this position. + /// </summary> + + protected int GetCharacterIndexFromPosition(Vector2 pos) + { + TextGenerator gen = m_TextComponent.cachedTextGenerator; + + if (gen.lineCount == 0) + return 0; + + int line = GetUnclampedCharacterLineFromPosition(pos, gen); + if (line < 0) + return 0; + if (line >= gen.lineCount) + return gen.characterCountVisible; + + int startCharIndex = gen.lines[line].startCharIdx; + int endCharIndex = GetLineEndPosition(gen, line); + + for (int i = startCharIndex; i < endCharIndex; i++) + { + if (i >= gen.characterCountVisible) + break; + + UICharInfo charInfo = gen.characters[i]; + Vector2 charPos = charInfo.cursorPos / m_TextComponent.pixelsPerUnit; + + float distToCharStart = pos.x - charPos.x; + float distToCharEnd = charPos.x + (charInfo.charWidth / m_TextComponent.pixelsPerUnit) - pos.x; + if (distToCharStart < distToCharEnd) + return i; + } + + return endCharIndex; + } + + private bool MayDrag(PointerEventData eventData) + { + return IsActive() && + IsInteractable() && + eventData.button == PointerEventData.InputButton.Left && + m_TextComponent != null && + m_Keyboard == null; + } + + public virtual void OnBeginDrag(PointerEventData eventData) + { + if (!MayDrag(eventData)) + return; + + m_UpdateDrag = true; + } + + public virtual void OnDrag(PointerEventData eventData) + { + if (!MayDrag(eventData)) + return; + + Vector2 localMousePos; + RectTransformUtility.ScreenPointToLocalPointInRectangle(textComponent.rectTransform, eventData.position, eventData.pressEventCamera, out localMousePos); + caretSelectPositionInternal = GetCharacterIndexFromPosition(localMousePos) + m_DrawStart; + MarkGeometryAsDirty(); + + m_DragPositionOutOfBounds = !RectTransformUtility.RectangleContainsScreenPoint(textComponent.rectTransform, eventData.position, eventData.pressEventCamera); + if (m_DragPositionOutOfBounds && m_DragCoroutine == null) + m_DragCoroutine = StartCoroutine(MouseDragOutsideRect(eventData)); + + eventData.Use(); + } + + IEnumerator MouseDragOutsideRect(PointerEventData eventData) + { + while (m_UpdateDrag && m_DragPositionOutOfBounds) + { + Vector2 localMousePos; + RectTransformUtility.ScreenPointToLocalPointInRectangle(textComponent.rectTransform, eventData.position, eventData.pressEventCamera, out localMousePos); + + Rect rect = textComponent.rectTransform.rect; + + if (multiLine) + { + if (localMousePos.y > rect.yMax) + MoveUp(true, true); + else if (localMousePos.y < rect.yMin) + MoveDown(true, true); + } + else + { + if (localMousePos.x < rect.xMin) + MoveLeft(true, false); + else if (localMousePos.x > rect.xMax) + MoveRight(true, false); + } + UpdateLabel(); + float delay = multiLine ? kVScrollSpeed : kHScrollSpeed; + yield return new WaitForSecondsRealtime(delay); + } + m_DragCoroutine = null; + } + + public virtual void OnEndDrag(PointerEventData eventData) + { + if (!MayDrag(eventData)) + return; + + m_UpdateDrag = false; + } + + public override void OnPointerDown(PointerEventData eventData) + { + if (!MayDrag(eventData)) + return; + + EventSystem.current.SetSelectedGameObject(gameObject, eventData); + + bool hadFocusBefore = m_AllowInput; + base.OnPointerDown(eventData); + + if (!InPlaceEditing()) + { + if (m_Keyboard == null || !m_Keyboard.active) + { + OnSelect(eventData); + return; + } + } + + // Only set caret position if we didn't just get focus now. + // Otherwise it will overwrite the select all on focus. + if (hadFocusBefore) + { + Vector2 localMousePos; + RectTransformUtility.ScreenPointToLocalPointInRectangle(textComponent.rectTransform, eventData.position, eventData.pressEventCamera, out localMousePos); + + caretSelectPositionInternal = caretPositionInternal = GetCharacterIndexFromPosition(localMousePos) + m_DrawStart; + } + UpdateLabel(); + eventData.Use(); + } + + protected enum EditState + { + Continue, + Finish + } + + protected EditState KeyPressed(Event evt) + { + var currentEventModifiers = evt.modifiers; + bool ctrl = SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX ? (currentEventModifiers & EventModifiers.Command) != 0 : (currentEventModifiers & EventModifiers.Control) != 0; + bool shift = (currentEventModifiers & EventModifiers.Shift) != 0; + bool alt = (currentEventModifiers & EventModifiers.Alt) != 0; + bool ctrlOnly = ctrl && !alt && !shift; + + switch (evt.keyCode) + { + case KeyCode.Backspace: + { + Backspace(); + return EditState.Continue; + } + + case KeyCode.Delete: + { + ForwardSpace(); + return EditState.Continue; + } + + case KeyCode.Home: + { + MoveTextStart(shift); + return EditState.Continue; + } + + case KeyCode.End: + { + MoveTextEnd(shift); + return EditState.Continue; + } + + // Select All + case KeyCode.A: + { + if (ctrlOnly) + { + SelectAll(); + return EditState.Continue; + } + break; + } + + // Copy + case KeyCode.C: + { + if (ctrlOnly) + { + if (inputType != InputType.Password) + clipboard = GetSelectedString(); + else + clipboard = ""; + return EditState.Continue; + } + break; + } + + // Paste + case KeyCode.V: + { + if (ctrlOnly) + { + Append(clipboard); + return EditState.Continue; + } + break; + } + + // Cut + case KeyCode.X: + { + if (ctrlOnly) + { + if (inputType != InputType.Password) + clipboard = GetSelectedString(); + else + clipboard = ""; + Delete(); + SendOnValueChangedAndUpdateLabel(); + return EditState.Continue; + } + break; + } + + case KeyCode.LeftArrow: + { + MoveLeft(shift, ctrl); + return EditState.Continue; + } + + case KeyCode.RightArrow: + { + MoveRight(shift, ctrl); + return EditState.Continue; + } + + case KeyCode.UpArrow: + { + MoveUp(shift); + return EditState.Continue; + } + + case KeyCode.DownArrow: + { + MoveDown(shift); + return EditState.Continue; + } + + // Submit + case KeyCode.Return: + case KeyCode.KeypadEnter: + { + if (lineType != LineType.MultiLineNewline) + { + return EditState.Finish; + } + break; + } + + case KeyCode.Escape: + { + m_WasCanceled = true; + return EditState.Finish; + } + } + + char c = evt.character; + // Don't allow return chars or tabulator key to be entered into single line fields. + if (!multiLine && (c == '\t' || c == '\r' || c == 10)) + return EditState.Continue; + + // Convert carriage return and end-of-text characters to newline. + if (c == '\r' || (int)c == 3) + c = '\n'; + + if (IsValidChar(c)) + { + Append(c); + } + + if (c == 0) + { + if (compositionString.Length > 0) + { + UpdateLabel(); + } + } + return EditState.Continue; + } + + private bool IsValidChar(char c) + { + // Delete key on mac + if ((int)c == 127) + return false; + // Accept newline and tab + if (c == '\t' || c == '\n') + return true; + + return m_TextComponent.font.HasCharacter(c); + } + + /// <summary> + /// Handle the specified event. + /// </summary> + private Event m_ProcessingEvent = new Event(); + + public void ProcessEvent(Event e) + { + KeyPressed(e); + } + + //c IUpdateSelectedHandler的接口 + public virtual void OnUpdateSelected(BaseEventData eventData) + { + if (!isFocused) + return; + + bool consumedEvent = false; + while (Event.PopEvent(m_ProcessingEvent)) + { + if (m_ProcessingEvent.rawType == EventType.KeyDown) + { + consumedEvent = true; + var shouldContinue = KeyPressed(m_ProcessingEvent); + if (shouldContinue == EditState.Finish) + { + DeactivateInputField(); + break; + } + } + + switch (m_ProcessingEvent.type) + { + case EventType.ValidateCommand: + case EventType.ExecuteCommand: + switch (m_ProcessingEvent.commandName) + { + case "SelectAll": + SelectAll(); + consumedEvent = true; + break; + } + break; + } + } + + if (consumedEvent) + UpdateLabel(); + + eventData.Use(); + } + + private string GetSelectedString() + { + if (!hasSelection) + return ""; + + int startPos = caretPositionInternal; + int endPos = caretSelectPositionInternal; + + // Ensure startPos is always less then endPos to make the code simpler + if (startPos > endPos) + { + int temp = startPos; + startPos = endPos; + endPos = temp; + } + + return text.Substring(startPos, endPos - startPos); + } + + private int FindtNextWordBegin() + { + if (caretSelectPositionInternal + 1 >= text.Length) + return text.Length; + + int spaceLoc = text.IndexOfAny(kSeparators, caretSelectPositionInternal + 1); + + if (spaceLoc == -1) + spaceLoc = text.Length; + else + spaceLoc++; + + return spaceLoc; + } + + private void MoveRight(bool shift, bool ctrl) + { + if (hasSelection && !shift) + { + // By convention, if we have a selection and move right without holding shift, + // we just place the cursor at the end. + caretPositionInternal = caretSelectPositionInternal = Mathf.Max(caretPositionInternal, caretSelectPositionInternal); + return; + } + + int position; + if (ctrl) + position = FindtNextWordBegin(); + else + position = caretSelectPositionInternal + 1; + + if (shift) + caretSelectPositionInternal = position; + else + caretSelectPositionInternal = caretPositionInternal = position; + } + + private int FindtPrevWordBegin() + { + if (caretSelectPositionInternal - 2 < 0) + return 0; + + int spaceLoc = text.LastIndexOfAny(kSeparators, caretSelectPositionInternal - 2); + + if (spaceLoc == -1) + spaceLoc = 0; + else + spaceLoc++; + + return spaceLoc; + } + + private void MoveLeft(bool shift, bool ctrl) + { + if (hasSelection && !shift) + { + // By convention, if we have a selection and move left without holding shift, + // we just place the cursor at the start. + caretPositionInternal = caretSelectPositionInternal = Mathf.Min(caretPositionInternal, caretSelectPositionInternal); + return; + } + + int position; + if (ctrl) + position = FindtPrevWordBegin(); + else + position = caretSelectPositionInternal - 1; + + if (shift) + caretSelectPositionInternal = position; + else + caretSelectPositionInternal = caretPositionInternal = position; + } + + private int DetermineCharacterLine(int charPos, TextGenerator generator) + { + for (int i = 0; i < generator.lineCount - 1; ++i) + { + if (generator.lines[i + 1].startCharIdx > charPos) + return i; + } + + return generator.lineCount - 1; + } + + /// <summary> + /// Use cachedInputTextGenerator as the y component for the UICharInfo is not required + /// </summary> + + private int LineUpCharacterPosition(int originalPos, bool goToFirstChar) + { + if (originalPos >= cachedInputTextGenerator.characters.Count) + return 0; + + UICharInfo originChar = cachedInputTextGenerator.characters[originalPos]; + int originLine = DetermineCharacterLine(originalPos, cachedInputTextGenerator); + + // We are on the first line return first character + if (originLine <= 0) + return goToFirstChar ? 0 : originalPos; + + int endCharIdx = cachedInputTextGenerator.lines[originLine].startCharIdx - 1; + + for (int i = cachedInputTextGenerator.lines[originLine - 1].startCharIdx; i < endCharIdx; ++i) + { + if (cachedInputTextGenerator.characters[i].cursorPos.x >= originChar.cursorPos.x) + return i; + } + return endCharIdx; + } + + /// <summary> + /// Use cachedInputTextGenerator as the y component for the UICharInfo is not required + /// </summary> + + private int LineDownCharacterPosition(int originalPos, bool goToLastChar) + { + if (originalPos >= cachedInputTextGenerator.characterCountVisible) + return text.Length; + + UICharInfo originChar = cachedInputTextGenerator.characters[originalPos]; + int originLine = DetermineCharacterLine(originalPos, cachedInputTextGenerator); + + // We are on the last line return last character + if (originLine + 1 >= cachedInputTextGenerator.lineCount) + return goToLastChar ? text.Length : originalPos; + + // Need to determine end line for next line. + int endCharIdx = GetLineEndPosition(cachedInputTextGenerator, originLine + 1); + + for (int i = cachedInputTextGenerator.lines[originLine + 1].startCharIdx; i < endCharIdx; ++i) + { + if (cachedInputTextGenerator.characters[i].cursorPos.x >= originChar.cursorPos.x) + return i; + } + return endCharIdx; + } + + private void MoveDown(bool shift) + { + MoveDown(shift, true); + } + + private void MoveDown(bool shift, bool goToLastChar) + { + if (hasSelection && !shift) + { + // If we have a selection and press down without shift, + // set caret position to end of selection before we move it down. + caretPositionInternal = caretSelectPositionInternal = Mathf.Max(caretPositionInternal, caretSelectPositionInternal); + } + + int position = multiLine ? LineDownCharacterPosition(caretSelectPositionInternal, goToLastChar) : text.Length; + + if (shift) + caretSelectPositionInternal = position; + else + caretPositionInternal = caretSelectPositionInternal = position; + } + + private void MoveUp(bool shift) + { + MoveUp(shift, true); + } + + private void MoveUp(bool shift, bool goToFirstChar) + { + if (hasSelection && !shift) + { + // If we have a selection and press up without shift, + // set caret position to start of selection before we move it up. + caretPositionInternal = caretSelectPositionInternal = Mathf.Min(caretPositionInternal, caretSelectPositionInternal); + } + + int position = multiLine ? LineUpCharacterPosition(caretSelectPositionInternal, goToFirstChar) : 0; + + if (shift) + caretSelectPositionInternal = position; + else + caretSelectPositionInternal = caretPositionInternal = position; + } + + private void Delete() + { + if (m_ReadOnly) + return; + + if (caretPositionInternal == caretSelectPositionInternal) + return; + + if (caretPositionInternal < caretSelectPositionInternal) + { + m_Text = text.Substring(0, caretPositionInternal) + text.Substring(caretSelectPositionInternal, text.Length - caretSelectPositionInternal); + caretSelectPositionInternal = caretPositionInternal; + } + else + { + m_Text = text.Substring(0, caretSelectPositionInternal) + text.Substring(caretPositionInternal, text.Length - caretPositionInternal); + caretPositionInternal = caretSelectPositionInternal; + } + } + + private void ForwardSpace() + { + if (m_ReadOnly) + return; + + if (hasSelection) + { + Delete(); + SendOnValueChangedAndUpdateLabel(); + } + else + { + if (caretPositionInternal < text.Length) + { + m_Text = text.Remove(caretPositionInternal, 1); + SendOnValueChangedAndUpdateLabel(); + } + } + } + + private void Backspace() + { + if (m_ReadOnly) + return; + + if (hasSelection) + { + Delete(); + SendOnValueChangedAndUpdateLabel(); + } + else + { + if (caretPositionInternal > 0) + { + m_Text = text.Remove(caretPositionInternal - 1, 1); + caretSelectPositionInternal = caretPositionInternal = caretPositionInternal - 1; + SendOnValueChangedAndUpdateLabel(); + } + } + } + + // Insert the character and update the label. + private void Insert(char c) + { + if (m_ReadOnly) + return; + + string replaceString = c.ToString(); + Delete(); + + // Can't go past the character limit + if (characterLimit > 0 && text.Length >= characterLimit) + return; + + m_Text = text.Insert(m_CaretPosition, replaceString); + caretSelectPositionInternal = caretPositionInternal += replaceString.Length; + + SendOnValueChanged(); + } + + private void SendOnValueChangedAndUpdateLabel() + { + SendOnValueChanged(); + UpdateLabel(); + } + + private void SendOnValueChanged() + { + UISystemProfilerApi.AddMarker("InputField.value", this); + if (onValueChanged != null) + onValueChanged.Invoke(text); + } + + /// <summary> + /// Submit the input field's text. + /// </summary> + + protected void SendOnSubmit() + { + UISystemProfilerApi.AddMarker("InputField.onSubmit", this); + if (onEndEdit != null) + onEndEdit.Invoke(m_Text); + } + + /// <summary> + /// Append the specified text to the end of the current. + /// </summary> + + protected virtual void Append(string input) + { + if (m_ReadOnly) + return; + + if (!InPlaceEditing()) + return; + + for (int i = 0, imax = input.Length; i < imax; ++i) + { + char c = input[i]; + + if (c >= ' ' || c == '\t' || c == '\r' || c == 10 || c == '\n') + { + Append(c); + } + } + } + + protected virtual void Append(char input) + { + if (m_ReadOnly) + return; + + if (!InPlaceEditing()) + return; + + // If we have an input validator, validate the input first + int insertionPoint = Math.Min(selectionFocusPosition, selectionAnchorPosition); + if (onValidateInput != null) + input = onValidateInput(text, insertionPoint, input); + else if (characterValidation != CharacterValidation.None) + input = Validate(text, insertionPoint, input); + + // If the input is invalid, skip it + if (input == 0) + return; + + // Append the character and update the label + Insert(input); + } + + /// <summary> + /// Update the visual text Text. + /// </summary> + + protected void UpdateLabel() + { + if (m_TextComponent != null && m_TextComponent.font != null && !m_PreventFontCallback) + { + // TextGenerator.Populate invokes a callback that's called for anything + // that needs to be updated when the data for that font has changed. + // This makes all Text components that use that font update their vertices. + // In turn, this makes the InputField that's associated with that Text component + // update its label by calling this UpdateLabel method. + // This is a recursive call we want to prevent, since it makes the InputField + // update based on font data that didn't yet finish executing, or alternatively + // hang on infinite recursion, depending on whether the cached value is cached + // before or after the calculation. + // + // This callback also occurs when assigning text to our Text component, i.e., + // m_TextComponent.text = processed; + + m_PreventFontCallback = true; + + string fullText; + if (compositionString.Length > 0) + fullText = text.Substring(0, m_CaretPosition) + compositionString + text.Substring(m_CaretPosition); + else + fullText = text; + + string processed; + if (inputType == InputType.Password) + processed = new string(asteriskChar, fullText.Length); + else + processed = fullText; + + bool isEmpty = string.IsNullOrEmpty(fullText); + + if (m_Placeholder != null) + m_Placeholder.enabled = isEmpty; + + // If not currently editing the text, set the visible range to the whole text. + // The UpdateLabel method will then truncate it to the part that fits inside the Text area. + // We can't do this when text is being edited since it would discard the current scroll, + // which is defined by means of the m_DrawStart and m_DrawEnd indices. + if (!m_AllowInput) + { + m_DrawStart = 0; + m_DrawEnd = m_Text.Length; + } + + if (!isEmpty) + { + // Determine what will actually fit into the given line + Vector2 extents = m_TextComponent.rectTransform.rect.size; + + var settings = m_TextComponent.GetGenerationSettings(extents); + settings.generateOutOfBounds = true; + + cachedInputTextGenerator.PopulateWithErrors(processed, settings, gameObject); + + SetDrawRangeToContainCaretPosition(caretSelectPositionInternal); + + processed = processed.Substring(m_DrawStart, Mathf.Min(m_DrawEnd, processed.Length) - m_DrawStart); + + SetCaretVisible(); + } + m_TextComponent.text = processed; + MarkGeometryAsDirty(); + m_PreventFontCallback = false; + } + } + + private bool IsSelectionVisible() + { + if (m_DrawStart > caretPositionInternal || m_DrawStart > caretSelectPositionInternal) + return false; + + if (m_DrawEnd < caretPositionInternal || m_DrawEnd < caretSelectPositionInternal) + return false; + + return true; + } + + private static int GetLineStartPosition(TextGenerator gen, int line) + { + line = Mathf.Clamp(line, 0, gen.lines.Count - 1); + return gen.lines[line].startCharIdx; + } + + private static int GetLineEndPosition(TextGenerator gen, int line) + { + line = Mathf.Max(line, 0); + if (line + 1 < gen.lines.Count) + return gen.lines[line + 1].startCharIdx - 1; + return gen.characterCountVisible; + } + + private void SetDrawRangeToContainCaretPosition(int caretPos) + { + // We don't have any generated lines generation is not valid. + if (cachedInputTextGenerator.lineCount <= 0) + return; + + // the extents gets modified by the pixel density, so we need to use the generated extents since that will be in the same 'space' as + // the values returned by the TextGenerator.lines[x].height for instance. + Vector2 extents = cachedInputTextGenerator.rectExtents.size; + + if (multiLine) + { + var lines = cachedInputTextGenerator.lines; + int caretLine = DetermineCharacterLine(caretPos, cachedInputTextGenerator); + + if (caretPos > m_DrawEnd) + { + // Caret comes after drawEnd, so we need to move drawEnd to the end of the line with the caret + m_DrawEnd = GetLineEndPosition(cachedInputTextGenerator, caretLine); + float bottomY = lines[caretLine].topY - lines[caretLine].height; + if (caretLine == lines.Count - 1) + { + // Remove interline spacing on last line. + bottomY += lines[caretLine].leading; + } + int startLine = caretLine; + while (startLine > 0) + { + float topY = lines[startLine - 1].topY; + if (topY - bottomY > extents.y) + break; + startLine--; + } + m_DrawStart = GetLineStartPosition(cachedInputTextGenerator, startLine); + } + else + { + if (caretPos < m_DrawStart) + { + // Caret comes before drawStart, so we need to move drawStart to an earlier line start that comes before caret. + m_DrawStart = GetLineStartPosition(cachedInputTextGenerator, caretLine); + } + + int startLine = DetermineCharacterLine(m_DrawStart, cachedInputTextGenerator); + int endLine = startLine; + + float topY = lines[startLine].topY; + float bottomY = lines[endLine].topY - lines[endLine].height; + + if (endLine == lines.Count - 1) + { + // Remove interline spacing on last line. + bottomY += lines[endLine].leading; + } + + while (endLine < lines.Count - 1) + { + bottomY = lines[endLine + 1].topY - lines[endLine + 1].height; + + if (endLine + 1 == lines.Count - 1) + { + // Remove interline spacing on last line. + bottomY += lines[endLine + 1].leading; + } + + if (topY - bottomY > extents.y) + break; + ++endLine; + } + + m_DrawEnd = GetLineEndPosition(cachedInputTextGenerator, endLine); + + while (startLine > 0) + { + topY = lines[startLine - 1].topY; + if (topY - bottomY > extents.y) + break; + startLine--; + } + m_DrawStart = GetLineStartPosition(cachedInputTextGenerator, startLine); + } + } + else + { + var characters = cachedInputTextGenerator.characters; + if (m_DrawEnd > cachedInputTextGenerator.characterCountVisible) + m_DrawEnd = cachedInputTextGenerator.characterCountVisible; + + float width = 0.0f; + if (caretPos > m_DrawEnd || (caretPos == m_DrawEnd && m_DrawStart > 0)) + { + // fit characters from the caretPos leftward + m_DrawEnd = caretPos; + for (m_DrawStart = m_DrawEnd - 1; m_DrawStart >= 0; --m_DrawStart) + { + if (width + characters[m_DrawStart].charWidth > extents.x) + break; + + width += characters[m_DrawStart].charWidth; + } + ++m_DrawStart; // move right one to the last character we could fit on the left + } + else + { + if (caretPos < m_DrawStart) + m_DrawStart = caretPos; + + m_DrawEnd = m_DrawStart; + } + + // fit characters rightward + for (; m_DrawEnd < cachedInputTextGenerator.characterCountVisible; ++m_DrawEnd) + { + width += characters[m_DrawEnd].charWidth; + if (width > extents.x) + break; + } + } + } + + public void ForceLabelUpdate() + { + UpdateLabel(); + } + + private void MarkGeometryAsDirty() + { +#if UNITY_EDITOR + if (!Application.isPlaying || UnityEditor.PrefabUtility.GetPrefabObject(gameObject) != null) + return; +#endif + + CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this); + } + + public virtual void Rebuild(CanvasUpdate update) + { + switch (update) + { + case CanvasUpdate.LatePreRender: + UpdateGeometry(); + break; + } + } + + public virtual void LayoutComplete() + {} + + public virtual void GraphicUpdateComplete() + {} + + private void UpdateGeometry() + { +#if UNITY_EDITOR + if (!Application.isPlaying) + return; +#endif + // No need to draw a cursor on mobile as its handled by the devices keyboard. + if (!shouldHideMobileInput) + return; + + if (m_CachedInputRenderer == null && m_TextComponent != null) + { + GameObject go = new GameObject(transform.name + " Input Caret", typeof(RectTransform), typeof(CanvasRenderer)); + go.hideFlags = HideFlags.DontSave; + go.transform.SetParent(m_TextComponent.transform.parent); + go.transform.SetAsFirstSibling(); + go.layer = gameObject.layer; + + caretRectTrans = go.GetComponent<RectTransform>(); + m_CachedInputRenderer = go.GetComponent<CanvasRenderer>(); + m_CachedInputRenderer.SetMaterial(m_TextComponent.GetModifiedMaterial(Graphic.defaultGraphicMaterial), Texture2D.whiteTexture); + + // Needed as if any layout is present we want the caret to always be the same as the text area. + go.AddComponent<LayoutElement>().ignoreLayout = true; + + AssignPositioningIfNeeded(); + } + + if (m_CachedInputRenderer == null) + return; + + OnFillVBO(mesh); + m_CachedInputRenderer.SetMesh(mesh); + } + + private void AssignPositioningIfNeeded() + { + if (m_TextComponent != null && caretRectTrans != null && + (caretRectTrans.localPosition != m_TextComponent.rectTransform.localPosition || + caretRectTrans.localRotation != m_TextComponent.rectTransform.localRotation || + caretRectTrans.localScale != m_TextComponent.rectTransform.localScale || + caretRectTrans.anchorMin != m_TextComponent.rectTransform.anchorMin || + caretRectTrans.anchorMax != m_TextComponent.rectTransform.anchorMax || + caretRectTrans.anchoredPosition != m_TextComponent.rectTransform.anchoredPosition || + caretRectTrans.sizeDelta != m_TextComponent.rectTransform.sizeDelta || + caretRectTrans.pivot != m_TextComponent.rectTransform.pivot)) + { + caretRectTrans.localPosition = m_TextComponent.rectTransform.localPosition; + caretRectTrans.localRotation = m_TextComponent.rectTransform.localRotation; + caretRectTrans.localScale = m_TextComponent.rectTransform.localScale; + caretRectTrans.anchorMin = m_TextComponent.rectTransform.anchorMin; + caretRectTrans.anchorMax = m_TextComponent.rectTransform.anchorMax; + caretRectTrans.anchoredPosition = m_TextComponent.rectTransform.anchoredPosition; + caretRectTrans.sizeDelta = m_TextComponent.rectTransform.sizeDelta; + caretRectTrans.pivot = m_TextComponent.rectTransform.pivot; + } + } + + private void OnFillVBO(Mesh vbo) + { + using (var helper = new VertexHelper()) + { + if (!isFocused) + { + helper.FillMesh(vbo); + return; + } + + Vector2 roundingOffset = m_TextComponent.PixelAdjustPoint(Vector2.zero); + if (!hasSelection) + GenerateCaret(helper, roundingOffset); + else + GenerateHightlight(helper, roundingOffset); + + helper.FillMesh(vbo); + } + } + + private void GenerateCaret(VertexHelper vbo, Vector2 roundingOffset) + { + if (!m_CaretVisible) + return; + + if (m_CursorVerts == null) + { + CreateCursorVerts(); + } + + float width = m_CaretWidth; + int adjustedPos = Mathf.Max(0, caretPositionInternal - m_DrawStart); + TextGenerator gen = m_TextComponent.cachedTextGenerator; + + if (gen == null) + return; + + if (gen.lineCount == 0) + return; + + Vector2 startPosition = Vector2.zero; + + // Calculate startPosition + if (adjustedPos < gen.characters.Count) + { + UICharInfo cursorChar = gen.characters[adjustedPos]; + startPosition.x = cursorChar.cursorPos.x; + } + startPosition.x /= m_TextComponent.pixelsPerUnit; + + // TODO: Only clamp when Text uses horizontal word wrap. + if (startPosition.x > m_TextComponent.rectTransform.rect.xMax) + startPosition.x = m_TextComponent.rectTransform.rect.xMax; + + int characterLine = DetermineCharacterLine(adjustedPos, gen); + startPosition.y = gen.lines[characterLine].topY / m_TextComponent.pixelsPerUnit; + float height = gen.lines[characterLine].height / m_TextComponent.pixelsPerUnit; + + for (int i = 0; i < m_CursorVerts.Length; i++) + m_CursorVerts[i].color = caretColor; + + m_CursorVerts[0].position = new Vector3(startPosition.x, startPosition.y - height, 0.0f); + m_CursorVerts[1].position = new Vector3(startPosition.x + width, startPosition.y - height, 0.0f); + m_CursorVerts[2].position = new Vector3(startPosition.x + width, startPosition.y, 0.0f); + m_CursorVerts[3].position = new Vector3(startPosition.x, startPosition.y, 0.0f); + + if (roundingOffset != Vector2.zero) + { + for (int i = 0; i < m_CursorVerts.Length; i++) + { + UIVertex uiv = m_CursorVerts[i]; + uiv.position.x += roundingOffset.x; + uiv.position.y += roundingOffset.y; + } + } + + vbo.AddUIVertexQuad(m_CursorVerts); + + int screenHeight = Screen.height; + // Multiple display support only when not the main display. For display 0 the reported + // resolution is always the desktops resolution since its part of the display API, + // so we use the standard none multiple display method. (case 741751) + int displayIndex = m_TextComponent.canvas.targetDisplay; + if (displayIndex > 0 && displayIndex < Display.displays.Length) + screenHeight = Display.displays[displayIndex].renderingHeight; + + startPosition.y = screenHeight - startPosition.y; + input.compositionCursorPos = startPosition; + } + + private void CreateCursorVerts() + { + m_CursorVerts = new UIVertex[4]; + + for (int i = 0; i < m_CursorVerts.Length; i++) + { + m_CursorVerts[i] = UIVertex.simpleVert; + m_CursorVerts[i].uv0 = Vector2.zero; + } + } + + private void GenerateHightlight(VertexHelper vbo, Vector2 roundingOffset) + { + int startChar = Mathf.Max(0, caretPositionInternal - m_DrawStart); + int endChar = Mathf.Max(0, caretSelectPositionInternal - m_DrawStart); + + // Ensure pos is always less then selPos to make the code simpler + if (startChar > endChar) + { + int temp = startChar; + startChar = endChar; + endChar = temp; + } + + endChar -= 1; + TextGenerator gen = m_TextComponent.cachedTextGenerator; + + if (gen.lineCount <= 0) + return; + + int currentLineIndex = DetermineCharacterLine(startChar, gen); + + int lastCharInLineIndex = GetLineEndPosition(gen, currentLineIndex); + + UIVertex vert = UIVertex.simpleVert; + vert.uv0 = Vector2.zero; + vert.color = selectionColor; + + int currentChar = startChar; + while (currentChar <= endChar && currentChar < gen.characterCount) + { + if (currentChar == lastCharInLineIndex || currentChar == endChar) + { + UICharInfo startCharInfo = gen.characters[startChar]; + UICharInfo endCharInfo = gen.characters[currentChar]; + Vector2 startPosition = new Vector2(startCharInfo.cursorPos.x / m_TextComponent.pixelsPerUnit, gen.lines[currentLineIndex].topY / m_TextComponent.pixelsPerUnit); + Vector2 endPosition = new Vector2((endCharInfo.cursorPos.x + endCharInfo.charWidth) / m_TextComponent.pixelsPerUnit, startPosition.y - gen.lines[currentLineIndex].height / m_TextComponent.pixelsPerUnit); + + // Checking xMin as well due to text generator not setting position if char is not rendered. + if (endPosition.x > m_TextComponent.rectTransform.rect.xMax || endPosition.x < m_TextComponent.rectTransform.rect.xMin) + endPosition.x = m_TextComponent.rectTransform.rect.xMax; + + var startIndex = vbo.currentVertCount; + vert.position = new Vector3(startPosition.x, endPosition.y, 0.0f) + (Vector3)roundingOffset; + vbo.AddVert(vert); + + vert.position = new Vector3(endPosition.x, endPosition.y, 0.0f) + (Vector3)roundingOffset; + vbo.AddVert(vert); + + vert.position = new Vector3(endPosition.x, startPosition.y, 0.0f) + (Vector3)roundingOffset; + vbo.AddVert(vert); + + vert.position = new Vector3(startPosition.x, startPosition.y, 0.0f) + (Vector3)roundingOffset; + vbo.AddVert(vert); + + vbo.AddTriangle(startIndex, startIndex + 1, startIndex + 2); + vbo.AddTriangle(startIndex + 2, startIndex + 3, startIndex + 0); + + startChar = currentChar + 1; + currentLineIndex++; + + lastCharInLineIndex = GetLineEndPosition(gen, currentLineIndex); + } + currentChar++; + } + } + + /// <summary> + /// Validate the specified input. + /// </summary> + + protected char Validate(string text, int pos, char ch) + { + // Validation is disabled + if (characterValidation == CharacterValidation.None || !enabled) + return ch; + + if (characterValidation == CharacterValidation.Integer || characterValidation == CharacterValidation.Decimal) + { + // Integer and decimal + bool cursorBeforeDash = (pos == 0 && text.Length > 0 && text[0] == '-'); + bool dashInSelection = text.Length > 0 && text[0] == '-' && ((caretPositionInternal == 0 && caretSelectPositionInternal > 0) || (caretSelectPositionInternal == 0 && caretPositionInternal > 0)); + bool selectionAtStart = caretPositionInternal == 0 || caretSelectPositionInternal == 0; + if (!cursorBeforeDash || dashInSelection) + { + if (ch >= '0' && ch <= '9') return ch; + if (ch == '-' && (pos == 0 || selectionAtStart)) return ch; + if (ch == '.' && characterValidation == CharacterValidation.Decimal && !text.Contains(".")) return ch; + } + } + else if (characterValidation == CharacterValidation.Alphanumeric) + { + // All alphanumeric characters + if (ch >= 'A' && ch <= 'Z') return ch; + if (ch >= 'a' && ch <= 'z') return ch; + if (ch >= '0' && ch <= '9') return ch; + } + else if (characterValidation == CharacterValidation.Name) + { + // FIXME: some actions still lead to invalid input: + // - Hitting delete in front of an uppercase letter + // - Selecting an uppercase letter and deleting it + // - Typing some text, hitting Home and typing more text (we then have an uppercase letter in the middle of a word) + // - Typing some text, hitting Home and typing a space (we then have a leading space) + // - Erasing a space between two words (we then have an uppercase letter in the middle of a word) + // - We accept a trailing space + // - We accept the insertion of a space between two lowercase letters. + // - Typing text in front of an existing uppercase letter + // - ... and certainly more + // + // The rule we try to implement are too complex for this kind of verification. + + if (char.IsLetter(ch)) + { + // Character following a space should be in uppercase. + if (char.IsLower(ch) && ((pos == 0) || (text[pos - 1] == ' '))) + { + return char.ToUpper(ch); + } + + // Character not following a space or an apostrophe should be in lowercase. + if (char.IsUpper(ch) && (pos > 0) && (text[pos - 1] != ' ') && (text[pos - 1] != '\'')) + { + return char.ToLower(ch); + } + + return ch; + } + + if (ch == '\'') + { + // Don't allow more than one apostrophe + if (!text.Contains("'")) + // Don't allow consecutive spaces and apostrophes. + if (!(((pos > 0) && ((text[pos - 1] == ' ') || (text[pos - 1] == '\''))) || + ((pos < text.Length) && ((text[pos] == ' ') || (text[pos] == '\''))))) + return ch; + } + + if (ch == ' ') + { + // Don't allow consecutive spaces and apostrophes. + if (!(((pos > 0) && ((text[pos - 1] == ' ') || (text[pos - 1] == '\''))) || + ((pos < text.Length) && ((text[pos] == ' ') || (text[pos] == '\''))))) + return ch; + } + } + else if (characterValidation == CharacterValidation.EmailAddress) + { + // From StackOverflow about allowed characters in email addresses: + // Uppercase and lowercase English letters (a-z, A-Z) + // Digits 0 to 9 + // Characters ! # $ % & ' * + - / = ? ^ _ ` { | } ~ + // Character . (dot, period, full stop) provided that it is not the first or last character, + // and provided also that it does not appear two or more times consecutively. + + if (ch >= 'A' && ch <= 'Z') return ch; + if (ch >= 'a' && ch <= 'z') return ch; + if (ch >= '0' && ch <= '9') return ch; + if (ch == '@' && text.IndexOf('@') == -1) return ch; + if (kEmailSpecialCharacters.IndexOf(ch) != -1) return ch; + if (ch == '.') + { + char lastChar = (text.Length > 0) ? text[Mathf.Clamp(pos, 0, text.Length - 1)] : ' '; + char nextChar = (text.Length > 0) ? text[Mathf.Clamp(pos + 1, 0, text.Length - 1)] : '\n'; + if (lastChar != '.' && nextChar != '.') + return ch; + } + } + return (char)0; + } + + public void ActivateInputField() + { + if (m_TextComponent == null || m_TextComponent.font == null || !IsActive() || !IsInteractable()) + return; + + if (isFocused) + { + if (m_Keyboard != null && !m_Keyboard.active) + { + m_Keyboard.active = true; + m_Keyboard.text = m_Text; + } + } + + m_ShouldActivateNextUpdate = true; + } + + private void ActivateInputFieldInternal() + { + if (EventSystem.current == null) + return; + + if (EventSystem.current.currentSelectedGameObject != gameObject) + EventSystem.current.SetSelectedGameObject(gameObject); + + if (TouchScreenKeyboard.isSupported) + { + if (input.touchSupported) + { + TouchScreenKeyboard.hideInput = shouldHideMobileInput; + } + + m_Keyboard = (inputType == InputType.Password) ? + TouchScreenKeyboard.Open(m_Text, keyboardType, false, multiLine, true) : + TouchScreenKeyboard.Open(m_Text, keyboardType, inputType == InputType.AutoCorrect, multiLine); + + // Mimics OnFocus but as mobile doesn't properly support select all + // just set it to the end of the text (where it would move when typing starts) + MoveTextEnd(false); + } + else + { + input.imeCompositionMode = IMECompositionMode.On; + OnFocus(); + } + + m_AllowInput = true; + m_OriginalText = text; + m_WasCanceled = false; + SetCaretVisible(); + UpdateLabel(); + } + + public override void OnSelect(BaseEventData eventData) + { + base.OnSelect(eventData); + + if (shouldActivateOnSelect) + ActivateInputField(); + } + + public virtual void OnPointerClick(PointerEventData eventData) + { + if (eventData.button != PointerEventData.InputButton.Left) + return; + + ActivateInputField(); + } + + public void DeactivateInputField() + { + // Not activated do nothing. + if (!m_AllowInput) + return; + + m_HasDoneFocusTransition = false; + m_AllowInput = false; + + if (m_Placeholder != null) + m_Placeholder.enabled = string.IsNullOrEmpty(m_Text); + + if (m_TextComponent != null && IsInteractable()) + { + if (m_WasCanceled) + text = m_OriginalText; + + if (m_Keyboard != null) + { + m_Keyboard.active = false; + m_Keyboard = null; + } + + m_CaretPosition = m_CaretSelectPosition = 0; + + SendOnSubmit(); + + input.imeCompositionMode = IMECompositionMode.Auto; + } + + MarkGeometryAsDirty(); + } + + public override void OnDeselect(BaseEventData eventData) + { + DeactivateInputField(); + base.OnDeselect(eventData); + } + + public virtual void OnSubmit(BaseEventData eventData) + { + if (!IsActive() || !IsInteractable()) + return; + + if (!isFocused) + m_ShouldActivateNextUpdate = true; + } + + private void EnforceContentType() + { + switch (contentType) + { + case ContentType.Standard: + { + // Don't enforce line type for this content type. + m_InputType = InputType.Standard; + m_KeyboardType = TouchScreenKeyboardType.Default; + m_CharacterValidation = CharacterValidation.None; + break; + } + case ContentType.Autocorrected: + { + // Don't enforce line type for this content type. + m_InputType = InputType.AutoCorrect; + m_KeyboardType = TouchScreenKeyboardType.Default; + m_CharacterValidation = CharacterValidation.None; + break; + } + case ContentType.IntegerNumber: + { + m_LineType = LineType.SingleLine; + m_InputType = InputType.Standard; + m_KeyboardType = TouchScreenKeyboardType.NumberPad; + m_CharacterValidation = CharacterValidation.Integer; + break; + } + case ContentType.DecimalNumber: + { + m_LineType = LineType.SingleLine; + m_InputType = InputType.Standard; + m_KeyboardType = TouchScreenKeyboardType.NumbersAndPunctuation; + m_CharacterValidation = CharacterValidation.Decimal; + break; + } + case ContentType.Alphanumeric: + { + m_LineType = LineType.SingleLine; + m_InputType = InputType.Standard; + m_KeyboardType = TouchScreenKeyboardType.ASCIICapable; + m_CharacterValidation = CharacterValidation.Alphanumeric; + break; + } + case ContentType.Name: + { + m_LineType = LineType.SingleLine; + m_InputType = InputType.Standard; + m_KeyboardType = TouchScreenKeyboardType.NamePhonePad; + m_CharacterValidation = CharacterValidation.Name; + break; + } + case ContentType.EmailAddress: + { + m_LineType = LineType.SingleLine; + m_InputType = InputType.Standard; + m_KeyboardType = TouchScreenKeyboardType.EmailAddress; + m_CharacterValidation = CharacterValidation.EmailAddress; + break; + } + case ContentType.Password: + { + m_LineType = LineType.SingleLine; + m_InputType = InputType.Password; + m_KeyboardType = TouchScreenKeyboardType.Default; + m_CharacterValidation = CharacterValidation.None; + break; + } + case ContentType.Pin: + { + m_LineType = LineType.SingleLine; + m_InputType = InputType.Password; + m_KeyboardType = TouchScreenKeyboardType.NumberPad; + m_CharacterValidation = CharacterValidation.Integer; + break; + } + default: + { + // Includes Custom type. Nothing should be enforced. + break; + } + } + + EnforceTextHOverflow(); + } + + void EnforceTextHOverflow() + { + if (m_TextComponent != null) + if (multiLine) + m_TextComponent.horizontalOverflow = HorizontalWrapMode.Wrap; + else + m_TextComponent.horizontalOverflow = HorizontalWrapMode.Overflow; + } + + void SetToCustomIfContentTypeIsNot(params ContentType[] allowedContentTypes) + { + if (contentType == ContentType.Custom) + return; + + for (int i = 0; i < allowedContentTypes.Length; i++) + if (contentType == allowedContentTypes[i]) + return; + + contentType = ContentType.Custom; + } + + void SetToCustom() + { + if (contentType == ContentType.Custom) + return; + + contentType = ContentType.Custom; + } + + protected override void DoStateTransition(SelectionState state, bool instant) + { + if (m_HasDoneFocusTransition) + state = SelectionState.Highlighted; + else if (state == SelectionState.Pressed) + m_HasDoneFocusTransition = true; + + base.DoStateTransition(state, instant); + } + + public virtual void CalculateLayoutInputHorizontal() {} + public virtual void CalculateLayoutInputVertical() {} + + public virtual float minWidth { get { return 0; } } + + public virtual float preferredWidth + { + get + { + if (textComponent == null) + return 0; + var settings = textComponent.GetGenerationSettings(Vector2.zero); + return textComponent.cachedTextGeneratorForLayout.GetPreferredWidth(m_Text, settings) / textComponent.pixelsPerUnit; + } + } + public virtual float flexibleWidth { get { return -1; } } + public virtual float minHeight { get { return 0; } } + + public virtual float preferredHeight + { + get + { + if (textComponent == null) + return 0; + var settings = textComponent.GetGenerationSettings(new Vector2(textComponent.rectTransform.rect.size.x, 0.0f)); + return textComponent.cachedTextGeneratorForLayout.GetPreferredHeight(m_Text, settings) / textComponent.pixelsPerUnit; + } + } + + public virtual float flexibleHeight { get { return -1; } } + public virtual int layoutPriority { get { return 1; } } + } +} diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/InputField.cs.meta b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/InputField.cs.meta new file mode 100644 index 0000000..a11780b --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/InputField.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd5762ccacc914a428b1e5e5ae0f0edb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Navigation.cs b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Navigation.cs new file mode 100644 index 0000000..98e77f9 --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Navigation.cs @@ -0,0 +1,74 @@ +using System; +using UnityEngine.Serialization; + +namespace UnityEngine.UI +{ + [Serializable] + public struct Navigation : IEquatable<Navigation> + { + /* + * This looks like it's not flags, but it is flags, + * the reason is that Automatic is considered horizontal + * and verical mode combined + */ + [Flags] + public enum Mode + { + None = 0, // 0 No navigation + Horizontal = 1, // 1 Automatic horizontal navigation + Vertical = 2, // 10 Automatic vertical navigation + Automatic = 3, // 11 Automatic navigation in both dimensions + Explicit = 4, // Explicitly specified only + } + + // Which method of navigation will be used. + [FormerlySerializedAs("mode")] + [SerializeField] + private Mode m_Mode; + + // Game object selected when the joystick moves up. Used when navigation is set to "Explicit". + [FormerlySerializedAs("selectOnUp")] + [SerializeField] + private Selectable m_SelectOnUp; + + // Game object selected when the joystick moves down. Used when navigation is set to "Explicit". + [FormerlySerializedAs("selectOnDown")] + [SerializeField] + private Selectable m_SelectOnDown; + + // Game object selected when the joystick moves left. Used when navigation is set to "Explicit". + [FormerlySerializedAs("selectOnLeft")] + [SerializeField] + private Selectable m_SelectOnLeft; + + // Game object selected when the joystick moves right. Used when navigation is set to "Explicit". + [FormerlySerializedAs("selectOnRight")] + [SerializeField] + private Selectable m_SelectOnRight; + + public Mode mode { get { return m_Mode; } set { m_Mode = value; } } + public Selectable selectOnUp { get { return m_SelectOnUp; } set { m_SelectOnUp = value; } } + public Selectable selectOnDown { get { return m_SelectOnDown; } set { m_SelectOnDown = value; } } + public Selectable selectOnLeft { get { return m_SelectOnLeft; } set { m_SelectOnLeft = value; } } + public Selectable selectOnRight { get { return m_SelectOnRight; } set { m_SelectOnRight = value; } } + + static public Navigation defaultNavigation + { + get + { + var defaultNav = new Navigation(); + defaultNav.m_Mode = Mode.Automatic; + return defaultNav; + } + } + + public bool Equals(Navigation other) + { + return mode == other.mode && + selectOnUp == other.selectOnUp && + selectOnDown == other.selectOnDown && + selectOnLeft == other.selectOnLeft && + selectOnRight == other.selectOnRight; + } + } +} diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Navigation.cs.meta b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Navigation.cs.meta new file mode 100644 index 0000000..a5f02d3 --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Navigation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7565fbbe496d6e749a2a17d8acb60c80 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ScrollRect.cs b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ScrollRect.cs new file mode 100644 index 0000000..106f15c --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ScrollRect.cs @@ -0,0 +1,873 @@ +using System; +using UnityEngine.Events; +using UnityEngine.EventSystems; + +namespace UnityEngine.UI +{ + [AddComponentMenu("UI/Scroll Rect", 37)] + [SelectionBase] + [ExecuteInEditMode] + [DisallowMultipleComponent] + [RequireComponent(typeof(RectTransform))] + public class ScrollRect + : UIBehaviour + , IInitializePotentialDragHandler + , IBeginDragHandler + , IEndDragHandler + , IDragHandler + , IScrollHandler + , ICanvasElement + , ILayoutElement + , ILayoutGroup + { + public enum MovementType + { + Unrestricted, // Unrestricted movement -- can scroll forever + Elastic, // Restricted but flexible -- can go past the edges, but springs back in place + Clamped, // Restricted movement where it's not possible to go past the edges + } + + public enum ScrollbarVisibility + { + Permanent, + AutoHide, + AutoHideAndExpandViewport, + } + + [Serializable] + public class ScrollRectEvent : UnityEvent<Vector2> {} + + [SerializeField] + private RectTransform m_Content; + public RectTransform content { get { return m_Content; } set { m_Content = value; } } + + [SerializeField] + private bool m_Horizontal = true; + public bool horizontal { get { return m_Horizontal; } set { m_Horizontal = value; } } + + [SerializeField] + private bool m_Vertical = true; + public bool vertical { get { return m_Vertical; } set { m_Vertical = value; } } + + [SerializeField] + private MovementType m_MovementType = MovementType.Elastic; + public MovementType movementType { get { return m_MovementType; } set { m_MovementType = value; } } + + [SerializeField] + private float m_Elasticity = 0.1f; // Only used for MovementType.Elastic + public float elasticity { get { return m_Elasticity; } set { m_Elasticity = value; } } + + [SerializeField] + private bool m_Inertia = true; + public bool inertia { get { return m_Inertia; } set { m_Inertia = value; } } + + [SerializeField] + private float m_DecelerationRate = 0.135f; // Only used when inertia is enabled + public float decelerationRate { get { return m_DecelerationRate; } set { m_DecelerationRate = value; } } + + [SerializeField] + private float m_ScrollSensitivity = 1.0f; + public float scrollSensitivity { get { return m_ScrollSensitivity; } set { m_ScrollSensitivity = value; } } + + [SerializeField] + private RectTransform m_Viewport; + public RectTransform viewport { get { return m_Viewport; } set { m_Viewport = value; SetDirtyCaching(); } } + + [SerializeField] + private Scrollbar m_HorizontalScrollbar; + public Scrollbar horizontalScrollbar + { + get + { + return m_HorizontalScrollbar; + } + set + { + if (m_HorizontalScrollbar) + m_HorizontalScrollbar.onValueChanged.RemoveListener(SetHorizontalNormalizedPosition); + m_HorizontalScrollbar = value; + if (m_HorizontalScrollbar) + m_HorizontalScrollbar.onValueChanged.AddListener(SetHorizontalNormalizedPosition); + SetDirtyCaching(); + } + } + + [SerializeField] + private Scrollbar m_VerticalScrollbar; + public Scrollbar verticalScrollbar + { + get + { + return m_VerticalScrollbar; + } + set + { + if (m_VerticalScrollbar) + m_VerticalScrollbar.onValueChanged.RemoveListener(SetVerticalNormalizedPosition); + m_VerticalScrollbar = value; + if (m_VerticalScrollbar) + m_VerticalScrollbar.onValueChanged.AddListener(SetVerticalNormalizedPosition); + SetDirtyCaching(); + } + } + + [SerializeField] + private ScrollbarVisibility m_HorizontalScrollbarVisibility; + public ScrollbarVisibility horizontalScrollbarVisibility { get { return m_HorizontalScrollbarVisibility; } set { m_HorizontalScrollbarVisibility = value; SetDirtyCaching(); } } + + [SerializeField] + private ScrollbarVisibility m_VerticalScrollbarVisibility; + public ScrollbarVisibility verticalScrollbarVisibility { get { return m_VerticalScrollbarVisibility; } set { m_VerticalScrollbarVisibility = value; SetDirtyCaching(); } } + + [SerializeField] + private float m_HorizontalScrollbarSpacing; + public float horizontalScrollbarSpacing { get { return m_HorizontalScrollbarSpacing; } set { m_HorizontalScrollbarSpacing = value; SetDirty(); } } + + [SerializeField] + private float m_VerticalScrollbarSpacing; + public float verticalScrollbarSpacing { get { return m_VerticalScrollbarSpacing; } set { m_VerticalScrollbarSpacing = value; SetDirty(); } } + + [SerializeField] + private ScrollRectEvent m_OnValueChanged = new ScrollRectEvent(); + public ScrollRectEvent onValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } } + + // The offset from handle position to mouse down position + private Vector2 m_PointerStartLocalCursor = Vector2.zero; + protected Vector2 m_ContentStartPosition = Vector2.zero; + + private RectTransform m_ViewRect; + + protected RectTransform viewRect + { + get + { + if (m_ViewRect == null) + m_ViewRect = m_Viewport; + if (m_ViewRect == null) + m_ViewRect = (RectTransform)transform; + return m_ViewRect; + } + } + + protected Bounds m_ContentBounds; + private Bounds m_ViewBounds; + + private Vector2 m_Velocity; + public Vector2 velocity { get { return m_Velocity; } set { m_Velocity = value; } } + + private bool m_Dragging; + + private Vector2 m_PrevPosition = Vector2.zero; + private Bounds m_PrevContentBounds; + private Bounds m_PrevViewBounds; + [NonSerialized] + private bool m_HasRebuiltLayout = false; + + private bool m_HSliderExpand; + private bool m_VSliderExpand; + private float m_HSliderHeight; + private float m_VSliderWidth; + + [System.NonSerialized] private RectTransform m_Rect; + private RectTransform rectTransform + { + get + { + if (m_Rect == null) + m_Rect = GetComponent<RectTransform>(); + return m_Rect; + } + } + + private RectTransform m_HorizontalScrollbarRect; + private RectTransform m_VerticalScrollbarRect; + + private DrivenRectTransformTracker m_Tracker; + + protected ScrollRect() + {} + + public virtual void Rebuild(CanvasUpdate executing) + { + if (executing == CanvasUpdate.Prelayout) + { + UpdateCachedData(); + } + + if (executing == CanvasUpdate.PostLayout) + { + UpdateBounds(); + UpdateScrollbars(Vector2.zero); + UpdatePrevData(); + + m_HasRebuiltLayout = true; + } + } + + public virtual void LayoutComplete() + {} + + public virtual void GraphicUpdateComplete() + {} + + void UpdateCachedData() + { + Transform transform = this.transform; + m_HorizontalScrollbarRect = m_HorizontalScrollbar == null ? null : m_HorizontalScrollbar.transform as RectTransform; + m_VerticalScrollbarRect = m_VerticalScrollbar == null ? null : m_VerticalScrollbar.transform as RectTransform; + + // These are true if either the elements are children, or they don't exist at all. + bool viewIsChild = (viewRect.parent == transform); + bool hScrollbarIsChild = (!m_HorizontalScrollbarRect || m_HorizontalScrollbarRect.parent == transform); + bool vScrollbarIsChild = (!m_VerticalScrollbarRect || m_VerticalScrollbarRect.parent == transform); + bool allAreChildren = (viewIsChild && hScrollbarIsChild && vScrollbarIsChild); + + m_HSliderExpand = allAreChildren && m_HorizontalScrollbarRect && horizontalScrollbarVisibility == ScrollbarVisibility.AutoHideAndExpandViewport; + m_VSliderExpand = allAreChildren && m_VerticalScrollbarRect && verticalScrollbarVisibility == ScrollbarVisibility.AutoHideAndExpandViewport; + m_HSliderHeight = (m_HorizontalScrollbarRect == null ? 0 : m_HorizontalScrollbarRect.rect.height); + m_VSliderWidth = (m_VerticalScrollbarRect == null ? 0 : m_VerticalScrollbarRect.rect.width); + } + + protected override void OnEnable() + { + base.OnEnable(); + + if (m_HorizontalScrollbar) + m_HorizontalScrollbar.onValueChanged.AddListener(SetHorizontalNormalizedPosition); + if (m_VerticalScrollbar) + m_VerticalScrollbar.onValueChanged.AddListener(SetVerticalNormalizedPosition); + + CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this); + } + + protected override void OnDisable() + { + CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild(this); + + if (m_HorizontalScrollbar) + m_HorizontalScrollbar.onValueChanged.RemoveListener(SetHorizontalNormalizedPosition); + if (m_VerticalScrollbar) + m_VerticalScrollbar.onValueChanged.RemoveListener(SetVerticalNormalizedPosition); + + m_HasRebuiltLayout = false; + m_Tracker.Clear(); + m_Velocity = Vector2.zero; + LayoutRebuilder.MarkLayoutForRebuild(rectTransform); + base.OnDisable(); + } + + public override bool IsActive() + { + return base.IsActive() && m_Content != null; + } + + private void EnsureLayoutHasRebuilt() + { + if (!m_HasRebuiltLayout && !CanvasUpdateRegistry.IsRebuildingLayout()) + Canvas.ForceUpdateCanvases(); + } + + public virtual void StopMovement() + { + m_Velocity = Vector2.zero; + } + + public virtual void OnScroll(PointerEventData data) + { + if (!IsActive()) + return; + + EnsureLayoutHasRebuilt(); + UpdateBounds(); + + Vector2 delta = data.scrollDelta; + // Down is positive for scroll events, while in UI system up is positive. + delta.y *= -1; + if (vertical && !horizontal) + { + if (Mathf.Abs(delta.x) > Mathf.Abs(delta.y)) + delta.y = delta.x; + delta.x = 0; + } + if (horizontal && !vertical) + { + if (Mathf.Abs(delta.y) > Mathf.Abs(delta.x)) + delta.x = delta.y; + delta.y = 0; + } + + Vector2 position = m_Content.anchoredPosition; + position += delta * m_ScrollSensitivity; + if (m_MovementType == MovementType.Clamped) + position += CalculateOffset(position - m_Content.anchoredPosition); + + SetContentAnchoredPosition(position); + UpdateBounds(); + } + + public virtual void OnInitializePotentialDrag(PointerEventData eventData) + { + if (eventData.button != PointerEventData.InputButton.Left) + return; + + m_Velocity = Vector2.zero; + } + + public virtual void OnBeginDrag(PointerEventData eventData) + { + if (eventData.button != PointerEventData.InputButton.Left) + return; + + if (!IsActive()) + return; + + UpdateBounds(); + + m_PointerStartLocalCursor = Vector2.zero; + RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect, eventData.position, eventData.pressEventCamera, out m_PointerStartLocalCursor); + m_ContentStartPosition = m_Content.anchoredPosition; + m_Dragging = true; + } + + public virtual void OnEndDrag(PointerEventData eventData) + { + if (eventData.button != PointerEventData.InputButton.Left) + return; + + m_Dragging = false; + } + + public virtual void OnDrag(PointerEventData eventData) + { + if (eventData.button != PointerEventData.InputButton.Left) + return; + + if (!IsActive()) + return; + + Vector2 localCursor; + if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect, eventData.position, eventData.pressEventCamera, out localCursor)) + return; + + UpdateBounds(); + + var pointerDelta = localCursor - m_PointerStartLocalCursor; + Vector2 position = m_ContentStartPosition + pointerDelta; + + // Offset to get content into place in the view. + Vector2 offset = CalculateOffset(position - m_Content.anchoredPosition); + position += offset; + if (m_MovementType == MovementType.Elastic) + { + if (offset.x != 0) + position.x = position.x - RubberDelta(offset.x, m_ViewBounds.size.x); + if (offset.y != 0) + position.y = position.y - RubberDelta(offset.y, m_ViewBounds.size.y); + } + + SetContentAnchoredPosition(position); + } + + protected virtual void SetContentAnchoredPosition(Vector2 position) + { + if (!m_Horizontal) + position.x = m_Content.anchoredPosition.x; + if (!m_Vertical) + position.y = m_Content.anchoredPosition.y; + + if (position != m_Content.anchoredPosition) + { + m_Content.anchoredPosition = position; + UpdateBounds(); + } + } + + protected virtual void LateUpdate() + { + if (!m_Content) + return; + + EnsureLayoutHasRebuilt(); + UpdateScrollbarVisibility(); + UpdateBounds(); + float deltaTime = Time.unscaledDeltaTime; + Vector2 offset = CalculateOffset(Vector2.zero); + if (!m_Dragging && (offset != Vector2.zero || m_Velocity != Vector2.zero)) + { + Vector2 position = m_Content.anchoredPosition; + for (int axis = 0; axis < 2; axis++) + { + // Apply spring physics if movement is elastic and content has an offset from the view. + if (m_MovementType == MovementType.Elastic && offset[axis] != 0) + { + float speed = m_Velocity[axis]; + position[axis] = Mathf.SmoothDamp(m_Content.anchoredPosition[axis], m_Content.anchoredPosition[axis] + offset[axis], ref speed, m_Elasticity, Mathf.Infinity, deltaTime); + if (Mathf.Abs(speed) < 1) + speed = 0; + m_Velocity[axis] = speed; + } + // Else move content according to velocity with deceleration applied. + else if (m_Inertia) + { + m_Velocity[axis] *= Mathf.Pow(m_DecelerationRate, deltaTime); + if (Mathf.Abs(m_Velocity[axis]) < 1) + m_Velocity[axis] = 0; + position[axis] += m_Velocity[axis] * deltaTime; + } + // If we have neither elaticity or friction, there shouldn't be any velocity. + else + { + m_Velocity[axis] = 0; + } + } + + if (m_Velocity != Vector2.zero) + { + if (m_MovementType == MovementType.Clamped) + { + offset = CalculateOffset(position - m_Content.anchoredPosition); + position += offset; + } + + SetContentAnchoredPosition(position); + } + } + + if (m_Dragging && m_Inertia) + { + Vector3 newVelocity = (m_Content.anchoredPosition - m_PrevPosition) / deltaTime; + m_Velocity = Vector3.Lerp(m_Velocity, newVelocity, deltaTime * 10); + } + + if (m_ViewBounds != m_PrevViewBounds || m_ContentBounds != m_PrevContentBounds || m_Content.anchoredPosition != m_PrevPosition) + { + UpdateScrollbars(offset); + UISystemProfilerApi.AddMarker("ScrollRect.value", this); + m_OnValueChanged.Invoke(normalizedPosition); + UpdatePrevData(); + } + } + + protected void UpdatePrevData() + { + if (m_Content == null) + m_PrevPosition = Vector2.zero; + else + m_PrevPosition = m_Content.anchoredPosition; + m_PrevViewBounds = m_ViewBounds; + m_PrevContentBounds = m_ContentBounds; + } + + private void UpdateScrollbars(Vector2 offset) + { + if (m_HorizontalScrollbar) + { + if (m_ContentBounds.size.x > 0) + m_HorizontalScrollbar.size = Mathf.Clamp01((m_ViewBounds.size.x - Mathf.Abs(offset.x)) / m_ContentBounds.size.x); + else + m_HorizontalScrollbar.size = 1; + + m_HorizontalScrollbar.value = horizontalNormalizedPosition; + } + + if (m_VerticalScrollbar) + { + if (m_ContentBounds.size.y > 0) + m_VerticalScrollbar.size = Mathf.Clamp01((m_ViewBounds.size.y - Mathf.Abs(offset.y)) / m_ContentBounds.size.y); + else + m_VerticalScrollbar.size = 1; + + m_VerticalScrollbar.value = verticalNormalizedPosition; + } + } + + public Vector2 normalizedPosition + { + get + { + return new Vector2(horizontalNormalizedPosition, verticalNormalizedPosition); + } + set + { + SetNormalizedPosition(value.x, 0); + SetNormalizedPosition(value.y, 1); + } + } + + public float horizontalNormalizedPosition + { + get + { + UpdateBounds(); + if (m_ContentBounds.size.x <= m_ViewBounds.size.x) + return (m_ViewBounds.min.x > m_ContentBounds.min.x) ? 1 : 0; + return (m_ViewBounds.min.x - m_ContentBounds.min.x) / (m_ContentBounds.size.x - m_ViewBounds.size.x); + } + set + { + SetNormalizedPosition(value, 0); + } + } + + public float verticalNormalizedPosition + { + get + { + UpdateBounds(); + if (m_ContentBounds.size.y <= m_ViewBounds.size.y) + return (m_ViewBounds.min.y > m_ContentBounds.min.y) ? 1 : 0; + ; + return (m_ViewBounds.min.y - m_ContentBounds.min.y) / (m_ContentBounds.size.y - m_ViewBounds.size.y); + } + set + { + SetNormalizedPosition(value, 1); + } + } + + private void SetHorizontalNormalizedPosition(float value) { SetNormalizedPosition(value, 0); } + private void SetVerticalNormalizedPosition(float value) { SetNormalizedPosition(value, 1); } + + protected virtual void SetNormalizedPosition(float value, int axis) + { + EnsureLayoutHasRebuilt(); + UpdateBounds(); + // How much the content is larger than the view. + float hiddenLength = m_ContentBounds.size[axis] - m_ViewBounds.size[axis]; + // Where the position of the lower left corner of the content bounds should be, in the space of the view. + float contentBoundsMinPosition = m_ViewBounds.min[axis] - value * hiddenLength; + // The new content localPosition, in the space of the view. + float newLocalPosition = m_Content.localPosition[axis] + contentBoundsMinPosition - m_ContentBounds.min[axis]; + + Vector3 localPosition = m_Content.localPosition; + if (Mathf.Abs(localPosition[axis] - newLocalPosition) > 0.01f) + { + localPosition[axis] = newLocalPosition; + m_Content.localPosition = localPosition; + m_Velocity[axis] = 0; + UpdateBounds(); + } + } + + private static float RubberDelta(float overStretching, float viewSize) + { + return (1 - (1 / ((Mathf.Abs(overStretching) * 0.55f / viewSize) + 1))) * viewSize * Mathf.Sign(overStretching); + } + + protected override void OnRectTransformDimensionsChange() + { + SetDirty(); + } + + private bool hScrollingNeeded + { + get + { + if (Application.isPlaying) + return m_ContentBounds.size.x > m_ViewBounds.size.x + 0.01f; + return true; + } + } + private bool vScrollingNeeded + { + get + { + if (Application.isPlaying) + return m_ContentBounds.size.y > m_ViewBounds.size.y + 0.01f; + return true; + } + } + + public virtual void CalculateLayoutInputHorizontal() {} + public virtual void CalculateLayoutInputVertical() {} + + public virtual float minWidth { get { return -1; } } + public virtual float preferredWidth { get { return -1; } } + public virtual float flexibleWidth { get { return -1; } } + + public virtual float minHeight { get { return -1; } } + public virtual float preferredHeight { get { return -1; } } + public virtual float flexibleHeight { get { return -1; } } + + public virtual int layoutPriority { get { return -1; } } + + public virtual void SetLayoutHorizontal() + { + m_Tracker.Clear(); + + if (m_HSliderExpand || m_VSliderExpand) + { + m_Tracker.Add(this, viewRect, + DrivenTransformProperties.Anchors | + DrivenTransformProperties.SizeDelta | + DrivenTransformProperties.AnchoredPosition); + + // Make view full size to see if content fits. + viewRect.anchorMin = Vector2.zero; + viewRect.anchorMax = Vector2.one; + viewRect.sizeDelta = Vector2.zero; + viewRect.anchoredPosition = Vector2.zero; + + // Recalculate content layout with this size to see if it fits when there are no scrollbars. + LayoutRebuilder.ForceRebuildLayoutImmediate(content); + m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size); + m_ContentBounds = GetBounds(); + } + + // If it doesn't fit vertically, enable vertical scrollbar and shrink view horizontally to make room for it. + if (m_VSliderExpand && vScrollingNeeded) + { + viewRect.sizeDelta = new Vector2(-(m_VSliderWidth + m_VerticalScrollbarSpacing), viewRect.sizeDelta.y); + + // Recalculate content layout with this size to see if it fits vertically + // when there is a vertical scrollbar (which may reflowed the content to make it taller). + LayoutRebuilder.ForceRebuildLayoutImmediate(content); + m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size); + m_ContentBounds = GetBounds(); + } + + // If it doesn't fit horizontally, enable horizontal scrollbar and shrink view vertically to make room for it. + if (m_HSliderExpand && hScrollingNeeded) + { + viewRect.sizeDelta = new Vector2(viewRect.sizeDelta.x, -(m_HSliderHeight + m_HorizontalScrollbarSpacing)); + m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size); + m_ContentBounds = GetBounds(); + } + + // If the vertical slider didn't kick in the first time, and the horizontal one did, + // we need to check again if the vertical slider now needs to kick in. + // If it doesn't fit vertically, enable vertical scrollbar and shrink view horizontally to make room for it. + if (m_VSliderExpand && vScrollingNeeded && viewRect.sizeDelta.x == 0 && viewRect.sizeDelta.y < 0) + { + viewRect.sizeDelta = new Vector2(-(m_VSliderWidth + m_VerticalScrollbarSpacing), viewRect.sizeDelta.y); + } + } + + public virtual void SetLayoutVertical() + { + UpdateScrollbarLayout(); + m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size); + m_ContentBounds = GetBounds(); + } + + void UpdateScrollbarVisibility() + { + UpdateOneScrollbarVisibility(vScrollingNeeded, m_Vertical, m_VerticalScrollbarVisibility, m_VerticalScrollbar); + UpdateOneScrollbarVisibility(hScrollingNeeded, m_Horizontal, m_HorizontalScrollbarVisibility, m_HorizontalScrollbar); + } + + private static void UpdateOneScrollbarVisibility(bool xScrollingNeeded, bool xAxisEnabled, ScrollbarVisibility scrollbarVisibility, Scrollbar scrollbar) + { + if (scrollbar) + { + if (scrollbarVisibility == ScrollbarVisibility.Permanent) + { + if (scrollbar.gameObject.activeSelf != xAxisEnabled) + scrollbar.gameObject.SetActive(xAxisEnabled); + } + else + { + if (scrollbar.gameObject.activeSelf != xScrollingNeeded) + scrollbar.gameObject.SetActive(xScrollingNeeded); + } + } + } + + void UpdateScrollbarLayout() + { + if (m_VSliderExpand && m_HorizontalScrollbar) + { + m_Tracker.Add(this, m_HorizontalScrollbarRect, + DrivenTransformProperties.AnchorMinX | + DrivenTransformProperties.AnchorMaxX | + DrivenTransformProperties.SizeDeltaX | + DrivenTransformProperties.AnchoredPositionX); + m_HorizontalScrollbarRect.anchorMin = new Vector2(0, m_HorizontalScrollbarRect.anchorMin.y); + m_HorizontalScrollbarRect.anchorMax = new Vector2(1, m_HorizontalScrollbarRect.anchorMax.y); + m_HorizontalScrollbarRect.anchoredPosition = new Vector2(0, m_HorizontalScrollbarRect.anchoredPosition.y); + if (vScrollingNeeded) + m_HorizontalScrollbarRect.sizeDelta = new Vector2(-(m_VSliderWidth + m_VerticalScrollbarSpacing), m_HorizontalScrollbarRect.sizeDelta.y); + else + m_HorizontalScrollbarRect.sizeDelta = new Vector2(0, m_HorizontalScrollbarRect.sizeDelta.y); + } + + if (m_HSliderExpand && m_VerticalScrollbar) + { + m_Tracker.Add(this, m_VerticalScrollbarRect, + DrivenTransformProperties.AnchorMinY | + DrivenTransformProperties.AnchorMaxY | + DrivenTransformProperties.SizeDeltaY | + DrivenTransformProperties.AnchoredPositionY); + m_VerticalScrollbarRect.anchorMin = new Vector2(m_VerticalScrollbarRect.anchorMin.x, 0); + m_VerticalScrollbarRect.anchorMax = new Vector2(m_VerticalScrollbarRect.anchorMax.x, 1); + m_VerticalScrollbarRect.anchoredPosition = new Vector2(m_VerticalScrollbarRect.anchoredPosition.x, 0); + if (hScrollingNeeded) + m_VerticalScrollbarRect.sizeDelta = new Vector2(m_VerticalScrollbarRect.sizeDelta.x, -(m_HSliderHeight + m_HorizontalScrollbarSpacing)); + else + m_VerticalScrollbarRect.sizeDelta = new Vector2(m_VerticalScrollbarRect.sizeDelta.x, 0); + } + } + + protected void UpdateBounds() + { + m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size); + m_ContentBounds = GetBounds(); + + if (m_Content == null) + return; + + Vector3 contentSize = m_ContentBounds.size; + Vector3 contentPos = m_ContentBounds.center; + var contentPivot = m_Content.pivot; + AdjustBounds(ref m_ViewBounds, ref contentPivot, ref contentSize, ref contentPos); + m_ContentBounds.size = contentSize; + m_ContentBounds.center = contentPos; + + if (movementType == MovementType.Clamped) + { + // Adjust content so that content bounds bottom (right side) is never higher (to the left) than the view bounds bottom (right side). + // top (left side) is never lower (to the right) than the view bounds top (left side). + // All this can happen if content has shrunk. + // This works because content size is at least as big as view size (because of the call to InternalUpdateBounds above). + Vector2 delta = Vector2.zero; + if (m_ViewBounds.max.x > m_ContentBounds.max.x) + { + delta.x = Math.Min(m_ViewBounds.min.x - m_ContentBounds.min.x, m_ViewBounds.max.x - m_ContentBounds.max.x); + } + else if (m_ViewBounds.min.x < m_ContentBounds.min.x) + { + delta.x = Math.Max(m_ViewBounds.min.x - m_ContentBounds.min.x, m_ViewBounds.max.x - m_ContentBounds.max.x); + } + + if (m_ViewBounds.min.y < m_ContentBounds.min.y) + { + delta.y = Math.Max(m_ViewBounds.min.y - m_ContentBounds.min.y, m_ViewBounds.max.y - m_ContentBounds.max.y); + } + else if (m_ViewBounds.max.y > m_ContentBounds.max.y) + { + delta.y = Math.Min(m_ViewBounds.min.y - m_ContentBounds.min.y, m_ViewBounds.max.y - m_ContentBounds.max.y); + } + if (delta.sqrMagnitude > float.Epsilon) + { + contentPos = m_Content.anchoredPosition + delta; + if (!m_Horizontal) + contentPos.x = m_Content.anchoredPosition.x; + if (!m_Vertical) + contentPos.y = m_Content.anchoredPosition.y; + AdjustBounds(ref m_ViewBounds, ref contentPivot, ref contentSize, ref contentPos); + } + } + } + + internal static void AdjustBounds(ref Bounds viewBounds, ref Vector2 contentPivot, ref Vector3 contentSize, ref Vector3 contentPos) + { + // Make sure content bounds are at least as large as view by adding padding if not. + // One might think at first that if the content is smaller than the view, scrolling should be allowed. + // However, that's not how scroll views normally work. + // Scrolling is *only* possible when content is *larger* than view. + // We use the pivot of the content rect to decide in which directions the content bounds should be expanded. + // E.g. if pivot is at top, bounds are expanded downwards. + // This also works nicely when ContentSizeFitter is used on the content. + Vector3 excess = viewBounds.size - contentSize; + if (excess.x > 0) + { + contentPos.x -= excess.x * (contentPivot.x - 0.5f); + contentSize.x = viewBounds.size.x; + } + if (excess.y > 0) + { + contentPos.y -= excess.y * (contentPivot.y - 0.5f); + contentSize.y = viewBounds.size.y; + } + } + + private readonly Vector3[] m_Corners = new Vector3[4]; + private Bounds GetBounds() + { + if (m_Content == null) + return new Bounds(); + m_Content.GetWorldCorners(m_Corners); + var viewWorldToLocalMatrix = viewRect.worldToLocalMatrix; + return InternalGetBounds(m_Corners, ref viewWorldToLocalMatrix); + } + + internal static Bounds InternalGetBounds(Vector3[] corners, ref Matrix4x4 viewWorldToLocalMatrix) + { + var vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); + var vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue); + + for (int j = 0; j < 4; j++) + { + Vector3 v = viewWorldToLocalMatrix.MultiplyPoint3x4(corners[j]); + vMin = Vector3.Min(v, vMin); + vMax = Vector3.Max(v, vMax); + } + + var bounds = new Bounds(vMin, Vector3.zero); + bounds.Encapsulate(vMax); + return bounds; + } + + private Vector2 CalculateOffset(Vector2 delta) + { + return InternalCalculateOffset(ref m_ViewBounds, ref m_ContentBounds, m_Horizontal, m_Vertical, m_MovementType, ref delta); + } + + internal static Vector2 InternalCalculateOffset(ref Bounds viewBounds, ref Bounds contentBounds, bool horizontal, bool vertical, MovementType movementType, ref Vector2 delta) + { + Vector2 offset = Vector2.zero; + if (movementType == MovementType.Unrestricted) + return offset; + + Vector2 min = contentBounds.min; + Vector2 max = contentBounds.max; + + if (horizontal) + { + min.x += delta.x; + max.x += delta.x; + if (min.x > viewBounds.min.x) + offset.x = viewBounds.min.x - min.x; + else if (max.x < viewBounds.max.x) + offset.x = viewBounds.max.x - max.x; + } + + if (vertical) + { + min.y += delta.y; + max.y += delta.y; + if (max.y < viewBounds.max.y) + offset.y = viewBounds.max.y - max.y; + else if (min.y > viewBounds.min.y) + offset.y = viewBounds.min.y - min.y; + } + + return offset; + } + + protected void SetDirty() + { + if (!IsActive()) + return; + + LayoutRebuilder.MarkLayoutForRebuild(rectTransform); + } + + protected void SetDirtyCaching() + { + if (!IsActive()) + return; + + CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this); + LayoutRebuilder.MarkLayoutForRebuild(rectTransform); + } + + #if UNITY_EDITOR + protected override void OnValidate() + { + SetDirtyCaching(); + } + + #endif + } +} diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ScrollRect.cs.meta b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ScrollRect.cs.meta new file mode 100644 index 0000000..98d9f63 --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ScrollRect.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 209674c43848ab24e8778e3f7bcd0f11 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Scrollbar.cs b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Scrollbar.cs new file mode 100644 index 0000000..c280305 --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Scrollbar.cs @@ -0,0 +1,422 @@ +using System; +using System.Collections; +using UnityEngine.Events; +using UnityEngine.EventSystems; + +namespace UnityEngine.UI +{ + [AddComponentMenu("UI/Scrollbar", 34)] + [RequireComponent(typeof(RectTransform))] + public class Scrollbar + : Selectable + , IBeginDragHandler + , IDragHandler + , IInitializePotentialDragHandler + , ICanvasElement + { + public enum Direction + { + LeftToRight, + RightToLeft, + BottomToTop, + TopToBottom, + } + + [Serializable] + public class ScrollEvent : UnityEvent<float> {} + + [SerializeField] + private RectTransform m_HandleRect; + public RectTransform handleRect { get { return m_HandleRect; } set { if (SetPropertyUtility.SetClass(ref m_HandleRect, value)) { UpdateCachedReferences(); UpdateVisuals(); } } } + + // Direction of movement. + [SerializeField] + private Direction m_Direction = Direction.LeftToRight; + public Direction direction { get { return m_Direction; } set { if (SetPropertyUtility.SetStruct(ref m_Direction, value)) UpdateVisuals(); } } + + protected Scrollbar() + {} + + // Scroll bar's current value in 0 to 1 range. + [Range(0f, 1f)] + [SerializeField] + private float m_Value; + public float value + { + get + { + float val = m_Value; + if (m_NumberOfSteps > 1) + val = Mathf.Round(val * (m_NumberOfSteps - 1)) / (m_NumberOfSteps - 1); + return val; + } + set + { + Set(value); + } + } + + // Scroll bar's current size in 0 to 1 range. + [Range(0f, 1f)] + [SerializeField] + private float m_Size = 0.2f; + public float size { get { return m_Size; } set { if (SetPropertyUtility.SetStruct(ref m_Size, Mathf.Clamp01(value))) UpdateVisuals(); } } + + // Number of steps the scroll bar should be divided into. For example 5 means possible values of 0, 0.25, 0.5, 0.75, and 1.0. + [Range(0, 11)] + [SerializeField] + private int m_NumberOfSteps = 0; + public int numberOfSteps { get { return m_NumberOfSteps; } set { if (SetPropertyUtility.SetStruct(ref m_NumberOfSteps, value)) { Set(m_Value); UpdateVisuals(); } } } + + [Space(6)] + + // Allow for delegate-based subscriptions for faster events than 'eventReceiver', and allowing for multiple receivers. + [SerializeField] + private ScrollEvent m_OnValueChanged = new ScrollEvent(); + public ScrollEvent onValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } } + + // Private fields + + private RectTransform m_ContainerRect; + + // The offset from handle position to mouse down position + private Vector2 m_Offset = Vector2.zero; + + // Size of each step. + float stepSize { get { return (m_NumberOfSteps > 1) ? 1f / (m_NumberOfSteps - 1) : 0.1f; } } + + private DrivenRectTransformTracker m_Tracker; + private Coroutine m_PointerDownRepeat; + private bool isPointerDownAndNotDragging = false; + +#if UNITY_EDITOR + protected override void OnValidate() + { + base.OnValidate(); + + m_Size = Mathf.Clamp01(m_Size); + + //This can be invoked before OnEnabled is called. So we shouldn't be accessing other objects, before OnEnable is called. + if (IsActive()) + { + UpdateCachedReferences(); + Set(m_Value, false); + // Update rects since other things might affect them even if value didn't change. + UpdateVisuals(); + } + + var prefabType = UnityEditor.PrefabUtility.GetPrefabType(this); + if (prefabType != UnityEditor.PrefabType.Prefab && !Application.isPlaying) + CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this); + } + +#endif // if UNITY_EDITOR + + public virtual void Rebuild(CanvasUpdate executing) + { +#if UNITY_EDITOR + if (executing == CanvasUpdate.Prelayout) + onValueChanged.Invoke(value); +#endif + } + + public virtual void LayoutComplete() + {} + + public virtual void GraphicUpdateComplete() + {} + + protected override void OnEnable() + { + base.OnEnable(); + UpdateCachedReferences(); + Set(m_Value, false); + // Update rects since they need to be initialized correctly. + UpdateVisuals(); + } + + protected override void OnDisable() + { + m_Tracker.Clear(); + base.OnDisable(); + } + + void UpdateCachedReferences() + { + if (m_HandleRect && m_HandleRect.parent != null) + m_ContainerRect = m_HandleRect.parent.GetComponent<RectTransform>(); + else + m_ContainerRect = null; + } + + // Update the visible Image. + void Set(float input) + { + Set(input, true); + } + + void Set(float input, bool sendCallback) + { + float currentValue = m_Value; + // Clamp the input + m_Value = Mathf.Clamp01(input); + + // If the stepped value doesn't match the last one, it's time to update + if (currentValue == value) + return; + + UpdateVisuals(); + if (sendCallback) + { + UISystemProfilerApi.AddMarker("Scrollbar.value", this); + m_OnValueChanged.Invoke(value); + } + } + + protected override void OnRectTransformDimensionsChange() + { + base.OnRectTransformDimensionsChange(); + + //This can be invoked before OnEnabled is called. So we shouldn't be accessing other objects, before OnEnable is called. + if (!IsActive()) + return; + + UpdateVisuals(); + } + + enum Axis + { + Horizontal = 0, + Vertical = 1 + } + + Axis axis { get { return (m_Direction == Direction.LeftToRight || m_Direction == Direction.RightToLeft) ? Axis.Horizontal : Axis.Vertical; } } + bool reverseValue { get { return m_Direction == Direction.RightToLeft || m_Direction == Direction.TopToBottom; } } + + // Force-update the scroll bar. Useful if you've changed the properties and want it to update visually. + private void UpdateVisuals() + { +#if UNITY_EDITOR + if (!Application.isPlaying) + UpdateCachedReferences(); +#endif + m_Tracker.Clear(); + + if (m_ContainerRect != null) + { + m_Tracker.Add(this, m_HandleRect, DrivenTransformProperties.Anchors); + Vector2 anchorMin = Vector2.zero; + Vector2 anchorMax = Vector2.one; + + float movement = value * (1 - size); + if (reverseValue) + { + anchorMin[(int)axis] = 1 - movement - size; + anchorMax[(int)axis] = 1 - movement; + } + else + { + anchorMin[(int)axis] = movement; + anchorMax[(int)axis] = movement + size; + } + + m_HandleRect.anchorMin = anchorMin; + m_HandleRect.anchorMax = anchorMax; + } + } + + // Update the scroll bar's position based on the mouse. + void UpdateDrag(PointerEventData eventData) + { + if (eventData.button != PointerEventData.InputButton.Left) + return; + + if (m_ContainerRect == null) + return; + + Vector2 localCursor; + if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(m_ContainerRect, eventData.position, eventData.pressEventCamera, out localCursor)) + return; + + Vector2 handleCenterRelativeToContainerCorner = localCursor - m_Offset - m_ContainerRect.rect.position; + Vector2 handleCorner = handleCenterRelativeToContainerCorner - (m_HandleRect.rect.size - m_HandleRect.sizeDelta) * 0.5f; + + float parentSize = axis == 0 ? m_ContainerRect.rect.width : m_ContainerRect.rect.height; + float remainingSize = parentSize * (1 - size); + if (remainingSize <= 0) + return; + + switch (m_Direction) + { + case Direction.LeftToRight: + Set(handleCorner.x / remainingSize); + break; + case Direction.RightToLeft: + Set(1f - (handleCorner.x / remainingSize)); + break; + case Direction.BottomToTop: + Set(handleCorner.y / remainingSize); + break; + case Direction.TopToBottom: + Set(1f - (handleCorner.y / remainingSize)); + break; + } + } + + private bool MayDrag(PointerEventData eventData) + { + return IsActive() && IsInteractable() && eventData.button == PointerEventData.InputButton.Left; + } + + public virtual void OnBeginDrag(PointerEventData eventData) + { + isPointerDownAndNotDragging = false; + + if (!MayDrag(eventData)) + return; + + if (m_ContainerRect == null) + return; + + m_Offset = Vector2.zero; + if (RectTransformUtility.RectangleContainsScreenPoint(m_HandleRect, eventData.position, eventData.enterEventCamera)) + { + Vector2 localMousePos; + if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_HandleRect, eventData.position, eventData.pressEventCamera, out localMousePos)) + m_Offset = localMousePos - m_HandleRect.rect.center; + } + } + + public virtual void OnDrag(PointerEventData eventData) + { + if (!MayDrag(eventData)) + return; + + if (m_ContainerRect != null) + UpdateDrag(eventData); + } + + public override void OnPointerDown(PointerEventData eventData) + { + if (!MayDrag(eventData)) + return; + + base.OnPointerDown(eventData); + isPointerDownAndNotDragging = true; + m_PointerDownRepeat = StartCoroutine(ClickRepeat(eventData)); + } + + protected IEnumerator ClickRepeat(PointerEventData eventData) + { + while (isPointerDownAndNotDragging) + { + if (!RectTransformUtility.RectangleContainsScreenPoint(m_HandleRect, eventData.position, eventData.enterEventCamera)) + { + Vector2 localMousePos; + if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_HandleRect, eventData.position, eventData.pressEventCamera, out localMousePos)) + { + var axisCoordinate = axis == 0 ? localMousePos.x : localMousePos.y; + if (axisCoordinate < 0) + value -= size; + else + value += size; + } + } + yield return new WaitForEndOfFrame(); + } + StopCoroutine(m_PointerDownRepeat); + } + + public override void OnPointerUp(PointerEventData eventData) + { + base.OnPointerUp(eventData); + isPointerDownAndNotDragging = false; + } + + public override void OnMove(AxisEventData eventData) + { + if (!IsActive() || !IsInteractable()) + { + base.OnMove(eventData); + return; + } + + switch (eventData.moveDir) + { + case MoveDirection.Left: + if (axis == Axis.Horizontal && FindSelectableOnLeft() == null) + Set(reverseValue ? value + stepSize : value - stepSize); + else + base.OnMove(eventData); + break; + case MoveDirection.Right: + if (axis == Axis.Horizontal && FindSelectableOnRight() == null) + Set(reverseValue ? value - stepSize : value + stepSize); + else + base.OnMove(eventData); + break; + case MoveDirection.Up: + if (axis == Axis.Vertical && FindSelectableOnUp() == null) + Set(reverseValue ? value - stepSize : value + stepSize); + else + base.OnMove(eventData); + break; + case MoveDirection.Down: + if (axis == Axis.Vertical && FindSelectableOnDown() == null) + Set(reverseValue ? value + stepSize : value - stepSize); + else + base.OnMove(eventData); + break; + } + } + + public override Selectable FindSelectableOnLeft() + { + if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Horizontal) + return null; + return base.FindSelectableOnLeft(); + } + + public override Selectable FindSelectableOnRight() + { + if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Horizontal) + return null; + return base.FindSelectableOnRight(); + } + + public override Selectable FindSelectableOnUp() + { + if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Vertical) + return null; + return base.FindSelectableOnUp(); + } + + public override Selectable FindSelectableOnDown() + { + if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Vertical) + return null; + return base.FindSelectableOnDown(); + } + + public virtual void OnInitializePotentialDrag(PointerEventData eventData) + { + eventData.useDragThreshold = false; + } + + public void SetDirection(Direction direction, bool includeRectLayouts) + { + Axis oldAxis = axis; + bool oldReverse = reverseValue; + this.direction = direction; + + if (!includeRectLayouts) + return; + + if (axis != oldAxis) + RectTransformUtility.FlipLayoutAxes(transform as RectTransform, true, true); + + if (reverseValue != oldReverse) + RectTransformUtility.FlipLayoutOnAxis(transform as RectTransform, (int)axis, true, true); + } + } +} diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Scrollbar.cs.meta b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Scrollbar.cs.meta new file mode 100644 index 0000000..04b16c4 --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Scrollbar.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 340d739069b7eae4daa7634ca999bcf8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Selectable.cs b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Selectable.cs new file mode 100644 index 0000000..9cc6178 --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Selectable.cs @@ -0,0 +1,672 @@ +using System; +using System.Collections.Generic; +using UnityEngine.Serialization; +using UnityEngine.EventSystems; + +namespace UnityEngine.UI +{ + // Simple selectable object - derived from to create a control. + [AddComponentMenu("UI/Selectable", 70)] + [ExecuteInEditMode] + [SelectionBase] + [DisallowMultipleComponent] + public class Selectable + : + UIBehaviour, + IMoveHandler, // Input>Horizontal\Vertical移动时收到这个消息 + IPointerDownHandler, IPointerUpHandler, // 点击click + IPointerEnterHandler, IPointerExitHandler, // 悬停hover + ISelectHandler, IDeselectHandler // navigation获得焦点时收到这个消息 + { + // Selection state + + // 当前场景中的Selectable组件 + // List of all the selectable objects currently active in the scene + private static List<Selectable> s_List = new List<Selectable>(); + public static List<Selectable> allSelectables { get { return s_List; } } + + // Navigation information. + [FormerlySerializedAs("navigation")] + [SerializeField] + private Navigation m_Navigation = Navigation.defaultNavigation; + + // Highlighting state + public enum Transition + { + None, + ColorTint, + SpriteSwap, + Animation + } + + // Type of the transition that occurs when the button state changes. + [FormerlySerializedAs("transition")] + [SerializeField] + private Transition m_Transition = Transition.ColorTint; + + // Colors used for a color tint-based transition. + [FormerlySerializedAs("colors")] + [SerializeField] + private ColorBlock m_Colors = ColorBlock.defaultColorBlock; + + // Sprites used for a Image swap-based transition. + [FormerlySerializedAs("spriteState")] + [SerializeField] + private SpriteState m_SpriteState; + + [FormerlySerializedAs("animationTriggers")] + [SerializeField] + private AnimationTriggers m_AnimationTriggers = new AnimationTriggers(); + + [Tooltip("Can the Selectable be interacted with?")] + [SerializeField] + private bool m_Interactable = true; + + //c m_TargetGraphic 只是用来做展示,这个变量不会用来进行射线检测,在Awake()里注释掉也还会触发点击事件 + // Graphic that will be colored. + [FormerlySerializedAs("highlightGraphic")] + [FormerlySerializedAs("m_HighlightGraphic")] + [SerializeField] + private Graphic m_TargetGraphic; + + + private bool m_GroupsAllowInteraction = true; + + private SelectionState m_CurrentSelectionState; + + public Navigation navigation { get { return m_Navigation; } set { if (SetPropertyUtility.SetStruct(ref m_Navigation, value)) OnSetProperty(); } } + public Transition transition { get { return m_Transition; } set { if (SetPropertyUtility.SetStruct(ref m_Transition, value)) OnSetProperty(); } } + public ColorBlock colors { get { return m_Colors; } set { if (SetPropertyUtility.SetStruct(ref m_Colors, value)) OnSetProperty(); } } + public SpriteState spriteState { get { return m_SpriteState; } set { if (SetPropertyUtility.SetStruct(ref m_SpriteState, value)) OnSetProperty(); } } + public AnimationTriggers animationTriggers { get { return m_AnimationTriggers; } set { if (SetPropertyUtility.SetClass(ref m_AnimationTriggers, value)) OnSetProperty(); } } + public Graphic targetGraphic { + get { + return m_TargetGraphic; + } + set { + if (SetPropertyUtility.SetClass(ref m_TargetGraphic, value)) + OnSetProperty(); + } + } + public bool interactable + { + get { return m_Interactable; } + set + { + if (SetPropertyUtility.SetStruct(ref m_Interactable, value)) + { + if (!m_Interactable && EventSystem.current != null && EventSystem.current.currentSelectedGameObject == gameObject) + EventSystem.current.SetSelectedGameObject(null); + if (m_Interactable) + UpdateSelectionState(null); + OnSetProperty(); + } + } + } + + private bool isPointerInside { get; set; } + private bool isPointerDown { get; set; } + private bool hasSelection { get; set; } + + protected Selectable() + {} + + // Convenience function that converts the Graphic to a Image, if possible + public Image image + { + get { + return m_TargetGraphic as Image; + } + set { + m_TargetGraphic = value; + } + } + + // Get the animator + public Animator animator + { + get { return GetComponent<Animator>(); } + } + + protected override void Awake() + { + if (m_TargetGraphic == null) + m_TargetGraphic = GetComponent<Graphic>(); + } + + private readonly List<CanvasGroup> m_CanvasGroupCache = new List<CanvasGroup>(); + protected override void OnCanvasGroupChanged() + { + // Figure out if parent groups allow interaction + // If no interaction is alowed... then we need + // to not do that :) + var groupAllowInteraction = true; + Transform t = transform; + while (t != null) + { + t.GetComponents(m_CanvasGroupCache); + bool shouldBreak = false; + for (var i = 0; i < m_CanvasGroupCache.Count; i++) + { + // if the parent group does not allow interaction + // we need to break + if (!m_CanvasGroupCache[i].interactable) + { + groupAllowInteraction = false; + shouldBreak = true; + } + // if this is a 'fresh' group, then break + // as we should not consider parents + if (m_CanvasGroupCache[i].ignoreParentGroups) + shouldBreak = true; + } + if (shouldBreak) + break; + + t = t.parent; + } + + if (groupAllowInteraction != m_GroupsAllowInteraction) + { + m_GroupsAllowInteraction = groupAllowInteraction; + OnSetProperty(); + } + } + + public virtual bool IsInteractable() + { + return m_GroupsAllowInteraction && m_Interactable; + } + + // Call from unity if animation properties have changed + protected override void OnDidApplyAnimationProperties() + { + OnSetProperty(); + } + + // Select on enable and add to the list. + protected override void OnEnable() + { + base.OnEnable(); + + s_List.Add(this); + var state = SelectionState.Normal; + + // The button will be highlighted even in some cases where it shouldn't. + // For example: We only want to set the State as Highlighted if the StandaloneInputModule.m_CurrentInputMode == InputMode.Buttons + // But we dont have access to this, and it might not apply to other InputModules. + // TODO: figure out how to solve this. Case 617348. + if (hasSelection) + state = SelectionState.Highlighted; + + m_CurrentSelectionState = state; + InternalEvaluateAndTransitionToSelectionState(true); + } + + private void OnSetProperty() + { +#if UNITY_EDITOR + if (!Application.isPlaying) + InternalEvaluateAndTransitionToSelectionState(true); + else +#endif + InternalEvaluateAndTransitionToSelectionState(false); + } + + // Remove from the list. + protected override void OnDisable() + { + s_List.Remove(this); + InstantClearState(); + base.OnDisable(); + } + +#if UNITY_EDITOR + protected override void OnValidate() + { + base.OnValidate(); + m_Colors.fadeDuration = Mathf.Max(m_Colors.fadeDuration, 0.0f); + + // OnValidate can be called before OnEnable, this makes it unsafe to access other components + // since they might not have been initialized yet. + // OnSetProperty potentially access Animator or Graphics. (case 618186) + if (isActiveAndEnabled) + { + if (!interactable && EventSystem.current != null && EventSystem.current.currentSelectedGameObject == gameObject) + EventSystem.current.SetSelectedGameObject(null); + // Need to clear out the override image on the target... + DoSpriteSwap(null); + + // If the transition mode got changed, we need to clear all the transitions, since we don't know what the old transition mode was. + StartColorTween(Color.white, true); + TriggerAnimation(m_AnimationTriggers.normalTrigger); + + // And now go to the right state. + InternalEvaluateAndTransitionToSelectionState(true); + } + } + + protected override void Reset() + { + m_TargetGraphic = GetComponent<Graphic>(); + } + +#endif // if UNITY_EDITOR + + protected SelectionState currentSelectionState + { + get { return m_CurrentSelectionState; } + } + + protected virtual void InstantClearState() + { + string triggerName = m_AnimationTriggers.normalTrigger; + + isPointerInside = false; + isPointerDown = false; + hasSelection = false; + + switch (m_Transition) + { + case Transition.ColorTint: + StartColorTween(Color.white, true); + break; + case Transition.SpriteSwap: + DoSpriteSwap(null); + break; + case Transition.Animation: + TriggerAnimation(triggerName); + break; + } + } + + protected virtual void DoStateTransition(SelectionState state, bool instant) + { + Color tintColor; + Sprite transitionSprite; + string triggerName; + + switch (state) + { + case SelectionState.Normal: + tintColor = m_Colors.normalColor; + transitionSprite = null; + triggerName = m_AnimationTriggers.normalTrigger; + break; + case SelectionState.Highlighted: + tintColor = m_Colors.highlightedColor; + transitionSprite = m_SpriteState.highlightedSprite; + triggerName = m_AnimationTriggers.highlightedTrigger; + break; + case SelectionState.Pressed: + tintColor = m_Colors.pressedColor; + transitionSprite = m_SpriteState.pressedSprite; + triggerName = m_AnimationTriggers.pressedTrigger; + break; + case SelectionState.Disabled: + tintColor = m_Colors.disabledColor; + transitionSprite = m_SpriteState.disabledSprite; + triggerName = m_AnimationTriggers.disabledTrigger; + break; + default: + tintColor = Color.black; + transitionSprite = null; + triggerName = string.Empty; + break; + } + + if (gameObject.activeInHierarchy) + { + switch (m_Transition) + { + case Transition.ColorTint: + StartColorTween(tintColor * m_Colors.colorMultiplier, instant); + break; + case Transition.SpriteSwap: + DoSpriteSwap(transitionSprite); + break; + case Transition.Animation: + TriggerAnimation(triggerName); + break; + } + } + } + + protected enum SelectionState + { + Normal, + Highlighted, + Pressed, + Disabled + } + + // Selection logic + + // Find the next selectable object in the specified world-space direction. + public Selectable FindSelectable(Vector3 dir) + { + dir = dir.normalized; + Vector3 localDir = Quaternion.Inverse(transform.rotation) * dir; + Vector3 pos = transform.TransformPoint(GetPointOnRectEdge(transform as RectTransform, localDir)); + float maxScore = Mathf.NegativeInfinity; + Selectable bestPick = null; + for (int i = 0; i < s_List.Count; ++i) // 遍历当前场景中的所有selectable组件 + { + Selectable sel = s_List[i]; + + if (sel == this || sel == null) + continue; + + if (!sel.IsInteractable() || sel.navigation.mode == Navigation.Mode.None) + continue; + + var selRect = sel.transform as RectTransform; + Vector3 selCenter = selRect != null ? (Vector3)selRect.rect.center : Vector3.zero; + Vector3 myVector = sel.transform.TransformPoint(selCenter) - pos; + + // Value that is the distance out along the direction. + float dot = Vector3.Dot(dir, myVector); + + // Skip elements that are in the wrong direction or which have zero distance. + // This also ensures that the scoring formula below will not have a division by zero error. + if (dot <= 0) + continue; + + // This scoring function has two priorities: + // - Score higher for positions that are closer. + // - Score higher for positions that are located in the right direction. + // This scoring function combines both of these criteria. + // It can be seen as this: + // Dot (dir, myVector.normalized) / myVector.magnitude + // The first part equals 1 if the direction of myVector is the same as dir, and 0 if it's orthogonal. + // The second part scores lower the greater the distance is by dividing by the distance. + // The formula below is equivalent but more optimized. + // + // If a given score is chosen, the positions that evaluate to that score will form a circle + // that touches pos and whose center is located along dir. A way to visualize the resulting functionality is this: + // From the position pos, blow up a circular balloon so it grows in the direction of dir. + // The first Selectable whose center the circular balloon touches is the one that's chosen. + float score = dot / myVector.sqrMagnitude; + + if (score > maxScore) + { + maxScore = score; + bestPick = sel; + } + } + return bestPick; + } + + private static Vector3 GetPointOnRectEdge(RectTransform rect, Vector2 dir) + { + if (rect == null) + return Vector3.zero; + if (dir != Vector2.zero) + dir /= Mathf.Max(Mathf.Abs(dir.x), Mathf.Abs(dir.y)); + dir = rect.rect.center + Vector2.Scale(rect.rect.size, dir * 0.5f); + return dir; + } + + // Convenience function -- change the selection to the specified object if it's not null and happens to be active. + void Navigate(AxisEventData eventData, Selectable sel) + { + if (sel != null && sel.IsActive()) + eventData.selectedObject = sel.gameObject; // 会发送一个selectHandler事件 + } + + // Find the selectable object to the left of this one. + public virtual Selectable FindSelectableOnLeft() + { + if (m_Navigation.mode == Navigation.Mode.Explicit) + { + return m_Navigation.selectOnLeft; + } + if ((m_Navigation.mode & Navigation.Mode.Horizontal) != 0) + { + return FindSelectable(transform.rotation * Vector3.left); + } + return null; + } + + // Find the selectable object to the right of this one. + public virtual Selectable FindSelectableOnRight() + { + if (m_Navigation.mode == Navigation.Mode.Explicit) + { + return m_Navigation.selectOnRight; + } + if ((m_Navigation.mode & Navigation.Mode.Horizontal) != 0) + { + return FindSelectable(transform.rotation * Vector3.right); + } + return null; + } + + // Find the selectable object above this one + public virtual Selectable FindSelectableOnUp() + { + if (m_Navigation.mode == Navigation.Mode.Explicit) + { + return m_Navigation.selectOnUp; + } + if ((m_Navigation.mode & Navigation.Mode.Vertical) != 0) + { + return FindSelectable(transform.rotation * Vector3.up); + } + return null; + } + + // Find the selectable object below this one. + public virtual Selectable FindSelectableOnDown() + { + if (m_Navigation.mode == Navigation.Mode.Explicit) + { + return m_Navigation.selectOnDown; + } + if ((m_Navigation.mode & Navigation.Mode.Vertical) != 0) + { + return FindSelectable(transform.rotation * Vector3.down); + } + return null; + } + + // input>horizontal\vertical产生这个事件, + public virtual void OnMove(AxisEventData eventData) + { + LogHelper.Log("OnMove() " + gameObject.name ); + + switch (eventData.moveDir) + { + case MoveDirection.Right: + Navigate(eventData, FindSelectableOnRight()); // 会向下一个selectableObj发送一个OnSelect消息 + break; + + case MoveDirection.Up: + Navigate(eventData, FindSelectableOnUp()); + break; + + case MoveDirection.Left: + Navigate(eventData, FindSelectableOnLeft()); + break; + + case MoveDirection.Down: + Navigate(eventData, FindSelectableOnDown()); + break; + } + } + + void StartColorTween(Color targetColor, bool instant) + { + if (m_TargetGraphic == null) + return; + + // 起一个协程做tween动画 + m_TargetGraphic.CrossFadeColor(targetColor, instant ? 0f : m_Colors.fadeDuration, true, true); + } + + void DoSpriteSwap(Sprite newSprite) + { + if (image == null) + return; + + image.overrideSprite = newSprite; + } + + void TriggerAnimation(string triggername) + { + if (transition != Transition.Animation || animator == null || !animator.isActiveAndEnabled || !animator.hasBoundPlayables || string.IsNullOrEmpty(triggername)) + return; + + LogHelper.Log("trigger animation"); + + animator.ResetTrigger(m_AnimationTriggers.normalTrigger); + animator.ResetTrigger(m_AnimationTriggers.pressedTrigger); + animator.ResetTrigger(m_AnimationTriggers.highlightedTrigger); + animator.ResetTrigger(m_AnimationTriggers.disabledTrigger); + + animator.SetTrigger(triggername); + } + + // Whether the control should be 'selected'. + protected bool IsHighlighted(BaseEventData eventData) + { + if (!IsActive()) + return false; + + if (IsPressed()) + return false; + + bool selected = hasSelection; + if (eventData is PointerEventData) + { + var pointerData = eventData as PointerEventData; + selected |= + (isPointerDown && !isPointerInside && pointerData.pointerPress == gameObject) // This object pressed, but pointer moved off + || (!isPointerDown && isPointerInside && pointerData.pointerPress == gameObject) // This object pressed, but pointer released over (PointerUp event) + || (!isPointerDown && isPointerInside && pointerData.pointerPress == null); // Nothing pressed, but pointer is over + } + else + { + selected |= isPointerInside; + } + return selected; + } + + [Obsolete("Is Pressed no longer requires eventData", false)] + protected bool IsPressed(BaseEventData eventData) + { + return IsPressed(); + } + + // Whether the control should be pressed. + protected bool IsPressed() + { + if (!IsActive()) + return false; + + return isPointerInside && isPointerDown; + } + + // The current visual state of the control. + protected void UpdateSelectionState(BaseEventData eventData) + { + if (IsPressed()) + { + m_CurrentSelectionState = SelectionState.Pressed; + return; + } + + if (IsHighlighted(eventData)) + { + m_CurrentSelectionState = SelectionState.Highlighted; + return; + } + + m_CurrentSelectionState = SelectionState.Normal; + } + + // 更新状态播放动画 + // Change the button to the correct state + private void EvaluateAndTransitionToSelectionState(BaseEventData eventData) + { + if (!IsActive() || !IsInteractable()) + return; + + UpdateSelectionState(eventData); + InternalEvaluateAndTransitionToSelectionState(false); + } + + private void InternalEvaluateAndTransitionToSelectionState(bool instant) + { + var transitionState = m_CurrentSelectionState; + if (IsActive() && !IsInteractable()) + transitionState = SelectionState.Disabled; + DoStateTransition(transitionState, instant); + } + + public virtual void OnPointerDown(PointerEventData eventData) + { + LogHelper.Log("OnPointerDown() "+ gameObject.name); + + if (eventData.button != PointerEventData.InputButton.Left) + return; + + // Selection tracking + if (IsInteractable() && navigation.mode != Navigation.Mode.None && EventSystem.current != null) + EventSystem.current.SetSelectedGameObject(gameObject, eventData); // 选中这个UI组件 + + isPointerDown = true; + EvaluateAndTransitionToSelectionState(eventData); + } + + public virtual void OnPointerUp(PointerEventData eventData) + { + LogHelper.Log("OnPointerUp() " + gameObject.name); + + if (eventData.button != PointerEventData.InputButton.Left) + return; + + isPointerDown = false; + EvaluateAndTransitionToSelectionState(eventData); + } + + public virtual void OnPointerEnter(PointerEventData eventData) + { + LogHelper.Log("OnPointerEnter() " + gameObject.name); + + isPointerInside = true; + EvaluateAndTransitionToSelectionState(eventData); + } + + public virtual void OnPointerExit(PointerEventData eventData) + { + LogHelper.Log("OnPointerExit() " + gameObject.name); + + isPointerInside = false; + EvaluateAndTransitionToSelectionState(eventData); + } + + // 被选中 + public virtual void OnSelect(BaseEventData eventData) + { + LogHelper.Log("OnSelect() " + gameObject.name); + hasSelection = true; + EvaluateAndTransitionToSelectionState(eventData); + } + + public virtual void OnDeselect(BaseEventData eventData) + { + LogHelper.Log("OnDeselect() " + gameObject.name); + hasSelection = false; + EvaluateAndTransitionToSelectionState(eventData); + } + + public virtual void Select() + { + if (EventSystem.current == null || EventSystem.current.alreadySelecting) + return; + + EventSystem.current.SetSelectedGameObject(gameObject); + } + } +} diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Selectable.cs.meta b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Selectable.cs.meta new file mode 100644 index 0000000..394fcbd --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Selectable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: af75b3d5d7b48814da1f058ff4ae7653 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Slider.cs b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Slider.cs new file mode 100644 index 0000000..4aad4e8 --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Slider.cs @@ -0,0 +1,450 @@ +using System; +using UnityEngine.Events; +using UnityEngine.EventSystems; + +namespace UnityEngine.UI +{ + [AddComponentMenu("UI/Slider", 33)] + [RequireComponent(typeof(RectTransform))] + public class Slider + : Selectable + , IDragHandler + , IInitializePotentialDragHandler // 拖拽之前 + , ICanvasElement // 编辑器下才会用到 + { + public enum Direction + { + LeftToRight, + RightToLeft, + BottomToTop, + TopToBottom, + } + + [Serializable] + public class SliderEvent : UnityEvent<float> {} + + [SerializeField] + private RectTransform m_FillRect; + public RectTransform fillRect { get { return m_FillRect; } set { if (SetPropertyUtility.SetClass(ref m_FillRect, value)) {UpdateCachedReferences(); UpdateVisuals(); } } } + + [SerializeField] + private RectTransform m_HandleRect; + public RectTransform handleRect { get { return m_HandleRect; } set { if (SetPropertyUtility.SetClass(ref m_HandleRect, value)) { UpdateCachedReferences(); UpdateVisuals(); } } } + + [Space] + + [SerializeField] + private Direction m_Direction = Direction.LeftToRight; + public Direction direction { get { return m_Direction; } set { if (SetPropertyUtility.SetStruct(ref m_Direction, value)) UpdateVisuals(); } } + + [SerializeField] + private float m_MinValue = 0; + public float minValue { get { return m_MinValue; } set { if (SetPropertyUtility.SetStruct(ref m_MinValue, value)) { Set(m_Value); UpdateVisuals(); } } } + + [SerializeField] + private float m_MaxValue = 1; + public float maxValue { get { return m_MaxValue; } set { if (SetPropertyUtility.SetStruct(ref m_MaxValue, value)) { Set(m_Value); UpdateVisuals(); } } } + + [SerializeField] + private bool m_WholeNumbers = false; + public bool wholeNumbers { get { return m_WholeNumbers; } set { if (SetPropertyUtility.SetStruct(ref m_WholeNumbers, value)) { Set(m_Value); UpdateVisuals(); } } } + + [SerializeField] + protected float m_Value; + public virtual float value + { + get + { + if (wholeNumbers) + return Mathf.Round(m_Value); + return m_Value; + } + set + { + Set(value); + } + } + + public float normalizedValue + { + get + { + if (Mathf.Approximately(minValue, maxValue)) + return 0; + return Mathf.InverseLerp(minValue, maxValue, value); + } + set + { + this.value = Mathf.Lerp(minValue, maxValue, value); + } + } + + [Space] + + // Allow for delegate-based subscriptions for faster events than 'eventReceiver', and allowing for multiple receivers. + [SerializeField] + private SliderEvent m_OnValueChanged = new SliderEvent(); + public SliderEvent onValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } } + + // Private fields + + private Image m_FillImage; + private Transform m_FillTransform; + private RectTransform m_FillContainerRect; + private Transform m_HandleTransform; + private RectTransform m_HandleContainerRect; + + // The offset from handle position to mouse down position + private Vector2 m_Offset = Vector2.zero; + + private DrivenRectTransformTracker m_Tracker; + + // Size of each step. + float stepSize { get { return wholeNumbers ? 1 : (maxValue - minValue) * 0.1f; } } // 1/10十分之一 + + protected Slider() + {} + +#if UNITY_EDITOR + protected override void OnValidate() + { + base.OnValidate(); + + if (wholeNumbers) + { + m_MinValue = Mathf.Round(m_MinValue); + m_MaxValue = Mathf.Round(m_MaxValue); + } + + //Onvalidate is called before OnEnabled. We need to make sure not to touch any other objects before OnEnable is run. + if (IsActive()) + { + UpdateCachedReferences(); + Set(m_Value, false); + // Update rects since other things might affect them even if value didn't change. + UpdateVisuals(); + } + + var prefabType = UnityEditor.PrefabUtility.GetPrefabType(this); + if (prefabType != UnityEditor.PrefabType.Prefab && !Application.isPlaying) + CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this); + } + +#endif // if UNITY_EDITOR + + public virtual void Rebuild(CanvasUpdate executing) + { +#if UNITY_EDITOR + if (executing == CanvasUpdate.Prelayout) + onValueChanged.Invoke(value); +#endif + } + + public virtual void LayoutComplete() + {} + + public virtual void GraphicUpdateComplete() + {} + + protected override void OnEnable() + { + base.OnEnable(); + UpdateCachedReferences(); + Set(m_Value, false); + // Update rects since they need to be initialized correctly. + UpdateVisuals(); + } + + protected override void OnDisable() + { + m_Tracker.Clear(); + base.OnDisable(); + } + + protected override void OnDidApplyAnimationProperties() + { + // Has value changed? Various elements of the slider have the old normalisedValue assigned, we can use this to perform a comparison. + // We also need to ensure the value stays within min/max. + m_Value = ClampValue(m_Value); + float oldNormalizedValue = normalizedValue; + if (m_FillContainerRect != null) + { + if (m_FillImage != null && m_FillImage.type == Image.Type.Filled) + oldNormalizedValue = m_FillImage.fillAmount; + else + oldNormalizedValue = (reverseValue ? 1 - m_FillRect.anchorMin[(int)axis] : m_FillRect.anchorMax[(int)axis]); + } + else if (m_HandleContainerRect != null) + oldNormalizedValue = (reverseValue ? 1 - m_HandleRect.anchorMin[(int)axis] : m_HandleRect.anchorMin[(int)axis]); + + UpdateVisuals(); + + if (oldNormalizedValue != normalizedValue) + { + UISystemProfilerApi.AddMarker("Slider.value", this); + onValueChanged.Invoke(m_Value); + } + } + + void UpdateCachedReferences() + { + if (m_FillRect) + { + m_FillTransform = m_FillRect.transform; + m_FillImage = m_FillRect.GetComponent<Image>(); + if (m_FillTransform.parent != null) + m_FillContainerRect = m_FillTransform.parent.GetComponent<RectTransform>(); + } + else + { + m_FillContainerRect = null; + m_FillImage = null; + } + + if (m_HandleRect) + { + m_HandleTransform = m_HandleRect.transform; + if (m_HandleTransform.parent != null) + m_HandleContainerRect = m_HandleTransform.parent.GetComponent<RectTransform>(); + } + else + { + m_HandleContainerRect = null; + } + } + + float ClampValue(float input) + { + float newValue = Mathf.Clamp(input, minValue, maxValue); + if (wholeNumbers) + newValue = Mathf.Round(newValue); + return newValue; + } + + // Set the valueUpdate the visible Image. + void Set(float input) + { + Set(input, true); + } + + protected virtual void Set(float input, bool sendCallback) + { + // Clamp the input + float newValue = ClampValue(input); + + // If the stepped value doesn't match the last one, it's time to update + if (m_Value == newValue) + return; + + m_Value = newValue; + UpdateVisuals(); + if (sendCallback) + { + UISystemProfilerApi.AddMarker("Slider.value", this); + m_OnValueChanged.Invoke(newValue); + } + } + + protected override void OnRectTransformDimensionsChange() + { + base.OnRectTransformDimensionsChange(); + + //This can be invoked before OnEnabled is called. So we shouldn't be accessing other objects, before OnEnable is called. + if (!IsActive()) + return; + + UpdateVisuals(); + } + + enum Axis + { + Horizontal = 0, + Vertical = 1 + } + + Axis axis { get { return (m_Direction == Direction.LeftToRight || m_Direction == Direction.RightToLeft) ? Axis.Horizontal : Axis.Vertical; } } + bool reverseValue { get { return m_Direction == Direction.RightToLeft || m_Direction == Direction.TopToBottom; } } + + // Force-update the slider. Useful if you've changed the properties and want it to update visually. + private void UpdateVisuals() + { + LogHelper.Log("UpdateVisuals"); + +#if UNITY_EDITOR + if (!Application.isPlaying) + UpdateCachedReferences(); +#endif + + m_Tracker.Clear(); + + if (m_FillContainerRect != null) + { + m_Tracker.Add(this, m_FillRect, DrivenTransformProperties.Anchors); + Vector2 anchorMin = Vector2.zero; + Vector2 anchorMax = Vector2.one; + + if (m_FillImage != null && m_FillImage.type == Image.Type.Filled) + { + m_FillImage.fillAmount = normalizedValue; + } + else + { + if (reverseValue) + anchorMin[(int)axis] = 1 - normalizedValue; + else + anchorMax[(int)axis] = normalizedValue; + } + + m_FillRect.anchorMin = anchorMin; + m_FillRect.anchorMax = anchorMax; + } + + if (m_HandleContainerRect != null) + { + m_Tracker.Add(this, m_HandleRect, DrivenTransformProperties.Anchors); + Vector2 anchorMin = Vector2.zero; + Vector2 anchorMax = Vector2.one; + anchorMin[(int)axis] = anchorMax[(int)axis] = (reverseValue ? (1 - normalizedValue) : normalizedValue); + m_HandleRect.anchorMin = anchorMin; + m_HandleRect.anchorMax = anchorMax; + } + } + + // Update the slider's position based on the mouse. + void UpdateDrag(PointerEventData eventData, Camera cam) + { + RectTransform clickRect = m_HandleContainerRect ?? m_FillContainerRect; + if (clickRect != null && clickRect.rect.size[(int)axis] > 0) + { + Vector2 localCursor; + if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(clickRect, eventData.position, cam, out localCursor)) + return; + localCursor -= clickRect.rect.position; + + float val = Mathf.Clamp01((localCursor - m_Offset)[(int)axis] / clickRect.rect.size[(int)axis]); + normalizedValue = (reverseValue ? 1f - val : val); + } + } + + private bool MayDrag(PointerEventData eventData) + { + return IsActive() && IsInteractable() && eventData.button == PointerEventData.InputButton.Left; + } + + public override void OnPointerDown(PointerEventData eventData) + { + if (!MayDrag(eventData)) + return; + + base.OnPointerDown(eventData); + + m_Offset = Vector2.zero; + if (m_HandleContainerRect != null && RectTransformUtility.RectangleContainsScreenPoint(m_HandleRect, eventData.position, eventData.enterEventCamera)) + { + Vector2 localMousePos; + if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_HandleRect, eventData.position, eventData.pressEventCamera, out localMousePos)) + m_Offset = localMousePos; + } + else + { + // Outside the slider handle - jump to this point instead + UpdateDrag(eventData, eventData.pressEventCamera); + } + } + + public virtual void OnDrag(PointerEventData eventData) + { + LogHelper.Log("OnDrag() " + gameObject.name); + if (!MayDrag(eventData)) + return; + UpdateDrag(eventData, eventData.pressEventCamera); + } + + public override void OnMove(AxisEventData eventData) + { + if (!IsActive() || !IsInteractable()) + { + base.OnMove(eventData); + return; + } + + switch (eventData.moveDir) + { + case MoveDirection.Left: + if (axis == Axis.Horizontal && FindSelectableOnLeft() == null) + Set(reverseValue ? value + stepSize : value - stepSize); + else + base.OnMove(eventData); + break; + case MoveDirection.Right: + if (axis == Axis.Horizontal && FindSelectableOnRight() == null) + Set(reverseValue ? value - stepSize : value + stepSize); + else + base.OnMove(eventData); + break; + case MoveDirection.Up: + if (axis == Axis.Vertical && FindSelectableOnUp() == null) + Set(reverseValue ? value - stepSize : value + stepSize); + else + base.OnMove(eventData); + break; + case MoveDirection.Down: + if (axis == Axis.Vertical && FindSelectableOnDown() == null) + Set(reverseValue ? value + stepSize : value - stepSize); + else + base.OnMove(eventData); + break; + } + } + + public override Selectable FindSelectableOnLeft() + { + if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Horizontal) + return null; + return base.FindSelectableOnLeft(); + } + + public override Selectable FindSelectableOnRight() + { + if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Horizontal) + return null; + return base.FindSelectableOnRight(); + } + + public override Selectable FindSelectableOnUp() + { + if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Vertical) + return null; + return base.FindSelectableOnUp(); + } + + public override Selectable FindSelectableOnDown() + { + if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Vertical) + return null; + return base.FindSelectableOnDown(); + } + + // + public virtual void OnInitializePotentialDrag(PointerEventData eventData) + { + eventData.useDragThreshold = false; + } + + public void SetDirection(Direction direction, bool includeRectLayouts) + { + Axis oldAxis = axis; + bool oldReverse = reverseValue; + this.direction = direction; + + if (!includeRectLayouts) + return; + + if (axis != oldAxis) + RectTransformUtility.FlipLayoutAxes(transform as RectTransform, true, true); + + if (reverseValue != oldReverse) + RectTransformUtility.FlipLayoutOnAxis(transform as RectTransform, (int)axis, true, true); + } + } +} diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Slider.cs.meta b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Slider.cs.meta new file mode 100644 index 0000000..dce4469 --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Slider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4735d34897dc608419d8f2b983b58420 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Toggle.cs b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Toggle.cs new file mode 100644 index 0000000..f2dc8b9 --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Toggle.cs @@ -0,0 +1,246 @@ +using System; +using UnityEngine.Events; +using UnityEngine.EventSystems; +using UnityEngine.Serialization; + +namespace UnityEngine.UI +{ + /// <summary> + /// Simple toggle -- something that has an 'on' and 'off' states: checkbox, toggle button, radio button, etc. + /// </summary> + [AddComponentMenu("UI/Toggle", 31)] + [RequireComponent(typeof(RectTransform))] + public class Toggle + : Selectable + , IPointerClickHandler + , ISubmitHandler + , ICanvasElement // 缂栬緫鍣ㄤ笅鐢ㄥ埌 + { + public enum ToggleTransition + { + None, + Fade + } + + [Serializable] + public class ToggleEvent : UnityEvent<bool> + {} + + /// <summary> + /// Transition type. + /// </summary> + public ToggleTransition toggleTransition = ToggleTransition.Fade; + + /// <summary> + /// Graphic the toggle should be working with. + /// </summary> + public Graphic graphic; + + // group that this toggle can belong to + [SerializeField] + private ToggleGroup m_Group; + + public ToggleGroup group + { + get { return m_Group; } + set + { + m_Group = value; +#if UNITY_EDITOR + if (Application.isPlaying) +#endif + { + SetToggleGroup(m_Group, true); + PlayEffect(true); + } + } + } + + /// <summary> + /// Allow for delegate-based subscriptions for faster events than 'eventReceiver', and allowing for multiple receivers. + /// </summary> + public ToggleEvent onValueChanged = new ToggleEvent(); + + // Whether the toggle is on + [FormerlySerializedAs("m_IsActive")] + [Tooltip("Is the toggle currently on or off?")] + [SerializeField] + private bool m_IsOn; + + protected Toggle() + {} + +#if UNITY_EDITOR + protected override void OnValidate() + { + base.OnValidate(); + + var prefabType = UnityEditor.PrefabUtility.GetPrefabType(this); + if (prefabType != UnityEditor.PrefabType.Prefab && !Application.isPlaying) + CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this); + } + +#endif // if UNITY_EDITOR + + public virtual void Rebuild(CanvasUpdate executing) + { +#if UNITY_EDITOR + if (executing == CanvasUpdate.Prelayout) + onValueChanged.Invoke(m_IsOn); +#endif + } + + public virtual void LayoutComplete() + {} + + public virtual void GraphicUpdateComplete() + {} + + protected override void OnEnable() + { + base.OnEnable(); + SetToggleGroup(m_Group, false); + PlayEffect(true); + } + + protected override void OnDisable() + { + SetToggleGroup(null, false); + base.OnDisable(); + } + + protected override void OnDidApplyAnimationProperties() + { + // Check if isOn has been changed by the animation. + // Unfortunately there is no way to check if we don锟絫 have a graphic. + if (graphic != null) + { + bool oldValue = !Mathf.Approximately(graphic.canvasRenderer.GetColor().a, 0); + if (m_IsOn != oldValue) + { + m_IsOn = oldValue; + Set(!oldValue); + } + } + + base.OnDidApplyAnimationProperties(); + } + + private void SetToggleGroup(ToggleGroup newGroup, bool setMemberValue) + { + ToggleGroup oldGroup = m_Group; + + // Sometimes IsActive returns false in OnDisable so don't check for it. + // Rather remove the toggle too often than too little. + if (m_Group != null) + m_Group.UnregisterToggle(this); + + // At runtime the group variable should be set but not when calling this method from OnEnable or OnDisable. + // That's why we use the setMemberValue parameter. + if (setMemberValue) + m_Group = newGroup; + + // Only register to the new group if this Toggle is active. + if (newGroup != null && IsActive()) + newGroup.RegisterToggle(this); + + // If we are in a new group, and this toggle is on, notify group. + // Note: Don't refer to m_Group here as it's not guaranteed to have been set. + if (newGroup != null && newGroup != oldGroup && isOn && IsActive()) + newGroup.NotifyToggleOn(this); + } + + /// <summary> + /// Whether the toggle is currently active. + /// </summary> + public bool isOn + { + get { return m_IsOn; } + set + { + Set(value); + } + } + + void Set(bool value) + { + Set(value, true); + } + + void Set(bool value, bool sendCallback) + { + if (m_IsOn == value) + return; + + // if we are in a group and set to true, do group logic + m_IsOn = value; + if (m_Group != null && IsActive()) + { + if (m_IsOn || (!m_Group.AnyTogglesOn() && !m_Group.allowSwitchOff)) + { + m_IsOn = true; + m_Group.NotifyToggleOn(this); + } + } + + // Always send event when toggle is clicked, even if value didn't change + // due to already active toggle in a toggle group being clicked. + // Controls like Dropdown rely on this. + // It's up to the user to ignore a selection being set to the same value it already was, if desired. + PlayEffect(toggleTransition == ToggleTransition.None); + if (sendCallback) + { + UISystemProfilerApi.AddMarker("Toggle.value", this); + onValueChanged.Invoke(m_IsOn); + } + } + + /// <summary> + /// Play the appropriate effect. + /// </summary> + private void PlayEffect(bool instant) + { + if (graphic == null) + return; + +#if UNITY_EDITOR + if (!Application.isPlaying) + graphic.canvasRenderer.SetAlpha(m_IsOn ? 1f : 0f); + else +#endif + graphic.CrossFadeAlpha(m_IsOn ? 1f : 0f, instant ? 0f : 0.1f, true); + } + + /// <summary> + /// Assume the correct visual state. + /// </summary> + protected override void Start() + { + PlayEffect(true); + } + + private void InternalToggle() + { + if (!IsActive() || !IsInteractable()) + return; + + isOn = !isOn; + } + + /// <summary> + /// React to clicks. + /// </summary> + public virtual void OnPointerClick(PointerEventData eventData) + { + if (eventData.button != PointerEventData.InputButton.Left) + return; + + InternalToggle(); + } + + public virtual void OnSubmit(BaseEventData eventData) + { + InternalToggle(); + } + } +} diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Toggle.cs.meta b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Toggle.cs.meta new file mode 100644 index 0000000..b4a15b7 --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Toggle.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 34e74f28b4633794eb61b9c3f6d97b37 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ToggleGroup.cs b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ToggleGroup.cs new file mode 100644 index 0000000..da5e021 --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ToggleGroup.cs @@ -0,0 +1,73 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using UnityEngine.EventSystems; + +namespace UnityEngine.UI +{ + [AddComponentMenu("UI/Toggle Group", 32)] + [DisallowMultipleComponent] + public class ToggleGroup : UIBehaviour + { + [SerializeField] private bool m_AllowSwitchOff = false; + public bool allowSwitchOff { get { return m_AllowSwitchOff; } set { m_AllowSwitchOff = value; } } + + private List<Toggle> m_Toggles = new List<Toggle>(); + + protected ToggleGroup() + {} + + private void ValidateToggleIsInGroup(Toggle toggle) + { + if (toggle == null || !m_Toggles.Contains(toggle)) + throw new ArgumentException(string.Format("Toggle {0} is not part of ToggleGroup {1}", new object[] {toggle, this})); + } + + public void NotifyToggleOn(Toggle toggle) + { + ValidateToggleIsInGroup(toggle); + + // disable all toggles in the group + for (var i = 0; i < m_Toggles.Count; i++) + { + if (m_Toggles[i] == toggle) + continue; + + m_Toggles[i].isOn = false; + } + } + + public void UnregisterToggle(Toggle toggle) + { + if (m_Toggles.Contains(toggle)) + m_Toggles.Remove(toggle); + } + + public void RegisterToggle(Toggle toggle) + { + if (!m_Toggles.Contains(toggle)) + m_Toggles.Add(toggle); + } + + public bool AnyTogglesOn() + { + return m_Toggles.Find(x => x.isOn) != null; + } + + public IEnumerable<Toggle> ActiveToggles() + { + return m_Toggles.Where(x => x.isOn); + } + + public void SetAllTogglesOff() + { + bool oldAllowSwitchOff = m_AllowSwitchOff; + m_AllowSwitchOff = true; + + for (var i = 0; i < m_Toggles.Count; i++) + m_Toggles[i].isOn = false; + + m_AllowSwitchOff = oldAllowSwitchOff; + } + } +} diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ToggleGroup.cs.meta b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ToggleGroup.cs.meta new file mode 100644 index 0000000..0649cf7 --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ToggleGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 10b2fd7b649a75145b1e4a36a3c91dfd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: |