Unity-jump-proj

This commit is contained in:
2024-09-09 11:07:16 +03:00
parent 2c29906bbf
commit fd96a5627d
13707 changed files with 866380 additions and 0 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7f27709c942d91541be1fd6aa5cb3d78
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b3cdabf2f1e76854d8aab5930305d70d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,25 @@
namespace UnityEngine.EventSystems
{
/// <summary>
/// Event Data associated with Axis Events (Controller / Keyboard).
/// </summary>
public class AxisEventData : BaseEventData
{
/// <summary>
/// Raw input vector associated with this event.
/// </summary>
public Vector2 moveVector { get; set; }
/// <summary>
/// MoveDirection for this event.
/// </summary>
public MoveDirection moveDir { get; set; }
public AxisEventData(EventSystem eventSystem)
: base(eventSystem)
{
moveVector = Vector2.zero;
moveDir = MoveDirection.None;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b180fd8310805e44dbbef545d0231418
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,66 @@
namespace UnityEngine.EventSystems
{
/// <summary>
/// A class that can be used for sending simple events via the event system.
/// </summary>
public abstract class AbstractEventData
{
protected bool m_Used;
/// <summary>
/// Reset the event.
/// </summary>
public virtual void Reset()
{
m_Used = false;
}
/// <summary>
/// Use the event.
/// </summary>
/// <remarks>
/// Internally sets a flag that can be checked via used to see if further processing should happen.
/// </remarks>
public virtual void Use()
{
m_Used = true;
}
/// <summary>
/// Is the event used?
/// </summary>
public virtual bool used
{
get { return m_Used; }
}
}
/// <summary>
/// A class that contains the base event data that is common to all event types in the new EventSystem.
/// </summary>
public class BaseEventData : AbstractEventData
{
private readonly EventSystem m_EventSystem;
public BaseEventData(EventSystem eventSystem)
{
m_EventSystem = eventSystem;
}
/// <summary>
/// >A reference to the BaseInputModule that sent this event.
/// </summary>
public BaseInputModule currentInputModule
{
get { return m_EventSystem.currentInputModule; }
}
/// <summary>
/// The object currently considered selected by the EventSystem.
/// </summary>
public GameObject selectedObject
{
get { return m_EventSystem.currentSelectedGameObject; }
set { m_EventSystem.SetSelectedGameObject(value, this); }
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 91f495459b6e34f419ac123740d798b1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,371 @@
using System;
using System.Text;
using System.Collections.Generic;
namespace UnityEngine.EventSystems
{
/// <summary>
/// Each touch event creates one of these containing all the relevant information.
/// </summary>
public class PointerEventData : BaseEventData
{
/// <summary>
/// Input press tracking.
/// </summary>
public enum InputButton
{
/// <summary>
/// Left button
/// </summary>
Left = 0,
/// <summary>
/// Right button.
/// </summary>
Right = 1,
/// <summary>
/// Middle button
/// </summary>
Middle = 2
}
/// <summary>
/// The state of a press for the given frame.
/// </summary>
public enum FramePressState
{
/// <summary>
/// Button was pressed this frame.
/// </summary>
Pressed,
/// <summary>
/// Button was released this frame.
/// </summary>
Released,
/// <summary>
/// Button was pressed and released this frame.
/// </summary>
PressedAndReleased,
/// <summary>
/// Same as last frame.
/// </summary>
NotChanged
}
/// <summary>
/// The object that received 'OnPointerEnter'.
/// </summary>
public GameObject pointerEnter { get; set; }
// The object that received OnPointerDown
private GameObject m_PointerPress;
/// <summary>
/// The raw GameObject for the last press event. This means that it is the 'pressed' GameObject even if it can not receive the press event itself.
/// </summary>
public GameObject lastPress { get; private set; }
/// <summary>
/// The object that the press happened on even if it can not handle the press event.
/// </summary>
public GameObject rawPointerPress { get; set; }
/// <summary>
/// The object that is receiving 'OnDrag'.
/// </summary>
public GameObject pointerDrag { get; set; }
/// <summary>
/// The object that should receive the 'OnPointerClick' event.
/// </summary>
public GameObject pointerClick { get; set; }
/// <summary>
/// RaycastResult associated with the current event.
/// </summary>
public RaycastResult pointerCurrentRaycast { get; set; }
/// <summary>
/// RaycastResult associated with the pointer press.
/// </summary>
public RaycastResult pointerPressRaycast { get; set; }
public List<GameObject> hovered = new List<GameObject>();
/// <summary>
/// Is it possible to click this frame
/// </summary>
public bool eligibleForClick { get; set; }
/// <summary>
/// The index of the display that this pointer event comes from.
/// </summary>
public int displayIndex { get; set; }
/// <summary>
/// Id of the pointer (touch id).
/// </summary>
public int pointerId { get; set; }
/// <summary>
/// Current pointer position.
/// </summary>
public Vector2 position { get; set; }
/// <summary>
/// Pointer delta since last update.
/// </summary>
public Vector2 delta { get; set; }
/// <summary>
/// Position of the press.
/// </summary>
public Vector2 pressPosition { get; set; }
/// <summary>
/// World-space position where a ray cast into the screen hits something
/// </summary>
[Obsolete("Use either pointerCurrentRaycast.worldPosition or pointerPressRaycast.worldPosition")]
public Vector3 worldPosition { get; set; }
/// <summary>
/// World-space normal where a ray cast into the screen hits something
/// </summary>
[Obsolete("Use either pointerCurrentRaycast.worldNormal or pointerPressRaycast.worldNormal")]
public Vector3 worldNormal { get; set; }
/// <summary>
/// The last time a click event was sent. Used for double click
/// </summary>
public float clickTime { get; set; }
/// <summary>
/// Number of clicks in a row.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI;
/// using UnityEngine.EventSystems;// Required when using Event data.
///
/// public class ExampleClass : MonoBehaviour, IPointerDownHandler
/// {
/// public void OnPointerDown(PointerEventData eventData)
/// {
/// //Grab the number of consecutive clicks and assign it to an integer varible.
/// int i = eventData.clickCount;
/// //Display the click count.
/// Debug.Log(i);
/// }
/// }
/// ]]>
///</code>
/// </example>
public int clickCount { get; set; }
/// <summary>
/// The amount of scroll since the last update.
/// </summary>
public Vector2 scrollDelta { get; set; }
/// <summary>
/// Should a drag threshold be used?
/// </summary>
/// <remarks>
/// If you do not want a drag threshold set this to false in IInitializePotentialDragHandler.OnInitializePotentialDrag.
/// </remarks>
public bool useDragThreshold { get; set; }
/// <summary>
/// Is a drag operation currently occuring.
/// </summary>
public bool dragging { get; set; }
/// <summary>
/// The EventSystems.PointerEventData.InputButton for this event.
/// </summary>
public InputButton button { get; set; }
/// <summary>
/// The amount of pressure currently applied by a touch.
/// </summary>
/// <remarks>
/// If the device does not report pressure, the value of this property is 1.0f.
/// </remarks>
/// <seealso cref="UnityEngine.UIElements.IPointerEvent" />
public float pressure { get; set; }
/// <summary>
/// The pressure applied to an additional pressure-sensitive control on the stylus.
/// </summary>
/// <seealso cref="UnityEngine.UIElements.IPointerEvent" />
public float tangentialPressure { get; set; }
/// <summary>
/// The angle of the stylus relative to the surface, in radians
/// </summary>
/// <remarks>
/// A value of 0 indicates that the stylus is parallel to the surface. A value of pi/2 indicates that it is perpendicular to the surface.
/// </remarks>
/// <seealso cref="UnityEngine.UIElements.IPointerEvent" />
public float altitudeAngle { get; set; }
/// <summary>
/// The angle of the stylus relative to the x-axis, in radians.
/// </summary>
/// <remarks>
/// A value of 0 indicates that the stylus is pointed along the x-axis of the device.
/// </remarks>
/// <seealso cref="UnityEngine.UIElements.IPointerEvent" />
public float azimuthAngle { get; set; }
/// <summary>
/// The rotation of the stylus around its axis, in radians.
/// </summary>
/// <seealso cref="UnityEngine.UIElements.IPointerEvent" />
public float twist { get; set; }
/// <summary>
/// Specifies the angle of the pen relative to the X & Y axis, in radians.
/// </summary>
/// <seealso cref="UnityEngine.UIElements.IPointerEvent" />
public Vector2 tilt { get; set; }
/// <summary>
/// Specifies the state of the pen. For example, whether the pen is in contact with the screen or tablet, whether the pen is inverted, and whether buttons are pressed.
/// </summary>
/// <seealso cref="UnityEngine.UIElements.IPointerEvent" />
public PenStatus penStatus { get; set; }
/// <summary>
/// An estimate of the radius of a touch.
/// </summary>
/// <remarks>
/// Add `radiusVariance` to get the maximum touch radius, subtract it to get the minimum touch radius.
/// </remarks>
/// <seealso cref="UnityEngine.UIElements.IPointerEvent" />
public Vector2 radius { get; set; }
/// <summary>
/// The accuracy of the touch radius.
/// </summary>
/// <remarks>
/// Add this value to the radius to get the maximum touch radius, subtract it to get the minimum touch radius.
/// </remarks>
public Vector2 radiusVariance { get; set; }
/// <summary>
/// Specifies in the case of a pointer exit if the pointer has fully exited the area or if it has just entered a child.
/// </summary>
public bool fullyExited { get; set; }
/// <summary>
/// Specifies in the case of a pointer enter if the pointer has entered a new area or if it has just reentered a parent after leaving a child.
/// </summary>
public bool reentered { get; set; }
/// <seealso cref="UnityEngine.UIElements.IPointerEvent" />
public PointerEventData(EventSystem eventSystem) : base(eventSystem)
{
eligibleForClick = false;
displayIndex = 0;
pointerId = -1;
position = Vector2.zero; // Current position of the mouse or touch event
delta = Vector2.zero; // Delta since last update
pressPosition = Vector2.zero; // Delta since the event started being tracked
clickTime = 0.0f; // The last time a click event was sent out (used for double-clicks)
clickCount = 0; // Number of clicks in a row. 2 for a double-click for example.
scrollDelta = Vector2.zero;
useDragThreshold = true;
dragging = false;
button = InputButton.Left;
pressure = 0f;
tangentialPressure = 0f;
altitudeAngle = 0f;
azimuthAngle = 0f;
twist = 0f;
tilt = new Vector2(0f, 0f);
penStatus = PenStatus.None;
radius = Vector2.zero;
radiusVariance = Vector2.zero;
}
/// <summary>
/// Is the pointer moving.
/// </summary>
public bool IsPointerMoving()
{
return delta.sqrMagnitude > 0.0f;
}
/// <summary>
/// Is scroll being used on the input device.
/// </summary>
public bool IsScrolling()
{
return scrollDelta.sqrMagnitude > 0.0f;
}
/// <summary>
/// The camera associated with the last OnPointerEnter event.
/// </summary>
public Camera enterEventCamera
{
get { return pointerCurrentRaycast.module == null ? null : pointerCurrentRaycast.module.eventCamera; }
}
/// <summary>
/// The camera associated with the last OnPointerPress event.
/// </summary>
public Camera pressEventCamera
{
get { return pointerPressRaycast.module == null ? null : pointerPressRaycast.module.eventCamera; }
}
/// <summary>
/// The GameObject that received the OnPointerDown.
/// </summary>
public GameObject pointerPress
{
get { return m_PointerPress; }
set
{
if (m_PointerPress == value)
return;
lastPress = m_PointerPress;
m_PointerPress = value;
}
}
public override string ToString()
{
var sb = new StringBuilder();
sb.AppendLine("<b>Position</b>: " + position);
sb.AppendLine("<b>delta</b>: " + delta);
sb.AppendLine("<b>eligibleForClick</b>: " + eligibleForClick);
sb.AppendLine("<b>pointerEnter</b>: " + pointerEnter);
sb.AppendLine("<b>pointerPress</b>: " + pointerPress);
sb.AppendLine("<b>lastPointerPress</b>: " + lastPress);
sb.AppendLine("<b>pointerDrag</b>: " + pointerDrag);
sb.AppendLine("<b>Use Drag Threshold</b>: " + useDragThreshold);
sb.AppendLine("<b>Current Raycast:</b>");
sb.AppendLine(pointerCurrentRaycast.ToString());
sb.AppendLine("<b>Press Raycast:</b>");
sb.AppendLine(pointerPressRaycast.ToString());
sb.AppendLine("<b>Display Index:</b>");
sb.AppendLine(displayIndex.ToString());
sb.AppendLine("<b>pressure</b>: " + pressure);
sb.AppendLine("<b>tangentialPressure</b>: " + tangentialPressure);
sb.AppendLine("<b>altitudeAngle</b>: " + altitudeAngle);
sb.AppendLine("<b>azimuthAngle</b>: " + azimuthAngle);
sb.AppendLine("<b>twist</b>: " + twist);
sb.AppendLine("<b>tilt</b>: " + tilt);
sb.AppendLine("<b>penStatus</b>: " + penStatus);
sb.AppendLine("<b>radius</b>: " + radius);
sb.AppendLine("<b>radiusVariance</b>: " + radiusVariance);
return sb.ToString();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 75df57ccb1e44c64085399277405e1ca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,14 @@
using System;
namespace UnityEngine.EventSystems
{
[Flags]
/// <summary>
/// Enum that tracks event State.
/// </summary>
public enum EventHandle
{
Unused = 0,
Used = 1
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: db7dd43fe1278a9459cd6036f096b91d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,398 @@
namespace UnityEngine.EventSystems
{
/// <summary>
/// Base class that all EventSystem events inherit from.
/// </summary>
public interface IEventSystemHandler
{
}
/// <summary>
/// Interface to implement if you wish to receive OnPointerMove callbacks.
/// </summary>
/// <remarks>
/// Criteria for this event is implementation dependent. For example see StandAloneInputModule.
/// </remarks>
public interface IPointerMoveHandler : IEventSystemHandler
{
/// <summary>
/// Use this callback to detect pointer move events
/// </summary>
void OnPointerMove(PointerEventData eventData);
}
/// <summary>
/// Interface to implement if you wish to receive OnPointerEnter callbacks.
/// </summary>
/// <remarks>
/// Criteria for this event is implementation dependent. For example see StandAloneInputModule.
/// </remarks>
public interface IPointerEnterHandler : IEventSystemHandler
{
/// <summary>
/// Use this callback to detect pointer enter events
/// </summary>
void OnPointerEnter(PointerEventData eventData);
}
/// <summary>
/// Interface to implement if you wish to receive OnPointerExit callbacks.
/// </summary>
/// <remarks>
/// Criteria for this event is implementation dependent. For example see StandAloneInputModule.
/// </remarks>
public interface IPointerExitHandler : IEventSystemHandler
{
/// <summary>
/// Use this callback to detect pointer exit events
/// </summary>
void OnPointerExit(PointerEventData eventData);
}
/// <summary>
/// Interface to implement if you wish to receive OnPointerDown callbacks.
/// </summary>
/// <remarks>
/// Criteria for this event is implementation dependent. For example see StandAloneInputModule.
/// </remarks>
public interface IPointerDownHandler : IEventSystemHandler
{
/// <summary>
/// Use this callback to detect pointer down events.
/// </summary>
void OnPointerDown(PointerEventData eventData);
}
/// <summary>
/// Interface to implement if you wish to receive OnPointerUp callbacks.
/// Note: In order to receive OnPointerUp callbacks, you must also implement the EventSystems.IPointerDownHandler|IPointerDownHandler interface
/// </summary>
/// <remarks>
/// Criteria for this event is implementation dependent. For example see StandAloneInputModule.
/// </remarks>
public interface IPointerUpHandler : IEventSystemHandler
{
/// <summary>
/// Use this callback to detect pointer up events.
/// </summary>
void OnPointerUp(PointerEventData eventData);
}
/// <summary>
/// Interface to implement if you wish to receive OnPointerClick callbacks.
/// </summary>
/// <remarks>
/// Criteria for this event is implementation dependent. For example see StandAloneInputModule.
/// </remarks>
/// <remarks>
/// Use the IPointerClickHandler Interface to handle click input using OnPointerClick callbacks. Ensure an Event System exists in the Scene to allow click detection. For click detection on non-UI GameObjects, ensure a EventSystems.PhysicsRaycaster is attached to the Camera.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using UnityEngine.EventSystems;
///
/// public class Example : MonoBehaviour, IPointerClickHandler
/// {
/// //Detect if a click occurs
/// public void OnPointerClick(PointerEventData pointerEventData)
/// {
/// //Output to console the clicked GameObject's name and the following message. You can replace this with your own actions for when clicking the GameObject.
/// Debug.Log(name + " Game Object Clicked!");
/// }
/// }
/// ]]>
///</code>
/// </example>
public interface IPointerClickHandler : IEventSystemHandler
{
/// <summary>
/// Use this callback to detect clicks.
/// </summary>
void OnPointerClick(PointerEventData eventData);
}
/// <summary>
/// Interface to implement if you wish to receive OnBeginDrag callbacks.
/// Note: You need to implement IDragHandler in addition to IBeginDragHandler.
/// </summary>
/// <remarks>
/// Criteria for this event is implementation dependent. For example see StandAloneInputModule.
/// </remarks>
public interface IBeginDragHandler : IEventSystemHandler
{
/// <summary>
/// Called by a BaseInputModule before a drag is started.
/// </summary>
void OnBeginDrag(PointerEventData eventData);
}
/// <summary>
/// Interface to implement if you wish to receive OnInitializePotentialDrag callbacks.
/// </summary>
/// <remarks>
/// Criteria for this event is implementation dependent. For example see StandAloneInputModule.
/// </remarks>
public interface IInitializePotentialDragHandler : IEventSystemHandler
{
/// <summary>
/// Called by a BaseInputModule when a drag has been found but before it is valid to begin the drag.
/// </summary>
void OnInitializePotentialDrag(PointerEventData eventData);
}
/// <summary>
/// Interface to implement if you wish to receive OnDrag callbacks.
/// </summary>
/// <remarks>
/// Criteria for this event is implementation dependent. For example see StandAloneInputModule.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using UnityEngine.EventSystems;
/// using UnityEngine.UI;
///
/// [RequireComponent(typeof(Image))]
/// public class DragMe : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
/// {
/// public bool dragOnSurfaces = true;
///
/// private GameObject m_DraggingIcon;
/// private RectTransform m_DraggingPlane;
///
/// public void OnBeginDrag(PointerEventData eventData)
/// {
/// var canvas = FindInParents<Canvas>(gameObject);
/// if (canvas == null)
/// return;
///
/// // We have clicked something that can be dragged.
/// // What we want to do is create an icon for this.
/// m_DraggingIcon = new GameObject("icon");
///
/// m_DraggingIcon.transform.SetParent(canvas.transform, false);
/// m_DraggingIcon.transform.SetAsLastSibling();
///
/// var image = m_DraggingIcon.AddComponent<Image>();
///
/// image.sprite = GetComponent<Image>().sprite;
/// image.SetNativeSize();
///
/// if (dragOnSurfaces)
/// m_DraggingPlane = transform as RectTransform;
/// else
/// m_DraggingPlane = canvas.transform as RectTransform;
///
/// SetDraggedPosition(eventData);
/// }
///
/// public void OnDrag(PointerEventData data)
/// {
/// if (m_DraggingIcon != null)
/// SetDraggedPosition(data);
/// }
///
/// private void SetDraggedPosition(PointerEventData data)
/// {
/// if (dragOnSurfaces && data.pointerEnter != null && data.pointerEnter.transform as RectTransform != null)
/// m_DraggingPlane = data.pointerEnter.transform as RectTransform;
///
/// var rt = m_DraggingIcon.GetComponent<RectTransform>();
/// Vector3 globalMousePos;
/// if (RectTransformUtility.ScreenPointToWorldPointInRectangle(m_DraggingPlane, data.position, data.pressEventCamera, out globalMousePos))
/// {
/// rt.position = globalMousePos;
/// rt.rotation = m_DraggingPlane.rotation;
/// }
/// }
///
/// public void OnEndDrag(PointerEventData eventData)
/// {
/// if (m_DraggingIcon != null)
/// Destroy(m_DraggingIcon);
/// }
///
/// static public T FindInParents<T>(GameObject go) where T : Component
/// {
/// if (go == null) return null;
/// var comp = go.GetComponent<T>();
///
/// if (comp != null)
/// return comp;
///
/// Transform t = go.transform.parent;
/// while (t != null && comp == null)
/// {
/// comp = t.gameObject.GetComponent<T>();
/// t = t.parent;
/// }
/// return comp;
/// }
/// }
/// ]]>
///</code>
/// </example>
public interface IDragHandler : IEventSystemHandler
{
/// <summary>
/// When dragging is occurring this will be called every time the cursor is moved.
/// </summary>
void OnDrag(PointerEventData eventData);
}
/// <summary>
/// Interface to implement if you wish to receive OnEndDrag callbacks.
/// Note: You need to implement IDragHandler in addition to IEndDragHandler.
/// </summary>
/// <remarks>
/// Criteria for this event is implementation dependent. For example see StandAloneInputModule.
/// </remarks>
public interface IEndDragHandler : IEventSystemHandler
{
/// <summary>
/// Called by a BaseInputModule when a drag is ended.
/// </summary>
void OnEndDrag(PointerEventData eventData);
}
/// <summary>
/// Interface to implement if you wish to receive OnDrop callbacks.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using UnityEngine.EventSystems;
///
/// public class DropMe : MonoBehaviour, IDropHandler
/// {
/// public void OnDrop(PointerEventData data)
/// {
/// if (data.pointerDrag != null)
/// {
/// Debug.Log ("Dropped object was: " + data.pointerDrag);
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
/// <remarks>
/// Criteria for this event is implementation dependent. For example see StandAloneInputModule.
/// </remarks>
public interface IDropHandler : IEventSystemHandler
{
/// <summary>
/// Called by a BaseInputModule on a target that can accept a drop.
/// </summary>
void OnDrop(PointerEventData eventData);
}
/// <summary>
/// Interface to implement if you wish to receive OnScroll callbacks.
/// </summary>
/// <remarks>
/// Criteria for this event is implementation dependent. For example see StandAloneInputModule.
/// </remarks>
public interface IScrollHandler : IEventSystemHandler
{
/// <summary>
/// Use this callback to detect scroll events.
/// </summary>
void OnScroll(PointerEventData eventData);
}
/// <summary>
/// Interface to implement if you wish to receive OnUpdateSelected callbacks.
/// </summary>
/// <remarks>
/// Criteria for this event is implementation dependent. For example see StandAloneInputModule.
/// </remarks>
public interface IUpdateSelectedHandler : IEventSystemHandler
{
/// <summary>
/// Called by the EventSystem when the object associated with this EventTrigger is updated.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using UnityEngine.EventSystems;
///
/// public class UpdateSelectedExample : MonoBehaviour, IUpdateSelectedHandler
/// {
/// public void OnUpdateSelected(BaseEventData data)
/// {
/// Debug.Log("OnUpdateSelected called.");
/// }
/// }
/// ]]>
///</code>
/// </example>
void OnUpdateSelected(BaseEventData eventData);
}
/// <summary>
/// Interface to implement if you wish to receive OnSelect callbacks.
/// </summary>
/// <remarks>
/// Criteria for this event is implementation dependent. For example see StandAloneInputModule.
/// </remarks>
public interface ISelectHandler : IEventSystemHandler
{
void OnSelect(BaseEventData eventData);
}
/// <summary>
/// Interface to implement if you wish to receive OnDeselect callbacks.
/// </summary>
/// <remarks>
/// Criteria for this event is implementation dependent. For example see StandAloneInputModule.
/// </remarks>
public interface IDeselectHandler : IEventSystemHandler
{
/// <summary>
/// Called by the EventSystem when a new object is being selected.
/// </summary>
void OnDeselect(BaseEventData eventData);
}
/// <summary>
/// Interface to implement if you wish to receive OnMove callbacks.
/// </summary>
/// <remarks>
/// Criteria for this event is implementation dependent. For example see StandAloneInputModule.
/// </remarks>
public interface IMoveHandler : IEventSystemHandler
{
/// <summary>
/// Called by a BaseInputModule when a move event occurs.
/// </summary>
void OnMove(AxisEventData eventData);
}
/// <summary>
/// Interface to implement if you wish to receive OnSubmit callbacks.
/// </summary>
/// <remarks>
/// Criteria for this event is implementation dependent. For example see StandAloneInputModule.
/// </remarks>
public interface ISubmitHandler : IEventSystemHandler
{
void OnSubmit(BaseEventData eventData);
}
/// <summary>
/// Interface to implement if you wish to receive OnCancel callbacks.
/// </summary>
/// <remarks>
/// Criteria for this event is implementation dependent. For example see StandAloneInputModule.
/// </remarks>
public interface ICancelHandler : IEventSystemHandler
{
void OnCancel(BaseEventData eventData);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2359c66dee10c454c97f6aea84e3fe22
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,571 @@
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Serialization;
using UnityEngine.UIElements;
namespace UnityEngine.EventSystems
{
[AddComponentMenu("Event/Event System")]
[DisallowMultipleComponent]
/// <summary>
/// Handles input, raycasting, and sending events.
/// </summary>
/// <remarks>
/// The EventSystem is responsible for processing and handling events in a Unity scene. A scene should only contain one EventSystem. The EventSystem works in conjunction with a number of modules and mostly just holds state and delegates functionality to specific, overrideable components.
/// When the EventSystem is started it searches for any BaseInputModules attached to the same GameObject and adds them to an internal list. On update each attached module receives an UpdateModules call, where the module can modify internal state. After each module has been Updated the active module has the Process call executed.This is where custom module processing can take place.
/// </remarks>
public class EventSystem : UIBehaviour
{
private List<BaseInputModule> m_SystemInputModules = new List<BaseInputModule>();
private BaseInputModule m_CurrentInputModule;
private static List<EventSystem> m_EventSystems = new List<EventSystem>();
/// <summary>
/// Return the current EventSystem.
/// </summary>
public static EventSystem current
{
get { return m_EventSystems.Count > 0 ? m_EventSystems[0] : null; }
set
{
int index = m_EventSystems.IndexOf(value);
if (index > 0)
{
m_EventSystems.RemoveAt(index);
m_EventSystems.Insert(0, value);
}
else if (index < 0)
{
Debug.LogError("Failed setting EventSystem.current to unknown EventSystem " + value);
}
}
}
[SerializeField]
[FormerlySerializedAs("m_Selected")]
private GameObject m_FirstSelected;
[SerializeField]
private bool m_sendNavigationEvents = true;
/// <summary>
/// Should the EventSystem allow navigation events (move / submit / cancel).
/// </summary>
public bool sendNavigationEvents
{
get { return m_sendNavigationEvents; }
set { m_sendNavigationEvents = value; }
}
[SerializeField]
private int m_DragThreshold = 10;
/// <summary>
/// The soft area for dragging in pixels.
/// </summary>
public int pixelDragThreshold
{
get { return m_DragThreshold; }
set { m_DragThreshold = value; }
}
private GameObject m_CurrentSelected;
/// <summary>
/// The currently active EventSystems.BaseInputModule.
/// </summary>
public BaseInputModule currentInputModule
{
get { return m_CurrentInputModule; }
}
/// <summary>
/// Only one object can be selected at a time. Think: controller-selected button.
/// </summary>
public GameObject firstSelectedGameObject
{
get { return m_FirstSelected; }
set { m_FirstSelected = value; }
}
/// <summary>
/// The GameObject currently considered active by the EventSystem.
/// </summary>
public GameObject currentSelectedGameObject
{
get { return m_CurrentSelected; }
}
[Obsolete("lastSelectedGameObject is no longer supported")]
public GameObject lastSelectedGameObject
{
get { return null; }
}
private bool m_HasFocus = true;
/// <summary>
/// Flag to say whether the EventSystem thinks it should be paused or not based upon focused state.
/// </summary>
/// <remarks>
/// Used to determine inside the individual InputModules if the module should be ticked while the application doesnt have focus.
/// </remarks>
public bool isFocused
{
get { return m_HasFocus; }
}
protected EventSystem()
{}
/// <summary>
/// Recalculate the internal list of BaseInputModules.
/// </summary>
public void UpdateModules()
{
GetComponents(m_SystemInputModules);
var systemInputModulesCount = m_SystemInputModules.Count;
for (int i = systemInputModulesCount - 1; i >= 0; i--)
{
if (m_SystemInputModules[i] && m_SystemInputModules[i].IsActive())
continue;
m_SystemInputModules.RemoveAt(i);
}
}
private bool m_SelectionGuard;
/// <summary>
/// Returns true if the EventSystem is already in a SetSelectedGameObject.
/// </summary>
public bool alreadySelecting
{
get { return m_SelectionGuard; }
}
/// <summary>
/// Set the object as selected. Will send an OnDeselect the the old selected object and OnSelect to the new selected object.
/// </summary>
/// <param name="selected">GameObject to select.</param>
/// <param name="pointer">Associated EventData.</param>
public void SetSelectedGameObject(GameObject selected, BaseEventData pointer)
{
if (m_SelectionGuard)
{
Debug.LogError("Attempting to select " + selected + "while already selecting an object.");
return;
}
m_SelectionGuard = true;
if (selected == m_CurrentSelected)
{
m_SelectionGuard = false;
return;
}
// Debug.Log("Selection: new (" + selected + ") old (" + m_CurrentSelected + ")");
ExecuteEvents.Execute(m_CurrentSelected, pointer, ExecuteEvents.deselectHandler);
m_CurrentSelected = selected;
ExecuteEvents.Execute(m_CurrentSelected, pointer, ExecuteEvents.selectHandler);
m_SelectionGuard = false;
}
private BaseEventData m_DummyData;
private BaseEventData baseEventDataCache
{
get
{
if (m_DummyData == null)
m_DummyData = new BaseEventData(this);
return m_DummyData;
}
}
/// <summary>
/// Set the object as selected. Will send an OnDeselect the the old selected object and OnSelect to the new selected object.
/// </summary>
/// <param name="selected">GameObject to select.</param>
public void SetSelectedGameObject(GameObject selected)
{
SetSelectedGameObject(selected, baseEventDataCache);
}
private static int RaycastComparer(RaycastResult lhs, RaycastResult rhs)
{
if (lhs.module != rhs.module)
{
var lhsEventCamera = lhs.module.eventCamera;
var rhsEventCamera = rhs.module.eventCamera;
if (lhsEventCamera != null && rhsEventCamera != null && lhsEventCamera.depth != rhsEventCamera.depth)
{
// need to reverse the standard compareTo
if (lhsEventCamera.depth < rhsEventCamera.depth)
return 1;
if (lhsEventCamera.depth == rhsEventCamera.depth)
return 0;
return -1;
}
if (lhs.module.sortOrderPriority != rhs.module.sortOrderPriority)
return rhs.module.sortOrderPriority.CompareTo(lhs.module.sortOrderPriority);
if (lhs.module.renderOrderPriority != rhs.module.renderOrderPriority)
return rhs.module.renderOrderPriority.CompareTo(lhs.module.renderOrderPriority);
}
// Renderer sorting
if (lhs.sortingLayer != rhs.sortingLayer)
{
// Uses the layer value to properly compare the relative order of the layers.
var rid = SortingLayer.GetLayerValueFromID(rhs.sortingLayer);
var lid = SortingLayer.GetLayerValueFromID(lhs.sortingLayer);
return rid.CompareTo(lid);
}
if (lhs.sortingOrder != rhs.sortingOrder)
return rhs.sortingOrder.CompareTo(lhs.sortingOrder);
// comparing depth only makes sense if the two raycast results have the same root canvas (case 912396)
if (lhs.depth != rhs.depth && lhs.module.rootRaycaster == rhs.module.rootRaycaster)
return rhs.depth.CompareTo(lhs.depth);
if (lhs.distance != rhs.distance)
return lhs.distance.CompareTo(rhs.distance);
#if PACKAGE_PHYSICS2D
// Sorting group
if (lhs.sortingGroupID != SortingGroup.invalidSortingGroupID && rhs.sortingGroupID != SortingGroup.invalidSortingGroupID)
{
if (lhs.sortingGroupID != rhs.sortingGroupID)
return lhs.sortingGroupID.CompareTo(rhs.sortingGroupID);
if (lhs.sortingGroupOrder != rhs.sortingGroupOrder)
return rhs.sortingGroupOrder.CompareTo(lhs.sortingGroupOrder);
}
#endif
return lhs.index.CompareTo(rhs.index);
}
private static readonly Comparison<RaycastResult> s_RaycastComparer = RaycastComparer;
/// <summary>
/// Raycast into the scene using all configured BaseRaycasters.
/// </summary>
/// <param name="eventData">Current pointer data.</param>
/// <param name="raycastResults">List of 'hits' to populate.</param>
public void RaycastAll(PointerEventData eventData, List<RaycastResult> raycastResults)
{
raycastResults.Clear();
var modules = RaycasterManager.GetRaycasters();
var modulesCount = modules.Count;
for (int i = 0; i < modulesCount; ++i)
{
var module = modules[i];
if (module == null || !module.IsActive())
continue;
module.Raycast(eventData, raycastResults);
}
raycastResults.Sort(s_RaycastComparer);
}
/// <summary>
/// Is the pointer with the given ID over an EventSystem object?
/// </summary>
public bool IsPointerOverGameObject()
{
return IsPointerOverGameObject(PointerInputModule.kMouseLeftId);
}
/// <summary>
/// Is the pointer with the given ID over an EventSystem object?
/// </summary>
/// <remarks>
/// If you use IsPointerOverGameObject() without a parameter, it points to the "left mouse button" (pointerId = -1); therefore when you use IsPointerOverGameObject for touch, you should consider passing a pointerId to it
/// Note that for touch, IsPointerOverGameObject should be used with ''OnMouseDown()'' or ''Input.GetMouseButtonDown(0)'' or ''Input.GetTouch(0).phase == TouchPhase.Began''.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.EventSystems;
///
/// public class MouseExample : MonoBehaviour
/// {
/// void Update()
/// {
/// // Check if the left mouse button was clicked
/// if (Input.GetMouseButtonDown(0))
/// {
/// // Check if the mouse was clicked over a UI element
/// if (EventSystem.current.IsPointerOverGameObject())
/// {
/// Debug.Log("Clicked on the UI");
/// }
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public bool IsPointerOverGameObject(int pointerId)
{
return m_CurrentInputModule != null && m_CurrentInputModule.IsPointerOverGameObject(pointerId);
}
// This code is disabled unless the UI Toolkit package or the com.unity.modules.uielements module are present.
// The UIElements module is always present in the Editor but it can be stripped from a project build if unused.
#if PACKAGE_UITOOLKIT
private struct UIToolkitOverrideConfig
{
public EventSystem activeEventSystem;
public bool sendEvents;
public bool createPanelGameObjectsOnStart;
}
private static UIToolkitOverrideConfig s_UIToolkitOverride = new UIToolkitOverrideConfig
{
activeEventSystem = null,
sendEvents = true,
createPanelGameObjectsOnStart = true
};
private bool isUIToolkitActiveEventSystem =>
s_UIToolkitOverride.activeEventSystem == this || s_UIToolkitOverride.activeEventSystem == null;
private bool sendUIToolkitEvents =>
s_UIToolkitOverride.sendEvents && isUIToolkitActiveEventSystem;
private bool createUIToolkitPanelGameObjectsOnStart =>
s_UIToolkitOverride.createPanelGameObjectsOnStart && isUIToolkitActiveEventSystem;
#endif
/// <summary>
/// Sets how UI Toolkit runtime panels receive events and handle selection
/// when interacting with other objects that use the EventSystem, such as components from the Unity UI package.
/// </summary>
/// <param name="activeEventSystem">
/// The EventSystem used to override UI Toolkit panel events and selection.
/// If activeEventSystem is null, UI Toolkit panels will use current enabled EventSystem
/// or, if there is none, the default InputManager-based event system will be used.
/// </param>
/// <param name="sendEvents">
/// If true, UI Toolkit events will come from this EventSystem
/// instead of the default InputManager-based event system.
/// </param>
/// <param name="createPanelGameObjectsOnStart">
/// If true, UI Toolkit panels' unassigned selectableGameObject will be automatically initialized
/// with children GameObjects of this EventSystem on Start.
/// </param>
public static void SetUITookitEventSystemOverride(EventSystem activeEventSystem, bool sendEvents = true, bool createPanelGameObjectsOnStart = true)
{
#if PACKAGE_UITOOLKIT
UIElementsRuntimeUtility.UnregisterEventSystem(UIElementsRuntimeUtility.activeEventSystem);
s_UIToolkitOverride = new UIToolkitOverrideConfig
{
activeEventSystem = activeEventSystem,
sendEvents = sendEvents,
createPanelGameObjectsOnStart = createPanelGameObjectsOnStart,
};
if (sendEvents)
{
var eventSystem = activeEventSystem != null ? activeEventSystem : EventSystem.current;
if (eventSystem.isActiveAndEnabled)
UIElementsRuntimeUtility.RegisterEventSystem(activeEventSystem);
}
#endif
}
#if PACKAGE_UITOOLKIT
private bool m_Started;
private bool m_IsTrackingUIToolkitPanels;
private void StartTrackingUIToolkitPanels()
{
if (createUIToolkitPanelGameObjectsOnStart)
{
foreach (BaseRuntimePanel panel in UIElementsRuntimeUtility.GetSortedPlayerPanels())
{
CreateUIToolkitPanelGameObject(panel);
}
UIElementsRuntimeUtility.onCreatePanel += CreateUIToolkitPanelGameObject;
m_IsTrackingUIToolkitPanels = true;
}
}
private void StopTrackingUIToolkitPanels()
{
if (m_IsTrackingUIToolkitPanels)
{
UIElementsRuntimeUtility.onCreatePanel -= CreateUIToolkitPanelGameObject;
m_IsTrackingUIToolkitPanels = false;
}
}
private void CreateUIToolkitPanelGameObject(BaseRuntimePanel panel)
{
if (panel.selectableGameObject == null)
{
var go = new GameObject(panel.name, typeof(PanelEventHandler), typeof(PanelRaycaster));
go.transform.SetParent(transform);
panel.selectableGameObject = go;
panel.destroyed += () => DestroyImmediate(go);
}
}
#endif
protected override void Start()
{
base.Start();
#if PACKAGE_UITOOLKIT
m_Started = true;
StartTrackingUIToolkitPanels();
#endif
}
protected override void OnEnable()
{
base.OnEnable();
m_EventSystems.Add(this);
#if PACKAGE_UITOOLKIT
if (m_Started && !m_IsTrackingUIToolkitPanels)
{
StartTrackingUIToolkitPanels();
}
if (sendUIToolkitEvents)
{
UIElementsRuntimeUtility.RegisterEventSystem(this);
}
#endif
}
protected override void OnDisable()
{
#if PACKAGE_UITOOLKIT
StopTrackingUIToolkitPanels();
UIElementsRuntimeUtility.UnregisterEventSystem(this);
#endif
if (m_CurrentInputModule != null)
{
m_CurrentInputModule.DeactivateModule();
m_CurrentInputModule = null;
}
m_EventSystems.Remove(this);
base.OnDisable();
}
private void TickModules()
{
var systemInputModulesCount = m_SystemInputModules.Count;
for (var i = 0; i < systemInputModulesCount; i++)
{
if (m_SystemInputModules[i] != null)
m_SystemInputModules[i].UpdateModule();
}
}
protected virtual void OnApplicationFocus(bool hasFocus)
{
m_HasFocus = hasFocus;
if (!m_HasFocus)
TickModules();
}
protected virtual void Update()
{
if (current != this)
return;
TickModules();
bool changedModule = false;
var systemInputModulesCount = m_SystemInputModules.Count;
for (var i = 0; i < systemInputModulesCount; i++)
{
var module = m_SystemInputModules[i];
if (module.IsModuleSupported() && module.ShouldActivateModule())
{
if (m_CurrentInputModule != module)
{
ChangeEventModule(module);
changedModule = true;
}
break;
}
}
// no event module set... set the first valid one...
if (m_CurrentInputModule == null)
{
for (var i = 0; i < systemInputModulesCount; i++)
{
var module = m_SystemInputModules[i];
if (module.IsModuleSupported())
{
ChangeEventModule(module);
changedModule = true;
break;
}
}
}
if (!changedModule && m_CurrentInputModule != null)
m_CurrentInputModule.Process();
#if UNITY_EDITOR
if (Application.isPlaying)
{
int eventSystemCount = 0;
for (int i = 0; i < m_EventSystems.Count; i++)
{
if (m_EventSystems[i].GetType() == typeof(EventSystem))
eventSystemCount++;
}
if (eventSystemCount > 1)
Debug.LogWarning("There are " + eventSystemCount + " event systems in the scene. Please ensure there is always exactly one event system in the scene");
}
#endif
}
private void ChangeEventModule(BaseInputModule module)
{
if (m_CurrentInputModule == module)
return;
if (m_CurrentInputModule != null)
m_CurrentInputModule.DeactivateModule();
if (module != null)
module.ActivateModule();
m_CurrentInputModule = module;
}
public override string ToString()
{
var sb = new StringBuilder();
sb.AppendLine("<b>Selected:</b>" + currentSelectedGameObject);
sb.AppendLine();
sb.AppendLine();
sb.AppendLine(m_CurrentInputModule != null ? m_CurrentInputModule.ToString() : "No module");
return sb.ToString();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 76c392e42b5098c458856cdf6ecaaaa1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,358 @@
using System;
using System.Collections.Generic;
using UnityEngine.Events;
using UnityEngine.Serialization;
namespace UnityEngine.EventSystems
{
[AddComponentMenu("Event/Event Trigger")]
/// <summary>
/// Receives events from the EventSystem and calls registered functions for each event.
/// </summary>
/// <remarks>
/// The EventTrigger can be used to specify functions you wish to be called for each EventSystem event.
/// You can assign multiple functions to a single event and whenever the EventTrigger receives that event it will call those functions in the order they were provided.
///
/// NOTE: Attaching this component to a GameObject will make that object intercept ALL events, and no events will propagate to parent objects.
/// </remarks>
/// <example>
/// There are two ways to intercept events: You could extend EventTrigger, and override the functions for the events you are interested in intercepting; as shown in this example:
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using UnityEngine.EventSystems;
///
/// public class EventTriggerExample : EventTrigger
/// {
/// public override void OnBeginDrag(PointerEventData data)
/// {
/// Debug.Log("OnBeginDrag called.");
/// }
///
/// public override void OnCancel(BaseEventData data)
/// {
/// Debug.Log("OnCancel called.");
/// }
///
/// public override void OnDeselect(BaseEventData data)
/// {
/// Debug.Log("OnDeselect called.");
/// }
///
/// public override void OnDrag(PointerEventData data)
/// {
/// Debug.Log("OnDrag called.");
/// }
///
/// public override void OnDrop(PointerEventData data)
/// {
/// Debug.Log("OnDrop called.");
/// }
///
/// public override void OnEndDrag(PointerEventData data)
/// {
/// Debug.Log("OnEndDrag called.");
/// }
///
/// public override void OnInitializePotentialDrag(PointerEventData data)
/// {
/// Debug.Log("OnInitializePotentialDrag called.");
/// }
///
/// public override void OnMove(AxisEventData data)
/// {
/// Debug.Log("OnMove called.");
/// }
///
/// public override void OnPointerClick(PointerEventData data)
/// {
/// Debug.Log("OnPointerClick called.");
/// }
///
/// public override void OnPointerDown(PointerEventData data)
/// {
/// Debug.Log("OnPointerDown called.");
/// }
///
/// public override void OnPointerEnter(PointerEventData data)
/// {
/// Debug.Log("OnPointerEnter called.");
/// }
///
/// public override void OnPointerExit(PointerEventData data)
/// {
/// Debug.Log("OnPointerExit called.");
/// }
///
/// public override void OnPointerUp(PointerEventData data)
/// {
/// Debug.Log("OnPointerUp called.");
/// }
///
/// public override void OnScroll(PointerEventData data)
/// {
/// Debug.Log("OnScroll called.");
/// }
///
/// public override void OnSelect(BaseEventData data)
/// {
/// Debug.Log("OnSelect called.");
/// }
///
/// public override void OnSubmit(BaseEventData data)
/// {
/// Debug.Log("OnSubmit called.");
/// }
///
/// public override void OnUpdateSelected(BaseEventData data)
/// {
/// Debug.Log("OnUpdateSelected called.");
/// }
/// }
/// ]]>
///</code>
/// or you can specify individual delegates:
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using UnityEngine.EventSystems;
///
///
/// public class EventTriggerDelegateExample : MonoBehaviour
/// {
/// void Start()
/// {
/// EventTrigger trigger = GetComponent<EventTrigger>();
/// EventTrigger.Entry entry = new EventTrigger.Entry();
/// entry.eventID = EventTriggerType.PointerDown;
/// entry.callback.AddListener((data) => { OnPointerDownDelegate((PointerEventData)data); });
/// trigger.triggers.Add(entry);
/// }
///
/// public void OnPointerDownDelegate(PointerEventData data)
/// {
/// Debug.Log("OnPointerDownDelegate called.");
/// }
/// }
/// ]]>
///</code>
/// </example>
public class EventTrigger :
MonoBehaviour,
IPointerEnterHandler,
IPointerExitHandler,
IPointerDownHandler,
IPointerUpHandler,
IPointerClickHandler,
IInitializePotentialDragHandler,
IBeginDragHandler,
IDragHandler,
IEndDragHandler,
IDropHandler,
IScrollHandler,
IUpdateSelectedHandler,
ISelectHandler,
IDeselectHandler,
IMoveHandler,
ISubmitHandler,
ICancelHandler
{
[Serializable]
/// <summary>
/// UnityEvent class for Triggers.
/// </summary>
public class TriggerEvent : UnityEvent<BaseEventData>
{}
[Serializable]
/// <summary>
/// An Entry in the EventSystem delegates list.
/// </summary>
/// <remarks>
/// It stores the callback and which event type should this callback be fired.
/// </remarks>
public class Entry
{
/// <summary>
/// What type of event is the associated callback listening for.
/// </summary>
public EventTriggerType eventID = EventTriggerType.PointerClick;
/// <summary>
/// The desired TriggerEvent to be Invoked.
/// </summary>
public TriggerEvent callback = new TriggerEvent();
}
[FormerlySerializedAs("delegates")]
[SerializeField]
private List<Entry> m_Delegates;
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
[Obsolete("Please use triggers instead (UnityUpgradable) -> triggers", true)]
public List<Entry> delegates { get { return triggers; } set { triggers = value; } }
protected EventTrigger()
{}
/// <summary>
/// All the functions registered in this EventTrigger
/// </summary>
public List<Entry> triggers
{
get
{
if (m_Delegates == null)
m_Delegates = new List<Entry>();
return m_Delegates;
}
set { m_Delegates = value; }
}
private void Execute(EventTriggerType id, BaseEventData eventData)
{
for (int i = 0; i < triggers.Count; ++i)
{
var ent = triggers[i];
if (ent.eventID == id && ent.callback != null)
ent.callback.Invoke(eventData);
}
}
/// <summary>
/// Called by the EventSystem when the pointer enters the object associated with this EventTrigger.
/// </summary>
public virtual void OnPointerEnter(PointerEventData eventData)
{
Execute(EventTriggerType.PointerEnter, eventData);
}
/// <summary>
/// Called by the EventSystem when the pointer exits the object associated with this EventTrigger.
/// </summary>
public virtual void OnPointerExit(PointerEventData eventData)
{
Execute(EventTriggerType.PointerExit, eventData);
}
/// <summary>
/// Called by the EventSystem every time the pointer is moved during dragging.
/// </summary>
public virtual void OnDrag(PointerEventData eventData)
{
Execute(EventTriggerType.Drag, eventData);
}
/// <summary>
/// Called by the EventSystem when an object accepts a drop.
/// </summary>
public virtual void OnDrop(PointerEventData eventData)
{
Execute(EventTriggerType.Drop, eventData);
}
/// <summary>
/// Called by the EventSystem when a PointerDown event occurs.
/// </summary>
public virtual void OnPointerDown(PointerEventData eventData)
{
Execute(EventTriggerType.PointerDown, eventData);
}
/// <summary>
/// Called by the EventSystem when a PointerUp event occurs.
/// </summary>
public virtual void OnPointerUp(PointerEventData eventData)
{
Execute(EventTriggerType.PointerUp, eventData);
}
/// <summary>
/// Called by the EventSystem when a Click event occurs.
/// </summary>
public virtual void OnPointerClick(PointerEventData eventData)
{
Execute(EventTriggerType.PointerClick, eventData);
}
/// <summary>
/// Called by the EventSystem when a Select event occurs.
/// </summary>
public virtual void OnSelect(BaseEventData eventData)
{
Execute(EventTriggerType.Select, eventData);
}
/// <summary>
/// Called by the EventSystem when a new object is being selected.
/// </summary>
public virtual void OnDeselect(BaseEventData eventData)
{
Execute(EventTriggerType.Deselect, eventData);
}
/// <summary>
/// Called by the EventSystem when a new Scroll event occurs.
/// </summary>
public virtual void OnScroll(PointerEventData eventData)
{
Execute(EventTriggerType.Scroll, eventData);
}
/// <summary>
/// Called by the EventSystem when a Move event occurs.
/// </summary>
public virtual void OnMove(AxisEventData eventData)
{
Execute(EventTriggerType.Move, eventData);
}
/// <summary>
/// Called by the EventSystem when the object associated with this EventTrigger is updated.
/// </summary>
public virtual void OnUpdateSelected(BaseEventData eventData)
{
Execute(EventTriggerType.UpdateSelected, eventData);
}
/// <summary>
/// Called by the EventSystem when a drag has been found, but before it is valid to begin the drag.
/// </summary>
public virtual void OnInitializePotentialDrag(PointerEventData eventData)
{
Execute(EventTriggerType.InitializePotentialDrag, eventData);
}
/// <summary>
/// Called before a drag is started.
/// </summary>
public virtual void OnBeginDrag(PointerEventData eventData)
{
Execute(EventTriggerType.BeginDrag, eventData);
}
/// <summary>
/// Called by the EventSystem once dragging ends.
/// </summary>
public virtual void OnEndDrag(PointerEventData eventData)
{
Execute(EventTriggerType.EndDrag, eventData);
}
/// <summary>
/// Called by the EventSystem when a Submit event occurs.
/// </summary>
public virtual void OnSubmit(BaseEventData eventData)
{
Execute(EventTriggerType.Submit, eventData);
}
/// <summary>
/// Called by the EventSystem when a Cancel event occurs.
/// </summary>
public virtual void OnCancel(BaseEventData eventData)
{
Execute(EventTriggerType.Cancel, eventData);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d0b148fe25e99eb48b9724523833bab1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,96 @@
namespace UnityEngine.EventSystems
{
/// <summary>
/// This class is capable of triggering one or more remote functions from a specified event.
/// Usage: Attach it to an object with a collider, or to a GUI Graphic of your choice.
/// NOTE: Doing this will make this object intercept ALL events, and no event bubbling will occur from this object!
/// </summary>
public enum EventTriggerType
{
/// <summary>
/// Intercepts a IPointerEnterHandler.OnPointerEnter.
/// </summary>
PointerEnter = 0,
/// <summary>
/// Intercepts a IPointerExitHandler.OnPointerExit.
/// </summary>
PointerExit = 1,
/// <summary>
/// Intercepts a IPointerDownHandler.OnPointerDown.
/// </summary>
PointerDown = 2,
/// <summary>
/// Intercepts a IPointerUpHandler.OnPointerUp.
/// </summary>
PointerUp = 3,
/// <summary>
/// Intercepts a IPointerClickHandler.OnPointerClick.
/// </summary>
PointerClick = 4,
/// <summary>
/// Intercepts a IDragHandler.OnDrag.
/// </summary>
Drag = 5,
/// <summary>
/// Intercepts a IDropHandler.OnDrop.
/// </summary>
Drop = 6,
/// <summary>
/// Intercepts a IScrollHandler.OnScroll.
/// </summary>
Scroll = 7,
/// <summary>
/// Intercepts a IUpdateSelectedHandler.OnUpdateSelected.
/// </summary>
UpdateSelected = 8,
/// <summary>
/// Intercepts a ISelectHandler.OnSelect.
/// </summary>
Select = 9,
/// <summary>
/// Intercepts a IDeselectHandler.OnDeselect.
/// </summary>
Deselect = 10,
/// <summary>
/// Intercepts a IMoveHandler.OnMove.
/// </summary>
Move = 11,
/// <summary>
/// Intercepts IInitializePotentialDrag.InitializePotentialDrag.
/// </summary>
InitializePotentialDrag = 12,
/// <summary>
/// Intercepts IBeginDragHandler.OnBeginDrag.
/// </summary>
BeginDrag = 13,
/// <summary>
/// Intercepts IEndDragHandler.OnEndDrag.
/// </summary>
EndDrag = 14,
/// <summary>
/// Intercepts ISubmitHandler.Submit.
/// </summary>
Submit = 15,
/// <summary>
/// Intercepts ICancelHandler.OnCancel.
/// </summary>
Cancel = 16
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3fa274f26b1574c40b949e114327022e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,374 @@
using System;
using System.Collections.Generic;
using UnityEngine.Pool;
namespace UnityEngine.EventSystems
{
public static class ExecuteEvents
{
public delegate void EventFunction<T1>(T1 handler, BaseEventData eventData);
public static T ValidateEventData<T>(BaseEventData data) where T : class
{
if ((data as T) == null)
throw new ArgumentException(String.Format("Invalid type: {0} passed to event expecting {1}", data.GetType(), typeof(T)));
return data as T;
}
private static readonly EventFunction<IPointerMoveHandler> s_PointerMoveHandler = Execute;
private static void Execute(IPointerMoveHandler handler, BaseEventData eventData)
{
handler.OnPointerMove(ValidateEventData<PointerEventData>(eventData));
}
private static readonly EventFunction<IPointerEnterHandler> s_PointerEnterHandler = Execute;
private static void Execute(IPointerEnterHandler handler, BaseEventData eventData)
{
handler.OnPointerEnter(ValidateEventData<PointerEventData>(eventData));
}
private static readonly EventFunction<IPointerExitHandler> s_PointerExitHandler = Execute;
private static void Execute(IPointerExitHandler handler, BaseEventData eventData)
{
handler.OnPointerExit(ValidateEventData<PointerEventData>(eventData));
}
private static readonly EventFunction<IPointerDownHandler> s_PointerDownHandler = Execute;
private static void Execute(IPointerDownHandler handler, BaseEventData eventData)
{
handler.OnPointerDown(ValidateEventData<PointerEventData>(eventData));
}
private static readonly EventFunction<IPointerUpHandler> s_PointerUpHandler = Execute;
private static void Execute(IPointerUpHandler handler, BaseEventData eventData)
{
handler.OnPointerUp(ValidateEventData<PointerEventData>(eventData));
}
private static readonly EventFunction<IPointerClickHandler> s_PointerClickHandler = Execute;
private static void Execute(IPointerClickHandler handler, BaseEventData eventData)
{
handler.OnPointerClick(ValidateEventData<PointerEventData>(eventData));
}
private static readonly EventFunction<IInitializePotentialDragHandler> s_InitializePotentialDragHandler = Execute;
private static void Execute(IInitializePotentialDragHandler handler, BaseEventData eventData)
{
handler.OnInitializePotentialDrag(ValidateEventData<PointerEventData>(eventData));
}
private static readonly EventFunction<IBeginDragHandler> s_BeginDragHandler = Execute;
private static void Execute(IBeginDragHandler handler, BaseEventData eventData)
{
handler.OnBeginDrag(ValidateEventData<PointerEventData>(eventData));
}
private static readonly EventFunction<IDragHandler> s_DragHandler = Execute;
private static void Execute(IDragHandler handler, BaseEventData eventData)
{
handler.OnDrag(ValidateEventData<PointerEventData>(eventData));
}
private static readonly EventFunction<IEndDragHandler> s_EndDragHandler = Execute;
private static void Execute(IEndDragHandler handler, BaseEventData eventData)
{
handler.OnEndDrag(ValidateEventData<PointerEventData>(eventData));
}
private static readonly EventFunction<IDropHandler> s_DropHandler = Execute;
private static void Execute(IDropHandler handler, BaseEventData eventData)
{
handler.OnDrop(ValidateEventData<PointerEventData>(eventData));
}
private static readonly EventFunction<IScrollHandler> s_ScrollHandler = Execute;
private static void Execute(IScrollHandler handler, BaseEventData eventData)
{
handler.OnScroll(ValidateEventData<PointerEventData>(eventData));
}
private static readonly EventFunction<IUpdateSelectedHandler> s_UpdateSelectedHandler = Execute;
private static void Execute(IUpdateSelectedHandler handler, BaseEventData eventData)
{
handler.OnUpdateSelected(eventData);
}
private static readonly EventFunction<ISelectHandler> s_SelectHandler = Execute;
private static void Execute(ISelectHandler handler, BaseEventData eventData)
{
handler.OnSelect(eventData);
}
private static readonly EventFunction<IDeselectHandler> s_DeselectHandler = Execute;
private static void Execute(IDeselectHandler handler, BaseEventData eventData)
{
handler.OnDeselect(eventData);
}
private static readonly EventFunction<IMoveHandler> s_MoveHandler = Execute;
private static void Execute(IMoveHandler handler, BaseEventData eventData)
{
handler.OnMove(ValidateEventData<AxisEventData>(eventData));
}
private static readonly EventFunction<ISubmitHandler> s_SubmitHandler = Execute;
private static void Execute(ISubmitHandler handler, BaseEventData eventData)
{
handler.OnSubmit(eventData);
}
private static readonly EventFunction<ICancelHandler> s_CancelHandler = Execute;
private static void Execute(ICancelHandler handler, BaseEventData eventData)
{
handler.OnCancel(eventData);
}
public static EventFunction<IPointerMoveHandler> pointerMoveHandler
{
get { return s_PointerMoveHandler; }
}
public static EventFunction<IPointerEnterHandler> pointerEnterHandler
{
get { return s_PointerEnterHandler; }
}
public static EventFunction<IPointerExitHandler> pointerExitHandler
{
get { return s_PointerExitHandler; }
}
public static EventFunction<IPointerDownHandler> pointerDownHandler
{
get { return s_PointerDownHandler; }
}
public static EventFunction<IPointerUpHandler> pointerUpHandler
{
get { return s_PointerUpHandler; }
}
public static EventFunction<IPointerClickHandler> pointerClickHandler
{
get { return s_PointerClickHandler; }
}
public static EventFunction<IInitializePotentialDragHandler> initializePotentialDrag
{
get { return s_InitializePotentialDragHandler; }
}
public static EventFunction<IBeginDragHandler> beginDragHandler
{
get { return s_BeginDragHandler; }
}
public static EventFunction<IDragHandler> dragHandler
{
get { return s_DragHandler; }
}
public static EventFunction<IEndDragHandler> endDragHandler
{
get { return s_EndDragHandler; }
}
public static EventFunction<IDropHandler> dropHandler
{
get { return s_DropHandler; }
}
public static EventFunction<IScrollHandler> scrollHandler
{
get { return s_ScrollHandler; }
}
public static EventFunction<IUpdateSelectedHandler> updateSelectedHandler
{
get { return s_UpdateSelectedHandler; }
}
public static EventFunction<ISelectHandler> selectHandler
{
get { return s_SelectHandler; }
}
public static EventFunction<IDeselectHandler> deselectHandler
{
get { return s_DeselectHandler; }
}
public static EventFunction<IMoveHandler> moveHandler
{
get { return s_MoveHandler; }
}
public static EventFunction<ISubmitHandler> submitHandler
{
get { return s_SubmitHandler; }
}
public static EventFunction<ICancelHandler> cancelHandler
{
get { return s_CancelHandler; }
}
private static void GetEventChain(GameObject root, IList<Transform> eventChain)
{
eventChain.Clear();
if (root == null)
return;
var t = root.transform;
while (t != null)
{
eventChain.Add(t);
t = t.parent;
}
}
public static bool Execute<T>(GameObject target, BaseEventData eventData, EventFunction<T> functor) where T : IEventSystemHandler
{
var internalHandlers = ListPool<IEventSystemHandler>.Get();
GetEventList<T>(target, internalHandlers);
// if (s_InternalHandlers.Count > 0)
// Debug.Log("Executinng " + typeof (T) + " on " + target);
var internalHandlersCount = internalHandlers.Count;
for (var i = 0; i < internalHandlersCount; i++)
{
T arg;
try
{
arg = (T)internalHandlers[i];
}
catch (Exception e)
{
var temp = internalHandlers[i];
Debug.LogException(new Exception(string.Format("Type {0} expected {1} received.", typeof(T).Name, temp.GetType().Name), e));
continue;
}
try
{
functor(arg, eventData);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
var handlerCount = internalHandlers.Count;
ListPool<IEventSystemHandler>.Release(internalHandlers);
return handlerCount > 0;
}
/// <summary>
/// Execute the specified event on the first game object underneath the current touch.
/// </summary>
private static readonly List<Transform> s_InternalTransformList = new List<Transform>(30);
public static GameObject ExecuteHierarchy<T>(GameObject root, BaseEventData eventData, EventFunction<T> callbackFunction) where T : IEventSystemHandler
{
GetEventChain(root, s_InternalTransformList);
var internalTransformListCount = s_InternalTransformList.Count;
for (var i = 0; i < internalTransformListCount; i++)
{
var transform = s_InternalTransformList[i];
if (Execute(transform.gameObject, eventData, callbackFunction))
return transform.gameObject;
}
return null;
}
private static bool ShouldSendToComponent<T>(Component component) where T : IEventSystemHandler
{
var valid = component is T;
if (!valid)
return false;
var behaviour = component as Behaviour;
if (behaviour != null)
return behaviour.isActiveAndEnabled;
return true;
}
/// <summary>
/// Get the specified object's event event.
/// </summary>
private static void GetEventList<T>(GameObject go, IList<IEventSystemHandler> results) where T : IEventSystemHandler
{
// Debug.LogWarning("GetEventList<" + typeof(T).Name + ">");
if (results == null)
throw new ArgumentException("Results array is null", "results");
if (go == null || !go.activeInHierarchy)
return;
var components = ListPool<Component>.Get();
go.GetComponents(components);
var componentsCount = components.Count;
for (var i = 0; i < componentsCount; i++)
{
if (!ShouldSendToComponent<T>(components[i]))
continue;
// Debug.Log(string.Format("{2} found! On {0}.{1}", go, s_GetComponentsScratch[i].GetType(), typeof(T)));
results.Add(components[i] as IEventSystemHandler);
}
ListPool<Component>.Release(components);
// Debug.LogWarning("end GetEventList<" + typeof(T).Name + ">");
}
/// <summary>
/// Whether the specified game object will be able to handle the specified event.
/// </summary>
public static bool CanHandleEvent<T>(GameObject go) where T : IEventSystemHandler
{
var internalHandlers = ListPool<IEventSystemHandler>.Get();
GetEventList<T>(go, internalHandlers);
var handlerCount = internalHandlers.Count;
ListPool<IEventSystemHandler>.Release(internalHandlers);
return handlerCount != 0;
}
/// <summary>
/// Bubble the specified event on the game object, figuring out which object will actually receive the event.
/// </summary>
public static GameObject GetEventHandler<T>(GameObject root) where T : IEventSystemHandler
{
if (root == null)
return null;
Transform t = root.transform;
while (t != null)
{
if (CanHandleEvent<T>(t.gameObject))
return t.gameObject;
t = t.parent;
}
return null;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 623c79a1e113b4941afdbfc88d19e8fd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8e78f8a8575e4a04f8337a54e241cdc5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,129 @@
using UnityEngine.UI;
namespace UnityEngine.EventSystems
{
/// <summary>
/// Interface to the Input system used by the BaseInputModule. With this it is possible to bypass the Input system with your own but still use the same InputModule. For example this can be used to feed fake input into the UI or interface with a different input system.
/// </summary>
public class BaseInput : UIBehaviour
{
/// <summary>
/// Interface to Input.compositionString. Can be overridden to provide custom input instead of using the Input class.
/// </summary>
public virtual string compositionString
{
get { return Input.compositionString; }
}
/// <summary>
/// Interface to Input.imeCompositionMode. Can be overridden to provide custom input instead of using the Input class.
/// </summary>
public virtual IMECompositionMode imeCompositionMode
{
get { return Input.imeCompositionMode; }
set { Input.imeCompositionMode = value; }
}
/// <summary>
/// Interface to Input.compositionCursorPos. Can be overridden to provide custom input instead of using the Input class.
/// </summary>
public virtual Vector2 compositionCursorPos
{
get { return Input.compositionCursorPos; }
set { Input.compositionCursorPos = value; }
}
/// <summary>
/// Interface to Input.mousePresent. Can be overridden to provide custom input instead of using the Input class.
/// </summary>
public virtual bool mousePresent
{
get { return Input.mousePresent; }
}
/// <summary>
/// Interface to Input.GetMouseButtonDown. Can be overridden to provide custom input instead of using the Input class.
/// </summary>
/// <param name="button"></param>
/// <returns></returns>
public virtual bool GetMouseButtonDown(int button)
{
return Input.GetMouseButtonDown(button);
}
/// <summary>
/// Interface to Input.GetMouseButtonUp. Can be overridden to provide custom input instead of using the Input class.
/// </summary>
public virtual bool GetMouseButtonUp(int button)
{
return Input.GetMouseButtonUp(button);
}
/// <summary>
/// Interface to Input.GetMouseButton. Can be overridden to provide custom input instead of using the Input class.
/// </summary>
public virtual bool GetMouseButton(int button)
{
return Input.GetMouseButton(button);
}
/// <summary>
/// Interface to Input.mousePosition. Can be overridden to provide custom input instead of using the Input class.
/// </summary>
public virtual Vector2 mousePosition
{
get { return Input.mousePosition; }
}
/// <summary>
/// Interface to Input.mouseScrollDelta. Can be overridden to provide custom input instead of using the Input class.
/// </summary>
public virtual Vector2 mouseScrollDelta
{
get { return Input.mouseScrollDelta; }
}
/// <summary>
/// Interface to Input.touchSupported. Can be overridden to provide custom input instead of using the Input class.
/// </summary>
public virtual bool touchSupported
{
get { return Input.touchSupported; }
}
/// <summary>
/// Interface to Input.touchCount. Can be overridden to provide custom input instead of using the Input class.
/// </summary>
public virtual int touchCount
{
get { return Input.touchCount; }
}
/// <summary>
/// Interface to Input.GetTouch. Can be overridden to provide custom input instead of using the Input class.
/// </summary>
/// <param name="index">Touch index to get</param>
public virtual Touch GetTouch(int index)
{
return Input.GetTouch(index);
}
/// <summary>
/// Interface to Input.GetAxisRaw. Can be overridden to provide custom input instead of using the Input class.
/// </summary>
/// <param name="axisName">Axis name to check</param>
public virtual float GetAxisRaw(string axisName)
{
return Input.GetAxisRaw(axisName);
}
/// <summary>
/// Interface to Input.GetButtonDown. Can be overridden to provide custom input instead of using the Input class.
/// </summary>
/// <param name="buttonName">Button name to get</param>
public virtual bool GetButtonDown(string buttonName)
{
return Input.GetButtonDown(buttonName);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1511ccae7919cfc46b603b9b337fdc94
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,394 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.EventSystems
{
[RequireComponent(typeof(EventSystem))]
/// <summary>
/// A base module that raises events and sends them to GameObjects.
/// </summary>
/// <remarks>
/// An Input Module is a component of the EventSystem that is responsible for raising events and sending them to GameObjects for handling. The BaseInputModule is a class that all Input Modules in the EventSystem inherit from. Examples of provided modules are TouchInputModule and StandaloneInputModule, if these are inadequate for your project you can create your own by extending from the BaseInputModule.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using UnityEngine.EventSystems;
///
/// /**
/// * Create a module that every tick sends a 'Move' event to
/// * the target object
/// */
/// public class MyInputModule : BaseInputModule
/// {
/// public GameObject m_TargetObject;
///
/// public override void Process()
/// {
/// if (m_TargetObject == null)
/// return;
/// ExecuteEvents.Execute (m_TargetObject, new BaseEventData (eventSystem), ExecuteEvents.moveHandler);
/// }
/// }
/// ]]>
///</code>
/// </example>
public abstract class BaseInputModule : UIBehaviour
{
[NonSerialized]
protected List<RaycastResult> m_RaycastResultCache = new List<RaycastResult>();
/// <summary>
/// True if pointer hover events will be sent to the parent
/// </summary>
[SerializeField] private bool m_SendPointerHoverToParent = true;
//This is needed for testing
internal bool sendPointerHoverToParent { get { return m_SendPointerHoverToParent; } set { m_SendPointerHoverToParent = value; } }
private AxisEventData m_AxisEventData;
private EventSystem m_EventSystem;
private BaseEventData m_BaseEventData;
protected BaseInput m_InputOverride;
private BaseInput m_DefaultInput;
/// <summary>
/// The current BaseInput being used by the input module.
/// </summary>
public BaseInput input
{
get
{
if (m_InputOverride != null)
return m_InputOverride;
if (m_DefaultInput == null)
{
var inputs = GetComponents<BaseInput>();
foreach (var baseInput in inputs)
{
// We dont want to use any classes that derrive from BaseInput for default.
if (baseInput != null && baseInput.GetType() == typeof(BaseInput))
{
m_DefaultInput = baseInput;
break;
}
}
if (m_DefaultInput == null)
m_DefaultInput = gameObject.AddComponent<BaseInput>();
}
return m_DefaultInput;
}
}
/// <summary>
/// Used to override the default BaseInput for the input module.
/// </summary>
/// <remarks>
/// With this it is possible to bypass the Input system with your own but still use the same InputModule. For example this can be used to feed fake input into the UI or interface with a different input system.
/// </remarks>
public BaseInput inputOverride
{
get { return m_InputOverride; }
set { m_InputOverride = value; }
}
protected EventSystem eventSystem
{
get { return m_EventSystem; }
}
protected override void OnEnable()
{
base.OnEnable();
m_EventSystem = GetComponent<EventSystem>();
m_EventSystem.UpdateModules();
}
protected override void OnDisable()
{
m_EventSystem.UpdateModules();
base.OnDisable();
}
/// <summary>
/// Process the current tick for the module.
/// </summary>
public abstract void Process();
/// <summary>
/// Return the first valid RaycastResult.
/// </summary>
protected static RaycastResult FindFirstRaycast(List<RaycastResult> candidates)
{
var candidatesCount = candidates.Count;
for (var i = 0; i < candidatesCount; ++i)
{
if (candidates[i].gameObject == null)
continue;
return candidates[i];
}
return new RaycastResult();
}
/// <summary>
/// Given an input movement, determine the best MoveDirection.
/// </summary>
/// <param name="x">X movement.</param>
/// <param name="y">Y movement.</param>
protected static MoveDirection DetermineMoveDirection(float x, float y)
{
return DetermineMoveDirection(x, y, 0.6f);
}
/// <summary>
/// Given an input movement, determine the best MoveDirection.
/// </summary>
/// <param name="x">X movement.</param>
/// <param name="y">Y movement.</param>
/// <param name="deadZone">Dead zone.</param>
protected static MoveDirection DetermineMoveDirection(float x, float y, float deadZone)
{
// if vector is too small... just return
if (new Vector2(x, y).sqrMagnitude < deadZone * deadZone)
return MoveDirection.None;
if (Mathf.Abs(x) > Mathf.Abs(y))
{
return x > 0 ? MoveDirection.Right : MoveDirection.Left;
}
return y > 0 ? MoveDirection.Up : MoveDirection.Down;
}
/// <summary>
/// Given 2 GameObjects, return a common root GameObject (or null).
/// </summary>
/// <param name="g1">GameObject to compare</param>
/// <param name="g2">GameObject to compare</param>
/// <returns></returns>
protected static GameObject FindCommonRoot(GameObject g1, GameObject g2)
{
if (g1 == null || g2 == null)
return null;
var t1 = g1.transform;
while (t1 != null)
{
var t2 = g2.transform;
while (t2 != null)
{
if (t1 == t2)
return t1.gameObject;
t2 = t2.parent;
}
t1 = t1.parent;
}
return null;
}
// walk up the tree till a common root between the last entered and the current entered is found
// send exit events up to (but not including) the common root. Then send enter events up to
// (but not including) the common root.
// Send move events before exit, after enter, and on hovered objects when pointer data has changed.
protected void HandlePointerExitAndEnter(PointerEventData currentPointerData, GameObject newEnterTarget)
{
// if we have no target / pointerEnter has been deleted
// just send exit events to anything we are tracking
// then exit
if (newEnterTarget == null || currentPointerData.pointerEnter == null)
{
var hoveredCount = currentPointerData.hovered.Count;
for (var i = 0; i < hoveredCount; ++i)
{
currentPointerData.fullyExited = true;
ExecuteEvents.Execute(currentPointerData.hovered[i], currentPointerData, ExecuteEvents.pointerMoveHandler);
ExecuteEvents.Execute(currentPointerData.hovered[i], currentPointerData, ExecuteEvents.pointerExitHandler);
}
currentPointerData.hovered.Clear();
if (newEnterTarget == null)
{
currentPointerData.pointerEnter = null;
return;
}
}
// if we have not changed hover target
if (currentPointerData.pointerEnter == newEnterTarget && newEnterTarget)
{
if (currentPointerData.IsPointerMoving())
{
var hoveredCount = currentPointerData.hovered.Count;
for (var i = 0; i < hoveredCount; ++i)
ExecuteEvents.Execute(currentPointerData.hovered[i], currentPointerData, ExecuteEvents.pointerMoveHandler);
}
return;
}
GameObject commonRoot = FindCommonRoot(currentPointerData.pointerEnter, newEnterTarget);
GameObject pointerParent = ((Component)newEnterTarget.GetComponentInParent<IPointerExitHandler>())?.gameObject;
// and we already an entered object from last time
if (currentPointerData.pointerEnter != null)
{
// send exit handler call to all elements in the chain
// until we reach the new target, or null!
// ** or when !m_SendPointerEnterToParent, stop when meeting a gameobject with an exit event handler
Transform t = currentPointerData.pointerEnter.transform;
while (t != null)
{
// if we reach the common root break out!
if (m_SendPointerHoverToParent && commonRoot != null && commonRoot.transform == t)
break;
// if we reach a PointerExitEvent break out!
if (!m_SendPointerHoverToParent && pointerParent == t.gameObject)
break;
currentPointerData.fullyExited = t.gameObject != commonRoot && currentPointerData.pointerEnter != newEnterTarget;
ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerMoveHandler);
ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerExitHandler);
currentPointerData.hovered.Remove(t.gameObject);
if (m_SendPointerHoverToParent) t = t.parent;
// if we reach the common root break out!
if (commonRoot != null && commonRoot.transform == t)
break;
if (!m_SendPointerHoverToParent) t = t.parent;
}
}
// now issue the enter call up to but not including the common root
var oldPointerEnter = currentPointerData.pointerEnter;
currentPointerData.pointerEnter = newEnterTarget;
if (newEnterTarget != null)
{
Transform t = newEnterTarget.transform;
while (t != null)
{
currentPointerData.reentered = t.gameObject == commonRoot && t.gameObject != oldPointerEnter;
// if we are sending the event to parent, they are already in hover mode at that point. No need to bubble up the event.
if (m_SendPointerHoverToParent && currentPointerData.reentered)
break;
ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerEnterHandler);
ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerMoveHandler);
currentPointerData.hovered.Add(t.gameObject);
// stop when encountering an object with the pointerEnterHandler
if (!m_SendPointerHoverToParent && t.gameObject.GetComponent<IPointerEnterHandler>() != null)
break;
if (m_SendPointerHoverToParent) t = t.parent;
// if we reach the common root break out!
if (commonRoot != null && commonRoot.transform == t)
break;
if (!m_SendPointerHoverToParent) t = t.parent;
}
}
}
/// <summary>
/// Given some input data generate an AxisEventData that can be used by the event system.
/// </summary>
/// <param name="x">X movement.</param>
/// <param name="y">Y movement.</param>
/// <param name="deadZone">Dead zone.</param>
protected virtual AxisEventData GetAxisEventData(float x, float y, float moveDeadZone)
{
if (m_AxisEventData == null)
m_AxisEventData = new AxisEventData(eventSystem);
m_AxisEventData.Reset();
m_AxisEventData.moveVector = new Vector2(x, y);
m_AxisEventData.moveDir = DetermineMoveDirection(x, y, moveDeadZone);
return m_AxisEventData;
}
/// <summary>
/// Generate a BaseEventData that can be used by the EventSystem.
/// </summary>
protected virtual BaseEventData GetBaseEventData()
{
if (m_BaseEventData == null)
m_BaseEventData = new BaseEventData(eventSystem);
m_BaseEventData.Reset();
return m_BaseEventData;
}
/// <summary>
/// If the module is pointer based, then override this to return true if the pointer is over an event system object.
/// </summary>
/// <param name="pointerId">Pointer ID</param>
/// <returns>Is the given pointer over an event system object?</returns>
public virtual bool IsPointerOverGameObject(int pointerId)
{
return false;
}
/// <summary>
/// Should the module be activated.
/// </summary>
public virtual bool ShouldActivateModule()
{
return enabled && gameObject.activeInHierarchy;
}
/// <summary>
/// Called when the module is deactivated. Override this if you want custom code to execute when you deactivate your module.
/// </summary>
public virtual void DeactivateModule()
{}
/// <summary>
/// Called when the module is activated. Override this if you want custom code to execute when you activate your module.
/// </summary>
public virtual void ActivateModule()
{}
/// <summary>
/// Update the internal state of the Module.
/// </summary>
public virtual void UpdateModule()
{}
/// <summary>
/// Check to see if the module is supported. Override this if you have a platform specific module (eg. TouchInputModule that you do not want to activate on standalone.)
/// </summary>
/// <returns>Is the module supported.</returns>
public virtual bool IsModuleSupported()
{
return true;
}
/// <summary>
/// Returns Id of the pointer following <see cref="UnityEngine.UIElements.PointerId"/> convention.
/// </summary>
/// <param name="sourcePointerData">PointerEventData whose pointerId will be converted to UI Toolkit pointer convention.</param>
/// <seealso cref="UnityEngine.UIElements.IPointerEvent" />
public virtual int ConvertUIToolkitPointerId(PointerEventData sourcePointerData)
{
#if PACKAGE_UITOOLKIT
return sourcePointerData.pointerId < 0 ?
UIElements.PointerId.mousePointerId :
UIElements.PointerId.touchPointerIdBase + sourcePointerData.pointerId;
#else
return -1;
#endif
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1ea10891dd782154ca0fb67bce9e6f72
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,437 @@
using System.Collections.Generic;
using System.Text;
using UnityEngine.UI;
namespace UnityEngine.EventSystems
{
/// <summary>
/// A BaseInputModule for pointer input.
/// </summary>
public abstract class PointerInputModule : BaseInputModule
{
/// <summary>
/// Id of the cached left mouse pointer event.
/// </summary>
public const int kMouseLeftId = -1;
/// <summary>
/// Id of the cached right mouse pointer event.
/// </summary>
public const int kMouseRightId = -2;
/// <summary>
/// Id of the cached middle mouse pointer event.
/// </summary>
public const int kMouseMiddleId = -3;
/// <summary>
/// Touch id for when simulating touches on a non touch device.
/// </summary>
public const int kFakeTouchesId = -4;
protected Dictionary<int, PointerEventData> m_PointerData = new Dictionary<int, PointerEventData>();
/// <summary>
/// Search the cache for currently active pointers, return true if found.
/// </summary>
/// <param name="id">Touch ID</param>
/// <param name="data">Found data</param>
/// <param name="create">If not found should it be created</param>
/// <returns>True if pointer is found.</returns>
protected bool GetPointerData(int id, out PointerEventData data, bool create)
{
if (!m_PointerData.TryGetValue(id, out data) && create)
{
data = new PointerEventData(eventSystem)
{
pointerId = id,
};
m_PointerData.Add(id, data);
return true;
}
return false;
}
/// <summary>
/// Remove the PointerEventData from the cache.
/// </summary>
protected void RemovePointerData(PointerEventData data)
{
m_PointerData.Remove(data.pointerId);
}
/// <summary>
/// Given a touch populate the PointerEventData and return if we are pressed or released.
/// </summary>
/// <param name="input">Touch being processed</param>
/// <param name="pressed">Are we pressed this frame</param>
/// <param name="released">Are we released this frame</param>
/// <returns></returns>
protected PointerEventData GetTouchPointerEventData(Touch input, out bool pressed, out bool released)
{
PointerEventData pointerData;
var created = GetPointerData(input.fingerId, out pointerData, true);
pointerData.Reset();
pressed = created || (input.phase == TouchPhase.Began);
released = (input.phase == TouchPhase.Canceled) || (input.phase == TouchPhase.Ended);
if (created)
pointerData.position = input.position;
if (pressed)
pointerData.delta = Vector2.zero;
else
pointerData.delta = input.position - pointerData.position;
pointerData.position = input.position;
pointerData.button = PointerEventData.InputButton.Left;
if (input.phase == TouchPhase.Canceled)
{
pointerData.pointerCurrentRaycast = new RaycastResult();
}
else
{
eventSystem.RaycastAll(pointerData, m_RaycastResultCache);
var raycast = FindFirstRaycast(m_RaycastResultCache);
pointerData.pointerCurrentRaycast = raycast;
m_RaycastResultCache.Clear();
}
pointerData.pressure = input.pressure;
pointerData.altitudeAngle = input.altitudeAngle;
pointerData.azimuthAngle = input.azimuthAngle;
pointerData.radius = Vector2.one * input.radius;
pointerData.radiusVariance = Vector2.one * input.radiusVariance;
return pointerData;
}
/// <summary>
/// Copy one PointerEventData to another.
/// </summary>
protected void CopyFromTo(PointerEventData @from, PointerEventData @to)
{
@to.position = @from.position;
@to.delta = @from.delta;
@to.scrollDelta = @from.scrollDelta;
@to.pointerCurrentRaycast = @from.pointerCurrentRaycast;
@to.pointerEnter = @from.pointerEnter;
@to.pressure = @from.pressure;
@to.tangentialPressure = @from.tangentialPressure;
@to.altitudeAngle = @from.altitudeAngle;
@to.azimuthAngle = @from.azimuthAngle;
@to.twist = @from.twist;
@to.radius = @from.radius;
@to.radiusVariance = @from.radiusVariance;
}
/// <summary>
/// Given a mouse button return the current state for the frame.
/// </summary>
/// <param name="buttonId">Mouse button ID</param>
protected PointerEventData.FramePressState StateForMouseButton(int buttonId)
{
var pressed = input.GetMouseButtonDown(buttonId);
var released = input.GetMouseButtonUp(buttonId);
if (pressed && released)
return PointerEventData.FramePressState.PressedAndReleased;
if (pressed)
return PointerEventData.FramePressState.Pressed;
if (released)
return PointerEventData.FramePressState.Released;
return PointerEventData.FramePressState.NotChanged;
}
protected class ButtonState
{
private PointerEventData.InputButton m_Button = PointerEventData.InputButton.Left;
public MouseButtonEventData eventData
{
get { return m_EventData; }
set { m_EventData = value; }
}
public PointerEventData.InputButton button
{
get { return m_Button; }
set { m_Button = value; }
}
private MouseButtonEventData m_EventData;
}
protected class MouseState
{
private List<ButtonState> m_TrackedButtons = new List<ButtonState>();
public bool AnyPressesThisFrame()
{
var trackedButtonsCount = m_TrackedButtons.Count;
for (int i = 0; i < trackedButtonsCount; i++)
{
if (m_TrackedButtons[i].eventData.PressedThisFrame())
return true;
}
return false;
}
public bool AnyReleasesThisFrame()
{
var trackedButtonsCount = m_TrackedButtons.Count;
for (int i = 0; i < trackedButtonsCount; i++)
{
if (m_TrackedButtons[i].eventData.ReleasedThisFrame())
return true;
}
return false;
}
public ButtonState GetButtonState(PointerEventData.InputButton button)
{
ButtonState tracked = null;
var trackedButtonsCount = m_TrackedButtons.Count;
for (int i = 0; i < trackedButtonsCount; i++)
{
if (m_TrackedButtons[i].button == button)
{
tracked = m_TrackedButtons[i];
break;
}
}
if (tracked == null)
{
tracked = new ButtonState { button = button, eventData = new MouseButtonEventData() };
m_TrackedButtons.Add(tracked);
}
return tracked;
}
public void SetButtonState(PointerEventData.InputButton button, PointerEventData.FramePressState stateForMouseButton, PointerEventData data)
{
var toModify = GetButtonState(button);
toModify.eventData.buttonState = stateForMouseButton;
toModify.eventData.buttonData = data;
}
}
/// <summary>
/// Information about a mouse button event.
/// </summary>
public class MouseButtonEventData
{
/// <summary>
/// The state of the button this frame.
/// </summary>
public PointerEventData.FramePressState buttonState;
/// <summary>
/// Pointer data associated with the mouse event.
/// </summary>
public PointerEventData buttonData;
/// <summary>
/// Was the button pressed this frame?
/// </summary>
public bool PressedThisFrame()
{
return buttonState == PointerEventData.FramePressState.Pressed || buttonState == PointerEventData.FramePressState.PressedAndReleased;
}
/// <summary>
/// Was the button released this frame?
/// </summary>
public bool ReleasedThisFrame()
{
return buttonState == PointerEventData.FramePressState.Released || buttonState == PointerEventData.FramePressState.PressedAndReleased;
}
}
private readonly MouseState m_MouseState = new MouseState();
/// <summary>
/// Return the current MouseState. Using the default pointer.
/// </summary>
protected virtual MouseState GetMousePointerEventData()
{
return GetMousePointerEventData(0);
}
/// <summary>
/// Return the current MouseState.
/// </summary>
protected virtual MouseState GetMousePointerEventData(int id)
{
// Populate the left button...
PointerEventData leftData;
var created = GetPointerData(kMouseLeftId, out leftData, true);
leftData.Reset();
if (created)
leftData.position = input.mousePosition;
Vector2 pos = input.mousePosition;
if (Cursor.lockState == CursorLockMode.Locked)
{
// We don't want to do ANY cursor-based interaction when the mouse is locked
leftData.position = new Vector2(-1.0f, -1.0f);
leftData.delta = Vector2.zero;
}
else
{
leftData.delta = pos - leftData.position;
leftData.position = pos;
}
leftData.scrollDelta = input.mouseScrollDelta;
leftData.button = PointerEventData.InputButton.Left;
eventSystem.RaycastAll(leftData, m_RaycastResultCache);
var raycast = FindFirstRaycast(m_RaycastResultCache);
leftData.pointerCurrentRaycast = raycast;
m_RaycastResultCache.Clear();
// copy the apropriate data into right and middle slots
PointerEventData rightData;
GetPointerData(kMouseRightId, out rightData, true);
rightData.Reset();
CopyFromTo(leftData, rightData);
rightData.button = PointerEventData.InputButton.Right;
PointerEventData middleData;
GetPointerData(kMouseMiddleId, out middleData, true);
middleData.Reset();
CopyFromTo(leftData, middleData);
middleData.button = PointerEventData.InputButton.Middle;
m_MouseState.SetButtonState(PointerEventData.InputButton.Left, StateForMouseButton(0), leftData);
m_MouseState.SetButtonState(PointerEventData.InputButton.Right, StateForMouseButton(1), rightData);
m_MouseState.SetButtonState(PointerEventData.InputButton.Middle, StateForMouseButton(2), middleData);
return m_MouseState;
}
/// <summary>
/// Return the last PointerEventData for the given touch / mouse id.
/// </summary>
protected PointerEventData GetLastPointerEventData(int id)
{
PointerEventData data;
GetPointerData(id, out data, false);
return data;
}
private static bool ShouldStartDrag(Vector2 pressPos, Vector2 currentPos, float threshold, bool useDragThreshold)
{
if (!useDragThreshold)
return true;
return (pressPos - currentPos).sqrMagnitude >= threshold * threshold;
}
/// <summary>
/// Process movement for the current frame with the given pointer event.
/// </summary>
protected virtual void ProcessMove(PointerEventData pointerEvent)
{
var targetGO = (Cursor.lockState == CursorLockMode.Locked ? null : pointerEvent.pointerCurrentRaycast.gameObject);
HandlePointerExitAndEnter(pointerEvent, targetGO);
}
/// <summary>
/// Process the drag for the current frame with the given pointer event.
/// </summary>
protected virtual void ProcessDrag(PointerEventData pointerEvent)
{
if (!pointerEvent.IsPointerMoving() ||
Cursor.lockState == CursorLockMode.Locked ||
pointerEvent.pointerDrag == null)
return;
if (!pointerEvent.dragging
&& ShouldStartDrag(pointerEvent.pressPosition, pointerEvent.position, eventSystem.pixelDragThreshold, pointerEvent.useDragThreshold))
{
ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.beginDragHandler);
pointerEvent.dragging = true;
}
// Drag notification
if (pointerEvent.dragging)
{
// Before doing drag we should cancel any pointer down state
// And clear selection!
if (pointerEvent.pointerPress != pointerEvent.pointerDrag)
{
ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler);
pointerEvent.eligibleForClick = false;
pointerEvent.pointerPress = null;
pointerEvent.rawPointerPress = null;
}
ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.dragHandler);
}
}
public override bool IsPointerOverGameObject(int pointerId)
{
var lastPointer = GetLastPointerEventData(pointerId);
if (lastPointer != null)
return lastPointer.pointerEnter != null;
return false;
}
/// <summary>
/// Clear all pointers and deselect any selected objects in the EventSystem.
/// </summary>
protected void ClearSelection()
{
var baseEventData = GetBaseEventData();
foreach (var pointer in m_PointerData.Values)
{
// clear all selection
HandlePointerExitAndEnter(pointer, null);
}
m_PointerData.Clear();
eventSystem.SetSelectedGameObject(null, baseEventData);
}
public override string ToString()
{
var sb = new StringBuilder("<b>Pointer Input Module of type: </b>" + GetType());
sb.AppendLine();
foreach (var pointer in m_PointerData)
{
if (pointer.Value == null)
continue;
sb.AppendLine("<B>Pointer:</b> " + pointer.Key);
sb.AppendLine(pointer.Value.ToString());
}
return sb.ToString();
}
/// <summary>
/// Deselect the current selected GameObject if the currently pointed-at GameObject is different.
/// </summary>
/// <param name="currentOverGo">The GameObject the pointer is currently over.</param>
/// <param name="pointerEvent">Current event data.</param>
protected void DeselectIfSelectionChanged(GameObject currentOverGo, BaseEventData pointerEvent)
{
// Selection tracking
var selectHandlerGO = ExecuteEvents.GetEventHandler<ISelectHandler>(currentOverGo);
// if we have clicked something new, deselect the old thing
// leave 'selection handling' up to the press event though.
if (selectHandlerGO != eventSystem.currentSelectedGameObject)
eventSystem.SetSelectedGameObject(null, pointerEvent);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cbab8835092323a4389f2dc7d8f6c781
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,663 @@
using System;
using UnityEngine;
using UnityEngine.Serialization;
namespace UnityEngine.EventSystems
{
[AddComponentMenu("Event/Standalone Input Module")]
/// <summary>
/// A BaseInputModule designed for mouse / keyboard / controller input.
/// </summary>
/// <remarks>
/// Input module for working with, mouse, keyboard, or controller.
/// </remarks>
public class StandaloneInputModule : PointerInputModule
{
private float m_PrevActionTime;
private Vector2 m_LastMoveVector;
private int m_ConsecutiveMoveCount = 0;
private Vector2 m_LastMousePosition;
private Vector2 m_MousePosition;
private GameObject m_CurrentFocusedGameObject;
private PointerEventData m_InputPointerEvent;
private const float doubleClickTime = 0.3f;
protected StandaloneInputModule()
{
}
[Obsolete("Mode is no longer needed on input module as it handles both mouse and keyboard simultaneously.", false)]
public enum InputMode
{
Mouse,
Buttons
}
[Obsolete("Mode is no longer needed on input module as it handles both mouse and keyboard simultaneously.", false)]
public InputMode inputMode
{
get { return InputMode.Mouse; }
}
[SerializeField]
private string m_HorizontalAxis = "Horizontal";
/// <summary>
/// Name of the vertical axis for movement (if axis events are used).
/// </summary>
[SerializeField]
private string m_VerticalAxis = "Vertical";
/// <summary>
/// Name of the submit button.
/// </summary>
[SerializeField]
private string m_SubmitButton = "Submit";
/// <summary>
/// Name of the submit button.
/// </summary>
[SerializeField]
private string m_CancelButton = "Cancel";
[SerializeField]
private float m_InputActionsPerSecond = 10;
[SerializeField]
private float m_RepeatDelay = 0.5f;
[SerializeField]
[FormerlySerializedAs("m_AllowActivationOnMobileDevice")]
[HideInInspector]
private bool m_ForceModuleActive;
[Obsolete("allowActivationOnMobileDevice has been deprecated. Use forceModuleActive instead (UnityUpgradable) -> forceModuleActive")]
public bool allowActivationOnMobileDevice
{
get { return m_ForceModuleActive; }
set { m_ForceModuleActive = value; }
}
/// <summary>
/// Force this module to be active.
/// </summary>
/// <remarks>
/// If there is no module active with higher priority (ordered in the inspector) this module will be forced active even if valid enabling conditions are not met.
/// </remarks>
[Obsolete("forceModuleActive has been deprecated. There is no need to force the module awake as StandaloneInputModule works for all platforms")]
public bool forceModuleActive
{
get { return m_ForceModuleActive; }
set { m_ForceModuleActive = value; }
}
/// <summary>
/// Number of keyboard / controller inputs allowed per second.
/// </summary>
public float inputActionsPerSecond
{
get { return m_InputActionsPerSecond; }
set { m_InputActionsPerSecond = value; }
}
/// <summary>
/// Delay in seconds before the input actions per second repeat rate takes effect.
/// </summary>
/// <remarks>
/// If the same direction is sustained, the inputActionsPerSecond property can be used to control the rate at which events are fired. However, it can be desirable that the first repetition is delayed, so the user doesn't get repeated actions by accident.
/// </remarks>
public float repeatDelay
{
get { return m_RepeatDelay; }
set { m_RepeatDelay = value; }
}
/// <summary>
/// Name of the horizontal axis for movement (if axis events are used).
/// </summary>
public string horizontalAxis
{
get { return m_HorizontalAxis; }
set { m_HorizontalAxis = value; }
}
/// <summary>
/// Name of the vertical axis for movement (if axis events are used).
/// </summary>
public string verticalAxis
{
get { return m_VerticalAxis; }
set { m_VerticalAxis = value; }
}
/// <summary>
/// Maximum number of input events handled per second.
/// </summary>
public string submitButton
{
get { return m_SubmitButton; }
set { m_SubmitButton = value; }
}
/// <summary>
/// Input manager name for the 'cancel' button.
/// </summary>
public string cancelButton
{
get { return m_CancelButton; }
set { m_CancelButton = value; }
}
private bool ShouldIgnoreEventsOnNoFocus()
{
#if UNITY_EDITOR
return !UnityEditor.EditorApplication.isRemoteConnected;
#else
return true;
#endif
}
public override void UpdateModule()
{
if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
{
if (m_InputPointerEvent != null && m_InputPointerEvent.pointerDrag != null && m_InputPointerEvent.dragging)
{
ReleaseMouse(m_InputPointerEvent, m_InputPointerEvent.pointerCurrentRaycast.gameObject);
}
m_InputPointerEvent = null;
return;
}
m_LastMousePosition = m_MousePosition;
m_MousePosition = input.mousePosition;
}
private void ReleaseMouse(PointerEventData pointerEvent, GameObject currentOverGo)
{
ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler);
var pointerClickHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
// PointerClick and Drop events
if (pointerEvent.pointerClick == pointerClickHandler && pointerEvent.eligibleForClick)
{
ExecuteEvents.Execute(pointerEvent.pointerClick, pointerEvent, ExecuteEvents.pointerClickHandler);
}
if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
{
ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.dropHandler);
}
pointerEvent.eligibleForClick = false;
pointerEvent.pointerPress = null;
pointerEvent.rawPointerPress = null;
pointerEvent.pointerClick = null;
if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.endDragHandler);
pointerEvent.dragging = false;
pointerEvent.pointerDrag = null;
// redo pointer enter / exit to refresh state
// so that if we moused over something that ignored it before
// due to having pressed on something else
// it now gets it.
if (currentOverGo != pointerEvent.pointerEnter)
{
HandlePointerExitAndEnter(pointerEvent, null);
HandlePointerExitAndEnter(pointerEvent, currentOverGo);
}
m_InputPointerEvent = pointerEvent;
}
public override bool ShouldActivateModule()
{
if (!base.ShouldActivateModule())
return false;
var shouldActivate = m_ForceModuleActive;
shouldActivate |= input.GetButtonDown(m_SubmitButton);
shouldActivate |= input.GetButtonDown(m_CancelButton);
shouldActivate |= !Mathf.Approximately(input.GetAxisRaw(m_HorizontalAxis), 0.0f);
shouldActivate |= !Mathf.Approximately(input.GetAxisRaw(m_VerticalAxis), 0.0f);
shouldActivate |= (m_MousePosition - m_LastMousePosition).sqrMagnitude > 0.0f;
shouldActivate |= input.GetMouseButtonDown(0);
if (input.touchCount > 0)
shouldActivate = true;
return shouldActivate;
}
/// <summary>
/// See BaseInputModule.
/// </summary>
public override void ActivateModule()
{
if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
return;
base.ActivateModule();
m_MousePosition = input.mousePosition;
m_LastMousePosition = input.mousePosition;
var toSelect = eventSystem.currentSelectedGameObject;
if (toSelect == null)
toSelect = eventSystem.firstSelectedGameObject;
eventSystem.SetSelectedGameObject(toSelect, GetBaseEventData());
}
/// <summary>
/// See BaseInputModule.
/// </summary>
public override void DeactivateModule()
{
base.DeactivateModule();
ClearSelection();
}
public override void Process()
{
if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
return;
bool usedEvent = SendUpdateEventToSelectedObject();
// case 1004066 - touch / mouse events should be processed before navigation events in case
// they change the current selected gameobject and the submit button is a touch / mouse button.
// touch needs to take precedence because of the mouse emulation layer
if (!ProcessTouchEvents() && input.mousePresent)
ProcessMouseEvent();
if (eventSystem.sendNavigationEvents)
{
if (!usedEvent)
usedEvent |= SendMoveEventToSelectedObject();
if (!usedEvent)
SendSubmitEventToSelectedObject();
}
}
private bool ProcessTouchEvents()
{
for (int i = 0; i < input.touchCount; ++i)
{
Touch touch = input.GetTouch(i);
if (touch.type == TouchType.Indirect)
continue;
bool released;
bool pressed;
var pointer = GetTouchPointerEventData(touch, out pressed, out released);
ProcessTouchPress(pointer, pressed, released);
if (!released)
{
ProcessMove(pointer);
ProcessDrag(pointer);
}
else
RemovePointerData(pointer);
}
return input.touchCount > 0;
}
/// <summary>
/// This method is called by Unity whenever a touch event is processed. Override this method with a custom implementation to process touch events yourself.
/// </summary>
/// <param name="pointerEvent">Event data relating to the touch event, such as position and ID to be passed to the touch event destination object.</param>
/// <param name="pressed">This is true for the first frame of a touch event, and false thereafter. This can therefore be used to determine the instant a touch event occurred.</param>
/// <param name="released">This is true only for the last frame of a touch event.</param>
/// <remarks>
/// This method can be overridden in derived classes to change how touch press events are handled.
/// </remarks>
protected void ProcessTouchPress(PointerEventData pointerEvent, bool pressed, bool released)
{
var currentOverGo = pointerEvent.pointerCurrentRaycast.gameObject;
// PointerDown notification
if (pressed)
{
pointerEvent.eligibleForClick = true;
pointerEvent.delta = Vector2.zero;
pointerEvent.dragging = false;
pointerEvent.useDragThreshold = true;
pointerEvent.pressPosition = pointerEvent.position;
pointerEvent.pointerPressRaycast = pointerEvent.pointerCurrentRaycast;
DeselectIfSelectionChanged(currentOverGo, pointerEvent);
if (pointerEvent.pointerEnter != currentOverGo)
{
// send a pointer enter to the touched element if it isn't the one to select...
HandlePointerExitAndEnter(pointerEvent, currentOverGo);
pointerEvent.pointerEnter = currentOverGo;
}
var resetDiffTime = Time.unscaledTime - pointerEvent.clickTime;
if (resetDiffTime >= doubleClickTime)
{
pointerEvent.clickCount = 0;
}
// search for the control that will receive the press
// if we can't find a press handler set the press
// handler to be what would receive a click.
var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.pointerDownHandler);
var newClick = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
// didnt find a press handler... search for a click handler
if (newPressed == null)
newPressed = newClick;
// Debug.Log("Pressed: " + newPressed);
float time = Time.unscaledTime;
if (newPressed == pointerEvent.lastPress)
{
var diffTime = time - pointerEvent.clickTime;
if (diffTime < doubleClickTime)
++pointerEvent.clickCount;
else
pointerEvent.clickCount = 1;
pointerEvent.clickTime = time;
}
else
{
pointerEvent.clickCount = 1;
}
pointerEvent.pointerPress = newPressed;
pointerEvent.rawPointerPress = currentOverGo;
pointerEvent.pointerClick = newClick;
pointerEvent.clickTime = time;
// Save the drag handler as well
pointerEvent.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(currentOverGo);
if (pointerEvent.pointerDrag != null)
ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.initializePotentialDrag);
}
// PointerUp notification
if (released)
{
// Debug.Log("Executing pressup on: " + pointer.pointerPress);
ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler);
// Debug.Log("KeyCode: " + pointer.eventData.keyCode);
// see if we mouse up on the same element that we clicked on...
var pointerClickHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
// PointerClick and Drop events
if (pointerEvent.pointerClick == pointerClickHandler && pointerEvent.eligibleForClick)
{
ExecuteEvents.Execute(pointerEvent.pointerClick, pointerEvent, ExecuteEvents.pointerClickHandler);
}
if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
{
ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.dropHandler);
}
pointerEvent.eligibleForClick = false;
pointerEvent.pointerPress = null;
pointerEvent.rawPointerPress = null;
pointerEvent.pointerClick = null;
if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.endDragHandler);
pointerEvent.dragging = false;
pointerEvent.pointerDrag = null;
// send exit events as we need to simulate this on touch up on touch device
ExecuteEvents.ExecuteHierarchy(pointerEvent.pointerEnter, pointerEvent, ExecuteEvents.pointerExitHandler);
pointerEvent.pointerEnter = null;
}
m_InputPointerEvent = pointerEvent;
}
/// <summary>
/// Calculate and send a submit event to the current selected object.
/// </summary>
/// <returns>If the submit event was used by the selected object.</returns>
protected bool SendSubmitEventToSelectedObject()
{
if (eventSystem.currentSelectedGameObject == null)
return false;
var data = GetBaseEventData();
if (input.GetButtonDown(m_SubmitButton))
ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.submitHandler);
if (input.GetButtonDown(m_CancelButton))
ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.cancelHandler);
return data.used;
}
private Vector2 GetRawMoveVector()
{
Vector2 move = Vector2.zero;
move.x = input.GetAxisRaw(m_HorizontalAxis);
move.y = input.GetAxisRaw(m_VerticalAxis);
if (input.GetButtonDown(m_HorizontalAxis))
{
if (move.x < 0)
move.x = -1f;
if (move.x > 0)
move.x = 1f;
}
if (input.GetButtonDown(m_VerticalAxis))
{
if (move.y < 0)
move.y = -1f;
if (move.y > 0)
move.y = 1f;
}
return move;
}
/// <summary>
/// Calculate and send a move event to the current selected object.
/// </summary>
/// <returns>If the move event was used by the selected object.</returns>
protected bool SendMoveEventToSelectedObject()
{
float time = Time.unscaledTime;
Vector2 movement = GetRawMoveVector();
if (Mathf.Approximately(movement.x, 0f) && Mathf.Approximately(movement.y, 0f))
{
m_ConsecutiveMoveCount = 0;
return false;
}
bool similarDir = (Vector2.Dot(movement, m_LastMoveVector) > 0);
// If direction didn't change at least 90 degrees, wait for delay before allowing consequtive event.
if (similarDir && m_ConsecutiveMoveCount == 1)
{
if (time <= m_PrevActionTime + m_RepeatDelay)
return false;
}
// If direction changed at least 90 degree, or we already had the delay, repeat at repeat rate.
else
{
if (time <= m_PrevActionTime + 1f / m_InputActionsPerSecond)
return false;
}
var axisEventData = GetAxisEventData(movement.x, movement.y, 0.6f);
if (axisEventData.moveDir != MoveDirection.None)
{
ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, axisEventData, ExecuteEvents.moveHandler);
if (!similarDir)
m_ConsecutiveMoveCount = 0;
m_ConsecutiveMoveCount++;
m_PrevActionTime = time;
m_LastMoveVector = movement;
}
else
{
m_ConsecutiveMoveCount = 0;
}
return axisEventData.used;
}
protected void ProcessMouseEvent()
{
ProcessMouseEvent(0);
}
[Obsolete("This method is no longer checked, overriding it with return true does nothing!")]
protected virtual bool ForceAutoSelect()
{
return false;
}
/// <summary>
/// Process all mouse events.
/// </summary>
protected void ProcessMouseEvent(int id)
{
var mouseData = GetMousePointerEventData(id);
var leftButtonData = mouseData.GetButtonState(PointerEventData.InputButton.Left).eventData;
m_CurrentFocusedGameObject = leftButtonData.buttonData.pointerCurrentRaycast.gameObject;
// Process the first mouse button fully
ProcessMousePress(leftButtonData);
ProcessMove(leftButtonData.buttonData);
ProcessDrag(leftButtonData.buttonData);
// Now process right / middle clicks
ProcessMousePress(mouseData.GetButtonState(PointerEventData.InputButton.Right).eventData);
ProcessDrag(mouseData.GetButtonState(PointerEventData.InputButton.Right).eventData.buttonData);
ProcessMousePress(mouseData.GetButtonState(PointerEventData.InputButton.Middle).eventData);
ProcessDrag(mouseData.GetButtonState(PointerEventData.InputButton.Middle).eventData.buttonData);
if (!Mathf.Approximately(leftButtonData.buttonData.scrollDelta.sqrMagnitude, 0.0f))
{
var scrollHandler = ExecuteEvents.GetEventHandler<IScrollHandler>(leftButtonData.buttonData.pointerCurrentRaycast.gameObject);
ExecuteEvents.ExecuteHierarchy(scrollHandler, leftButtonData.buttonData, ExecuteEvents.scrollHandler);
}
}
protected bool SendUpdateEventToSelectedObject()
{
if (eventSystem.currentSelectedGameObject == null)
return false;
var data = GetBaseEventData();
ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.updateSelectedHandler);
return data.used;
}
/// <summary>
/// Calculate and process any mouse button state changes.
/// </summary>
protected void ProcessMousePress(MouseButtonEventData data)
{
var pointerEvent = data.buttonData;
var currentOverGo = pointerEvent.pointerCurrentRaycast.gameObject;
// PointerDown notification
if (data.PressedThisFrame())
{
pointerEvent.eligibleForClick = true;
pointerEvent.delta = Vector2.zero;
pointerEvent.dragging = false;
pointerEvent.useDragThreshold = true;
pointerEvent.pressPosition = pointerEvent.position;
pointerEvent.pointerPressRaycast = pointerEvent.pointerCurrentRaycast;
DeselectIfSelectionChanged(currentOverGo, pointerEvent);
var resetDiffTime = Time.unscaledTime - pointerEvent.clickTime;
if (resetDiffTime >= doubleClickTime)
{
pointerEvent.clickCount = 0;
}
// search for the control that will receive the press
// if we can't find a press handler set the press
// handler to be what would receive a click.
var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.pointerDownHandler);
var newClick = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
// didnt find a press handler... search for a click handler
if (newPressed == null)
newPressed = newClick;
// Debug.Log("Pressed: " + newPressed);
float time = Time.unscaledTime;
if (newPressed == pointerEvent.lastPress)
{
var diffTime = time - pointerEvent.clickTime;
if (diffTime < doubleClickTime)
++pointerEvent.clickCount;
else
pointerEvent.clickCount = 1;
pointerEvent.clickTime = time;
}
else
{
pointerEvent.clickCount = 1;
}
pointerEvent.pointerPress = newPressed;
pointerEvent.rawPointerPress = currentOverGo;
pointerEvent.pointerClick = newClick;
pointerEvent.clickTime = time;
// Save the drag handler as well
pointerEvent.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(currentOverGo);
if (pointerEvent.pointerDrag != null)
ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.initializePotentialDrag);
m_InputPointerEvent = pointerEvent;
}
// PointerUp notification
if (data.ReleasedThisFrame())
{
ReleaseMouse(pointerEvent, currentOverGo);
}
}
protected GameObject GetCurrentFocusedGameObject()
{
return m_CurrentFocusedGameObject;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4f231c4fb786f3946a6b90b886c48677
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,266 @@
using System;
using System.Text;
using UnityEngine.Serialization;
namespace UnityEngine.EventSystems
{
[Obsolete("TouchInputModule is no longer required as Touch input is now handled in StandaloneInputModule.")]
[AddComponentMenu("Event/Touch Input Module")]
public class TouchInputModule : PointerInputModule
{
protected TouchInputModule()
{}
private Vector2 m_LastMousePosition;
private Vector2 m_MousePosition;
private PointerEventData m_InputPointerEvent;
[SerializeField]
[FormerlySerializedAs("m_AllowActivationOnStandalone")]
private bool m_ForceModuleActive;
[Obsolete("allowActivationOnStandalone has been deprecated. Use forceModuleActive instead (UnityUpgradable) -> forceModuleActive")]
public bool allowActivationOnStandalone
{
get { return m_ForceModuleActive; }
set { m_ForceModuleActive = value; }
}
public bool forceModuleActive
{
get { return m_ForceModuleActive; }
set { m_ForceModuleActive = value; }
}
public override void UpdateModule()
{
if (!eventSystem.isFocused)
{
if (m_InputPointerEvent != null && m_InputPointerEvent.pointerDrag != null && m_InputPointerEvent.dragging)
ExecuteEvents.Execute(m_InputPointerEvent.pointerDrag, m_InputPointerEvent, ExecuteEvents.endDragHandler);
m_InputPointerEvent = null;
}
m_LastMousePosition = m_MousePosition;
m_MousePosition = input.mousePosition;
}
public override bool IsModuleSupported()
{
return forceModuleActive || input.touchSupported;
}
public override bool ShouldActivateModule()
{
if (!base.ShouldActivateModule())
return false;
if (m_ForceModuleActive)
return true;
if (UseFakeInput())
{
bool wantsEnable = input.GetMouseButtonDown(0);
wantsEnable |= (m_MousePosition - m_LastMousePosition).sqrMagnitude > 0.0f;
return wantsEnable;
}
return input.touchCount > 0;
}
private bool UseFakeInput()
{
return !input.touchSupported;
}
public override void Process()
{
if (UseFakeInput())
FakeTouches();
else
ProcessTouchEvents();
}
/// <summary>
/// For debugging touch-based devices using the mouse.
/// </summary>
private void FakeTouches()
{
var pointerData = GetMousePointerEventData(0);
var leftPressData = pointerData.GetButtonState(PointerEventData.InputButton.Left).eventData;
// fake touches... on press clear delta
if (leftPressData.PressedThisFrame())
leftPressData.buttonData.delta = Vector2.zero;
ProcessTouchPress(leftPressData.buttonData, leftPressData.PressedThisFrame(), leftPressData.ReleasedThisFrame());
// only process move if we are pressed...
if (input.GetMouseButton(0))
{
ProcessMove(leftPressData.buttonData);
ProcessDrag(leftPressData.buttonData);
}
}
/// <summary>
/// Process all touch events.
/// </summary>
private void ProcessTouchEvents()
{
for (int i = 0; i < input.touchCount; ++i)
{
Touch touch = input.GetTouch(i);
if (touch.type == TouchType.Indirect)
continue;
bool released;
bool pressed;
var pointer = GetTouchPointerEventData(touch, out pressed, out released);
ProcessTouchPress(pointer, pressed, released);
if (!released)
{
ProcessMove(pointer);
ProcessDrag(pointer);
}
else
RemovePointerData(pointer);
}
}
protected void ProcessTouchPress(PointerEventData pointerEvent, bool pressed, bool released)
{
var currentOverGo = pointerEvent.pointerCurrentRaycast.gameObject;
// PointerDown notification
if (pressed)
{
pointerEvent.eligibleForClick = true;
pointerEvent.delta = Vector2.zero;
pointerEvent.dragging = false;
pointerEvent.useDragThreshold = true;
pointerEvent.pressPosition = pointerEvent.position;
pointerEvent.pointerPressRaycast = pointerEvent.pointerCurrentRaycast;
DeselectIfSelectionChanged(currentOverGo, pointerEvent);
if (pointerEvent.pointerEnter != currentOverGo)
{
// send a pointer enter to the touched element if it isn't the one to select...
HandlePointerExitAndEnter(pointerEvent, currentOverGo);
pointerEvent.pointerEnter = currentOverGo;
}
// search for the control that will receive the press
// if we can't find a press handler set the press
// handler to be what would receive a click.
var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.pointerDownHandler);
// didnt find a press handler... search for a click handler
if (newPressed == null)
newPressed = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
// Debug.Log("Pressed: " + newPressed);
float time = Time.unscaledTime;
if (newPressed == pointerEvent.lastPress)
{
var diffTime = time - pointerEvent.clickTime;
if (diffTime < 0.3f)
++pointerEvent.clickCount;
else
pointerEvent.clickCount = 1;
pointerEvent.clickTime = time;
}
else
{
pointerEvent.clickCount = 1;
}
pointerEvent.pointerPress = newPressed;
pointerEvent.rawPointerPress = currentOverGo;
pointerEvent.clickTime = time;
// Save the drag handler as well
pointerEvent.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(currentOverGo);
if (pointerEvent.pointerDrag != null)
ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.initializePotentialDrag);
m_InputPointerEvent = pointerEvent;
}
// PointerUp notification
if (released)
{
// Debug.Log("Executing pressup on: " + pointer.pointerPress);
ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler);
// Debug.Log("KeyCode: " + pointer.eventData.keyCode);
// see if we mouse up on the same element that we clicked on...
var pointerUpHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
// PointerClick and Drop events
if (pointerEvent.pointerPress == pointerUpHandler && pointerEvent.eligibleForClick)
{
ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerClickHandler);
}
else if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
{
ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.dropHandler);
}
pointerEvent.eligibleForClick = false;
pointerEvent.pointerPress = null;
pointerEvent.rawPointerPress = null;
if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.endDragHandler);
pointerEvent.dragging = false;
pointerEvent.pointerDrag = null;
// send exit events as we need to simulate this on touch up on touch device
ExecuteEvents.ExecuteHierarchy(pointerEvent.pointerEnter, pointerEvent, ExecuteEvents.pointerExitHandler);
pointerEvent.pointerEnter = null;
m_InputPointerEvent = pointerEvent;
}
}
public override void DeactivateModule()
{
base.DeactivateModule();
ClearSelection();
}
public override string ToString()
{
var sb = new StringBuilder();
sb.AppendLine(UseFakeInput() ? "Input: Faked" : "Input: Touch");
if (UseFakeInput())
{
var pointerData = GetLastPointerEventData(kMouseLeftId);
if (pointerData != null)
sb.AppendLine(pointerData.ToString());
}
else
{
foreach (var pointerEventData in m_PointerData)
sb.AppendLine(pointerEventData.ToString());
}
return sb.ToString();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2d49b7c1bcd2e07499844da127be038d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,569 @@
namespace UnityEngine.EventSystems
{
/// <summary>
/// This is an 4 direction movement enum.
/// </summary>
/// <remarks>
/// MoveDirection provides a way of switching between moving states. You must assign these states to actions, such as moving the GameObject by an up vector when in the Up state.
/// Having states like these are easier to identify than always having to include a large amount of vectors and calculations.Instead, you define what you want the state to do in only one part, and switch to the appropriate state when it is needed.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// //This is a full example of how a GameObject changes direction using MoveDirection states
/// //Assign this script to a visible GameObject (with a Rigidbody attached) to see it in action
///
/// using UnityEngine;
/// using UnityEngine.EventSystems;
///
/// public class Example : MonoBehaviour
/// {
/// Vector3 m_StartPosition, m_StartForce;
/// Rigidbody m_Rigidbody;
/// //Use Enum for easy switching between direction states
/// MoveDirection m_MoveDirection;
///
/// //Use these Vectors for moving Rigidbody components
/// Vector3 m_ResetVector;
/// Vector3 m_UpVector;
/// Vector3 m_RightVector;
/// const float speed = 5.0f;
///
/// void Start()
/// {
/// //You get the Rigidbody component attached to the GameObject
/// m_Rigidbody = GetComponent<Rigidbody>();
/// //This starts with the Rigidbody not moving in any direction at all
/// m_MoveDirection = MoveDirection.None;
///
/// //These are the GameObjects starting position and Rigidbody position
/// m_StartPosition = transform.position;
/// m_StartForce = m_Rigidbody.transform.position;
///
/// //This Vector is set to 1 in the y axis (for moving upwards)
/// m_UpVector = Vector3.up;
/// //This Vector is set to 1 in the x axis (for moving in the right direction)
/// m_RightVector = Vector3.right;
/// //This Vector is zeroed out for when the Rigidbody should not move
/// m_ResetVector = Vector3.zero;
/// }
///
/// void Update()
/// {
/// //This switches the direction depending on button presses
/// switch (m_MoveDirection)
/// {
/// //The starting state which resets the object
/// case MoveDirection.None:
/// //Reset to the starting position of the GameObject and Rigidbody
/// transform.position = m_StartPosition;
/// m_Rigidbody.transform.position = m_StartForce;
/// //This resets the velocity of the Rigidbody
/// m_Rigidbody.velocity = m_ResetVector;
/// break;
///
/// //This is for moving in an upwards direction
/// case MoveDirection.Up:
/// //Change the velocity so that the Rigidbody travels upwards
/// m_Rigidbody.velocity = m_UpVector * speed;
/// break;
///
/// //This is for moving left
/// case MoveDirection.Left:
/// //This moves the Rigidbody to the left (minus right Vector)
/// m_Rigidbody.velocity = -m_RightVector * speed;
/// break;
///
/// //This is for moving right
/// case MoveDirection.Right:
/// //This moves the Rigidbody to the right
/// m_Rigidbody.velocity = m_RightVector * speed;
/// break;
///
/// //This is for moving down
/// case MoveDirection.Down:
/// //This moves the Rigidbody down
/// m_Rigidbody.velocity = -m_UpVector * speed;
/// break;
/// }
/// }
///
/// void OnGUI()
/// {
/// //Press the reset Button to switch to no mode
/// if (GUI.Button(new Rect(100, 0, 150, 30), "Reset"))
/// {
/// //Switch to start/reset case
/// m_MoveDirection = MoveDirection.None;
/// }
///
/// //Press the Left button to switch the Rigidbody direction to the left
/// if (GUI.Button(new Rect(100, 30, 150, 30), "Move Left"))
/// {
/// //Switch to the left direction
/// m_MoveDirection = MoveDirection.Left;
/// }
///
/// //Press the Up button to switch the Rigidbody direction to upwards
/// if (GUI.Button(new Rect(100, 60, 150, 30), "Move Up"))
/// {
/// //Switch to Up Direction
/// m_MoveDirection = MoveDirection.Up;
/// }
///
/// //Press the Down button to switch the direction to down
/// if (GUI.Button(new Rect(100, 90, 150, 30), "Move Down"))
/// {
/// //Switch to Down Direction
/// m_MoveDirection = MoveDirection.Down;
/// }
///
/// //Press the right button to switch to the right direction
/// if (GUI.Button(new Rect(100, 120, 150, 30), "Move Right"))
/// {
/// //Switch to Right Direction
/// m_MoveDirection = MoveDirection.Right;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public enum MoveDirection
{
/// <summary>
/// This is the Left state of MoveDirection. Assign functionality for moving to the left.
/// </summary>
/// <remarks>
/// Use the Left state for an easily identifiable way of moving a GameObject to the left (-1 , 0 , 0). This is a state without any predefined functionality. Before using this state, you should define what your GameObject will do in code.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// //Assign this script to a visible GameObject (with a Rigidbody attached) to see this in action
///
/// using UnityEngine;
/// using UnityEngine.EventSystems;
///
/// public class Example : MonoBehaviour
/// {
/// Vector3 m_StartPosition, m_StartForce;
/// Rigidbody m_Rigidbody;
/// //Use Enum for easy switching between direction states
/// MoveDirection m_MoveDirection;
///
/// //Use these Vectors for moving Rigidbody components
/// Vector3 m_ResetVector;
/// Vector3 m_RightVector;
/// const float speed = 5.0f;
///
/// void Start()
/// {
/// //You get the Rigidbody component attached to the GameObject
/// m_Rigidbody = GetComponent<Rigidbody>();
/// //This starts with the Rigidbody not moving in any direction at all
/// m_MoveDirection = MoveDirection.None;
///
/// //These are the GameObjects starting position and Rigidbody position
/// m_StartPosition = transform.position;
/// m_StartForce = m_Rigidbody.transform.position;
///
/// //This Vector is set to 1 in the x axis (for moving in the right direction)
/// m_RightVector = Vector3.right;
/// //This Vector is zeroed out for when the Rigidbody should not move
/// m_ResetVector = Vector3.zero;
/// }
///
/// void Update()
/// {
/// //This switches the direction depending on button presses
/// switch (m_MoveDirection)
/// {
/// //The starting state which resets the object
/// case MoveDirection.None:
/// //Reset to the starting position of the GameObject and Rigidbody
/// transform.position = m_StartPosition;
/// m_Rigidbody.transform.position = m_StartForce;
/// //This resets the velocity of the Rigidbody
/// m_Rigidbody.velocity = m_ResetVector;
/// break;
///
/// //This is for moving left
/// case MoveDirection.Left:
/// //This moves the Rigidbody to the left (minus right Vector)
/// m_Rigidbody.velocity = -m_RightVector * speed;
/// break;
/// }
/// }
///
/// void OnGUI()
/// {
/// //Press the reset Button to switch to no mode
/// if (GUI.Button(new Rect(100, 0, 150, 30), "Reset"))
/// {
/// //Switch to start/reset case
/// m_MoveDirection = MoveDirection.None;
/// }
///
/// //Press the Left button to switch the Rigidbody direction to the left
/// if (GUI.Button(new Rect(100, 30, 150, 30), "Move Left"))
/// {
/// //Switch to the left direction
/// m_MoveDirection = MoveDirection.Left;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
Left,
/// <summary>
/// This is the Up state of MoveDirection. Assign functionality for moving in an upward direction.
/// </summary>
/// <remarks>
/// Use the Up state for an easily identifiable way of moving a GameObject upwards (0 , 1 , 0). This is a state without any predefined functionality. Before using this state, you should define what your GameObject will do in code.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// //Attach this script to a GameObject with a Rigidbody component. Press the "Move Up" button in Game view to see it in action.
///
/// using UnityEngine;
/// using UnityEngine.EventSystems;
///
/// public class Example : MonoBehaviour
/// {
/// Vector3 m_StartPosition, m_StartForce;
/// Rigidbody m_Rigidbody;
/// //Use Enum for easy switching between direction states
/// MoveDirection m_MoveDirection;
///
/// //Use these Vectors for moving Rigidbody components
/// Vector3 m_ResetVector;
/// Vector3 m_UpVector;
/// const float speed = 10.0f;
///
/// void Start()
/// {
/// //You get the Rigidbody component attached to the GameObject
/// m_Rigidbody = GetComponent<Rigidbody>();
/// //This starts with the Rigidbody not moving in any direction at all
/// m_MoveDirection = MoveDirection.None;
///
/// //These are the GameObjects starting position and Rigidbody position
/// m_StartPosition = transform.position;
/// m_StartForce = m_Rigidbody.transform.position;
///
/// //This Vector is set to 1 in the y axis (for moving upwards)
/// m_UpVector = Vector3.up;
/// //This Vector is zeroed out for when the Rigidbody should not move
/// m_ResetVector = Vector3.zero;
/// }
///
/// void Update()
/// {
/// //This switches the direction depending on button presses
/// switch (m_MoveDirection)
/// {
/// //The starting state which resets the object
/// case MoveDirection.None:
/// //Reset to the starting position of the GameObject and Rigidbody
/// transform.position = m_StartPosition;
/// m_Rigidbody.transform.position = m_StartForce;
/// //This resets the velocity of the Rigidbody
/// m_Rigidbody.velocity = m_ResetVector;
/// break;
///
/// //This is for moving in an upwards direction
/// case MoveDirection.Up:
/// //Change the velocity so that the Rigidbody travels upwards
/// m_Rigidbody.velocity = m_UpVector * speed;
/// break;
/// }
/// }
///
/// void OnGUI()
/// {
/// //Press the reset Button to switch to no mode
/// if (GUI.Button(new Rect(100, 0, 150, 30), "Reset"))
/// {
/// //Switch to start/reset case
/// m_MoveDirection = MoveDirection.None;
/// }
///
/// //Press the Up button to switch the Rigidbody direction to upwards
/// if (GUI.Button(new Rect(100, 60, 150, 30), "Move Up"))
/// {
/// //Switch to Up Direction
/// m_MoveDirection = MoveDirection.Up;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
Up,
/// <summary>
/// This is the Right state of MoveDirection. Assign functionality for moving to the right.
/// </summary>
/// <remarks>
/// Use the Right state for an easily identifiable way of moving a GameObject to the right (1 , 0 , 0). This is a state without any predefined functionality. Before using this state, you should define what your GameObject will do in code.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// //Attach this script to a GameObject with a Rigidbody component. Press the "Move Right" button in Game view to see it in action.
///
/// using UnityEngine;
/// using UnityEngine.EventSystems;
///
/// public class MoveDirectionExample : MonoBehaviour
/// {
/// Vector3 m_StartPosition, m_StartForce;
/// Rigidbody m_Rigidbody;
/// //Use Enum for easy switching between direction states
/// MoveDirection m_MoveDirection;
///
/// //Use these Vectors for moving Rigidbody components
/// Vector3 m_ResetVector;
/// Vector3 m_RightVector;
/// const float speed = 5.0f;
///
/// void Start()
/// {
/// //You get the Rigidbody component attached to the GameObject
/// m_Rigidbody = GetComponent<Rigidbody>();
/// //This starts with the Rigidbody not moving in any direction at all
/// m_MoveDirection = MoveDirection.None;
///
/// //These are the GameObjects starting position and Rigidbody position
/// m_StartPosition = transform.position;
/// m_StartForce = m_Rigidbody.transform.position;
///
/// //This Vector is set to 1 in the x axis (for moving in the right direction)
/// m_RightVector = Vector3.right;
/// //This Vector is zeroed out for when the Rigidbody should not move
/// m_ResetVector = Vector3.zero;
/// }
///
/// void Update()
/// {
/// //This switches the direction depending on button presses
/// switch (m_MoveDirection)
/// {
/// //The starting state which resets the object
/// case MoveDirection.None:
/// //Reset to the starting position of the GameObject and Rigidbody
/// transform.position = m_StartPosition;
/// m_Rigidbody.transform.position = m_StartForce;
/// //This resets the velocity of the Rigidbody
/// m_Rigidbody.velocity = m_ResetVector;
/// break;
///
/// //This is for moving right
/// case MoveDirection.Right:
/// //This moves the Rigidbody to the right
/// m_Rigidbody.velocity = m_RightVector * speed;
/// break;
/// }
/// }
///
/// void OnGUI()
/// {
/// //Press the reset Button to switch to no mode
/// if (GUI.Button(new Rect(100, 0, 150, 30), "Reset"))
/// {
/// //Switch to start/reset case
/// m_MoveDirection = MoveDirection.None;
/// }
///
/// //Press the Left button to switch the Rigidbody direction to the right
/// if (GUI.Button(new Rect(100, 30, 150, 30), "Move Right"))
/// {
/// //Switch to the left direction
/// m_MoveDirection = MoveDirection.Right;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
Right,
/// <summary>
/// The Down State of MoveDirection. Assign functionality for moving in a downward direction.
/// </summary>
/// <remarks>
/// Use the Down state for an easily identifiable way of moving a GameObject downwards (0 , -1 , 0). This is a state without any predefined functionality. Before using this state, you should define what your GameObject will do in code.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// //Attach this script to a GameObject with a Rigidbody component. Press the "Move Down" button in Game view to see it in action.
///
/// using UnityEngine;
/// using UnityEngine.EventSystems;
///
/// public class Example : MonoBehaviour
/// {
/// Vector3 m_StartPosition, m_StartForce;
/// Rigidbody m_Rigidbody;
/// //Use Enum for easy switching between direction states
/// MoveDirection m_MoveDirection;
///
/// //Use these Vectors for moving Rigidbody components
/// Vector3 m_ResetVector;
/// Vector3 m_UpVector;
/// const float speed = 10.0f;
///
/// void Start()
/// {
/// //You get the Rigidbody component attached to the GameObject
/// m_Rigidbody = GetComponent<Rigidbody>();
/// //This starts with the Rigidbody not moving in any direction at all
/// m_MoveDirection = MoveDirection.None;
///
/// //These are the GameObjects starting position and Rigidbody position
/// m_StartPosition = transform.position;
/// m_StartForce = m_Rigidbody.transform.position;
///
/// //This Vector is set to 1 in the y axis (for moving upwards)
/// m_UpVector = Vector3.up;
/// //This Vector is zeroed out for when the Rigidbody should not move
/// m_ResetVector = Vector3.zero;
/// }
///
/// void Update()
/// {
/// //This switches the direction depending on button presses
/// switch (m_MoveDirection)
/// {
/// //The starting state which resets the object
/// case MoveDirection.None:
/// //Reset to the starting position of the GameObject and Rigidbody
/// transform.position = m_StartPosition;
/// m_Rigidbody.transform.position = m_StartForce;
/// //This resets the velocity of the Rigidbody
/// m_Rigidbody.velocity = m_ResetVector;
/// break;
///
/// //This is for moving down
/// case MoveDirection.Down:
/// //This moves the Rigidbody down
/// m_Rigidbody.velocity = -m_UpVector * speed;
/// break;
/// }
/// }
///
/// void OnGUI()
/// {
/// //Press the reset Button to switch to no mode
/// if (GUI.Button(new Rect(100, 0, 150, 30), "Reset"))
/// {
/// //Switch to start/reset case
/// m_MoveDirection = MoveDirection.None;
/// }
///
/// //Press the Down button to switch the direction to down
/// if (GUI.Button(new Rect(100, 90, 150, 30), "Move Down"))
/// {
/// //Switch to Down Direction
/// m_MoveDirection = MoveDirection.Down;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
Down,
/// <summary>
/// This is the None state. Assign functionality that stops movement.
/// </summary>
/// <remarks>
/// Use the None state for an easily identifiable way of stopping, resetting or initialising a GameObject's movement. This is a state without any predefined functionality. Before using this state, you should define what your GameObject will do in code.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// //Attach this script to a GameObject with a Rigidbody attached. This script starts off on the ModeDirection.None state but changes depending on buttons you press.
///
/// using UnityEngine;
/// using UnityEngine.EventSystems;
///
/// public class Example : MonoBehaviour
/// {
/// Vector3 m_StartPosition, m_StartForce;
/// Rigidbody m_Rigidbody;
/// //Use Enum for easy switching between direction states
/// MoveDirection m_MoveDirection;
///
/// //Use these Vectors for moving Rigidbody components
/// Vector3 m_ResetVector;
/// Vector3 m_UpVector;
/// const float speed = 10.0f;
///
/// void Start()
/// {
/// //You get the Rigidbody component attached to the GameObject
/// m_Rigidbody = GetComponent<Rigidbody>();
/// //This starts with the Rigidbody not moving in any direction at all
/// m_MoveDirection = MoveDirection.None;
///
/// //These are the GameObjects starting position and Rigidbody position
/// m_StartPosition = transform.position;
/// m_StartForce = m_Rigidbody.transform.position;
///
/// //This Vector is set to 1 in the y axis (for moving upwards)
/// m_UpVector = Vector3.up;
/// //This Vector is zeroed out for when the Rigidbody should not move
/// m_ResetVector = Vector3.zero;
/// }
///
/// void Update()
/// {
/// //This switches the direction depending on button presses
/// switch (m_MoveDirection)
/// {
/// //The starting state which resets the object
/// case MoveDirection.None:
/// //Reset to the starting position of the GameObject and Rigidbody
/// transform.position = m_StartPosition;
/// m_Rigidbody.transform.position = m_StartForce;
/// //This resets the velocity of the Rigidbody
/// m_Rigidbody.velocity = m_ResetVector;
/// break;
///
/// //This is for moving down
/// case MoveDirection.Down:
/// //This moves the Rigidbody down
/// m_Rigidbody.velocity = -m_UpVector * speed;
/// break;
/// }
/// }
///
/// void OnGUI()
/// {
/// //Press the reset Button to switch to no mode
/// if (GUI.Button(new Rect(100, 0, 150, 30), "Reset"))
/// {
/// //Switch to start/reset case
/// m_MoveDirection = MoveDirection.None;
/// }
///
/// //Press the Down button to switch the direction to down
/// if (GUI.Button(new Rect(100, 90, 150, 30), "Move Down"))
/// {
/// //Switch to Down Direction
/// m_MoveDirection = MoveDirection.Down;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
None
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4d2250412b81fe34abf39f246e274479
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,143 @@
namespace UnityEngine.EventSystems
{
/// <summary>
/// A hit result from a BaseRaycaster.
/// </summary>
public struct RaycastResult
{
private GameObject m_GameObject; // Game object hit by the raycast
/// <summary>
/// The GameObject that was hit by the raycast.
/// </summary>
public GameObject gameObject
{
get { return m_GameObject; }
set { m_GameObject = value; }
}
/// <summary>
/// BaseRaycaster that raised the hit.
/// </summary>
public BaseRaycaster module;
/// <summary>
/// Distance to the hit.
/// </summary>
public float distance;
/// <summary>
/// Hit index
/// </summary>
public float index;
/// <summary>
/// Used by raycasters where elements may have the same unit distance, but have specific ordering.
/// </summary>
public int depth;
/// <summary>
/// The sorting group ID when the hit object is influenced by a SortingGroup.
/// </summary>
/// <remarks>
/// For UI.Graphic elements will always be 0.
/// For 3D objects this will always be 0.
/// For 2D objects if a SortingOrder is influencing the same object as the hit collider then the renderers sortingGroupID will be used; otherwise SortingGroup.invalidSortingGroupID.
/// </remarks>
public int sortingGroupID;
/// <summary>
/// The sorting group order when the hit object is influenced by a SortingGroup.
/// </summary>
/// <remarks>
/// For UI.Graphic elements this will always be 0.
/// For 3D objects this will always be 0.
/// For 2D objects if a SortingOrder is influencing the same object as the hit collider then the renderers sortingGroupOrder will be used.
/// </remarks>
public int sortingGroupOrder;
/// <summary>
/// The SortingLayer of the hit object.
/// </summary>
/// <remarks>
/// For UI.Graphic elements this will be the values from that graphic's Canvas
/// For 3D objects this will always be 0.
/// For 2D objects if a 2D Renderer (Sprite, Tilemap, SpriteShape) is attached to the same object as the hit collider that sortingLayerID will be used.
/// </remarks>
public int sortingLayer;
/// <summary>
/// The SortingOrder for the hit object.
/// </summary>
/// <remarks>
/// For Graphic elements this will be the values from that graphics Canvas
/// For 3D objects this will always be 0.
/// For 2D objects if a 2D Renderer (Sprite, Tilemap, SpriteShape) is attached to the same object as the hit collider that sortingOrder will be used.
/// </remarks>
public int sortingOrder;
/// <summary>
/// The world position of the where the raycast has hit.
/// </summary>
public Vector3 worldPosition;
/// <summary>
/// The normal at the hit location of the raycast.
/// </summary>
public Vector3 worldNormal;
/// <summary>
/// The screen position from which the raycast was generated.
/// </summary>
public Vector2 screenPosition;
/// <summary>
/// The display index from which the raycast was generated.
/// </summary>
public int displayIndex;
/// <summary>
/// Is there an associated module and a hit GameObject.
/// </summary>
public bool isValid
{
get { return module != null && gameObject != null; }
}
/// <summary>
/// Reset the result.
/// </summary>
public void Clear()
{
gameObject = null;
module = null;
distance = 0;
index = 0;
depth = 0;
sortingLayer = 0;
sortingOrder = 0;
worldNormal = Vector3.up;
worldPosition = Vector3.zero;
screenPosition = Vector3.zero;
}
public override string ToString()
{
if (!isValid)
return "";
return "Name: " + gameObject + "\n" +
"module: " + module + "\n" +
"distance: " + distance + "\n" +
"index: " + index + "\n" +
"depth: " + depth + "\n" +
"worldNormal: " + worldNormal + "\n" +
"worldPosition: " + worldPosition + "\n" +
"screenPosition: " + screenPosition + "\n" +
"module.sortOrderPriority: " + module.sortOrderPriority + "\n" +
"module.renderOrderPriority: " + module.renderOrderPriority + "\n" +
"sortingLayer: " + sortingLayer + "\n" +
"sortingOrder: " + sortingOrder;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3550d8ec6f29ab34d895ae9a43d560c2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,32 @@
using System.Collections.Generic;
namespace UnityEngine.EventSystems
{
public static class RaycasterManager
{
private static readonly List<BaseRaycaster> s_Raycasters = new List<BaseRaycaster>();
internal static void AddRaycaster(BaseRaycaster baseRaycaster)
{
if (s_Raycasters.Contains(baseRaycaster))
return;
s_Raycasters.Add(baseRaycaster);
}
/// <summary>
/// List of BaseRaycasters that has been registered.
/// </summary>
public static List<BaseRaycaster> GetRaycasters()
{
return s_Raycasters;
}
internal static void RemoveRaycasters(BaseRaycaster baseRaycaster)
{
if (!s_Raycasters.Contains(baseRaycaster))
return;
s_Raycasters.Remove(baseRaycaster);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 26570be2af04195458e6f1ac1f5c48e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 46646a5562f14984690c85ee7b946bc9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.EventSystems
{
/// <summary>
/// Base class for any RayCaster.
/// </summary>
/// <remarks>
/// A Raycaster is responsible for raycasting against scene elements to determine if the cursor is over them. Default Raycasters include PhysicsRaycaster, Physics2DRaycaster, GraphicRaycaster.
/// Custom raycasters can be added by extending this class.
/// </remarks>
public abstract class BaseRaycaster : UIBehaviour
{
private BaseRaycaster m_RootRaycaster;
/// <summary>
/// Raycast against the scene.
/// </summary>
/// <param name="eventData">Current event data.</param>
/// <param name="resultAppendList">List of hit Objects.</param>
public abstract void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList);
/// <summary>
/// The camera that will generate rays for this raycaster.
/// </summary>
public abstract Camera eventCamera { get; }
[Obsolete("Please use sortOrderPriority and renderOrderPriority", false)]
public virtual int priority
{
get { return 0; }
}
/// <summary>
/// Priority of the raycaster based upon sort order.
/// </summary>
public virtual int sortOrderPriority
{
get { return int.MinValue; }
}
/// <summary>
/// Priority of the raycaster based upon render order.
/// </summary>
public virtual int renderOrderPriority
{
get { return int.MinValue; }
}
/// <summary>
/// Raycaster on root canvas
/// </summary>
public BaseRaycaster rootRaycaster
{
get
{
if (m_RootRaycaster == null)
{
var baseRaycasters = GetComponentsInParent<BaseRaycaster>();
if (baseRaycasters.Length != 0)
m_RootRaycaster = baseRaycasters[baseRaycasters.Length - 1];
}
return m_RootRaycaster;
}
}
public override string ToString()
{
return "Name: " + gameObject + "\n" +
"eventCamera: " + eventCamera + "\n" +
"sortOrderPriority: " + sortOrderPriority + "\n" +
"renderOrderPriority: " + renderOrderPriority;
}
protected override void OnEnable()
{
base.OnEnable();
RaycasterManager.AddRaycaster(this);
}
protected override void OnDisable()
{
RaycasterManager.RemoveRaycasters(this);
base.OnDisable();
}
protected override void OnCanvasHierarchyChanged()
{
base.OnCanvasHierarchyChanged();
m_RootRaycaster = null;
}
protected override void OnTransformParentChanged()
{
base.OnTransformParentChanged();
m_RootRaycaster = null;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 426106349a0ff964fa4e7178c1d3a4f5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,120 @@
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine.Rendering;
#if PACKAGE_TILEMAP
using UnityEngine.Tilemaps;
#endif
using UnityEngine.U2D;
namespace UnityEngine.EventSystems
{
/// <summary>
/// Simple event system using physics raycasts.
/// </summary>
[AddComponentMenu("Event/Physics 2D Raycaster")]
[RequireComponent(typeof(Camera))]
/// <summary>
/// Raycaster for casting against 2D Physics components.
/// </summary>
public class Physics2DRaycaster : PhysicsRaycaster
{
#if PACKAGE_PHYSICS2D
RaycastHit2D[] m_Hits;
#endif
protected Physics2DRaycaster()
{}
/// <summary>
/// Raycast against 2D elements in the scene.
/// </summary>
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
{
#if PACKAGE_PHYSICS2D
Ray ray = new Ray();
float distanceToClipPlane = 0;
int displayIndex = 0;
if (!ComputeRayAndDistance(eventData, ref ray, ref displayIndex, ref distanceToClipPlane))
return;
int hitCount = 0;
if (maxRayIntersections == 0)
{
if (ReflectionMethodsCache.Singleton.getRayIntersectionAll == null)
return;
m_Hits = ReflectionMethodsCache.Singleton.getRayIntersectionAll(ray, distanceToClipPlane, finalEventMask);
hitCount = m_Hits.Length;
}
else
{
if (ReflectionMethodsCache.Singleton.getRayIntersectionAllNonAlloc == null)
return;
if (m_LastMaxRayIntersections != m_MaxRayIntersections)
{
m_Hits = new RaycastHit2D[maxRayIntersections];
m_LastMaxRayIntersections = m_MaxRayIntersections;
}
hitCount = ReflectionMethodsCache.Singleton.getRayIntersectionAllNonAlloc(ray, m_Hits, distanceToClipPlane, finalEventMask);
}
if (hitCount != 0)
{
for (int b = 0, bmax = hitCount; b < bmax; ++b)
{
Renderer r2d = null;
// Case 1198442: Check for 2D renderers when filling in RaycastResults
var rendererResult = m_Hits[b].collider.gameObject.GetComponent<Renderer>();
if (rendererResult != null)
{
if (rendererResult is SpriteRenderer)
{
r2d = rendererResult;
}
#if PACKAGE_TILEMAP
if (rendererResult is TilemapRenderer)
{
r2d = rendererResult;
}
#endif
if (rendererResult is SpriteShapeRenderer)
{
r2d = rendererResult;
}
}
var result = new RaycastResult
{
gameObject = m_Hits[b].collider.gameObject,
module = this,
distance = m_Hits[b].distance,
worldPosition = m_Hits[b].point,
worldNormal = m_Hits[b].normal,
screenPosition = eventData.position,
displayIndex = displayIndex,
index = resultAppendList.Count,
sortingGroupID = r2d != null ? r2d.sortingGroupID : SortingGroup.invalidSortingGroupID,
sortingGroupOrder = r2d != null ? r2d.sortingGroupOrder : 0,
sortingLayer = r2d != null ? r2d.sortingLayerID : 0,
sortingOrder = r2d != null ? r2d.sortingOrder : 0
};
if (result.sortingGroupID != SortingGroup.invalidSortingGroupID &&
SortingGroup.GetSortingGroupByIndex(r2d.sortingGroupID) is SortingGroup sortingGroup)
{
// Calculate how far along the ray the sorting group is.
result.distance = Vector3.Dot(ray.direction, sortingGroup.transform.position - ray.origin);
result.sortingLayer = sortingGroup.sortingLayerID;
result.sortingOrder = sortingGroup.sortingOrder;
}
resultAppendList.Add(result);
}
}
#endif
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 56666c5a40171f54783dd416a44f42bf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,207 @@
using System.Collections.Generic;
using UnityEngine.UI;
namespace UnityEngine.EventSystems
{
/// <summary>
/// Simple event system using physics raycasts.
/// </summary>
[AddComponentMenu("Event/Physics Raycaster")]
[RequireComponent(typeof(Camera))]
/// <summary>
/// Raycaster for casting against 3D Physics components.
/// </summary>
public class PhysicsRaycaster : BaseRaycaster
{
/// <summary>
/// Const to use for clarity when no event mask is set
/// </summary>
protected const int kNoEventMaskSet = -1;
protected Camera m_EventCamera;
/// <summary>
/// Layer mask used to filter events. Always combined with the camera's culling mask if a camera is used.
/// </summary>
[SerializeField]
protected LayerMask m_EventMask = kNoEventMaskSet;
/// <summary>
/// The max number of intersections allowed. 0 = allocating version anything else is non alloc.
/// </summary>
[SerializeField]
protected int m_MaxRayIntersections = 0;
protected int m_LastMaxRayIntersections = 0;
#if PACKAGE_PHYSICS
RaycastHit[] m_Hits;
#endif
protected PhysicsRaycaster()
{}
public override Camera eventCamera
{
get
{
if (m_EventCamera == null)
m_EventCamera = GetComponent<Camera>();
if (m_EventCamera == null)
return Camera.main;
return m_EventCamera ;
}
}
/// <summary>
/// Depth used to determine the order of event processing.
/// </summary>
public virtual int depth
{
get { return (eventCamera != null) ? (int)eventCamera.depth : 0xFFFFFF; }
}
/// <summary>
/// Event mask used to determine which objects will receive events.
/// </summary>
public int finalEventMask
{
get { return (eventCamera != null) ? eventCamera.cullingMask & m_EventMask : kNoEventMaskSet; }
}
/// <summary>
/// Layer mask used to filter events. Always combined with the camera's culling mask if a camera is used.
/// </summary>
public LayerMask eventMask
{
get { return m_EventMask; }
set { m_EventMask = value; }
}
/// <summary>
/// Max number of ray intersection allowed to be found.
/// </summary>
/// <remarks>
/// A value of zero will represent using the allocating version of the raycast function where as any other value will use the non allocating version.
/// </remarks>
public int maxRayIntersections
{
get { return m_MaxRayIntersections; }
set { m_MaxRayIntersections = value; }
}
/// <summary>
/// Returns a ray going from camera through the event position and the distance between the near and far clipping planes along that ray.
/// </summary>
/// <param name="eventData">The pointer event for which we will cast a ray.</param>
/// <param name="ray">The ray to use.</param>
/// <param name="eventDisplayIndex">The display index used.</param>
/// <param name="distanceToClipPlane">The distance between the near and far clipping planes along the ray.</param>
/// <returns>True if the operation was successful. false if it was not possible to compute, such as the eventPosition being outside of the view.</returns>
protected bool ComputeRayAndDistance(PointerEventData eventData, ref Ray ray, ref int eventDisplayIndex, ref float distanceToClipPlane)
{
if (eventCamera == null)
return false;
var eventPosition = MultipleDisplayUtilities.RelativeMouseAtScaled(eventData.position);
if (eventPosition != Vector3.zero)
{
// We support multiple display and display identification based on event position.
eventDisplayIndex = (int)eventPosition.z;
// Discard events that are not part of this display so the user does not interact with multiple displays at once.
if (eventDisplayIndex != eventCamera.targetDisplay)
return false;
}
else
{
// The multiple display system is not supported on all platforms, when it is not supported the returned position
// will be all zeros so when the returned index is 0 we will default to the event data to be safe.
eventPosition = eventData.position;
}
// Cull ray casts that are outside of the view rect. (case 636595)
if (!eventCamera.pixelRect.Contains(eventPosition))
return false;
ray = eventCamera.ScreenPointToRay(eventPosition);
// compensate far plane distance - see MouseEvents.cs
float projectionDirection = ray.direction.z;
distanceToClipPlane = Mathf.Approximately(0.0f, projectionDirection)
? Mathf.Infinity
: Mathf.Abs((eventCamera.farClipPlane - eventCamera.nearClipPlane) / projectionDirection);
return true;
}
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
{
#if PACKAGE_PHYSICS
Ray ray = new Ray();
int displayIndex = 0;
float distanceToClipPlane = 0;
if (!ComputeRayAndDistance(eventData, ref ray, ref displayIndex, ref distanceToClipPlane))
return;
int hitCount = 0;
if (m_MaxRayIntersections == 0)
{
if (ReflectionMethodsCache.Singleton.raycast3DAll == null)
return;
m_Hits = ReflectionMethodsCache.Singleton.raycast3DAll(ray, distanceToClipPlane, finalEventMask);
hitCount = m_Hits.Length;
}
else
{
if (ReflectionMethodsCache.Singleton.getRaycastNonAlloc == null)
return;
if (m_LastMaxRayIntersections != m_MaxRayIntersections)
{
m_Hits = new RaycastHit[m_MaxRayIntersections];
m_LastMaxRayIntersections = m_MaxRayIntersections;
}
hitCount = ReflectionMethodsCache.Singleton.getRaycastNonAlloc(ray, m_Hits, distanceToClipPlane, finalEventMask);
}
if (hitCount != 0)
{
if (hitCount > 1)
System.Array.Sort(m_Hits, 0, hitCount, RaycastHitComparer.instance);
for (int b = 0, bmax = hitCount; b < bmax; ++b)
{
var result = new RaycastResult
{
gameObject = m_Hits[b].collider.gameObject,
module = this,
distance = m_Hits[b].distance,
worldPosition = m_Hits[b].point,
worldNormal = m_Hits[b].normal,
screenPosition = eventData.position,
displayIndex = displayIndex,
index = resultAppendList.Count,
sortingLayer = 0,
sortingOrder = 0
};
resultAppendList.Add(result);
}
}
#endif
}
#if PACKAGE_PHYSICS
private class RaycastHitComparer : IComparer<RaycastHit>
{
public static RaycastHitComparer instance = new RaycastHitComparer();
public int Compare(RaycastHit x, RaycastHit y)
{
return x.distance.CompareTo(y.distance);
}
}
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c49b4cc203aa6414fae5c798d1d0e7d6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,76 @@
namespace UnityEngine.EventSystems
{
/// <summary>
/// Base behaviour that has protected implementations of Unity lifecycle functions.
/// </summary>
public abstract class UIBehaviour : MonoBehaviour
{
protected virtual void Awake()
{}
protected virtual void OnEnable()
{}
protected virtual void Start()
{}
protected virtual void OnDisable()
{}
protected virtual void OnDestroy()
{}
/// <summary>
/// Returns true if the GameObject and the Component are active.
/// </summary>
public virtual bool IsActive()
{
return isActiveAndEnabled;
}
#if UNITY_EDITOR
protected virtual void OnValidate()
{}
protected virtual void Reset()
{}
#endif
/// <summary>
/// This callback is called when the dimensions of an associated RectTransform change. It is always called before Awake, OnEnable, or Start. The call is also made to all child RectTransforms, regardless of whether their dimensions change (which depends on how they are anchored).
/// </summary>
protected virtual void OnRectTransformDimensionsChange()
{}
protected virtual void OnBeforeTransformParentChanged()
{}
protected virtual void OnTransformParentChanged()
{}
protected virtual void OnDidApplyAnimationProperties()
{}
protected virtual void OnCanvasGroupChanged()
{}
/// <summary>
/// Called when the state of the parent Canvas is changed.
/// </summary>
protected virtual void OnCanvasHierarchyChanged()
{}
/// <summary>
/// Returns true if the native representation of the behaviour has been destroyed.
/// </summary>
/// <remarks>
/// When a parent canvas is either enabled, disabled or a nested canvas's OverrideSorting is changed this function is called. You can for example use this to modify objects below a canvas that may depend on a parent canvas - for example, if a canvas is disabled you may want to halt some processing of a UI element.
/// </remarks>
public bool IsDestroyed()
{
// Workaround for Unity native side of the object
// having been destroyed but accessing via interface
// won't call the overloaded ==
return this == null;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0aaa057ce5566e940b18a0ccd0344693
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5dc64c943466a44498be3b620c743b45
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,501 @@
using UnityEngine.EventSystems;
namespace UnityEngine.UIElements
{
// This code is disabled unless the UI Toolkit package or the com.unity.modules.uielements module are present.
// The UIElements module is always present in the Editor but it can be stripped from a project build if unused.
#if PACKAGE_UITOOLKIT
/// <summary>
/// Use this class to handle input and send events to UI Toolkit runtime panels.
/// </summary>
[AddComponentMenu("UI Toolkit/Panel Event Handler (UI Toolkit)")]
public class PanelEventHandler : UIBehaviour, IPointerMoveHandler, IPointerUpHandler, IPointerDownHandler,
ISubmitHandler, ICancelHandler, IMoveHandler, IScrollHandler, ISelectHandler, IDeselectHandler,
IPointerExitHandler, IPointerEnterHandler, IRuntimePanelComponent
{
private BaseRuntimePanel m_Panel;
/// <summary>
/// The panel that this component relates to. If panel is null, this component will have no effect.
/// Will be set to null automatically if panel is Disposed from an external source.
/// </summary>
public IPanel panel
{
get => m_Panel;
set
{
var newPanel = (BaseRuntimePanel)value;
if (m_Panel != newPanel)
{
UnregisterCallbacks();
m_Panel = newPanel;
RegisterCallbacks();
}
}
}
private GameObject selectableGameObject => m_Panel?.selectableGameObject;
private EventSystem eventSystem => UIElementsRuntimeUtility.activeEventSystem as EventSystem;
private bool isCurrentFocusedPanel => m_Panel != null && eventSystem != null &&
eventSystem.currentSelectedGameObject == selectableGameObject;
private Focusable currentFocusedElement => m_Panel?.focusController.GetLeafFocusedElement();
private readonly PointerEvent m_PointerEvent = new PointerEvent();
protected override void OnEnable()
{
base.OnEnable();
RegisterCallbacks();
}
protected override void OnDisable()
{
base.OnDisable();
UnregisterCallbacks();
}
void RegisterCallbacks()
{
if (m_Panel != null)
{
m_Panel.destroyed += OnPanelDestroyed;
m_Panel.visualTree.RegisterCallback<FocusEvent>(OnElementFocus, TrickleDown.TrickleDown);
m_Panel.visualTree.RegisterCallback<BlurEvent>(OnElementBlur, TrickleDown.TrickleDown);
}
}
void UnregisterCallbacks()
{
if (m_Panel != null)
{
m_Panel.destroyed -= OnPanelDestroyed;
m_Panel.visualTree.UnregisterCallback<FocusEvent>(OnElementFocus, TrickleDown.TrickleDown);
m_Panel.visualTree.UnregisterCallback<BlurEvent>(OnElementBlur, TrickleDown.TrickleDown);
}
}
void OnPanelDestroyed()
{
panel = null;
}
void OnElementFocus(FocusEvent e)
{
if (!m_Selecting && eventSystem != null)
eventSystem.SetSelectedGameObject(selectableGameObject);
}
void OnElementBlur(BlurEvent e)
{
// Important: if panel discards focus entirely, it doesn't discard EventSystem selection necessarily.
// Also note that if we arrive here through eventSystem.SetSelectedGameObject -> OnDeselect,
// eventSystem.currentSelectedGameObject will still have its old value and we can't reaffect it immediately.
}
private bool m_Selecting;
public void OnSelect(BaseEventData eventData)
{
m_Selecting = true;
try
{
// This shouldn't conflict with EditorWindow calling Panel.Focus (only on Editor-type panels).
m_Panel?.Focus();
}
finally
{
m_Selecting = false;
}
}
public void OnDeselect(BaseEventData eventData)
{
m_Panel?.Blur();
}
public void OnPointerMove(PointerEventData eventData)
{
if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData))
return;
using (var e = PointerMoveEvent.GetPooled(m_PointerEvent))
{
SendEvent(e, eventData);
}
}
public void OnPointerUp(PointerEventData eventData)
{
if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData, PointerEventType.Up))
return;
using (var e = PointerUpEvent.GetPooled(m_PointerEvent))
{
SendEvent(e, eventData);
if (e.pressedButtons == 0)
PointerDeviceState.SetPlayerPanelWithSoftPointerCapture(e.pointerId, null);
}
}
public void OnPointerDown(PointerEventData eventData)
{
if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData, PointerEventType.Down))
return;
if (eventSystem != null)
eventSystem.SetSelectedGameObject(selectableGameObject);
using (var e = PointerDownEvent.GetPooled(m_PointerEvent))
{
SendEvent(e, eventData);
PointerDeviceState.SetPlayerPanelWithSoftPointerCapture(e.pointerId, m_Panel);
}
}
public void OnPointerExit(PointerEventData eventData)
{
if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData))
return;
// If a pointer exit is called while the pointer is still on top of this object, it means
// there's something else removing the pointer, so we might need to send a PointerCancelEvent.
// This is necessary for touch pointers that are being released, because in UGUI the object
// that was last hovered will not always be the one receiving the pointer up.
if (eventData.pointerCurrentRaycast.gameObject == gameObject &&
eventData.pointerPressRaycast.gameObject != gameObject &&
m_PointerEvent.pointerId != PointerId.mousePointerId)
{
using (var e = PointerCancelEvent.GetPooled(m_PointerEvent))
{
SendEvent(e, eventData);
}
}
m_Panel.PointerLeavesPanel(m_PointerEvent.pointerId, m_PointerEvent.position);
}
public void OnPointerEnter(PointerEventData eventData)
{
if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData))
return;
m_Panel.PointerEntersPanel(m_PointerEvent.pointerId, m_PointerEvent.position);
}
public void OnSubmit(BaseEventData eventData)
{
if (m_Panel == null)
return;
// Allow KeyDown/KeyUp events to be processed before navigation events.
var target = currentFocusedElement ?? m_Panel.visualTree;
ProcessImguiEvents(target);
using (var e = NavigationSubmitEvent.GetPooled(s_Modifiers))
{
e.target = target;
SendEvent(e, eventData);
}
}
public void OnCancel(BaseEventData eventData)
{
if (m_Panel == null)
return;
// Allow KeyDown/KeyUp events to be processed before navigation events.
var target = currentFocusedElement ?? m_Panel.visualTree;
ProcessImguiEvents(target);
using (var e = NavigationCancelEvent.GetPooled(s_Modifiers))
{
e.target = target;
SendEvent(e, eventData);
}
}
public void OnMove(AxisEventData eventData)
{
if (m_Panel == null)
return;
// Allow KeyDown/KeyUp events to be processed before navigation events.
var target = currentFocusedElement ?? m_Panel.visualTree;
ProcessImguiEvents(target);
using (var e = NavigationMoveEvent.GetPooled(eventData.moveVector, s_Modifiers))
{
e.target = target;
SendEvent(e, eventData);
}
// TODO: if runtime panel has no internal navigation, switch to the next UGUI selectable element.
}
public void OnScroll(PointerEventData eventData)
{
if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData))
return;
var scrollDelta = eventData.scrollDelta;
scrollDelta.y = -scrollDelta.y;
const float kPixelPerLine = 20;
// The old input system reported scroll deltas in lines, we report pixels.
// Need to scale as the UI system expects lines.
scrollDelta /= kPixelPerLine;
using (var e = WheelEvent.GetPooled(scrollDelta, m_PointerEvent))
{
SendEvent(e, eventData);
}
}
private void SendEvent(EventBase e, BaseEventData sourceEventData)
{
//e.runtimeEventData = sourceEventData;
m_Panel.SendEvent(e);
if (e.isPropagationStopped)
sourceEventData.Use();
}
private void SendEvent(EventBase e, Event sourceEvent)
{
m_Panel.SendEvent(e);
if (e.isPropagationStopped)
sourceEvent.Use();
}
void Update()
{
if (isCurrentFocusedPanel)
ProcessImguiEvents(currentFocusedElement ?? m_Panel.visualTree);
}
void LateUpdate()
{
// Empty the Event queue, look for EventModifiers.
ProcessImguiEvents(null);
}
private Event m_Event = new Event();
private static EventModifiers s_Modifiers = EventModifiers.None;
// Send IMGUI events to given focus-based target, if any, or simply flush the event queue if not.
// For uniformity of composite events (keyDown vs navigation), target should remain the same
// throughout the entire processing cycle.
void ProcessImguiEvents(Focusable target)
{
bool first = true;
while (Event.PopEvent(m_Event))
{
if (m_Event.type == EventType.Ignore || m_Event.type == EventType.Repaint ||
m_Event.type == EventType.Layout)
continue;
s_Modifiers = first ? m_Event.modifiers : (s_Modifiers | m_Event.modifiers);
first = false;
if (target != null)
{
ProcessKeyboardEvent(m_Event, target);
if (eventSystem.sendNavigationEvents)
ProcessTabEvent(m_Event, target);
}
}
}
void ProcessKeyboardEvent(Event e, Focusable target)
{
if (e.type == EventType.KeyUp)
{
SendKeyUpEvent(e, target);
}
else if (e.type == EventType.KeyDown)
{
SendKeyDownEvent(e, target);
}
}
// TODO: add an ITabHandler interface
void ProcessTabEvent(Event e, Focusable target)
{
if (e.ShouldSendNavigationMoveEventRuntime())
{
SendTabEvent(e, e.shift ? NavigationMoveEvent.Direction.Previous : NavigationMoveEvent.Direction.Next, target);
}
}
private void SendTabEvent(Event e, NavigationMoveEvent.Direction direction, Focusable target)
{
using (var ev = NavigationMoveEvent.GetPooled(direction, s_Modifiers))
{
ev.target = target;
SendEvent(ev, e);
}
}
private void SendKeyUpEvent(Event e, Focusable target)
{
using (var ev = KeyUpEvent.GetPooled('\0', e.keyCode, e.modifiers))
{
ev.target = target;
SendEvent(ev, e);
}
}
private void SendKeyDownEvent(Event e, Focusable target)
{
using (var ev = KeyDownEvent.GetPooled(e.character, e.keyCode, e.modifiers))
{
ev.target = target;
SendEvent(ev, e);
}
}
private bool ReadPointerData(PointerEvent pe, PointerEventData eventData, PointerEventType eventType = PointerEventType.Default)
{
if (eventSystem == null || eventSystem.currentInputModule == null)
return false;
pe.Read(this, eventData, eventType);
// PointerEvents making it this far have been validated by PanelRaycaster already
m_Panel.ScreenToPanel(pe.position, pe.deltaPosition,
out var panelPosition, out var panelDelta, allowOutside:true);
pe.SetPosition(panelPosition, panelDelta);
return true;
}
enum PointerEventType
{
Default, Down, Up
}
class PointerEvent : IPointerEvent
{
public int pointerId { get; private set; }
public string pointerType { get; private set; }
public bool isPrimary { get; private set; }
public int button { get; private set; }
public int pressedButtons { get; private set; }
public Vector3 position { get; private set; }
public Vector3 localPosition { get; private set; }
public Vector3 deltaPosition { get; private set; }
public float deltaTime { get; private set; }
public int clickCount { get; private set; }
public float pressure { get; private set; }
public float tangentialPressure { get; private set; }
public float altitudeAngle { get; private set; }
public float azimuthAngle { get; private set; }
public float twist { get; private set; }
public Vector2 tilt { get; private set; }
public PenStatus penStatus { get; private set; }
public Vector2 radius { get; private set; }
public Vector2 radiusVariance { get; private set; }
public EventModifiers modifiers { get; private set; }
public bool shiftKey => (modifiers & EventModifiers.Shift) != 0;
public bool ctrlKey => (modifiers & EventModifiers.Control) != 0;
public bool commandKey => (modifiers & EventModifiers.Command) != 0;
public bool altKey => (modifiers & EventModifiers.Alt) != 0;
public bool actionKey =>
Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.OSXPlayer
? commandKey
: ctrlKey;
public void Read(PanelEventHandler self, PointerEventData eventData, PointerEventType eventType)
{
pointerId = self.eventSystem.currentInputModule.ConvertUIToolkitPointerId(eventData);
bool InRange(int i, int start, int count) => i >= start && i < start + count;
pointerType =
InRange(pointerId, PointerId.touchPointerIdBase, PointerId.touchPointerCount) ? PointerType.touch :
InRange(pointerId, PointerId.penPointerIdBase, PointerId.penPointerCount) ? PointerType.pen :
PointerType.mouse;
isPrimary = pointerId == PointerId.mousePointerId ||
pointerId == PointerId.touchPointerIdBase ||
pointerId == PointerId.penPointerIdBase;
// Flip Y axis between input and UITK
var h = Screen.height;
var eventPosition = Display.RelativeMouseAt(eventData.position);
if (eventPosition != Vector3.zero)
{
// We support multiple display and display identification based on event position.
int eventDisplayIndex = (int)eventPosition.z;
if (eventDisplayIndex > 0 && eventDisplayIndex < Display.displays.Length)
h = Display.displays[eventDisplayIndex].systemHeight;
}
else
{
eventPosition = eventData.position;
}
var delta = eventData.delta;
eventPosition.y = h - eventPosition.y;
delta.y = -delta.y;
localPosition = position = eventPosition;
deltaPosition = delta;
deltaTime = 0; //TODO: find out what's expected here. Time since last frame? Since last sent event?
pressure = eventData.pressure;
tangentialPressure = eventData.tangentialPressure;
altitudeAngle = eventData.altitudeAngle;
azimuthAngle = eventData.azimuthAngle;
twist = eventData.twist;
tilt = eventData.tilt;
penStatus = eventData.penStatus;
radius = eventData.radius;
radiusVariance = eventData.radiusVariance;
modifiers = s_Modifiers;
if (eventType == PointerEventType.Default)
{
button = -1;
clickCount = 0;
}
else
{
button = Mathf.Max(0, (int)eventData.button);
clickCount = eventData.clickCount;
if (eventType == PointerEventType.Down)
{
// Case 1379054: UIToolkit assumes clickCount is increased before PointerDown, but UGUI does it after.
clickCount++;
PointerDeviceState.PressButton(pointerId, button);
}
else if (eventType == PointerEventType.Up)
{
PointerDeviceState.ReleaseButton(pointerId, button);
}
clickCount = Mathf.Max(1, clickCount);
}
pressedButtons = PointerDeviceState.GetPressedButtons(pointerId);
}
public void SetPosition(Vector3 positionOverride, Vector3 deltaOverride)
{
localPosition = position = positionOverride;
deltaPosition = deltaOverride;
}
}
}
#endif
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8a410b213209442fb15a69799f0240a5
timeCreated: 1602089617

View File

@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine.EventSystems;
namespace UnityEngine.UIElements
{
// This code is disabled unless the UI Toolkit package or the com.unity.modules.uielements module are present.
// The UIElements module is always present in the Editor but it can be stripped from a project build if unused.
#if PACKAGE_UITOOLKIT
/// <summary>
/// A derived BaseRaycaster to raycast against UI Toolkit panel instances at runtime.
/// </summary>
[AddComponentMenu("UI Toolkit/Panel Raycaster (UI Toolkit)")]
public class PanelRaycaster : BaseRaycaster, IRuntimePanelComponent
{
private BaseRuntimePanel m_Panel;
/// <summary>
/// The panel that this component relates to. If panel is null, this component will have no effect.
/// Will be set to null automatically if panel is Disposed from an external source.
/// </summary>
public IPanel panel
{
get => m_Panel;
set
{
var newPanel = (BaseRuntimePanel)value;
if (m_Panel != newPanel)
{
UnregisterCallbacks();
m_Panel = newPanel;
RegisterCallbacks();
}
}
}
void RegisterCallbacks()
{
if (m_Panel != null)
{
m_Panel.destroyed += OnPanelDestroyed;
}
}
void UnregisterCallbacks()
{
if (m_Panel != null)
{
m_Panel.destroyed -= OnPanelDestroyed;
}
}
void OnPanelDestroyed()
{
panel = null;
}
private GameObject selectableGameObject => m_Panel?.selectableGameObject;
public override int sortOrderPriority => Mathf.FloorToInt(m_Panel?.sortingPriority ?? 0f);
public override int renderOrderPriority => int.MaxValue - (UIElementsRuntimeUtility.s_ResolvedSortingIndexMax - (m_Panel?.resolvedSortingIndex ?? 0));
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
{
if (m_Panel == null)
return;
var eventPosition = Display.RelativeMouseAt(eventData.position);
var displayIndex = m_Panel.targetDisplay;
var originalEventPosition = eventPosition;
if (eventPosition != Vector3.zero)
{
// We support multiple display and display identification based on event position.
int eventDisplayIndex = (int)eventPosition.z;
// Discard events that are not part of this display so the user does not interact with multiple displays at once.
if (eventDisplayIndex != displayIndex)
return;
}
else
{
// The multiple display system is not supported on all platforms, when it is not supported the returned position
// will be all zeros so when the returned index is 0 we will default to the event data to be safe.
eventPosition = eventData.position;
#if UNITY_EDITOR
if (Display.activeEditorGameViewTarget != displayIndex)
return;
eventPosition.z = Display.activeEditorGameViewTarget;
#endif
// We don't really know in which display the event occurred. We will process the event assuming it occurred in our display.
}
var position = eventPosition;
var delta = eventData.delta;
float h = Screen.height;
if (displayIndex > 0 && displayIndex < Display.displays.Length)
{
h = Display.displays[displayIndex].systemHeight;
}
position.y = h - position.y;
delta.y = -delta.y;
var eventSystem = UIElementsRuntimeUtility.activeEventSystem as EventSystem;
if (eventSystem == null || eventSystem.currentInputModule == null)
return;
var pointerId = eventSystem.currentInputModule.ConvertUIToolkitPointerId(eventData);
var capturingElement = m_Panel.GetCapturingElement(pointerId);
if (capturingElement is VisualElement ve && ve.panel != m_Panel)
return;
var capturingPanel = PointerDeviceState.GetPressedButtons(pointerId) != 0 ?
PointerDeviceState.GetPlayerPanelWithSoftPointerCapture(pointerId) :
null;
if (capturingPanel != null && capturingPanel != m_Panel)
return;
if (capturingElement == null && capturingPanel == null)
{
if (!m_Panel.ScreenToPanel(position, delta, out var panelPosition, out _))
return;
var pick = m_Panel.Pick(panelPosition);
if (pick == null)
return;
}
resultAppendList.Add(new RaycastResult
{
gameObject = selectableGameObject,
module = this,
screenPosition = eventPosition,
displayIndex = m_Panel.targetDisplay,
});
}
public override Camera eventCamera => null;
}
#endif
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7a79c4e23d4d4b64bfa5fa9e6671a460
timeCreated: 1602101961

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 32e2186f4598cff489784aae586f2215
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,37 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("guisystem")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft")]
[assembly: AssemblyProduct("guisystem")]
[assembly: AssemblyCopyright("Copyright © Microsoft 2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: InternalsVisibleTo("UnityEngine.UI.Tests")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("d4f464c7-9b15-460d-b4bc-2cacd1c1df73")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7d7bd4bcc815cfb44b9990c29dabdb9f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 742654cad2425334696ba6ed4495cfef
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5472815444de2ce45bf2053a4af04b9d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,244 @@
using System.Collections;
using UnityEngine.Events;
namespace UnityEngine.UI.CoroutineTween
{
// Base interface for tweeners,
// using an interface instead of
// an abstract class as we want the
// tweens to be structs.
internal interface ITweenValue
{
void TweenValue(float floatPercentage);
bool ignoreTimeScale { get; }
float duration { get; }
bool ValidTarget();
}
// Color tween class, receives the
// TweenValue callback and then sets
// the value on the target.
internal struct ColorTween : ITweenValue
{
public enum ColorTweenMode
{
All,
RGB,
Alpha
}
public class ColorTweenCallback : UnityEvent<Color> {}
private ColorTweenCallback m_Target;
private Color m_StartColor;
private Color m_TargetColor;
private ColorTweenMode m_TweenMode;
private float m_Duration;
private bool m_IgnoreTimeScale;
public Color startColor
{
get { return m_StartColor; }
set { m_StartColor = value; }
}
public Color targetColor
{
get { return m_TargetColor; }
set { m_TargetColor = value; }
}
public ColorTweenMode tweenMode
{
get { return m_TweenMode; }
set { m_TweenMode = value; }
}
public float duration
{
get { return m_Duration; }
set { m_Duration = value; }
}
public bool ignoreTimeScale
{
get { return m_IgnoreTimeScale; }
set { m_IgnoreTimeScale = value; }
}
public void TweenValue(float floatPercentage)
{
if (!ValidTarget())
return;
var newColor = Color.Lerp(m_StartColor, m_TargetColor, floatPercentage);
if (m_TweenMode == ColorTweenMode.Alpha)
{
newColor.r = m_StartColor.r;
newColor.g = m_StartColor.g;
newColor.b = m_StartColor.b;
}
else if (m_TweenMode == ColorTweenMode.RGB)
{
newColor.a = m_StartColor.a;
}
m_Target.Invoke(newColor);
}
public void AddOnChangedCallback(UnityAction<Color> callback)
{
if (m_Target == null)
m_Target = new ColorTweenCallback();
m_Target.AddListener(callback);
}
public bool GetIgnoreTimescale()
{
return m_IgnoreTimeScale;
}
public float GetDuration()
{
return m_Duration;
}
public bool ValidTarget()
{
return m_Target != null;
}
}
// Float tween class, receives the
// TweenValue callback and then sets
// the value on the target.
internal struct FloatTween : ITweenValue
{
public class FloatTweenCallback : UnityEvent<float> {}
private FloatTweenCallback m_Target;
private float m_StartValue;
private float m_TargetValue;
private float m_Duration;
private bool m_IgnoreTimeScale;
public float startValue
{
get { return m_StartValue; }
set { m_StartValue = value; }
}
public float targetValue
{
get { return m_TargetValue; }
set { m_TargetValue = value; }
}
public float duration
{
get { return m_Duration; }
set { m_Duration = value; }
}
public bool ignoreTimeScale
{
get { return m_IgnoreTimeScale; }
set { m_IgnoreTimeScale = value; }
}
public void TweenValue(float floatPercentage)
{
if (!ValidTarget())
return;
var newValue = Mathf.Lerp(m_StartValue, m_TargetValue, floatPercentage);
m_Target.Invoke(newValue);
}
public void AddOnChangedCallback(UnityAction<float> callback)
{
if (m_Target == null)
m_Target = new FloatTweenCallback();
m_Target.AddListener(callback);
}
public bool GetIgnoreTimescale()
{
return m_IgnoreTimeScale;
}
public float GetDuration()
{
return m_Duration;
}
public bool ValidTarget()
{
return m_Target != null;
}
}
// Tween runner, executes the given tween.
// The coroutine will live within the given
// behaviour container.
internal class TweenRunner<T> where T : struct, ITweenValue
{
protected MonoBehaviour m_CoroutineContainer;
protected IEnumerator m_Tween;
// utility function for starting the tween
private static IEnumerator Start(T tweenInfo)
{
if (!tweenInfo.ValidTarget())
yield break;
var elapsedTime = 0.0f;
while (elapsedTime < tweenInfo.duration)
{
elapsedTime += tweenInfo.ignoreTimeScale ? Time.unscaledDeltaTime : Time.deltaTime;
var percentage = Mathf.Clamp01(elapsedTime / tweenInfo.duration);
tweenInfo.TweenValue(percentage);
yield return null;
}
tweenInfo.TweenValue(1.0f);
}
public void Init(MonoBehaviour coroutineContainer)
{
m_CoroutineContainer = coroutineContainer;
}
public void StartTween(T info)
{
if (m_CoroutineContainer == null)
{
Debug.LogWarning("Coroutine container not configured... did you forget to call Init?");
return;
}
StopTween();
if (!m_CoroutineContainer.gameObject.activeInHierarchy)
{
info.TweenValue(1.0f);
return;
}
m_Tween = Start(info);
m_CoroutineContainer.StartCoroutine(m_Tween);
}
public void StopTween()
{
if (m_Tween != null)
{
m_CoroutineContainer.StopCoroutine(m_Tween);
m_Tween = null;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7be84a49bb2cd7e4a9ed097ba22794d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 12c42068351bb084abde965d725b9887
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,163 @@
using System;
using UnityEngine.Serialization;
namespace UnityEngine.UI
{
/// <summary>
/// Structure that stores the state of an animation transition on a Selectable.
/// </summary>
[Serializable]
public class AnimationTriggers
{
private const string kDefaultNormalAnimName = "Normal";
private const string kDefaultHighlightedAnimName = "Highlighted";
private const string kDefaultPressedAnimName = "Pressed";
private const string kDefaultSelectedAnimName = "Selected";
private const string kDefaultDisabledAnimName = "Disabled";
[FormerlySerializedAs("normalTrigger")]
[SerializeField]
private string m_NormalTrigger = kDefaultNormalAnimName;
[FormerlySerializedAs("highlightedTrigger")]
[SerializeField]
private string m_HighlightedTrigger = kDefaultHighlightedAnimName;
[FormerlySerializedAs("pressedTrigger")]
[SerializeField]
private string m_PressedTrigger = kDefaultPressedAnimName;
[FormerlySerializedAs("m_HighlightedTrigger")]
[SerializeField]
private string m_SelectedTrigger = kDefaultSelectedAnimName;
[FormerlySerializedAs("disabledTrigger")]
[SerializeField]
private string m_DisabledTrigger = kDefaultDisabledAnimName;
/// <summary>
/// Trigger to send to animator when entering normal state.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Animator buttonAnimator;
/// public Button button;
/// void SomeFunction()
/// {
/// //Sets the button to the Normal state (Useful when making tutorials).
/// buttonAnimator.SetTrigger(button.animationTriggers.normalTrigger);
/// }
/// }
/// ]]>
///</code>
/// </example>
public string normalTrigger { get { return m_NormalTrigger; } set { m_NormalTrigger = value; } }
/// <summary>
/// Trigger to send to animator when entering highlighted state.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Animator buttonAnimator;
/// public Button button;
/// void SomeFunction()
/// {
/// //Sets the button to the Highlighted state (Useful when making tutorials).
/// buttonAnimator.SetTrigger(button.animationTriggers.highlightedTrigger);
/// }
/// }
/// ]]>
///</code>
/// </example>
public string highlightedTrigger { get { return m_HighlightedTrigger; } set { m_HighlightedTrigger = value; } }
/// <summary>
/// Trigger to send to animator when entering pressed state.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Animator buttonAnimator;
/// public Button button;
/// void SomeFunction()
/// {
/// //Sets the button to the Pressed state (Useful when making tutorials).
/// buttonAnimator.SetTrigger(button.animationTriggers.pressedTrigger);
/// }
/// }
/// ]]>
///</code>
/// </example>
public string pressedTrigger { get { return m_PressedTrigger; } set { m_PressedTrigger = value; } }
/// <summary>
/// Trigger to send to animator when entering selected state.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Animator buttonAnimator;
/// public Button button;
/// void SomeFunction()
/// {
/// //Sets the button to the Selected state (Useful when making tutorials).
/// buttonAnimator.SetTrigger(button.animationTriggers.selectedTrigger);
/// }
/// }
/// ]]>
///</code>
/// </example>
public string selectedTrigger { get { return m_SelectedTrigger; } set { m_SelectedTrigger = value; } }
/// <summary>
/// Trigger to send to animator when entering disabled state.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Animator buttonAnimator;
/// public Button button;
/// void SomeFunction()
/// {
/// //Sets the button to the Disabled state (Useful when making tutorials).
/// buttonAnimator.SetTrigger(button.animationTriggers.disabledTrigger);
/// }
/// }
/// ]]>
///</code>
/// </example>
public string disabledTrigger { get { return m_DisabledTrigger; } set { m_DisabledTrigger = value; } }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c67ac6e40bbb6fe47a095b949b609ce0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,175 @@
using System;
using System.Collections;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
namespace UnityEngine.UI
{
/// <summary>
/// A standard button that sends an event when clicked.
/// </summary>
[AddComponentMenu("UI/Button", 30)]
public class Button : Selectable, IPointerClickHandler, ISubmitHandler
{
[Serializable]
/// <summary>
/// Function definition for a button click event.
/// </summary>
public class ButtonClickedEvent : UnityEvent {}
// Event delegates triggered on click.
[FormerlySerializedAs("onClick")]
[SerializeField]
private ButtonClickedEvent m_OnClick = new ButtonClickedEvent();
protected Button()
{}
/// <summary>
/// UnityEvent that is triggered when the button is pressed.
/// Note: Triggered on MouseUp after MouseDown on the same object.
/// </summary>
///<example>
///<code>
/// <![CDATA[
/// using UnityEngine;
/// using UnityEngine.UI;
/// using System.Collections;
///
/// public class ClickExample : MonoBehaviour
/// {
/// public Button yourButton;
///
/// void Start()
/// {
/// Button btn = yourButton.GetComponent<Button>();
/// btn.onClick.AddListener(TaskOnClick);
/// }
///
/// void TaskOnClick()
/// {
/// Debug.Log("You have clicked the button!");
/// }
/// }
/// ]]>
///</code>
///</example>
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();
}
/// <summary>
/// Call all registered IPointerClickHandlers.
/// Register button presses using the IPointerClickHandler. You can also use it to tell what type of click happened (left, right etc.).
/// Make sure your Scene has an EventSystem.
/// </summary>
/// <param name="eventData">Pointer Data associated with the event. Typically by the event system.</param>
/// <example>
/// <code>
/// <![CDATA[
/// //Attatch this script to a Button GameObject
/// using UnityEngine;
/// using UnityEngine.EventSystems;
///
/// public class Example : MonoBehaviour, IPointerClickHandler
/// {
/// //Detect if a click occurs
/// public void OnPointerClick(PointerEventData pointerEventData)
/// {
/// //Use this to tell when the user right-clicks on the Button
/// if (pointerEventData.button == PointerEventData.InputButton.Right)
/// {
/// //Output to console the clicked GameObject's name and the following message. You can replace this with your own actions for when clicking the GameObject.
/// Debug.Log(name + " Game Object Right Clicked!");
/// }
///
/// //Use this to tell when the user left-clicks on the Button
/// if (pointerEventData.button == PointerEventData.InputButton.Left)
/// {
/// Debug.Log(name + " Game Object Left Clicked!");
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual void OnPointerClick(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
Press();
}
/// <summary>
/// Call all registered ISubmitHandler.
/// </summary>
/// <param name="eventData">Associated data with the event. Typically by the event system.</param>
/// <remarks>
/// This detects when a Button has been selected via a "submit" key you specify (default is the return key).
///
/// To change the submit key, either:
///
/// 1. Go to Edit->Project Settings->Input.
///
/// 2. Next, expand the Axes section and go to the Submit section if it exists.
///
/// 3. If Submit doesnt exist, add 1 number to the Size field. This creates a new section at the bottom. Expand the new section and change the Name field to “Submit”.
///
/// 4. Change the Positive Button field to the key you want (e.g. space).
///
///
/// Or:
///
/// 1. Go to your EventSystem in your Project
///
/// 2. Go to the Inspector window and change the Submit Button field to one of the sections in the Input Manager (e.g. "Submit"), or create your own by naming it what you like, then following the next few steps.
///
/// 3. Go to Edit->Project Settings->Input to get to the Input Manager.
///
/// 4. Expand the Axes section in the Inspector window. Add 1 to the number in the Size field. This creates a new section at the bottom.
///
/// 5. Expand the new section and name it the same as the name you inserted in the Submit Button field in the EventSystem. Set the Positive Button field to the key you want (e.g. space)
/// </remarks>
public virtual void OnSubmit(BaseEventData eventData)
{
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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4e29b1a8efbd4b44bb3f3716e73f07ff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,412 @@
using System;
using System.Collections.Generic;
using UnityEngine.UI.Collections;
namespace UnityEngine.UI
{
/// <summary>
/// Values of 'update' called on a Canvas update.
/// </summary>
/// <remarks> If modifying also modify m_CanvasUpdateProfilerStrings to match.</remarks>
public enum CanvasUpdate
{
/// <summary>
/// Called before layout.
/// </summary>
Prelayout = 0,
/// <summary>
/// Called for layout.
/// </summary>
Layout = 1,
/// <summary>
/// Called after layout.
/// </summary>
PostLayout = 2,
/// <summary>
/// Called before rendering.
/// </summary>
PreRender = 3,
/// <summary>
/// Called late, before render.
/// </summary>
LatePreRender = 4,
/// <summary>
/// Max enum value. Always last.
/// </summary>
MaxUpdateValue = 5
}
/// <summary>
/// This is an element that can live on a Canvas.
/// </summary>
public interface ICanvasElement
{
/// <summary>
/// Rebuild the element for the given stage.
/// </summary>
/// <param name="executing">The current CanvasUpdate stage being rebuild.</param>
void Rebuild(CanvasUpdate executing);
/// <summary>
/// Get the transform associated with the ICanvasElement.
/// </summary>
Transform transform { get; }
/// <summary>
/// Callback sent when this ICanvasElement has completed layout.
/// </summary>
void LayoutComplete();
/// <summary>
/// Callback sent when this ICanvasElement has completed Graphic rebuild.
/// </summary>
void GraphicUpdateComplete();
/// <summary>
/// Used if the native representation has been destroyed.
/// </summary>
/// <returns>Return true if the element is considered destroyed.</returns>
bool IsDestroyed();
}
/// <summary>
/// A place where CanvasElements can register themselves for rebuilding.
/// </summary>
public class CanvasUpdateRegistry
{
private static CanvasUpdateRegistry s_Instance;
private bool m_PerformingLayoutUpdate;
private bool m_PerformingGraphicUpdate;
// This list matches the CanvasUpdate enum above. Keep in sync
private string[] m_CanvasUpdateProfilerStrings = new string[] { "CanvasUpdate.Prelayout", "CanvasUpdate.Layout", "CanvasUpdate.PostLayout", "CanvasUpdate.PreRender", "CanvasUpdate.LatePreRender" };
private const string m_CullingUpdateProfilerString = "ClipperRegistry.Cull";
private readonly IndexedSet<ICanvasElement> m_LayoutRebuildQueue = new IndexedSet<ICanvasElement>();
private readonly IndexedSet<ICanvasElement> m_GraphicRebuildQueue = new IndexedSet<ICanvasElement>();
protected CanvasUpdateRegistry()
{
Canvas.willRenderCanvases += PerformUpdate;
}
/// <summary>
/// Get the singleton registry instance.
/// </summary>
public static CanvasUpdateRegistry instance
{
get
{
if (s_Instance == null)
s_Instance = new CanvasUpdateRegistry();
return s_Instance;
}
}
private bool ObjectValidForUpdate(ICanvasElement element)
{
var valid = element != null;
var isUnityObject = element is Object;
if (isUnityObject)
valid = (element as Object) != null; //Here we make use of the overloaded UnityEngine.Object == null, that checks if the native object is alive.
return valid;
}
private void CleanInvalidItems()
{
// So MB's override the == operator for null equality, which checks
// if they are destroyed. This is fine if you are looking at a concrete
// mb, but in this case we are looking at a list of ICanvasElement
// this won't forward the == operator to the MB, but just check if the
// interface is null. IsDestroyed will return if the backend is destroyed.
var layoutRebuildQueueCount = m_LayoutRebuildQueue.Count;
for (int i = layoutRebuildQueueCount - 1; i >= 0; --i)
{
var item = m_LayoutRebuildQueue[i];
if (item == null)
{
m_LayoutRebuildQueue.RemoveAt(i);
continue;
}
if (item.IsDestroyed())
{
m_LayoutRebuildQueue.RemoveAt(i);
item.LayoutComplete();
}
}
var graphicRebuildQueueCount = m_GraphicRebuildQueue.Count;
for (int i = graphicRebuildQueueCount - 1; i >= 0; --i)
{
var item = m_GraphicRebuildQueue[i];
if (item == null)
{
m_GraphicRebuildQueue.RemoveAt(i);
continue;
}
if (item.IsDestroyed())
{
m_GraphicRebuildQueue.RemoveAt(i);
item.GraphicUpdateComplete();
}
}
}
private static readonly Comparison<ICanvasElement> s_SortLayoutFunction = SortLayoutList;
private void PerformUpdate()
{
UISystemProfilerApi.BeginSample(UISystemProfilerApi.SampleType.Layout);
CleanInvalidItems();
m_PerformingLayoutUpdate = true;
m_LayoutRebuildQueue.Sort(s_SortLayoutFunction);
for (int i = 0; i <= (int)CanvasUpdate.PostLayout; i++)
{
UnityEngine.Profiling.Profiler.BeginSample(m_CanvasUpdateProfilerStrings[i]);
for (int j = 0; j < m_LayoutRebuildQueue.Count; j++)
{
var rebuild = m_LayoutRebuildQueue[j];
try
{
if (ObjectValidForUpdate(rebuild))
rebuild.Rebuild((CanvasUpdate)i);
}
catch (Exception e)
{
Debug.LogException(e, rebuild.transform);
}
}
UnityEngine.Profiling.Profiler.EndSample();
}
for (int i = 0; i < m_LayoutRebuildQueue.Count; ++i)
m_LayoutRebuildQueue[i].LayoutComplete();
m_LayoutRebuildQueue.Clear();
m_PerformingLayoutUpdate = false;
UISystemProfilerApi.EndSample(UISystemProfilerApi.SampleType.Layout);
UISystemProfilerApi.BeginSample(UISystemProfilerApi.SampleType.Render);
// now layout is complete do culling...
UnityEngine.Profiling.Profiler.BeginSample(m_CullingUpdateProfilerString);
ClipperRegistry.instance.Cull();
UnityEngine.Profiling.Profiler.EndSample();
m_PerformingGraphicUpdate = true;
for (var i = (int)CanvasUpdate.PreRender; i < (int)CanvasUpdate.MaxUpdateValue; i++)
{
UnityEngine.Profiling.Profiler.BeginSample(m_CanvasUpdateProfilerStrings[i]);
for (var k = 0; k < m_GraphicRebuildQueue.Count; k++)
{
try
{
var element = m_GraphicRebuildQueue[k];
if (ObjectValidForUpdate(element))
element.Rebuild((CanvasUpdate)i);
}
catch (Exception e)
{
Debug.LogException(e, m_GraphicRebuildQueue[k].transform);
}
}
UnityEngine.Profiling.Profiler.EndSample();
}
for (int i = 0; i < m_GraphicRebuildQueue.Count; ++i)
m_GraphicRebuildQueue[i].GraphicUpdateComplete();
m_GraphicRebuildQueue.Clear();
m_PerformingGraphicUpdate = false;
UISystemProfilerApi.EndSample(UISystemProfilerApi.SampleType.Render);
}
private static int ParentCount(Transform child)
{
if (child == null)
return 0;
var parent = child.parent;
int count = 0;
while (parent != null)
{
count++;
parent = parent.parent;
}
return count;
}
private static int SortLayoutList(ICanvasElement x, ICanvasElement y)
{
Transform t1 = x.transform;
Transform t2 = y.transform;
return ParentCount(t1) - ParentCount(t2);
}
/// <summary>
/// Try and add the given element to the layout rebuild list.
/// Will not return if successfully added.
/// </summary>
/// <param name="element">The element that is needing rebuilt.</param>
public static void RegisterCanvasElementForLayoutRebuild(ICanvasElement element)
{
instance.InternalRegisterCanvasElementForLayoutRebuild(element);
}
/// <summary>
/// Try and add the given element to the layout rebuild list.
/// </summary>
/// <param name="element">The element that is needing rebuilt.</param>
/// <returns>
/// True if the element was successfully added to the rebuilt list.
/// False if either already inside a Graphic Update loop OR has already been added to the list.
/// </returns>
public static bool TryRegisterCanvasElementForLayoutRebuild(ICanvasElement element)
{
return instance.InternalRegisterCanvasElementForLayoutRebuild(element);
}
private bool InternalRegisterCanvasElementForLayoutRebuild(ICanvasElement element)
{
if (m_LayoutRebuildQueue.Contains(element))
return false;
/* TODO: this likely should be here but causes the error to show just resizing the game view (case 739376)
if (m_PerformingLayoutUpdate)
{
Debug.LogError(string.Format("Trying to add {0} for layout rebuild while we are already inside a layout rebuild loop. This is not supported.", element));
return false;
}*/
return m_LayoutRebuildQueue.AddUnique(element);
}
/// <summary>
/// Try and add the given element to the rebuild list.
/// Will not return if successfully added.
/// </summary>
/// <param name="element">The element that is needing rebuilt.</param>
public static void RegisterCanvasElementForGraphicRebuild(ICanvasElement element)
{
instance.InternalRegisterCanvasElementForGraphicRebuild(element);
}
/// <summary>
/// Try and add the given element to the rebuild list.
/// </summary>
/// <param name="element">The element that is needing rebuilt.</param>
/// <returns>
/// True if the element was successfully added to the rebuilt list.
/// False if either already inside a Graphic Update loop OR has already been added to the list.
/// </returns>
public static bool TryRegisterCanvasElementForGraphicRebuild(ICanvasElement element)
{
return instance.InternalRegisterCanvasElementForGraphicRebuild(element);
}
private bool InternalRegisterCanvasElementForGraphicRebuild(ICanvasElement element)
{
if (m_PerformingGraphicUpdate)
{
Debug.LogError(string.Format("Trying to add {0} for graphic rebuild while we are already inside a graphic rebuild loop. This is not supported.", element));
return false;
}
return m_GraphicRebuildQueue.AddUnique(element);
}
/// <summary>
/// Remove the given element from both the graphic and the layout rebuild lists.
/// </summary>
/// <param name="element"></param>
public static void UnRegisterCanvasElementForRebuild(ICanvasElement element)
{
instance.InternalUnRegisterCanvasElementForLayoutRebuild(element);
instance.InternalUnRegisterCanvasElementForGraphicRebuild(element);
}
/// <summary>
/// Disable the given element from both the graphic and the layout rebuild lists.
/// </summary>
/// <param name="element"></param>
public static void DisableCanvasElementForRebuild(ICanvasElement element)
{
instance.InternalDisableCanvasElementForLayoutRebuild(element);
instance.InternalDisableCanvasElementForGraphicRebuild(element);
}
private void InternalUnRegisterCanvasElementForLayoutRebuild(ICanvasElement element)
{
if (m_PerformingLayoutUpdate)
{
Debug.LogError(string.Format("Trying to remove {0} from rebuild list while we are already inside a rebuild loop. This is not supported.", element));
return;
}
element.LayoutComplete();
instance.m_LayoutRebuildQueue.Remove(element);
}
private void InternalUnRegisterCanvasElementForGraphicRebuild(ICanvasElement element)
{
if (m_PerformingGraphicUpdate)
{
Debug.LogError(string.Format("Trying to remove {0} from rebuild list while we are already inside a rebuild loop. This is not supported.", element));
return;
}
element.GraphicUpdateComplete();
instance.m_GraphicRebuildQueue.Remove(element);
}
private void InternalDisableCanvasElementForLayoutRebuild(ICanvasElement element)
{
if (m_PerformingLayoutUpdate)
{
Debug.LogError(string.Format("Trying to remove {0} from rebuild list while we are already inside a rebuild loop. This is not supported.", element));
return;
}
element.LayoutComplete();
instance.m_LayoutRebuildQueue.DisableItem(element);
}
private void InternalDisableCanvasElementForGraphicRebuild(ICanvasElement element)
{
if (m_PerformingGraphicUpdate)
{
Debug.LogError(string.Format("Trying to remove {0} from rebuild list while we are already inside a rebuild loop. This is not supported.", element));
return;
}
element.GraphicUpdateComplete();
instance.m_GraphicRebuildQueue.DisableItem(element);
}
/// <summary>
/// Are graphics layouts currently being calculated..
/// </summary>
/// <returns>True if the rebuild loop is CanvasUpdate.Prelayout, CanvasUpdate.Layout or CanvasUpdate.Postlayout</returns>
public static bool IsRebuildingLayout()
{
return instance.m_PerformingLayoutUpdate;
}
/// <summary>
/// Are graphics currently being rebuild.
/// </summary>
/// <returns>True if the rebuild loop is CanvasUpdate.PreRender or CanvasUpdate.Render</returns>
public static bool IsRebuildingGraphics()
{
return instance.m_PerformingGraphicUpdate;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bfb788a43e03363419155b8af77da971
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,243 @@
using System;
using UnityEngine.Serialization;
namespace UnityEngine.UI
{
[Serializable]
/// <summary>
/// Structure that stores the state of a color transition on a Selectable.
/// </summary>
public struct ColorBlock : IEquatable<ColorBlock>
{
[FormerlySerializedAs("normalColor")]
[SerializeField]
private Color m_NormalColor;
[FormerlySerializedAs("highlightedColor")]
[SerializeField]
private Color m_HighlightedColor;
[FormerlySerializedAs("pressedColor")]
[SerializeField]
private Color m_PressedColor;
[FormerlySerializedAs("m_HighlightedColor")]
[SerializeField]
private Color m_SelectedColor;
[FormerlySerializedAs("disabledColor")]
[SerializeField]
private Color m_DisabledColor;
[Range(1, 5)]
[SerializeField]
private float m_ColorMultiplier;
[FormerlySerializedAs("fadeDuration")]
[SerializeField]
private float m_FadeDuration;
/// <summary>
/// The normal color for this color block.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Button button;
/// public Color newColor;
///
/// void Start()
/// {
/// //Changes the button's Normal color to the new color.
/// ColorBlock cb = button.colors;
/// cb.normalColor = newColor;
/// button.colors = cb;
/// }
/// }
/// ]]>
///</code>
/// </example>
public Color normalColor { get { return m_NormalColor; } set { m_NormalColor = value; } }
/// <summary>
/// The highlight color for this color block.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Button button;
/// public Color newColor;
///
/// void Start()
/// {
/// //Changes the button's Highlighted color to the new color.
/// ColorBlock cb = button.colors;
/// cb.highlightedColor = newColor;
/// button.colors = cb;
/// }
/// }
/// ]]>
///</code>
/// </example>
public Color highlightedColor { get { return m_HighlightedColor; } set { m_HighlightedColor = value; } }
/// <summary>
/// The pressed color for this color block.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Button button;
/// public Color newColor;
///
/// void Start()
/// {
/// //Changes the button's Pressed color to the new color.
/// ColorBlock cb = button.colors;
/// cb.pressedColor = newColor;
/// button.colors = cb;
/// }
/// }
/// ]]>
///</code>
/// </example>
public Color pressedColor { get { return m_PressedColor; } set { m_PressedColor = value; } }
/// <summary>
/// The selected color for this color block.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Button button;
/// public Color newColor;
///
/// void Start()
/// {
/// //Changes the button's Selected color to the new color.
/// ColorBlock cb = button.colors;
/// cb.selectedColor = newColor;
/// button.colors = cb;
/// }
/// }
/// ]]>
///</code>
/// </example>
public Color selectedColor { get { return m_SelectedColor; } set { m_SelectedColor = value; } }
/// <summary>
/// The disabled color for this color block.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Button button;
/// public Color newColor;
///
/// void Start()
/// {
/// //Changes the button's Disabled color to the new color.
/// ColorBlock cb = button.colors;
/// cb.disabledColor = newColor;
/// button.colors = cb;
/// }
/// }
/// ]]>
///</code>
/// </example>
public Color disabledColor { get { return m_DisabledColor; } set { m_DisabledColor = value; } }
/// <summary>
/// Multiplier applied to colors (allows brightening greater then base color).
/// </summary>
public float colorMultiplier { get { return m_ColorMultiplier; } set { m_ColorMultiplier = value; } }
/// <summary>
/// How long a color transition between states should take.
/// </summary>
public float fadeDuration { get { return m_FadeDuration; } set { m_FadeDuration = value; } }
/// <summary>
/// Simple getter for a code generated default ColorBlock.
/// </summary>
public static ColorBlock defaultColorBlock;
static ColorBlock()
{
defaultColorBlock = 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_SelectedColor = new Color32(245, 245, 245, 255),
m_DisabledColor = new Color32(200, 200, 200, 128),
colorMultiplier = 1.0f,
fadeDuration = 0.1f
};
}
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 &&
selectedColor == other.selectedColor &&
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();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 83088ba2132cbc940b7ca0c679a02b0d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 27ed3e221887b3544bd9d6505d4a789f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,80 @@
using System.Collections.Generic;
using UnityEngine.UI.Collections;
namespace UnityEngine.UI
{
/// <summary>
/// Registry class to keep track of all IClippers that exist in the scene
/// </summary>
/// <remarks>
/// This is used during the CanvasUpdate loop to cull clippable elements. The clipping is called after layout, but before Graphic update.
/// </remarks>
public class ClipperRegistry
{
static ClipperRegistry s_Instance;
readonly IndexedSet<IClipper> m_Clippers = new IndexedSet<IClipper>();
protected ClipperRegistry()
{
// This is needed for AOT platforms. Without it the compile doesn't get the definition of the Dictionarys
#pragma warning disable 168
Dictionary<IClipper, int> emptyIClipperDic;
#pragma warning restore 168
}
/// <summary>
/// The singleton instance of the clipper registry.
/// </summary>
public static ClipperRegistry instance
{
get
{
if (s_Instance == null)
s_Instance = new ClipperRegistry();
return s_Instance;
}
}
/// <summary>
/// Perform the clipping on all registered IClipper
/// </summary>
public void Cull()
{
var clippersCount = m_Clippers.Count;
for (var i = 0; i < clippersCount; ++i)
{
m_Clippers[i].PerformClipping();
}
}
/// <summary>
/// Register a unique IClipper element
/// </summary>
/// <param name="c">The clipper element to add</param>
public static void Register(IClipper c)
{
if (c == null)
return;
instance.m_Clippers.AddUnique(c);
}
/// <summary>
/// UnRegister a IClipper element
/// </summary>
/// <param name="c">The Element to try and remove.</param>
public static void Unregister(IClipper c)
{
instance.m_Clippers.Remove(c);
}
/// <summary>
/// Disable a IClipper element
/// </summary>
/// <param name="c">The Element to try and disable.</param>
public static void Disable(IClipper c)
{
instance.m_Clippers.DisableItem(c);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9f1026265f8e3d54fb6e9f082c43debf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,51 @@
using System.Collections.Generic;
namespace UnityEngine.UI
{
/// <summary>
/// Utility class to help when clipping using IClipper.
/// </summary>
public static class Clipping
{
/// <summary>
/// Find the Rect to use for clipping.
/// Given the input RectMask2ds find a rectangle that is the overlap of all the inputs.
/// </summary>
/// <param name="rectMaskParents">RectMasks to build the overlap rect from.</param>
/// <param name="validRect">Was there a valid Rect found.</param>
/// <returns>The final compounded overlapping rect</returns>
public static Rect FindCullAndClipWorldRect(List<RectMask2D> rectMaskParents, out bool validRect)
{
if (rectMaskParents.Count == 0)
{
validRect = false;
return new Rect();
}
Rect current = rectMaskParents[0].canvasRect;
Vector4 offset = rectMaskParents[0].padding;
float xMin = current.xMin + offset.x;
float xMax = current.xMax - offset.z;
float yMin = current.yMin + offset.y;
float yMax = current.yMax - offset.w;
var rectMaskParentsCount = rectMaskParents.Count;
for (var i = 1; i < rectMaskParentsCount; ++i)
{
current = rectMaskParents[i].canvasRect;
offset = rectMaskParents[i].padding;
if (xMin < current.xMin + offset.x)
xMin = current.xMin + offset.x;
if (yMin < current.yMin + offset.y)
yMin = current.yMin + offset.y;
if (xMax > current.xMax - offset.z)
xMax = current.xMax - offset.z;
if (yMax > current.yMax - offset.w)
yMax = current.yMax - offset.w;
}
validRect = xMax > xMin && yMax > yMin;
return validRect ? new Rect(xMin, yMin, xMax - xMin, yMax - yMin) : new Rect();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 67e3583b91179094094c6a188b232262
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,60 @@
namespace UnityEngine.UI
{
/// <summary>
/// Interface that can be used to recieve clipping callbacks as part of the canvas update loop.
/// </summary>
public interface IClipper
{
/// <summary>
/// Function to to cull / clip children elements.
/// </summary>
/// <remarks>
/// Called after layout and before Graphic update of the Canvas update loop.
/// </remarks>
void PerformClipping();
}
/// <summary>
/// Interface for elements that can be clipped if they are under an IClipper
/// </summary>
public interface IClippable
{
/// <summary>
/// GameObject of the IClippable object
/// </summary>
GameObject gameObject { get; }
/// <summary>
/// Will be called when the state of a parent IClippable changed.
/// </summary>
void RecalculateClipping();
/// <summary>
/// The RectTransform of the clippable.
/// </summary>
RectTransform rectTransform { get; }
/// <summary>
/// Clip and cull the IClippable given a specific clipping rect
/// </summary>
/// <param name="clipRect">The Rectangle in which to clip against.</param>
/// <param name="validRect">Is the Rect valid. If not then the rect has 0 size.</param>
void Cull(Rect clipRect, bool validRect);
/// <summary>
/// Set the clip rect for the IClippable.
/// </summary>
/// <param name="value">The Rectangle for the clipping</param>
/// <param name="validRect">Is the rect valid.</param>
void SetClipRect(Rect value, bool validRect);
/// <summary>
/// Set the clip softness for the IClippable.
///
/// The softness is a linear alpha falloff over clipSoftness pixels.
/// </summary>
/// <param name="clipSoftness">The number of pixels to apply the softness to </param>
void SetClipSoftness(Vector2 clipSoftness);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 371b6b9c8adc50745ace66a6fdf67481
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,21 @@
namespace UnityEngine.UI
{
internal class RectangularVertexClipper
{
readonly Vector3[] m_WorldCorners = new Vector3[4];
readonly Vector3[] m_CanvasCorners = new Vector3[4];
public Rect GetCanvasRect(RectTransform t, Canvas c)
{
if (c == null)
return new Rect();
t.GetWorldCorners(m_WorldCorners);
var canvasTransform = c.GetComponent<Transform>();
for (int i = 0; i < 4; ++i)
m_CanvasCorners[i] = canvasTransform.InverseTransformPoint(m_WorldCorners[i]);
return new Rect(m_CanvasCorners[0].x, m_CanvasCorners[0].y, m_CanvasCorners[2].x - m_CanvasCorners[0].x, m_CanvasCorners[2].y - m_CanvasCorners[0].y);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c606041f02fc7d542b1429806872292b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,804 @@
using System;
using UnityEngine;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityEngine.UI
{
/// <summary>
/// Utility class for creating default implementations of builtin UI controls.
/// </summary>
/// <remarks>
/// The recommended workflow for using UI controls with the UI system is to create a prefab for each type of control and instantiate those when needed. This way changes can be made to the prefabs which immediately have effect on all used instances.
///
/// However, in certain cases there can be reasons to create UI controls entirely from code. The DefaultControls class provide methods to create each of the builtin UI controls. The resulting objects are the same as are obtained from using the corresponding UI menu entries in the GameObject menu in the Editor.
///
/// An example use of this is creating menu items for custom new UI controls that mimics the ones that are builtin in Unity. Some such UI controls may contain other UI controls. For example, a scroll view contains scrollbars.By using the DefaultControls methods to create those parts, it is ensured that they are identical in look and setup to the ones provided in the menu items builtin with Unity.
///
/// Note that the details of the setup of the UI controls created by the methods in this class may change with later revisions of the UI system.As such, they are not guaranteed to be 100% backwards compatible. It is recommended not to rely on the specific hierarchies of the GameObjects created by these methods, and limit your code to only interface with the root GameObject created by each method.
/// </remarks>
public static class DefaultControls
{
static IFactoryControls m_CurrentFactory = DefaultRuntimeFactory.Default;
public static IFactoryControls factory
{
get { return m_CurrentFactory; }
#if UNITY_EDITOR
set { m_CurrentFactory = value; }
#endif
}
/// <summary>
/// Factory interface to create a GameObject in this class.
/// It is necessary to use this interface in the whole class so MenuOption and Editor can work using ObjectFactory and default Presets.
/// </summary>
/// <remarks>
/// The only available method is CreateGameObject.
/// It needs to be called with every Components the created Object will need because of a bug with Undo and RectTransform.
/// Adding a UI component on the created GameObject may crash if done after Undo.SetTransformParent,
/// So it's better to prevent such behavior in this class by asking for full creation with all the components.
/// </remarks>
public interface IFactoryControls
{
GameObject CreateGameObject(string name, params Type[] components);
}
private class DefaultRuntimeFactory : IFactoryControls
{
public static IFactoryControls Default = new DefaultRuntimeFactory();
public GameObject CreateGameObject(string name, params Type[] components)
{
return new GameObject(name, components);
}
}
/// <summary>
/// Object used to pass resources to use for the default controls.
/// </summary>
public struct Resources
{
/// <summary>
/// The primary sprite to be used for graphical UI elements, used by the button, toggle, and dropdown controls, among others.
/// </summary>
public Sprite standard;
/// <summary>
/// Sprite used for background elements.
/// </summary>
public Sprite background;
/// <summary>
/// Sprite used as background for input fields.
/// </summary>
public Sprite inputField;
/// <summary>
/// Sprite used for knobs that can be dragged, such as on a slider.
/// </summary>
public Sprite knob;
/// <summary>
/// Sprite used for representation of an "on" state when present, such as a checkmark.
/// </summary>
public Sprite checkmark;
/// <summary>
/// Sprite used to indicate that a button will open a dropdown when clicked.
/// </summary>
public Sprite dropdown;
/// <summary>
/// Sprite used for masking purposes, for example to be used for the viewport of a scroll view.
/// </summary>
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, params Type[] components)
{
GameObject child = factory.CreateGameObject(name, components);
RectTransform rectTransform = child.GetComponent<RectTransform>();
rectTransform.sizeDelta = size;
return child;
}
private static GameObject CreateUIObject(string name, GameObject parent, params Type[] components)
{
GameObject go = factory.CreateGameObject(name, components);
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
// We may have a font assigned from default preset. We shouldn't override it
if (lbl.font == null)
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;
#if UNITY_EDITOR
Undo.SetTransformParent(child.transform, parent.transform, "");
#else
child.transform.SetParent(parent.transform, false);
#endif
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);
}
/// <summary>
/// Create the basic UI Panel.
/// </summary>
/// <remarks>
/// Hierarchy:
/// (root)
/// Image
/// </remarks>
/// <param name="resources">The resources to use for creation.</param>
/// <returns>The root GameObject of the created element.</returns>
public static GameObject CreatePanel(Resources resources)
{
GameObject panelRoot = CreateUIElementRoot("Panel", s_ThickElementSize, typeof(Image));
// 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.GetComponent<Image>();
image.sprite = resources.background;
image.type = Image.Type.Sliced;
image.color = s_PanelColor;
return panelRoot;
}
/// <summary>
/// Create the basic UI button.
/// </summary>
/// <remarks>
/// Hierarchy:
/// (root)
/// Button
/// -Text
/// </remarks>
/// <param name="resources">The resources to use for creation.</param>
/// <returns>The root GameObject of the created element.</returns>
public static GameObject CreateButton(Resources resources)
{
GameObject buttonRoot = CreateUIElementRoot("Button (Legacy)", s_ThickElementSize, typeof(Image), typeof(Button));
GameObject childText = CreateUIObject("Text (Legacy)", buttonRoot, typeof(Text));
Image image = buttonRoot.GetComponent<Image>();
image.sprite = resources.standard;
image.type = Image.Type.Sliced;
image.color = s_DefaultSelectableColor;
Button bt = buttonRoot.GetComponent<Button>();
SetDefaultColorTransitionValues(bt);
Text text = childText.GetComponent<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;
}
/// <summary>
/// Create the basic UI Text.
/// </summary>
/// <remarks>
/// Hierarchy:
/// (root)
/// Text
/// </remarks>
/// <param name="resources">The resources to use for creation.</param>
/// <returns>The root GameObject of the created element.</returns>
public static GameObject CreateText(Resources resources)
{
GameObject go = CreateUIElementRoot("Text (Legacy)", s_ThickElementSize, typeof(Text));
Text lbl = go.GetComponent<Text>();
lbl.text = "New Text";
SetDefaultTextValues(lbl);
return go;
}
/// <summary>
/// Create the basic UI Image.
/// </summary>
/// <remarks>
/// Hierarchy:
/// (root)
/// Image
/// </remarks>
/// <param name="resources">The resources to use for creation.</param>
/// <returns>The root GameObject of the created element.</returns>
public static GameObject CreateImage(Resources resources)
{
GameObject go = CreateUIElementRoot("Image", s_ImageElementSize, typeof(Image));
return go;
}
/// <summary>
/// Create the basic UI RawImage.
/// </summary>
/// <remarks>
/// Hierarchy:
/// (root)
/// RawImage
/// </remarks>
/// <param name="resources">The resources to use for creation.</param>
/// <returns>The root GameObject of the created element.</returns>
public static GameObject CreateRawImage(Resources resources)
{
GameObject go = CreateUIElementRoot("RawImage", s_ImageElementSize, typeof(RawImage));
return go;
}
/// <summary>
/// Create the basic UI Slider.
/// </summary>
/// <remarks>
/// Hierarchy:
/// (root)
/// Slider
/// - Background
/// - Fill Area
/// - Fill
/// - Handle Slide Area
/// - Handle
/// </remarks>
/// <param name="resources">The resources to use for creation.</param>
/// <returns>The root GameObject of the created element.</returns>
public static GameObject CreateSlider(Resources resources)
{
// Create GOs Hierarchy
GameObject root = CreateUIElementRoot("Slider", s_ThinElementSize, typeof(Slider));
GameObject background = CreateUIObject("Background", root, typeof(Image));
GameObject fillArea = CreateUIObject("Fill Area", root, typeof(RectTransform));
GameObject fill = CreateUIObject("Fill", fillArea, typeof(Image));
GameObject handleArea = CreateUIObject("Handle Slide Area", root, typeof(RectTransform));
GameObject handle = CreateUIObject("Handle", handleArea, typeof(Image));
// Background
Image backgroundImage = background.GetComponent<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.GetComponent<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.GetComponent<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.GetComponent<Slider>();
slider.fillRect = fill.GetComponent<RectTransform>();
slider.handleRect = handle.GetComponent<RectTransform>();
slider.targetGraphic = handleImage;
slider.direction = Slider.Direction.LeftToRight;
SetDefaultColorTransitionValues(slider);
return root;
}
/// <summary>
/// Create the basic UI Scrollbar.
/// </summary>
/// <remarks>
/// Hierarchy:
/// (root)
/// Scrollbar
/// - Sliding Area
/// - Handle
/// </remarks>
/// <param name="resources">The resources to use for creation.</param>
/// <returns>The root GameObject of the created element.</returns>
public static GameObject CreateScrollbar(Resources resources)
{
// Create GOs Hierarchy
GameObject scrollbarRoot = CreateUIElementRoot("Scrollbar", s_ThinElementSize, typeof(Image), typeof(Scrollbar));
GameObject sliderArea = CreateUIObject("Sliding Area", scrollbarRoot, typeof(RectTransform));
GameObject handle = CreateUIObject("Handle", sliderArea, typeof(Image));
Image bgImage = scrollbarRoot.GetComponent<Image>();
bgImage.sprite = resources.background;
bgImage.type = Image.Type.Sliced;
bgImage.color = s_DefaultSelectableColor;
Image handleImage = handle.GetComponent<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.GetComponent<Scrollbar>();
scrollbar.handleRect = handleRect;
scrollbar.targetGraphic = handleImage;
SetDefaultColorTransitionValues(scrollbar);
return scrollbarRoot;
}
/// <summary>
/// Create the basic UI Toggle.
/// </summary>
/// <remarks>
/// Hierarchy:
/// (root)
/// Toggle
/// - Background
/// - Checkmark
/// - Label
/// </remarks>
/// <param name="resources">The resources to use for creation.</param>
/// <returns>The root GameObject of the created element.</returns>
public static GameObject CreateToggle(Resources resources)
{
// Set up hierarchy
GameObject toggleRoot = CreateUIElementRoot("Toggle", s_ThinElementSize, typeof(Toggle));
GameObject background = CreateUIObject("Background", toggleRoot, typeof(Image));
GameObject checkmark = CreateUIObject("Checkmark", background, typeof(Image));
GameObject childLabel = CreateUIObject("Label", toggleRoot, typeof(Text));
// Set up components
Toggle toggle = toggleRoot.GetComponent<Toggle>();
toggle.isOn = true;
Image bgImage = background.GetComponent<Image>();
bgImage.sprite = resources.standard;
bgImage.type = Image.Type.Sliced;
bgImage.color = s_DefaultSelectableColor;
Image checkmarkImage = checkmark.GetComponent<Image>();
checkmarkImage.sprite = resources.checkmark;
Text label = childLabel.GetComponent<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;
}
/// <summary>
/// Create the basic UI input field.
/// </summary>
/// <remarks>
/// Hierarchy:
/// (root)
/// InputField
/// - PlaceHolder
/// - Text
/// </remarks>
/// <param name="resources">The resources to use for creation.</param>
/// <returns>The root GameObject of the created element.</returns>
public static GameObject CreateInputField(Resources resources)
{
GameObject root = CreateUIElementRoot("InputField (Legacy)", s_ThickElementSize, typeof(Image), typeof(InputField));
GameObject childPlaceholder = CreateUIObject("Placeholder", root, typeof(Text));
GameObject childText = CreateUIObject("Text (Legacy)", root, typeof(Text));
Image image = root.GetComponent<Image>();
image.sprite = resources.inputField;
image.type = Image.Type.Sliced;
image.color = s_DefaultSelectableColor;
InputField inputField = root.GetComponent<InputField>();
SetDefaultColorTransitionValues(inputField);
Text text = childText.GetComponent<Text>();
text.text = "";
text.supportRichText = false;
SetDefaultTextValues(text);
Text placeholder = childPlaceholder.GetComponent<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;
}
/// <summary>
/// Create the basic UI dropdown.
/// </summary>
/// <remarks>
/// Hierarchy:
/// (root)
/// Dropdown
/// - Label
/// - Arrow
/// - Template
/// - Viewport
/// - Content
/// - Item
/// - Item Background
/// - Item Checkmark
/// - Item Label
/// - Scrollbar
/// - Sliding Area
/// - Handle
/// </remarks>
/// <param name="resources">The resources to use for creation.</param>
/// <returns>The root GameObject of the created element.</returns>
public static GameObject CreateDropdown(Resources resources)
{
GameObject root = CreateUIElementRoot("Dropdown (Legacy)", s_ThickElementSize, typeof(Image), typeof(Dropdown));
GameObject label = CreateUIObject("Label", root, typeof(Text));
GameObject arrow = CreateUIObject("Arrow", root, typeof(Image));
GameObject template = CreateUIObject("Template", root, typeof(Image), typeof(ScrollRect));
GameObject viewport = CreateUIObject("Viewport", template, typeof(Image), typeof(Mask));
GameObject content = CreateUIObject("Content", viewport, typeof(RectTransform));
GameObject item = CreateUIObject("Item", content, typeof(Toggle));
GameObject itemBackground = CreateUIObject("Item Background", item, typeof(Image));
GameObject itemCheckmark = CreateUIObject("Item Checkmark", item, typeof(Image));
GameObject itemLabel = CreateUIObject("Item Label", item, typeof(Text));
// 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.GetComponent<Text>();
SetDefaultTextValues(itemLabelText);
itemLabelText.alignment = TextAnchor.MiddleLeft;
Image itemBackgroundImage = itemBackground.GetComponent<Image>();
itemBackgroundImage.color = new Color32(245, 245, 245, 255);
Image itemCheckmarkImage = itemCheckmark.GetComponent<Image>();
itemCheckmarkImage.sprite = resources.checkmark;
Toggle itemToggle = item.GetComponent<Toggle>();
itemToggle.targetGraphic = itemBackgroundImage;
itemToggle.graphic = itemCheckmarkImage;
itemToggle.isOn = true;
// Setup template UI components.
Image templateImage = template.GetComponent<Image>();
templateImage.sprite = resources.standard;
templateImage.type = Image.Type.Sliced;
ScrollRect templateScrollRect = template.GetComponent<ScrollRect>();
templateScrollRect.content = content.GetComponent<RectTransform>();
templateScrollRect.viewport = viewport.GetComponent<RectTransform>();
templateScrollRect.horizontal = false;
templateScrollRect.movementType = ScrollRect.MovementType.Clamped;
templateScrollRect.verticalScrollbar = scrollbarScrollbar;
templateScrollRect.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHideAndExpandViewport;
templateScrollRect.verticalScrollbarSpacing = -3;
Mask scrollRectMask = viewport.GetComponent<Mask>();
scrollRectMask.showMaskGraphic = false;
Image viewportImage = viewport.GetComponent<Image>();
viewportImage.sprite = resources.mask;
viewportImage.type = Image.Type.Sliced;
// Setup dropdown UI components.
Text labelText = label.GetComponent<Text>();
SetDefaultTextValues(labelText);
labelText.alignment = TextAnchor.MiddleLeft;
Image arrowImage = arrow.GetComponent<Image>();
arrowImage.sprite = resources.dropdown;
Image backgroundImage = root.GetComponent<Image>();
backgroundImage.sprite = resources.standard;
backgroundImage.color = s_DefaultSelectableColor;
backgroundImage.type = Image.Type.Sliced;
Dropdown dropdown = root.GetComponent<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;
}
/// <summary>
/// Create the basic UI Scrollview.
/// </summary>
/// <remarks>
/// Hierarchy:
/// (root)
/// Scrollview
/// - Viewport
/// - Content
/// - Scrollbar Horizontal
/// - Sliding Area
/// - Handle
/// - Scrollbar Vertical
/// - Sliding Area
/// - Handle
/// </remarks>
/// <param name="resources">The resources to use for creation.</param>
/// <returns>The root GameObject of the created element.</returns>
public static GameObject CreateScrollView(Resources resources)
{
GameObject root = CreateUIElementRoot("Scroll View", new Vector2(200, 200), typeof(Image), typeof(ScrollRect));
GameObject viewport = CreateUIObject("Viewport", root, typeof(Image), typeof(Mask));
GameObject content = CreateUIObject("Content", viewport, typeof(RectTransform));
// 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.GetComponent<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.GetComponent<Image>();
rootImage.sprite = resources.background;
rootImage.type = Image.Type.Sliced;
rootImage.color = s_PanelColor;
Mask viewportMask = viewport.GetComponent<Mask>();
viewportMask.showMaskGraphic = false;
Image viewportImage = viewport.GetComponent<Image>();
viewportImage.sprite = resources.mask;
viewportImage.type = Image.Type.Sliced;
return root;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bf87e20fb5440ec498f441167b5291f8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,1147 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.Pool;
using UnityEngine.UI.CoroutineTween;
namespace UnityEngine.UI
{
[AddComponentMenu("UI/Legacy/Dropdown", 102)]
[RequireComponent(typeof(RectTransform))]
/// <summary>
/// A standard dropdown that presents a list of options when clicked, of which one can be chosen.
/// </summary>
/// <remarks>
/// The dropdown component is a Selectable. When an option is chosen, the label and/or image of the control changes to show the chosen option.
///
/// When a dropdown event occurs a callback is sent to any registered listeners of onValueChanged.
/// </remarks>
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]
/// <summary>
/// Class to store the text and/or image of a single option in the dropdown list.
/// </summary>
public class OptionData
{
[SerializeField]
private string m_Text;
[SerializeField]
private Sprite m_Image;
/// <summary>
/// The text associated with the option.
/// </summary>
public string text { get { return m_Text; } set { m_Text = value; } }
/// <summary>
/// The image associated with the option.
/// </summary>
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;
}
/// <summary>
/// Create an object representing a single option for the dropdown list.
/// </summary>
/// <param name="text">Optional text for the option.</param>
/// <param name="image">Optional image for the option.</param>
public OptionData(string text, Sprite image)
{
this.text = text;
this.image = image;
}
}
[Serializable]
/// <summary>
/// Class used internally to store the list of options for the dropdown list.
/// </summary>
/// <remarks>
/// The usage of this class is not exposed in the runtime API. It's only relevant for the PropertyDrawer drawing the list of options.
/// </remarks>
public class OptionDataList
{
[SerializeField]
private List<OptionData> m_Options;
/// <summary>
/// The list of options for the dropdown list.
/// </summary>
public List<OptionData> options { get { return m_Options; } set { m_Options = value; } }
public OptionDataList()
{
options = new List<OptionData>();
}
}
[Serializable]
/// <summary>
/// UnityEvent callback for when a dropdown current option is changed.
/// </summary>
public class DropdownEvent : UnityEvent<int> {}
// Template used to create the dropdown.
[SerializeField]
private RectTransform m_Template;
/// <summary>
/// The Rect Transform of the template for the dropdown list.
/// </summary>
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;
/// <summary>
/// The Text component to hold the text of the currently selected option.
/// </summary>
public Text captionText { get { return m_CaptionText; } set { m_CaptionText = value; RefreshShownValue(); } }
[SerializeField]
private Image m_CaptionImage;
/// <summary>
/// The Image component to hold the image of the currently selected option.
/// </summary>
public Image captionImage { get { return m_CaptionImage; } set { m_CaptionImage = value; RefreshShownValue(); } }
[Space]
[SerializeField]
private Text m_ItemText;
/// <summary>
/// The Text component to hold the text of the item.
/// </summary>
public Text itemText { get { return m_ItemText; } set { m_ItemText = value; RefreshShownValue(); } }
[SerializeField]
private Image m_ItemImage;
/// <summary>
/// The Image component to hold the image of the item
/// </summary>
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();
/// <summary>
/// The list of possible options. A text string and an image can be specified for each option.
/// </summary>
/// <remarks>
/// This is the list of options within the Dropdown. Each option contains Text and/or image data that you can specify using UI.Dropdown.OptionData before adding to the Dropdown list.
/// This also unlocks the ability to edit the Dropdown, including the insertion, removal, and finding of options, as well as other useful tools
/// </remarks>
/// /// <example>
/// <code>
/// <![CDATA[
/// //Create a new Dropdown GameObject by going to the Hierarchy and clicking __Create__>__UI__>__Dropdown__. Attach this script to the Dropdown GameObject.
///
/// using UnityEngine;
/// using UnityEngine.UI;
/// using System.Collections.Generic;
///
/// public class Example : MonoBehaviour
/// {
/// //Use these for adding options to the Dropdown List
/// Dropdown.OptionData m_NewData, m_NewData2;
/// //The list of messages for the Dropdown
/// List<Dropdown.OptionData> m_Messages = new List<Dropdown.OptionData>();
///
///
/// //This is the Dropdown
/// Dropdown m_Dropdown;
/// string m_MyString;
/// int m_Index;
///
/// void Start()
/// {
/// //Fetch the Dropdown GameObject the script is attached to
/// m_Dropdown = GetComponent<Dropdown>();
/// //Clear the old options of the Dropdown menu
/// m_Dropdown.ClearOptions();
///
/// //Create a new option for the Dropdown menu which reads "Option 1" and add to messages List
/// m_NewData = new Dropdown.OptionData();
/// m_NewData.text = "Option 1";
/// m_Messages.Add(m_NewData);
///
/// //Create a new option for the Dropdown menu which reads "Option 2" and add to messages List
/// m_NewData2 = new Dropdown.OptionData();
/// m_NewData2.text = "Option 2";
/// m_Messages.Add(m_NewData2);
///
/// //Take each entry in the message List
/// foreach (Dropdown.OptionData message in m_Messages)
/// {
/// //Add each entry to the Dropdown
/// m_Dropdown.options.Add(message);
/// //Make the index equal to the total number of entries
/// m_Index = m_Messages.Count - 1;
/// }
/// }
///
/// //This OnGUI function is used here for a quick demonstration. See the [[wiki:UISystem|UI Section]] for more information about setting up your own UI.
/// void OnGUI()
/// {
/// //TextField for user to type new entry to add to Dropdown
/// m_MyString = GUI.TextField(new Rect(0, 40, 100, 40), m_MyString);
///
/// //Press the "Add" Button to add a new entry to the Dropdown
/// if (GUI.Button(new Rect(0, 0, 100, 40), "Add"))
/// {
/// //Make the index the last number of entries
/// m_Index = m_Messages.Count;
/// //Create a temporary option
/// Dropdown.OptionData temp = new Dropdown.OptionData();
/// //Make the option the data from the TextField
/// temp.text = m_MyString;
///
/// //Update the messages list with the TextField data
/// m_Messages.Add(temp);
///
/// //Add the Textfield data to the Dropdown
/// m_Dropdown.options.Insert(m_Index, temp);
/// }
///
/// //Press the "Remove" button to delete the selected option
/// if (GUI.Button(new Rect(110, 0, 100, 40), "Remove"))
/// {
/// //Remove the current selected item from the Dropdown from the messages List
/// m_Messages.RemoveAt(m_Dropdown.value);
/// //Remove the current selection from the Dropdown
/// m_Dropdown.options.RemoveAt(m_Dropdown.value);
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
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();
/// <summary>
/// A UnityEvent that is invoked when when a user has clicked one of the options in the dropdown list.
/// </summary>
/// <remarks>
/// Use this to detect when a user selects one or more options in the Dropdown. Add a listener to perform an action when this UnityEvent detects a selection by the user. See https://unity3d.com/learn/tutorials/topics/scripting/delegates for more information on delegates.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// //Create a new Dropdown GameObject by going to the Hierarchy and clicking Create>UI>Dropdown. Attach this script to the Dropdown GameObject.
/// //Set your own Text in the Inspector window
///
/// using UnityEngine;
/// using UnityEngine.UI;
///
/// public class Example : MonoBehaviour
/// {
/// Dropdown m_Dropdown;
/// public Text m_Text;
///
/// void Start()
/// {
/// //Fetch the Dropdown GameObject
/// m_Dropdown = GetComponent<Dropdown>();
/// //Add listener for when the value of the Dropdown changes, to take action
/// m_Dropdown.onValueChanged.AddListener(delegate {
/// DropdownValueChanged(m_Dropdown);
/// });
///
/// //Initialise the Text to say the first value of the Dropdown
/// m_Text.text = "First Value : " + m_Dropdown.value;
/// }
///
/// //Ouput the new value of the Dropdown into Text
/// void DropdownValueChanged(Dropdown change)
/// {
/// m_Text.text = "New Value : " + change.value;
/// }
/// }
/// ]]>
///</code>
/// </example>
public DropdownEvent onValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } }
[SerializeField]
private float m_AlphaFadeSpeed = 0.15f;
/// <summary>
/// The time interval at which a drop down will appear and disappear
/// </summary>
public float alphaFadeSpeed { get { return m_AlphaFadeSpeed; } set { m_AlphaFadeSpeed = 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 const int kHighSortingLayer = 30000;
private static OptionData s_NoOptionData = new OptionData();
/// <summary>
/// The Value is the index number of the current selection in the Dropdown. 0 is the first option in the Dropdown, 1 is the second, and so on.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// //Create a new Dropdown GameObject by going to the Hierarchy and clicking __Create__>__UI__>__Dropdown__. Attach this script to the Dropdown GameObject.
/// //Set your own Text in the Inspector window
///
/// using UnityEngine;
/// using UnityEngine.UI;
///
/// public class Example : MonoBehaviour
/// {
/// //Attach this script to a Dropdown GameObject
/// Dropdown m_Dropdown;
/// //This is the string that stores the current selection m_Text of the Dropdown
/// string m_Message;
/// //This Text outputs the current selection to the screen
/// public Text m_Text;
/// //This is the index value of the Dropdown
/// int m_DropdownValue;
///
/// void Start()
/// {
/// //Fetch the DropDown component from the GameObject
/// m_Dropdown = GetComponent<Dropdown>();
/// //Output the first Dropdown index value
/// Debug.Log("Starting Dropdown Value : " + m_Dropdown.value);
/// }
///
/// void Update()
/// {
/// //Keep the current index of the Dropdown in a variable
/// m_DropdownValue = m_Dropdown.value;
/// //Change the message to say the name of the current Dropdown selection using the value
/// m_Message = m_Dropdown.options[m_DropdownValue].text;
/// //Change the onscreen Text to reflect the current Dropdown selection
/// m_Text.text = m_Message;
/// }
/// }
/// ]]>
///</code>
/// </example>
public int value
{
get
{
return m_Value;
}
set
{
Set(value);
}
}
/// <summary>
/// Set index number of the current selection in the Dropdown without invoking onValueChanged callback.
/// </summary>
/// <param name="input"> The new index for the current selection. </param>
public void SetValueWithoutNotify(int input)
{
Set(input, false);
}
void Set(int value, bool sendCallback = true)
{
if (Application.isPlaying && (value == m_Value || options.Count == 0))
return;
m_Value = Mathf.Clamp(value, 0, options.Count - 1);
RefreshShownValue();
if (sendCallback)
{
// 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
if (m_CaptionImage)
m_CaptionImage.enabled = (m_CaptionImage.sprite != null);
if (m_Template)
m_Template.gameObject.SetActive(false);
}
protected override void Start()
{
m_AlphaTweenRunner = new TweenRunner<FloatTween>();
m_AlphaTweenRunner.Init(this);
base.Start();
RefreshShownValue();
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (!IsActive())
return;
RefreshShownValue();
}
#endif
protected override void OnDisable()
{
//Destroy dropdown and blocker in case user deactivates the dropdown when they click an option (case 935649)
ImmediateDestroyDropdownList();
if (m_Blocker != null)
DestroyBlocker(m_Blocker);
m_Blocker = null;
base.OnDisable();
}
/// <summary>
/// Refreshes the text and image (if available) of the currently selected option.
/// </summary>
/// <remarks>
/// If you have modified the list of options, you should call this method afterwards to ensure that the visual state of the dropdown corresponds to the updated options.
/// </remarks>
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);
}
}
/// <summary>
/// Add multiple options to the options of the Dropdown based on a list of OptionData objects.
/// </summary>
/// <param name="options">The list of OptionData to add.</param>
/// <remarks>
/// <![CDATA[
/// See AddOptions(List<string> options) for code example of usages.
/// ]]>
/// </remarks>
public void AddOptions(List<OptionData> options)
{
this.options.AddRange(options);
RefreshShownValue();
}
/// <summary>
/// Add multiple text-only options to the options of the Dropdown based on a list of strings.
/// </summary>
/// <remarks>
/// Add a List of string messages to the Dropdown. The Dropdown shows each member of the list as a separate option.
/// </remarks>
/// <param name="options">The list of text strings to add.</param>
/// <example>
/// <code>
/// <![CDATA[
/// //Create a new Dropdown GameObject by going to the Hierarchy and clicking Create>UI>Dropdown. Attach this script to the Dropdown GameObject.
///
/// using System.Collections.Generic;
/// using UnityEngine;
/// using UnityEngine.UI;
///
/// public class Example : MonoBehaviour
/// {
/// //Create a List of new Dropdown options
/// List<string> m_DropOptions = new List<string> { "Option 1", "Option 2"};
/// //This is the Dropdown
/// Dropdown m_Dropdown;
///
/// void Start()
/// {
/// //Fetch the Dropdown GameObject the script is attached to
/// m_Dropdown = GetComponent<Dropdown>();
/// //Clear the old options of the Dropdown menu
/// m_Dropdown.ClearOptions();
/// //Add the options created in the List above
/// m_Dropdown.AddOptions(m_DropOptions);
/// }
/// }
/// ]]>
///</code>
/// </example>
public void AddOptions(List<string> options)
{
var optionsCount = options.Count;
for (int i = 0; i < optionsCount; i++)
this.options.Add(new OptionData(options[i]));
RefreshShownValue();
}
/// <summary>
/// Add multiple image-only options to the options of the Dropdown based on a list of Sprites.
/// </summary>
/// <param name="options">The list of Sprites to add.</param>
/// <remarks>
/// <![CDATA[
/// See AddOptions(List<string> options) for code example of usages.
/// ]]>
/// </remarks>
public void AddOptions(List<Sprite> options)
{
var optionsCount = options.Count;
for (int i = 0; i < optionsCount; i++)
this.options.Add(new OptionData(options[i]));
RefreshShownValue();
}
/// <summary>
/// Clear the list of options in the Dropdown.
/// </summary>
public void ClearOptions()
{
options.Clear();
m_Value = 0;
RefreshShownValue();
}
private void SetupTemplate(Canvas rootCanvas)
{
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;
// Find the Canvas that this dropdown is a part of
Canvas parentCanvas = null;
Transform parentTransform = m_Template.parent;
while (parentTransform != null)
{
parentCanvas = parentTransform.GetComponent<Canvas>();
if (parentCanvas != null)
break;
parentTransform = parentTransform.parent;
}
// checks if a Canvas already exists before overriding it. (case 958281 - [UI] Child Canvas' Sorting Layer is changed to the same value as the parent)
if (!templateGo.TryGetComponent<Canvas>(out _))
{
Canvas popupCanvas = templateGo.AddComponent<Canvas>();
popupCanvas.overrideSorting = true;
popupCanvas.sortingOrder = kHighSortingLayer;
// popupCanvas used to assume the root canvas had the default sorting Layer, next line fixes (case 958281 - [UI] Dropdown list does not copy the parent canvas layer when the panel is opened)
popupCanvas.sortingLayerID = rootCanvas.sortingLayerID;
}
// If we have a parent canvas, apply the same raycasters as the parent for consistency.
if (parentCanvas != null)
{
Component[] components = parentCanvas.GetComponents<BaseRaycaster>();
for (int i = 0; i < components.Length; i++)
{
Type raycasterType = components[i].GetType();
if (templateGo.GetComponent(raycasterType) == null)
{
templateGo.AddComponent(raycasterType);
}
}
}
else
{
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;
}
/// <summary>
/// Handling for when the dropdown is initially 'clicked'. Typically shows the dropdown
/// </summary>
/// <param name="eventData">The asocciated event data.</param>
public virtual void OnPointerClick(PointerEventData eventData)
{
Show();
}
/// <summary>
/// Handling for when the dropdown is selected and a submit event is processed. Typically shows the dropdown
/// </summary>
/// <param name="eventData">The asocciated event data.</param>
public virtual void OnSubmit(BaseEventData eventData)
{
Show();
}
/// <summary>
/// This will hide the dropdown list.
/// </summary>
/// <remarks>
/// Called by a BaseInputModule when a Cancel event occurs.
/// </remarks>
/// <param name="eventData">The asocciated event data.</param>
public virtual void OnCancel(BaseEventData eventData)
{
Hide();
}
/// <summary>
/// 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 contain dropdowns.
/// </summary>
public void Show()
{
if (!IsActive() || !IsInteractable() || m_Dropdown != null)
return;
// Get root Canvas.
var list = ListPool<Canvas>.Get();
gameObject.GetComponentsInParent(false, list);
if (list.Count == 0)
return;
// case 1064466 rootCanvas should be last element returned by GetComponentsInParent()
var listCount = list.Count;
Canvas rootCanvas = list[listCount - 1];
for (int i = 0; i < listCount; i++)
{
if (list[i].isRootCanvas || list[i].overrideSorting)
{
rootCanvas = list[i];
break;
}
}
ListPool<Canvas>.Release(list);
if (!validTemplate)
{
SetupTemplate(rootCanvas);
if (!validTemplate)
return;
}
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;
var optionsCount = options.Count;
for (int i = 0; i < optionsCount; ++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] && !Mathf.Approximately(corner[axis], rootCanvasRect.min[axis])) ||
(corner[axis] > rootCanvasRect.max[axis] && !Mathf.Approximately(corner[axis], rootCanvasRect.max[axis])))
{
outside = true;
break;
}
}
if (outside)
RectTransformUtility.FlipLayoutOnAxis(dropdownRectTransform, axis, false, false);
}
var itemsCount = m_Items.Count;
for (int i = 0; i < itemsCount; 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 * (itemsCount - 1 - i) + itemSize.y * itemRect.pivot.y);
itemRect.sizeDelta = new Vector2(itemRect.sizeDelta.x, itemSize.y);
}
// Fade in the popup
AlphaFadeList(m_AlphaFadeSpeed, 0f, 1f);
// Make drop-down template and item template inactive
m_Template.gameObject.SetActive(false);
itemTemplate.gameObject.SetActive(false);
m_Blocker = CreateBlocker(rootCanvas);
}
/// <summary>
/// Create a blocker that blocks clicks to other controls while the dropdown list is open.
/// </summary>
/// <remarks>
/// Override this method to implement a different way to obtain a blocker GameObject.
/// </remarks>
/// <param name="rootCanvas">The root canvas the dropdown is under.</param>
/// <returns>The created blocker object</returns>
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;
// Find the Canvas that this dropdown is a part of
Canvas parentCanvas = null;
Transform parentTransform = m_Template.parent;
while (parentTransform != null)
{
parentCanvas = parentTransform.GetComponent<Canvas>();
if (parentCanvas != null)
break;
parentTransform = parentTransform.parent;
}
// If we have a parent canvas, apply the same raycasters as the parent for consistency.
if (parentCanvas != null)
{
Component[] components = parentCanvas.GetComponents<BaseRaycaster>();
for (int i = 0; i < components.Length; i++)
{
Type raycasterType = components[i].GetType();
if (blocker.GetComponent(raycasterType) == null)
{
blocker.AddComponent(raycasterType);
}
}
}
else
{
// Add raycaster since it's needed to block.
GetOrAddComponent<GraphicRaycaster>(blocker);
}
// 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);
//add canvas group to ensure clicking outside the dropdown will hide it (UUM-33691)
CanvasGroup blockerCanvasGroup = blocker.AddComponent<CanvasGroup>();
blockerCanvasGroup.ignoreParentGroups = true;
return blocker;
}
/// <summary>
/// Convenience method to explicitly destroy the previously generated blocker object
/// </summary>
/// <remarks>
/// Override this method to implement a different way to dispose of a blocker GameObject that blocks clicks to other controls while the dropdown list is open.
/// </remarks>
/// <param name="blocker">The blocker object to destroy.</param>
protected virtual void DestroyBlocker(GameObject blocker)
{
Destroy(blocker);
}
/// <summary>
/// Create the dropdown list to be shown when the dropdown is clicked. The dropdown list should correspond to the provided template GameObject, equivalent to instantiating a copy of it.
/// </summary>
/// <remarks>
/// Override this method to implement a different way to obtain a dropdown list GameObject.
/// </remarks>
/// <param name="template">The template to create the dropdown list from.</param>
/// <returns>The created drop down list gameobject.</returns>
protected virtual GameObject CreateDropdownList(GameObject template)
{
return (GameObject)Instantiate(template);
}
/// <summary>
/// Convenience method to explicitly destroy the previously generated dropdown list
/// </summary>
/// <remarks>
/// Override this method to implement a different way to dispose of a dropdown list GameObject.
/// </remarks>
/// <param name="dropdownList">The dropdown list GameObject to destroy</param>
protected virtual void DestroyDropdownList(GameObject dropdownList)
{
Destroy(dropdownList);
}
/// <summary>
/// Create a dropdown item based upon the item template.
/// </summary>
/// <remarks>
/// Override this method to implement a different way to obtain an option item.
/// The option item should correspond to the provided template DropdownItem and its GameObject, equivalent to instantiating a copy of it.
/// </remarks>
/// <param name="itemTemplate">e template to create the option item from.</param>
/// <returns>The created dropdown item component</returns>
protected virtual DropdownItem CreateItem(DropdownItem itemTemplate)
{
return (DropdownItem)Instantiate(itemTemplate);
}
/// <summary>
/// Convenience method to explicitly destroy the previously generated Items.
/// </summary>
/// <remarks>
/// Override this method to implement a different way to dispose of an option item.
/// Likely no action needed since destroying the dropdown list destroys all contained items as well.
/// </remarks>
/// <param name="item">The Item to destroy.</param>
protected virtual void DestroyItem(DropdownItem item)
{}
// 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;
}
/// <summary>
/// Hide the dropdown list. I.e. close it.
/// </summary>
public void Hide()
{
if (m_Dropdown != null)
{
AlphaFadeList(m_AlphaFadeSpeed, 0f);
// User could have disabled the dropdown during the OnValueChanged call.
if (IsActive())
StartCoroutine(DelayedDestroyDropdownList(m_AlphaFadeSpeed));
}
if (m_Blocker != null)
DestroyBlocker(m_Blocker);
m_Blocker = null;
Select();
}
private IEnumerator DelayedDestroyDropdownList(float delay)
{
yield return new WaitForSecondsRealtime(delay);
ImmediateDestroyDropdownList();
}
private void ImmediateDestroyDropdownList()
{
var itemsCount = m_Items.Count;
for (int i = 0; i < itemsCount; 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();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0d0b652f32a2cc243917e4028fa0f046
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,199 @@
using System;
using UnityEngine.Serialization;
namespace UnityEngine.UI
{
[Serializable]
/// <summary>
/// Struct for storing Text generation settings.
/// </summary>
public class FontData : ISerializationCallbackReceiver
{
[SerializeField]
[FormerlySerializedAs("font")]
private Font m_Font;
[SerializeField]
[FormerlySerializedAs("fontSize")]
private int m_FontSize;
[SerializeField]
[FormerlySerializedAs("fontStyle")]
private FontStyle m_FontStyle;
[SerializeField]
private bool m_BestFit;
[SerializeField]
private int m_MinSize;
[SerializeField]
private int m_MaxSize;
[SerializeField]
[FormerlySerializedAs("alignment")]
private TextAnchor m_Alignment;
[SerializeField]
private bool m_AlignByGeometry;
[SerializeField]
[FormerlySerializedAs("richText")]
private bool m_RichText;
[SerializeField]
private HorizontalWrapMode m_HorizontalOverflow;
[SerializeField]
private VerticalWrapMode m_VerticalOverflow;
[SerializeField]
private float m_LineSpacing;
/// <summary>
/// Get a font data with sensible defaults.
/// </summary>
public static FontData defaultFontData
{
get
{
var fontData = new FontData
{
m_FontSize = 14,
m_LineSpacing = 1f,
m_FontStyle = FontStyle.Normal,
m_BestFit = false,
m_MinSize = 10,
m_MaxSize = 40,
m_Alignment = TextAnchor.UpperLeft,
m_HorizontalOverflow = HorizontalWrapMode.Wrap,
m_VerticalOverflow = VerticalWrapMode.Truncate,
m_RichText = true,
m_AlignByGeometry = false
};
return fontData;
}
}
/// <summary>
/// The Font to use for this generated Text object.
/// </summary>
public Font font
{
get { return m_Font; }
set { m_Font = value; }
}
/// <summary>
/// The Font size to use for this generated Text object.
/// </summary>
public int fontSize
{
get { return m_FontSize; }
set { m_FontSize = value; }
}
/// <summary>
/// The font style to use for this generated Text object.
/// </summary>
public FontStyle fontStyle
{
get { return m_FontStyle; }
set { m_FontStyle = value; }
}
/// <summary>
/// Is best fit used for this generated Text object.
/// </summary>
public bool bestFit
{
get { return m_BestFit; }
set { m_BestFit = value; }
}
/// <summary>
/// The min size for this generated Text object.
/// </summary>
public int minSize
{
get { return m_MinSize; }
set { m_MinSize = value; }
}
/// <summary>
/// The max size for this generated Text object.
/// </summary>
public int maxSize
{
get { return m_MaxSize; }
set { m_MaxSize = value; }
}
/// <summary>
/// How is the text aligned for this generated Text object.
/// </summary>
public TextAnchor alignment
{
get { return m_Alignment; }
set { m_Alignment = value; }
}
/// <summary>
/// Use the extents of glyph geometry to perform horizontal alignment rather than glyph metrics.
/// </summary>
/// <remarks>
/// This can result in better fitting left and right alignment, but may result in incorrect positioning when attempting to overlay multiple fonts (such as a specialized outline font) on top of each other.
/// </remarks>
public bool alignByGeometry
{
get { return m_AlignByGeometry; }
set { m_AlignByGeometry = value; }
}
/// <summary>
/// Should rich text be used for this generated Text object.
/// </summary>
public bool richText
{
get { return m_RichText; }
set { m_RichText = value; }
}
/// <summary>
/// The horizontal overflow policy for this generated Text object.
/// </summary>
public HorizontalWrapMode horizontalOverflow
{
get { return m_HorizontalOverflow; }
set { m_HorizontalOverflow = value; }
}
/// <summary>
/// The vertical overflow policy for this generated Text object.
/// </summary>
public VerticalWrapMode verticalOverflow
{
get { return m_VerticalOverflow; }
set { m_VerticalOverflow = value; }
}
/// <summary>
/// The line spaceing for this generated Text object.
/// </summary>
public float lineSpacing
{
get { return m_LineSpacing; }
set { m_LineSpacing = value; }
}
void ISerializationCallbackReceiver.OnBeforeSerialize()
{}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
m_FontSize = Mathf.Clamp(m_FontSize, 0, 300);
m_MinSize = Mathf.Clamp(m_MinSize, 0, m_FontSize);
m_MaxSize = Mathf.Clamp(m_MaxSize, m_FontSize, 300);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 13cd966480ec3354bb318ee1aeccff6f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEngine.UI
{
/// <summary>
/// Utility class that is used to help with Text update.
/// </summary>
/// <remarks>
/// When Unity rebuilds a font atlas a callback is sent to the font. Using this class you can register your text as needing to be rebuilt if the font atlas is updated.
/// </remarks>
public static class FontUpdateTracker
{
static Dictionary<Font, HashSet<Text>> m_Tracked = new Dictionary<Font, HashSet<Text>>();
/// <summary>
/// Register a Text element for receiving texture atlas rebuild calls.
/// </summary>
/// <param name="t">The Text object to track</param>
public static void TrackText(Text t)
{
if (t.font == null)
return;
HashSet<Text> exists;
m_Tracked.TryGetValue(t.font, out exists);
if (exists == null)
{
// The textureRebuilt event is global for all fonts, so we add our delegate the first time we register *any* Text
if (m_Tracked.Count == 0)
Font.textureRebuilt += RebuildForFont;
exists = new HashSet<Text>();
m_Tracked.Add(t.font, exists);
}
exists.Add(t);
}
private static void RebuildForFont(Font f)
{
HashSet<Text> texts;
m_Tracked.TryGetValue(f, out texts);
if (texts == null)
return;
foreach (var text in texts)
text.FontTextureChanged();
}
/// <summary>
/// Deregister a Text element from receiving texture atlas rebuild calls.
/// </summary>
/// <param name="t">The Text object to no longer track</param>
public static void UntrackText(Text t)
{
if (t.font == null)
return;
HashSet<Text> texts;
m_Tracked.TryGetValue(t.font, out texts);
if (texts == null)
return;
texts.Remove(t);
if (texts.Count == 0)
{
m_Tracked.Remove(t.font);
// There is a global textureRebuilt event for all fonts, so once the last Text reference goes away, remove our delegate
if (m_Tracked.Count == 0)
Font.textureRebuilt -= RebuildForFont;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 39ab466162988eb4f83443f911bbf5c8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,1057 @@
using System;
#if UNITY_EDITOR
using System.Reflection;
#endif
using System.Collections.Generic;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
using UnityEngine.UI.CoroutineTween;
using UnityEngine.Pool;
namespace UnityEngine.UI
{
/// <summary>
/// Base class for all UI components that should be derived from when creating new Graphic types.
/// </summary>
[DisallowMultipleComponent]
[RequireComponent(typeof(RectTransform))]
[ExecuteAlways]
/// <summary>
/// Base class for all visual UI Component.
/// When creating visual UI components you should inherit from this class.
/// </summary>
/// <example>
/// Below is a simple example that draws a colored quad inside the Rect Transform area.
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using UnityEngine.UI;
///
/// [ExecuteInEditMode]
/// public class SimpleImage : Graphic
/// {
/// protected override void OnPopulateMesh(VertexHelper vh)
/// {
/// Vector2 corner1 = Vector2.zero;
/// Vector2 corner2 = Vector2.zero;
///
/// corner1.x = 0f;
/// corner1.y = 0f;
/// corner2.x = 1f;
/// corner2.y = 1f;
///
/// corner1.x -= rectTransform.pivot.x;
/// corner1.y -= rectTransform.pivot.y;
/// corner2.x -= rectTransform.pivot.x;
/// corner2.y -= rectTransform.pivot.y;
///
/// corner1.x *= rectTransform.rect.width;
/// corner1.y *= rectTransform.rect.height;
/// corner2.x *= rectTransform.rect.width;
/// corner2.y *= rectTransform.rect.height;
///
/// vh.Clear();
///
/// UIVertex vert = UIVertex.simpleVert;
///
/// vert.position = new Vector2(corner1.x, corner1.y);
/// vert.color = color;
/// vh.AddVert(vert);
///
/// vert.position = new Vector2(corner1.x, corner2.y);
/// vert.color = color;
/// vh.AddVert(vert);
///
/// vert.position = new Vector2(corner2.x, corner2.y);
/// vert.color = color;
/// vh.AddVert(vert);
///
/// vert.position = new Vector2(corner2.x, corner1.y);
/// vert.color = color;
/// vh.AddVert(vert);
///
/// vh.AddTriangle(0, 1, 2);
/// vh.AddTriangle(2, 3, 0);
/// }
/// }
/// ]]>
///</code>
/// </example>
public abstract class Graphic
: UIBehaviour,
ICanvasElement
{
static protected Material s_DefaultUI = null;
static protected Texture2D s_WhiteTexture = null;
/// <summary>
/// Default material used to draw UI elements if no explicit material was specified.
/// </summary>
static public Material defaultGraphicMaterial
{
get
{
if (s_DefaultUI == null)
s_DefaultUI = Canvas.GetDefaultCanvasMaterial();
return s_DefaultUI;
}
}
// Cached and saved values
[FormerlySerializedAs("m_Mat")]
[SerializeField] protected Material m_Material;
[SerializeField] private Color m_Color = Color.white;
[NonSerialized] protected bool m_SkipLayoutUpdate;
[NonSerialized] protected bool m_SkipMaterialUpdate;
/// <summary>
/// Base color of the Graphic.
/// </summary>
/// <remarks>
/// The builtin UI Components use this as their vertex color. Use this to fetch or change the Color of visual UI elements, such as an Image.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// //Place this script on a GameObject with a Graphic component attached e.g. a visual UI element (Image).
///
/// using UnityEngine;
/// using UnityEngine.UI;
///
/// public class Example : MonoBehaviour
/// {
/// Graphic m_Graphic;
/// Color m_MyColor;
///
/// void Start()
/// {
/// //Fetch the Graphic from the GameObject
/// m_Graphic = GetComponent<Graphic>();
/// //Create a new Color that starts as red
/// m_MyColor = Color.red;
/// //Change the Graphic Color to the new Color
/// m_Graphic.color = m_MyColor;
/// }
///
/// // Update is called once per frame
/// void Update()
/// {
/// //When the mouse button is clicked, change the Graphic Color
/// if (Input.GetKey(KeyCode.Mouse0))
/// {
/// //Change the Color over time between blue and red while the mouse button is pressed
/// m_MyColor = Color.Lerp(Color.red, Color.blue, Mathf.PingPong(Time.time, 1));
/// }
/// //Change the Graphic Color to the new Color
/// m_Graphic.color = m_MyColor;
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual Color color { get { return m_Color; } set { if (SetPropertyUtility.SetColor(ref m_Color, value)) SetVerticesDirty(); } }
[SerializeField] private bool m_RaycastTarget = true;
private bool m_RaycastTargetCache = true;
/// <summary>
/// Should this graphic be considered a target for raycasting?
/// </summary>
public virtual bool raycastTarget
{
get
{
return m_RaycastTarget;
}
set
{
if (value != m_RaycastTarget)
{
if (m_RaycastTarget)
GraphicRegistry.UnregisterRaycastGraphicForCanvas(canvas, this);
m_RaycastTarget = value;
if (m_RaycastTarget && isActiveAndEnabled)
GraphicRegistry.RegisterRaycastGraphicForCanvas(canvas, this);
}
m_RaycastTargetCache = value;
}
}
[SerializeField]
private Vector4 m_RaycastPadding = new Vector4();
/// <summary>
/// Padding to be applied to the masking
/// X = Left
/// Y = Bottom
/// Z = Right
/// W = Top
/// </summary>
public Vector4 raycastPadding
{
get { return m_RaycastPadding; }
set
{
m_RaycastPadding = value;
}
}
[NonSerialized] private RectTransform m_RectTransform;
[NonSerialized] private CanvasRenderer m_CanvasRenderer;
[NonSerialized] private Canvas m_Canvas;
[NonSerialized] private bool m_VertsDirty;
[NonSerialized] private bool m_MaterialDirty;
[NonSerialized] protected UnityAction m_OnDirtyLayoutCallback;
[NonSerialized] protected UnityAction m_OnDirtyVertsCallback;
[NonSerialized] protected UnityAction m_OnDirtyMaterialCallback;
[NonSerialized] protected static Mesh s_Mesh;
[NonSerialized] private static readonly VertexHelper s_VertexHelper = new VertexHelper();
[NonSerialized] protected Mesh m_CachedMesh;
[NonSerialized] protected Vector2[] m_CachedUvs;
// Tween controls for the Graphic
[NonSerialized]
private readonly TweenRunner<ColorTween> m_ColorTweenRunner;
protected bool useLegacyMeshGeneration { get; set; }
// Called by Unity prior to deserialization,
// should not be called by users
protected Graphic()
{
if (m_ColorTweenRunner == null)
m_ColorTweenRunner = new TweenRunner<ColorTween>();
m_ColorTweenRunner.Init(this);
useLegacyMeshGeneration = true;
}
/// <summary>
/// Set all properties of the Graphic dirty and needing rebuilt.
/// Dirties Layout, Vertices, and Materials.
/// </summary>
public virtual void SetAllDirty()
{
// Optimization: Graphic layout doesn't need recalculation if
// the underlying Sprite is the same size with the same texture.
// (e.g. Sprite sheet texture animation)
if (m_SkipLayoutUpdate)
{
m_SkipLayoutUpdate = false;
}
else
{
SetLayoutDirty();
}
if (m_SkipMaterialUpdate)
{
m_SkipMaterialUpdate = false;
}
else
{
SetMaterialDirty();
}
SetVerticesDirty();
SetRaycastDirty();
}
/// <summary>
/// Mark the layout as dirty and needing rebuilt.
/// </summary>
/// <remarks>
/// Send a OnDirtyLayoutCallback notification if any elements are registered. See RegisterDirtyLayoutCallback
/// </remarks>
public virtual void SetLayoutDirty()
{
if (!IsActive())
return;
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
if (m_OnDirtyLayoutCallback != null)
m_OnDirtyLayoutCallback();
}
/// <summary>
/// Mark the vertices as dirty and needing rebuilt.
/// </summary>
/// <remarks>
/// Send a OnDirtyVertsCallback notification if any elements are registered. See RegisterDirtyVerticesCallback
/// </remarks>
public virtual void SetVerticesDirty()
{
if (!IsActive())
return;
m_VertsDirty = true;
CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
if (m_OnDirtyVertsCallback != null)
m_OnDirtyVertsCallback();
}
/// <summary>
/// Mark the material as dirty and needing rebuilt.
/// </summary>
/// <remarks>
/// Send a OnDirtyMaterialCallback notification if any elements are registered. See RegisterDirtyMaterialCallback
/// </remarks>
public virtual void SetMaterialDirty()
{
if (!IsActive())
return;
m_MaterialDirty = true;
CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
if (m_OnDirtyMaterialCallback != null)
m_OnDirtyMaterialCallback();
}
public void SetRaycastDirty()
{
if (m_RaycastTargetCache != m_RaycastTarget)
{
if (m_RaycastTarget && isActiveAndEnabled)
GraphicRegistry.RegisterRaycastGraphicForCanvas(canvas, this);
else if (!m_RaycastTarget)
GraphicRegistry.UnregisterRaycastGraphicForCanvas(canvas, this);
}
m_RaycastTargetCache = m_RaycastTarget;
}
protected override void OnRectTransformDimensionsChange()
{
if (gameObject.activeInHierarchy)
{
// prevent double dirtying...
if (CanvasUpdateRegistry.IsRebuildingLayout())
SetVerticesDirty();
else
{
SetVerticesDirty();
SetLayoutDirty();
}
}
}
protected override void OnBeforeTransformParentChanged()
{
GraphicRegistry.UnregisterGraphicForCanvas(canvas, this);
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
}
protected override void OnTransformParentChanged()
{
base.OnTransformParentChanged();
m_Canvas = null;
if (!IsActive())
return;
CacheCanvas();
GraphicRegistry.RegisterGraphicForCanvas(canvas, this);
SetAllDirty();
}
/// <summary>
/// Absolute depth of the graphic, used by rendering and events -- lowest to highest.
/// </summary>
/// <example>
/// The depth is relative to the first root canvas.
///
/// Canvas
/// Graphic - 1
/// Graphic - 2
/// Nested Canvas
/// Graphic - 3
/// Graphic - 4
/// Graphic - 5
///
/// This value is used to determine draw and event ordering.
/// </example>
public int depth { get { return canvasRenderer.absoluteDepth; } }
/// <summary>
/// The RectTransform component used by the Graphic. Cached for speed.
/// </summary>
public RectTransform rectTransform
{
get
{
// The RectTransform is a required component that must not be destroyed. Based on this assumption, a
// null-reference check is sufficient.
if (ReferenceEquals(m_RectTransform, null))
{
m_RectTransform = GetComponent<RectTransform>();
}
return m_RectTransform;
}
}
/// <summary>
/// A reference to the Canvas this Graphic is rendering to.
/// </summary>
/// <remarks>
/// In the situation where the Graphic is used in a hierarchy with multiple Canvases, the Canvas closest to the root will be used.
/// </remarks>
public Canvas canvas
{
get
{
if (m_Canvas == null)
CacheCanvas();
return m_Canvas;
}
}
private void CacheCanvas()
{
var list = ListPool<Canvas>.Get();
gameObject.GetComponentsInParent(false, list);
if (list.Count > 0)
{
// Find the first active and enabled canvas.
for (int i = 0; i < list.Count; ++i)
{
if (list[i].isActiveAndEnabled)
{
m_Canvas = list[i];
break;
}
// if we reached the end and couldn't find an active and enabled canvas, we should return null . case 1171433
if (i == list.Count - 1)
m_Canvas = null;
}
}
else
{
m_Canvas = null;
}
ListPool<Canvas>.Release(list);
}
/// <summary>
/// A reference to the CanvasRenderer populated by this Graphic.
/// </summary>
public CanvasRenderer canvasRenderer
{
get
{
// The CanvasRenderer is a required component that must not be destroyed. Based on this assumption, a
// null-reference check is sufficient.
if (ReferenceEquals(m_CanvasRenderer, null))
{
m_CanvasRenderer = GetComponent<CanvasRenderer>();
if (ReferenceEquals(m_CanvasRenderer, null))
{
m_CanvasRenderer = gameObject.AddComponent<CanvasRenderer>();
}
}
return m_CanvasRenderer;
}
}
/// <summary>
/// Returns the default material for the graphic.
/// </summary>
public virtual Material defaultMaterial
{
get { return defaultGraphicMaterial; }
}
/// <summary>
/// The Material set by the user
/// </summary>
public virtual Material material
{
get
{
return (m_Material != null) ? m_Material : defaultMaterial;
}
set
{
if (m_Material == value)
return;
m_Material = value;
SetMaterialDirty();
}
}
/// <summary>
/// The material that will be sent for Rendering (Read only).
/// </summary>
/// <remarks>
/// This is the material that actually gets sent to the CanvasRenderer. By default it's the same as [[Graphic.material]]. When extending Graphic you can override this to send a different material to the CanvasRenderer than the one set by Graphic.material. This is useful if you want to modify the user set material in a non destructive manner.
/// </remarks>
public virtual Material materialForRendering
{
get
{
var components = ListPool<IMaterialModifier>.Get();
GetComponents<IMaterialModifier>(components);
var currentMat = material;
for (var i = 0; i < components.Count; i++)
currentMat = (components[i] as IMaterialModifier).GetModifiedMaterial(currentMat);
ListPool<IMaterialModifier>.Release(components);
return currentMat;
}
}
/// <summary>
/// The graphic's texture. (Read Only).
/// </summary>
/// <remarks>
/// This is the Texture that gets passed to the CanvasRenderer, Material and then Shader _MainTex.
///
/// When implementing your own Graphic you can override this to control which texture goes through the UI Rendering pipeline.
///
/// Bear in mind that Unity tries to batch UI elements together to improve performance, so its ideal to work with atlas to reduce the number of draw calls.
/// </remarks>
public virtual Texture mainTexture
{
get
{
return s_WhiteTexture;
}
}
/// <summary>
/// Mark the Graphic and the canvas as having been changed.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
CacheCanvas();
GraphicRegistry.RegisterGraphicForCanvas(canvas, this);
#if UNITY_EDITOR
GraphicRebuildTracker.TrackGraphic(this);
#endif
if (s_WhiteTexture == null)
s_WhiteTexture = Texture2D.whiteTexture;
SetAllDirty();
}
/// <summary>
/// Clear references.
/// </summary>
protected override void OnDisable()
{
#if UNITY_EDITOR
GraphicRebuildTracker.UnTrackGraphic(this);
#endif
GraphicRegistry.DisableGraphicForCanvas(canvas, this);
CanvasUpdateRegistry.DisableCanvasElementForRebuild(this);
if (canvasRenderer != null)
canvasRenderer.Clear();
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
base.OnDisable();
}
protected override void OnDestroy()
{
#if UNITY_EDITOR
GraphicRebuildTracker.UnTrackGraphic(this);
#endif
GraphicRegistry.UnregisterGraphicForCanvas(canvas, this);
CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild(this);
if (m_CachedMesh)
Destroy(m_CachedMesh);
m_CachedMesh = null;
base.OnDestroy();
}
protected override void OnCanvasHierarchyChanged()
{
// Use m_Cavas so we dont auto call CacheCanvas
Canvas currentCanvas = m_Canvas;
// Clear the cached canvas. Will be fetched below if active.
m_Canvas = null;
if (!IsActive())
{
GraphicRegistry.UnregisterGraphicForCanvas(currentCanvas, this);
return;
}
CacheCanvas();
if (currentCanvas != m_Canvas)
{
GraphicRegistry.UnregisterGraphicForCanvas(currentCanvas, this);
// Only register if we are active and enabled as OnCanvasHierarchyChanged can get called
// during object destruction and we dont want to register ourself and then become null.
if (IsActive())
GraphicRegistry.RegisterGraphicForCanvas(canvas, this);
}
}
/// <summary>
/// This method must be called when <c>CanvasRenderer.cull</c> is modified.
/// </summary>
/// <remarks>
/// This can be used to perform operations that were previously skipped because the <c>Graphic</c> was culled.
/// </remarks>
public virtual void OnCullingChanged()
{
if (!canvasRenderer.cull && (m_VertsDirty || m_MaterialDirty))
{
/// When we were culled, we potentially skipped calls to <c>Rebuild</c>.
CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
}
}
/// <summary>
/// Rebuilds the graphic geometry and its material on the PreRender cycle.
/// </summary>
/// <param name="update">The current step of the rendering CanvasUpdate cycle.</param>
/// <remarks>
/// See CanvasUpdateRegistry for more details on the canvas update cycle.
/// </remarks>
public virtual void Rebuild(CanvasUpdate update)
{
if (canvasRenderer == null || canvasRenderer.cull)
return;
switch (update)
{
case CanvasUpdate.PreRender:
if (m_VertsDirty)
{
UpdateGeometry();
m_VertsDirty = false;
}
if (m_MaterialDirty)
{
UpdateMaterial();
m_MaterialDirty = false;
}
break;
}
}
public virtual void LayoutComplete()
{}
public virtual void GraphicUpdateComplete()
{}
/// <summary>
/// Call to update the Material of the graphic onto the CanvasRenderer.
/// </summary>
protected virtual void UpdateMaterial()
{
if (!IsActive())
return;
canvasRenderer.materialCount = 1;
canvasRenderer.SetMaterial(materialForRendering, 0);
canvasRenderer.SetTexture(mainTexture);
}
/// <summary>
/// Call to update the geometry of the Graphic onto the CanvasRenderer.
/// </summary>
protected virtual void UpdateGeometry()
{
if (useLegacyMeshGeneration)
{
DoLegacyMeshGeneration();
}
else
{
DoMeshGeneration();
}
}
private void DoMeshGeneration()
{
if (rectTransform != null && rectTransform.rect.width >= 0 && rectTransform.rect.height >= 0)
OnPopulateMesh(s_VertexHelper);
else
s_VertexHelper.Clear(); // clear the vertex helper so invalid graphics dont draw.
var components = ListPool<Component>.Get();
GetComponents(typeof(IMeshModifier), components);
for (var i = 0; i < components.Count; i++)
((IMeshModifier)components[i]).ModifyMesh(s_VertexHelper);
ListPool<Component>.Release(components);
s_VertexHelper.FillMesh(workerMesh);
canvasRenderer.SetMesh(workerMesh);
}
private void DoLegacyMeshGeneration()
{
if (rectTransform != null && rectTransform.rect.width >= 0 && rectTransform.rect.height >= 0)
{
#pragma warning disable 618
OnPopulateMesh(workerMesh);
#pragma warning restore 618
}
else
{
workerMesh.Clear();
}
var components = ListPool<Component>.Get();
GetComponents(typeof(IMeshModifier), components);
for (var i = 0; i < components.Count; i++)
{
#pragma warning disable 618
((IMeshModifier)components[i]).ModifyMesh(workerMesh);
#pragma warning restore 618
}
ListPool<Component>.Release(components);
canvasRenderer.SetMesh(workerMesh);
}
protected static Mesh workerMesh
{
get
{
if (s_Mesh == null)
{
s_Mesh = new Mesh();
s_Mesh.name = "Shared UI Mesh";
}
return s_Mesh;
}
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
[Obsolete("Use OnPopulateMesh instead.", true)]
protected virtual void OnFillVBO(System.Collections.Generic.List<UIVertex> vbo) {}
[Obsolete("Use OnPopulateMesh(VertexHelper vh) instead.", false)]
/// <summary>
/// Callback function when a UI element needs to generate vertices. Fills the vertex buffer data.
/// </summary>
/// <param name="m">Mesh to populate with UI data.</param>
/// <remarks>
/// Used by Text, UI.Image, and RawImage for example to generate vertices specific to their use case.
/// </remarks>
protected virtual void OnPopulateMesh(Mesh m)
{
OnPopulateMesh(s_VertexHelper);
s_VertexHelper.FillMesh(m);
}
/// <summary>
/// Callback function when a UI element needs to generate vertices. Fills the vertex buffer data.
/// </summary>
/// <param name="vh">VertexHelper utility.</param>
/// <remarks>
/// Used by Text, UI.Image, and RawImage for example to generate vertices specific to their use case.
/// </remarks>
protected virtual void OnPopulateMesh(VertexHelper vh)
{
var r = GetPixelAdjustedRect();
var v = new Vector4(r.x, r.y, r.x + r.width, r.y + r.height);
Color32 color32 = color;
vh.Clear();
vh.AddVert(new Vector3(v.x, v.y), color32, new Vector2(0f, 0f));
vh.AddVert(new Vector3(v.x, v.w), color32, new Vector2(0f, 1f));
vh.AddVert(new Vector3(v.z, v.w), color32, new Vector2(1f, 1f));
vh.AddVert(new Vector3(v.z, v.y), color32, new Vector2(1f, 0f));
vh.AddTriangle(0, 1, 2);
vh.AddTriangle(2, 3, 0);
}
#if UNITY_EDITOR
/// <summary>
/// Editor-only callback that is issued by Unity if a rebuild of the Graphic is required.
/// Currently sent when an asset is reimported.
/// </summary>
public virtual void OnRebuildRequested()
{
// when rebuild is requested we need to rebuild all the graphics /
// and associated components... The correct way to do this is by
// calling OnValidate... Because MB's don't have a common base class
// we do this via reflection. It's nasty and ugly... Editor only.
m_SkipLayoutUpdate = true;
var mbs = gameObject.GetComponents<MonoBehaviour>();
foreach (var mb in mbs)
{
if (mb == null)
continue;
var methodInfo = mb.GetType().GetMethod("OnValidate", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (methodInfo != null)
methodInfo.Invoke(mb, null);
}
m_SkipLayoutUpdate = false;
}
protected override void Reset()
{
SetAllDirty();
}
#endif
// Call from unity if animation properties have changed
protected override void OnDidApplyAnimationProperties()
{
SetAllDirty();
}
/// <summary>
/// Make the Graphic have the native size of its content.
/// </summary>
public virtual void SetNativeSize() {}
/// <summary>
/// When a GraphicRaycaster is raycasting into the scene it does two things. First it filters the elements using their RectTransform rect. Then it uses this Raycast function to determine the elements hit by the raycast.
/// </summary>
/// <param name="sp">Screen point being tested</param>
/// <param name="eventCamera">Camera that is being used for the testing.</param>
/// <returns>True if the provided point is a valid location for GraphicRaycaster raycasts.</returns>
public virtual bool Raycast(Vector2 sp, Camera eventCamera)
{
if (!isActiveAndEnabled)
return false;
var t = transform;
var components = ListPool<Component>.Get();
bool ignoreParentGroups = false;
bool continueTraversal = true;
while (t != null)
{
t.GetComponents(components);
for (var i = 0; i < components.Count; i++)
{
var canvas = components[i] as Canvas;
if (canvas != null && canvas.overrideSorting)
continueTraversal = false;
var filter = components[i] as ICanvasRaycastFilter;
if (filter == null)
continue;
var raycastValid = true;
var group = components[i] as CanvasGroup;
if (group != null)
{
if (!group.enabled)
continue;
if (ignoreParentGroups == false && group.ignoreParentGroups)
{
ignoreParentGroups = true;
raycastValid = filter.IsRaycastLocationValid(sp, eventCamera);
}
else if (!ignoreParentGroups)
raycastValid = filter.IsRaycastLocationValid(sp, eventCamera);
}
else
{
raycastValid = filter.IsRaycastLocationValid(sp, eventCamera);
}
if (!raycastValid)
{
ListPool<Component>.Release(components);
return false;
}
}
t = continueTraversal ? t.parent : null;
}
ListPool<Component>.Release(components);
return true;
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
SetAllDirty();
}
#endif
///<summary>
///Adjusts the given pixel to be pixel perfect.
///</summary>
///<param name="point">Local space point.</param>
///<returns>Pixel perfect adjusted point.</returns>
///<remarks>
///Note: This is only accurate if the Graphic root Canvas is in Screen Space.
///</remarks>
public Vector2 PixelAdjustPoint(Vector2 point)
{
if (!canvas || canvas.renderMode == RenderMode.WorldSpace || canvas.scaleFactor == 0.0f || !canvas.pixelPerfect)
return point;
else
{
return RectTransformUtility.PixelAdjustPoint(point, transform, canvas);
}
}
/// <summary>
/// Returns a pixel perfect Rect closest to the Graphic RectTransform.
/// </summary>
/// <remarks>
/// Note: This is only accurate if the Graphic root Canvas is in Screen Space.
/// </remarks>
/// <returns>A Pixel perfect Rect.</returns>
public Rect GetPixelAdjustedRect()
{
if (!canvas || canvas.renderMode == RenderMode.WorldSpace || canvas.scaleFactor == 0.0f || !canvas.pixelPerfect)
return rectTransform.rect;
else
return RectTransformUtility.PixelAdjustRect(rectTransform, canvas);
}
///<summary>
///Tweens the CanvasRenderer color associated with this Graphic.
///</summary>
///<param name="targetColor">Target color.</param>
///<param name="duration">Tween duration.</param>
///<param name="ignoreTimeScale">Should ignore Time.scale?</param>
///<param name="useAlpha">Should also Tween the alpha channel?</param>
public virtual void CrossFadeColor(Color targetColor, float duration, bool ignoreTimeScale, bool useAlpha)
{
CrossFadeColor(targetColor, duration, ignoreTimeScale, useAlpha, true);
}
///<summary>
///Tweens the CanvasRenderer color associated with this Graphic.
///</summary>
///<param name="targetColor">Target color.</param>
///<param name="duration">Tween duration.</param>
///<param name="ignoreTimeScale">Should ignore Time.scale?</param>
///<param name="useAlpha">Should also Tween the alpha channel?</param>
/// <param name="useRGB">Should the color or the alpha be used to tween</param>
public virtual void CrossFadeColor(Color targetColor, float duration, bool ignoreTimeScale, bool useAlpha, bool useRGB)
{
if (canvasRenderer == null || (!useRGB && !useAlpha))
return;
Color currentColor = canvasRenderer.GetColor();
if (currentColor.Equals(targetColor))
{
m_ColorTweenRunner.StopTween();
return;
}
ColorTween.ColorTweenMode mode = (useRGB && useAlpha ?
ColorTween.ColorTweenMode.All :
(useRGB ? ColorTween.ColorTweenMode.RGB : ColorTween.ColorTweenMode.Alpha));
var colorTween = new ColorTween {duration = duration, startColor = canvasRenderer.GetColor(), targetColor = targetColor};
colorTween.AddOnChangedCallback(canvasRenderer.SetColor);
colorTween.ignoreTimeScale = ignoreTimeScale;
colorTween.tweenMode = mode;
m_ColorTweenRunner.StartTween(colorTween);
}
static private Color CreateColorFromAlpha(float alpha)
{
var alphaColor = Color.black;
alphaColor.a = alpha;
return alphaColor;
}
///<summary>
///Tweens the alpha of the CanvasRenderer color associated with this Graphic.
///</summary>
///<param name="alpha">Target alpha.</param>
///<param name="duration">Duration of the tween in seconds.</param>
///<param name="ignoreTimeScale">Should ignore [[Time.scale]]?</param>
public virtual void CrossFadeAlpha(float alpha, float duration, bool ignoreTimeScale)
{
CrossFadeColor(CreateColorFromAlpha(alpha), duration, ignoreTimeScale, true, false);
}
/// <summary>
/// Add a listener to receive notification when the graphics layout is dirtied.
/// </summary>
/// <param name="action">The method to call when invoked.</param>
public void RegisterDirtyLayoutCallback(UnityAction action)
{
m_OnDirtyLayoutCallback += action;
}
/// <summary>
/// Remove a listener from receiving notifications when the graphics layout are dirtied
/// </summary>
/// <param name="action">The method to call when invoked.</param>
public void UnregisterDirtyLayoutCallback(UnityAction action)
{
m_OnDirtyLayoutCallback -= action;
}
/// <summary>
/// Add a listener to receive notification when the graphics vertices are dirtied.
/// </summary>
/// <param name="action">The method to call when invoked.</param>
public void RegisterDirtyVerticesCallback(UnityAction action)
{
m_OnDirtyVertsCallback += action;
}
/// <summary>
/// Remove a listener from receiving notifications when the graphics vertices are dirtied
/// </summary>
/// <param name="action">The method to call when invoked.</param>
public void UnregisterDirtyVerticesCallback(UnityAction action)
{
m_OnDirtyVertsCallback -= action;
}
/// <summary>
/// Add a listener to receive notification when the graphics material is dirtied.
/// </summary>
/// <param name="action">The method to call when invoked.</param>
public void RegisterDirtyMaterialCallback(UnityAction action)
{
m_OnDirtyMaterialCallback += action;
}
/// <summary>
/// Remove a listener from receiving notifications when the graphics material are dirtied
/// </summary>
/// <param name="action">The method to call when invoked.</param>
public void UnregisterDirtyMaterialCallback(UnityAction action)
{
m_OnDirtyMaterialCallback -= action;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a6238e9452bfc704f82ff36791fe1a45
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,354 @@
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
namespace UnityEngine.UI
{
[AddComponentMenu("Event/Graphic Raycaster")]
[RequireComponent(typeof(Canvas))]
/// <summary>
/// A derived BaseRaycaster to raycast against Graphic elements.
/// </summary>
public class GraphicRaycaster : BaseRaycaster
{
protected const int kNoEventMaskSet = -1;
/// <summary>
/// Type of raycasters to check against to check for canvas blocking elements.
/// </summary>
public enum BlockingObjects
{
/// <summary>
/// Perform no raycasts.
/// </summary>
None = 0,
/// <summary>
/// Perform a 2D raycast check to check for blocking 2D elements
/// </summary>
TwoD = 1,
/// <summary>
/// Perform a 3D raycast check to check for blocking 3D elements
/// </summary>
ThreeD = 2,
/// <summary>
/// Perform a 2D and a 3D raycasts to check for blocking 2D and 3D elements.
/// </summary>
All = 3,
}
/// <summary>
/// Priority of the raycaster based upon sort order.
/// </summary>
/// <returns>
/// The sortOrder priority.
/// </returns>
public override int sortOrderPriority
{
get
{
// We need to return the sorting order here as distance will all be 0 for overlay.
if (canvas.renderMode == RenderMode.ScreenSpaceOverlay)
return canvas.sortingOrder;
return base.sortOrderPriority;
}
}
/// <summary>
/// Priority of the raycaster based upon render order.
/// </summary>
/// <returns>
/// The renderOrder priority.
/// </returns>
public override int renderOrderPriority
{
get
{
// We need to return the sorting order here as distance will all be 0 for overlay.
if (canvas.renderMode == RenderMode.ScreenSpaceOverlay)
return canvas.rootCanvas.renderOrder;
return base.renderOrderPriority;
}
}
[FormerlySerializedAs("ignoreReversedGraphics")]
[SerializeField]
private bool m_IgnoreReversedGraphics = true;
[FormerlySerializedAs("blockingObjects")]
[SerializeField]
private BlockingObjects m_BlockingObjects = BlockingObjects.None;
/// <summary>
/// Whether Graphics facing away from the raycaster are checked for raycasts.
/// </summary>
public bool ignoreReversedGraphics { get {return m_IgnoreReversedGraphics; } set { m_IgnoreReversedGraphics = value; } }
/// <summary>
/// The type of objects that are checked to determine if they block graphic raycasts.
/// </summary>
public BlockingObjects blockingObjects { get {return m_BlockingObjects; } set { m_BlockingObjects = value; } }
[SerializeField]
protected LayerMask m_BlockingMask = kNoEventMaskSet;
/// <summary>
/// The type of objects specified through LayerMask that are checked to determine if they block graphic raycasts.
/// </summary>
public LayerMask blockingMask { get { return m_BlockingMask; } set { m_BlockingMask = value; } }
private Canvas m_Canvas;
protected GraphicRaycaster()
{}
private Canvas canvas
{
get
{
if (m_Canvas != null)
return m_Canvas;
m_Canvas = GetComponent<Canvas>();
return m_Canvas;
}
}
[NonSerialized] private List<Graphic> m_RaycastResults = new List<Graphic>();
/// <summary>
/// Perform the raycast against the list of graphics associated with the Canvas.
/// </summary>
/// <param name="eventData">Current event data</param>
/// <param name="resultAppendList">List of hit objects to append new results to.</param>
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
{
if (canvas == null)
return;
var canvasGraphics = GraphicRegistry.GetRaycastableGraphicsForCanvas(canvas);
if (canvasGraphics == null || canvasGraphics.Count == 0)
return;
int displayIndex;
var currentEventCamera = eventCamera; // Property can call Camera.main, so cache the reference
if (canvas.renderMode == RenderMode.ScreenSpaceOverlay || currentEventCamera == null)
displayIndex = canvas.targetDisplay;
else
displayIndex = currentEventCamera.targetDisplay;
// The multiple display system is not supported on all platforms, when it is not supported the returned position
// will be all zeros so when the returned index is 0 we will default to the event data to be safe.
Vector3 eventPosition = MultipleDisplayUtilities.RelativeMouseAtScaled(eventData.position);
if (eventPosition == Vector3.zero)
{
eventPosition = eventData.position;
#if UNITY_EDITOR
eventPosition.z = Display.activeEditorGameViewTarget;
#endif
// We don't really know in which display the event occurred. We will process the event assuming it occurred in our display.
}
// We support multiple display on some platforms. When supported:
// - InputSystem will set eventData.displayIndex
// - Old Input System will set eventPosition.z
#if ENABLE_INPUT_SYSTEM && PACKAGE_INPUTSYSTEM
eventPosition.z = eventData.displayIndex;
#endif
// Discard events that are not part of this display so the user does not interact with multiple displays at once.
if ((int) eventPosition.z != displayIndex)
return;
// Convert to view space
Vector2 pos;
if (currentEventCamera == null)
{
// 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)
float w = Screen.width;
float h = Screen.height;
if (displayIndex > 0 && displayIndex < Display.displays.Length)
{
w = Display.displays[displayIndex].systemWidth;
h = Display.displays[displayIndex].systemHeight;
}
pos = new Vector2(eventPosition.x / w, eventPosition.y / h);
}
else
pos = currentEventCamera.ScreenToViewportPoint(eventPosition);
// If it's outside the camera's viewport, do nothing
if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f)
return;
float hitDistance = float.MaxValue;
Ray ray = new Ray();
if (currentEventCamera != null)
ray = currentEventCamera.ScreenPointToRay(eventPosition);
if (canvas.renderMode != RenderMode.ScreenSpaceOverlay && blockingObjects != BlockingObjects.None)
{
float distanceToClipPlane = 100.0f;
if (currentEventCamera != null)
{
float projectionDirection = ray.direction.z;
distanceToClipPlane = Mathf.Approximately(0.0f, projectionDirection)
? Mathf.Infinity
: Mathf.Abs((currentEventCamera.farClipPlane - currentEventCamera.nearClipPlane) / projectionDirection);
}
#if PACKAGE_PHYSICS
if (blockingObjects == BlockingObjects.ThreeD || blockingObjects == BlockingObjects.All)
{
if (ReflectionMethodsCache.Singleton.raycast3D != null)
{
var hits = ReflectionMethodsCache.Singleton.raycast3DAll(ray, distanceToClipPlane, (int)m_BlockingMask);
if (hits.Length > 0)
hitDistance = hits[0].distance;
}
}
#endif
#if PACKAGE_PHYSICS2D
if (blockingObjects == BlockingObjects.TwoD || blockingObjects == BlockingObjects.All)
{
if (ReflectionMethodsCache.Singleton.raycast2D != null)
{
var hits = ReflectionMethodsCache.Singleton.getRayIntersectionAll(ray, distanceToClipPlane, (int)m_BlockingMask);
if (hits.Length > 0)
hitDistance = hits[0].distance;
}
}
#endif
}
m_RaycastResults.Clear();
Raycast(canvas, currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults);
int totalCount = m_RaycastResults.Count;
for (var index = 0; index < totalCount; index++)
{
var go = m_RaycastResults[index].gameObject;
bool appendGraphic = true;
if (ignoreReversedGraphics)
{
if (currentEventCamera == null)
{
// If we dont have a camera we know that we should always be facing forward
var dir = go.transform.rotation * Vector3.forward;
appendGraphic = Vector3.Dot(Vector3.forward, dir) > 0;
}
else
{
// If we have a camera compare the direction against the cameras forward.
var cameraForward = currentEventCamera.transform.rotation * Vector3.forward * currentEventCamera.nearClipPlane;
appendGraphic = Vector3.Dot(go.transform.position - currentEventCamera.transform.position - cameraForward, go.transform.forward) >= 0;
}
}
if (appendGraphic)
{
float distance = 0;
Transform trans = go.transform;
Vector3 transForward = trans.forward;
if (currentEventCamera == null || canvas.renderMode == RenderMode.ScreenSpaceOverlay)
distance = 0;
else
{
// http://geomalgorithms.com/a06-_intersect-2.html
distance = (Vector3.Dot(transForward, trans.position - ray.origin) / Vector3.Dot(transForward, ray.direction));
// Check to see if the go is behind the camera.
if (distance < 0)
continue;
}
if (distance >= hitDistance)
continue;
var castResult = new RaycastResult
{
gameObject = go,
module = this,
distance = distance,
screenPosition = eventPosition,
displayIndex = displayIndex,
index = resultAppendList.Count,
depth = m_RaycastResults[index].depth,
sortingLayer = canvas.sortingLayerID,
sortingOrder = canvas.sortingOrder,
worldPosition = ray.origin + ray.direction * distance,
worldNormal = -transForward
};
resultAppendList.Add(castResult);
}
}
}
/// <summary>
/// The camera that will generate rays for this raycaster.
/// </summary>
/// <returns>
/// - Null if Camera mode is ScreenSpaceOverlay or ScreenSpaceCamera and has no camera.
/// - canvas.worldCanvas if not null
/// - Camera.main.
/// </returns>
public override Camera eventCamera
{
get
{
var canvas = this.canvas;
var renderMode = canvas.renderMode;
if (renderMode == RenderMode.ScreenSpaceOverlay
|| (renderMode == RenderMode.ScreenSpaceCamera && canvas.worldCamera == null))
return null;
return canvas.worldCamera ?? Camera.main;
}
}
/// <summary>
/// Perform a raycast into the screen and collect all graphics underneath it.
/// </summary>
[NonSerialized] static readonly List<Graphic> s_SortedGraphics = new List<Graphic>();
private static void Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, IList<Graphic> foundGraphics, List<Graphic> results)
{
// Necessary for the event system
int totalCount = foundGraphics.Count;
for (int i = 0; i < totalCount; ++i)
{
Graphic graphic = foundGraphics[i];
// -1 means it hasn't been processed by the canvas, which means it isn't actually drawn
if (!graphic.raycastTarget || graphic.canvasRenderer.cull || graphic.depth == -1)
continue;
if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera, graphic.raycastPadding))
continue;
if (eventCamera != null && eventCamera.WorldToScreenPoint(graphic.rectTransform.position).z > eventCamera.farClipPlane)
continue;
if (graphic.Raycast(pointerPosition, eventCamera))
{
s_SortedGraphics.Add(graphic);
}
}
s_SortedGraphics.Sort((g1, g2) => g2.depth.CompareTo(g1.depth));
totalCount = s_SortedGraphics.Count;
for (int i = 0; i < totalCount; ++i)
results.Add(s_SortedGraphics[i]);
s_SortedGraphics.Clear();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dc42784cf147c0c48a680349fa168899
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,59 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEngine.UI.Collections;
namespace UnityEngine.UI
{
/// <summary>
/// EditorOnly class for tracking all Graphics.
/// Used when a source asset is reimported into the editor to ensure that Graphics are updated as intended.
/// </summary>
public static class GraphicRebuildTracker
{
static IndexedSet<Graphic> m_Tracked = new IndexedSet<Graphic>();
static bool s_Initialized;
/// <summary>
/// Add a Graphic to the list of tracked Graphics
/// </summary>
/// <param name="g">The graphic to track</param>
public static void TrackGraphic(Graphic g)
{
if (!s_Initialized)
{
CanvasRenderer.onRequestRebuild += OnRebuildRequested;
s_Initialized = true;
}
m_Tracked.AddUnique(g);
}
/// <summary>
/// Remove a Graphic to the list of tracked Graphics
/// </summary>
/// <param name="g">The graphic to remove from tracking.</param>
public static void UnTrackGraphic(Graphic g)
{
m_Tracked.Remove(g);
}
/// <summary>
/// Remove a Graphic to the list of tracked Graphics
/// </summary>
/// <param name="g">The graphic to remove from tracking.</param>
public static void DisableTrackGraphic(Graphic g)
{
m_Tracked.DisableItem(g);
}
static void OnRebuildRequested()
{
StencilMaterial.ClearAll();
for (int i = 0; i < m_Tracked.Count; i++)
{
m_Tracked[i].OnRebuildRequested();
}
}
}
}
#endif // if UNITY_EDITOR

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 445cdcfc747eba94288b97f5869aa2fb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,212 @@
using System.Collections.Generic;
using UnityEngine.UI.Collections;
namespace UnityEngine.UI
{
/// <summary>
/// Registry which maps a Graphic to the canvas it belongs to.
/// </summary>
public class GraphicRegistry
{
private static GraphicRegistry s_Instance;
private readonly Dictionary<Canvas, IndexedSet<Graphic>> m_Graphics = new Dictionary<Canvas, IndexedSet<Graphic>>();
private readonly Dictionary<Canvas, IndexedSet<Graphic>> m_RaycastableGraphics = new Dictionary<Canvas, IndexedSet<Graphic>>();
protected GraphicRegistry()
{
// Avoid runtime generation of these types. Some platforms are AOT only and do not support
// JIT. What's more we actually create a instance of the required types instead of
// just declaring an unused variable which may be optimized away by some compilers (Mono vs MS).
// See: 877060
System.GC.KeepAlive(new Dictionary<Graphic, int>());
System.GC.KeepAlive(new Dictionary<ICanvasElement, int>());
System.GC.KeepAlive(new Dictionary<IClipper, int>());
}
/// <summary>
/// The singleton instance of the GraphicRegistry. Creates a new instance if it does not exist.
/// </summary>
public static GraphicRegistry instance
{
get
{
if (s_Instance == null)
s_Instance = new GraphicRegistry();
return s_Instance;
}
}
/// <summary>
/// Associates a Graphic with a Canvas and stores this association in the registry.
/// </summary>
/// <param name="c">The canvas being associated with the Graphic.</param>
/// <param name="graphic">The Graphic being associated with the Canvas.</param>
public static void RegisterGraphicForCanvas(Canvas c, Graphic graphic)
{
if (c == null || graphic == null)
return;
IndexedSet<Graphic> graphics;
instance.m_Graphics.TryGetValue(c, out graphics);
if (graphics != null)
{
graphics.AddUnique(graphic);
RegisterRaycastGraphicForCanvas(c, graphic);
return;
}
// Dont need to AddUnique as we know its the only item in the list
graphics = new IndexedSet<Graphic>();
graphics.Add(graphic);
instance.m_Graphics.Add(c, graphics);
RegisterRaycastGraphicForCanvas(c, graphic);
}
/// <summary>
/// Associates a raycastable Graphic with a Canvas and stores this association in the registry.
/// </summary>
/// <param name="c">The canvas being associated with the Graphic.</param>
/// <param name="graphic">The Graphic being associated with the Canvas.</param>
public static void RegisterRaycastGraphicForCanvas(Canvas c, Graphic graphic)
{
if (c == null || graphic == null || !graphic.raycastTarget)
return;
IndexedSet<Graphic> graphics;
instance.m_RaycastableGraphics.TryGetValue(c, out graphics);
if (graphics != null)
{
graphics.AddUnique(graphic);
return;
}
// Dont need to AddUnique as we know its the only item in the list
graphics = new IndexedSet<Graphic>();
graphics.Add(graphic);
instance.m_RaycastableGraphics.Add(c, graphics);
}
/// <summary>
/// Dissociates a Graphic from a Canvas, removing this association from the registry.
/// </summary>
/// <param name="c">The Canvas to dissociate from the Graphic.</param>
/// <param name="graphic">The Graphic to dissociate from the Canvas.</param>
public static void UnregisterGraphicForCanvas(Canvas c, Graphic graphic)
{
if (c == null || graphic == null)
return;
IndexedSet<Graphic> graphics;
if (instance.m_Graphics.TryGetValue(c, out graphics))
{
graphics.Remove(graphic);
if (graphics.Capacity == 0)
instance.m_Graphics.Remove(c);
UnregisterRaycastGraphicForCanvas(c, graphic);
}
}
/// <summary>
/// Dissociates a Graphic from a Canvas, removing this association from the registry.
/// </summary>
/// <param name="c">The Canvas to dissociate from the Graphic.</param>
/// <param name="graphic">The Graphic to dissociate from the Canvas.</param>
public static void UnregisterRaycastGraphicForCanvas(Canvas c, Graphic graphic)
{
if (c == null || graphic == null)
return;
IndexedSet<Graphic> graphics;
if (instance.m_RaycastableGraphics.TryGetValue(c, out graphics))
{
graphics.Remove(graphic);
if (graphics.Count == 0)
instance.m_RaycastableGraphics.Remove(c);
}
}
/// <summary>
/// Disables a Graphic from a Canvas, disabling this association from the registry.
/// </summary>
/// <param name="c">The Canvas to dissociate from the Graphic.</param>
/// <param name="graphic">The Graphic to dissociate from the Canvas.</param>
public static void DisableGraphicForCanvas(Canvas c, Graphic graphic)
{
if (c == null)
return;
IndexedSet<Graphic> graphics;
if (instance.m_Graphics.TryGetValue(c, out graphics))
{
graphics.DisableItem(graphic);
if (graphics.Capacity == 0)
instance.m_Graphics.Remove(c);
DisableRaycastGraphicForCanvas(c, graphic);
}
}
/// <summary>
/// Disables the raycast for a Graphic from a Canvas, disabling this association from the registry.
/// </summary>
/// <param name="c">The Canvas to dissociate from the Graphic.</param>
/// <param name="graphic">The Graphic to dissociate from the Canvas.</param>
public static void DisableRaycastGraphicForCanvas(Canvas c, Graphic graphic)
{
if (c == null || !graphic.raycastTarget)
return;
IndexedSet<Graphic> graphics;
if (instance.m_RaycastableGraphics.TryGetValue(c, out graphics))
{
graphics.DisableItem(graphic);
if (graphics.Capacity == 0)
instance.m_RaycastableGraphics.Remove(c);
}
}
private static readonly List<Graphic> s_EmptyList = new List<Graphic>();
/// <summary>
/// Retrieves the list of Graphics associated with a Canvas.
/// </summary>
/// <param name="canvas">The Canvas to search</param>
/// <returns>Returns a list of Graphics. Returns an empty list if no Graphics are associated with the specified Canvas.</returns>
public static IList<Graphic> GetGraphicsForCanvas(Canvas canvas)
{
IndexedSet<Graphic> graphics;
if (instance.m_Graphics.TryGetValue(canvas, out graphics))
return graphics;
return s_EmptyList;
}
/// <summary>
/// Retrieves the list of Graphics that are raycastable and associated with a Canvas.
/// </summary>
/// <param name="canvas">The Canvas to search</param>
/// <returns>Returns a list of Graphics. Returns an empty list if no Graphics are associated with the specified Canvas.</returns>
public static IList<Graphic> GetRaycastableGraphicsForCanvas(Canvas canvas)
{
IndexedSet<Graphic> graphics;
if (instance.m_RaycastableGraphics.TryGetValue(canvas, out graphics))
return graphics;
return s_EmptyList;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 69e3979b7029e8a4da2d96b714ba5c3a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,10 @@
using System;
namespace UnityEngine.UI
{
[Obsolete("Not supported anymore")]
interface IGraphicEnabledDisabled
{
void OnSiblingGraphicEnabledDisabled();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 928dc55e2c8c3ee4dad33b6d561cb6ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,12 @@
using System;
namespace UnityEngine.UI
{
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
[Obsolete("Not supported anymore.", true)]
public interface IMask
{
bool Enabled();
RectTransform rectTransform { get; }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ceb629fc661813d40986b4abbefe72c6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
using System;
namespace UnityEngine.UI
{
/// <summary>
/// This element is capable of being masked out.
/// </summary>
public interface IMaskable
{
/// <summary>
/// Recalculate masking for this element and all children elements.
/// </summary>
/// <remarks>
/// Use this to update the internal state (recreate materials etc).
/// </remarks>
void RecalculateMasking();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 61a20120cddc53849bbc10fc805ffe3e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,1937 @@
using System;
using System.Collections.Generic;
using System.Text;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine.Experimental.Rendering;
using UnityEngine.Serialization;
using UnityEngine.U2D;
namespace UnityEngine.UI
{
/// <summary>
/// Image is a textured element in the UI hierarchy.
/// </summary>
[RequireComponent(typeof(CanvasRenderer))]
[AddComponentMenu("UI/Image", 11)]
/// <summary>
/// Displays a Sprite inside the UI System.
/// </summary>
public class Image : MaskableGraphic, ISerializationCallbackReceiver, ILayoutElement, ICanvasRaycastFilter
{
/// <summary>
/// Image fill type controls how to display the image.
/// </summary>
public enum Type
{
/// <summary>
/// Displays the full Image
/// </summary>
/// <remarks>
/// This setting shows the entire image stretched across the Image's RectTransform
/// </remarks>
Simple,
/// <summary>
/// Displays the Image as a 9-sliced graphic.
/// </summary>
/// <remarks>
/// A 9-sliced image displays a central area stretched across the image surrounded by a border comprising of 4 corners and 4 stretched edges.
///
/// This has the effect of creating a resizable skinned rectangular element suitable for dialog boxes, windows, and general UI elements.
///
/// Note: For this method to work properly the Sprite assigned to Image.sprite needs to have Sprite.border defined.
/// </remarks>
Sliced,
/// <summary>
/// Displays a sliced Sprite with its resizable sections tiled instead of stretched.
/// </summary>
/// <remarks>
/// A Tiled image behaves similarly to a UI.Image.Type.Sliced|Sliced image, except that the resizable sections of the image are repeated instead of being stretched. This can be useful for detailed UI graphics that do not look good when stretched.
///
/// It uses the Sprite.border value to determine how each part (border and center) should be tiled.
///
/// The Image sections will repeat the corresponding section in the Sprite until the whole section is filled. The corner sections will be unaffected and will draw in the same way as a Sliced Image. The edges will repeat along their lengths. The center section will repeat across the whole central part of the Image.
///
/// The Image section will repeat the corresponding section in the Sprite until the whole section is filled.
///
/// Be aware that if you are tiling a Sprite with borders or a packed sprite, a mesh will be generated to create the tiles. The size of the mesh will be limited to 16250 quads; if your tiling would require more tiles, the size of the tiles will be enlarged to ensure that the number of generated quads stays below this limit.
///
/// For optimum efficiency, use a Sprite with no borders and with no packing, and make sure the Sprite.texture wrap mode is set to TextureWrapMode.Repeat.These settings will prevent the generation of additional geometry.If this is not possible, limit the number of tiles in your Image.
/// </remarks>
Tiled,
/// <summary>
/// Displays only a portion of the Image.
/// </summary>
/// <remarks>
/// A Filled Image will display a section of the Sprite, with the rest of the RectTransform left transparent. The Image.fillAmount determines how much of the Image to show, and Image.fillMethod controls the shape in which the Image will be cut.
///
/// This can be used for example to display circular or linear status information such as timers, health bars, and loading bars.
/// </remarks>
Filled
}
/// <summary>
/// The possible fill method types for a Filled Image.
/// </summary>
public enum FillMethod
{
/// <summary>
/// The Image will be filled Horizontally.
/// </summary>
/// <remarks>
/// The Image will be Cropped at either left or right size depending on Image.fillOriging at the Image.fillAmount
/// </remarks>
Horizontal,
/// <summary>
/// The Image will be filled Vertically.
/// </summary>
/// <remarks>
/// The Image will be Cropped at either top or Bottom size depending on Image.fillOrigin at the Image.fillAmount
/// </remarks>
Vertical,
/// <summary>
/// The Image will be filled Radially with the radial center in one of the corners.
/// </summary>
/// <remarks>
/// For this method the Image.fillAmount represents an angle between 0 and 90 degrees. The Image will be cut by a line passing at the Image.fillOrigin at the specified angle.
/// </remarks>
Radial90,
/// <summary>
/// The Image will be filled Radially with the radial center in one of the edges.
/// </summary>
/// <remarks>
/// For this method the Image.fillAmount represents an angle between 0 and 180 degrees. The Image will be cut by a line passing at the Image.fillOrigin at the specified angle.
/// </remarks>
Radial180,
/// <summary>
/// The Image will be filled Radially with the radial center at the center.
/// </summary>
/// <remarks>
/// or this method the Image.fillAmount represents an angle between 0 and 360 degrees. The Arc defined by the center of the Image, the Image.fillOrigin and the angle will be cut from the Image.
/// </remarks>
Radial360,
}
/// <summary>
/// Origin for the Image.FillMethod.Horizontal.
/// </summary>
public enum OriginHorizontal
{
/// <summary>
/// >Origin at the Left side.
/// </summary>
Left,
/// <summary>
/// >Origin at the Right side.
/// </summary>
Right,
}
/// <summary>
/// Origin for the Image.FillMethod.Vertical.
/// </summary>
public enum OriginVertical
{
/// <summary>
/// >Origin at the Bottom Edge.
/// </summary>
Bottom,
/// <summary>
/// >Origin at the Top Edge.
/// </summary>
Top,
}
/// <summary>
/// Origin for the Image.FillMethod.Radial90.
/// </summary>
public enum Origin90
{
/// <summary>
/// Radial starting at the Bottom Left corner.
/// </summary>
BottomLeft,
/// <summary>
/// Radial starting at the Top Left corner.
/// </summary>
TopLeft,
/// <summary>
/// Radial starting at the Top Right corner.
/// </summary>
TopRight,
/// <summary>
/// Radial starting at the Bottom Right corner.
/// </summary>
BottomRight,
}
/// <summary>
/// Origin for the Image.FillMethod.Radial180.
/// </summary>
public enum Origin180
{
/// <summary>
/// Center of the radial at the center of the Bottom edge.
/// </summary>
Bottom,
/// <summary>
/// Center of the radial at the center of the Left edge.
/// </summary>
Left,
/// <summary>
/// Center of the radial at the center of the Top edge.
/// </summary>
Top,
/// <summary>
/// Center of the radial at the center of the Right edge.
/// </summary>
Right,
}
/// <summary>
/// One of the points of the Arc for the Image.FillMethod.Radial360.
/// </summary>
public enum Origin360
{
/// <summary>
/// Arc starting at the center of the Bottom edge.
/// </summary>
Bottom,
/// <summary>
/// Arc starting at the center of the Right edge.
/// </summary>
Right,
/// <summary>
/// Arc starting at the center of the Top edge.
/// </summary>
Top,
/// <summary>
/// Arc starting at the center of the Left edge.
/// </summary>
Left,
}
static protected Material s_ETC1DefaultUI = null;
[FormerlySerializedAs("m_Frame")]
[SerializeField]
private Sprite m_Sprite;
/// <summary>
/// The sprite that is used to render this image.
/// </summary>
/// <remarks>
/// This returns the source Sprite of an Image. This Sprite can also be viewed and changed in the Inspector as part of an Image component. This can also be used to change the Sprite using a script.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// //Attach this script to an Image GameObject and set its Source Image to the Sprite you would like.
/// //Press the space key to change the Sprite. Remember to assign a second Sprite in this script's section of the Inspector.
///
/// using UnityEngine;
/// using UnityEngine.UI;
///
/// public class Example : MonoBehaviour
/// {
/// Image m_Image;
/// //Set this in the Inspector
/// public Sprite m_Sprite;
///
/// void Start()
/// {
/// //Fetch the Image from the GameObject
/// m_Image = GetComponent<Image>();
/// }
///
/// void Update()
/// {
/// //Press space to change the Sprite of the Image
/// if (Input.GetKey(KeyCode.Space))
/// {
/// m_Image.sprite = m_Sprite;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public Sprite sprite
{
get { return m_Sprite; }
set
{
if (m_Sprite != null)
{
if (m_Sprite != value)
{
m_SkipLayoutUpdate = m_Sprite.rect.size.Equals(value ? value.rect.size : Vector2.zero);
m_SkipMaterialUpdate = m_Sprite.texture == (value ? value.texture : null);
m_Sprite = value;
ResetAlphaHitThresholdIfNeeded();
SetAllDirty();
TrackSprite();
}
}
else if (value != null)
{
m_SkipLayoutUpdate = value.rect.size == Vector2.zero;
m_SkipMaterialUpdate = value.texture == null;
m_Sprite = value;
ResetAlphaHitThresholdIfNeeded();
SetAllDirty();
TrackSprite();
}
void ResetAlphaHitThresholdIfNeeded()
{
if (!SpriteSupportsAlphaHitTest() && m_AlphaHitTestMinimumThreshold > 0)
{
Debug.LogWarning("Sprite was changed for one not readable or with Crunch Compression. Resetting the AlphaHitThreshold to 0.", this);
m_AlphaHitTestMinimumThreshold = 0;
}
}
bool SpriteSupportsAlphaHitTest()
{
return m_Sprite != null && m_Sprite.texture != null && !GraphicsFormatUtility.IsCrunchFormat(m_Sprite.texture.format) && m_Sprite.texture.isReadable;
}
}
}
/// <summary>
/// Disable all automatic sprite optimizations.
/// </summary>
/// <remarks>
/// When a new Sprite is assigned update optimizations are automatically applied.
/// </remarks>
public void DisableSpriteOptimizations()
{
m_SkipLayoutUpdate = false;
m_SkipMaterialUpdate = false;
}
[NonSerialized]
private Sprite m_OverrideSprite;
/// <summary>
/// Set an override sprite to be used for rendering.
/// </summary>
/// <remarks>
/// The UI.Image-overrideSprite|overrideSprite variable allows a sprite to have the
/// sprite changed.This change happens immediately.When the changed
/// sprite is no longer needed the sprite can be reverted back to the
/// original version.This happens when the overrideSprite
/// is set to /null/.
/// </remarks>
/// <example>
/// Note: The script example below has two buttons. The button textures are loaded from the
/// /Resources/ folder. (They are not used in the shown example). Two sprites are added to
/// the example code. /Example1/ and /Example2/ are functions called by the button OnClick
/// functions. Example1 calls overrideSprite and Example2 sets overrideSprite to null.
/// <code>
/// <![CDATA[
/// using System.Collections;
/// using System.Collections.Generic;
/// using UnityEngine;
/// using UnityEngine.UI;
///
/// public class ExampleClass : MonoBehaviour
/// {
/// private Sprite sprite1;
/// private Sprite sprite2;
/// private Image i;
///
/// public void Start()
/// {
/// i = GetComponent<Image>();
/// sprite1 = Resources.Load<Sprite>("texture1");
/// sprite2 = Resources.Load<Sprite>("texture2");
///
/// i.sprite = sprite1;
/// }
///
/// // Called by a Button OnClick() with ExampleClass.Example1
/// // Uses overrideSprite to make this change temporary
/// public void Example1()
/// {
/// i.overrideSprite = sprite2;
/// }
///
/// // Called by a Button OnClick() with ExampleClass.Example2
/// // Removes the overrideSprite which causes the original sprite to be used again.
/// public void Example2()
/// {
/// i.overrideSprite = null;
/// }
/// }
/// ]]>
///</code>
/// </example>
public Sprite overrideSprite
{
get { return activeSprite; }
set
{
if (SetPropertyUtility.SetClass(ref m_OverrideSprite, value))
{
SetAllDirty();
TrackSprite();
}
}
}
private Sprite activeSprite { get { return m_OverrideSprite != null ? m_OverrideSprite : sprite; } }
/// How the Image is drawn.
[SerializeField] private Type m_Type = Type.Simple;
/// <summary>
/// How to display the image.
/// </summary>
/// <remarks>
/// Unity can interpret an Image in various different ways depending on the intended purpose. This can be used to display:
/// - Whole images stretched to fit the RectTransform of the Image.
/// - A 9-sliced image useful for various decorated UI boxes and other rectangular elements.
/// - A tiled image with sections of the sprite repeated.
/// - As a partial image, useful for wipes, fades, timers, status bars etc.
/// </remarks>
public Type type { get { return m_Type; } set { if (SetPropertyUtility.SetStruct(ref m_Type, value)) SetVerticesDirty(); } }
[SerializeField] private bool m_PreserveAspect = false;
/// <summary>
/// Whether this image should preserve its Sprite aspect ratio.
/// </summary>
public bool preserveAspect { get { return m_PreserveAspect; } set { if (SetPropertyUtility.SetStruct(ref m_PreserveAspect, value)) SetVerticesDirty(); } }
[SerializeField] private bool m_FillCenter = true;
/// <summary>
/// Whether or not to render the center of a Tiled or Sliced image.
/// </summary>
/// <remarks>
/// This will only have any effect if the Image.sprite has borders.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI;
///
/// public class FillCenterScript : MonoBehaviour
/// {
/// public Image xmasCalenderDoor;
///
/// // removes the center of the image to reveal the image behind it
/// void OpenCalendarDoor()
/// {
/// xmasCalenderDoor.fillCenter = false;
/// }
/// }
/// ]]>
///</code>
/// </example>
public bool fillCenter { get { return m_FillCenter; } set { if (SetPropertyUtility.SetStruct(ref m_FillCenter, value)) SetVerticesDirty(); } }
/// Filling method for filled sprites.
[SerializeField] private FillMethod m_FillMethod = FillMethod.Radial360;
public FillMethod fillMethod { get { return m_FillMethod; } set { if (SetPropertyUtility.SetStruct(ref m_FillMethod, value)) { SetVerticesDirty(); m_FillOrigin = 0; } } }
/// Amount of the Image shown. 0-1 range with 0 being nothing shown, and 1 being the full Image.
[Range(0, 1)]
[SerializeField]
private float m_FillAmount = 1.0f;
/// <summary>
/// Amount of the Image shown when the Image.type is set to Image.Type.Filled.
/// </summary>
/// <remarks>
/// 0-1 range with 0 being nothing shown, and 1 being the full Image.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Cooldown : MonoBehaviour
/// {
/// public Image cooldown;
/// public bool coolingDown;
/// public float waitTime = 30.0f;
///
/// // Update is called once per frame
/// void Update()
/// {
/// if (coolingDown == true)
/// {
/// //Reduce fill amount over 30 seconds
/// cooldown.fillAmount -= 1.0f / waitTime * Time.deltaTime;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public float fillAmount { get { return m_FillAmount; } set { if (SetPropertyUtility.SetStruct(ref m_FillAmount, Mathf.Clamp01(value))) SetVerticesDirty(); } }
/// Whether the Image should be filled clockwise (true) or counter-clockwise (false).
[SerializeField] private bool m_FillClockwise = true;
/// <summary>
/// Whether the Image should be filled clockwise (true) or counter-clockwise (false).
/// </summary>
/// <remarks>
/// This will only have any effect if the Image.type is set to Image.Type.Filled and Image.fillMethod is set to any of the Radial methods.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class FillClockwiseScript : MonoBehaviour
/// {
/// public Image healthCircle;
///
/// // This method sets the direction of the health circle.
/// // Clockwise for the Player, Counter Clockwise for the opponent.
/// void SetHealthDirection(GameObject target)
/// {
/// if (target.tag == "Player")
/// {
/// healthCircle.fillClockwise = true;
/// }
/// else if (target.tag == "Opponent")
/// {
/// healthCircle.fillClockwise = false;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public bool fillClockwise { get { return m_FillClockwise; } set { if (SetPropertyUtility.SetStruct(ref m_FillClockwise, value)) SetVerticesDirty(); } }
/// Controls the origin point of the Fill process. Value means different things with each fill method.
[SerializeField] private int m_FillOrigin;
/// <summary>
/// Controls the origin point of the Fill process. Value means different things with each fill method.
/// </summary>
/// <remarks>
/// You should cast to the appropriate origin type: Image.OriginHorizontal, Image.OriginVertical, Image.Origin90, Image.Origin180 or Image.Origin360 depending on the Image.Fillmethod.
/// Note: This will only have any effect if the Image.type is set to Image.Type.Filled.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using UnityEngine.UI;
/// using System.Collections;
///
/// [RequireComponent(typeof(Image))]
/// public class ImageOriginCycle : MonoBehaviour
/// {
/// void OnEnable()
/// {
/// Image image = GetComponent<Image>();
/// string fillOriginName = "";
///
/// switch ((Image.FillMethod)image.fillMethod)
/// {
/// case Image.FillMethod.Horizontal:
/// fillOriginName = ((Image.OriginHorizontal)image.fillOrigin).ToString();
/// break;
/// case Image.FillMethod.Vertical:
/// fillOriginName = ((Image.OriginVertical)image.fillOrigin).ToString();
/// break;
/// case Image.FillMethod.Radial90:
///
/// fillOriginName = ((Image.Origin90)image.fillOrigin).ToString();
/// break;
/// case Image.FillMethod.Radial180:
///
/// fillOriginName = ((Image.Origin180)image.fillOrigin).ToString();
/// break;
/// case Image.FillMethod.Radial360:
/// fillOriginName = ((Image.Origin360)image.fillOrigin).ToString();
/// break;
/// }
/// Debug.Log(string.Format("{0} is using {1} fill method with the origin on {2}", name, image.fillMethod, fillOriginName));
/// }
/// }
/// ]]>
///</code>
/// </example>
public int fillOrigin { get { return m_FillOrigin; } set { if (SetPropertyUtility.SetStruct(ref m_FillOrigin, value)) SetVerticesDirty(); } }
// Not serialized until we support read-enabled sprites better.
private float m_AlphaHitTestMinimumThreshold = 0;
// Whether this is being tracked for Atlas Binding.
private bool m_Tracked = false;
[Obsolete("eventAlphaThreshold has been deprecated. Use eventMinimumAlphaThreshold instead (UnityUpgradable) -> alphaHitTestMinimumThreshold")]
/// <summary>
/// Obsolete. You should use UI.Image.alphaHitTestMinimumThreshold instead.
/// The alpha threshold specifies the minimum alpha a pixel must have for the event to considered a "hit" on the Image.
/// </summary>
public float eventAlphaThreshold { get { return 1 - alphaHitTestMinimumThreshold; } set { alphaHitTestMinimumThreshold = 1 - value; } }
/// <summary>
/// The alpha threshold specifies the minimum alpha a pixel must have for the event to considered a "hit" on the Image.
/// </summary>
/// <remarks>
/// Alpha values less than the threshold will cause raycast events to pass through the Image. An value of 1 would cause only fully opaque pixels to register raycast events on the Image. The alpha tested is retrieved from the image sprite only, while the alpha of the Image [[UI.Graphic.color]] is disregarded.
///
/// alphaHitTestMinimumThreshold defaults to 0; all raycast events inside the Image rectangle are considered a hit. In order for greater than 0 to values to work, the sprite used by the Image must have readable pixels. This can be achieved by enabling Read/Write enabled in the advanced Texture Import Settings for the sprite and disabling atlassing for the sprite.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Image theButton;
///
/// // Use this for initialization
/// void Start()
/// {
/// theButton.alphaHitTestMinimumThreshold = 0.5f;
/// }
/// }
/// ]]>
///</code>
/// </example>
public float alphaHitTestMinimumThreshold { get { return m_AlphaHitTestMinimumThreshold; }
set
{
if (sprite != null && (GraphicsFormatUtility.IsCrunchFormat(sprite.texture.format) || !sprite.texture.isReadable))
throw new InvalidOperationException("alphaHitTestMinimumThreshold should not be modified on a texture not readeable or not using Crunch Compression.");
m_AlphaHitTestMinimumThreshold = value;
}
}
/// Controls whether or not to use the generated mesh from the sprite importer.
[SerializeField] private bool m_UseSpriteMesh;
/// <summary>
/// Allows you to specify whether the UI Image should be displayed using the mesh generated by the TextureImporter, or by a simple quad mesh.
/// </summary>
/// <remarks>
/// When this property is set to false, the UI Image uses a simple quad. When set to true, the UI Image uses the sprite mesh generated by the [[TextureImporter]]. You should set this to true if you want to use a tightly fitted sprite mesh based on the alpha values in your image.
/// Note: If the texture importer's SpriteMeshType property is set to SpriteMeshType.FullRect, it will only generate a quad, and not a tightly fitted sprite mesh, which means this UI image will be drawn using a quad regardless of the value of this property. Therefore, when enabling this property to use a tightly fitted sprite mesh, you must also ensure the texture importer's SpriteMeshType property is set to Tight.
/// </remarks>
public bool useSpriteMesh { get { return m_UseSpriteMesh; } set { if (SetPropertyUtility.SetStruct(ref m_UseSpriteMesh, value)) SetVerticesDirty(); } }
protected Image()
{
useLegacyMeshGeneration = false;
}
/// <summary>
/// Cache of the default Canvas Ericsson Texture Compression 1 (ETC1) and alpha Material.
/// </summary>
/// <remarks>
/// Stores the ETC1 supported Canvas Material that is returned from GetETC1SupportedCanvasMaterial().
/// Note: Always specify the UI/DefaultETC1 Shader in the Always Included Shader list, to use the ETC1 and alpha Material.
/// </remarks>
static public Material defaultETC1GraphicMaterial
{
get
{
if (s_ETC1DefaultUI == null)
s_ETC1DefaultUI = Canvas.GetETC1SupportedCanvasMaterial();
return s_ETC1DefaultUI;
}
}
/// <summary>
/// Image's texture comes from the UnityEngine.Image.
/// </summary>
public override Texture mainTexture
{
get
{
if (activeSprite == null)
{
if (material != null && material.mainTexture != null)
{
return material.mainTexture;
}
return s_WhiteTexture;
}
return activeSprite.texture;
}
}
/// <summary>
/// Whether the Sprite of the image has a border to work with.
/// </summary>
public bool hasBorder
{
get
{
if (activeSprite != null)
{
Vector4 v = activeSprite.border;
return v.sqrMagnitude > 0f;
}
return false;
}
}
[SerializeField]
private float m_PixelsPerUnitMultiplier = 1.0f;
/// <summary>
/// Pixel per unit modifier to change how sliced sprites are generated.
/// </summary>
public float pixelsPerUnitMultiplier
{
get { return m_PixelsPerUnitMultiplier; }
set
{
m_PixelsPerUnitMultiplier = Mathf.Max(0.01f, value);
SetVerticesDirty();
}
}
// case 1066689 cache referencePixelsPerUnit when canvas parent is disabled;
private float m_CachedReferencePixelsPerUnit = 100;
public float pixelsPerUnit
{
get
{
float spritePixelsPerUnit = 100;
if (activeSprite)
spritePixelsPerUnit = activeSprite.pixelsPerUnit;
if (canvas)
m_CachedReferencePixelsPerUnit = canvas.referencePixelsPerUnit;
return spritePixelsPerUnit / m_CachedReferencePixelsPerUnit;
}
}
protected float multipliedPixelsPerUnit
{
get { return pixelsPerUnit * m_PixelsPerUnitMultiplier; }
}
/// <summary>
/// The specified Material used by this Image. The default Material is used instead if one wasn't specified.
/// </summary>
public override Material material
{
get
{
if (m_Material != null)
return m_Material;
//Edit and Runtime should use Split Alpha Shader if EditorSettings.spritePackerMode = Sprite Atlas V2
#if UNITY_EDITOR
if ((Application.isPlaying || EditorSettings.spritePackerMode == SpritePackerMode.SpriteAtlasV2) &&
activeSprite && activeSprite.associatedAlphaSplitTexture != null)
{
return defaultETC1GraphicMaterial;
}
#else
if (activeSprite && activeSprite.associatedAlphaSplitTexture != null)
return defaultETC1GraphicMaterial;
#endif
return defaultMaterial;
}
set
{
base.material = value;
}
}
/// <summary>
/// See ISerializationCallbackReceiver.
/// </summary>
public virtual void OnBeforeSerialize() {}
/// <summary>
/// See ISerializationCallbackReceiver.
/// </summary>
public virtual void OnAfterDeserialize()
{
if (m_FillOrigin < 0)
m_FillOrigin = 0;
else if (m_FillMethod == FillMethod.Horizontal && m_FillOrigin > 1)
m_FillOrigin = 0;
else if (m_FillMethod == FillMethod.Vertical && m_FillOrigin > 1)
m_FillOrigin = 0;
else if (m_FillOrigin > 3)
m_FillOrigin = 0;
m_FillAmount = Mathf.Clamp(m_FillAmount, 0f, 1f);
}
private void PreserveSpriteAspectRatio(ref Rect rect, Vector2 spriteSize)
{
var spriteRatio = spriteSize.x / spriteSize.y;
var rectRatio = rect.width / rect.height;
if (spriteRatio > rectRatio)
{
var oldHeight = rect.height;
rect.height = rect.width * (1.0f / spriteRatio);
rect.y += (oldHeight - rect.height) * rectTransform.pivot.y;
}
else
{
var oldWidth = rect.width;
rect.width = rect.height * spriteRatio;
rect.x += (oldWidth - rect.width) * rectTransform.pivot.x;
}
}
/// Image's dimensions used for drawing. X = left, Y = bottom, Z = right, W = top.
private Vector4 GetDrawingDimensions(bool shouldPreserveAspect)
{
var padding = activeSprite == null ? Vector4.zero : Sprites.DataUtility.GetPadding(activeSprite);
var size = activeSprite == null ? Vector2.zero : new Vector2(activeSprite.rect.width, activeSprite.rect.height);
Rect r = GetPixelAdjustedRect();
// Debug.Log(string.Format("r:{2}, size:{0}, padding:{1}", size, padding, r));
int spriteW = Mathf.RoundToInt(size.x);
int spriteH = Mathf.RoundToInt(size.y);
var v = new Vector4(
padding.x / spriteW,
padding.y / spriteH,
(spriteW - padding.z) / spriteW,
(spriteH - padding.w) / spriteH);
if (shouldPreserveAspect && size.sqrMagnitude > 0.0f)
{
PreserveSpriteAspectRatio(ref r, size);
}
v = new Vector4(
r.x + r.width * v.x,
r.y + r.height * v.y,
r.x + r.width * v.z,
r.y + r.height * v.w
);
return v;
}
/// <summary>
/// Adjusts the image size to make it pixel-perfect.
/// </summary>
/// <remarks>
/// This means setting the Images RectTransform.sizeDelta to be equal to the Sprite dimensions.
/// </remarks>
public override void SetNativeSize()
{
if (activeSprite != null)
{
float w = activeSprite.rect.width / pixelsPerUnit;
float h = activeSprite.rect.height / pixelsPerUnit;
rectTransform.anchorMax = rectTransform.anchorMin;
rectTransform.sizeDelta = new Vector2(w, h);
SetAllDirty();
}
}
/// <summary>
/// Update the UI renderer mesh.
/// </summary>
protected override void OnPopulateMesh(VertexHelper toFill)
{
if (activeSprite == null)
{
base.OnPopulateMesh(toFill);
return;
}
switch (type)
{
case Type.Simple:
if (!useSpriteMesh)
GenerateSimpleSprite(toFill, m_PreserveAspect);
else
GenerateSprite(toFill, m_PreserveAspect);
break;
case Type.Sliced:
GenerateSlicedSprite(toFill);
break;
case Type.Tiled:
GenerateTiledSprite(toFill);
break;
case Type.Filled:
GenerateFilledSprite(toFill, m_PreserveAspect);
break;
}
}
private void TrackSprite()
{
if (activeSprite != null && activeSprite.texture == null)
{
TrackImage(this);
m_Tracked = true;
}
}
protected override void OnEnable()
{
base.OnEnable();
TrackSprite();
}
protected override void OnDisable()
{
base.OnDisable();
if (m_Tracked)
UnTrackImage(this);
}
/// <summary>
/// Update the renderer's material.
/// </summary>
protected override void UpdateMaterial()
{
base.UpdateMaterial();
// check if this sprite has an associated alpha texture (generated when splitting RGBA = RGB + A as two textures without alpha)
if (activeSprite == null)
{
canvasRenderer.SetAlphaTexture(null);
return;
}
Texture2D alphaTex = activeSprite.associatedAlphaSplitTexture;
if (alphaTex != null)
{
canvasRenderer.SetAlphaTexture(alphaTex);
}
}
protected override void OnCanvasHierarchyChanged()
{
base.OnCanvasHierarchyChanged();
if (canvas == null)
{
m_CachedReferencePixelsPerUnit = 100;
}
else if (canvas.referencePixelsPerUnit != m_CachedReferencePixelsPerUnit)
{
m_CachedReferencePixelsPerUnit = canvas.referencePixelsPerUnit;
if (type == Type.Sliced || type == Type.Tiled)
{
SetVerticesDirty();
SetLayoutDirty();
}
}
}
/// <summary>
/// Generate vertices for a simple Image.
/// </summary>
void GenerateSimpleSprite(VertexHelper vh, bool lPreserveAspect)
{
Vector4 v = GetDrawingDimensions(lPreserveAspect);
var uv = (activeSprite != null) ? Sprites.DataUtility.GetOuterUV(activeSprite) : Vector4.zero;
var color32 = color;
vh.Clear();
vh.AddVert(new Vector3(v.x, v.y), color32, new Vector2(uv.x, uv.y));
vh.AddVert(new Vector3(v.x, v.w), color32, new Vector2(uv.x, uv.w));
vh.AddVert(new Vector3(v.z, v.w), color32, new Vector2(uv.z, uv.w));
vh.AddVert(new Vector3(v.z, v.y), color32, new Vector2(uv.z, uv.y));
vh.AddTriangle(0, 1, 2);
vh.AddTriangle(2, 3, 0);
}
private void GenerateSprite(VertexHelper vh, bool lPreserveAspect)
{
var spriteSize = new Vector2(activeSprite.rect.width, activeSprite.rect.height);
// Covert sprite pivot into normalized space.
var spritePivot = activeSprite.pivot / spriteSize;
var rectPivot = rectTransform.pivot;
Rect r = GetPixelAdjustedRect();
if (lPreserveAspect & spriteSize.sqrMagnitude > 0.0f)
{
PreserveSpriteAspectRatio(ref r, spriteSize);
}
var drawingSize = new Vector2(r.width, r.height);
var spriteBoundSize = activeSprite.bounds.size;
// Calculate the drawing offset based on the difference between the two pivots.
var drawOffset = (rectPivot - spritePivot) * drawingSize;
var color32 = color;
vh.Clear();
Vector2[] vertices = activeSprite.vertices;
Vector2[] uvs = activeSprite.uv;
for (int i = 0; i < vertices.Length; ++i)
{
vh.AddVert(new Vector3((vertices[i].x / spriteBoundSize.x) * drawingSize.x - drawOffset.x, (vertices[i].y / spriteBoundSize.y) * drawingSize.y - drawOffset.y), color32, new Vector2(uvs[i].x, uvs[i].y));
}
UInt16[] triangles = activeSprite.triangles;
for (int i = 0; i < triangles.Length; i += 3)
{
vh.AddTriangle(triangles[i + 0], triangles[i + 1], triangles[i + 2]);
}
}
static readonly Vector2[] s_VertScratch = new Vector2[4];
static readonly Vector2[] s_UVScratch = new Vector2[4];
/// <summary>
/// Generate vertices for a 9-sliced Image.
/// </summary>
private void GenerateSlicedSprite(VertexHelper toFill)
{
if (!hasBorder)
{
GenerateSimpleSprite(toFill, false);
return;
}
Vector4 outer, inner, padding, border;
if (activeSprite != null)
{
outer = Sprites.DataUtility.GetOuterUV(activeSprite);
inner = Sprites.DataUtility.GetInnerUV(activeSprite);
padding = Sprites.DataUtility.GetPadding(activeSprite);
border = activeSprite.border;
}
else
{
outer = Vector4.zero;
inner = Vector4.zero;
padding = Vector4.zero;
border = Vector4.zero;
}
Rect rect = GetPixelAdjustedRect();
Vector4 adjustedBorders = GetAdjustedBorders(border / multipliedPixelsPerUnit, rect);
padding = padding / multipliedPixelsPerUnit;
s_VertScratch[0] = new Vector2(padding.x, padding.y);
s_VertScratch[3] = new Vector2(rect.width - padding.z, rect.height - padding.w);
s_VertScratch[1].x = adjustedBorders.x;
s_VertScratch[1].y = adjustedBorders.y;
s_VertScratch[2].x = rect.width - adjustedBorders.z;
s_VertScratch[2].y = rect.height - adjustedBorders.w;
for (int i = 0; i < 4; ++i)
{
s_VertScratch[i].x += rect.x;
s_VertScratch[i].y += rect.y;
}
s_UVScratch[0] = new Vector2(outer.x, outer.y);
s_UVScratch[1] = new Vector2(inner.x, inner.y);
s_UVScratch[2] = new Vector2(inner.z, inner.w);
s_UVScratch[3] = new Vector2(outer.z, outer.w);
toFill.Clear();
for (int x = 0; x < 3; ++x)
{
int x2 = x + 1;
for (int y = 0; y < 3; ++y)
{
if (!m_FillCenter && x == 1 && y == 1)
continue;
int y2 = y + 1;
AddQuad(toFill,
new Vector2(s_VertScratch[x].x, s_VertScratch[y].y),
new Vector2(s_VertScratch[x2].x, s_VertScratch[y2].y),
color,
new Vector2(s_UVScratch[x].x, s_UVScratch[y].y),
new Vector2(s_UVScratch[x2].x, s_UVScratch[y2].y));
}
}
}
/// <summary>
/// Generate vertices for a tiled Image.
/// </summary>
void GenerateTiledSprite(VertexHelper toFill)
{
Vector4 outer, inner, border;
Vector2 spriteSize;
if (activeSprite != null)
{
outer = Sprites.DataUtility.GetOuterUV(activeSprite);
inner = Sprites.DataUtility.GetInnerUV(activeSprite);
border = activeSprite.border;
spriteSize = activeSprite.rect.size;
}
else
{
outer = Vector4.zero;
inner = Vector4.zero;
border = Vector4.zero;
spriteSize = Vector2.one * 100;
}
Rect rect = GetPixelAdjustedRect();
float tileWidth = (spriteSize.x - border.x - border.z) / multipliedPixelsPerUnit;
float tileHeight = (spriteSize.y - border.y - border.w) / multipliedPixelsPerUnit;
border = GetAdjustedBorders(border / multipliedPixelsPerUnit, rect);
var uvMin = new Vector2(inner.x, inner.y);
var uvMax = new Vector2(inner.z, inner.w);
// Min to max max range for tiled region in coordinates relative to lower left corner.
float xMin = border.x;
float xMax = rect.width - border.z;
float yMin = border.y;
float yMax = rect.height - border.w;
toFill.Clear();
var clipped = uvMax;
// if either width is zero we cant tile so just assume it was the full width.
if (tileWidth <= 0)
tileWidth = xMax - xMin;
if (tileHeight <= 0)
tileHeight = yMax - yMin;
if (activeSprite != null && (hasBorder || activeSprite.packed || activeSprite.texture != null && activeSprite.texture.wrapMode != TextureWrapMode.Repeat))
{
// Sprite has border, or is not in repeat mode, or cannot be repeated because of packing.
// We cannot use texture tiling so we will generate a mesh of quads to tile the texture.
// Evaluate how many vertices we will generate. Limit this number to something sane,
// especially since meshes can not have more than 65000 vertices.
long nTilesW = 0;
long nTilesH = 0;
if (m_FillCenter)
{
nTilesW = (long)Math.Ceiling((xMax - xMin) / tileWidth);
nTilesH = (long)Math.Ceiling((yMax - yMin) / tileHeight);
double nVertices = 0;
if (hasBorder)
{
nVertices = (nTilesW + 2.0) * (nTilesH + 2.0) * 4.0; // 4 vertices per tile
}
else
{
nVertices = nTilesW * nTilesH * 4.0; // 4 vertices per tile
}
if (nVertices > 65000.0)
{
Debug.LogError("Too many sprite tiles on Image \"" + name + "\". The tile size will be increased. To remove the limit on the number of tiles, set the Wrap mode to Repeat in the Image Import Settings", this);
double maxTiles = 65000.0 / 4.0; // Max number of vertices is 65000; 4 vertices per tile.
double imageRatio;
if (hasBorder)
{
imageRatio = (nTilesW + 2.0) / (nTilesH + 2.0);
}
else
{
imageRatio = (double)nTilesW / nTilesH;
}
double targetTilesW = Math.Sqrt(maxTiles / imageRatio);
double targetTilesH = targetTilesW * imageRatio;
if (hasBorder)
{
targetTilesW -= 2;
targetTilesH -= 2;
}
nTilesW = (long)Math.Floor(targetTilesW);
nTilesH = (long)Math.Floor(targetTilesH);
tileWidth = (xMax - xMin) / nTilesW;
tileHeight = (yMax - yMin) / nTilesH;
}
}
else
{
if (hasBorder)
{
// Texture on the border is repeated only in one direction.
nTilesW = (long)Math.Ceiling((xMax - xMin) / tileWidth);
nTilesH = (long)Math.Ceiling((yMax - yMin) / tileHeight);
double nVertices = (nTilesH + nTilesW + 2.0 /*corners*/) * 2.0 /*sides*/ * 4.0 /*vertices per tile*/;
if (nVertices > 65000.0)
{
Debug.LogError("Too many sprite tiles on Image \"" + name + "\". The tile size will be increased. To remove the limit on the number of tiles, set the Wrap mode to Repeat in the Image Import Settings", this);
double maxTiles = 65000.0 / 4.0; // Max number of vertices is 65000; 4 vertices per tile.
double imageRatio = (double)nTilesW / nTilesH;
double targetTilesW = (maxTiles - 4 /*corners*/) / (2 * (1.0 + imageRatio));
double targetTilesH = targetTilesW * imageRatio;
nTilesW = (long)Math.Floor(targetTilesW);
nTilesH = (long)Math.Floor(targetTilesH);
tileWidth = (xMax - xMin) / nTilesW;
tileHeight = (yMax - yMin) / nTilesH;
}
}
else
{
nTilesH = nTilesW = 0;
}
}
if (m_FillCenter)
{
// TODO: we could share vertices between quads. If vertex sharing is implemented. update the computation for the number of vertices accordingly.
for (long j = 0; j < nTilesH; j++)
{
float y1 = yMin + j * tileHeight;
float y2 = yMin + (j + 1) * tileHeight;
if (y2 > yMax)
{
clipped.y = uvMin.y + (uvMax.y - uvMin.y) * (yMax - y1) / (y2 - y1);
y2 = yMax;
}
clipped.x = uvMax.x;
for (long i = 0; i < nTilesW; i++)
{
float x1 = xMin + i * tileWidth;
float x2 = xMin + (i + 1) * tileWidth;
if (x2 > xMax)
{
clipped.x = uvMin.x + (uvMax.x - uvMin.x) * (xMax - x1) / (x2 - x1);
x2 = xMax;
}
AddQuad(toFill, new Vector2(x1, y1) + rect.position, new Vector2(x2, y2) + rect.position, color, uvMin, clipped);
}
}
}
if (hasBorder)
{
clipped = uvMax;
for (long j = 0; j < nTilesH; j++)
{
float y1 = yMin + j * tileHeight;
float y2 = yMin + (j + 1) * tileHeight;
if (y2 > yMax)
{
clipped.y = uvMin.y + (uvMax.y - uvMin.y) * (yMax - y1) / (y2 - y1);
y2 = yMax;
}
AddQuad(toFill,
new Vector2(0, y1) + rect.position,
new Vector2(xMin, y2) + rect.position,
color,
new Vector2(outer.x, uvMin.y),
new Vector2(uvMin.x, clipped.y));
AddQuad(toFill,
new Vector2(xMax, y1) + rect.position,
new Vector2(rect.width, y2) + rect.position,
color,
new Vector2(uvMax.x, uvMin.y),
new Vector2(outer.z, clipped.y));
}
// Bottom and top tiled border
clipped = uvMax;
for (long i = 0; i < nTilesW; i++)
{
float x1 = xMin + i * tileWidth;
float x2 = xMin + (i + 1) * tileWidth;
if (x2 > xMax)
{
clipped.x = uvMin.x + (uvMax.x - uvMin.x) * (xMax - x1) / (x2 - x1);
x2 = xMax;
}
AddQuad(toFill,
new Vector2(x1, 0) + rect.position,
new Vector2(x2, yMin) + rect.position,
color,
new Vector2(uvMin.x, outer.y),
new Vector2(clipped.x, uvMin.y));
AddQuad(toFill,
new Vector2(x1, yMax) + rect.position,
new Vector2(x2, rect.height) + rect.position,
color,
new Vector2(uvMin.x, uvMax.y),
new Vector2(clipped.x, outer.w));
}
// Corners
AddQuad(toFill,
new Vector2(0, 0) + rect.position,
new Vector2(xMin, yMin) + rect.position,
color,
new Vector2(outer.x, outer.y),
new Vector2(uvMin.x, uvMin.y));
AddQuad(toFill,
new Vector2(xMax, 0) + rect.position,
new Vector2(rect.width, yMin) + rect.position,
color,
new Vector2(uvMax.x, outer.y),
new Vector2(outer.z, uvMin.y));
AddQuad(toFill,
new Vector2(0, yMax) + rect.position,
new Vector2(xMin, rect.height) + rect.position,
color,
new Vector2(outer.x, uvMax.y),
new Vector2(uvMin.x, outer.w));
AddQuad(toFill,
new Vector2(xMax, yMax) + rect.position,
new Vector2(rect.width, rect.height) + rect.position,
color,
new Vector2(uvMax.x, uvMax.y),
new Vector2(outer.z, outer.w));
}
}
else
{
// Texture has no border, is in repeat mode and not packed. Use texture tiling.
Vector2 uvScale = new Vector2((xMax - xMin) / tileWidth, (yMax - yMin) / tileHeight);
if (m_FillCenter)
{
AddQuad(toFill, new Vector2(xMin, yMin) + rect.position, new Vector2(xMax, yMax) + rect.position, color, Vector2.Scale(uvMin, uvScale), Vector2.Scale(uvMax, uvScale));
}
}
}
static void AddQuad(VertexHelper vertexHelper, Vector3[] quadPositions, Color32 color, Vector3[] quadUVs)
{
int startIndex = vertexHelper.currentVertCount;
for (int i = 0; i < 4; ++i)
vertexHelper.AddVert(quadPositions[i], color, quadUVs[i]);
vertexHelper.AddTriangle(startIndex, startIndex + 1, startIndex + 2);
vertexHelper.AddTriangle(startIndex + 2, startIndex + 3, startIndex);
}
static void AddQuad(VertexHelper vertexHelper, Vector2 posMin, Vector2 posMax, Color32 color, Vector2 uvMin, Vector2 uvMax)
{
int startIndex = vertexHelper.currentVertCount;
vertexHelper.AddVert(new Vector3(posMin.x, posMin.y, 0), color, new Vector2(uvMin.x, uvMin.y));
vertexHelper.AddVert(new Vector3(posMin.x, posMax.y, 0), color, new Vector2(uvMin.x, uvMax.y));
vertexHelper.AddVert(new Vector3(posMax.x, posMax.y, 0), color, new Vector2(uvMax.x, uvMax.y));
vertexHelper.AddVert(new Vector3(posMax.x, posMin.y, 0), color, new Vector2(uvMax.x, uvMin.y));
vertexHelper.AddTriangle(startIndex, startIndex + 1, startIndex + 2);
vertexHelper.AddTriangle(startIndex + 2, startIndex + 3, startIndex);
}
private Vector4 GetAdjustedBorders(Vector4 border, Rect adjustedRect)
{
Rect originalRect = rectTransform.rect;
for (int axis = 0; axis <= 1; axis++)
{
float borderScaleRatio;
// The adjusted rect (adjusted for pixel correctness)
// may be slightly larger than the original rect.
// Adjust the border to match the adjustedRect to avoid
// small gaps between borders (case 833201).
if (originalRect.size[axis] != 0)
{
borderScaleRatio = adjustedRect.size[axis] / originalRect.size[axis];
border[axis] *= borderScaleRatio;
border[axis + 2] *= borderScaleRatio;
}
// If the rect is smaller than the combined borders, then there's not room for the borders at their normal size.
// In order to avoid artefacts with overlapping borders, we scale the borders down to fit.
float combinedBorders = border[axis] + border[axis + 2];
if (adjustedRect.size[axis] < combinedBorders && combinedBorders != 0)
{
borderScaleRatio = adjustedRect.size[axis] / combinedBorders;
border[axis] *= borderScaleRatio;
border[axis + 2] *= borderScaleRatio;
}
}
return border;
}
static readonly Vector3[] s_Xy = new Vector3[4];
static readonly Vector3[] s_Uv = new Vector3[4];
/// <summary>
/// Generate vertices for a filled Image.
/// </summary>
void GenerateFilledSprite(VertexHelper toFill, bool preserveAspect)
{
toFill.Clear();
if (m_FillAmount < 0.001f)
return;
Vector4 v = GetDrawingDimensions(preserveAspect);
Vector4 outer = activeSprite != null ? Sprites.DataUtility.GetOuterUV(activeSprite) : Vector4.zero;
UIVertex uiv = UIVertex.simpleVert;
uiv.color = color;
float tx0 = outer.x;
float ty0 = outer.y;
float tx1 = outer.z;
float ty1 = outer.w;
// Horizontal and vertical filled sprites are simple -- just end the Image prematurely
if (m_FillMethod == FillMethod.Horizontal || m_FillMethod == FillMethod.Vertical)
{
if (fillMethod == FillMethod.Horizontal)
{
float fill = (tx1 - tx0) * m_FillAmount;
if (m_FillOrigin == 1)
{
v.x = v.z - (v.z - v.x) * m_FillAmount;
tx0 = tx1 - fill;
}
else
{
v.z = v.x + (v.z - v.x) * m_FillAmount;
tx1 = tx0 + fill;
}
}
else if (fillMethod == FillMethod.Vertical)
{
float fill = (ty1 - ty0) * m_FillAmount;
if (m_FillOrigin == 1)
{
v.y = v.w - (v.w - v.y) * m_FillAmount;
ty0 = ty1 - fill;
}
else
{
v.w = v.y + (v.w - v.y) * m_FillAmount;
ty1 = ty0 + fill;
}
}
}
s_Xy[0] = new Vector2(v.x, v.y);
s_Xy[1] = new Vector2(v.x, v.w);
s_Xy[2] = new Vector2(v.z, v.w);
s_Xy[3] = new Vector2(v.z, v.y);
s_Uv[0] = new Vector2(tx0, ty0);
s_Uv[1] = new Vector2(tx0, ty1);
s_Uv[2] = new Vector2(tx1, ty1);
s_Uv[3] = new Vector2(tx1, ty0);
{
if (m_FillAmount < 1f && m_FillMethod != FillMethod.Horizontal && m_FillMethod != FillMethod.Vertical)
{
if (fillMethod == FillMethod.Radial90)
{
if (RadialCut(s_Xy, s_Uv, m_FillAmount, m_FillClockwise, m_FillOrigin))
AddQuad(toFill, s_Xy, color, s_Uv);
}
else if (fillMethod == FillMethod.Radial180)
{
for (int side = 0; side < 2; ++side)
{
float fx0, fx1, fy0, fy1;
int even = m_FillOrigin > 1 ? 1 : 0;
if (m_FillOrigin == 0 || m_FillOrigin == 2)
{
fy0 = 0f;
fy1 = 1f;
if (side == even)
{
fx0 = 0f;
fx1 = 0.5f;
}
else
{
fx0 = 0.5f;
fx1 = 1f;
}
}
else
{
fx0 = 0f;
fx1 = 1f;
if (side == even)
{
fy0 = 0.5f;
fy1 = 1f;
}
else
{
fy0 = 0f;
fy1 = 0.5f;
}
}
s_Xy[0].x = Mathf.Lerp(v.x, v.z, fx0);
s_Xy[1].x = s_Xy[0].x;
s_Xy[2].x = Mathf.Lerp(v.x, v.z, fx1);
s_Xy[3].x = s_Xy[2].x;
s_Xy[0].y = Mathf.Lerp(v.y, v.w, fy0);
s_Xy[1].y = Mathf.Lerp(v.y, v.w, fy1);
s_Xy[2].y = s_Xy[1].y;
s_Xy[3].y = s_Xy[0].y;
s_Uv[0].x = Mathf.Lerp(tx0, tx1, fx0);
s_Uv[1].x = s_Uv[0].x;
s_Uv[2].x = Mathf.Lerp(tx0, tx1, fx1);
s_Uv[3].x = s_Uv[2].x;
s_Uv[0].y = Mathf.Lerp(ty0, ty1, fy0);
s_Uv[1].y = Mathf.Lerp(ty0, ty1, fy1);
s_Uv[2].y = s_Uv[1].y;
s_Uv[3].y = s_Uv[0].y;
float val = m_FillClockwise ? fillAmount * 2f - side : m_FillAmount * 2f - (1 - side);
if (RadialCut(s_Xy, s_Uv, Mathf.Clamp01(val), m_FillClockwise, ((side + m_FillOrigin + 3) % 4)))
{
AddQuad(toFill, s_Xy, color, s_Uv);
}
}
}
else if (fillMethod == FillMethod.Radial360)
{
for (int corner = 0; corner < 4; ++corner)
{
float fx0, fx1, fy0, fy1;
if (corner < 2)
{
fx0 = 0f;
fx1 = 0.5f;
}
else
{
fx0 = 0.5f;
fx1 = 1f;
}
if (corner == 0 || corner == 3)
{
fy0 = 0f;
fy1 = 0.5f;
}
else
{
fy0 = 0.5f;
fy1 = 1f;
}
s_Xy[0].x = Mathf.Lerp(v.x, v.z, fx0);
s_Xy[1].x = s_Xy[0].x;
s_Xy[2].x = Mathf.Lerp(v.x, v.z, fx1);
s_Xy[3].x = s_Xy[2].x;
s_Xy[0].y = Mathf.Lerp(v.y, v.w, fy0);
s_Xy[1].y = Mathf.Lerp(v.y, v.w, fy1);
s_Xy[2].y = s_Xy[1].y;
s_Xy[3].y = s_Xy[0].y;
s_Uv[0].x = Mathf.Lerp(tx0, tx1, fx0);
s_Uv[1].x = s_Uv[0].x;
s_Uv[2].x = Mathf.Lerp(tx0, tx1, fx1);
s_Uv[3].x = s_Uv[2].x;
s_Uv[0].y = Mathf.Lerp(ty0, ty1, fy0);
s_Uv[1].y = Mathf.Lerp(ty0, ty1, fy1);
s_Uv[2].y = s_Uv[1].y;
s_Uv[3].y = s_Uv[0].y;
float val = m_FillClockwise ?
m_FillAmount * 4f - ((corner + m_FillOrigin) % 4) :
m_FillAmount * 4f - (3 - ((corner + m_FillOrigin) % 4));
if (RadialCut(s_Xy, s_Uv, Mathf.Clamp01(val), m_FillClockwise, ((corner + 2) % 4)))
AddQuad(toFill, s_Xy, color, s_Uv);
}
}
}
else
{
AddQuad(toFill, s_Xy, color, s_Uv);
}
}
}
/// <summary>
/// Adjust the specified quad, making it be radially filled instead.
/// </summary>
static bool RadialCut(Vector3[] xy, Vector3[] uv, float fill, bool invert, int corner)
{
// Nothing to fill
if (fill < 0.001f) return false;
// Even corners invert the fill direction
if ((corner & 1) == 1) invert = !invert;
// Nothing to adjust
if (!invert && fill > 0.999f) return true;
// Convert 0-1 value into 0 to 90 degrees angle in radians
float angle = Mathf.Clamp01(fill);
if (invert) angle = 1f - angle;
angle *= 90f * Mathf.Deg2Rad;
// Calculate the effective X and Y factors
float cos = Mathf.Cos(angle);
float sin = Mathf.Sin(angle);
RadialCut(xy, cos, sin, invert, corner);
RadialCut(uv, cos, sin, invert, corner);
return true;
}
/// <summary>
/// Adjust the specified quad, making it be radially filled instead.
/// </summary>
static void RadialCut(Vector3[] xy, float cos, float sin, bool invert, int corner)
{
int i0 = corner;
int i1 = ((corner + 1) % 4);
int i2 = ((corner + 2) % 4);
int i3 = ((corner + 3) % 4);
if ((corner & 1) == 1)
{
if (sin > cos)
{
cos /= sin;
sin = 1f;
if (invert)
{
xy[i1].x = Mathf.Lerp(xy[i0].x, xy[i2].x, cos);
xy[i2].x = xy[i1].x;
}
}
else if (cos > sin)
{
sin /= cos;
cos = 1f;
if (!invert)
{
xy[i2].y = Mathf.Lerp(xy[i0].y, xy[i2].y, sin);
xy[i3].y = xy[i2].y;
}
}
else
{
cos = 1f;
sin = 1f;
}
if (!invert) xy[i3].x = Mathf.Lerp(xy[i0].x, xy[i2].x, cos);
else xy[i1].y = Mathf.Lerp(xy[i0].y, xy[i2].y, sin);
}
else
{
if (cos > sin)
{
sin /= cos;
cos = 1f;
if (!invert)
{
xy[i1].y = Mathf.Lerp(xy[i0].y, xy[i2].y, sin);
xy[i2].y = xy[i1].y;
}
}
else if (sin > cos)
{
cos /= sin;
sin = 1f;
if (invert)
{
xy[i2].x = Mathf.Lerp(xy[i0].x, xy[i2].x, cos);
xy[i3].x = xy[i2].x;
}
}
else
{
cos = 1f;
sin = 1f;
}
if (invert) xy[i3].y = Mathf.Lerp(xy[i0].y, xy[i2].y, sin);
else xy[i1].x = Mathf.Lerp(xy[i0].x, xy[i2].x, cos);
}
}
/// <summary>
/// See ILayoutElement.CalculateLayoutInputHorizontal.
/// </summary>
public virtual void CalculateLayoutInputHorizontal() {}
/// <summary>
/// See ILayoutElement.CalculateLayoutInputVertical.
/// </summary>
public virtual void CalculateLayoutInputVertical() {}
/// <summary>
/// See ILayoutElement.minWidth.
/// </summary>
public virtual float minWidth { get { return 0; } }
/// <summary>
/// If there is a sprite being rendered returns the size of that sprite.
/// In the case of a slided or tiled sprite will return the calculated minimum size possible
/// </summary>
public virtual float preferredWidth
{
get
{
if (activeSprite == null)
return 0;
if (type == Type.Sliced || type == Type.Tiled)
return Sprites.DataUtility.GetMinSize(activeSprite).x / pixelsPerUnit;
return activeSprite.rect.size.x / pixelsPerUnit;
}
}
/// <summary>
/// See ILayoutElement.flexibleWidth.
/// </summary>
public virtual float flexibleWidth { get { return -1; } }
/// <summary>
/// See ILayoutElement.minHeight.
/// </summary>
public virtual float minHeight { get { return 0; } }
/// <summary>
/// If there is a sprite being rendered returns the size of that sprite.
/// In the case of a slided or tiled sprite will return the calculated minimum size possible
/// </summary>
public virtual float preferredHeight
{
get
{
if (activeSprite == null)
return 0;
if (type == Type.Sliced || type == Type.Tiled)
return Sprites.DataUtility.GetMinSize(activeSprite).y / pixelsPerUnit;
return activeSprite.rect.size.y / pixelsPerUnit;
}
}
/// <summary>
/// See ILayoutElement.flexibleHeight.
/// </summary>
public virtual float flexibleHeight { get { return -1; } }
/// <summary>
/// See ILayoutElement.layoutPriority.
/// </summary>
public virtual int layoutPriority { get { return 0; } }
/// <summary>
/// Calculate if the ray location for this image is a valid hit location. Takes into account a Alpha test threshold.
/// </summary>
/// <param name="screenPoint">The screen point to check against</param>
/// <param name="eventCamera">The camera in which to use to calculate the coordinating position</param>
/// <returns>If the location is a valid hit or not.</returns>
/// <remarks> Also see See:ICanvasRaycastFilter.</remarks>
public virtual bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
{
if (alphaHitTestMinimumThreshold <= 0)
return true;
if (alphaHitTestMinimumThreshold > 1)
return false;
if (activeSprite == null)
return true;
Vector2 local;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera, out local))
return false;
Rect rect = GetPixelAdjustedRect();
if (m_PreserveAspect)
PreserveSpriteAspectRatio(ref rect, new Vector2(activeSprite.texture.width, activeSprite.texture.height));
// Convert to have lower left corner as reference point.
local.x += rectTransform.pivot.x * rect.width;
local.y += rectTransform.pivot.y * rect.height;
local = MapCoordinate(local, rect);
// Convert local coordinates to texture space.
float x = local.x / activeSprite.texture.width;
float y = local.y / activeSprite.texture.height;
try
{
return activeSprite.texture.GetPixelBilinear(x, y).a >= alphaHitTestMinimumThreshold;
}
catch (UnityException e)
{
Debug.LogError("Using alphaHitTestMinimumThreshold greater than 0 on Image whose sprite texture cannot be read. " + e.Message + " Also make sure to disable sprite packing for this sprite.", this);
return true;
}
}
private Vector2 MapCoordinate(Vector2 local, Rect rect)
{
Rect spriteRect = activeSprite.rect;
if (type == Type.Simple || type == Type.Filled)
return new Vector2(spriteRect.position.x + local.x * spriteRect.width / rect.width, spriteRect.position.y + local.y * spriteRect.height / rect.height);
Vector4 border = activeSprite.border;
Vector4 adjustedBorder = GetAdjustedBorders(border / pixelsPerUnit, rect);
for (int i = 0; i < 2; i++)
{
if (local[i] <= adjustedBorder[i])
continue;
if (rect.size[i] - local[i] <= adjustedBorder[i + 2])
{
local[i] -= (rect.size[i] - spriteRect.size[i]);
continue;
}
if (type == Type.Sliced)
{
float lerp = Mathf.InverseLerp(adjustedBorder[i], rect.size[i] - adjustedBorder[i + 2], local[i]);
local[i] = Mathf.Lerp(border[i], spriteRect.size[i] - border[i + 2], lerp);
}
else
{
local[i] -= adjustedBorder[i];
local[i] = Mathf.Repeat(local[i], spriteRect.size[i] - border[i] - border[i + 2]);
local[i] += border[i];
}
}
return local + spriteRect.position;
}
// To track textureless images, which will be rebuild if sprite atlas manager registered a Sprite Atlas that will give this image new texture
static List<Image> m_TrackedTexturelessImages = new List<Image>();
static bool s_Initialized;
static void RebuildImage(SpriteAtlas spriteAtlas)
{
for (var i = m_TrackedTexturelessImages.Count - 1; i >= 0; i--)
{
var g = m_TrackedTexturelessImages[i];
if (null != g.activeSprite && spriteAtlas.CanBindTo(g.activeSprite))
{
g.SetAllDirty();
m_TrackedTexturelessImages.RemoveAt(i);
}
}
}
private static void TrackImage(Image g)
{
if (!s_Initialized)
{
SpriteAtlasManager.atlasRegistered += RebuildImage;
s_Initialized = true;
}
m_TrackedTexturelessImages.Add(g);
}
private static void UnTrackImage(Image g)
{
m_TrackedTexturelessImages.Remove(g);
}
protected override void OnDidApplyAnimationProperties()
{
SetMaterialDirty();
SetVerticesDirty();
SetRaycastDirty();
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
m_PixelsPerUnitMultiplier = Mathf.Max(0.01f, m_PixelsPerUnitMultiplier);
}
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fe87c0e1cc204ed48ad3b37840f39efc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,3476 @@
using System;
using System.Collections;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityEngine.UI
{
/// <summary>
/// Turn a simple label into a interactable input field.
/// </summary>
[AddComponentMenu("UI/Legacy/Input Field", 103)]
public class InputField
: Selectable,
IUpdateSelectedHandler,
IBeginDragHandler,
IDragHandler,
IEndDragHandler,
IPointerClickHandler,
ISubmitHandler,
ICanvasElement,
ILayoutElement
{
/// <summary>
/// Setting the content type acts as a shortcut for setting a combination of InputType, CharacterValidation, LineType, and TouchScreenKeyboardType
/// </summary>
/// <remarks>
/// The ContentType affects character validation, keyboard type used (on platforms with on-screen keyboards), whether the InputField accepts multiple lines, and whether the text is autocorrected (on platforms that offer input auto-correction) or is treated as a password where the characters are not shown directly.
/// </remarks>
public enum ContentType
{
/// <summary>
/// Allows all input.
/// </summary>
Standard,
/// <summary>
/// Allows all input and performs auto-correction on platforms that support it.
/// </summary>
Autocorrected,
/// <summary>
/// Allow whole numbers (positive or negative).
/// </summary>
IntegerNumber,
/// <summary>
/// Allows decimal numbers (positive or negative).
/// </summary>
DecimalNumber,
/// <summary>
/// Allows letters A-Z, a-z and numbers 0-9.
/// </summary>
Alphanumeric,
/// <summary>
/// The InputField is used for typing in a name, and enforces capitalization of the first letter of each word. Note that the user can circumvent the first letter capitalization rules by deleting automatically-capitalized letters.
/// </summary>
Name,
/// <summary>
/// The input is used for typing in an email address.
/// </summary>
EmailAddress,
/// <summary>
/// Allows all input and hides the typed characters by showing them as asterisks characters.
/// </summary>
Password,
/// <summary>
/// Allows integer numbers and hides the typed characters by showing them as asterisks characters.
/// </summary>
Pin,
/// <summary>
/// Custom types that allows user-defined settings.
/// </summary>
Custom
}
/// <summary>
/// Type of data expected by the input field mobile keyboard.
/// </summary>
public enum InputType
{
/// <summary>
/// The standard mobile keyboard
/// </summary>
Standard,
/// <summary>
/// The mobile autocorrect keyboard.
/// </summary>
AutoCorrect,
/// <summary>
/// The mobile password keyboard.
/// </summary>
Password,
}
/// <summary>
/// The type of characters that are allowed to be added to the string.
/// </summary>
/// <remarks>
/// Note that the character validation does not validate the entire string as being valid or not. It only does validation on a per-character level, resulting in the typed character either being added to the string or not
/// </remarks>
public enum CharacterValidation
{
/// <summary>
/// No validation. Any input is valid.
/// </summary>
None,
/// <summary>
/// Allow whole numbers (positive or negative).
/// Characters 0-9 and - (dash / minus sign) are allowed. The dash is only allowed as the first character.
/// </summary>
Integer,
/// <summary>
/// Allows decimal numbers (positive or negative).
/// </summary>
/// <remarks>
/// Characters 0-9, . (dot), and - (dash / minus sign) are allowed. The dash is only allowed as the first character. Only one dot in the string is allowed.
/// </remarks>
Decimal,
/// <summary>
/// Allows letters A-Z, a-z and numbers 0-9.
/// </summary>
Alphanumeric,
/// <summary>
/// Only allow names and enforces capitalization.
/// </summary>
/// <remarks>
/// Allows letters, spaces, and ' (apostrophe). A character after a space is automatically made upper case. A character not after a space is automatically made lowercase. A character after an apostrophe can be either upper or lower case. Only one apostrophe in the string is allowed. More than one space in a row is not allowed.
///
/// A characters is considered a letter if it is categorized as a Unicode letter, as implemented by the Char.Isletter method in .Net.
/// </remarks>
Name,
/// <summary>
/// Allows the characters that are allowed in an email address.
/// </summary>
/// <remarks>
/// Allows characters A-Z, a.z, 0-9, @, . (dot), !, #, $, %, &amp;, ', *, +, -, /, =, ?, ^, _, `, {, |, }, and ~.
///
/// Only one @ is allowed in the string and more than one dot in a row are not allowed. Note that the character validation does not validate the entire string as being a valid email address since it only does validation on a per-character level, resulting in the typed character either being added to the string or not.
/// </remarks>
EmailAddress
}
/// <summary>
/// The LineType is used to describe the behavior of the InputField.
/// </summary>
public enum LineType
{
/// <summary>
/// Only allows 1 line to be entered. Has horizontal scrolling and no word wrap. Pressing enter will submit the InputField.
/// </summary>
SingleLine,
/// <summary>
/// Is a multiline InputField with vertical scrolling and overflow. Pressing the return key will submit.
/// </summary>
MultiLineSubmit,
/// <summary>
/// Is a multiline InputField with vertical scrolling and overflow. Pressing the return key will insert a new line character.
/// </summary>
MultiLineNewline
}
public delegate char OnValidateInput(string text, int charIndex, char addedChar);
[Serializable]
/// <summary>
/// Unity Event with a inputfield as a param.
/// </summary>
public class SubmitEvent : UnityEvent<string> {}
[Serializable]
/// <summary>
/// Unity Event with a inputfield as a param.
/// </summary>
public class EndEditEvent : UnityEvent<string> {}
[Serializable]
/// <summary>
/// The callback sent anytime the Inputfield is updated.
/// </summary>
public class OnChangeEvent : UnityEvent<string> {}
protected TouchScreenKeyboard m_Keyboard;
static private readonly char[] kSeparators = { ' ', '.', ',', '\t', '\r', '\n' };
#if UNITY_ANDROID
static private bool s_IsQuestDeviceEvaluated = false;
#endif // if UNITY_ANDROID
static private bool s_IsQuestDevice = false;
/// <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;
[FormerlySerializedAs("inputType")]
[SerializeField]
private InputType m_InputType = InputType.Standard;
[FormerlySerializedAs("asteriskChar")]
[SerializeField]
private char m_AsteriskChar = '*';
[FormerlySerializedAs("keyboardType")]
[SerializeField]
private TouchScreenKeyboardType m_KeyboardType = TouchScreenKeyboardType.Default;
[SerializeField]
private LineType m_LineType = LineType.SingleLine;
[FormerlySerializedAs("hideMobileInput")]
[SerializeField]
private bool m_HideMobileInput = false;
[FormerlySerializedAs("validation")]
[SerializeField]
private CharacterValidation m_CharacterValidation = CharacterValidation.None;
[FormerlySerializedAs("characterLimit")]
[SerializeField]
private int m_CharacterLimit = 0;
[FormerlySerializedAs("onSubmit")]
[FormerlySerializedAs("m_OnSubmit")]
[FormerlySerializedAs("m_EndEdit")]
[FormerlySerializedAs("m_OnEndEdit")]
[SerializeField]
private SubmitEvent m_OnSubmit = new SubmitEvent();
[SerializeField]
private EndEditEvent m_OnDidEndEdit = new EndEditEvent();
[FormerlySerializedAs("onValueChange")]
[FormerlySerializedAs("m_OnValueChange")]
[SerializeField]
private OnChangeEvent m_OnValueChanged = new OnChangeEvent();
[FormerlySerializedAs("onValidateInput")]
[SerializeField]
private OnValidateInput m_OnValidateInput;
[FormerlySerializedAs("selectionColor")]
[SerializeField]
private Color m_CaretColor = new Color(50f / 255f, 50f / 255f, 50f / 255f, 1f);
[SerializeField]
private bool m_CustomCaretColor = false;
[SerializeField]
private Color m_SelectionColor = new Color(168f / 255f, 206f / 255f, 255f / 255f, 192f / 255f);
[SerializeField]
[Multiline]
[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;
[SerializeField]
private bool m_ShouldActivateOnSelect = true;
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 WaitForSecondsRealtime m_WaitForSecondsRealtime;
private bool m_TouchKeyboardAllowsInPlaceEditing = false;
private bool m_IsCompositionActive = 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 = "!#$%&'*+-/=?^_`{|}~";
const string kOculusQuestDeviceModel = "Oculus Quest";
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. This allows for input to happen with a caret in the InputField instead of a OS input box above the keyboard.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public InputField mainInputField;
///
/// public void Start()
/// {
/// //This setting can be toggled in the inspector.
/// mainInputField.shouldHideMobileInput = true;
/// }
/// }
/// ]]>
///</code>
/// </example>
public bool shouldHideMobileInput
{
set
{
SetPropertyUtility.SetStruct(ref m_HideMobileInput, value);
}
get
{
switch (Application.platform)
{
case RuntimePlatform.Android:
case RuntimePlatform.IPhonePlayer:
case RuntimePlatform.tvOS:
return m_HideMobileInput;
}
return true;
}
}
/// <summary>
/// Should the inputfield be automatically activated upon selection.
/// </summary>
public virtual bool shouldActivateOnSelect
{
set
{
m_ShouldActivateOnSelect = value;
}
get
{
return m_ShouldActivateOnSelect && Application.platform != RuntimePlatform.tvOS;
}
}
/// <summary>
/// Input field's current text value. This is not necessarily the same as what is visible on screen.
/// </summary>
/// <remarks>
/// Note that null is invalid value for InputField.text.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public InputField mainInputField;
///
/// public void Start()
/// {
/// mainInputField.text = "Enter Text Here...";
/// }
/// }
/// ]]>
///</code>
/// </example>
public string text
{
get
{
return m_Text;
}
set
{
SetText(value);
}
}
/// <summary>
/// Set the current text value of the Input field without invoking onValueChanged.
/// </summary>
/// <remarks>
/// This is not necessarily the same as what is visible on screen.
/// </remarks>
public void SetTextWithoutNotify(string input)
{
SetText(input, false);
}
void SetText(string value, bool sendCallback = true)
{
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;
if (sendCallback)
SendOnValueChanged();
UpdateLabel();
}
/// <summary>
/// Whether the InputField has focus and whether it is able to process events.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public GameObject mainInputField;
///
/// void Update()
/// {
/// //If the input field is focused, change its color to green.
/// if (mainInputField.GetComponent<InputField>().isFocused == true)
/// {
/// mainInputField.GetComponent<Image>().color = Color.green;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public bool isFocused
{
get { return m_AllowInput; }
}
/// <summary>
/// The blinking rate of the input caret, defined as the number of times the blink cycle occurs per second.
/// </summary>
public float caretBlinkRate
{
get { return m_CaretBlinkRate; }
set
{
if (SetPropertyUtility.SetStruct(ref m_CaretBlinkRate, value))
{
if (m_AllowInput)
SetCaretActive();
}
}
}
/// <summary>
/// The width of the caret in pixels.
/// </summary>
public int caretWidth { get { return m_CaretWidth; } set { if (SetPropertyUtility.SetStruct(ref m_CaretWidth, value)) MarkGeometryAsDirty(); } }
/// <summary>
/// The Text component that is going to be used to render the text to screen.
/// </summary>
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);
}
}
}
}
/// <summary>
/// This is an optional empty graphic to show that the InputField text field is empty. Note that this empty' graphic still displays even when the InputField is selected (that is; when there is focus on it).
/// A placeholder graphic can be used to show subtle hints or make it more obvious that the control is an InputField.
/// </summary>
/// <remarks>
/// If a Text component is used as the placeholder, it's recommended to make the placeholder text look different from the real text of the InputField so they are not easily confused. For example, the placeholder text might be a more subtle color or have lower alpha value.
/// </remarks>
public Graphic placeholder { get { return m_Placeholder; } set { SetPropertyUtility.SetClass(ref m_Placeholder, value); } }
/// <summary>
/// The custom caret color used if customCaretColor is set.
/// </summary>
public Color caretColor { get { return customCaretColor ? m_CaretColor : textComponent.color; } set { if (SetPropertyUtility.SetColor(ref m_CaretColor, value)) MarkGeometryAsDirty(); } }
/// <summary>
/// Should a custom caret color be used or should the textComponent.color be used.
/// </summary>
public bool customCaretColor { get { return m_CustomCaretColor; } set { if (m_CustomCaretColor != value) { m_CustomCaretColor = value; MarkGeometryAsDirty(); } } }
/// <summary>
/// The color of the highlight to show which characters are selected.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public InputField mainInputField;
///
/// // Changes the color of the highlight that shows what characters are selected.
/// void ChangeSelectionColor()
/// {
/// mainInputField.selectionColor = Color.red;
/// }
/// }
/// ]]>
///</code>
/// </example>
public Color selectionColor { get { return m_SelectionColor; } set { if (SetPropertyUtility.SetColor(ref m_SelectionColor, value)) MarkGeometryAsDirty(); } }
/// <summary>
/// The Unity Event to call when editing has ended
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public InputField mainInputField;
///
/// // Checks if there is anything entered into the input field.
/// void LockInput(InputField input)
/// {
/// if (input.text.Length > 0)
/// {
/// Debug.Log("Text has been entered");
/// }
/// else if (input.text.Length == 0)
/// {
/// Debug.Log("Main Input Empty");
/// }
/// }
///
/// public void Start()
/// {
/// //Adds a listener that invokes the "LockInput" method when the player finishes editing the main input field.
/// //Passes the main input field into the method when "LockInput" is invoked
/// mainInputField.onEndEdit.AddListener(delegate {LockInput(mainInputField); });
/// }
/// }
/// ]]>
///</code>
/// </example>
public EndEditEvent onEndEdit { get { return m_OnDidEndEdit; } set { SetPropertyUtility.SetClass(ref m_OnDidEndEdit, value); } }
/// <summary>
/// The Unity Event to call when editing has ended
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public InputField mainInputField;
///
/// // Checks if there is anything entered into the input field.
/// void LockInput(InputField input)
/// {
/// if (input.text.Length > 0)
/// {
/// Debug.Log("Text has been entered");
/// }
/// else if (input.text.Length == 0)
/// {
/// Debug.Log("Main Input Empty");
/// }
/// }
///
/// public void Start()
/// {
/// //Adds a listener that invokes the "LockInput" method when the player finishes editing the main input field.
/// //Passes the main input field into the method when "LockInput" is invoked
/// mainInputField.onSubmit.AddListener(delegate {LockInput(mainInputField); });
/// }
/// }
/// ]]>
///</code>
/// </example>
public SubmitEvent onSubmit { get { return m_OnSubmit; } set { SetPropertyUtility.SetClass(ref m_OnSubmit, value); } }
[Obsolete("onValueChange has been renamed to onValueChanged")]
public OnChangeEvent onValueChange { get { return onValueChanged; } set { onValueChanged = value; } }
/// <summary>
/// Accessor to the OnChangeEvent.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public InputField mainInputField;
///
/// public void Start()
/// {
/// //Adds a listener to the main input field and invokes a method when the value changes.
/// mainInputField.onValueChange.AddListener(delegate {ValueChangeCheck(); });
/// }
///
/// // Invoked when the value of the text field changes.
/// public void ValueChangeCheck()
/// {
/// Debug.Log("Value Changed");
/// }
/// }
/// ]]>
///</code>
/// </example>
public OnChangeEvent onValueChanged { get { return m_OnValueChanged; } set { SetPropertyUtility.SetClass(ref m_OnValueChanged, value); } }
/// <summary>
/// The function to call to validate the input characters.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public InputField mainInputField;
///
/// public void Start()
/// {
/// // Sets the MyValidate method to invoke after the input field's default input validation invoke (default validation happens every time a character is entered into the text field.)
/// mainInputField.onValidateInput += delegate(string input, int charIndex, char addedChar) { return MyValidate(addedChar); };
/// }
///
/// private char MyValidate(char charToValidate)
/// {
/// //Checks if a dollar sign is entered....
/// if (charToValidate == '$')
/// {
/// // ... if it is change it to an empty character.
/// charToValidate = '\0';
/// }
/// return charToValidate;
/// }
/// }
/// ]]>
///</code>
/// </example>
public OnValidateInput onValidateInput { get { return m_OnValidateInput; } set { SetPropertyUtility.SetClass(ref m_OnValidateInput, value); } }
/// <summary>
/// How many characters the input field is limited to. 0 = infinite.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public InputField mainInputField;
/// public string playerName;
///
/// void Start()
/// {
/// //Changes the character limit in the main input field.
/// mainInputField.characterLimit = playerName.Length;
/// }
/// }
/// ]]>
///</code>
/// </example>
public int characterLimit
{
get { return m_CharacterLimit; }
set
{
if (SetPropertyUtility.SetStruct(ref m_CharacterLimit, Math.Max(0, value)))
{
UpdateLabel();
if (m_Keyboard != null)
m_Keyboard.characterLimit = value;
}
}
}
/// <summary>
/// Specifies the type of the input text content.
/// </summary>
/// <remarks>
/// The ContentType affects character validation, keyboard type used (on platforms with on-screen keyboards), whether the InputField accepts multiple lines, and whether the text is autocorrected (on platforms that offer input auto-correction) or is treated as a password where the characters are not shown directly.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public InputField mainInputField;
/// public string playerName;
///
/// void Start()
/// {
/// //Changes the character limit in the main input field.
/// mainInputField.characterLimit = playerName.Length;
/// }
/// }
/// ]]>
///</code>
/// </example>
public ContentType contentType { get { return m_ContentType; } set { if (SetPropertyUtility.SetStruct(ref m_ContentType, value)) EnforceContentType(); } }
/// <summary>
/// The LineType used by the InputField.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public GameObject mainInputField;
///
/// //When you press a button, this method is called.
/// public void ChangeInputField(int type)
/// {
/// if (type == 0)
/// {
/// //Change the input field to "Single Line" line type.
/// mainInputField.GetComponent<InputField>().lineType = InputField.LineType.SingleLine;
/// }
/// else if (type == 1)
/// {
/// //Change the input field to "MultiLine Newline" line type.
/// mainInputField.GetComponent<InputField>().lineType = InputField.LineType.MultiLineNewline;
/// }
/// else if (type == 2)
/// {
/// //Change the input field to "MultiLine Submit" line type.
/// mainInputField.GetComponent<InputField>().lineType = InputField.LineType.MultiLineSubmit;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public LineType lineType
{
get { return m_LineType; }
set
{
if (SetPropertyUtility.SetStruct(ref m_LineType, value))
{
SetToCustomIfContentTypeIsNot(ContentType.Standard, ContentType.Autocorrected);
EnforceTextHOverflow();
}
}
}
/// <summary>
/// The type of input expected. See InputField.InputType.
/// </summary>
public InputType inputType { get { return m_InputType; } set { if (SetPropertyUtility.SetStruct(ref m_InputType, value)) SetToCustom(); } }
/// <summary>
/// The TouchScreenKeyboard being used to edit the Input Field.
/// </summary>
public TouchScreenKeyboard touchScreenKeyboard { get { return m_Keyboard; } }
/// <summary>
/// They type of mobile keyboard that will be used.
/// </summary>
public TouchScreenKeyboardType keyboardType
{
get { return m_KeyboardType; }
set
{
if (SetPropertyUtility.SetStruct(ref m_KeyboardType, value))
SetToCustom();
}
}
/// <summary>
/// The type of validation to perform on a character
/// </summary>
public CharacterValidation characterValidation { get { return m_CharacterValidation; } set { if (SetPropertyUtility.SetStruct(ref m_CharacterValidation, value)) SetToCustom(); } }
/// <summary>
/// Set the InputField to be read only.
/// </summary>
/// <remarks>
/// Setting read only allows for highlighting of text without allowing modifications via keyboard.
/// </remarks>
public bool readOnly { get { return m_ReadOnly; } set { m_ReadOnly = value; } }
/// <summary>
/// If the input field supports multiple lines.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public InputField mainInputField;
///
/// public void Update()
/// {
/// //Check to see if the input field is set to allow multiple lines.
/// if (mainInputField.multiLine)
/// {
/// //Set the input field to only allow Single Lines, if it is currently set to allow Multiple lines.
/// mainInputField.lineType = InputField.LineType.SingleLine;
/// //Print to console
/// Debug.Log("The main input field is now set to allow single lines only!");
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public bool multiLine { get { return m_LineType == LineType.MultiLineNewline || lineType == LineType.MultiLineSubmit; } }
/// <summary>
/// The character used to hide text in password field.
/// </summary>
/// <remarks>
/// Not shown in the inspector.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public InputField mainInputField;
///
/// void Start()
/// {
/// // changes the password symbol. 0 = $, 1 = ! 2 = £ and so on.
/// mainInputField.asteriskChar = "$!£%&*"[0];
/// }
/// }
/// ]]>
///</code>
/// </example>
public char asteriskChar { get { return m_AsteriskChar; } set { if (SetPropertyUtility.SetStruct(ref m_AsteriskChar, value)) UpdateLabel(); } }
/// <summary>
/// If the InputField was canceled and will revert back to the original text upon DeactivateInputField.
/// </summary>
public bool wasCanceled { get { return m_WasCanceled; } }
/// <summary>
/// Clamp a value (by reference) between 0 and the current text length.
/// </summary>
/// <param name="pos">The input position to be clampped</param>
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
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
[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>
/// The beginning point of the selection.
/// </summary>
/// <remarks>
/// When making a selection with a mouse, the anchor is where in the document the mouse button is initially pressed.
/// Get: Returns the beginning position of selection
/// Set: If Input.compositionString is 0 set the fixed position.
/// </remarks>
public int selectionAnchorPosition
{
get { return m_CaretPosition + compositionString.Length; }
set
{
if (compositionString.Length != 0)
return;
m_CaretPosition = value;
ClampPos(ref m_CaretPosition);
}
}
/// <summary>
/// The end point of the selection.
/// </summary>
/// <remarks>
/// When making a selection with a mouse, the focus is where in the document the mouse button is released.
/// Get: Returns the end position of selection
/// Set: If Input.compositionString is 0 set the variable position.
/// </remarks>
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;
// fix case 1040277
ClampPos(ref m_CaretPosition);
ClampPos(ref m_CaretSelectPosition);
UpdateLabel();
if (m_AllowInput)
SetCaretActive();
}
#endif // if UNITY_EDITOR
#if UNITY_ANDROID
protected override void Awake()
{
base.Awake();
if (s_IsQuestDeviceEvaluated)
return;
// Used for Oculus Quest 1 and 2 software keyboard regression.
// TouchScreenKeyboard.isInPlaceEditingAllowed is always returning true in these devices and would prevent the software keyboard from showing up if that value was used.
s_IsQuestDevice = SystemInfo.deviceModel == kOculusQuestDeviceModel;
s_IsQuestDeviceEvaluated = true;
}
#endif // if UNITY_ANDROID
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.DisableCanvasElementForRebuild(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();
}
protected override void OnDestroy()
{
CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild(this);
base.OnDestroy();
}
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);
}
/// <summary>
/// Focus the input field initializing properties.
/// </summary>
/// <remarks>
/// Handles what happens after a user selects an InputField. This is a protected property. To return the focus state use InputField.isFocused. To shift focus to another GameObject, use EventSystem.SetSelectedGameObject.
/// A common use of this is allowing the user to type once focussed. Another way is outputting a message when the user clicks on a field(often seen when creating passwords).
/// </remarks>
/// <example>
/// //Create an Input Field by going to __Create__>__UI__>__Input Field__. Attach this script to the Input Field GameObject
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using UnityEngine.UI;
///
/// public class Example : MonoBehaviour
/// {
/// InputField m_InputField;
/// void Start()
/// {
/// //Fetch the Input Field component from the GameObject
/// m_InputField = GetComponent<InputField>();
/// }
///
/// void Update()
/// {
/// //Check if the Input Field is in focus and able to alter
/// if (m_InputField.isFocused)
/// {
/// //Change the Color of the Input Field's Image to green
/// m_InputField.GetComponent<Image>().color = Color.green;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
protected void OnFocus()
{
SelectAll();
}
/// <summary>
/// Highlight the whole InputField.
/// </summary>
/// <remarks>
/// Sets the caretPosition to the length of the text and caretSelectPos to 0.
/// </remarks>
protected void SelectAll()
{
caretPositionInternal = text.Length;
caretSelectPositionInternal = 0;
}
/// <summary>
/// Move the caret index to end of text.
/// </summary>
/// <param name="shift">Only move the selection position to facilate selection</param>
public void MoveTextEnd(bool shift)
{
int position = text.Length;
if (shift)
{
caretSelectPositionInternal = position;
}
else
{
caretPositionInternal = position;
caretSelectPositionInternal = caretPositionInternal;
}
UpdateLabel();
}
/// <summary>
/// Move the caret index to start of text.
/// </summary>
/// <param name="shift">Only move the selection position to facilate selection</param>
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;
}
}
// Returns true if the TouchScreenKeyboard should be used. On Android and Chrome OS, we only want to use the
// TouchScreenKeyboard if in-place editing is not allowed (i.e. when we do not have a hardware keyboard available).
private bool TouchScreenKeyboardShouldBeUsed()
{
RuntimePlatform platform = Application.platform;
switch (platform)
{
case RuntimePlatform.Android:
if (s_IsQuestDevice)
return TouchScreenKeyboard.isSupported;
return !TouchScreenKeyboard.isInPlaceEditingAllowed;
case RuntimePlatform.WebGLPlayer:
return !TouchScreenKeyboard.isInPlaceEditingAllowed;
default:
return TouchScreenKeyboard.isSupported;
}
}
private bool InPlaceEditing()
{
return !TouchScreenKeyboard.isSupported || m_TouchKeyboardAllowsInPlaceEditing;
}
// In-place editing can change state if a hardware keyboard becomes available or is hidden while the input field is activated.
// This currently only happens on Chrome OS devices (that support laptop and tablet mode).
private bool InPlaceEditingChanged()
{
return !s_IsQuestDevice && m_TouchKeyboardAllowsInPlaceEditing != TouchScreenKeyboard.isInPlaceEditingAllowed;
}
RangeInt GetInternalSelection()
{
var selectionStart = Mathf.Min(caretSelectPositionInternal, caretPositionInternal);
var selectionLength = Mathf.Abs(caretSelectPositionInternal - caretPositionInternal);
return new RangeInt(selectionStart, selectionLength);
}
void UpdateKeyboardCaret()
{
// On iOS/tvOS we only update SoftKeyboard selection when we know that it might have changed by touch/pointer interactions with InputField
// Setting the TouchScreenKeyboard selection here instead of LateUpdate so that we wouldn't override
// TouchScreenKeyboard selection when it's changed with cmd+a/ctrl+a/arrow/etc. in the TouchScreenKeyboard
// This is only applicable for iOS/tvOS as we have instance of TouchScreenKeyboard even when external keyboard is connected
if (m_HideMobileInput && m_Keyboard != null && m_Keyboard.canSetSelection &&
(Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.tvOS))
{
m_Keyboard.selection = GetInternalSelection();
}
}
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;
}
AssignPositioningIfNeeded();
// If the device's state changed in a way that affects whether we should use a touchscreen keyboard or not,
// then we make sure to clear all of the caret/highlight state visually and deactivate the input field.
if (isFocused && InPlaceEditingChanged())
{
if (m_CachedInputRenderer != null)
{
using (var helper = new VertexHelper())
helper.FillMesh(mesh);
m_CachedInputRenderer.SetMesh(mesh);
}
DeactivateInputField();
}
if (!isFocused || InPlaceEditing())
return;
if (m_Keyboard == null || m_Keyboard.status != TouchScreenKeyboard.Status.Visible)
{
if (m_Keyboard != null)
{
if (!m_ReadOnly)
text = m_Keyboard.text;
if (m_Keyboard.status == TouchScreenKeyboard.Status.Canceled)
m_WasCanceled = true;
else if (m_Keyboard.status == TouchScreenKeyboard.Status.Done)
SendOnSubmit();
}
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;
SendOnSubmit();
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();
}
}
// On iOS/tvOS we always have TouchScreenKeyboard instance even when using external keyboard
// so we keep track of the caret position there
else if (m_HideMobileInput && m_Keyboard != null && m_Keyboard.canSetSelection &&
Application.platform != RuntimePlatform.IPhonePlayer && Application.platform != RuntimePlatform.tvOS)
{
m_Keyboard.selection = GetInternalSelection();
}
else if (m_Keyboard != null && m_Keyboard.canGetSelection)
{
UpdateCaretFromKeyboard();
}
if (m_Keyboard.status != TouchScreenKeyboard.Status.Visible)
{
if (m_Keyboard.status == TouchScreenKeyboard.Status.Canceled)
m_WasCanceled = true;
else if (m_Keyboard.status == TouchScreenKeyboard.Status.Done)
SendOnSubmit();
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>
/// <param name="pos">Mouse position.</param>
/// <returns>Character index with in value.</returns>
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 &&
(InPlaceEditing() || m_HideMobileInput);
}
/// <summary>
/// Capture the OnBeginDrag callback from the EventSystem and ensure we should listen to the drag events to follow.
/// </summary>
/// <param name="eventData">The data passed by the EventSystem</param>
public virtual void OnBeginDrag(PointerEventData eventData)
{
if (!MayDrag(eventData))
return;
m_UpdateDrag = true;
}
/// <summary>
/// If we are able to drag, try and select the character range underneath the bounding rect.
/// </summary>
/// <param name="eventData"></param>
public virtual void OnDrag(PointerEventData eventData)
{
if (!MayDrag(eventData))
return;
Vector2 position = Vector2.zero;
if (!MultipleDisplayUtilities.GetRelativeMousePositionForDrag(eventData, ref position))
return;
Vector2 localMousePos;
RectTransformUtility.ScreenPointToLocalPointInRectangle(textComponent.rectTransform, 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));
UpdateKeyboardCaret();
eventData.Use();
}
IEnumerator MouseDragOutsideRect(PointerEventData eventData)
{
while (m_UpdateDrag && m_DragPositionOutOfBounds)
{
Vector2 position = Vector2.zero;
if (!MultipleDisplayUtilities.GetRelativeMousePositionForDrag(eventData, ref position))
break;
Vector2 localMousePos;
RectTransformUtility.ScreenPointToLocalPointInRectangle(textComponent.rectTransform, 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;
if (m_WaitForSecondsRealtime == null)
m_WaitForSecondsRealtime = new WaitForSecondsRealtime(delay);
else
m_WaitForSecondsRealtime.waitTime = delay;
yield return m_WaitForSecondsRealtime;
}
m_DragCoroutine = null;
}
/// <summary>
/// Capture the OnEndDrag callback from the EventSystem and cancel the listening of drag events.
/// </summary>
/// <param name="eventData">The eventData sent by the EventSystem.</param>
public virtual void OnEndDrag(PointerEventData eventData)
{
if (!MayDrag(eventData))
return;
m_UpdateDrag = false;
}
/// <summary>
/// The action to perform when the event system sends a pointer down Event.
/// </summary>
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.pointerPressRaycast.screenPosition, eventData.pressEventCamera, out localMousePos);
caretSelectPositionInternal = caretPositionInternal = GetCharacterIndexFromPosition(localMousePos) + m_DrawStart;
}
UpdateLabel();
UpdateKeyboardCaret();
eventData.Use();
}
protected enum EditState
{
Continue,
Finish
}
/// <summary>
/// Process the Event and perform the appropriate action for that key.
/// </summary>
/// <param name="evt">The Event that is currently being processed.</param>
/// <returns>If we should continue processing events or we have hit an end condition.</returns>
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;
bool shiftOnly = shift && !ctrl && !alt;
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);
UpdateLabel();
return EditState.Continue;
}
break;
}
// Cut
case KeyCode.X:
{
if (ctrlOnly)
{
if (inputType != InputType.Password)
clipboard = GetSelectedString();
else
clipboard = "";
Delete();
UpdateTouchKeyboardFromEditChanges();
SendOnValueChangedAndUpdateLabel();
return EditState.Continue;
}
break;
}
case KeyCode.Insert:
{
// Copy via Insert key
if (ctrlOnly)
{
if (inputType != InputType.Password)
clipboard = GetSelectedString();
else
clipboard = "";
return EditState.Continue;
}
// Paste via insert key.
else if (shiftOnly)
{
Append(clipboard);
UpdateLabel();
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)
{
if (c == 0)
return false;
// 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();
/// <summary>
/// Helper function to allow separate events to be processed by the InputField.
/// </summary>
/// <param name="e">The Event to process</param>
public void ProcessEvent(Event e)
{
KeyPressed(e);
}
/// <summary>
/// What to do when the event system sends a Update selected Event.
/// </summary>
/// <param name="eventData">The data on which to process.</param>
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;
// Special handling on OSX which produces more events which need to be suppressed.
if (m_IsCompositionActive && compositionString.Length == 0)
{
// Suppress other events related to navigation or termination of composition sequence.
if (m_ProcessingEvent.character == 0 && m_ProcessingEvent.modifiers == EventModifiers.None)
{
continue;
}
}
var shouldContinue = KeyPressed(m_ProcessingEvent);
if (shouldContinue == EditState.Finish)
{
if (!m_WasCanceled)
SendOnSubmit();
DeactivateInputField();
continue;
}
UpdateLabel();
}
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();
UpdateTouchKeyboardFromEditChanges();
SendOnValueChangedAndUpdateLabel();
}
else
{
if (caretPositionInternal < text.Length)
{
m_Text = text.Remove(caretPositionInternal, 1);
UpdateTouchKeyboardFromEditChanges();
SendOnValueChangedAndUpdateLabel();
}
}
}
private void Backspace()
{
if (m_ReadOnly)
return;
if (hasSelection)
{
Delete();
UpdateTouchKeyboardFromEditChanges();
SendOnValueChangedAndUpdateLabel();
}
else
{
if (caretPositionInternal > 0 && caretPositionInternal - 1 < text.Length)
{
m_Text = text.Remove(caretPositionInternal - 1, 1);
caretSelectPositionInternal = caretPositionInternal = caretPositionInternal - 1;
UpdateTouchKeyboardFromEditChanges();
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;
UpdateTouchKeyboardFromEditChanges();
SendOnValueChanged();
}
private void UpdateTouchKeyboardFromEditChanges()
{
// Update the TouchKeyboard's text from edit changes
// if in-place editing is allowed
if (m_Keyboard != null && InPlaceEditing())
{
m_Keyboard.text = m_Text;
}
}
private void SendOnValueChangedAndUpdateLabel()
{
SendOnValueChanged();
UpdateLabel();
}
private void SendOnValueChanged()
{
UISystemProfilerApi.AddMarker("InputField.value", this);
if (onValueChanged != null)
onValueChanged.Invoke(text);
}
/// <summary>
/// Convenience function to make functionality to send the ::ref::EndEditEvent easier.
/// </summary>
protected void SendOnEndEdit()
{
UISystemProfilerApi.AddMarker("InputField.onEndEdit", this);
if (onEndEdit != null)
onEndEdit.Invoke(m_Text);
}
/// <summary>
/// Convenience function to make functionality to send the ::ref::SubmitEvent easier.
/// </summary>
protected void SendOnSubmit()
{
UISystemProfilerApi.AddMarker("InputField.onSubmit", this);
if (onSubmit != null)
onSubmit.Invoke(m_Text);
}
/// <summary>
/// Append the specified text to the end of the current text string. Appends character by character testing validation criteria.
/// </summary>
/// <param name="input">The String to append.</param>
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);
}
}
}
// cf. TextGenerator.cpp
private const int k_MaxTextLength = UInt16.MaxValue / 4 - 1;
/// <summary>
/// Append a character to the input field, taking into account the validation of each character.
/// </summary>
/// <param name="input">Character to append.</param>
protected virtual void Append(char input)
{
// We do not currently support surrogate pairs
if (char.IsSurrogate(input))
return;
if (m_ReadOnly || text.Length >= k_MaxTextLength)
return;
if (!InPlaceEditing())
return;
// If we have an input validator, validate the input first
int insertionPoint = Math.Min(selectionFocusPosition, selectionAnchorPosition);
//Get the text based on selection for validation instead of whole text(case 1253193).
var validateText = text;
if (selectionFocusPosition != selectionAnchorPosition)
{
if (caretPositionInternal < caretSelectPositionInternal)
{
validateText = text.Substring(0, caretPositionInternal) + text.Substring(caretSelectPositionInternal, text.Length - caretSelectPositionInternal);
}
else
{
validateText = text.Substring(0, caretSelectPositionInternal) + text.Substring(caretPositionInternal, text.Length - caretPositionInternal);
}
}
if (onValidateInput != null)
input = onValidateInput(validateText, insertionPoint, input);
else if (characterValidation != CharacterValidation.None)
input = Validate(validateText, 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 Text associated with this input field.
/// </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 (EventSystem.current != null && gameObject == EventSystem.current.currentSelectedGameObject && compositionString.Length > 0)
{
m_IsCompositionActive = true;
fullText = text.Substring(0, m_CaretPosition) + compositionString + text.Substring(m_CaretPosition);
}
else
{
m_IsCompositionActive = false;
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;
}
// To fix case 1320719; we need to rebuild the layout before we check the number of characters that can fit within the extents.
// Otherwise, the extents provided may not be good.
textComponent.SetLayoutDirty();
Canvas.ForceUpdateCanvases();
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;
}
}
}
/// <summary>
/// Force the label to update immediatly. This will recalculate the positioning of the caret and the visible text.
/// </summary>
public void ForceLabelUpdate()
{
UpdateLabel();
}
private void MarkGeometryAsDirty()
{
#if UNITY_EDITOR
if (!Application.isPlaying || UnityEditor.PrefabUtility.IsPartOfPrefabAsset(gameObject))
return;
#endif
CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
}
/// <summary>
/// Rebuild the input fields geometry. (caret and highlight).
/// </summary>
/// <param name="update">Which update loop we are in.</param>
public virtual void Rebuild(CanvasUpdate update)
{
switch (update)
{
case CanvasUpdate.LatePreRender:
UpdateGeometry();
break;
}
}
/// <summary>
/// See ICanvasElement.LayoutComplete. Does nothing by default.
/// </summary>
public virtual void LayoutComplete()
{}
/// <summary>
/// See ICanvasElement.GraphicUpdateComplete. Does nothing by default.
/// </summary>
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 (!InPlaceEditing() && !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
GenerateHighlight(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;
// Calculate position of IME Window in screen space.
Camera cameraRef;
if (m_TextComponent.canvas.renderMode == RenderMode.ScreenSpaceOverlay)
cameraRef = null;
else
cameraRef = m_TextComponent.canvas.worldCamera;
Vector3 cursorPosition = m_CachedInputRenderer.gameObject.transform.TransformPoint(m_CursorVerts[0].position);
Vector2 screenPosition = RectTransformUtility.WorldToScreenPoint(cameraRef, cursorPosition);
screenPosition.y = screenHeight - screenPosition.y;
if (input != null)
input.compositionCursorPos = screenPosition;
}
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 GenerateHighlight(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>
/// Predefined validation functionality for different characterValidation types.
/// </summary>
/// <param name="text">The whole text string to validate.</param>
/// <param name="pos">The position at which the current character is being inserted.</param>
/// <param name="ch">The character that is being inserted</param>
/// <returns>The character that should be inserted.</returns>
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 == '.' || ch == ',') && characterValidation == CharacterValidation.Decimal && text.IndexOfAny(new[] { '.', ',' }) == -1) 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 or a hyphen should be in uppercase.
if (char.IsLower(ch) && ((pos == 0) || (text[pos - 1] == ' ') || (text[pos - 1] == '-')))
{
return char.ToUpper(ch);
}
// Character not following a space or an apostrophe or a hyphen should be in lowercase.
if (char.IsUpper(ch) && (pos > 0) && (text[pos - 1] != ' ') && (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] == '\'') || (text[pos - 1] == '-'))) ||
((pos < text.Length) && ((text[pos] == ' ') || (text[pos] == '\'') || (text[pos] == '-')))))
return ch;
}
if (ch == ' ' || ch == '-')
{
if (pos != 0) // Don't allow leading spaces and hyphens
{
// Don't allow consecutive spaces, apostrophes and hyphens.
if (!(((pos > 0) && ((text[pos - 1] == ' ') || (text[pos - 1] == '\'') || (text[pos - 1] == '-'))) ||
((pos < text.Length) && ((text[pos] == ' ') || (text[pos] == '\'') || (text[pos - 1] == '-')))))
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;
}
/// <summary>
/// Function to activate the InputField to begin processing Events.
/// </summary>
/// <remarks>
/// Will only activate if deactivated.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI;
///
/// public class Example : MonoBehaviour
/// {
/// public InputField mainInputField;
///
/// // Activate the main input field when the scene starts.
/// void Start()
/// {
/// mainInputField.ActivateInputField();
/// }
/// }
/// ]]>
///</code>
/// </example>
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);
// Cache the value of isInPlaceEditingAllowed, because on UWP this involves calling into native code
// Usually, the value only needs to be updated once when the TouchKeyboard is opened; however, on Chrome OS,
// we check repeatedly to see if the in-place editing state has changed, so we can take action.
m_TouchKeyboardAllowsInPlaceEditing = !s_IsQuestDevice && TouchScreenKeyboard.isInPlaceEditingAllowed;
if (TouchScreenKeyboardShouldBeUsed())
{
if (input != null && input.touchSupported)
{
TouchScreenKeyboard.hideInput = shouldHideMobileInput;
}
m_Keyboard = (inputType == InputType.Password) ?
TouchScreenKeyboard.Open(m_Text, keyboardType, false, multiLine, true, false, "", characterLimit) :
TouchScreenKeyboard.Open(m_Text, keyboardType, inputType == InputType.AutoCorrect, multiLine, false, false, "", characterLimit);
// If TouchKeyboard doesn't support InPlaceEditing don't call OnFocus as mobile doesn't properly support select all
// Just set it to the end of the text (where it would move when typing starts)
if (!m_TouchKeyboardAllowsInPlaceEditing)
{
MoveTextEnd(false);
}
}
// Perform normal OnFocus routine if platform supports it
if (!TouchScreenKeyboard.isSupported || m_TouchKeyboardAllowsInPlaceEditing)
{
if (input != null)
input.imeCompositionMode = IMECompositionMode.On;
OnFocus();
}
m_AllowInput = true;
m_OriginalText = text;
m_WasCanceled = false;
SetCaretVisible();
UpdateLabel();
}
/// <summary>
/// What to do when the event system sends a submit Event.
/// </summary>
/// <param name="eventData">The data on which to process</param>
public override void OnSelect(BaseEventData eventData)
{
base.OnSelect(eventData);
if (shouldActivateOnSelect)
ActivateInputField();
}
/// <summary>
/// What to do when the event system sends a pointer click Event
/// </summary>
/// <param name="eventData">The data on which to process</param>
public virtual void OnPointerClick(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
ActivateInputField();
}
/// <summary>
/// Function to deactivate the InputField to stop the processing of Events and send OnSubmit if not canceled.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public InputField mainInputField;
///
/// // Deactivates the main input field when the scene starts.
/// void Start()
/// {
/// mainInputField.DeactivateInputField();
/// }
/// }
/// ]]>
///</code>
/// </example>
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;
SendOnEndEdit();
if (m_Keyboard != null)
{
m_Keyboard.active = false;
m_Keyboard = null;
}
m_CaretPosition = m_CaretSelectPosition = 0;
if (input != null)
input.imeCompositionMode = IMECompositionMode.Auto;
}
MarkGeometryAsDirty();
}
/// <summary>
/// What to do when the event system sends a Deselect Event. Defaults to deactivating the inputfield.
/// </summary>
/// <param name="eventData">The data sent by the EventSystem</param>
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.Selected;
else if (state == SelectionState.Pressed)
m_HasDoneFocusTransition = true;
base.DoStateTransition(state, instant);
}
/// <summary>
/// See ILayoutElement.CalculateLayoutInputHorizontal.
/// </summary>
public virtual void CalculateLayoutInputHorizontal() {}
/// <summary>
/// See ILayoutElement.CalculateLayoutInputVertical.
/// </summary>
public virtual void CalculateLayoutInputVertical() {}
/// <summary>
/// See ILayoutElement.minWidth.
/// </summary>
public virtual float minWidth { get { return 5; } }
/// <summary>
/// Get the displayed with of all input characters.
/// </summary>
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;
}
}
/// <summary>
/// See ILayoutElement.flexibleWidth.
/// </summary>
public virtual float flexibleWidth { get { return -1; } }
/// <summary>
/// See ILayoutElement.minHeight.
/// </summary>
public virtual float minHeight { get { return 0; } }
/// <summary>
/// Get the height of all the text if constrained to the height of the RectTransform.
/// </summary>
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;
}
}
/// <summary>
/// See ILayoutElement.flexibleHeight.
/// </summary>
public virtual float flexibleHeight { get { return -1; } }
/// <summary>
/// See ILayoutElement.layoutPriority.
/// </summary>
public virtual int layoutPriority { get { return 1; } }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d199490a83bb2b844b9695cbf13b01ef
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1999349e7f492c947bb6eb70f624382e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,256 @@
using UnityEngine.EventSystems;
namespace UnityEngine.UI
{
[AddComponentMenu("Layout/Aspect Ratio Fitter", 142)]
[ExecuteAlways]
[RequireComponent(typeof(RectTransform))]
[DisallowMultipleComponent]
/// <summary>
/// Resizes a RectTransform to fit a specified aspect ratio.
/// </summary>
public class AspectRatioFitter : UIBehaviour, ILayoutSelfController
{
/// <summary>
/// Specifies a mode to use to enforce an aspect ratio.
/// </summary>
public enum AspectMode
{
/// <summary>
/// The aspect ratio is not enforced
/// </summary>
None,
/// <summary>
/// Changes the height of the rectangle to match the aspect ratio.
/// </summary>
WidthControlsHeight,
/// <summary>
/// Changes the width of the rectangle to match the aspect ratio.
/// </summary>
HeightControlsWidth,
/// <summary>
/// Sizes the rectangle such that it's fully contained within the parent rectangle.
/// </summary>
FitInParent,
/// <summary>
/// Sizes the rectangle such that the parent rectangle is fully contained within.
/// </summary>
EnvelopeParent
}
[SerializeField] private AspectMode m_AspectMode = AspectMode.None;
/// <summary>
/// The mode to use to enforce the aspect ratio.
/// </summary>
public AspectMode aspectMode { get { return m_AspectMode; } set { if (SetPropertyUtility.SetStruct(ref m_AspectMode, value)) SetDirty(); } }
[SerializeField] private float m_AspectRatio = 1;
/// <summary>
/// The aspect ratio to enforce. This means width divided by height.
/// </summary>
public float aspectRatio { get { return m_AspectRatio; } set { if (SetPropertyUtility.SetStruct(ref m_AspectRatio, value)) SetDirty(); } }
[System.NonSerialized]
private RectTransform m_Rect;
// This "delayed" mechanism is required for case 1014834.
private bool m_DelayedSetDirty = false;
//Does the gameobject has a parent for reference to enable FitToParent/EnvelopeParent modes.
private bool m_DoesParentExist = false;
private RectTransform rectTransform
{
get
{
if (m_Rect == null)
m_Rect = GetComponent<RectTransform>();
return m_Rect;
}
}
// field is never assigned warning
#pragma warning disable 649
private DrivenRectTransformTracker m_Tracker;
#pragma warning restore 649
protected AspectRatioFitter() {}
protected override void OnEnable()
{
base.OnEnable();
m_DoesParentExist = rectTransform.parent ? true : false;
SetDirty();
}
protected override void Start()
{
base.Start();
//Disable the component if the aspect mode is not valid or the object state/setup is not supported with AspectRatio setup.
if (!IsComponentValidOnObject() || !IsAspectModeValid())
this.enabled = false;
}
protected override void OnDisable()
{
m_Tracker.Clear();
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
base.OnDisable();
}
protected override void OnTransformParentChanged()
{
base.OnTransformParentChanged();
m_DoesParentExist = rectTransform.parent ? true : false;
SetDirty();
}
/// <summary>
/// Update the rect based on the delayed dirty.
/// Got around issue of calling onValidate from OnEnable function.
/// </summary>
protected virtual void Update()
{
if (m_DelayedSetDirty)
{
m_DelayedSetDirty = false;
SetDirty();
}
}
/// <summary>
/// Function called when this RectTransform or parent RectTransform has changed dimensions.
/// </summary>
protected override void OnRectTransformDimensionsChange()
{
UpdateRect();
}
private void UpdateRect()
{
if (!IsActive() || !IsComponentValidOnObject())
return;
m_Tracker.Clear();
switch (m_AspectMode)
{
#if UNITY_EDITOR
case AspectMode.None:
{
if (!Application.isPlaying)
m_AspectRatio = Mathf.Clamp(rectTransform.rect.width / rectTransform.rect.height, 0.001f, 1000f);
break;
}
#endif
case AspectMode.HeightControlsWidth:
{
m_Tracker.Add(this, rectTransform, DrivenTransformProperties.SizeDeltaX);
rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, rectTransform.rect.height * m_AspectRatio);
break;
}
case AspectMode.WidthControlsHeight:
{
m_Tracker.Add(this, rectTransform, DrivenTransformProperties.SizeDeltaY);
rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, rectTransform.rect.width / m_AspectRatio);
break;
}
case AspectMode.FitInParent:
case AspectMode.EnvelopeParent:
{
if (!DoesParentExists())
break;
m_Tracker.Add(this, rectTransform,
DrivenTransformProperties.Anchors |
DrivenTransformProperties.AnchoredPosition |
DrivenTransformProperties.SizeDeltaX |
DrivenTransformProperties.SizeDeltaY);
rectTransform.anchorMin = Vector2.zero;
rectTransform.anchorMax = Vector2.one;
rectTransform.anchoredPosition = Vector2.zero;
Vector2 sizeDelta = Vector2.zero;
Vector2 parentSize = GetParentSize();
if ((parentSize.y * aspectRatio < parentSize.x) ^ (m_AspectMode == AspectMode.FitInParent))
{
sizeDelta.y = GetSizeDeltaToProduceSize(parentSize.x / aspectRatio, 1);
}
else
{
sizeDelta.x = GetSizeDeltaToProduceSize(parentSize.y * aspectRatio, 0);
}
rectTransform.sizeDelta = sizeDelta;
break;
}
}
}
private float GetSizeDeltaToProduceSize(float size, int axis)
{
return size - GetParentSize()[axis] * (rectTransform.anchorMax[axis] - rectTransform.anchorMin[axis]);
}
private Vector2 GetParentSize()
{
RectTransform parent = rectTransform.parent as RectTransform;
return !parent ? Vector2.zero : parent.rect.size;
}
/// <summary>
/// Method called by the layout system. Has no effect
/// </summary>
public virtual void SetLayoutHorizontal() {}
/// <summary>
/// Method called by the layout system. Has no effect
/// </summary>
public virtual void SetLayoutVertical() {}
/// <summary>
/// Mark the AspectRatioFitter as dirty.
/// </summary>
protected void SetDirty()
{
UpdateRect();
}
public bool IsComponentValidOnObject()
{
Canvas canvas = gameObject.GetComponent<Canvas>();
if (canvas && canvas.isRootCanvas && canvas.renderMode != RenderMode.WorldSpace)
{
return false;
}
return true;
}
public bool IsAspectModeValid()
{
if (!DoesParentExists() && (aspectMode == AspectMode.EnvelopeParent || aspectMode == AspectMode.FitInParent))
return false;
return true;
}
private bool DoesParentExists()
{
return m_DoesParentExist;
}
#if UNITY_EDITOR
protected override void OnValidate()
{
m_AspectRatio = Mathf.Clamp(m_AspectRatio, 0.001f, 1000f);
m_DelayedSetDirty = true;
}
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 86710e43de46f6f4bac7c8e50813a599
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,405 @@
using System;
using UnityEngine.Events;
using UnityEngine.EventSystems;
namespace UnityEngine.UI
{
[RequireComponent(typeof(Canvas))]
[ExecuteAlways]
[AddComponentMenu("Layout/Canvas Scaler", 101)]
[DisallowMultipleComponent]
/// <summary>
/// The Canvas Scaler component is used for controlling the overall scale and pixel density of UI elements in the Canvas. This scaling affects everything under the Canvas, including font sizes and image borders.
/// </summary>
/// <remarks>
/// For a Canvas set to 'Screen Space - Overlay' or 'Screen Space - Camera', the Canvas Scaler UI Scale Mode can be set to Constant Pixel Size, Scale With Screen Size, or Constant Physical Size.
///
/// Using the Constant Pixel Size mode, positions and sizes of UI elements are specified in pixels on the screen. This is also the default functionality of the Canvas when no Canvas Scaler is attached. However, With the Scale Factor setting in the Canvas Scaler, a constant scaling can be applied to all UI elements in the Canvas.
///
/// Using the Scale With Screen Size mode, positions and sizes can be specified according to the pixels of a specified reference resolution. If the current screen resolution is larger than the reference resolution, the Canvas will keep having only the resolution of the reference resolution, but will scale up in order to fit the screen. If the current screen resolution is smaller than the reference resolution, the Canvas will similarly be scaled down to fit. If the current screen resolution has a different aspect ratio than the reference resolution, scaling each axis individually to fit the screen would result in non-uniform scaling, which is generally undesirable. Instead of this, the ReferenceResolution component will make the Canvas resolution deviate from the reference resolution in order to respect the aspect ratio of the screen. It is possible to control how this deviation should behave using the ::ref::screenMatchMode setting.
///
/// Using the Constant Physical Size mode, positions and sizes of UI elements are specified in physical units, such as millimeters, points, or picas. This mode relies on the device reporting its screen DPI correctly. You can specify a fallback DPI to use for devices that do not report a DPI.
///
/// For a Canvas set to 'World Space' the Canvas Scaler can be used to control the pixel density of UI elements in the Canvas.
/// </remarks>
public class CanvasScaler : UIBehaviour
{
/// <summary>
/// Determines how UI elements in the Canvas are scaled.
/// </summary>
public enum ScaleMode
{
/// <summary>
/// Using the Constant Pixel Size mode, positions and sizes of UI elements are specified in pixels on the screen.
/// </summary>
ConstantPixelSize,
/// <summary>
/// Using the Scale With Screen Size mode, positions and sizes can be specified according to the pixels of a specified reference resolution.
/// If the current screen resolution is larger than the reference resolution, the Canvas will keep having only the resolution of the reference resolution, but will scale up in order to fit the screen. If the current screen resolution is smaller than the reference resolution, the Canvas will similarly be scaled down to fit.
/// </summary>
ScaleWithScreenSize,
/// <summary>
/// Using the Constant Physical Size mode, positions and sizes of UI elements are specified in physical units, such as millimeters, points, or picas.
/// </summary>
ConstantPhysicalSize
}
[Tooltip("Determines how UI elements in the Canvas are scaled.")]
[SerializeField] private ScaleMode m_UiScaleMode = ScaleMode.ConstantPixelSize;
///<summary>
///Determines how UI elements in the Canvas are scaled.
///</summary>
public ScaleMode uiScaleMode { get { return m_UiScaleMode; } set { m_UiScaleMode = value; } }
[Tooltip("If a sprite has this 'Pixels Per Unit' setting, then one pixel in the sprite will cover one unit in the UI.")]
[SerializeField] protected float m_ReferencePixelsPerUnit = 100;
/// <summary>
/// If a sprite has this 'Pixels Per Unit' setting, then one pixel in the sprite will cover one unit in the UI.
/// </summary>
public float referencePixelsPerUnit { get { return m_ReferencePixelsPerUnit; } set { m_ReferencePixelsPerUnit = value; } }
// Constant Pixel Size settings
[Tooltip("Scales all UI elements in the Canvas by this factor.")]
[SerializeField] protected float m_ScaleFactor = 1;
/// <summary>
/// Scales all UI elements in the Canvas by this factor.
/// </summary>
/// <summary>
/// Scales all UI elements in the Canvas by this factor.
/// </summary>
public float scaleFactor { get { return m_ScaleFactor; } set { m_ScaleFactor = Mathf.Max(0.01f, value); } }
/// Scale the canvas area with the width as reference, the height as reference, or something in between.
/// <summary>
/// Scale the canvas area with the width as reference, the height as reference, or something in between.
/// </summary>
public enum ScreenMatchMode
{
/// <summary>
/// Scale the canvas area with the width as reference, the height as reference, or something in between.
/// </summary>
MatchWidthOrHeight = 0,
/// <summary>
/// Expand the canvas area either horizontally or vertically, so the size of the canvas will never be smaller than the reference.
/// </summary>
Expand = 1,
/// <summary>
/// Crop the canvas area either horizontally or vertically, so the size of the canvas will never be larger than the reference.
/// </summary>
Shrink = 2
}
[Tooltip("The resolution the UI layout is designed for. If the screen resolution is larger, the UI will be scaled up, and if it's smaller, the UI will be scaled down. This is done in accordance with the Screen Match Mode.")]
[SerializeField] protected Vector2 m_ReferenceResolution = new Vector2(800, 600);
/// <summary>
/// The resolution the UI layout is designed for.
/// </summary>
/// <remarks>
/// If the screen resolution is larger, the UI will be scaled up, and if it's smaller, the UI will be scaled down. This is done in accordance with the Screen Match Mode.
/// </remarks>
public Vector2 referenceResolution
{
get
{
return m_ReferenceResolution;
}
set
{
m_ReferenceResolution = value;
const float k_MinimumResolution = 0.00001f;
if (m_ReferenceResolution.x > -k_MinimumResolution && m_ReferenceResolution.x < k_MinimumResolution) m_ReferenceResolution.x = k_MinimumResolution * Mathf.Sign(m_ReferenceResolution.x);
if (m_ReferenceResolution.y > -k_MinimumResolution && m_ReferenceResolution.y < k_MinimumResolution) m_ReferenceResolution.y = k_MinimumResolution * Mathf.Sign(m_ReferenceResolution.y);
}
}
[Tooltip("A mode used to scale the canvas area if the aspect ratio of the current resolution doesn't fit the reference resolution.")]
[SerializeField] protected ScreenMatchMode m_ScreenMatchMode = ScreenMatchMode.MatchWidthOrHeight;
/// <summary>
/// A mode used to scale the canvas area if the aspect ratio of the current resolution doesn't fit the reference resolution.
/// </summary>
public ScreenMatchMode screenMatchMode { get { return m_ScreenMatchMode; } set { m_ScreenMatchMode = value; } }
[Tooltip("Determines if the scaling is using the width or height as reference, or a mix in between.")]
[Range(0, 1)]
[SerializeField] protected float m_MatchWidthOrHeight = 0;
/// <summary>
/// Setting to scale the Canvas to match the width or height of the reference resolution, or a combination.
/// </summary>
/// <remarks>
/// If the setting is set to 0, the Canvas is scaled according to the difference between the current screen resolution width and the reference resolution width. If the setting is set to 1, the Canvas is scaled according to the difference between the current screen resolution height and the reference resolution height.
///
/// For values in between 0 and 1, the scaling is based on a combination of the relative width and height.
///
/// Consider an example where the reference resolution of 640x480, and the current screen resolution is a landscape mode of 480x640.
///
/// If the scaleWidthOrHeight setting is set to 0, the Canvas is scaled by 0.75 because the current resolution width of 480 is 0.75 times the reference resolution width of 640. The Canvas resolution gets a resolution of 640x853.33. This resolution has the same width as the reference resolution width, but has the aspect ratio of the current screen resolution. Note that the Canvas resolution of 640x853.33 is the current screen resolution divided by the scale factor of 0.75.
///
/// If the scaleWidthOrHeight setting is set to 1, the Canvas is scaled by 1.33 because the current resolution height of 640 is 1.33 times the reference resolution height of 480. The Canvas resolution gets a resolution of 360x480. This resolution has the same height as the reference resolution width, but has the aspect ratio of the current screen resolution. Note that the Canvas resolution of 360x480 is the current screen resolution divided by the scale factor of 1.33.
///
/// If the scaleWidthOrHeight setting is set to 0.5, we find the horizontal scaling needed (0.75) and the vertical scaling needed (1.33) and find the average. However, we do the average in logarithmic space. A regular average of 0.75 and 1.33 would produce a result of 1.04. However, since multiplying by 1.33 is the same as diving by 0.75, the two scale factor really corresponds to multiplying by 0.75 versus dividing by 0.75, and the average of those two things should even out and produce a neutral result. The average in logarithmic space of 0.75 and 1.33 is exactly 1.0, which is what we want. The Canvas resolution hence ends up being 480x640 which is the current resolution divided by the scale factor of 1.0.
///
/// The logic works the same for all values. The average between the horizontal and vertical scale factor is a weighted average based on the matchWidthOrHeight value.
/// </remarks>
public float matchWidthOrHeight { get { return m_MatchWidthOrHeight; } set { m_MatchWidthOrHeight = value; } }
// The log base doesn't have any influence on the results whatsoever, as long as the same base is used everywhere.
private const float kLogBase = 2;
/// <summary>
/// The possible physical unit types
/// </summary>
public enum Unit
{
/// <summary>
/// Use centimeters.
/// A centimeter is 1/100 of a meter
/// </summary>
Centimeters,
/// <summary>
/// Use millimeters.
/// A millimeter is 1/10 of a centimeter, and 1/1000 of a meter.
/// </summary>
Millimeters,
/// <summary>
/// Use inches.
/// </summary>
Inches,
/// <summary>
/// Use points.
/// One point is 1/12 of a pica, and 1/72 of an inch.
/// </summary>
Points,
/// <summary>
/// Use picas.
/// One pica is 1/6 of an inch.
/// </summary>
Picas
}
[Tooltip("The physical unit to specify positions and sizes in.")]
[SerializeField] protected Unit m_PhysicalUnit = Unit.Points;
/// <summary>
/// The physical unit to specify positions and sizes in.
/// </summary>
public Unit physicalUnit { get { return m_PhysicalUnit; } set { m_PhysicalUnit = value; } }
[Tooltip("The DPI to assume if the screen DPI is not known.")]
[SerializeField] protected float m_FallbackScreenDPI = 96;
/// <summary>
/// The DPI to assume if the screen DPI is not known.
/// </summary>
public float fallbackScreenDPI { get { return m_FallbackScreenDPI; } set { m_FallbackScreenDPI = value; } }
[Tooltip("The pixels per inch to use for sprites that have a 'Pixels Per Unit' setting that matches the 'Reference Pixels Per Unit' setting.")]
[SerializeField] protected float m_DefaultSpriteDPI = 96;
/// <summary>
/// The pixels per inch to use for sprites that have a 'Pixels Per Unit' setting that matches the 'Reference Pixels Per Unit' setting.
/// </summary>
public float defaultSpriteDPI { get { return m_DefaultSpriteDPI; } set { m_DefaultSpriteDPI = Mathf.Max(1, value); } }
// World Canvas settings
[Tooltip("The amount of pixels per unit to use for dynamically created bitmaps in the UI, such as Text.")]
[SerializeField] protected float m_DynamicPixelsPerUnit = 1;
/// <summary>
/// The amount of pixels per unit to use for dynamically created bitmaps in the UI, such as Text.
/// </summary>
public float dynamicPixelsPerUnit { get { return m_DynamicPixelsPerUnit; } set { m_DynamicPixelsPerUnit = value; } }
// General variables
private Canvas m_Canvas;
[System.NonSerialized]
private float m_PrevScaleFactor = 1;
[System.NonSerialized]
private float m_PrevReferencePixelsPerUnit = 100;
[SerializeField] protected bool m_PresetInfoIsWorld = false;
protected CanvasScaler() {}
protected override void OnEnable()
{
base.OnEnable();
m_Canvas = GetComponent<Canvas>();
Handle();
Canvas.preWillRenderCanvases += Canvas_preWillRenderCanvases;
}
private void Canvas_preWillRenderCanvases()
{
Handle();
}
protected override void OnDisable()
{
SetScaleFactor(1);
SetReferencePixelsPerUnit(100);
Canvas.preWillRenderCanvases -= Canvas_preWillRenderCanvases;
base.OnDisable();
}
///<summary>
///Method that handles calculations of canvas scaling.
///</summary>
protected virtual void Handle()
{
if (m_Canvas == null || !m_Canvas.isRootCanvas)
return;
if (m_Canvas.renderMode == RenderMode.WorldSpace)
{
HandleWorldCanvas();
return;
}
switch (m_UiScaleMode)
{
case ScaleMode.ConstantPixelSize: HandleConstantPixelSize(); break;
case ScaleMode.ScaleWithScreenSize: HandleScaleWithScreenSize(); break;
case ScaleMode.ConstantPhysicalSize: HandleConstantPhysicalSize(); break;
}
}
/// <summary>
/// Handles canvas scaling for world canvas.
/// </summary>
protected virtual void HandleWorldCanvas()
{
SetScaleFactor(m_DynamicPixelsPerUnit);
SetReferencePixelsPerUnit(m_ReferencePixelsPerUnit);
}
/// <summary>
/// Handles canvas scaling for a constant pixel size.
/// </summary>
protected virtual void HandleConstantPixelSize()
{
SetScaleFactor(m_ScaleFactor);
SetReferencePixelsPerUnit(m_ReferencePixelsPerUnit);
}
/// <summary>
/// Handles canvas scaling that scales with the screen size.
/// </summary>
protected virtual void HandleScaleWithScreenSize()
{
Vector2 screenSize = m_Canvas.renderingDisplaySize;
// 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_Canvas.targetDisplay;
if (displayIndex > 0 && displayIndex < Display.displays.Length)
{
Display disp = Display.displays[displayIndex];
screenSize = new Vector2(disp.renderingWidth, disp.renderingHeight);
}
float scaleFactor = 0;
switch (m_ScreenMatchMode)
{
case ScreenMatchMode.MatchWidthOrHeight:
{
// We take the log of the relative width and height before taking the average.
// Then we transform it back in the original space.
// the reason to transform in and out of logarithmic space is to have better behavior.
// If one axis has twice resolution and the other has half, it should even out if widthOrHeight value is at 0.5.
// In normal space the average would be (0.5 + 2) / 2 = 1.25
// In logarithmic space the average is (-1 + 1) / 2 = 0
float logWidth = Mathf.Log(screenSize.x / m_ReferenceResolution.x, kLogBase);
float logHeight = Mathf.Log(screenSize.y / m_ReferenceResolution.y, kLogBase);
float logWeightedAverage = Mathf.Lerp(logWidth, logHeight, m_MatchWidthOrHeight);
scaleFactor = Mathf.Pow(kLogBase, logWeightedAverage);
break;
}
case ScreenMatchMode.Expand:
{
scaleFactor = Mathf.Min(screenSize.x / m_ReferenceResolution.x, screenSize.y / m_ReferenceResolution.y);
break;
}
case ScreenMatchMode.Shrink:
{
scaleFactor = Mathf.Max(screenSize.x / m_ReferenceResolution.x, screenSize.y / m_ReferenceResolution.y);
break;
}
}
SetScaleFactor(scaleFactor);
SetReferencePixelsPerUnit(m_ReferencePixelsPerUnit);
}
///<summary>
///Handles canvas scaling for a constant physical size.
///</summary>
protected virtual void HandleConstantPhysicalSize()
{
float currentDpi = Screen.dpi;
float dpi = (currentDpi == 0 ? m_FallbackScreenDPI : currentDpi);
float targetDPI = 1;
switch (m_PhysicalUnit)
{
case Unit.Centimeters: targetDPI = 2.54f; break;
case Unit.Millimeters: targetDPI = 25.4f; break;
case Unit.Inches: targetDPI = 1; break;
case Unit.Points: targetDPI = 72; break;
case Unit.Picas: targetDPI = 6; break;
}
SetScaleFactor(dpi / targetDPI);
SetReferencePixelsPerUnit(m_ReferencePixelsPerUnit * targetDPI / m_DefaultSpriteDPI);
}
/// <summary>
/// Sets the scale factor on the canvas.
/// </summary>
/// <param name="scaleFactor">The scale factor to use.</param>
protected void SetScaleFactor(float scaleFactor)
{
if (scaleFactor == m_PrevScaleFactor)
return;
m_Canvas.scaleFactor = scaleFactor;
m_PrevScaleFactor = scaleFactor;
}
/// <summary>
/// Sets the referencePixelsPerUnit on the Canvas.
/// </summary>
/// <param name="referencePixelsPerUnit">The new reference pixels per Unity value</param>
protected void SetReferencePixelsPerUnit(float referencePixelsPerUnit)
{
if (referencePixelsPerUnit == m_PrevReferencePixelsPerUnit)
return;
m_Canvas.referencePixelsPerUnit = referencePixelsPerUnit;
m_PrevReferencePixelsPerUnit = referencePixelsPerUnit;
}
#if UNITY_EDITOR
protected override void OnValidate()
{
m_ScaleFactor = Mathf.Max(0.01f, m_ScaleFactor);
m_DefaultSpriteDPI = Mathf.Max(1, m_DefaultSpriteDPI);
}
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0cd44c1031e13a943bb63640046fad76
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,138 @@
using UnityEngine.EventSystems;
namespace UnityEngine.UI
{
[AddComponentMenu("Layout/Content Size Fitter", 141)]
[ExecuteAlways]
[RequireComponent(typeof(RectTransform))]
/// <summary>
/// Resizes a RectTransform to fit the size of its content.
/// </summary>
/// <remarks>
/// The ContentSizeFitter can be used on GameObjects that have one or more ILayoutElement components, such as Text, Image, HorizontalLayoutGroup, VerticalLayoutGroup, and GridLayoutGroup.
/// </remarks>
public class ContentSizeFitter : UIBehaviour, ILayoutSelfController
{
/// <summary>
/// The size fit modes avaliable to use.
/// </summary>
public enum FitMode
{
/// <summary>
/// Don't perform any resizing.
/// </summary>
Unconstrained,
/// <summary>
/// Resize to the minimum size of the content.
/// </summary>
MinSize,
/// <summary>
/// Resize to the preferred size of the content.
/// </summary>
PreferredSize
}
[SerializeField] protected FitMode m_HorizontalFit = FitMode.Unconstrained;
/// <summary>
/// The fit mode to use to determine the width.
/// </summary>
public FitMode horizontalFit { get { return m_HorizontalFit; } set { if (SetPropertyUtility.SetStruct(ref m_HorizontalFit, value)) SetDirty(); } }
[SerializeField] protected FitMode m_VerticalFit = FitMode.Unconstrained;
/// <summary>
/// The fit mode to use to determine the height.
/// </summary>
public FitMode verticalFit { get { return m_VerticalFit; } set { if (SetPropertyUtility.SetStruct(ref m_VerticalFit, value)) SetDirty(); } }
[System.NonSerialized] private RectTransform m_Rect;
private RectTransform rectTransform
{
get
{
if (m_Rect == null)
m_Rect = GetComponent<RectTransform>();
return m_Rect;
}
}
// field is never assigned warning
#pragma warning disable 649
private DrivenRectTransformTracker m_Tracker;
#pragma warning restore 649
protected ContentSizeFitter()
{}
protected override void OnEnable()
{
base.OnEnable();
SetDirty();
}
protected override void OnDisable()
{
m_Tracker.Clear();
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
base.OnDisable();
}
protected override void OnRectTransformDimensionsChange()
{
SetDirty();
}
private void HandleSelfFittingAlongAxis(int axis)
{
FitMode fitting = (axis == 0 ? horizontalFit : verticalFit);
if (fitting == FitMode.Unconstrained)
{
// Keep a reference to the tracked transform, but don't control its properties:
m_Tracker.Add(this, rectTransform, DrivenTransformProperties.None);
return;
}
m_Tracker.Add(this, rectTransform, (axis == 0 ? DrivenTransformProperties.SizeDeltaX : DrivenTransformProperties.SizeDeltaY));
// Set size to min or preferred size
if (fitting == FitMode.MinSize)
rectTransform.SetSizeWithCurrentAnchors((RectTransform.Axis)axis, LayoutUtility.GetMinSize(m_Rect, axis));
else
rectTransform.SetSizeWithCurrentAnchors((RectTransform.Axis)axis, LayoutUtility.GetPreferredSize(m_Rect, axis));
}
/// <summary>
/// Calculate and apply the horizontal component of the size to the RectTransform
/// </summary>
public virtual void SetLayoutHorizontal()
{
m_Tracker.Clear();
HandleSelfFittingAlongAxis(0);
}
/// <summary>
/// Calculate and apply the vertical component of the size to the RectTransform
/// </summary>
public virtual void SetLayoutVertical()
{
HandleSelfFittingAlongAxis(1);
}
protected void SetDirty()
{
if (!IsActive())
return;
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
}
#if UNITY_EDITOR
protected override void OnValidate()
{
SetDirty();
}
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3245ec927659c4140ac4f8d17403cc18
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,354 @@
using UnityEngine;
using System.Collections.Generic;
namespace UnityEngine.UI
{
[AddComponentMenu("Layout/Grid Layout Group", 152)]
/// <summary>
/// Layout class to arrange child elements in a grid format.
/// </summary>
/// <remarks>
/// The GridLayoutGroup component is used to layout child layout elements in a uniform grid where all cells have the same size. The size and the spacing between cells is controlled by the GridLayoutGroup itself. The children have no influence on their sizes.
/// </remarks>
public class GridLayoutGroup : LayoutGroup
{
/// <summary>
/// Which corner is the starting corner for the grid.
/// </summary>
public enum Corner
{
/// <summary>
/// Upper Left corner.
/// </summary>
UpperLeft = 0,
/// <summary>
/// Upper Right corner.
/// </summary>
UpperRight = 1,
/// <summary>
/// Lower Left corner.
/// </summary>
LowerLeft = 2,
/// <summary>
/// Lower Right corner.
/// </summary>
LowerRight = 3
}
/// <summary>
/// The grid axis we are looking at.
/// </summary>
/// <remarks>
/// As the storage is a [][] we make access easier by passing a axis.
/// </remarks>
public enum Axis
{
/// <summary>
/// Horizontal axis
/// </summary>
Horizontal = 0,
/// <summary>
/// Vertical axis.
/// </summary>
Vertical = 1
}
/// <summary>
/// Constraint type on either the number of columns or rows.
/// </summary>
public enum Constraint
{
/// <summary>
/// Don't constrain the number of rows or columns.
/// </summary>
Flexible = 0,
/// <summary>
/// Constrain the number of columns to a specified number.
/// </summary>
FixedColumnCount = 1,
/// <summary>
/// Constraint the number of rows to a specified number.
/// </summary>
FixedRowCount = 2
}
[SerializeField] protected Corner m_StartCorner = Corner.UpperLeft;
/// <summary>
/// Which corner should the first cell be placed in?
/// </summary>
public Corner startCorner { get { return m_StartCorner; } set { SetProperty(ref m_StartCorner, value); } }
[SerializeField] protected Axis m_StartAxis = Axis.Horizontal;
/// <summary>
/// Which axis should cells be placed along first
/// </summary>
/// <remarks>
/// When startAxis is set to horizontal, an entire row will be filled out before proceeding to the next row. When set to vertical, an entire column will be filled out before proceeding to the next column.
/// </remarks>
public Axis startAxis { get { return m_StartAxis; } set { SetProperty(ref m_StartAxis, value); } }
[SerializeField] protected Vector2 m_CellSize = new Vector2(100, 100);
/// <summary>
/// The size to use for each cell in the grid.
/// </summary>
public Vector2 cellSize { get { return m_CellSize; } set { SetProperty(ref m_CellSize, value); } }
[SerializeField] protected Vector2 m_Spacing = Vector2.zero;
/// <summary>
/// The spacing to use between layout elements in the grid on both axises.
/// </summary>
public Vector2 spacing { get { return m_Spacing; } set { SetProperty(ref m_Spacing, value); } }
[SerializeField] protected Constraint m_Constraint = Constraint.Flexible;
/// <summary>
/// Which constraint to use for the GridLayoutGroup.
/// </summary>
/// <remarks>
/// Specifying a constraint can make the GridLayoutGroup work better in conjunction with a [[ContentSizeFitter]] component. When GridLayoutGroup is used on a RectTransform with a manually specified size, there's no need to specify a constraint.
/// </remarks>
public Constraint constraint { get { return m_Constraint; } set { SetProperty(ref m_Constraint, value); } }
[SerializeField] protected int m_ConstraintCount = 2;
/// <summary>
/// How many cells there should be along the constrained axis.
/// </summary>
public int constraintCount { get { return m_ConstraintCount; } set { SetProperty(ref m_ConstraintCount, Mathf.Max(1, value)); } }
protected GridLayoutGroup()
{}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
constraintCount = constraintCount;
}
#endif
/// <summary>
/// Called by the layout system to calculate the horizontal layout size.
/// Also see ILayoutElement
/// </summary>
public override void CalculateLayoutInputHorizontal()
{
base.CalculateLayoutInputHorizontal();
int minColumns = 0;
int preferredColumns = 0;
if (m_Constraint == Constraint.FixedColumnCount)
{
minColumns = preferredColumns = m_ConstraintCount;
}
else if (m_Constraint == Constraint.FixedRowCount)
{
minColumns = preferredColumns = Mathf.CeilToInt(rectChildren.Count / (float)m_ConstraintCount - 0.001f);
}
else
{
minColumns = 1;
preferredColumns = Mathf.CeilToInt(Mathf.Sqrt(rectChildren.Count));
}
SetLayoutInputForAxis(
padding.horizontal + (cellSize.x + spacing.x) * minColumns - spacing.x,
padding.horizontal + (cellSize.x + spacing.x) * preferredColumns - spacing.x,
-1, 0);
}
/// <summary>
/// Called by the layout system to calculate the vertical layout size.
/// Also see ILayoutElement
/// </summary>
public override void CalculateLayoutInputVertical()
{
int minRows = 0;
if (m_Constraint == Constraint.FixedColumnCount)
{
minRows = Mathf.CeilToInt(rectChildren.Count / (float)m_ConstraintCount - 0.001f);
}
else if (m_Constraint == Constraint.FixedRowCount)
{
minRows = m_ConstraintCount;
}
else
{
float width = rectTransform.rect.width;
int cellCountX = Mathf.Max(1, Mathf.FloorToInt((width - padding.horizontal + spacing.x + 0.001f) / (cellSize.x + spacing.x)));
minRows = Mathf.CeilToInt(rectChildren.Count / (float)cellCountX);
}
float minSpace = padding.vertical + (cellSize.y + spacing.y) * minRows - spacing.y;
SetLayoutInputForAxis(minSpace, minSpace, -1, 1);
}
/// <summary>
/// Called by the layout system
/// Also see ILayoutElement
/// </summary>
public override void SetLayoutHorizontal()
{
SetCellsAlongAxis(0);
}
/// <summary>
/// Called by the layout system
/// Also see ILayoutElement
/// </summary>
public override void SetLayoutVertical()
{
SetCellsAlongAxis(1);
}
private void SetCellsAlongAxis(int axis)
{
// Normally a Layout Controller should only set horizontal values when invoked for the horizontal axis
// and only vertical values when invoked for the vertical axis.
// However, in this case we set both the horizontal and vertical position when invoked for the vertical axis.
// Since we only set the horizontal position and not the size, it shouldn't affect children's layout,
// and thus shouldn't break the rule that all horizontal layout must be calculated before all vertical layout.
var rectChildrenCount = rectChildren.Count;
if (axis == 0)
{
// Only set the sizes when invoked for horizontal axis, not the positions.
for (int i = 0; i < rectChildrenCount; i++)
{
RectTransform rect = rectChildren[i];
m_Tracker.Add(this, rect,
DrivenTransformProperties.Anchors |
DrivenTransformProperties.AnchoredPosition |
DrivenTransformProperties.SizeDelta);
rect.anchorMin = Vector2.up;
rect.anchorMax = Vector2.up;
rect.sizeDelta = cellSize;
}
return;
}
float width = rectTransform.rect.size.x;
float height = rectTransform.rect.size.y;
int cellCountX = 1;
int cellCountY = 1;
if (m_Constraint == Constraint.FixedColumnCount)
{
cellCountX = m_ConstraintCount;
if (rectChildrenCount > cellCountX)
cellCountY = rectChildrenCount / cellCountX + (rectChildrenCount % cellCountX > 0 ? 1 : 0);
}
else if (m_Constraint == Constraint.FixedRowCount)
{
cellCountY = m_ConstraintCount;
if (rectChildrenCount > cellCountY)
cellCountX = rectChildrenCount / cellCountY + (rectChildrenCount % cellCountY > 0 ? 1 : 0);
}
else
{
if (cellSize.x + spacing.x <= 0)
cellCountX = int.MaxValue;
else
cellCountX = Mathf.Max(1, Mathf.FloorToInt((width - padding.horizontal + spacing.x + 0.001f) / (cellSize.x + spacing.x)));
if (cellSize.y + spacing.y <= 0)
cellCountY = int.MaxValue;
else
cellCountY = Mathf.Max(1, Mathf.FloorToInt((height - padding.vertical + spacing.y + 0.001f) / (cellSize.y + spacing.y)));
}
int cornerX = (int)startCorner % 2;
int cornerY = (int)startCorner / 2;
int cellsPerMainAxis, actualCellCountX, actualCellCountY;
if (startAxis == Axis.Horizontal)
{
cellsPerMainAxis = cellCountX;
actualCellCountX = Mathf.Clamp(cellCountX, 1, rectChildrenCount);
if (m_Constraint == Constraint.FixedRowCount)
actualCellCountY = Mathf.Min(cellCountY, rectChildrenCount);
else
actualCellCountY = Mathf.Clamp(cellCountY, 1, Mathf.CeilToInt(rectChildrenCount / (float)cellsPerMainAxis));
}
else
{
cellsPerMainAxis = cellCountY;
actualCellCountY = Mathf.Clamp(cellCountY, 1, rectChildrenCount);
if (m_Constraint == Constraint.FixedColumnCount)
actualCellCountX = Mathf.Min(cellCountX, rectChildrenCount);
else
actualCellCountX = Mathf.Clamp(cellCountX, 1, Mathf.CeilToInt(rectChildrenCount / (float)cellsPerMainAxis));
}
Vector2 requiredSpace = new Vector2(
actualCellCountX * cellSize.x + (actualCellCountX - 1) * spacing.x,
actualCellCountY * cellSize.y + (actualCellCountY - 1) * spacing.y
);
Vector2 startOffset = new Vector2(
GetStartOffset(0, requiredSpace.x),
GetStartOffset(1, requiredSpace.y)
);
// Fixes case 1345471 - Makes sure the constraint column / row amount is always respected
int childrenToMove = 0;
if (rectChildrenCount > m_ConstraintCount && Mathf.CeilToInt((float)rectChildrenCount / (float)cellsPerMainAxis) < m_ConstraintCount)
{
childrenToMove = m_ConstraintCount - Mathf.CeilToInt((float)rectChildrenCount / (float)cellsPerMainAxis);
childrenToMove += Mathf.FloorToInt((float)childrenToMove / ((float)cellsPerMainAxis - 1));
if (rectChildrenCount % cellsPerMainAxis == 1)
childrenToMove += 1;
}
for (int i = 0; i < rectChildrenCount; i++)
{
int positionX;
int positionY;
if (startAxis == Axis.Horizontal)
{
if (m_Constraint == Constraint.FixedRowCount && rectChildrenCount - i <= childrenToMove)
{
positionX = 0;
positionY = m_ConstraintCount - (rectChildrenCount - i);
}
else
{
positionX = i % cellsPerMainAxis;
positionY = i / cellsPerMainAxis;
}
}
else
{
if (m_Constraint == Constraint.FixedColumnCount && rectChildrenCount - i <= childrenToMove)
{
positionX = m_ConstraintCount - (rectChildrenCount - i);
positionY = 0;
}
else
{
positionX = i / cellsPerMainAxis;
positionY = i % cellsPerMainAxis;
}
}
if (cornerX == 1)
positionX = actualCellCountX - 1 - positionX;
if (cornerY == 1)
positionY = actualCellCountY - 1 - positionY;
SetChildAlongAxis(rectChildren[i], 0, startOffset.x + (cellSize[0] + spacing[0]) * positionX, cellSize[0]);
SetChildAlongAxis(rectChildren[i], 1, startOffset.y + (cellSize[1] + spacing[1]) * positionY, cellSize[1]);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8a8695521f0d02e499659fee002a26c2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,45 @@
namespace UnityEngine.UI
{
[AddComponentMenu("Layout/Horizontal Layout Group", 150)]
/// <summary>
/// Layout class for arranging child elements side by side.
/// </summary>
public class HorizontalLayoutGroup : HorizontalOrVerticalLayoutGroup
{
protected HorizontalLayoutGroup()
{}
/// <summary>
/// Called by the layout system. Also see ILayoutElement
/// </summary>
public override void CalculateLayoutInputHorizontal()
{
base.CalculateLayoutInputHorizontal();
CalcAlongAxis(0, false);
}
/// <summary>
/// Called by the layout system. Also see ILayoutElement
/// </summary>
public override void CalculateLayoutInputVertical()
{
CalcAlongAxis(1, false);
}
/// <summary>
/// Called by the layout system. Also see ILayoutElement
/// </summary>
public override void SetLayoutHorizontal()
{
SetChildrenAlongAxis(0, false);
}
/// <summary>
/// Called by the layout system. Also see ILayoutElement
/// </summary>
public override void SetLayoutVertical()
{
SetChildrenAlongAxis(1, false);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 30649d3a9faa99c48a7b1166b86bf2a0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,295 @@
using System.Collections.Generic;
namespace UnityEngine.UI
{
/// <summary>
/// Abstract base class for HorizontalLayoutGroup and VerticalLayoutGroup to generalize common functionality.
/// </summary>
///
[ExecuteAlways]
public abstract class HorizontalOrVerticalLayoutGroup : LayoutGroup
{
[SerializeField] protected float m_Spacing = 0;
/// <summary>
/// The spacing to use between layout elements in the layout group.
/// </summary>
public float spacing { get { return m_Spacing; } set { SetProperty(ref m_Spacing, value); } }
[SerializeField] protected bool m_ChildForceExpandWidth = true;
/// <summary>
/// Whether to force the children to expand to fill additional available horizontal space.
/// </summary>
public bool childForceExpandWidth { get { return m_ChildForceExpandWidth; } set { SetProperty(ref m_ChildForceExpandWidth, value); } }
[SerializeField] protected bool m_ChildForceExpandHeight = true;
/// <summary>
/// Whether to force the children to expand to fill additional available vertical space.
/// </summary>
public bool childForceExpandHeight { get { return m_ChildForceExpandHeight; } set { SetProperty(ref m_ChildForceExpandHeight, value); } }
[SerializeField] protected bool m_ChildControlWidth = true;
/// <summary>
/// Returns true if the Layout Group controls the widths of its children. Returns false if children control their own widths.
/// </summary>
/// <remarks>
/// If set to false, the layout group will only affect the positions of the children while leaving the widths untouched. The widths of the children can be set via the respective RectTransforms in this case.
///
/// If set to true, the widths of the children are automatically driven by the layout group according to their respective minimum, preferred, and flexible widths. This is useful if the widths of the children should change depending on how much space is available.In this case the width of each child cannot be set manually in the RectTransform, but the minimum, preferred and flexible width for each child can be controlled by adding a LayoutElement component to it.
/// </remarks>
public bool childControlWidth { get { return m_ChildControlWidth; } set { SetProperty(ref m_ChildControlWidth, value); } }
[SerializeField] protected bool m_ChildControlHeight = true;
/// <summary>
/// Returns true if the Layout Group controls the heights of its children. Returns false if children control their own heights.
/// </summary>
/// <remarks>
/// If set to false, the layout group will only affect the positions of the children while leaving the heights untouched. The heights of the children can be set via the respective RectTransforms in this case.
///
/// If set to true, the heights of the children are automatically driven by the layout group according to their respective minimum, preferred, and flexible heights. This is useful if the heights of the children should change depending on how much space is available.In this case the height of each child cannot be set manually in the RectTransform, but the minimum, preferred and flexible height for each child can be controlled by adding a LayoutElement component to it.
/// </remarks>
public bool childControlHeight { get { return m_ChildControlHeight; } set { SetProperty(ref m_ChildControlHeight, value); } }
[SerializeField] protected bool m_ChildScaleWidth = false;
/// <summary>
/// Whether to use the x scale of each child when calculating its width.
/// </summary>
public bool childScaleWidth { get { return m_ChildScaleWidth; } set { SetProperty(ref m_ChildScaleWidth, value); } }
[SerializeField] protected bool m_ChildScaleHeight = false;
/// <summary>
/// Whether to use the y scale of each child when calculating its height.
/// </summary>
public bool childScaleHeight { get { return m_ChildScaleHeight; } set { SetProperty(ref m_ChildScaleHeight, value); } }
/// <summary>
/// Whether the order of children objects should be sorted in reverse.
/// </summary>
/// <remarks>
/// If False the first child object will be positioned first.
/// If True the last child object will be positioned first.
/// </remarks>
public bool reverseArrangement { get { return m_ReverseArrangement; } set { SetProperty(ref m_ReverseArrangement, value); } }
[SerializeField] protected bool m_ReverseArrangement = false;
/// <summary>
/// Calculate the layout element properties for this layout element along the given axis.
/// </summary>
/// <param name="axis">The axis to calculate for. 0 is horizontal and 1 is vertical.</param>
/// <param name="isVertical">Is this group a vertical group?</param>
protected void CalcAlongAxis(int axis, bool isVertical)
{
float combinedPadding = (axis == 0 ? padding.horizontal : padding.vertical);
bool controlSize = (axis == 0 ? m_ChildControlWidth : m_ChildControlHeight);
bool useScale = (axis == 0 ? m_ChildScaleWidth : m_ChildScaleHeight);
bool childForceExpandSize = (axis == 0 ? m_ChildForceExpandWidth : m_ChildForceExpandHeight);
float totalMin = combinedPadding;
float totalPreferred = combinedPadding;
float totalFlexible = 0;
bool alongOtherAxis = (isVertical ^ (axis == 1));
var rectChildrenCount = rectChildren.Count;
for (int i = 0; i < rectChildrenCount; i++)
{
RectTransform child = rectChildren[i];
float min, preferred, flexible;
GetChildSizes(child, axis, controlSize, childForceExpandSize, out min, out preferred, out flexible);
if (useScale)
{
float scaleFactor = child.localScale[axis];
min *= scaleFactor;
preferred *= scaleFactor;
flexible *= scaleFactor;
}
if (alongOtherAxis)
{
totalMin = Mathf.Max(min + combinedPadding, totalMin);
totalPreferred = Mathf.Max(preferred + combinedPadding, totalPreferred);
totalFlexible = Mathf.Max(flexible, totalFlexible);
}
else
{
totalMin += min + spacing;
totalPreferred += preferred + spacing;
// Increment flexible size with element's flexible size.
totalFlexible += flexible;
}
}
if (!alongOtherAxis && rectChildren.Count > 0)
{
totalMin -= spacing;
totalPreferred -= spacing;
}
totalPreferred = Mathf.Max(totalMin, totalPreferred);
SetLayoutInputForAxis(totalMin, totalPreferred, totalFlexible, axis);
}
/// <summary>
/// Set the positions and sizes of the child layout elements for the given axis.
/// </summary>
/// <param name="axis">The axis to handle. 0 is horizontal and 1 is vertical.</param>
/// <param name="isVertical">Is this group a vertical group?</param>
protected void SetChildrenAlongAxis(int axis, bool isVertical)
{
float size = rectTransform.rect.size[axis];
bool controlSize = (axis == 0 ? m_ChildControlWidth : m_ChildControlHeight);
bool useScale = (axis == 0 ? m_ChildScaleWidth : m_ChildScaleHeight);
bool childForceExpandSize = (axis == 0 ? m_ChildForceExpandWidth : m_ChildForceExpandHeight);
float alignmentOnAxis = GetAlignmentOnAxis(axis);
bool alongOtherAxis = (isVertical ^ (axis == 1));
int startIndex = m_ReverseArrangement ? rectChildren.Count - 1 : 0;
int endIndex = m_ReverseArrangement ? 0 : rectChildren.Count;
int increment = m_ReverseArrangement ? -1 : 1;
if (alongOtherAxis)
{
float innerSize = size - (axis == 0 ? padding.horizontal : padding.vertical);
for (int i = startIndex; m_ReverseArrangement ? i >= endIndex : i < endIndex; i += increment)
{
RectTransform child = rectChildren[i];
float min, preferred, flexible;
GetChildSizes(child, axis, controlSize, childForceExpandSize, out min, out preferred, out flexible);
float scaleFactor = useScale ? child.localScale[axis] : 1f;
float requiredSpace = Mathf.Clamp(innerSize, min, flexible > 0 ? size : preferred);
float startOffset = GetStartOffset(axis, requiredSpace * scaleFactor);
if (controlSize)
{
SetChildAlongAxisWithScale(child, axis, startOffset, requiredSpace, scaleFactor);
}
else
{
float offsetInCell = (requiredSpace - child.sizeDelta[axis]) * alignmentOnAxis;
SetChildAlongAxisWithScale(child, axis, startOffset + offsetInCell, scaleFactor);
}
}
}
else
{
float pos = (axis == 0 ? padding.left : padding.top);
float itemFlexibleMultiplier = 0;
float surplusSpace = size - GetTotalPreferredSize(axis);
if (surplusSpace > 0)
{
if (GetTotalFlexibleSize(axis) == 0)
pos = GetStartOffset(axis, GetTotalPreferredSize(axis) - (axis == 0 ? padding.horizontal : padding.vertical));
else if (GetTotalFlexibleSize(axis) > 0)
itemFlexibleMultiplier = surplusSpace / GetTotalFlexibleSize(axis);
}
float minMaxLerp = 0;
if (GetTotalMinSize(axis) != GetTotalPreferredSize(axis))
minMaxLerp = Mathf.Clamp01((size - GetTotalMinSize(axis)) / (GetTotalPreferredSize(axis) - GetTotalMinSize(axis)));
for (int i = startIndex; m_ReverseArrangement ? i >= endIndex : i < endIndex; i += increment)
{
RectTransform child = rectChildren[i];
float min, preferred, flexible;
GetChildSizes(child, axis, controlSize, childForceExpandSize, out min, out preferred, out flexible);
float scaleFactor = useScale ? child.localScale[axis] : 1f;
float childSize = Mathf.Lerp(min, preferred, minMaxLerp);
childSize += flexible * itemFlexibleMultiplier;
if (controlSize)
{
SetChildAlongAxisWithScale(child, axis, pos, childSize, scaleFactor);
}
else
{
float offsetInCell = (childSize - child.sizeDelta[axis]) * alignmentOnAxis;
SetChildAlongAxisWithScale(child, axis, pos + offsetInCell, scaleFactor);
}
pos += childSize * scaleFactor + spacing;
}
}
}
private void GetChildSizes(RectTransform child, int axis, bool controlSize, bool childForceExpand,
out float min, out float preferred, out float flexible)
{
if (!controlSize)
{
min = child.sizeDelta[axis];
preferred = min;
flexible = 0;
}
else
{
min = LayoutUtility.GetMinSize(child, axis);
preferred = LayoutUtility.GetPreferredSize(child, axis);
flexible = LayoutUtility.GetFlexibleSize(child, axis);
}
if (childForceExpand)
flexible = Mathf.Max(flexible, 1);
}
#if UNITY_EDITOR
protected override void Reset()
{
base.Reset();
// For new added components we want these to be set to false,
// so that the user's sizes won't be overwritten before they
// have a chance to turn these settings off.
// However, for existing components that were added before this
// feature was introduced, we want it to be on be default for
// backwardds compatibility.
// Hence their default value is on, but we set to off in reset.
m_ChildControlWidth = false;
m_ChildControlHeight = false;
}
private int m_Capacity = 10;
private Vector2[] m_Sizes = new Vector2[10];
protected virtual void Update()
{
if (Application.isPlaying)
return;
int count = transform.childCount;
if (count > m_Capacity)
{
if (count > m_Capacity * 2)
m_Capacity = count;
else
m_Capacity *= 2;
m_Sizes = new Vector2[m_Capacity];
}
// If children size change in editor, update layout (case 945680 - Child GameObjects in a Horizontal/Vertical Layout Group don't display their correct position in the Editor)
bool dirty = false;
for (int i = 0; i < count; i++)
{
RectTransform t = transform.GetChild(i) as RectTransform;
if (t != null && t.sizeDelta != m_Sizes[i])
{
dirty = true;
m_Sizes[i] = t.sizeDelta;
}
}
if (dirty)
LayoutRebuilder.MarkLayoutForRebuild(transform as RectTransform);
}
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a5c214e5846a99242b348c37e49b2f59
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,268 @@
using UnityEngine;
using System.Collections;
namespace UnityEngine.UI
{
/// <summary>
/// A component is treated as a layout element by the auto layout system if it implements ILayoutElement.
/// </summary>
/// <remarks>
/// The layout system will invoke CalculateLayoutInputHorizontal before querying minWidth, preferredWidth, and flexibleWidth. It can potentially save performance if these properties are cached when CalculateLayoutInputHorizontal is invoked, so they don't need to be recalculated every time the properties are queried.
///
/// The layout system will invoke CalculateLayoutInputVertical before querying minHeight, preferredHeight, and flexibleHeight.It can potentially save performance if these properties are cached when CalculateLayoutInputVertical is invoked, so they don't need to be recalculated every time the properties are queried.
///
/// The minWidth, preferredWidth, and flexibleWidth properties should not rely on any properties of the RectTransform of the layout element, otherwise the behavior will be non-deterministic.
/// The minHeight, preferredHeight, and flexibleHeight properties may rely on horizontal aspects of the RectTransform, such as the width or the X component of the position.
/// Any properties of the RectTransforms on child layout elements may always be relied on.
/// </remarks>
public interface ILayoutElement
{
/// <summary>
/// After this method is invoked, layout horizontal input properties should return up-to-date values.
/// Children will already have up-to-date layout horizontal inputs when this methods is called.
/// </summary>
void CalculateLayoutInputHorizontal();
/// <summary>
///After this method is invoked, layout vertical input properties should return up-to-date values.
///Children will already have up-to-date layout vertical inputs when this methods is called.
/// </summary>
void CalculateLayoutInputVertical();
/// <summary>
/// The minimum width this layout element may be allocated.
/// </summary>
float minWidth { get; }
/// <summary>
/// The preferred width this layout element should be allocated if there is sufficient space.
/// </summary>
/// <remarks>
/// PreferredWidth can be set to -1 to remove the size.
/// </remarks>
float preferredWidth { get; }
/// <summary>
/// The extra relative width this layout element should be allocated if there is additional available space.
/// </summary>
/// <remarks>
/// Setting preferredWidth to -1 removed the preferredWidth.
/// </remarks>
/// <example>
///<code>
///<![CDATA[
///using UnityEngine;
///using System.Collections;
///using UnityEngine.UI; // Required when using UI elements.
///
///public class ExampleClass : MonoBehaviour
///{
/// public Transform MyContentPanel;
///
/// //Sets the flexible height on on all children in the content panel.
/// public void Start()
/// {
/// //Assign all the children of the content panel to an array.
/// LayoutElement[] myLayoutElements = MyContentPanel.GetComponentsInChildren<LayoutElement>();
///
/// //For each child in the array change its LayoutElement's flexible width to 200.
/// foreach (LayoutElement element in myLayoutElements)
/// {
/// element.flexibleWidth = 200f;
/// }
/// }
///}
///]]>
///</code>
///</example>
float flexibleWidth { get; }
/// <summary>
/// The minimum height this layout element may be allocated.
/// </summary>
float minHeight { get; }
/// <summary>
/// The preferred height this layout element should be allocated if there is sufficient space.
/// </summary>
/// <remarks>
/// PreferredHeight can be set to -1 to remove the size.
/// </remarks>
float preferredHeight { get; }
/// <summary>
/// The extra relative height this layout element should be allocated if there is additional available space.
/// </summary>
///<example>
///<code>
///<![CDATA[
///using UnityEngine;
///using System.Collections;
///using UnityEngine.UI; // Required when using UI elements.
///
///public class ExampleClass : MonoBehaviour
///{
/// public Transform MyContentPanel;
///
/// //Sets the flexible height on on all children in the content panel.
/// public void Start()
/// {
/// //Assign all the children of the content panel to an array.
/// LayoutElement[] myLayoutElements = MyContentPanel.GetComponentsInChildren<LayoutElement>();
///
/// //For each child in the array change its LayoutElement's flexible height to 100.
/// foreach (LayoutElement element in myLayoutElements)
/// {
/// element.flexibleHeight = 100f;
/// }
/// }
///}
///]]>
///</code>
///</example>
float flexibleHeight { get; }
/// <summary>
/// The layout priority of this component.
/// </summary>
/// <remarks>
/// If multiple components on the same GameObject implement the ILayoutElement interface, the values provided by components that return a higher priority value are given priority. However, values less than zero are ignored. This way a component can override only select properties by leaving the remaning values to be -1 or other values less than zero.
/// </remarks>
int layoutPriority { get; }
}
/// <summary>
/// Base interface to be implemented by components that control the layout of RectTransforms.
/// </summary>
/// <remarks>
/// If a component is driving its own RectTransform it should implement the interface [[ILayoutSelfController]].
/// If a component is driving the RectTransforms of its children, it should implement [[ILayoutGroup]].
///
/// The layout system will first invoke SetLayoutHorizontal and then SetLayoutVertical.
///
/// In the SetLayoutHorizontal call it is valid to call LayoutUtility.GetMinWidth, LayoutUtility.GetPreferredWidth, and LayoutUtility.GetFlexibleWidth on the RectTransform of itself or any of its children.
/// In the SetLayoutVertical call it is valid to call LayoutUtility.GetMinHeight, LayoutUtility.GetPreferredHeight, and LayoutUtility.GetFlexibleHeight on the RectTransform of itself or any of its children.
///
/// The component may use this information to determine the width and height to use for its own RectTransform or the RectTransforms of its children.
/// </remarks>
public interface ILayoutController
{
/// <summary>
/// Callback invoked by the auto layout system which handles horizontal aspects of the layout.
/// </summary>
void SetLayoutHorizontal();
/// <summary>
/// Callback invoked by the auto layout system which handles vertical aspects of the layout.
/// </summary>
void SetLayoutVertical();
}
/// <summary>
/// ILayoutGroup is an ILayoutController that should drive the RectTransforms of its children.
/// </summary>
/// <remarks>
/// ILayoutGroup derives from ILayoutController and requires the same members to be implemented.
/// </remarks>
public interface ILayoutGroup : ILayoutController
{
}
/// <summary>
/// ILayoutSelfController is an ILayoutController that should drive its own RectTransform.
/// </summary>
/// <remarks>
/// The iLayoutSelfController derives from the base controller [[ILayoutController]] and controls the layout of a RectTransform.
///
/// Use the ILayoutSelfController to manipulate a GameObjects own RectTransform component, which you attach in the Inspector.Use ILayoutGroup to manipulate RectTransforms belonging to the children of the GameObject.
///
/// Call ILayoutController.SetLayoutHorizontal to handle horizontal parts of the layout, and call ILayoutController.SetLayoutVertical to handle vertical parts.
/// You can change the height, width, position and rotation of the RectTransform.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// //This script shows how the GameObjects own RectTransforms can be changed.
/// //This creates a rectangle on the screen of the scale, positition and rotation you define in the Inspector.
/// //Make sure to set the X and Y scale to be more than 0 to see it
///
/// using UnityEngine;
/// using UnityEngine.UI;
/// using UnityEngine.EventSystems;
///
/// public class Example : UIBehaviour, ILayoutSelfController
/// {
/// //Fields in the inspector used to manipulate the RectTransform
/// public Vector3 m_Position;
/// public Vector3 m_Rotation;
/// public Vector2 m_Scale;
///
/// //This handles horizontal aspects of the layout (derived from ILayoutController)
/// public virtual void SetLayoutHorizontal()
/// {
/// //Move and Rotate the RectTransform appropriately
/// UpdateRectTransform();
/// }
///
/// //This handles vertical aspects of the layout
/// public virtual void SetLayoutVertical()
/// {
/// //Move and Rotate the RectTransform appropriately
/// UpdateRectTransform();
/// }
///
/// //This tells when there is a change in the inspector
/// #if UNITY_EDITOR
/// protected override void OnValidate()
/// {
/// Debug.Log("Validate");
/// //Update the RectTransform position, rotation and scale
/// UpdateRectTransform();
/// }
///
/// #endif
///
/// //This tells when there has been a change to the RectTransform's settings in the inspector
/// protected override void OnRectTransformDimensionsChange()
/// {
/// //Update the RectTransform position, rotation and scale
/// UpdateRectTransform();
/// }
///
/// void UpdateRectTransform()
/// {
/// //Fetch the RectTransform from the GameObject
/// RectTransform rectTransform = GetComponent<RectTransform>();
///
/// //Change the scale of the RectTransform using the fields in the inspector
/// rectTransform.localScale = new Vector3(m_Scale.x, m_Scale.y, 0);
///
/// //Change the position and rotation of the RectTransform
/// rectTransform.SetPositionAndRotation(m_Position, Quaternion.Euler(m_Rotation));
/// }
/// }
/// ]]>
///</code>
/// </example>
public interface ILayoutSelfController : ILayoutController
{
}
/// <summary>
/// A RectTransform will be ignored by the layout system if it has a component which implements ILayoutIgnorer.
/// </summary>
/// <remarks>
/// A components that implements ILayoutIgnorer can be used to make a parent layout group component not consider this RectTransform part of the group. The RectTransform can then be manually positioned despite being a child GameObject of a layout group.
/// </remarks>
public interface ILayoutIgnorer
{
/// <summary>
/// Should this RectTransform be ignored bvy the layout system?
/// </summary>
/// <remarks>
/// Setting this property to true will make a parent layout group component not consider this RectTransform part of the group. The RectTransform can then be manually positioned despite being a child GameObject of a layout group.
/// </remarks>
bool ignoreLayout { get; }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 368d3a0498e78014da578aa5f45e2797
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,228 @@
using UnityEngine.EventSystems;
namespace UnityEngine.UI
{
[AddComponentMenu("Layout/Layout Element", 140)]
[RequireComponent(typeof(RectTransform))]
[ExecuteAlways]
/// <summary>
/// Add this component to a GameObject to make it into a layout element or override values on an existing layout element.
/// </summary>
public class LayoutElement : UIBehaviour, ILayoutElement, ILayoutIgnorer
{
[SerializeField] private bool m_IgnoreLayout = false;
[SerializeField] private float m_MinWidth = -1;
[SerializeField] private float m_MinHeight = -1;
[SerializeField] private float m_PreferredWidth = -1;
[SerializeField] private float m_PreferredHeight = -1;
[SerializeField] private float m_FlexibleWidth = -1;
[SerializeField] private float m_FlexibleHeight = -1;
[SerializeField] private int m_LayoutPriority = 1;
/// <summary>
/// Should this RectTransform be ignored by the layout system?
/// </summary>
/// <remarks>
/// Setting this property to true will make a parent layout group component not consider this RectTransform part of the group. The RectTransform can then be manually positioned despite being a child GameObject of a layout group.
/// </remarks>
public virtual bool ignoreLayout { get { return m_IgnoreLayout; } set { if (SetPropertyUtility.SetStruct(ref m_IgnoreLayout, value)) SetDirty(); } }
public virtual void CalculateLayoutInputHorizontal() {}
public virtual void CalculateLayoutInputVertical() {}
/// <summary>
/// The minimum width this layout element may be allocated.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Transform MyContentPanel;
///
/// //Sets the flexible height on on all children in the content panel.
/// public void Start()
/// {
/// //Assign all the children of the content panel to an array.
/// LayoutElement[] myLayoutElements = MyContentPanel.GetComponentsInChildren<LayoutElement>();
///
/// //For each child in the array change its LayoutElement's minimum width size to 200.
/// foreach (LayoutElement element in myLayoutElements)
/// {
/// element.minWidth = 200f;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual float minWidth { get { return m_MinWidth; } set { if (SetPropertyUtility.SetStruct(ref m_MinWidth, value)) SetDirty(); } }
/// <summary>
/// The minimum height this layout element may be allocated.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Transform MyContentPanel;
///
/// //Sets the flexible height on on all children in the content panel.
/// public void Start()
/// {
/// //Assign all the children of the content panel to an array.
/// LayoutElement[] myLayoutElements = MyContentPanel.GetComponentsInChildren<LayoutElement>();
///
/// //For each child in the array change its LayoutElement's minimum height size to 64.
/// foreach (LayoutElement element in myLayoutElements)
/// {
/// element.minHeight = 64f;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual float minHeight { get { return m_MinHeight; } set { if (SetPropertyUtility.SetStruct(ref m_MinHeight, value)) SetDirty(); } }
/// <summary>
/// The preferred width this layout element should be allocated if there is sufficient space. The preferredWidth can be set to -1 to remove the size.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Transform MyContentPanel;
///
/// //Sets the flexible height on on all children in the content panel.
/// public void Start()
/// {
/// //Assign all the children of the content panel to an array.
/// LayoutElement[] myLayoutElements = MyContentPanel.GetComponentsInChildren<LayoutElement>();
///
/// //For each child in the array change its LayoutElement's preferred width size to 250.
/// foreach (LayoutElement element in myLayoutElements)
/// {
/// element.preferredWidth = 250f;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual float preferredWidth { get { return m_PreferredWidth; } set { if (SetPropertyUtility.SetStruct(ref m_PreferredWidth, value)) SetDirty(); } }
/// <summary>
/// The preferred height this layout element should be allocated if there is sufficient space.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Transform MyContentPanel;
///
/// //Sets the flexible height on on all children in the content panel.
/// public void Start()
/// {
/// //Assign all the children of the content panel to an array.
/// LayoutElement[] myLayoutElements = MyContentPanel.GetComponentsInChildren<LayoutElement>();
///
/// //For each child in the array change its LayoutElement's preferred height size to 100.
/// foreach (LayoutElement element in myLayoutElements)
/// {
/// element.preferredHeight = 100f;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual float preferredHeight { get { return m_PreferredHeight; } set { if (SetPropertyUtility.SetStruct(ref m_PreferredHeight, value)) SetDirty(); } }
/// <summary>
/// The extra relative width this layout element should be allocated if there is additional available space.
/// </summary>
public virtual float flexibleWidth { get { return m_FlexibleWidth; } set { if (SetPropertyUtility.SetStruct(ref m_FlexibleWidth, value)) SetDirty(); } }
/// <summary>
/// The extra relative height this layout element should be allocated if there is additional available space.
/// </summary>
public virtual float flexibleHeight { get { return m_FlexibleHeight; } set { if (SetPropertyUtility.SetStruct(ref m_FlexibleHeight, value)) SetDirty(); } }
/// <summary>
/// The Priority of layout this element has.
/// </summary>
public virtual int layoutPriority { get { return m_LayoutPriority; } set { if (SetPropertyUtility.SetStruct(ref m_LayoutPriority, value)) SetDirty(); } }
protected LayoutElement()
{}
protected override void OnEnable()
{
base.OnEnable();
SetDirty();
}
protected override void OnTransformParentChanged()
{
SetDirty();
}
protected override void OnDisable()
{
SetDirty();
base.OnDisable();
}
protected override void OnDidApplyAnimationProperties()
{
SetDirty();
}
protected override void OnBeforeTransformParentChanged()
{
SetDirty();
}
/// <summary>
/// Mark the LayoutElement as dirty.
/// </summary>
/// <remarks>
/// This will make the auto layout system process this element on the next layout pass. This method should be called by the LayoutElement whenever a change is made that potentially affects the layout.
/// </remarks>
protected void SetDirty()
{
if (!IsActive())
return;
LayoutRebuilder.MarkLayoutForRebuild(transform as RectTransform);
}
#if UNITY_EDITOR
protected override void OnValidate()
{
SetDirty();
}
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 306cc8c2b49d7114eaa3623786fc2126
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,381 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine.EventSystems;
using UnityEngine.Pool;
namespace UnityEngine.UI
{
[DisallowMultipleComponent]
[ExecuteAlways]
[RequireComponent(typeof(RectTransform))]
/// <summary>
/// Abstract base class to use for layout groups.
/// </summary>
public abstract class LayoutGroup : UIBehaviour, ILayoutElement, ILayoutGroup
{
[SerializeField] protected RectOffset m_Padding = new RectOffset();
/// <summary>
/// The padding to add around the child layout elements.
/// </summary>
public RectOffset padding { get { return m_Padding; } set { SetProperty(ref m_Padding, value); } }
[SerializeField] protected TextAnchor m_ChildAlignment = TextAnchor.UpperLeft;
/// <summary>
/// The alignment to use for the child layout elements in the layout group.
/// </summary>
/// <remarks>
/// If a layout element does not specify a flexible width or height, its child elements many not use the available space within the layout group. In this case, use the alignment settings to specify how to align child elements within their layout group.
/// </remarks>
public TextAnchor childAlignment { get { return m_ChildAlignment; } set { SetProperty(ref m_ChildAlignment, value); } }
[System.NonSerialized] private RectTransform m_Rect;
protected RectTransform rectTransform
{
get
{
if (m_Rect == null)
m_Rect = GetComponent<RectTransform>();
return m_Rect;
}
}
protected DrivenRectTransformTracker m_Tracker;
private Vector2 m_TotalMinSize = Vector2.zero;
private Vector2 m_TotalPreferredSize = Vector2.zero;
private Vector2 m_TotalFlexibleSize = Vector2.zero;
[System.NonSerialized] private List<RectTransform> m_RectChildren = new List<RectTransform>();
protected List<RectTransform> rectChildren { get { return m_RectChildren; } }
public virtual void CalculateLayoutInputHorizontal()
{
m_RectChildren.Clear();
var toIgnoreList = ListPool<Component>.Get();
for (int i = 0; i < rectTransform.childCount; i++)
{
var rect = rectTransform.GetChild(i) as RectTransform;
if (rect == null || !rect.gameObject.activeInHierarchy)
continue;
rect.GetComponents(typeof(ILayoutIgnorer), toIgnoreList);
if (toIgnoreList.Count == 0)
{
m_RectChildren.Add(rect);
continue;
}
for (int j = 0; j < toIgnoreList.Count; j++)
{
var ignorer = (ILayoutIgnorer)toIgnoreList[j];
if (!ignorer.ignoreLayout)
{
m_RectChildren.Add(rect);
break;
}
}
}
ListPool<Component>.Release(toIgnoreList);
m_Tracker.Clear();
}
public abstract void CalculateLayoutInputVertical();
/// <summary>
/// See LayoutElement.minWidth
/// </summary>
public virtual float minWidth { get { return GetTotalMinSize(0); } }
/// <summary>
/// See LayoutElement.preferredWidth
/// </summary>
public virtual float preferredWidth { get { return GetTotalPreferredSize(0); } }
/// <summary>
/// See LayoutElement.flexibleWidth
/// </summary>
public virtual float flexibleWidth { get { return GetTotalFlexibleSize(0); } }
/// <summary>
/// See LayoutElement.minHeight
/// </summary>
public virtual float minHeight { get { return GetTotalMinSize(1); } }
/// <summary>
/// See LayoutElement.preferredHeight
/// </summary>
public virtual float preferredHeight { get { return GetTotalPreferredSize(1); } }
/// <summary>
/// See LayoutElement.flexibleHeight
/// </summary>
public virtual float flexibleHeight { get { return GetTotalFlexibleSize(1); } }
/// <summary>
/// See LayoutElement.layoutPriority
/// </summary>
public virtual int layoutPriority { get { return 0; } }
// ILayoutController Interface
public abstract void SetLayoutHorizontal();
public abstract void SetLayoutVertical();
// Implementation
protected LayoutGroup()
{
if (m_Padding == null)
m_Padding = new RectOffset();
}
protected override void OnEnable()
{
base.OnEnable();
SetDirty();
}
protected override void OnDisable()
{
m_Tracker.Clear();
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
base.OnDisable();
}
/// <summary>
/// Callback for when properties have been changed by animation.
/// </summary>
protected override void OnDidApplyAnimationProperties()
{
SetDirty();
}
/// <summary>
/// The min size for the layout group on the given axis.
/// </summary>
/// <param name="axis">The axis index. 0 is horizontal and 1 is vertical.</param>
/// <returns>The min size</returns>
protected float GetTotalMinSize(int axis)
{
return m_TotalMinSize[axis];
}
/// <summary>
/// The preferred size for the layout group on the given axis.
/// </summary>
/// <param name="axis">The axis index. 0 is horizontal and 1 is vertical.</param>
/// <returns>The preferred size.</returns>
protected float GetTotalPreferredSize(int axis)
{
return m_TotalPreferredSize[axis];
}
/// <summary>
/// The flexible size for the layout group on the given axis.
/// </summary>
/// <param name="axis">The axis index. 0 is horizontal and 1 is vertical.</param>
/// <returns>The flexible size</returns>
protected float GetTotalFlexibleSize(int axis)
{
return m_TotalFlexibleSize[axis];
}
/// <summary>
/// Returns the calculated position of the first child layout element along the given axis.
/// </summary>
/// <param name="axis">The axis index. 0 is horizontal and 1 is vertical.</param>
/// <param name="requiredSpaceWithoutPadding">The total space required on the given axis for all the layout elements including spacing and excluding padding.</param>
/// <returns>The position of the first child along the given axis.</returns>
protected float GetStartOffset(int axis, float requiredSpaceWithoutPadding)
{
float requiredSpace = requiredSpaceWithoutPadding + (axis == 0 ? padding.horizontal : padding.vertical);
float availableSpace = rectTransform.rect.size[axis];
float surplusSpace = availableSpace - requiredSpace;
float alignmentOnAxis = GetAlignmentOnAxis(axis);
return (axis == 0 ? padding.left : padding.top) + surplusSpace * alignmentOnAxis;
}
/// <summary>
/// Returns the alignment on the specified axis as a fraction where 0 is left/top, 0.5 is middle, and 1 is right/bottom.
/// </summary>
/// <param name="axis">The axis to get alignment along. 0 is horizontal and 1 is vertical.</param>
/// <returns>The alignment as a fraction where 0 is left/top, 0.5 is middle, and 1 is right/bottom.</returns>
protected float GetAlignmentOnAxis(int axis)
{
if (axis == 0)
return ((int)childAlignment % 3) * 0.5f;
else
return ((int)childAlignment / 3) * 0.5f;
}
/// <summary>
/// Used to set the calculated layout properties for the given axis.
/// </summary>
/// <param name="totalMin">The min size for the layout group.</param>
/// <param name="totalPreferred">The preferred size for the layout group.</param>
/// <param name="totalFlexible">The flexible size for the layout group.</param>
/// <param name="axis">The axis to set sizes for. 0 is horizontal and 1 is vertical.</param>
protected void SetLayoutInputForAxis(float totalMin, float totalPreferred, float totalFlexible, int axis)
{
m_TotalMinSize[axis] = totalMin;
m_TotalPreferredSize[axis] = totalPreferred;
m_TotalFlexibleSize[axis] = totalFlexible;
}
/// <summary>
/// Set the position and size of a child layout element along the given axis.
/// </summary>
/// <param name="rect">The RectTransform of the child layout element.</param>
/// <param name="axis">The axis to set the position and size along. 0 is horizontal and 1 is vertical.</param>
/// <param name="pos">The position from the left side or top.</param>
protected void SetChildAlongAxis(RectTransform rect, int axis, float pos)
{
if (rect == null)
return;
SetChildAlongAxisWithScale(rect, axis, pos, 1.0f);
}
/// <summary>
/// Set the position and size of a child layout element along the given axis.
/// </summary>
/// <param name="rect">The RectTransform of the child layout element.</param>
/// <param name="axis">The axis to set the position and size along. 0 is horizontal and 1 is vertical.</param>
/// <param name="pos">The position from the left side or top.</param>
protected void SetChildAlongAxisWithScale(RectTransform rect, int axis, float pos, float scaleFactor)
{
if (rect == null)
return;
m_Tracker.Add(this, rect,
DrivenTransformProperties.Anchors |
(axis == 0 ? DrivenTransformProperties.AnchoredPositionX : DrivenTransformProperties.AnchoredPositionY));
// Inlined rect.SetInsetAndSizeFromParentEdge(...) and refactored code in order to multiply desired size by scaleFactor.
// sizeDelta must stay the same but the size used in the calculation of the position must be scaled by the scaleFactor.
rect.anchorMin = Vector2.up;
rect.anchorMax = Vector2.up;
Vector2 anchoredPosition = rect.anchoredPosition;
anchoredPosition[axis] = (axis == 0) ? (pos + rect.sizeDelta[axis] * rect.pivot[axis] * scaleFactor) : (-pos - rect.sizeDelta[axis] * (1f - rect.pivot[axis]) * scaleFactor);
rect.anchoredPosition = anchoredPosition;
}
/// <summary>
/// Set the position and size of a child layout element along the given axis.
/// </summary>
/// <param name="rect">The RectTransform of the child layout element.</param>
/// <param name="axis">The axis to set the position and size along. 0 is horizontal and 1 is vertical.</param>
/// <param name="pos">The position from the left side or top.</param>
/// <param name="size">The size.</param>
protected void SetChildAlongAxis(RectTransform rect, int axis, float pos, float size)
{
if (rect == null)
return;
SetChildAlongAxisWithScale(rect, axis, pos, size, 1.0f);
}
/// <summary>
/// Set the position and size of a child layout element along the given axis.
/// </summary>
/// <param name="rect">The RectTransform of the child layout element.</param>
/// <param name="axis">The axis to set the position and size along. 0 is horizontal and 1 is vertical.</param>
/// <param name="pos">The position from the left side or top.</param>
/// <param name="size">The size.</param>
protected void SetChildAlongAxisWithScale(RectTransform rect, int axis, float pos, float size, float scaleFactor)
{
if (rect == null)
return;
m_Tracker.Add(this, rect,
DrivenTransformProperties.Anchors |
(axis == 0 ?
(DrivenTransformProperties.AnchoredPositionX | DrivenTransformProperties.SizeDeltaX) :
(DrivenTransformProperties.AnchoredPositionY | DrivenTransformProperties.SizeDeltaY)
)
);
// Inlined rect.SetInsetAndSizeFromParentEdge(...) and refactored code in order to multiply desired size by scaleFactor.
// sizeDelta must stay the same but the size used in the calculation of the position must be scaled by the scaleFactor.
rect.anchorMin = Vector2.up;
rect.anchorMax = Vector2.up;
Vector2 sizeDelta = rect.sizeDelta;
sizeDelta[axis] = size;
rect.sizeDelta = sizeDelta;
Vector2 anchoredPosition = rect.anchoredPosition;
anchoredPosition[axis] = (axis == 0) ? (pos + size * rect.pivot[axis] * scaleFactor) : (-pos - size * (1f - rect.pivot[axis]) * scaleFactor);
rect.anchoredPosition = anchoredPosition;
}
private bool isRootLayoutGroup
{
get
{
Transform parent = transform.parent;
if (parent == null)
return true;
return transform.parent.GetComponent(typeof(ILayoutGroup)) == null;
}
}
protected override void OnRectTransformDimensionsChange()
{
base.OnRectTransformDimensionsChange();
if (isRootLayoutGroup)
SetDirty();
}
protected virtual void OnTransformChildrenChanged()
{
SetDirty();
}
/// <summary>
/// Helper method used to set a given property if it has changed.
/// </summary>
/// <param name="currentValue">A reference to the member value.</param>
/// <param name="newValue">The new value.</param>
protected void SetProperty<T>(ref T currentValue, T newValue)
{
if ((currentValue == null && newValue == null) || (currentValue != null && currentValue.Equals(newValue)))
return;
currentValue = newValue;
SetDirty();
}
/// <summary>
/// Mark the LayoutGroup as dirty.
/// </summary>
protected void SetDirty()
{
if (!IsActive())
return;
if (!CanvasUpdateRegistry.IsRebuildingLayout())
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
else
StartCoroutine(DelayedSetDirty(rectTransform));
}
IEnumerator DelayedSetDirty(RectTransform rectTransform)
{
yield return null;
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
}
#if UNITY_EDITOR
protected override void OnValidate()
{
SetDirty();
}
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cb7c939a806f03341b682c180dc13f08
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,267 @@
using System.Collections.Generic;
using UnityEngine.Events;
using UnityEngine.Pool;
namespace UnityEngine.UI
{
/// <summary>
/// Wrapper class for managing layout rebuilding of CanvasElement.
/// </summary>
public class LayoutRebuilder : ICanvasElement
{
private RectTransform m_ToRebuild;
//There are a few of reasons we need to cache the Hash fromt he transform:
// - This is a ValueType (struct) and .Net calculates Hash from the Value Type fields.
// - The key of a Dictionary should have a constant Hash value.
// - It's possible for the Transform to get nulled from the Native side.
// We use this struct with the IndexedSet container, which uses a dictionary as part of it's implementation
// So this struct gets used as a key to a dictionary, so we need to guarantee a constant Hash value.
private int m_CachedHashFromTransform;
static ObjectPool<LayoutRebuilder> s_Rebuilders = new ObjectPool<LayoutRebuilder>(() => new LayoutRebuilder(), null, x => x.Clear());
private void Initialize(RectTransform controller)
{
m_ToRebuild = controller;
m_CachedHashFromTransform = controller.GetHashCode();
}
private void Clear()
{
m_ToRebuild = null;
m_CachedHashFromTransform = 0;
}
static LayoutRebuilder()
{
RectTransform.reapplyDrivenProperties += ReapplyDrivenProperties;
}
static void ReapplyDrivenProperties(RectTransform driven)
{
MarkLayoutForRebuild(driven);
}
public Transform transform { get { return m_ToRebuild; }}
/// <summary>
/// Has the native representation of this LayoutRebuilder been destroyed?
/// </summary>
public bool IsDestroyed()
{
return m_ToRebuild == null;
}
static void StripDisabledBehavioursFromList(List<Component> components)
{
components.RemoveAll(e => e is Behaviour && !((Behaviour)e).isActiveAndEnabled);
}
/// <summary>
/// Forces an immediate rebuild of the layout element and child layout elements affected by the calculations.
/// </summary>
/// <param name="layoutRoot">The layout element to perform the layout rebuild on.</param>
/// <remarks>
/// Normal use of the layout system should not use this method. Instead MarkLayoutForRebuild should be used instead, which triggers a delayed layout rebuild during the next layout pass. The delayed rebuild automatically handles objects in the entire layout hierarchy in the correct order, and prevents multiple recalculations for the same layout elements.
/// However, for special layout calculation needs, ::ref::ForceRebuildLayoutImmediate can be used to get the layout of a sub-tree resolved immediately. This can even be done from inside layout calculation methods such as ILayoutController.SetLayoutHorizontal orILayoutController.SetLayoutVertical. Usage should be restricted to cases where multiple layout passes are unavaoidable despite the extra cost in performance.
/// </remarks>
public static void ForceRebuildLayoutImmediate(RectTransform layoutRoot)
{
var rebuilder = s_Rebuilders.Get();
rebuilder.Initialize(layoutRoot);
rebuilder.Rebuild(CanvasUpdate.Layout);
s_Rebuilders.Release(rebuilder);
}
public void Rebuild(CanvasUpdate executing)
{
switch (executing)
{
case CanvasUpdate.Layout:
// It's unfortunate that we'll perform the same GetComponents querys for the tree 2 times,
// but each tree have to be fully iterated before going to the next action,
// so reusing the results would entail storing results in a Dictionary or similar,
// which is probably a bigger overhead than performing GetComponents multiple times.
PerformLayoutCalculation(m_ToRebuild, e => (e as ILayoutElement).CalculateLayoutInputHorizontal());
PerformLayoutControl(m_ToRebuild, e => (e as ILayoutController).SetLayoutHorizontal());
PerformLayoutCalculation(m_ToRebuild, e => (e as ILayoutElement).CalculateLayoutInputVertical());
PerformLayoutControl(m_ToRebuild, e => (e as ILayoutController).SetLayoutVertical());
break;
}
}
private void PerformLayoutControl(RectTransform rect, UnityAction<Component> action)
{
if (rect == null)
return;
var components = ListPool<Component>.Get();
rect.GetComponents(typeof(ILayoutController), components);
StripDisabledBehavioursFromList(components);
// If there are no controllers on this rect we can skip this entire sub-tree
// We don't need to consider controllers on children deeper in the sub-tree either,
// since they will be their own roots.
if (components.Count > 0)
{
// Layout control needs to executed top down with parents being done before their children,
// because the children rely on the sizes of the parents.
// First call layout controllers that may change their own RectTransform
for (int i = 0; i < components.Count; i++)
if (components[i] is ILayoutSelfController)
action(components[i]);
// Then call the remaining, such as layout groups that change their children, taking their own RectTransform size into account.
for (int i = 0; i < components.Count; i++)
if (!(components[i] is ILayoutSelfController))
{
var scrollRect = components[i];
if (scrollRect && scrollRect is UnityEngine.UI.ScrollRect)
{
if (((UnityEngine.UI.ScrollRect)scrollRect).content != rect)
action(components[i]);
}
else
{
action(components[i]);
}
}
for (int i = 0; i < rect.childCount; i++)
PerformLayoutControl(rect.GetChild(i) as RectTransform, action);
}
ListPool<Component>.Release(components);
}
private void PerformLayoutCalculation(RectTransform rect, UnityAction<Component> action)
{
if (rect == null)
return;
var components = ListPool<Component>.Get();
rect.GetComponents(typeof(ILayoutElement), components);
StripDisabledBehavioursFromList(components);
// If there are no controllers on this rect we can skip this entire sub-tree
// We don't need to consider controllers on children deeper in the sub-tree either,
// since they will be their own roots.
if (components.Count > 0 || rect.TryGetComponent(typeof(ILayoutGroup), out _))
{
// Layout calculations needs to executed bottom up with children being done before their parents,
// because the parent calculated sizes rely on the sizes of the children.
for (int i = 0; i < rect.childCount; i++)
PerformLayoutCalculation(rect.GetChild(i) as RectTransform, action);
for (int i = 0; i < components.Count; i++)
action(components[i]);
}
ListPool<Component>.Release(components);
}
/// <summary>
/// Mark the given RectTransform as needing it's layout to be recalculated during the next layout pass.
/// </summary>
/// <param name="rect">Rect to rebuild.</param>
public static void MarkLayoutForRebuild(RectTransform rect)
{
if (rect == null || rect.gameObject == null)
return;
var comps = ListPool<Component>.Get();
bool validLayoutGroup = true;
RectTransform layoutRoot = rect;
var parent = layoutRoot.parent as RectTransform;
while (validLayoutGroup && !(parent == null || parent.gameObject == null))
{
validLayoutGroup = false;
parent.GetComponents(typeof(ILayoutGroup), comps);
for (int i = 0; i < comps.Count; ++i)
{
var cur = comps[i];
if (cur != null && cur is Behaviour && ((Behaviour)cur).isActiveAndEnabled)
{
validLayoutGroup = true;
layoutRoot = parent;
break;
}
}
parent = parent.parent as RectTransform;
}
// We know the layout root is valid if it's not the same as the rect,
// since we checked that above. But if they're the same we still need to check.
if (layoutRoot == rect && !ValidController(layoutRoot, comps))
{
ListPool<Component>.Release(comps);
return;
}
MarkLayoutRootForRebuild(layoutRoot);
ListPool<Component>.Release(comps);
}
private static bool ValidController(RectTransform layoutRoot, List<Component> comps)
{
if (layoutRoot == null || layoutRoot.gameObject == null)
return false;
layoutRoot.GetComponents(typeof(ILayoutController), comps);
for (int i = 0; i < comps.Count; ++i)
{
var cur = comps[i];
if (cur != null && cur is Behaviour && ((Behaviour)cur).isActiveAndEnabled)
{
return true;
}
}
return false;
}
private static void MarkLayoutRootForRebuild(RectTransform controller)
{
if (controller == null)
return;
var rebuilder = s_Rebuilders.Get();
rebuilder.Initialize(controller);
if (!CanvasUpdateRegistry.TryRegisterCanvasElementForLayoutRebuild(rebuilder))
s_Rebuilders.Release(rebuilder);
}
public void LayoutComplete()
{
s_Rebuilders.Release(this);
}
public void GraphicUpdateComplete()
{}
public override int GetHashCode()
{
return m_CachedHashFromTransform;
}
/// <summary>
/// Does the passed rebuilder point to the same CanvasElement.
/// </summary>
/// <param name="obj">The other object to compare</param>
/// <returns>Are they equal</returns>
public override bool Equals(object obj)
{
return obj.GetHashCode() == GetHashCode();
}
public override string ToString()
{
return "(Layout Rebuilder for) " + m_ToRebuild;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 006633c8a6f4ae94aa9babf72234e1a2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,187 @@
using UnityEngine.Pool;
namespace UnityEngine.UI
{
/// <summary>
/// Utility functions for querying layout elements for their minimum, preferred, and flexible sizes.
/// </summary>
public static class LayoutUtility
{
/// <summary>
/// Returns the minimum size of the layout element.
/// </summary>
/// <param name="rect">The RectTransform of the layout element to query.</param>
/// <param name="axis">The axis to query. This can be 0 or 1.</param>
/// <remarks>All components on the GameObject that implement the ILayoutElement are queried. The one with the highest priority which has a value for this setting is used. If multiple componets have this setting and have the same priority, the maximum value out of those is used.</remarks>
public static float GetMinSize(RectTransform rect, int axis)
{
return axis == 0 ? GetMinWidth(rect) : GetMinHeight(rect);
}
/// <summary>
/// Returns the preferred size of the layout element.
/// </summary>
/// <param name="rect">The RectTransform of the layout element to query.</param>
/// <param name="axis">The axis to query. This can be 0 or 1.</param>
/// <remarks>
/// All components on the GameObject that implement the ILayoutElement are queried. The one with the highest priority which has a value for this setting is used. If multiple componets have this setting and have the same priority, the maximum value out of those is used.
/// </remarks>
public static float GetPreferredSize(RectTransform rect, int axis)
{
return axis == 0 ? GetPreferredWidth(rect) : GetPreferredHeight(rect);
}
/// <summary>
/// Returns the flexible size of the layout element.
/// </summary>
/// <remarks>
/// All components on the GameObject that implement the ILayoutElement are queried. The one with the highest priority which has a value for this setting is used. If multiple componets have this setting and have the same priority, the maximum value out of those is used.
/// </remarks>
/// <param name="rect">The RectTransform of the layout element to query.</param>
/// <param name="axis">The axis to query. This can be 0 or 1.</param>
public static float GetFlexibleSize(RectTransform rect, int axis)
{
return axis == 0 ? GetFlexibleWidth(rect) : GetFlexibleHeight(rect);
}
/// <summary>
/// Returns the minimum width of the layout element.
/// </summary>
/// <param name="rect">The RectTransform of the layout element to query.</param>
/// <remarks>
/// All components on the GameObject that implement the ILayoutElement are queried. The one with the highest priority which has a value for this setting is used. If multiple componets have this setting and have the same priority, the maximum value out of those is used.
/// </remarks>
public static float GetMinWidth(RectTransform rect)
{
return GetLayoutProperty(rect, e => e.minWidth, 0);
}
/// <summary>
/// Returns the preferred width of the layout element.
/// </summary>
/// <param name="rect">The RectTransform of the layout element to query.</param>
/// <returns>
/// All components on the GameObject that implement the ILayoutElement are queried. The one with the highest priority which has a value for this setting is used. If multiple componets have this setting and have the same priority, the maximum value out of those is used.
/// </returns>
public static float GetPreferredWidth(RectTransform rect)
{
return Mathf.Max(GetLayoutProperty(rect, e => e.minWidth, 0), GetLayoutProperty(rect, e => e.preferredWidth, 0));
}
/// <summary>
/// Returns the flexible width of the layout element.
/// </summary>
/// <remarks>
/// All components on the GameObject that implement the ILayoutElement are queried. The one with the highest priority which has a value for this setting is used. If multiple componets have this setting and have the same priority, the maximum value out of those is used
/// </remarks>
/// <param name="rect">The RectTransform of the layout element to query.</param>
public static float GetFlexibleWidth(RectTransform rect)
{
return GetLayoutProperty(rect, e => e.flexibleWidth, 0);
}
/// <summary>
/// Returns the minimum height of the layout element.
/// </summary>
/// <param name="rect">The RectTransform of the layout element to query.</param>
/// <remarks>
/// All components on the GameObject that implement the ILayoutElement are queried. The one with the highest priority which has a value for this setting is used. If multiple componets have this setting and have the same priority, the maximum value out of those is used.
/// </remarks>
public static float GetMinHeight(RectTransform rect)
{
return GetLayoutProperty(rect, e => e.minHeight, 0);
}
/// <summary>
/// Returns the preferred height of the layout element.
/// </summary>
/// <param name="rect">The RectTransform of the layout element to query.</param>
/// <remarks>
/// All components on the GameObject that implement the ILayoutElement are queried. The one with the highest priority which has a value for this setting is used. If multiple componets have this setting and have the same priority, the maximum value out of those is used.
/// </remarks>
public static float GetPreferredHeight(RectTransform rect)
{
return Mathf.Max(GetLayoutProperty(rect, e => e.minHeight, 0), GetLayoutProperty(rect, e => e.preferredHeight, 0));
}
/// <summary>
/// Returns the flexible height of the layout element.
/// </summary>
/// <remarks>
/// All components on the GameObject that implement the ILayoutElement are queried. The one with the highest priority which has a value for this setting is used. If multiple componets have this setting and have the same priority, the maximum value out of those is used.
/// </remarks>
/// <param name="rect">The RectTransform of the layout element to query.</param>
public static float GetFlexibleHeight(RectTransform rect)
{
return GetLayoutProperty(rect, e => e.flexibleHeight, 0);
}
/// <summary>
/// Gets a calculated layout property for the layout element with the given RectTransform.
/// </summary>
/// <param name="rect">The RectTransform of the layout element to get a property for.</param>
/// <param name="property">The property to calculate.</param>
/// <param name="defaultValue">The default value to use if no component on the layout element supplies the given property</param>
/// <returns>The calculated value of the layout property.</returns>
public static float GetLayoutProperty(RectTransform rect, System.Func<ILayoutElement, float> property, float defaultValue)
{
ILayoutElement dummy;
return GetLayoutProperty(rect, property, defaultValue, out dummy);
}
/// <summary>
/// Gets a calculated layout property for the layout element with the given RectTransform.
/// </summary>
/// <param name="rect">The RectTransform of the layout element to get a property for.</param>
/// <param name="property">The property to calculate.</param>
/// <param name="defaultValue">The default value to use if no component on the layout element supplies the given property</param>
/// <param name="source">Optional out parameter to get the component that supplied the calculated value.</param>
/// <returns>The calculated value of the layout property.</returns>
public static float GetLayoutProperty(RectTransform rect, System.Func<ILayoutElement, float> property, float defaultValue, out ILayoutElement source)
{
source = null;
if (rect == null)
return 0;
float min = defaultValue;
int maxPriority = System.Int32.MinValue;
var components = ListPool<Component>.Get();
rect.GetComponents(typeof(ILayoutElement), components);
var componentsCount = components.Count;
for (int i = 0; i < componentsCount; i++)
{
var layoutComp = components[i] as ILayoutElement;
if (layoutComp is Behaviour && !((Behaviour)layoutComp).isActiveAndEnabled)
continue;
int priority = layoutComp.layoutPriority;
// If this layout components has lower priority than a previously used, ignore it.
if (priority < maxPriority)
continue;
float prop = property(layoutComp);
// If this layout property is set to a negative value, it means it should be ignored.
if (prop < 0)
continue;
// If this layout component has higher priority than all previous ones,
// overwrite with this one's value.
if (priority > maxPriority)
{
min = prop;
maxPriority = priority;
source = layoutComp;
}
// If the layout component has the same priority as a previously used,
// use the largest of the values with the same priority.
else if (prop > min)
{
min = prop;
source = layoutComp;
}
}
ListPool<Component>.Release(components);
return min;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 82bf3f737dec0be43a60891958a8da87
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,45 @@
namespace UnityEngine.UI
{
[AddComponentMenu("Layout/Vertical Layout Group", 151)]
/// <summary>
/// Layout child layout elements below each other.
/// </summary>
public class VerticalLayoutGroup : HorizontalOrVerticalLayoutGroup
{
protected VerticalLayoutGroup()
{}
/// <summary>
/// Called by the layout system. Also see ILayoutElement
/// </summary>
public override void CalculateLayoutInputHorizontal()
{
base.CalculateLayoutInputHorizontal();
CalcAlongAxis(0, true);
}
/// <summary>
/// Called by the layout system. Also see ILayoutElement
/// </summary>
public override void CalculateLayoutInputVertical()
{
CalcAlongAxis(1, true);
}
/// <summary>
/// Called by the layout system. Also see ILayoutElement
/// </summary>
public override void SetLayoutHorizontal()
{
SetChildrenAlongAxis(0, true);
}
/// <summary>
/// Called by the layout system. Also see ILayoutElement
/// </summary>
public override void SetLayoutVertical()
{
SetChildrenAlongAxis(1, true);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 59f8146938fff824cb5fd77236b75775
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,192 @@
using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Rendering;
using UnityEngine.Serialization;
namespace UnityEngine.UI
{
[AddComponentMenu("UI/Mask", 13)]
[ExecuteAlways]
[RequireComponent(typeof(RectTransform))]
[DisallowMultipleComponent]
/// <summary>
/// A component for masking children elements.
/// </summary>
/// <remarks>
/// By using this element any children elements that have masking enabled will mask where a sibling Graphic would write 0 to the stencil buffer.
/// </remarks>
public class Mask : UIBehaviour, ICanvasRaycastFilter, IMaterialModifier
{
[NonSerialized]
private RectTransform m_RectTransform;
public RectTransform rectTransform
{
get { return m_RectTransform ?? (m_RectTransform = GetComponent<RectTransform>()); }
}
[SerializeField]
private bool m_ShowMaskGraphic = true;
/// <summary>
/// Show the graphic that is associated with the Mask render area.
/// </summary>
public bool showMaskGraphic
{
get { return m_ShowMaskGraphic; }
set
{
if (m_ShowMaskGraphic == value)
return;
m_ShowMaskGraphic = value;
if (graphic != null)
graphic.SetMaterialDirty();
}
}
[NonSerialized]
private Graphic m_Graphic;
/// <summary>
/// The graphic associated with the Mask.
/// </summary>
public Graphic graphic
{
get { return m_Graphic ?? (m_Graphic = GetComponent<Graphic>()); }
}
[NonSerialized]
private Material m_MaskMaterial;
[NonSerialized]
private Material m_UnmaskMaterial;
protected Mask()
{}
public virtual bool MaskEnabled() { return IsActive() && graphic != null; }
[Obsolete("Not used anymore.")]
public virtual void OnSiblingGraphicEnabledDisabled() {}
protected override void OnEnable()
{
base.OnEnable();
if (graphic != null)
{
graphic.canvasRenderer.hasPopInstruction = true;
graphic.SetMaterialDirty();
// Default the graphic to being the maskable graphic if its found.
if (graphic is MaskableGraphic)
(graphic as MaskableGraphic).isMaskingGraphic = true;
}
MaskUtilities.NotifyStencilStateChanged(this);
}
protected override void OnDisable()
{
// we call base OnDisable first here
// as we need to have the IsActive return the
// correct value when we notify the children
// that the mask state has changed.
base.OnDisable();
if (graphic != null)
{
graphic.SetMaterialDirty();
graphic.canvasRenderer.hasPopInstruction = false;
graphic.canvasRenderer.popMaterialCount = 0;
if (graphic is MaskableGraphic)
(graphic as MaskableGraphic).isMaskingGraphic = false;
}
StencilMaterial.Remove(m_MaskMaterial);
m_MaskMaterial = null;
StencilMaterial.Remove(m_UnmaskMaterial);
m_UnmaskMaterial = null;
MaskUtilities.NotifyStencilStateChanged(this);
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (!IsActive())
return;
if (graphic != null)
{
// Default the graphic to being the maskable graphic if its found.
if (graphic is MaskableGraphic)
(graphic as MaskableGraphic).isMaskingGraphic = true;
graphic.SetMaterialDirty();
}
MaskUtilities.NotifyStencilStateChanged(this);
}
#endif
public virtual bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
{
if (!isActiveAndEnabled)
return true;
return RectTransformUtility.RectangleContainsScreenPoint(rectTransform, sp, eventCamera);
}
/// Stencil calculation time!
public virtual Material GetModifiedMaterial(Material baseMaterial)
{
if (!MaskEnabled())
return baseMaterial;
var rootSortCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
var stencilDepth = MaskUtilities.GetStencilDepth(transform, rootSortCanvas);
if (stencilDepth >= 8)
{
Debug.LogWarning("Attempting to use a stencil mask with depth > 8", gameObject);
return baseMaterial;
}
int desiredStencilBit = 1 << stencilDepth;
// if we are at the first level...
// we want to destroy what is there
if (desiredStencilBit == 1)
{
var maskMaterial = StencilMaterial.Add(baseMaterial, 1, StencilOp.Replace, CompareFunction.Always, m_ShowMaskGraphic ? ColorWriteMask.All : 0);
StencilMaterial.Remove(m_MaskMaterial);
m_MaskMaterial = maskMaterial;
var unmaskMaterial = StencilMaterial.Add(baseMaterial, 1, StencilOp.Zero, CompareFunction.Always, 0);
StencilMaterial.Remove(m_UnmaskMaterial);
m_UnmaskMaterial = unmaskMaterial;
graphic.canvasRenderer.popMaterialCount = 1;
graphic.canvasRenderer.SetPopMaterial(m_UnmaskMaterial, 0);
return m_MaskMaterial;
}
//otherwise we need to be a bit smarter and set some read / write masks
var maskMaterial2 = StencilMaterial.Add(baseMaterial, desiredStencilBit | (desiredStencilBit - 1), StencilOp.Replace, CompareFunction.Equal, m_ShowMaskGraphic ? ColorWriteMask.All : 0, desiredStencilBit - 1, desiredStencilBit | (desiredStencilBit - 1));
StencilMaterial.Remove(m_MaskMaterial);
m_MaskMaterial = maskMaterial2;
graphic.canvasRenderer.hasPopInstruction = true;
var unmaskMaterial2 = StencilMaterial.Add(baseMaterial, desiredStencilBit - 1, StencilOp.Replace, CompareFunction.Equal, 0, desiredStencilBit - 1, desiredStencilBit | (desiredStencilBit - 1));
StencilMaterial.Remove(m_UnmaskMaterial);
m_UnmaskMaterial = unmaskMaterial2;
graphic.canvasRenderer.popMaterialCount = 1;
graphic.canvasRenderer.SetPopMaterial(m_UnmaskMaterial, 0);
return m_MaskMaterial;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 31a19414c41e5ae4aae2af33fee712f6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,220 @@
using System.Collections.Generic;
using UnityEngine.Pool;
namespace UnityEngine.UI
{
/// <summary>
/// Mask related utility class. This class provides masking-specific utility functions.
/// </summary>
public class MaskUtilities
{
/// <summary>
/// Notify all IClippables under the given component that they need to recalculate clipping.
/// </summary>
/// <param name="mask">The object thats changed for whose children should be notified.</param>
public static void Notify2DMaskStateChanged(Component mask)
{
var components = ListPool<Component>.Get();
mask.GetComponentsInChildren(components);
for (var i = 0; i < components.Count; i++)
{
if (components[i] == null || components[i].gameObject == mask.gameObject)
continue;
var toNotify = components[i] as IClippable;
if (toNotify != null)
toNotify.RecalculateClipping();
}
ListPool<Component>.Release(components);
}
/// <summary>
/// Notify all IMaskable under the given component that they need to recalculate masking.
/// </summary>
/// <param name="mask">The object thats changed for whose children should be notified.</param>
public static void NotifyStencilStateChanged(Component mask)
{
var components = ListPool<Component>.Get();
mask.GetComponentsInChildren(components);
for (var i = 0; i < components.Count; i++)
{
if (components[i] == null || components[i].gameObject == mask.gameObject)
continue;
var toNotify = components[i] as IMaskable;
if (toNotify != null)
toNotify.RecalculateMasking();
}
ListPool<Component>.Release(components);
}
/// <summary>
/// Find a root Canvas.
/// </summary>
/// <param name="start">Transform to start the search at going up the hierarchy.</param>
/// <returns>Finds either the most root canvas, or the first canvas that overrides sorting.</returns>
public static Transform FindRootSortOverrideCanvas(Transform start)
{
var canvasList = ListPool<Canvas>.Get();
start.GetComponentsInParent(false, canvasList);
Canvas canvas = null;
for (int i = 0; i < canvasList.Count; ++i)
{
canvas = canvasList[i];
// We found the canvas we want to use break
if (canvas.overrideSorting)
break;
}
ListPool<Canvas>.Release(canvasList);
return canvas != null ? canvas.transform : null;
}
/// <summary>
/// Find the stencil depth for a given element.
/// </summary>
/// <param name="transform">The starting transform to search.</param>
/// <param name="stopAfter">Where the search of parents should stop</param>
/// <returns>What the proper stencil buffer index should be.</returns>
public static int GetStencilDepth(Transform transform, Transform stopAfter)
{
var depth = 0;
if (transform == stopAfter)
return depth;
var t = transform.parent;
var components = ListPool<Mask>.Get();
while (t != null)
{
t.GetComponents<Mask>(components);
for (var i = 0; i < components.Count; ++i)
{
if (components[i] != null && components[i].MaskEnabled() && components[i].graphic.IsActive())
{
++depth;
break;
}
}
if (t == stopAfter)
break;
t = t.parent;
}
ListPool<Mask>.Release(components);
return depth;
}
/// <summary>
/// Helper function to determine if the child is a descendant of father or is father.
/// </summary>
/// <param name="father">The transform to compare against.</param>
/// <param name="child">The starting transform to search up the hierarchy.</param>
/// <returns>Is child equal to father or is a descendant.</returns>
public static bool IsDescendantOrSelf(Transform father, Transform child)
{
if (father == null || child == null)
return false;
if (father == child)
return true;
while (child.parent != null)
{
if (child.parent == father)
return true;
child = child.parent;
}
return false;
}
/// <summary>
/// Find the correct RectMask2D for a given IClippable.
/// </summary>
/// <param name="clippable">Clippable to search from.</param>
/// <returns>The Correct RectMask2D</returns>
public static RectMask2D GetRectMaskForClippable(IClippable clippable)
{
List<RectMask2D> rectMaskComponents = ListPool<RectMask2D>.Get();
List<Canvas> canvasComponents = ListPool<Canvas>.Get();
RectMask2D componentToReturn = null;
clippable.gameObject.GetComponentsInParent(false, rectMaskComponents);
if (rectMaskComponents.Count > 0)
{
for (int rmi = 0; rmi < rectMaskComponents.Count; rmi++)
{
componentToReturn = rectMaskComponents[rmi];
if (componentToReturn.gameObject == clippable.gameObject)
{
componentToReturn = null;
continue;
}
if (!componentToReturn.isActiveAndEnabled)
{
componentToReturn = null;
continue;
}
clippable.gameObject.GetComponentsInParent(false, canvasComponents);
for (int i = canvasComponents.Count - 1; i >= 0; i--)
{
if (!IsDescendantOrSelf(canvasComponents[i].transform, componentToReturn.transform) && canvasComponents[i].overrideSorting)
{
componentToReturn = null;
break;
}
}
break;
}
}
ListPool<RectMask2D>.Release(rectMaskComponents);
ListPool<Canvas>.Release(canvasComponents);
return componentToReturn;
}
/// <summary>
/// Search for all RectMask2D that apply to the given RectMask2D (includes self).
/// </summary>
/// <param name="clipper">Starting clipping object.</param>
/// <param name="masks">The list of Rect masks</param>
public static void GetRectMasksForClip(RectMask2D clipper, List<RectMask2D> masks)
{
masks.Clear();
List<Canvas> canvasComponents = ListPool<Canvas>.Get();
List<RectMask2D> rectMaskComponents = ListPool<RectMask2D>.Get();
clipper.transform.GetComponentsInParent(false, rectMaskComponents);
if (rectMaskComponents.Count > 0)
{
clipper.transform.GetComponentsInParent(false, canvasComponents);
for (int i = rectMaskComponents.Count - 1; i >= 0; i--)
{
if (!rectMaskComponents[i].IsActive())
continue;
bool shouldAdd = true;
for (int j = canvasComponents.Count - 1; j >= 0; j--)
{
if (!IsDescendantOrSelf(canvasComponents[j].transform, rectMaskComponents[i].transform) && canvasComponents[j].overrideSorting)
{
shouldAdd = false;
break;
}
}
if (shouldAdd)
masks.Add(rectMaskComponents[i]);
}
}
ListPool<RectMask2D>.Release(rectMaskComponents);
ListPool<Canvas>.Release(canvasComponents);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7bd96d76711152648a736c4d28d865f2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,304 @@
using System;
using UnityEngine.Events;
using UnityEngine.Rendering;
namespace UnityEngine.UI
{
/// <summary>
/// A Graphic that is capable of being masked out.
/// </summary>
public abstract class MaskableGraphic : Graphic, IClippable, IMaskable, IMaterialModifier
{
[NonSerialized]
protected bool m_ShouldRecalculateStencil = true;
[NonSerialized]
protected Material m_MaskMaterial;
[NonSerialized]
private RectMask2D m_ParentMask;
// m_Maskable is whether this graphic is allowed to be masked or not. It has the matching public property maskable.
// The default for m_Maskable is true, so graphics under a mask are masked out of the box.
// The maskable property can be turned off from script by the user if masking is not desired.
// m_IncludeForMasking is whether we actually consider this graphic for masking or not - this is an implementation detail.
// m_IncludeForMasking should only be true if m_Maskable is true AND a parent of the graphic has an IMask component.
// Things would still work correctly if m_IncludeForMasking was always true when m_Maskable is, but performance would suffer.
[SerializeField]
private bool m_Maskable = true;
private bool m_IsMaskingGraphic = false;
[NonSerialized]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
[Obsolete("Not used anymore.", true)]
protected bool m_IncludeForMasking = false;
[Serializable]
public class CullStateChangedEvent : UnityEvent<bool> {}
// Event delegates triggered on click.
[SerializeField]
private CullStateChangedEvent m_OnCullStateChanged = new CullStateChangedEvent();
/// <summary>
/// Callback issued when culling changes.
/// </summary>
/// <remarks>
/// Called whene the culling state of this MaskableGraphic either becomes culled or visible. You can use this to control other elements of your UI as culling happens.
/// </remarks>
public CullStateChangedEvent onCullStateChanged
{
get { return m_OnCullStateChanged; }
set { m_OnCullStateChanged = value; }
}
/// <summary>
/// Does this graphic allow masking.
/// </summary>
public bool maskable
{
get { return m_Maskable; }
set
{
if (value == m_Maskable)
return;
m_Maskable = value;
m_ShouldRecalculateStencil = true;
SetMaterialDirty();
}
}
/// <summary>
/// Is this graphic the graphic on the same object as a Mask that is enabled.
/// </summary>
/// <remarks>
/// If toggled ensure to call MaskUtilities.NotifyStencilStateChanged(this); manually as it changes how stenciles are calculated for this image.
/// </remarks>
public bool isMaskingGraphic
{
get { return m_IsMaskingGraphic; }
set
{
if (value == m_IsMaskingGraphic)
return;
m_IsMaskingGraphic = value;
}
}
[NonSerialized]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
[Obsolete("Not used anymore", true)]
protected bool m_ShouldRecalculate = true;
[NonSerialized]
protected int m_StencilValue;
/// <summary>
/// See IMaterialModifier.GetModifiedMaterial
/// </summary>
public virtual Material GetModifiedMaterial(Material baseMaterial)
{
var toUse = baseMaterial;
if (m_ShouldRecalculateStencil)
{
if (maskable)
{
var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
m_StencilValue = MaskUtilities.GetStencilDepth(transform, rootCanvas);
}
else
m_StencilValue = 0;
m_ShouldRecalculateStencil = false;
}
// if we have a enabled Mask component then it will
// generate the mask material. This is an optimization
// it adds some coupling between components though :(
if (m_StencilValue > 0 && !isMaskingGraphic)
{
var maskMat = StencilMaterial.Add(toUse, (1 << m_StencilValue) - 1, StencilOp.Keep, CompareFunction.Equal, ColorWriteMask.All, (1 << m_StencilValue) - 1, 0);
StencilMaterial.Remove(m_MaskMaterial);
m_MaskMaterial = maskMat;
toUse = m_MaskMaterial;
}
return toUse;
}
/// <summary>
/// See IClippable.Cull
/// </summary>
public virtual void Cull(Rect clipRect, bool validRect)
{
var cull = !validRect || !clipRect.Overlaps(rootCanvasRect, true);
UpdateCull(cull);
}
private void UpdateCull(bool cull)
{
if (canvasRenderer.cull != cull)
{
canvasRenderer.cull = cull;
UISystemProfilerApi.AddMarker("MaskableGraphic.cullingChanged", this);
m_OnCullStateChanged.Invoke(cull);
OnCullingChanged();
}
}
/// <summary>
/// See IClippable.SetClipRect
/// </summary>
public virtual void SetClipRect(Rect clipRect, bool validRect)
{
if (validRect)
canvasRenderer.EnableRectClipping(clipRect);
else
canvasRenderer.DisableRectClipping();
}
public virtual void SetClipSoftness(Vector2 clipSoftness)
{
canvasRenderer.clippingSoftness = clipSoftness;
}
protected override void OnEnable()
{
base.OnEnable();
m_ShouldRecalculateStencil = true;
UpdateClipParent();
SetMaterialDirty();
if (isMaskingGraphic)
{
MaskUtilities.NotifyStencilStateChanged(this);
}
}
protected override void OnDisable()
{
base.OnDisable();
m_ShouldRecalculateStencil = true;
SetMaterialDirty();
UpdateClipParent();
StencilMaterial.Remove(m_MaskMaterial);
m_MaskMaterial = null;
if (isMaskingGraphic)
{
MaskUtilities.NotifyStencilStateChanged(this);
}
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
m_ShouldRecalculateStencil = true;
UpdateClipParent();
SetMaterialDirty();
}
#endif
protected override void OnTransformParentChanged()
{
base.OnTransformParentChanged();
if (!isActiveAndEnabled)
return;
m_ShouldRecalculateStencil = true;
UpdateClipParent();
SetMaterialDirty();
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
[Obsolete("Not used anymore.", true)]
public virtual void ParentMaskStateChanged() {}
protected override void OnCanvasHierarchyChanged()
{
base.OnCanvasHierarchyChanged();
if (!isActiveAndEnabled)
return;
m_ShouldRecalculateStencil = true;
UpdateClipParent();
SetMaterialDirty();
}
readonly Vector3[] m_Corners = new Vector3[4];
private Rect rootCanvasRect
{
get
{
rectTransform.GetWorldCorners(m_Corners);
if (canvas)
{
Matrix4x4 mat = canvas.rootCanvas.transform.worldToLocalMatrix;
for (int i = 0; i < 4; ++i)
m_Corners[i] = mat.MultiplyPoint(m_Corners[i]);
}
// bounding box is now based on the min and max of all corners (case 1013182)
Vector2 min = m_Corners[0];
Vector2 max = m_Corners[0];
for (int i = 1; i < 4; i++)
{
min.x = Mathf.Min(m_Corners[i].x, min.x);
min.y = Mathf.Min(m_Corners[i].y, min.y);
max.x = Mathf.Max(m_Corners[i].x, max.x);
max.y = Mathf.Max(m_Corners[i].y, max.y);
}
return new Rect(min, max - min);
}
}
private void UpdateClipParent()
{
var newParent = (maskable && IsActive()) ? MaskUtilities.GetRectMaskForClippable(this) : null;
// if the new parent is different OR is now inactive
if (m_ParentMask != null && (newParent != m_ParentMask || !newParent.IsActive()))
{
m_ParentMask.RemoveClippable(this);
UpdateCull(false);
}
// don't re-add it if the newparent is inactive
if (newParent != null && newParent.IsActive())
newParent.AddClippable(this);
m_ParentMask = newParent;
}
/// <summary>
/// See IClippable.RecalculateClipping
/// </summary>
public virtual void RecalculateClipping()
{
UpdateClipParent();
}
/// <summary>
/// See IMaskable.RecalculateMasking
/// </summary>
public virtual void RecalculateMasking()
{
// Remove the material reference as either the graphic of the mask has been enable/ disabled.
// This will cause the material to be repopulated from the original if need be. (case 994413)
StencilMaterial.Remove(m_MaskMaterial);
m_MaskMaterial = null;
m_ShouldRecalculateStencil = true;
SetMaterialDirty();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 976acc75bfafe594cb01142ba21947be
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7c6295db74da28645bf49db58b7c9c65
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
namespace UnityEngine.UI
{
/// <summary>
/// Use this interface to modify a Material that renders a Graphic. The Material is modified before the it is passed to the CanvasRenderer.
/// </summary>
/// <remarks>
/// When a Graphic sets a material that is passed (in order) to any components on the GameObject that implement IMaterialModifier. This component can modify the material to be used for rendering.
/// </remarks>
public interface IMaterialModifier
{
/// <summary>
/// Perform material modification in this function.
/// </summary>
/// <param name="baseMaterial">The material that is to be modified</param>
/// <returns>The modified material.</returns>
Material GetModifiedMaterial(Material baseMaterial);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 20ff4283f687e044087714f82c4d6d3f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,44 @@
namespace UnityEngine.UI
{
/// <summary>
/// Helper class containing generic functions used throughout the UI library.
/// </summary>
internal static class Misc
{
/// <summary>
/// Destroy the specified object, immediately if in edit mode.
/// </summary>
static public void Destroy(UnityEngine.Object obj)
{
if (obj != null)
{
if (Application.isPlaying)
{
if (obj is GameObject)
{
GameObject go = obj as GameObject;
go.transform.parent = null;
}
Object.Destroy(obj);
}
else Object.DestroyImmediate(obj);
}
}
/// <summary>
/// Destroy the specified object immediately, unless not in the editor, in which case the regular Destroy is used instead.
/// </summary>
static public void DestroyImmediate(Object obj)
{
if (obj != null)
{
if (Application.isEditor) Object.DestroyImmediate(obj);
else Object.Destroy(obj);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 828075bc53f2de84982a943870529b7b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,105 @@
using UnityEngine.EventSystems;
namespace UnityEngine.UI
{
internal static class MultipleDisplayUtilities
{
/// <summary>
/// Converts the current drag position into a relative position for the display.
/// </summary>
/// <param name="eventData"></param>
/// <param name="position"></param>
/// <returns>Returns true except when the drag operation is not on the same display as it originated</returns>
public static bool GetRelativeMousePositionForDrag(PointerEventData eventData, ref Vector2 position)
{
#if UNITY_EDITOR
position = eventData.position;
#else
int pressDisplayIndex = eventData.pointerPressRaycast.displayIndex;
var relativePosition = RelativeMouseAtScaled(eventData.position);
int currentDisplayIndex = (int)relativePosition.z;
// Discard events on a different display.
if (currentDisplayIndex != pressDisplayIndex)
return false;
// If we are not on the main display then we must use the relative position.
position = pressDisplayIndex != 0 ? (Vector2)relativePosition : eventData.position;
#endif
return true;
}
/// <summary>
/// A version of Display.RelativeMouseAt that scales the position when the main display has a different rendering resolution to the system resolution.
/// By default, the mouse position is relative to the main render area, we need to adjust this so it is relative to the system resolution
/// in order to correctly determine the position on other displays.
/// </summary>
/// <returns></returns>
public static Vector3 RelativeMouseAtScaled(Vector2 position)
{
#if !UNITY_EDITOR && !UNITY_WSA
// If the main display is now the same resolution as the system then we need to scale the mouse position. (case 1141732)
if (Display.main.renderingWidth != Display.main.systemWidth || Display.main.renderingHeight != Display.main.systemHeight)
{
// The system will add padding when in full-screen and using a non-native aspect ratio. (case UUM-7893)
// For example Rendering 1920x1080 with a systeem resolution of 3440x1440 would create black bars on each side that are 330 pixels wide.
// we need to account for this or it will offset our coordinates when we are not on the main display.
var systemAspectRatio = Display.main.systemWidth / (float)Display.main.systemHeight;
var sizePlusPadding = new Vector2(Display.main.renderingWidth, Display.main.renderingHeight);
var padding = Vector2.zero;
if (Screen.fullScreen)
{
var aspectRatio = Screen.width / (float)Screen.height;
if (Display.main.systemHeight * aspectRatio < Display.main.systemWidth)
{
// Horizontal padding
sizePlusPadding.x = Display.main.renderingHeight * systemAspectRatio;
padding.x = (sizePlusPadding.x - Display.main.renderingWidth) * 0.5f;
}
else
{
// Vertical padding
sizePlusPadding.y = Display.main.renderingWidth / systemAspectRatio;
padding.y = (sizePlusPadding.y - Display.main.renderingHeight) * 0.5f;
}
}
var sizePlusPositivePadding = sizePlusPadding - padding;
// If we are not inside of the main display then we must adjust the mouse position so it is scaled by
// the main display and adjusted for any padding that may have been added due to different aspect ratios.
if (position.y < -padding.y || position.y > sizePlusPositivePadding.y ||
position.x < -padding.x || position.x > sizePlusPositivePadding.x)
{
var adjustedPosition = position;
if (!Screen.fullScreen)
{
// When in windowed mode, the window will be centered with the 0,0 coordinate at the top left, we need to adjust so it is relative to the screen instead.
adjustedPosition.x -= (Display.main.renderingWidth - Display.main.systemWidth) * 0.5f;
adjustedPosition.y -= (Display.main.renderingHeight - Display.main.systemHeight) * 0.5f;
}
else
{
// Scale the mouse position to account for the black bars when in a non-native aspect ratio.
adjustedPosition += padding;
adjustedPosition.x *= Display.main.systemWidth / sizePlusPadding.x;
adjustedPosition.y *= Display.main.systemHeight / sizePlusPadding.y;
}
var relativePos = Display.RelativeMouseAt(adjustedPosition);
// If we are not on the main display then return the adjusted position.
if (relativePos.z != 0)
return relativePos;
}
// We are using the main display.
return new Vector3(position.x, position.y, 0);
}
#endif
return Display.RelativeMouseAt(position);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5ebeb75bd91642048bb3f6c1939fde66
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,308 @@
using System;
using UnityEngine.Serialization;
namespace UnityEngine.UI
{
[Serializable]
/// <summary>
/// Structure storing details related to navigation.
/// </summary>
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]
/// <summary>
/// Navigation mode enumeration.
/// </summary>
/// <remarks>
/// This looks like it's not flags, but it is flags, the reason is that Automatic is considered horizontal and vertical mode combined
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Button button;
///
/// void Start()
/// {
/// //Set the navigation to the mode "Vertical".
/// if (button.navigation.mode == Navigation.Mode.Vertical)
/// {
/// Debug.Log("The button's navigation mode is Vertical");
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public enum Mode
{
/// <summary>
/// No navigation is allowed from this object.
/// </summary>
None = 0,
/// <summary>
/// Horizontal Navigation.
/// </summary>
/// <remarks>
/// Navigation should only be allowed when left / right move events happen.
/// </remarks>
Horizontal = 1,
/// <summary>
/// Vertical navigation.
/// </summary>
/// <remarks>
/// Navigation should only be allowed when up / down move events happen.
/// </remarks>
Vertical = 2,
/// <summary>
/// Automatic navigation.
/// </summary>
/// <remarks>
/// Attempt to find the 'best' next object to select. This should be based on a sensible heuristic.
/// </remarks>
Automatic = 3,
/// <summary>
/// Explicit navigation.
/// </summary>
/// <remarks>
/// User should explicitly specify what is selected by each move event.
/// </remarks>
Explicit = 4,
}
// Which method of navigation will be used.
[SerializeField]
private Mode m_Mode;
[Tooltip("Enables navigation to wrap around from last to first or first to last element. Does not work for automatic grid navigation")]
[SerializeField]
private bool m_WrapAround;
// Game object selected when the joystick moves up. Used when navigation is set to "Explicit".
[SerializeField]
private Selectable m_SelectOnUp;
// Game object selected when the joystick moves down. Used when navigation is set to "Explicit".
[SerializeField]
private Selectable m_SelectOnDown;
// Game object selected when the joystick moves left. Used when navigation is set to "Explicit".
[SerializeField]
private Selectable m_SelectOnLeft;
// Game object selected when the joystick moves right. Used when navigation is set to "Explicit".
[SerializeField]
private Selectable m_SelectOnRight;
/// <summary>
/// Navigation mode.
/// </summary>
public Mode mode { get { return m_Mode; } set { m_Mode = value; } }
/// <summary>
/// Enables navigation to wrap around from last to first or first to last element.
/// Will find the furthest element from the current element in the opposite direction of movement.
/// </summary>
/// <example>
/// Note: If you have a grid of elements and you are on the last element in a row it will not wrap over to the next row it will pick the furthest element in the opposite direction.
/// </example>
public bool wrapAround { get { return m_WrapAround; } set { m_WrapAround = value; } }
/// <summary>
/// Specify a Selectable UI GameObject to highlight when the Up arrow key is pressed.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class HighlightOnKey : MonoBehaviour
/// {
/// public Button btnSave;
/// public Button btnLoad;
///
/// public void Start()
/// {
/// // get the Navigation data
/// Navigation navigation = btnLoad.navigation;
///
/// // switch mode to Explicit to allow for custom assigned behavior
/// navigation.mode = Navigation.Mode.Explicit;
///
/// // highlight the Save button if the up arrow key is pressed
/// navigation.selectOnUp = btnSave;
///
/// // reassign the struct data to the button
/// btnLoad.navigation = navigation;
/// }
/// }
/// ]]>
///</code>
/// </example>
public Selectable selectOnUp { get { return m_SelectOnUp; } set { m_SelectOnUp = value; } }
/// <summary>
/// Specify a Selectable UI GameObject to highlight when the down arrow key is pressed.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class HighlightOnKey : MonoBehaviour
/// {
/// public Button btnSave;
/// public Button btnLoad;
///
/// public void Start()
/// {
/// // get the Navigation data
/// Navigation navigation = btnLoad.navigation;
///
/// // switch mode to Explicit to allow for custom assigned behavior
/// navigation.mode = Navigation.Mode.Explicit;
///
/// // highlight the Save button if the down arrow key is pressed
/// navigation.selectOnDown = btnSave;
///
/// // reassign the struct data to the button
/// btnLoad.navigation = navigation;
/// }
/// }
/// ]]>
///</code>
/// </example>
public Selectable selectOnDown { get { return m_SelectOnDown; } set { m_SelectOnDown = value; } }
/// <summary>
/// Specify a Selectable UI GameObject to highlight when the left arrow key is pressed.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class HighlightOnKey : MonoBehaviour
/// {
/// public Button btnSave;
/// public Button btnLoad;
///
/// public void Start()
/// {
/// // get the Navigation data
/// Navigation navigation = btnLoad.navigation;
///
/// // switch mode to Explicit to allow for custom assigned behavior
/// navigation.mode = Navigation.Mode.Explicit;
///
/// // highlight the Save button if the left arrow key is pressed
/// navigation.selectOnLeft = btnSave;
///
/// // reassign the struct data to the button
/// btnLoad.navigation = navigation;
/// }
/// }
/// ]]>
///</code>
/// </example>
public Selectable selectOnLeft { get { return m_SelectOnLeft; } set { m_SelectOnLeft = value; } }
/// <summary>
/// Specify a Selectable UI GameObject to highlight when the right arrow key is pressed.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class HighlightOnKey : MonoBehaviour
/// {
/// public Button btnSave;
/// public Button btnLoad;
///
/// public void Start()
/// {
/// // get the Navigation data
/// Navigation navigation = btnLoad.navigation;
///
/// // switch mode to Explicit to allow for custom assigned behavior
/// navigation.mode = Navigation.Mode.Explicit;
///
/// // highlight the Save button if the right arrow key is pressed
/// navigation.selectOnRight = btnSave;
///
/// // reassign the struct data to the button
/// btnLoad.navigation = navigation;
/// }
/// }
/// ]]>
///</code>
/// </example>
public Selectable selectOnRight { get { return m_SelectOnRight; } set { m_SelectOnRight = value; } }
/// <summary>
/// Return a Navigation with sensible default values.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Button button;
///
/// void Start()
/// {
/// //Set the navigation to the default value. ("Automatic" is the default value).
/// button.navigation = Navigation.defaultNavigation;
/// }
/// }
/// ]]>
///</code>
/// </example>
static public Navigation defaultNavigation
{
get
{
var defaultNav = new Navigation();
defaultNav.m_Mode = Mode.Automatic;
defaultNav.m_WrapAround = false;
return defaultNav;
}
}
public bool Equals(Navigation other)
{
return mode == other.mode &&
selectOnUp == other.selectOnUp &&
selectOnDown == other.selectOnDown &&
selectOnLeft == other.selectOnLeft &&
selectOnRight == other.selectOnRight;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 61d491d99e9292c4a81d7d01a74781ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
using UnityEngine.Serialization;
namespace UnityEngine.UI
{
/// <summary>
/// Displays a Texture2D for the UI System.
/// </summary>
/// <remarks>
/// If you don't have or don't wish to create an atlas, you can simply use this script to draw a texture.
/// Keep in mind though that this will create an extra draw call with each RawImage present, so it's
/// best to use it only for backgrounds or temporary visible graphics.
/// </remarks>
[RequireComponent(typeof(CanvasRenderer))]
[AddComponentMenu("UI/Raw Image", 12)]
public class RawImage : MaskableGraphic
{
[FormerlySerializedAs("m_Tex")]
[SerializeField] Texture m_Texture;
[SerializeField] Rect m_UVRect = new Rect(0f, 0f, 1f, 1f);
protected RawImage()
{
useLegacyMeshGeneration = false;
}
/// <summary>
/// Returns the texture used to draw this Graphic.
/// </summary>
public override Texture mainTexture
{
get
{
if (m_Texture == null)
{
if (material != null && material.mainTexture != null)
{
return material.mainTexture;
}
return s_WhiteTexture;
}
return m_Texture;
}
}
/// <summary>
/// The RawImage's texture to be used.
/// </summary>
/// <remarks>
/// Use this to alter or return the Texture the RawImage displays. The Raw Image can display any Texture whereas an Image component can only show a Sprite Texture.
/// Note : Keep in mind that using a RawImage creates an extra draw call with each RawImage present, so it's best to use it only for backgrounds or temporary visible graphics.Note: Keep in mind that using a RawImage creates an extra draw call with each RawImage present, so it's best to use it only for backgrounds or temporary visible graphics.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// //Create a new RawImage by going to Create>UI>Raw Image in the hierarchy.
/// //Attach this script to the RawImage GameObject.
///
/// using UnityEngine;
/// using UnityEngine.UI;
///
/// public class RawImageTexture : MonoBehaviour
/// {
/// RawImage m_RawImage;
/// //Select a Texture in the Inspector to change to
/// public Texture m_Texture;
///
/// void Start()
/// {
/// //Fetch the RawImage component from the GameObject
/// m_RawImage = GetComponent<RawImage>();
/// //Change the Texture to be the one you define in the Inspector
/// m_RawImage.texture = m_Texture;
/// }
/// }
/// ]]>
///</code>
/// </example>
public Texture texture
{
get
{
return m_Texture;
}
set
{
if (m_Texture == value)
return;
m_Texture = value;
SetVerticesDirty();
SetMaterialDirty();
}
}
/// <summary>
/// UV rectangle used by the texture.
/// </summary>
public Rect uvRect
{
get
{
return m_UVRect;
}
set
{
if (m_UVRect == value)
return;
m_UVRect = value;
SetVerticesDirty();
}
}
/// <summary>
/// Adjust the scale of the Graphic to make it pixel-perfect.
/// </summary>
/// <remarks>
/// This means setting the RawImage's RectTransform.sizeDelta to be equal to the Texture dimensions.
/// </remarks>
public override void SetNativeSize()
{
Texture tex = mainTexture;
if (tex != null)
{
int w = Mathf.RoundToInt(tex.width * uvRect.width);
int h = Mathf.RoundToInt(tex.height * uvRect.height);
rectTransform.anchorMax = rectTransform.anchorMin;
rectTransform.sizeDelta = new Vector2(w, h);
}
}
protected override void OnPopulateMesh(VertexHelper vh)
{
Texture tex = mainTexture;
vh.Clear();
if (tex != null)
{
var r = GetPixelAdjustedRect();
var v = new Vector4(r.x, r.y, r.x + r.width, r.y + r.height);
var scaleX = tex.width * tex.texelSize.x;
var scaleY = tex.height * tex.texelSize.y;
{
var color32 = color;
vh.AddVert(new Vector3(v.x, v.y), color32, new Vector2(m_UVRect.xMin * scaleX, m_UVRect.yMin * scaleY));
vh.AddVert(new Vector3(v.x, v.w), color32, new Vector2(m_UVRect.xMin * scaleX, m_UVRect.yMax * scaleY));
vh.AddVert(new Vector3(v.z, v.w), color32, new Vector2(m_UVRect.xMax * scaleX, m_UVRect.yMax * scaleY));
vh.AddVert(new Vector3(v.z, v.y), color32, new Vector2(m_UVRect.xMax * scaleX, m_UVRect.yMin * scaleY));
vh.AddTriangle(0, 1, 2);
vh.AddTriangle(2, 3, 0);
}
}
}
protected override void OnDidApplyAnimationProperties()
{
SetMaterialDirty();
SetVerticesDirty();
SetRaycastDirty();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1344c3c82d62a2a41a3576d8abb8e3ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,359 @@
using System;
using System.Collections.Generic;
using UnityEngine.EventSystems;
using UnityEngine.Pool;
namespace UnityEngine.UI
{
[AddComponentMenu("UI/Rect Mask 2D", 14)]
[ExecuteAlways]
[DisallowMultipleComponent]
[RequireComponent(typeof(RectTransform))]
/// <summary>
/// A 2D rectangular mask that allows for clipping / masking of areas outside the mask.
/// </summary>
/// <remarks>
/// The RectMask2D behaves in a similar way to a standard Mask component. It differs though in some of the restrictions that it has.
/// A RectMask2D:
/// *Only works in the 2D plane
/// *Requires elements on the mask to be coplanar.
/// *Does not require stencil buffer / extra draw calls
/// *Requires fewer draw calls
/// *Culls elements that are outside the mask area.
/// </remarks>
public class RectMask2D : UIBehaviour, IClipper, ICanvasRaycastFilter
{
[NonSerialized]
private readonly RectangularVertexClipper m_VertexClipper = new RectangularVertexClipper();
[NonSerialized]
private RectTransform m_RectTransform;
[NonSerialized]
private HashSet<MaskableGraphic> m_MaskableTargets = new HashSet<MaskableGraphic>();
[NonSerialized]
private HashSet<IClippable> m_ClipTargets = new HashSet<IClippable>();
[NonSerialized]
private bool m_ShouldRecalculateClipRects;
[NonSerialized]
private List<RectMask2D> m_Clippers = new List<RectMask2D>();
[NonSerialized]
private Rect m_LastClipRectCanvasSpace;
[NonSerialized]
private bool m_ForceClip;
[SerializeField]
private Vector4 m_Padding = new Vector4();
/// <summary>
/// Padding to be applied to the masking
/// X = Left
/// Y = Bottom
/// Z = Right
/// W = Top
/// </summary>
public Vector4 padding
{
get { return m_Padding; }
set
{
m_Padding = value;
MaskUtilities.Notify2DMaskStateChanged(this);
}
}
[SerializeField]
private Vector2Int m_Softness;
/// <summary>
/// The softness to apply to the horizontal and vertical axis.
/// </summary>
public Vector2Int softness
{
get { return m_Softness; }
set
{
m_Softness.x = Mathf.Max(0, value.x);
m_Softness.y = Mathf.Max(0, value.y);
MaskUtilities.Notify2DMaskStateChanged(this);
}
}
/// <remarks>
/// Returns a non-destroyed instance or a null reference.
/// </remarks>
[NonSerialized] private Canvas m_Canvas;
internal Canvas Canvas
{
get
{
if (m_Canvas == null)
{
var list = ListPool<Canvas>.Get();
gameObject.GetComponentsInParent(false, list);
if (list.Count > 0)
m_Canvas = list[list.Count - 1];
else
m_Canvas = null;
ListPool<Canvas>.Release(list);
}
return m_Canvas;
}
}
/// <summary>
/// Get the Rect for the mask in canvas space.
/// </summary>
public Rect canvasRect
{
get
{
return m_VertexClipper.GetCanvasRect(rectTransform, Canvas);
}
}
/// <summary>
/// Helper function to get the RectTransform for the mask.
/// </summary>
public RectTransform rectTransform
{
get { return m_RectTransform ?? (m_RectTransform = GetComponent<RectTransform>()); }
}
protected RectMask2D()
{}
protected override void OnEnable()
{
base.OnEnable();
m_ShouldRecalculateClipRects = true;
ClipperRegistry.Register(this);
MaskUtilities.Notify2DMaskStateChanged(this);
}
protected override void OnDisable()
{
// we call base OnDisable first here
// as we need to have the IsActive return the
// correct value when we notify the children
// that the mask state has changed.
base.OnDisable();
m_ClipTargets.Clear();
m_MaskableTargets.Clear();
m_Clippers.Clear();
ClipperRegistry.Disable(this);
MaskUtilities.Notify2DMaskStateChanged(this);
}
protected override void OnDestroy()
{
ClipperRegistry.Unregister(this);
base.OnDestroy();
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
m_ShouldRecalculateClipRects = true;
// Dont allow negative softness.
m_Softness.x = Mathf.Max(0, m_Softness.x);
m_Softness.y = Mathf.Max(0, m_Softness.y);
if (!IsActive())
return;
MaskUtilities.Notify2DMaskStateChanged(this);
}
#endif
public virtual bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
{
if (!isActiveAndEnabled)
return true;
return RectTransformUtility.RectangleContainsScreenPoint(rectTransform, sp, eventCamera, m_Padding);
}
private Vector3[] m_Corners = new Vector3[4];
private Rect rootCanvasRect
{
get
{
rectTransform.GetWorldCorners(m_Corners);
if (!ReferenceEquals(Canvas, null))
{
Canvas rootCanvas = Canvas.rootCanvas;
for (int i = 0; i < 4; ++i)
m_Corners[i] = rootCanvas.transform.InverseTransformPoint(m_Corners[i]);
}
return new Rect(m_Corners[0].x, m_Corners[0].y, m_Corners[2].x - m_Corners[0].x, m_Corners[2].y - m_Corners[0].y);
}
}
public virtual void PerformClipping()
{
if (ReferenceEquals(Canvas, null))
{
return;
}
//TODO See if an IsActive() test would work well here or whether it might cause unexpected side effects (re case 776771)
// if the parents are changed
// or something similar we
// do a recalculate here
if (m_ShouldRecalculateClipRects)
{
MaskUtilities.GetRectMasksForClip(this, m_Clippers);
m_ShouldRecalculateClipRects = false;
}
// get the compound rects from
// the clippers that are valid
bool validRect = true;
Rect clipRect = Clipping.FindCullAndClipWorldRect(m_Clippers, out validRect);
// If the mask is in ScreenSpaceOverlay/Camera render mode, its content is only rendered when its rect
// overlaps that of the root canvas.
RenderMode renderMode = Canvas.rootCanvas.renderMode;
bool maskIsCulled =
(renderMode == RenderMode.ScreenSpaceCamera || renderMode == RenderMode.ScreenSpaceOverlay) &&
!clipRect.Overlaps(rootCanvasRect, true);
if (maskIsCulled)
{
// Children are only displayed when inside the mask. If the mask is culled, then the children
// inside the mask are also culled. In that situation, we pass an invalid rect to allow callees
// to avoid some processing.
clipRect = Rect.zero;
validRect = false;
}
if (clipRect != m_LastClipRectCanvasSpace)
{
foreach (IClippable clipTarget in m_ClipTargets)
{
clipTarget.SetClipRect(clipRect, validRect);
}
foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
{
maskableTarget.SetClipRect(clipRect, validRect);
maskableTarget.Cull(clipRect, validRect);
}
}
else if (m_ForceClip)
{
foreach (IClippable clipTarget in m_ClipTargets)
{
clipTarget.SetClipRect(clipRect, validRect);
}
foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
{
maskableTarget.SetClipRect(clipRect, validRect);
if (maskableTarget.canvasRenderer.hasMoved)
maskableTarget.Cull(clipRect, validRect);
}
}
else
{
foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
{
//Case 1170399 - hasMoved is not a valid check when animating on pivot of the object
maskableTarget.Cull(clipRect, validRect);
}
}
m_LastClipRectCanvasSpace = clipRect;
m_ForceClip = false;
UpdateClipSoftness();
}
public virtual void UpdateClipSoftness()
{
if (ReferenceEquals(Canvas, null))
{
return;
}
foreach (IClippable clipTarget in m_ClipTargets)
{
clipTarget.SetClipSoftness(m_Softness);
}
foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
{
maskableTarget.SetClipSoftness(m_Softness);
}
}
/// <summary>
/// Add a IClippable to be tracked by the mask.
/// </summary>
/// <param name="clippable">Add the clippable object for this mask</param>
public void AddClippable(IClippable clippable)
{
if (clippable == null)
return;
m_ShouldRecalculateClipRects = true;
MaskableGraphic maskable = clippable as MaskableGraphic;
if (maskable == null)
m_ClipTargets.Add(clippable);
else
m_MaskableTargets.Add(maskable);
m_ForceClip = true;
}
/// <summary>
/// Remove an IClippable from being tracked by the mask.
/// </summary>
/// <param name="clippable">Remove the clippable object from this mask</param>
public void RemoveClippable(IClippable clippable)
{
if (clippable == null)
return;
m_ShouldRecalculateClipRects = true;
clippable.SetClipRect(new Rect(), false);
MaskableGraphic maskable = clippable as MaskableGraphic;
if (maskable == null)
m_ClipTargets.Remove(clippable);
else
m_MaskableTargets.Remove(maskable);
m_ForceClip = true;
}
protected override void OnTransformParentChanged()
{
m_Canvas = null;
base.OnTransformParentChanged();
m_ShouldRecalculateClipRects = true;
}
protected override void OnCanvasHierarchyChanged()
{
m_Canvas = null;
base.OnCanvasHierarchyChanged();
m_ShouldRecalculateClipRects = true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3312d7739989d2b4e91e6319e9a96d76
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,1460 @@
using System;
using UnityEngine.Events;
using UnityEngine.EventSystems;
namespace UnityEngine.UI
{
[AddComponentMenu("UI/Scroll Rect", 37)]
[SelectionBase]
[ExecuteAlways]
[DisallowMultipleComponent]
[RequireComponent(typeof(RectTransform))]
/// <summary>
/// A component for making a child RectTransform scroll.
/// </summary>
/// <remarks>
/// ScrollRect will not do any clipping on its own. Combined with a Mask component, it can be turned into a scroll view.
/// </remarks>
public class ScrollRect : UIBehaviour, IInitializePotentialDragHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler, ICanvasElement, ILayoutElement, ILayoutGroup
{
/// <summary>
/// A setting for which behavior to use when content moves beyond the confines of its container.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public ScrollRect myScrollRect;
/// public Scrollbar newScrollBar;
///
/// //Called when a button is pressed
/// public void Example(int option)
/// {
/// if (option == 0)
/// {
/// myScrollRect.movementType = ScrollRect.MovementType.Clamped;
/// }
/// else if (option == 1)
/// {
/// myScrollRect.movementType = ScrollRect.MovementType.Elastic;
/// }
/// else if (option == 2)
/// {
/// myScrollRect.movementType = ScrollRect.MovementType.Unrestricted;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public enum MovementType
{
/// <summary>
/// Unrestricted movement. The content can move forever.
/// </summary>
Unrestricted,
/// <summary>
/// Elastic movement. The content is allowed to temporarily move beyond the container, but is pulled back elastically.
/// </summary>
Elastic,
/// <summary>
/// Clamped movement. The content can not be moved beyond its container.
/// </summary>
Clamped,
}
/// <summary>
/// Enum for which behavior to use for scrollbar visibility.
/// </summary>
public enum ScrollbarVisibility
{
/// <summary>
/// Always show the scrollbar.
/// </summary>
Permanent,
/// <summary>
/// Automatically hide the scrollbar when no scrolling is needed on this axis. The viewport rect will not be changed.
/// </summary>
AutoHide,
/// <summary>
/// Automatically hide the scrollbar when no scrolling is needed on this axis, and expand the viewport rect accordingly.
/// </summary>
/// <remarks>
/// When this setting is used, the scrollbar and the viewport rect become driven, meaning that values in the RectTransform are calculated automatically and can't be manually edited.
/// </remarks>
AutoHideAndExpandViewport,
}
[Serializable]
/// <summary>
/// Event type used by the ScrollRect.
/// </summary>
public class ScrollRectEvent : UnityEvent<Vector2> {}
[SerializeField]
private RectTransform m_Content;
/// <summary>
/// The content that can be scrolled. It should be a child of the GameObject with ScrollRect on it.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public ScrollRect myScrollRect;
/// public RectTransform scrollableContent;
///
/// //Do this when the Save button is selected.
/// public void Start()
/// {
/// // assigns the contect that can be scrolled using the ScrollRect.
/// myScrollRect.content = scrollableContent;
/// }
/// }
/// ]]>
///</code>
/// </example>
public RectTransform content { get { return m_Content; } set { m_Content = value; } }
[SerializeField]
private bool m_Horizontal = true;
/// <summary>
/// Should horizontal scrolling be enabled?
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public ScrollRect myScrollRect;
///
/// public void Start()
/// {
/// // Is horizontal scrolling enabled?
/// if (myScrollRect.horizontal == true)
/// {
/// Debug.Log("Horizontal Scrolling is Enabled!");
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public bool horizontal { get { return m_Horizontal; } set { m_Horizontal = value; } }
[SerializeField]
private bool m_Vertical = true;
/// <summary>
/// Should vertical scrolling be enabled?
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public ScrollRect myScrollRect;
///
/// public void Start()
/// {
/// // Is Vertical scrolling enabled?
/// if (myScrollRect.vertical == true)
/// {
/// Debug.Log("Vertical Scrolling is Enabled!");
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public bool vertical { get { return m_Vertical; } set { m_Vertical = value; } }
[SerializeField]
private MovementType m_MovementType = MovementType.Elastic;
/// <summary>
/// The behavior to use when the content moves beyond the scroll rect.
/// </summary>
public MovementType movementType { get { return m_MovementType; } set { m_MovementType = value; } }
[SerializeField]
private float m_Elasticity = 0.1f;
/// <summary>
/// The amount of elasticity to use when the content moves beyond the scroll rect.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI;
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public ScrollRect myScrollRect;
///
/// public void Start()
/// {
/// // assigns a new value to the elasticity of the scroll rect.
/// // The higher the number the longer it takes to snap back.
/// myScrollRect.elasticity = 3.0f;
/// }
/// }
/// ]]>
///</code>
/// </example>
public float elasticity { get { return m_Elasticity; } set { m_Elasticity = value; } }
[SerializeField]
private bool m_Inertia = true;
/// <summary>
/// Should movement inertia be enabled?
/// </summary>
/// <remarks>
/// Inertia means that the scrollrect content will keep scrolling for a while after being dragged. It gradually slows down according to the decelerationRate.
/// </remarks>
public bool inertia { get { return m_Inertia; } set { m_Inertia = value; } }
[SerializeField]
private float m_DecelerationRate = 0.135f; // Only used when inertia is enabled
/// <summary>
/// The rate at which movement slows down.
/// </summary>
/// <remarks>
/// The deceleration rate is the speed reduction per second. A value of 0.5 halves the speed each second. The default is 0.135. The deceleration rate is only used when inertia is enabled.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public ScrollRect myScrollRect;
///
/// public void Start()
/// {
/// // assigns a new value to the decelerationRate of the scroll rect.
/// // The higher the number the longer it takes to decelerate.
/// myScrollRect.decelerationRate = 5.0f;
/// }
/// }
/// ]]>
///</code>
/// </example>
public float decelerationRate { get { return m_DecelerationRate; } set { m_DecelerationRate = value; } }
[SerializeField]
private float m_ScrollSensitivity = 1.0f;
/// <summary>
/// The sensitivity to scroll wheel and track pad scroll events.
/// </summary>
/// <remarks>
/// Higher values indicate higher sensitivity.
/// </remarks>
public float scrollSensitivity { get { return m_ScrollSensitivity; } set { m_ScrollSensitivity = value; } }
[SerializeField]
private RectTransform m_Viewport;
/// <summary>
/// Reference to the viewport RectTransform that is the parent of the content RectTransform.
/// </summary>
public RectTransform viewport { get { return m_Viewport; } set { m_Viewport = value; SetDirtyCaching(); } }
[SerializeField]
private Scrollbar m_HorizontalScrollbar;
/// <summary>
/// Optional Scrollbar object linked to the horizontal scrolling of the ScrollRect.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public ScrollRect myScrollRect;
/// public Scrollbar newScrollBar;
///
/// public void Start()
/// {
/// // Assigns a scroll bar element to the ScrollRect, allowing you to scroll in the horizontal axis.
/// myScrollRect.horizontalScrollbar = newScrollBar;
/// }
/// }
/// ]]>
///</code>
/// </example>
public Scrollbar horizontalScrollbar
{
get
{
return m_HorizontalScrollbar;
}
set
{
if (m_HorizontalScrollbar)
m_HorizontalScrollbar.onValueChanged.RemoveListener(SetHorizontalNormalizedPosition);
m_HorizontalScrollbar = value;
if (m_Horizontal && m_HorizontalScrollbar)
m_HorizontalScrollbar.onValueChanged.AddListener(SetHorizontalNormalizedPosition);
SetDirtyCaching();
}
}
[SerializeField]
private Scrollbar m_VerticalScrollbar;
/// <summary>
/// Optional Scrollbar object linked to the vertical scrolling of the ScrollRect.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public ScrollRect myScrollRect;
/// public Scrollbar newScrollBar;
///
/// public void Start()
/// {
/// // Assigns a scroll bar element to the ScrollRect, allowing you to scroll in the vertical axis.
/// myScrollRect.verticalScrollbar = newScrollBar;
/// }
/// }
/// ]]>
///</code>
/// </example>
public Scrollbar verticalScrollbar
{
get
{
return m_VerticalScrollbar;
}
set
{
if (m_VerticalScrollbar)
m_VerticalScrollbar.onValueChanged.RemoveListener(SetVerticalNormalizedPosition);
m_VerticalScrollbar = value;
if (m_Vertical && m_VerticalScrollbar)
m_VerticalScrollbar.onValueChanged.AddListener(SetVerticalNormalizedPosition);
SetDirtyCaching();
}
}
[SerializeField]
private ScrollbarVisibility m_HorizontalScrollbarVisibility;
/// <summary>
/// The mode of visibility for the horizontal scrollbar.
/// </summary>
public ScrollbarVisibility horizontalScrollbarVisibility { get { return m_HorizontalScrollbarVisibility; } set { m_HorizontalScrollbarVisibility = value; SetDirtyCaching(); } }
[SerializeField]
private ScrollbarVisibility m_VerticalScrollbarVisibility;
/// <summary>
/// The mode of visibility for the vertical scrollbar.
/// </summary>
public ScrollbarVisibility verticalScrollbarVisibility { get { return m_VerticalScrollbarVisibility; } set { m_VerticalScrollbarVisibility = value; SetDirtyCaching(); } }
[SerializeField]
private float m_HorizontalScrollbarSpacing;
/// <summary>
/// The space between the scrollbar and the viewport.
/// </summary>
public float horizontalScrollbarSpacing { get { return m_HorizontalScrollbarSpacing; } set { m_HorizontalScrollbarSpacing = value; SetDirty(); } }
[SerializeField]
private float m_VerticalScrollbarSpacing;
/// <summary>
/// The space between the scrollbar and the viewport.
/// </summary>
public float verticalScrollbarSpacing { get { return m_VerticalScrollbarSpacing; } set { m_VerticalScrollbarSpacing = value; SetDirty(); } }
[SerializeField]
private ScrollRectEvent m_OnValueChanged = new ScrollRectEvent();
/// <summary>
/// Callback executed when the position of the child changes.
/// </summary>
/// <remarks>
/// onValueChanged is used to watch for changes in the ScrollRect object.
/// The onValueChanged call will use the UnityEvent.AddListener API to watch for
/// changes. When changes happen script code provided by the user will be called.
/// The UnityEvent.AddListener API for UI.ScrollRect._onValueChanged takes a Vector2.
///
/// Note: The editor allows the onValueChanged value to be set up manually.For example the
/// value can be set to run only a runtime. The object and script function to call are also
/// provided here.
///
/// The onValueChanged variable can be alternatively set-up at runtime.The script example below
/// shows how this can be done.The script is attached to the ScrollRect object.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using UnityEngine.UI;
///
/// public class ExampleScript : MonoBehaviour
/// {
/// static ScrollRect scrollRect;
///
/// void Start()
/// {
/// scrollRect = GetComponent<ScrollRect>();
/// scrollRect.onValueChanged.AddListener(ListenerMethod);
/// }
///
/// public void ListenerMethod(Vector2 value)
/// {
/// Debug.Log("ListenerMethod: " + value);
/// }
/// }
/// ]]>
///</code>
/// </example>
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;
/// <summary>
/// The current velocity of the content.
/// </summary>
/// <remarks>
/// The velocity is defined in units per second.
/// </remarks>
public Vector2 velocity { get { return m_Velocity; } set { m_Velocity = value; } }
private bool m_Dragging;
private bool m_Scrolling;
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;
// field is never assigned warning
#pragma warning disable 649
private DrivenRectTransformTracker m_Tracker;
#pragma warning restore 649
protected ScrollRect()
{}
/// <summary>
/// Rebuilds the scroll rect data after initialization.
/// </summary>
/// <param name="executing">The current step in the rendering CanvasUpdate cycle.</param>
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_Horizontal && m_HorizontalScrollbar)
m_HorizontalScrollbar.onValueChanged.AddListener(SetHorizontalNormalizedPosition);
if (m_Vertical && m_VerticalScrollbar)
m_VerticalScrollbar.onValueChanged.AddListener(SetVerticalNormalizedPosition);
CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
SetDirty();
}
protected override void OnDisable()
{
CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild(this);
if (m_HorizontalScrollbar)
m_HorizontalScrollbar.onValueChanged.RemoveListener(SetHorizontalNormalizedPosition);
if (m_VerticalScrollbar)
m_VerticalScrollbar.onValueChanged.RemoveListener(SetVerticalNormalizedPosition);
m_Dragging = false;
m_Scrolling = false;
m_HasRebuiltLayout = false;
m_Tracker.Clear();
m_Velocity = Vector2.zero;
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
base.OnDisable();
}
/// <summary>
/// See member in base class.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public ScrollRect myScrollRect;
///
/// public void Start()
/// {
/// //Checks if the ScrollRect called "myScrollRect" is active.
/// if (myScrollRect.IsActive())
/// {
/// Debug.Log("The Scroll Rect is active!");
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public override bool IsActive()
{
return base.IsActive() && m_Content != null;
}
private void EnsureLayoutHasRebuilt()
{
if (!m_HasRebuiltLayout && !CanvasUpdateRegistry.IsRebuildingLayout())
Canvas.ForceUpdateCanvases();
}
/// <summary>
/// Sets the velocity to zero on both axes so the content stops moving.
/// </summary>
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;
}
if (data.IsScrolling())
m_Scrolling = true;
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;
}
/// <summary>
/// Handling for when the content is beging being dragged.
/// </summary>
///<example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.EventSystems; // Required when using event data
///
/// public class ExampleClass : MonoBehaviour, IBeginDragHandler // required interface when using the OnBeginDrag method.
/// {
/// //Do this when the user starts dragging the element this script is attached to..
/// public void OnBeginDrag(PointerEventData data)
/// {
/// Debug.Log("They started dragging " + this.name);
/// }
/// }
/// ]]>
///</code>
/// </example>
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;
}
/// <summary>
/// Handling for when the content has finished being dragged.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.EventSystems; // Required when using event data
///
/// public class ExampleClass : MonoBehaviour, IEndDragHandler // required interface when using the OnEndDrag method.
/// {
/// //Do this when the user stops dragging this UI Element.
/// public void OnEndDrag(PointerEventData data)
/// {
/// Debug.Log("Stopped dragging " + this.name + "!");
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual void OnEndDrag(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
m_Dragging = false;
}
/// <summary>
/// Handling for when the content is dragged.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.EventSystems; // Required when using event data
///
/// public class ExampleClass : MonoBehaviour, IDragHandler // required interface when using the OnDrag method.
/// {
/// //Do this while the user is dragging this UI Element.
/// public void OnDrag(PointerEventData data)
/// {
/// Debug.Log("Currently dragging " + this.name);
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual void OnDrag(PointerEventData eventData)
{
if (!m_Dragging)
return;
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);
}
/// <summary>
/// Sets the anchored position of the content.
/// </summary>
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();
UpdateBounds();
float deltaTime = Time.unscaledDeltaTime;
Vector2 offset = CalculateOffset(Vector2.zero);
// Skip processing if deltaTime is invalid (0 or less) as it will cause inaccurate velocity calculations and a divide by zero error.
if (deltaTime > 0.0f)
{
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];
float smoothTime = m_Elasticity;
if (m_Scrolling)
smoothTime *= 3.0f;
position[axis] = Mathf.SmoothDamp(m_Content.anchoredPosition[axis], m_Content.anchoredPosition[axis] + offset[axis], ref speed, smoothTime, 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_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();
}
UpdateScrollbarVisibility();
m_Scrolling = false;
}
/// <summary>
/// Helper function to update the previous data fields on a ScrollRect. Call this before you change data in the ScrollRect.
/// </summary>
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;
}
}
/// <summary>
/// The scroll position as a Vector2 between (0,0) and (1,1) with (0,0) being the lower left corner.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public ScrollRect myScrollRect;
/// public Vector2 myPosition = new Vector2(0.5f, 0.5f);
///
/// public void Start()
/// {
/// //Change the current scroll position.
/// myScrollRect.normalizedPosition = myPosition;
/// }
/// }
/// ]]>
///</code>
/// </example>
public Vector2 normalizedPosition
{
get
{
return new Vector2(horizontalNormalizedPosition, verticalNormalizedPosition);
}
set
{
SetNormalizedPosition(value.x, 0);
SetNormalizedPosition(value.y, 1);
}
}
/// <summary>
/// The horizontal scroll position as a value between 0 and 1, with 0 being at the left.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public ScrollRect myScrollRect;
/// public Scrollbar newScrollBar;
///
/// public void Start()
/// {
/// //Change the current horizontal scroll position.
/// myScrollRect.horizontalNormalizedPosition = 0.5f;
/// }
/// }
/// ]]>
///</code>
/// </example>
public float horizontalNormalizedPosition
{
get
{
UpdateBounds();
if ((m_ContentBounds.size.x <= m_ViewBounds.size.x) || Mathf.Approximately(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);
}
}
/// <summary>
/// The vertical scroll position as a value between 0 and 1, with 0 being at the bottom.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public ScrollRect myScrollRect;
/// public Scrollbar newScrollBar;
///
/// public void Start()
/// {
/// //Change the current vertical scroll position.
/// myScrollRect.verticalNormalizedPosition = 0.5f;
/// }
/// }
/// ]]>
///</code>
/// </example>
public float verticalNormalizedPosition
{
get
{
UpdateBounds();
if ((m_ContentBounds.size.y <= m_ViewBounds.size.y) || Mathf.Approximately(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); }
/// <summary>
/// >Set the horizontal or vertical scroll position as a value between 0 and 1, with 0 being at the left or at the bottom.
/// </summary>
/// <param name="value">The position to set, between 0 and 1.</param>
/// <param name="axis">The axis to set: 0 for horizontal, 1 for vertical.</param>
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 newAnchoredPosition = m_Content.anchoredPosition[axis] + contentBoundsMinPosition - m_ContentBounds.min[axis];
Vector3 anchoredPosition = m_Content.anchoredPosition;
if (Mathf.Abs(anchoredPosition[axis] - newAnchoredPosition) > 0.01f)
{
anchoredPosition[axis] = newAnchoredPosition;
m_Content.anchoredPosition = anchoredPosition;
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;
}
}
/// <summary>
/// Called by the layout system.
/// </summary>
public virtual void CalculateLayoutInputHorizontal() {}
/// <summary>
/// Called by the layout system.
/// </summary>
public virtual void CalculateLayoutInputVertical() {}
/// <summary>
/// Called by the layout system.
/// </summary>
public virtual float minWidth { get { return -1; } }
/// <summary>
/// Called by the layout system.
/// </summary>
public virtual float preferredWidth { get { return -1; } }
/// <summary>
/// Called by the layout system.
/// </summary>
public virtual float flexibleWidth { get { return -1; } }
/// <summary>
/// Called by the layout system.
/// </summary>
public virtual float minHeight { get { return -1; } }
/// <summary>
/// Called by the layout system.
/// </summary>
public virtual float preferredHeight { get { return -1; } }
/// <summary>
/// Called by the layout system.
/// </summary>
public virtual float flexibleHeight { get { return -1; } }
/// <summary>
/// Called by the layout system.
/// </summary>
public virtual int layoutPriority { get { return -1; } }
/// <summary>
/// Called by the layout system.
/// </summary>
public virtual void SetLayoutHorizontal()
{
m_Tracker.Clear();
UpdateCachedData();
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);
}
}
/// <summary>
/// Called by the layout system.
/// </summary>
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);
}
}
/// <summary>
/// Calculate the bounds the ScrollRect should be using.
/// </summary>
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;
// min/max offset extracted to check if approximately 0 and avoid recalculating layout every frame (case 1010178)
if (horizontal)
{
min.x += delta.x;
max.x += delta.x;
float maxOffset = viewBounds.max.x - max.x;
float minOffset = viewBounds.min.x - min.x;
if (minOffset < -0.001f)
offset.x = minOffset;
else if (maxOffset > 0.001f)
offset.x = maxOffset;
}
if (vertical)
{
min.y += delta.y;
max.y += delta.y;
float maxOffset = viewBounds.max.y - max.y;
float minOffset = viewBounds.min.y - min.y;
if (maxOffset > 0.001f)
offset.y = maxOffset;
else if (minOffset < -0.001f)
offset.y = minOffset;
}
return offset;
}
/// <summary>
/// Override to alter or add to the code that keeps the appearance of the scroll rect synced with its data.
/// </summary>
protected void SetDirty()
{
if (!IsActive())
return;
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
}
/// <summary>
/// Override to alter or add to the code that caches data to avoid repeated heavy operations.
/// </summary>
protected void SetDirtyCaching()
{
if (!IsActive())
return;
CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
m_ViewRect = null;
}
#if UNITY_EDITOR
protected override void OnValidate()
{
SetDirtyCaching();
}
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1aa08ab6e0800fa44ae55d278d1423e3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,555 @@
using System;
using System.Collections;
using UnityEngine.Events;
using UnityEngine.EventSystems;
namespace UnityEngine.UI
{
[AddComponentMenu("UI/Scrollbar", 36)]
[ExecuteAlways]
[RequireComponent(typeof(RectTransform))]
/// <summary>
/// A standard scrollbar with a variable sized handle that can be dragged between 0 and 1.
/// </summary>
/// <remarks>
/// The slider component is a Selectable that controls a handle which follow the current value and is sized according to the size property.
/// The anchors of the handle RectTransforms are driven by the Scrollbar. The handle can be a direct child of the GameObject with the Scrollbar, or intermediary RectTransforms can be placed in between for additional control.
/// When a change to the scrollbar value occurs, a callback is sent to any registered listeners of onValueChanged.
/// </remarks>
public class Scrollbar : Selectable, IBeginDragHandler, IDragHandler, IInitializePotentialDragHandler, ICanvasElement
{
/// <summary>
/// Setting that indicates one of four directions the scrollbar will travel.
/// </summary>
public enum Direction
{
/// <summary>
/// Starting position is the Left.
/// </summary>
LeftToRight,
/// <summary>
/// Starting position is the Right
/// </summary>
RightToLeft,
/// <summary>
/// Starting position is the Bottom.
/// </summary>
BottomToTop,
/// <summary>
/// Starting position is the Top.
/// </summary>
TopToBottom,
}
[Serializable]
/// <summary>
/// UnityEvent callback for when a scrollbar is scrolled.
/// </summary>
public class ScrollEvent : UnityEvent<float> {}
[SerializeField]
private RectTransform m_HandleRect;
/// <summary>
/// The RectTransform to use for the handle.
/// </summary>
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;
/// <summary>
/// The direction of the scrollbar from minimum to maximum value.
/// </summary>
public Direction direction { get { return m_Direction; } set { if (SetPropertyUtility.SetStruct(ref m_Direction, value)) UpdateVisuals(); } }
protected Scrollbar()
{}
[Range(0f, 1f)]
[SerializeField]
private float m_Value;
/// <summary>
/// The current value of the scrollbar, between 0 and 1.
/// </summary>
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);
}
}
/// <summary>
/// Set the value of the scrollbar without invoking onValueChanged callback.
/// </summary>
/// <param name="input">The new value for the scrollbar.</param>
public virtual void SetValueWithoutNotify(float input)
{
Set(input, false);
}
[Range(0f, 1f)]
[SerializeField]
private float m_Size = 0.2f;
/// <summary>
/// The size of the scrollbar handle where 1 means it fills the entire scrollbar.
/// </summary>
public float size { get { return m_Size; } set { if (SetPropertyUtility.SetStruct(ref m_Size, Mathf.Clamp01(value))) UpdateVisuals(); } }
[Range(0, 11)]
[SerializeField]
private int m_NumberOfSteps = 0;
/// <summary>
/// The number of steps to use for the value. A value of 0 disables use of steps.
/// </summary>
public int numberOfSteps { get { return m_NumberOfSteps; } set { if (SetPropertyUtility.SetStruct(ref m_NumberOfSteps, value)) { Set(m_Value); UpdateVisuals(); } } }
[Space(6)]
[SerializeField]
private ScrollEvent m_OnValueChanged = new ScrollEvent();
/// <summary>
/// Handling for when the scrollbar value is changed.
/// </summary>
/// <remarks>
/// Allow for delegate-based subscriptions for faster events than 'eventReceiver', and allowing for multiple receivers.
/// </remarks>
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; } }
// field is never assigned warning
#pragma warning disable 649
private DrivenRectTransformTracker m_Tracker;
#pragma warning restore 649
private Coroutine m_PointerDownRepeat;
private bool isPointerDownAndNotDragging = false;
// This "delayed" mechanism is required for case 1037681.
private bool m_DelayedUpdateVisuals = 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 (in next update) since other things might affect them even if value didn't change.
m_DelayedUpdateVisuals = true;
}
if (!UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this) && !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
}
/// <summary>
/// See ICanvasElement.LayoutComplete.
/// </summary>
public virtual void LayoutComplete()
{}
/// <summary>
/// See ICanvasElement.GraphicUpdateComplete.
/// </summary>
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();
}
/// <summary>
/// Update the rect based on the delayed update visuals.
/// Got around issue of calling sendMessage from onValidate.
/// </summary>
protected virtual void Update()
{
if (m_DelayedUpdateVisuals)
{
m_DelayedUpdateVisuals = false;
UpdateVisuals();
}
}
void UpdateCachedReferences()
{
if (m_HandleRect && m_HandleRect.parent != null)
m_ContainerRect = m_HandleRect.parent.GetComponent<RectTransform>();
else
m_ContainerRect = null;
}
void Set(float input, bool sendCallback = true)
{
float currentValue = m_Value;
// bugfix (case 802330) clamp01 input in callee before calling this function, this allows inertia from dragging content to go past extremities without being clamped
m_Value = 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 = Mathf.Clamp01(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 position = Vector2.zero;
if (!MultipleDisplayUtilities.GetRelativeMousePositionForDrag(eventData, ref position))
return;
Vector2 localCursor;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(m_ContainerRect, 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;
DoUpdateDrag(handleCorner, remainingSize);
}
//this function is testable, it is found using reflection in ScrollbarClamp test
private void DoUpdateDrag(Vector2 handleCorner, float remainingSize)
{
switch (m_Direction)
{
case Direction.LeftToRight:
Set(Mathf.Clamp01(handleCorner.x / remainingSize));
break;
case Direction.RightToLeft:
Set(Mathf.Clamp01(1f - (handleCorner.x / remainingSize)));
break;
case Direction.BottomToTop:
Set(Mathf.Clamp01(handleCorner.y / remainingSize));
break;
case Direction.TopToBottom:
Set(Mathf.Clamp01(1f - (handleCorner.y / remainingSize)));
break;
}
}
private bool MayDrag(PointerEventData eventData)
{
return IsActive() && IsInteractable() && eventData.button == PointerEventData.InputButton.Left;
}
/// <summary>
/// Handling for when the scrollbar value is begin being dragged.
/// </summary>
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.pointerPressRaycast.screenPosition, eventData.enterEventCamera))
{
Vector2 localMousePos;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_HandleRect, eventData.pointerPressRaycast.screenPosition, eventData.pressEventCamera, out localMousePos))
m_Offset = localMousePos - m_HandleRect.rect.center;
}
}
/// <summary>
/// Handling for when the scrollbar value is dragged.
/// </summary>
public virtual void OnDrag(PointerEventData eventData)
{
if (!MayDrag(eventData))
return;
if (m_ContainerRect != null)
UpdateDrag(eventData);
}
/// <summary>
/// Event triggered when pointer is pressed down on the scrollbar.
/// </summary>
public override void OnPointerDown(PointerEventData eventData)
{
if (!MayDrag(eventData))
return;
base.OnPointerDown(eventData);
isPointerDownAndNotDragging = true;
m_PointerDownRepeat = StartCoroutine(ClickRepeat(eventData.pointerPressRaycast.screenPosition, eventData.enterEventCamera));
}
protected IEnumerator ClickRepeat(PointerEventData eventData)
{
return ClickRepeat(eventData.pointerPressRaycast.screenPosition, eventData.enterEventCamera);
}
/// <summary>
/// Coroutine function for handling continual press during Scrollbar.OnPointerDown.
/// </summary>
protected IEnumerator ClickRepeat(Vector2 screenPosition, Camera camera)
{
while (isPointerDownAndNotDragging)
{
if (!RectTransformUtility.RectangleContainsScreenPoint(m_HandleRect, screenPosition, camera))
{
Vector2 localMousePos;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_HandleRect, screenPosition, camera, out localMousePos))
{
var axisCoordinate = axis == 0 ? localMousePos.x : localMousePos.y;
// modifying value depending on direction, fixes (case 925824)
float change = axisCoordinate < 0 ? size : -size;
value += reverseValue ? change : -change;
value = Mathf.Clamp01(value);
// Only keep 4 decimals of precision
value = Mathf.Round(value * 10000f) / 10000f;
}
}
yield return new WaitForEndOfFrame();
}
StopCoroutine(m_PointerDownRepeat);
}
/// <summary>
/// Event triggered when pointer is released after pressing on the scrollbar.
/// </summary>
public override void OnPointerUp(PointerEventData eventData)
{
base.OnPointerUp(eventData);
isPointerDownAndNotDragging = false;
}
/// <summary>
/// Handling for movement events.
/// </summary>
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(Mathf.Clamp01(reverseValue ? value + stepSize : value - stepSize));
else
base.OnMove(eventData);
break;
case MoveDirection.Right:
if (axis == Axis.Horizontal && FindSelectableOnRight() == null)
Set(Mathf.Clamp01(reverseValue ? value - stepSize : value + stepSize));
else
base.OnMove(eventData);
break;
case MoveDirection.Up:
if (axis == Axis.Vertical && FindSelectableOnUp() == null)
Set(Mathf.Clamp01(reverseValue ? value - stepSize : value + stepSize));
else
base.OnMove(eventData);
break;
case MoveDirection.Down:
if (axis == Axis.Vertical && FindSelectableOnDown() == null)
Set(Mathf.Clamp01(reverseValue ? value + stepSize : value - stepSize));
else
base.OnMove(eventData);
break;
}
}
/// <summary>
/// Prevents selection if we we move on the Horizontal axis. See Selectable.FindSelectableOnLeft.
/// </summary>
public override Selectable FindSelectableOnLeft()
{
if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Horizontal)
return null;
return base.FindSelectableOnLeft();
}
/// <summary>
/// Prevents selection if we we move on the Horizontal axis. See Selectable.FindSelectableOnRight.
/// </summary>
public override Selectable FindSelectableOnRight()
{
if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Horizontal)
return null;
return base.FindSelectableOnRight();
}
/// <summary>
/// Prevents selection if we we move on the Vertical axis. See Selectable.FindSelectableOnUp.
/// </summary>
public override Selectable FindSelectableOnUp()
{
if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Vertical)
return null;
return base.FindSelectableOnUp();
}
/// <summary>
/// Prevents selection if we we move on the Vertical axis. See Selectable.FindSelectableOnDown.
/// </summary>
public override Selectable FindSelectableOnDown()
{
if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Vertical)
return null;
return base.FindSelectableOnDown();
}
/// <summary>
/// See: IInitializePotentialDragHandler.OnInitializePotentialDrag
/// </summary>
public virtual void OnInitializePotentialDrag(PointerEventData eventData)
{
eventData.useDragThreshold = false;
}
/// <summary>
/// Set the direction of the scrollbar, optionally setting the layout as well.
/// </summary>
/// <param name="direction">The direction of the scrollbar.</param>
/// <param name="includeRectLayouts">Should the layout be flipped together with the direction?</param>
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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2a4db7a114972834c8e4117be1d82ba3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,1394 @@
using System;
using System.Collections.Generic;
using UnityEngine.Serialization;
using UnityEngine.EventSystems;
namespace UnityEngine.UI
{
[AddComponentMenu("UI/Selectable", 35)]
[ExecuteAlways]
[SelectionBase]
[DisallowMultipleComponent]
/// <summary>
/// Simple selectable object - derived from to create a selectable control.
/// </summary>
public class Selectable
:
UIBehaviour,
IMoveHandler,
IPointerDownHandler, IPointerUpHandler,
IPointerEnterHandler, IPointerExitHandler,
ISelectHandler, IDeselectHandler
{
protected static Selectable[] s_Selectables = new Selectable[10];
protected static int s_SelectableCount = 0;
private bool m_EnableCalled = false;
/// <summary>
/// Copy of the array of all the selectable objects currently active in the scene.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // required when using UI elements in scripts
///
/// public class Example : MonoBehaviour
/// {
/// //Displays the names of all selectable elements in the scene
/// public void GetNames()
/// {
/// foreach (Selectable selectableUI in Selectable.allSelectablesArray)
/// {
/// Debug.Log(selectableUI.name);
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public static Selectable[] allSelectablesArray
{
get
{
Selectable[] temp = new Selectable[s_SelectableCount];
Array.Copy(s_Selectables, temp, s_SelectableCount);
return temp;
}
}
/// <summary>
/// How many selectable elements are currently active.
/// </summary>
public static int allSelectableCount { get { return s_SelectableCount; } }
/// <summary>
/// A List instance of the allSelectablesArray to maintain API compatibility.
/// </summary>
[Obsolete("Replaced with allSelectablesArray to have better performance when disabling a element", false)]
public static List<Selectable> allSelectables
{
get
{
return new List<Selectable>(allSelectablesArray);
}
}
/// <summary>
/// Non allocating version for getting the all selectables.
/// If selectables.Length is less then s_SelectableCount only selectables.Length elments will be copied which
/// could result in a incomplete list of elements.
/// </summary>
/// <param name="selectables">The array to be filled with current selectable objects</param>
/// <returns>The number of element copied.</returns>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // required when using UI elements in scripts
///
/// public class Example : MonoBehaviour
/// {
/// Selectable[] m_Selectables = new Selectable[10];
///
/// //Displays the names of all selectable elements in the scene
/// public void GetNames()
/// {
/// if (m_Selectables.Length < Selectable.allSelectableCount)
/// m_Selectables = new Selectable[Selectable.allSelectableCount];
///
/// int count = Selectable.AllSelectablesNoAlloc(ref m_Selectables);
///
/// for (int i = 0; i < count; ++i)
/// {
/// Debug.Log(m_Selectables[i].name);
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public static int AllSelectablesNoAlloc(Selectable[] selectables)
{
int copyCount = selectables.Length < s_SelectableCount ? selectables.Length : s_SelectableCount;
Array.Copy(s_Selectables, selectables, copyCount);
return copyCount;
}
// Navigation information.
[FormerlySerializedAs("navigation")]
[SerializeField]
private Navigation m_Navigation = Navigation.defaultNavigation;
/// <summary>
///Transition mode for a Selectable.
/// </summary>
public enum Transition
{
/// <summary>
/// No Transition.
/// </summary>
None,
/// <summary>
/// Use an color tint transition.
/// </summary>
ColorTint,
/// <summary>
/// Use a sprite swap transition.
/// </summary>
SpriteSwap,
/// <summary>
/// Use an animation transition.
/// </summary>
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;
// Graphic that will be colored.
[FormerlySerializedAs("highlightGraphic")]
[FormerlySerializedAs("m_HighlightGraphic")]
[SerializeField]
private Graphic m_TargetGraphic;
private bool m_GroupsAllowInteraction = true;
protected int m_CurrentIndex = -1;
/// <summary>
/// The Navigation setting for this selectable object.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Button button;
///
/// void Start()
/// {
/// //Set the navigation to the default value. ("Automatic" is the default value).
/// button.navigation = Navigation.defaultNavigation;
/// }
/// }
/// ]]>
///</code>
/// </example>
public Navigation navigation { get { return m_Navigation; } set { if (SetPropertyUtility.SetStruct(ref m_Navigation, value)) OnSetProperty(); } }
/// <summary>
/// The type of transition that will be applied to the targetGraphic when the state changes.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Button btnMain;
///
/// void SomeFunction()
/// {
/// //Sets the main button's transition setting to "Color Tint".
/// btnMain.transition = Selectable.Transition.ColorTint;
/// }
/// }
/// ]]>
///</code>
/// </example>
public Transition transition { get { return m_Transition; } set { if (SetPropertyUtility.SetStruct(ref m_Transition, value)) OnSetProperty(); } }
/// <summary>
/// The ColorBlock for this selectable object.
/// </summary>
/// <remarks>
/// Modifications will not be visible if transition is not ColorTint.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Button button;
///
/// void Start()
/// {
/// //Resets the colors in the buttons transitions.
/// button.colors = ColorBlock.defaultColorBlock;
/// }
/// }
/// ]]>
///</code>
/// </example>
public ColorBlock colors { get { return m_Colors; } set { if (SetPropertyUtility.SetStruct(ref m_Colors, value)) OnSetProperty(); } }
/// <summary>
/// The SpriteState for this selectable object.
/// </summary>
/// <remarks>
/// Modifications will not be visible if transition is not SpriteSwap.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// //Creates an instance of a sprite state (This includes the highlighted, pressed and disabled sprite.
/// public SpriteState sprState = new SpriteState();
/// public Button btnMain;
///
///
/// void Start()
/// {
/// //Assigns the new sprite states to the button.
/// btnMain.spriteState = sprState;
/// }
/// }
/// ]]>
/// </code>
/// </example>
public SpriteState spriteState { get { return m_SpriteState; } set { if (SetPropertyUtility.SetStruct(ref m_SpriteState, value)) OnSetProperty(); } }
/// <summary>
/// The AnimationTriggers for this selectable object.
/// </summary>
/// <remarks>
/// Modifications will not be visible if transition is not Animation.
/// </remarks>
public AnimationTriggers animationTriggers { get { return m_AnimationTriggers; } set { if (SetPropertyUtility.SetClass(ref m_AnimationTriggers, value)) OnSetProperty(); } }
/// <summary>
/// Graphic that will be transitioned upon.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Image newImage;
/// public Button btnMain;
///
/// void SomeFunction()
/// {
/// //Displays the sprite transitions on the image when the transition to Highlighted,pressed or disabled is made.
/// btnMain.targetGraphic = newImage;
/// }
/// }
/// ]]>
///</code>
/// </example>
public Graphic targetGraphic { get { return m_TargetGraphic; } set { if (SetPropertyUtility.SetClass(ref m_TargetGraphic, value)) OnSetProperty(); } }
/// <summary>
/// Is this object interactable.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // required when using UI elements in scripts
///
/// public class Example : MonoBehaviour
/// {
/// public Button startButton;
/// public bool playersReady;
///
///
/// void Update()
/// {
/// // checks if the players are ready and if the start button is useable
/// if (playersReady == true && startButton.interactable == false)
/// {
/// //allows the start button to be used
/// startButton.interactable = true;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
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);
OnSetProperty();
}
}
}
private bool isPointerInside { get; set; }
private bool isPointerDown { get; set; }
private bool hasSelection { get; set; }
protected Selectable()
{}
/// <summary>
/// Convenience function that converts the referenced Graphic to a Image, if possible.
/// </summary>
public Image image
{
get { return m_TargetGraphic as Image; }
set { m_TargetGraphic = value; }
}
/// <summary>
/// Convenience function to get the Animator component on the GameObject.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class ExampleClass : MonoBehaviour
/// {
/// private Animator buttonAnimator;
/// public Button button;
///
/// void Start()
/// {
/// //Assigns the "buttonAnimator" with the button's animator.
/// buttonAnimator = button.animator;
/// }
/// }
/// ]]>
///</code>
/// </example>
#if PACKAGE_ANIMATION
public Animator animator
{
get { return GetComponent<Animator>(); }
}
#endif
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()
{
var parentGroupAllowsInteraction = ParentGroupAllowsInteraction();
if (parentGroupAllowsInteraction != m_GroupsAllowInteraction)
{
m_GroupsAllowInteraction = parentGroupAllowsInteraction;
OnSetProperty();
}
}
bool ParentGroupAllowsInteraction()
{
Transform t = transform;
while (t != null)
{
t.GetComponents(m_CanvasGroupCache);
for (var i = 0; i < m_CanvasGroupCache.Count; i++)
{
if (m_CanvasGroupCache[i].enabled && !m_CanvasGroupCache[i].interactable)
return false;
if (m_CanvasGroupCache[i].ignoreParentGroups)
return true;
}
t = t.parent;
}
return true;
}
/// <summary>
/// Is the object interactable.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // required when using UI elements in scripts
///
/// public class Example : MonoBehaviour
/// {
/// public Button startButton;
///
/// void Update()
/// {
/// if (!startButton.IsInteractable())
/// {
/// Debug.Log("Start Button has been Disabled");
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
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()
{
//Check to avoid multiple OnEnable() calls for each selectable
if (m_EnableCalled)
return;
base.OnEnable();
if (s_SelectableCount == s_Selectables.Length)
{
Selectable[] temp = new Selectable[s_Selectables.Length * 2];
Array.Copy(s_Selectables, temp, s_Selectables.Length);
s_Selectables = temp;
}
if (EventSystem.current && EventSystem.current.currentSelectedGameObject == gameObject)
{
hasSelection = true;
}
m_CurrentIndex = s_SelectableCount;
s_Selectables[m_CurrentIndex] = this;
s_SelectableCount++;
isPointerDown = false;
m_GroupsAllowInteraction = ParentGroupAllowsInteraction();
DoStateTransition(currentSelectionState, true);
m_EnableCalled = true;
}
protected override void OnTransformParentChanged()
{
base.OnTransformParentChanged();
// If our parenting changes figure out if we are under a new CanvasGroup.
OnCanvasGroupChanged();
}
private void OnSetProperty()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
DoStateTransition(currentSelectionState, true);
else
#endif
DoStateTransition(currentSelectionState, false);
}
// Remove from the list.
protected override void OnDisable()
{
//Check to avoid multiple OnDisable() calls for each selectable
if (!m_EnableCalled)
return;
s_SelectableCount--;
// Update the last elements index to be this index
s_Selectables[s_SelectableCount].m_CurrentIndex = m_CurrentIndex;
// Swap the last element and this element
s_Selectables[m_CurrentIndex] = s_Selectables[s_SelectableCount];
// null out last element.
s_Selectables[s_SelectableCount] = null;
InstantClearState();
base.OnDisable();
m_EnableCalled = false;
}
void OnApplicationFocus(bool hasFocus)
{
if (!hasFocus && IsPressed())
{
InstantClearState();
}
}
#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.
DoStateTransition(currentSelectionState, true);
}
}
protected override void Reset()
{
m_TargetGraphic = GetComponent<Graphic>();
}
#endif // if UNITY_EDITOR
protected SelectionState currentSelectionState
{
get
{
if (!IsInteractable())
return SelectionState.Disabled;
if (isPointerDown)
return SelectionState.Pressed;
if (hasSelection)
return SelectionState.Selected;
if (isPointerInside)
return SelectionState.Highlighted;
return SelectionState.Normal;
}
}
/// <summary>
/// Clear any internal state from the Selectable (used when disabling).
/// </summary>
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;
}
}
/// <summary>
/// Transition the Selectable to the entered state.
/// </summary>
/// <param name="state">State to transition to</param>
/// <param name="instant">Should the transition occur instantly.</param>
protected virtual void DoStateTransition(SelectionState state, bool instant)
{
if (!gameObject.activeInHierarchy)
return;
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.Selected:
tintColor = m_Colors.selectedColor;
transitionSprite = m_SpriteState.selectedSprite;
triggerName = m_AnimationTriggers.selectedTrigger;
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;
}
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;
}
}
/// <summary>
/// An enumeration of selected states of objects
/// </summary>
protected enum SelectionState
{
/// <summary>
/// The UI object can be selected.
/// </summary>
Normal,
/// <summary>
/// The UI object is highlighted.
/// </summary>
Highlighted,
/// <summary>
/// The UI object is pressed.
/// </summary>
Pressed,
/// <summary>
/// The UI object is selected
/// </summary>
Selected,
/// <summary>
/// The UI object cannot be selected.
/// </summary>
Disabled,
}
// Selection logic
/// <summary>
/// Finds the selectable object next to this one.
/// </summary>
/// <remarks>
/// The direction is determined by a Vector3 variable.
/// </remarks>
/// <param name="dir">The direction in which to search for a neighbouring Selectable object.</param>
/// <returns>The neighbouring Selectable object. Null if none found.</returns>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // required when using UI elements in scripts
///
/// public class ExampleClass : MonoBehaviour
/// {
/// //Sets the direction as "Up" (Y is in positive).
/// public Vector3 direction = new Vector3(0, 1, 0);
/// public Button btnMain;
///
/// public void Start()
/// {
/// //Finds and assigns the selectable above the main button
/// Selectable newSelectable = btnMain.FindSelectable(direction);
///
/// Debug.Log(newSelectable.name);
/// }
/// }
/// ]]>
///</code>
/// </example>
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;
float maxFurthestScore = Mathf.NegativeInfinity;
float score = 0;
bool wantsWrapAround = navigation.wrapAround && (m_Navigation.mode == Navigation.Mode.Vertical || m_Navigation.mode == Navigation.Mode.Horizontal);
Selectable bestPick = null;
Selectable bestFurthestPick = null;
for (int i = 0; i < s_SelectableCount; ++i)
{
Selectable sel = s_Selectables[i];
if (sel == this)
continue;
if (!sel.IsInteractable() || sel.navigation.mode == Navigation.Mode.None)
continue;
#if UNITY_EDITOR
// Apart from runtime use, FindSelectable is used by custom editors to
// draw arrows between different selectables. For scene view cameras,
// only selectables in the same stage should be considered.
if (Camera.current != null && !UnityEditor.SceneManagement.StageUtility.IsGameObjectRenderedByCamera(sel.gameObject, Camera.current))
continue;
#endif
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);
// If element is in wrong direction and we have wrapAround enabled check and cache it if furthest away.
if (wantsWrapAround && dot < 0)
{
score = -dot * myVector.sqrMagnitude;
if (score > maxFurthestScore)
{
maxFurthestScore = score;
bestFurthestPick = sel;
}
continue;
}
// 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.
score = dot / myVector.sqrMagnitude;
if (score > maxScore)
{
maxScore = score;
bestPick = sel;
}
}
if (wantsWrapAround && null == bestPick) return bestFurthestPick;
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;
}
/// <summary>
/// Find the selectable object to the left of this one.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // required when using UI elements in scripts
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Button btnMain;
///
/// // Disables the selectable UI element directly to the left of the Start Button
/// public void IgnoreSelectables()
/// {
/// //Finds the selectable UI element to the left the start button and assigns it to a variable of type "Selectable"
/// Selectable secondButton = startButton.FindSelectableOnLeft();
/// //Disables interaction with the selectable UI element
/// secondButton.interactable = false;
/// }
/// }
/// ]]>
///</code>
/// </example>
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;
}
/// <summary>
/// Find the selectable object to the right of this one.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // required when using UI elements in scripts
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Button btnMain;
///
/// // Disables the selectable UI element directly to the right the Start Button
/// public void IgnoreSelectables()
/// {
/// //Finds the selectable UI element to the right the start button and assigns it to a variable of type "Selectable"
/// Selectable secondButton = startButton.FindSelectableOnRight();
/// //Disables interaction with the selectable UI element
/// secondButton.interactable = false;
/// }
/// }
/// ]]>
///</code>
/// </example>
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;
}
/// <summary>
/// The Selectable object above current
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // required when using UI elements in scripts
///
/// public class ExampleClass : MonoBehaviour
/// {
/// public Button btnMain;
///
/// // Disables the selectable UI element directly above the Start Button
/// public void IgnoreSelectables()
/// {
/// //Finds the selectable UI element above the start button and assigns it to a variable of type "Selectable"
/// Selectable secondButton = startButton.FindSelectableOnUp();
/// //Disables interaction with the selectable UI element
/// secondButton.interactable = false;
/// }
/// }
/// ]]>
///</code>
/// </example>
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;
}
/// <summary>
/// Find the selectable object below this one.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // required when using UI elements in scripts
///
/// public class Example : MonoBehaviour
/// {
/// public Button startButton;
///
/// // Disables the selectable UI element directly below the Start Button
/// public void IgnoreSelectables()
/// {
/// //Finds the selectable UI element below the start button and assigns it to a variable of type "Selectable"
/// Selectable secondButton = startButton.FindSelectableOnDown();
/// //Disables interaction with the selectable UI element
/// secondButton.interactable = false;
/// }
/// }
/// ]]>
///</code>
/// </example>
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;
}
/// <summary>
/// Determine in which of the 4 move directions the next selectable object should be found.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI;
/// using UnityEngine.EventSystems;// Required when using Event data.
///
/// public class ExampleClass : MonoBehaviour, IMoveHandler
/// {
/// //When the focus moves to another selectable object, Invoke this Method.
/// public void OnMove(AxisEventData eventData)
/// {
/// //Assigns the move direction and the raw input vector representing the direction from the event data.
/// MoveDirection moveDir = eventData.moveDir;
/// Vector2 moveVector = eventData.moveVector;
///
/// //Displays the information in the console
/// Debug.Log(moveDir + ", " + moveVector);
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual void OnMove(AxisEventData eventData)
{
switch (eventData.moveDir)
{
case MoveDirection.Right:
Navigate(eventData, FindSelectableOnRight());
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;
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 PACKAGE_ANIMATION
if (transition != Transition.Animation || animator == null || !animator.isActiveAndEnabled || !animator.hasBoundPlayables || string.IsNullOrEmpty(triggername))
return;
animator.ResetTrigger(m_AnimationTriggers.normalTrigger);
animator.ResetTrigger(m_AnimationTriggers.highlightedTrigger);
animator.ResetTrigger(m_AnimationTriggers.pressedTrigger);
animator.ResetTrigger(m_AnimationTriggers.selectedTrigger);
animator.ResetTrigger(m_AnimationTriggers.disabledTrigger);
animator.SetTrigger(triggername);
#endif
}
/// <summary>
/// Returns whether the selectable is currently 'highlighted' or not.
/// </summary>
/// <remarks>
/// Use this to check if the selectable UI element is currently highlighted.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// //Create a UI element. To do this go to Create>UI and select from the list. Attach this script to the UI GameObject to see this script working. The script also works with non-UI elements, but highlighting works better with UI.
///
/// using UnityEngine;
/// using UnityEngine.Events;
/// using UnityEngine.EventSystems;
/// using UnityEngine.UI;
///
/// //Use the Selectable class as a base class to access the IsHighlighted method
/// public class Example : Selectable
/// {
/// //Use this to check what Events are happening
/// BaseEventData m_BaseEvent;
///
/// void Update()
/// {
/// //Check if the GameObject is being highlighted
/// if (IsHighlighted())
/// {
/// //Output that the GameObject was highlighted, or do something else
/// Debug.Log("Selectable is Highlighted");
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
protected bool IsHighlighted()
{
if (!IsActive() || !IsInteractable())
return false;
return isPointerInside && !isPointerDown && !hasSelection;
}
/// <summary>
/// Whether the current selectable is being pressed.
/// </summary>
protected bool IsPressed()
{
if (!IsActive() || !IsInteractable())
return false;
return isPointerDown;
}
// Change the button to the correct state
private void EvaluateAndTransitionToSelectionState()
{
if (!IsActive() || !IsInteractable())
return;
DoStateTransition(currentSelectionState, false);
}
/// <summary>
/// Evaluate current state and transition to pressed state.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI;
/// using UnityEngine.EventSystems;// Required when using Event data.
///
/// public class ExampleClass : MonoBehaviour, IPointerDownHandler// required interface when using the OnPointerDown method.
/// {
/// //Do this when the mouse is clicked over the selectable object this script is attached to.
/// public void OnPointerDown(PointerEventData eventData)
/// {
/// Debug.Log(this.gameObject.name + " Was Clicked.");
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual void OnPointerDown(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
// Selection tracking
if (IsInteractable() && navigation.mode != Navigation.Mode.None && EventSystem.current != null)
EventSystem.current.SetSelectedGameObject(gameObject, eventData);
isPointerDown = true;
EvaluateAndTransitionToSelectionState();
}
/// <summary>
/// Evaluate eventData and transition to appropriate state.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI;
/// using UnityEngine.EventSystems;// Required when using Event data.
///
/// public class ExampleClass : MonoBehaviour, IPointerUpHandler, IPointerDownHandler// These are the interfaces the OnPointerUp method requires.
/// {
/// //OnPointerDown is also required to receive OnPointerUp callbacks
/// public void OnPointerDown(PointerEventData eventData)
/// {
/// }
///
/// //Do this when the mouse click on this selectable UI object is released.
/// public void OnPointerUp(PointerEventData eventData)
/// {
/// Debug.Log("The mouse click was released");
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual void OnPointerUp(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
isPointerDown = false;
EvaluateAndTransitionToSelectionState();
}
/// <summary>
/// Evaluate current state and transition to appropriate state.
/// New state could be pressed or hover depending on pressed state.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI;
/// using UnityEngine.EventSystems;// Required when using Event data.
///
/// public class ExampleClass : MonoBehaviour, IPointerEnterHandler// required interface when using the OnPointerEnter method.
/// {
/// //Do this when the cursor enters the rect area of this selectable UI object.
/// public void OnPointerEnter(PointerEventData eventData)
/// {
/// Debug.Log("The cursor entered the selectable UI element.");
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual void OnPointerEnter(PointerEventData eventData)
{
isPointerInside = true;
EvaluateAndTransitionToSelectionState();
}
/// <summary>
/// Evaluate current state and transition to normal state.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI;
/// using UnityEngine.EventSystems;// Required when using Event data.
///
/// public class ExampleClass : MonoBehaviour, IPointerExitHandler// required interface when using the OnPointerExit method.
/// {
/// //Do this when the cursor exits the rect area of this selectable UI object.
/// public void OnPointerExit(PointerEventData eventData)
/// {
/// Debug.Log("The cursor exited the selectable UI element.");
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual void OnPointerExit(PointerEventData eventData)
{
isPointerInside = false;
EvaluateAndTransitionToSelectionState();
}
/// <summary>
/// Set selection and transition to appropriate state.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI;
/// using UnityEngine.EventSystems;// Required when using Event data.
///
/// public class ExampleClass : MonoBehaviour, ISelectHandler// required interface when using the OnSelect method.
/// {
/// //Do this when the selectable UI object is selected.
/// public void OnSelect(BaseEventData eventData)
/// {
/// Debug.Log(this.gameObject.name + " was selected");
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual void OnSelect(BaseEventData eventData)
{
hasSelection = true;
EvaluateAndTransitionToSelectionState();
}
/// <summary>
/// Unset selection and transition to appropriate state.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI;
/// using UnityEngine.EventSystems;// Required when using Event data.
///
/// public class ExampleClass : MonoBehaviour, IDeselectHandler //This Interface is required to receive OnDeselect callbacks.
/// {
/// public void OnDeselect(BaseEventData data)
/// {
/// Debug.Log("Deselected");
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual void OnDeselect(BaseEventData eventData)
{
hasSelection = false;
EvaluateAndTransitionToSelectionState();
}
/// <summary>
/// Selects this Selectable.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // required when using UI elements in scripts
/// using UnityEngine.EventSystems;// Required when using Event data.
///
/// public class ExampleClass : MonoBehaviour// required interface when using the OnSelect method.
/// {
/// public InputField myInputField;
///
/// //Do this OnClick.
/// public void SaveGame()
/// {
/// //Makes the Input Field the selected UI Element.
/// myInputField.Select();
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual void Select()
{
if (EventSystem.current == null || EventSystem.current.alreadySelecting)
return;
EventSystem.current.SetSelectedGameObject(gameObject);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7a98125502f715b4b83cfb77b434e436
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using UnityEngine.Events;
namespace UnityEngine.UI
{
internal static class SetPropertyUtility
{
public static bool SetColor(ref Color currentValue, Color newValue)
{
if (currentValue.r == newValue.r && currentValue.g == newValue.g && currentValue.b == newValue.b && currentValue.a == newValue.a)
return false;
currentValue = newValue;
return true;
}
public static bool SetStruct<T>(ref T currentValue, T newValue) where T : struct
{
if (EqualityComparer<T>.Default.Equals(currentValue, newValue))
return false;
currentValue = newValue;
return true;
}
public static bool SetClass<T>(ref T currentValue, T newValue) where T : class
{
if ((currentValue == null && newValue == null) || (currentValue != null && currentValue.Equals(newValue)))
return false;
currentValue = newValue;
return true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b9ba34e0b1884454fbc4257260e22b64
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,782 @@
using System;
using UnityEngine.Events;
using UnityEngine.EventSystems;
namespace UnityEngine.UI
{
[AddComponentMenu("UI/Slider", 34)]
[ExecuteAlways]
[RequireComponent(typeof(RectTransform))]
/// <summary>
/// A standard slider that can be moved between a minimum and maximum value.
/// </summary>
/// <remarks>
/// The slider component is a Selectable that controls a fill, a handle, or both. The fill, when used, spans from the minimum value to the current value while the handle, when used, follow the current value.
/// The anchors of the fill and handle RectTransforms are driven by the Slider. The fill and handle can be direct children of the GameObject with the Slider, or intermediary RectTransforms can be placed in between for additional control.
/// When a change to the slider value occurs, a callback is sent to any registered listeners of UI.Slider.onValueChanged.
/// </remarks>
public class Slider : Selectable, IDragHandler, IInitializePotentialDragHandler, ICanvasElement
{
/// <summary>
/// Setting that indicates one of four directions.
/// </summary>
public enum Direction
{
/// <summary>
/// From the left to the right
/// </summary>
LeftToRight,
/// <summary>
/// From the right to the left
/// </summary>
RightToLeft,
/// <summary>
/// From the bottom to the top.
/// </summary>
BottomToTop,
/// <summary>
/// From the top to the bottom.
/// </summary>
TopToBottom,
}
[Serializable]
/// <summary>
/// Event type used by the UI.Slider.
/// </summary>
public class SliderEvent : UnityEvent<float> {}
[SerializeField]
private RectTransform m_FillRect;
/// <summary>
/// Optional RectTransform to use as fill for the slider.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
/// //Reference to new "RectTransform"(Child of FillArea).
/// public RectTransform newFillRect;
///
/// //Deactivates the old FillRect and assigns a new one.
/// void Start()
/// {
/// mainSlider.fillRect.gameObject.SetActive(false);
/// mainSlider.fillRect = newFillRect;
/// }
/// }
/// ]]>
///</code>
/// </example>
public RectTransform fillRect { get { return m_FillRect; } set { if (SetPropertyUtility.SetClass(ref m_FillRect, value)) {UpdateCachedReferences(); UpdateVisuals(); } } }
[SerializeField]
private RectTransform m_HandleRect;
/// <summary>
/// Optional RectTransform to use as a handle for the slider.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
/// //Reference to new "RectTransform" (Child of "Handle Slide Area").
/// public RectTransform handleHighlighted;
///
/// //Deactivates the old Handle, then assigns and enables the new one.
/// void Start()
/// {
/// mainSlider.handleRect.gameObject.SetActive(false);
/// mainSlider.handleRect = handleHighlighted;
/// mainSlider.handleRect.gameObject.SetActive(true);
/// }
/// }
/// ]]>
///</code>
/// </example>
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;
/// <summary>
/// The direction of the slider, from minimum to maximum value.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
///
/// public void Start()
/// {
/// //Changes the direction of the slider.
/// if (mainSlider.direction == Slider.Direction.BottomToTop)
/// {
/// mainSlider.direction = Slider.Direction.TopToBottom;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public Direction direction { get { return m_Direction; } set { if (SetPropertyUtility.SetStruct(ref m_Direction, value)) UpdateVisuals(); } }
[SerializeField]
private float m_MinValue = 0;
/// <summary>
/// The minimum allowed value of the slider.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
///
/// void Start()
/// {
/// // Changes the minimum value of the slider to 10;
/// mainSlider.minValue = 10;
/// }
/// }
/// ]]>
///</code>
/// </example>
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;
/// <summary>
/// The maximum allowed value of the slider.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
///
/// void Start()
/// {
/// // Changes the max value of the slider to 20;
/// mainSlider.maxValue = 20;
/// }
/// }
/// ]]>
///</code>
/// </example>
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;
/// <summary>
/// Should the value only be allowed to be whole numbers?
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
///
/// public void Start()
/// {
/// //sets the slider's value to accept whole numbers only.
/// mainSlider.wholeNumbers = true;
/// }
/// }
/// ]]>
///</code>
/// </example>
public bool wholeNumbers { get { return m_WholeNumbers; } set { if (SetPropertyUtility.SetStruct(ref m_WholeNumbers, value)) { Set(m_Value); UpdateVisuals(); } } }
[SerializeField]
protected float m_Value;
/// <summary>
/// The current value of the slider.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
///
/// //Invoked when a submit button is clicked.
/// public void SubmitSliderSetting()
/// {
/// //Displays the value of the slider in the console.
/// Debug.Log(mainSlider.value);
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual float value
{
get
{
return wholeNumbers ? Mathf.Round(m_Value) : m_Value;
}
set
{
Set(value);
}
}
/// <summary>
/// Set the value of the slider without invoking onValueChanged callback.
/// </summary>
/// <param name="input">The new value for the slider.</param>
public virtual void SetValueWithoutNotify(float input)
{
Set(input, false);
}
/// <summary>
/// The current value of the slider normalized into a value between 0 and 1.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
///
/// //Set to invoke when "OnValueChanged" method is called.
/// void CheckNormalisedValue()
/// {
/// //Displays the normalised value of the slider everytime the value changes.
/// Debug.Log(mainSlider.normalizedValue);
/// }
/// }
/// ]]>
///</code>
/// </example>
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]
[SerializeField]
private SliderEvent m_OnValueChanged = new SliderEvent();
/// <summary>
/// Callback executed when the value of the slider is changed.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
///
/// public void Start()
/// {
/// //Adds a listener to the main slider and invokes a method when the value changes.
/// mainSlider.onValueChanged.AddListener(delegate {ValueChangeCheck(); });
/// }
///
/// // Invoked when the value of the slider changes.
/// public void ValueChangeCheck()
/// {
/// Debug.Log(mainSlider.value);
/// }
/// }
/// ]]>
///</code>
/// </example>
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;
// field is never assigned warning
#pragma warning disable 649
private DrivenRectTransformTracker m_Tracker;
#pragma warning restore 649
// This "delayed" mechanism is required for case 1037681.
private bool m_DelayedUpdateVisuals = false;
// Size of each step.
float stepSize { get { return wholeNumbers ? 1 : (maxValue - minValue) * 0.1f; } }
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();
// Update rects in next update since other things might affect them even if value didn't change.
m_DelayedUpdateVisuals = true;
}
if (!UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this) && !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
}
/// <summary>
/// See ICanvasElement.LayoutComplete
/// </summary>
public virtual void LayoutComplete()
{}
/// <summary>
/// See ICanvasElement.GraphicUpdateComplete
/// </summary>
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();
}
/// <summary>
/// Update the rect based on the delayed update visuals.
/// Got around issue of calling sendMessage from onValidate.
/// </summary>
protected virtual void Update()
{
if (m_DelayedUpdateVisuals)
{
m_DelayedUpdateVisuals = false;
Set(m_Value, false);
UpdateVisuals();
}
}
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);
}
// UUM-34170 Apparently, some properties on slider such as IsInteractable and Normalcolor Animation is broken.
// We need to call base here to render the animation on Scene
base.OnDidApplyAnimationProperties();
}
void UpdateCachedReferences()
{
if (m_FillRect && m_FillRect != (RectTransform)transform)
{
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_FillRect = null;
m_FillContainerRect = null;
m_FillImage = null;
}
if (m_HandleRect && m_HandleRect != (RectTransform)transform)
{
m_HandleTransform = m_HandleRect.transform;
if (m_HandleTransform.parent != null)
m_HandleContainerRect = m_HandleTransform.parent.GetComponent<RectTransform>();
}
else
{
m_HandleRect = null;
m_HandleContainerRect = null;
}
}
float ClampValue(float input)
{
float newValue = Mathf.Clamp(input, minValue, maxValue);
if (wholeNumbers)
newValue = Mathf.Round(newValue);
return newValue;
}
/// <summary>
/// Set the value of the slider.
/// </summary>
/// <param name="input">The new value for the slider.</param>
/// <param name="sendCallback">If the OnValueChanged callback should be invoked.</param>
/// <remarks>
/// Process the input to ensure the value is between min and max value. If the input is different set the value and send the callback is required.
/// </remarks>
protected virtual void Set(float input, bool sendCallback = true)
{
// 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()
{
#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 position = Vector2.zero;
if (!MultipleDisplayUtilities.GetRelativeMousePositionForDrag(eventData, ref position))
return;
Vector2 localCursor;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(clickRect, 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.pointerPressRaycast.screenPosition, eventData.enterEventCamera))
{
Vector2 localMousePos;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_HandleRect, eventData.pointerPressRaycast.screenPosition, 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)
{
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;
}
}
/// <summary>
/// See Selectable.FindSelectableOnLeft
/// </summary>
public override Selectable FindSelectableOnLeft()
{
if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Horizontal)
return null;
return base.FindSelectableOnLeft();
}
/// <summary>
/// See Selectable.FindSelectableOnRight
/// </summary>
public override Selectable FindSelectableOnRight()
{
if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Horizontal)
return null;
return base.FindSelectableOnRight();
}
/// <summary>
/// See Selectable.FindSelectableOnUp
/// </summary>
public override Selectable FindSelectableOnUp()
{
if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Vertical)
return null;
return base.FindSelectableOnUp();
}
/// <summary>
/// See Selectable.FindSelectableOnDown
/// </summary>
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;
}
/// <summary>
/// Sets the direction of this slider, optionally changing the layout as well.
/// </summary>
/// <param name="direction">The direction of the slider</param>
/// <param name="includeRectLayouts">Should the layout be flipped together with the slider direction</param>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using System.Collections;
/// using UnityEngine.UI; // Required when Using UI elements.
///
/// public class Example : MonoBehaviour
/// {
/// public Slider mainSlider;
///
/// public void Start()
/// {
/// mainSlider.SetDirection(Slider.Direction.LeftToRight, false);
/// }
/// }
/// ]]>
///</code>
/// </example>
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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 67db9e8f0e2ae9c40bc1e2b64352a6b4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f8ed4321a98682942b9980996131cf26
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,231 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace UnityEngine.UI.Collections
{
internal class IndexedSet<T> : IList<T>
{
//This is a container that gives:
// - Unique items
// - Fast random removal
// - Fast unique inclusion to the end
// - Sequential access
// - Possibility to have disabled items registered
//Downsides:
// - Uses more memory
// - Ordering is not persistent
// - Not Serialization Friendly.
//We use a Dictionary to speed up list lookup, this makes it cheaper to guarantee no duplicates (set)
//When removing we move the last item to the removed item position, this way we only need to update the index cache of a single item. (fast removal)
//Order of the elements is not guaranteed. A removal will change the order of the items.
readonly List<T> m_List = new List<T>();
Dictionary<T, int> m_Dictionary = new Dictionary<T, int>();
int m_EnabledObjectCount = 0;
public void Add(T item)
{
Add(item, true);
}
public void Add(T item, bool isActive)
{
m_List.Add(item);
m_Dictionary.Add(item, m_List.Count - 1);
if (isActive)
EnableItem(item);
}
public bool AddUnique(T item, bool isActive = true)
{
if (m_Dictionary.ContainsKey(item))
{
if (isActive)
EnableItem(item);
else
DisableItem(item);
return false;
}
Add(item, isActive);
return true;
}
public bool EnableItem(T item)
{
if (!m_Dictionary.TryGetValue(item, out int index))
return false;
if (index < m_EnabledObjectCount)
return true;
if (index > m_EnabledObjectCount)
Swap(m_EnabledObjectCount, index);
m_EnabledObjectCount++;
return true;
}
public bool DisableItem(T item)
{
if (!m_Dictionary.TryGetValue(item, out int index))
return false;
if (index >= m_EnabledObjectCount)
return true;
if (index < m_EnabledObjectCount - 1)
Swap(index, m_EnabledObjectCount - 1);
m_EnabledObjectCount--;
return true;
}
public bool Remove(T item)
{
int index = -1;
if (!m_Dictionary.TryGetValue(item, out index))
return false;
RemoveAt(index);
return true;
}
public IEnumerator<T> GetEnumerator()
{
throw new System.NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Clear()
{
m_List.Clear();
m_Dictionary.Clear();
m_EnabledObjectCount = 0;
}
public bool Contains(T item)
{
return m_Dictionary.ContainsKey(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
m_List.CopyTo(array, arrayIndex);
}
public int Count { get { return m_EnabledObjectCount; } }
public int Capacity { get { return m_List.Count; } }
public bool IsReadOnly { get { return false; } }
public int IndexOf(T item)
{
int index = -1;
if (m_Dictionary.TryGetValue(item, out index))
return index;
return -1;
}
public void Insert(int index, T item)
{
//We could support this, but the semantics would be weird. Order is not guaranteed..
throw new NotSupportedException("Random Insertion is semantically invalid, since this structure does not guarantee ordering.");
}
public void RemoveAt(int index)
{
T item = m_List[index];
if (index == m_List.Count - 1)
{
if (m_EnabledObjectCount == m_List.Count)
m_EnabledObjectCount--;
m_List.RemoveAt(index);
}
else
{
int replaceItemIndex = m_List.Count - 1;
if (index < m_EnabledObjectCount - 1)
{
Swap(--m_EnabledObjectCount, index);
index = m_EnabledObjectCount;
}
else if (index == m_EnabledObjectCount - 1)
{
m_EnabledObjectCount--;
}
Swap(replaceItemIndex, index);
m_List.RemoveAt(replaceItemIndex);
}
m_Dictionary.Remove(item);
}
private void Swap(int index1, int index2)
{
if (index1 == index2)
return;
T item1 = m_List[index1];
T item2 = m_List[index2];
m_List[index1] = item2;
m_List[index2] = item1;
m_Dictionary[item2] = index1;
m_Dictionary[item1] = index2;
}
public T this[int index]
{
get
{
if ((uint)index >= (uint)m_EnabledObjectCount)
throw new IndexOutOfRangeException();
return m_List[index];
}
set
{
//Order in the list should not be set manually since the order is not guaranteed.
//The item will be activated or not according to the old item's state.
T item = m_List[index];
m_Dictionary.Remove(item);
m_List[index] = value;
m_Dictionary.Add(value, index);
}
}
public void RemoveAll(Predicate<T> match)
{
//I guess this could be optmized by instead of removing the items from the list immediatly,
//We move them to the end, and then remove all in one go.
//But I don't think this is going to be the bottleneck, so leaving as is for now.
int i = 0;
while (i < m_List.Count)
{
T item = m_List[i];
if (match(item))
Remove(item);
else
i++;
}
}
//Sorts the internal list, this makes the exposed index accessor sorted as well.
//But note that any insertion or deletion, can unorder the collection again.
public void Sort(Comparison<T> sortLayoutFunction)
{
//There might be better ways to sort and keep the dictionary index up to date.
m_List.Sort(sortLayoutFunction);
//Rebuild the dictionary index.
for (int i = 0; i < m_List.Count; ++i)
{
T item = m_List[i];
m_Dictionary[item] = i;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a8604e8f13b3be84b8bc0c1507d8f3f6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,53 @@
using System;
using UnityEngine.Serialization;
namespace UnityEngine.UI
{
[Serializable]
/// <summary>
/// Structure that stores the state of a sprite transition on a Selectable.
/// </summary>
public struct SpriteState : IEquatable<SpriteState>
{
[SerializeField]
private Sprite m_HighlightedSprite;
[SerializeField]
private Sprite m_PressedSprite;
[FormerlySerializedAs("m_HighlightedSprite")]
[SerializeField]
private Sprite m_SelectedSprite;
[SerializeField]
private Sprite m_DisabledSprite;
/// <summary>
/// Highlighted sprite.
/// </summary>
public Sprite highlightedSprite { get { return m_HighlightedSprite; } set { m_HighlightedSprite = value; } }
/// <summary>
/// Pressed sprite.
/// </summary>
public Sprite pressedSprite { get { return m_PressedSprite; } set { m_PressedSprite = value; } }
/// <summary>
/// Selected sprite.
/// </summary>
public Sprite selectedSprite { get { return m_SelectedSprite; } set { m_SelectedSprite = value; } }
/// <summary>
/// Disabled sprite.
/// </summary>
public Sprite disabledSprite { get { return m_DisabledSprite; } set { m_DisabledSprite = value; } }
public bool Equals(SpriteState other)
{
return highlightedSprite == other.highlightedSprite &&
pressedSprite == other.pressedSprite &&
selectedSprite == other.selectedSprite &&
disabledSprite == other.disabledSprite;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 25b01819c6e1649428db2a9b274cf364
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,177 @@
using System;
using System.Collections.Generic;
using UnityEngine.Rendering;
namespace UnityEngine.UI
{
/// <summary>
/// Dynamic material class makes it possible to create custom materials on the fly on a per-Graphic basis,
/// and still have them get cleaned up correctly.
/// </summary>
public static class StencilMaterial
{
private class MatEntry
{
public Material baseMat;
public Material customMat;
public int count;
public int stencilId;
public StencilOp operation = StencilOp.Keep;
public CompareFunction compareFunction = CompareFunction.Always;
public int readMask;
public int writeMask;
public bool useAlphaClip;
public ColorWriteMask colorMask;
}
private static List<MatEntry> m_List = new List<MatEntry>();
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
[Obsolete("Use Material.Add instead.", true)]
public static Material Add(Material baseMat, int stencilID) { return null; }
/// <summary>
/// Add a new material using the specified base and stencil ID.
/// </summary>
public static Material Add(Material baseMat, int stencilID, StencilOp operation, CompareFunction compareFunction, ColorWriteMask colorWriteMask)
{
return Add(baseMat, stencilID, operation, compareFunction, colorWriteMask, 255, 255);
}
static void LogWarningWhenNotInBatchmode(string warning, Object context)
{
// Do not log warnings in batchmode (case 1350059)
if (!Application.isBatchMode)
Debug.LogWarning(warning, context);
}
/// <summary>
/// Add a new material using the specified base and stencil ID.
/// </summary>
public static Material Add(Material baseMat, int stencilID, StencilOp operation, CompareFunction compareFunction, ColorWriteMask colorWriteMask, int readMask, int writeMask)
{
if ((stencilID <= 0 && colorWriteMask == ColorWriteMask.All) || baseMat == null)
return baseMat;
if (!baseMat.HasProperty("_Stencil"))
{
LogWarningWhenNotInBatchmode("Material " + baseMat.name + " doesn't have _Stencil property", baseMat);
return baseMat;
}
if (!baseMat.HasProperty("_StencilOp"))
{
LogWarningWhenNotInBatchmode("Material " + baseMat.name + " doesn't have _StencilOp property", baseMat);
return baseMat;
}
if (!baseMat.HasProperty("_StencilComp"))
{
LogWarningWhenNotInBatchmode("Material " + baseMat.name + " doesn't have _StencilComp property", baseMat);
return baseMat;
}
if (!baseMat.HasProperty("_StencilReadMask"))
{
LogWarningWhenNotInBatchmode("Material " + baseMat.name + " doesn't have _StencilReadMask property", baseMat);
return baseMat;
}
if (!baseMat.HasProperty("_StencilWriteMask"))
{
LogWarningWhenNotInBatchmode("Material " + baseMat.name + " doesn't have _StencilWriteMask property", baseMat);
return baseMat;
}
if (!baseMat.HasProperty("_ColorMask"))
{
LogWarningWhenNotInBatchmode("Material " + baseMat.name + " doesn't have _ColorMask property", baseMat);
return baseMat;
}
var listCount = m_List.Count;
for (int i = 0; i < listCount; ++i)
{
MatEntry ent = m_List[i];
if (ent.baseMat == baseMat
&& ent.stencilId == stencilID
&& ent.operation == operation
&& ent.compareFunction == compareFunction
&& ent.readMask == readMask
&& ent.writeMask == writeMask
&& ent.colorMask == colorWriteMask)
{
++ent.count;
return ent.customMat;
}
}
var newEnt = new MatEntry();
newEnt.count = 1;
newEnt.baseMat = baseMat;
newEnt.customMat = new Material(baseMat);
newEnt.customMat.hideFlags = HideFlags.HideAndDontSave;
newEnt.stencilId = stencilID;
newEnt.operation = operation;
newEnt.compareFunction = compareFunction;
newEnt.readMask = readMask;
newEnt.writeMask = writeMask;
newEnt.colorMask = colorWriteMask;
newEnt.useAlphaClip = operation != StencilOp.Keep && writeMask > 0;
newEnt.customMat.name = string.Format("Stencil Id:{0}, Op:{1}, Comp:{2}, WriteMask:{3}, ReadMask:{4}, ColorMask:{5} AlphaClip:{6} ({7})", stencilID, operation, compareFunction, writeMask, readMask, colorWriteMask, newEnt.useAlphaClip, baseMat.name);
newEnt.customMat.SetFloat("_Stencil", (float)stencilID);
newEnt.customMat.SetFloat("_StencilOp", (float)operation);
newEnt.customMat.SetFloat("_StencilComp", (float)compareFunction);
newEnt.customMat.SetFloat("_StencilReadMask", (float)readMask);
newEnt.customMat.SetFloat("_StencilWriteMask", (float)writeMask);
newEnt.customMat.SetFloat("_ColorMask", (float)colorWriteMask);
newEnt.customMat.SetFloat("_UseUIAlphaClip", newEnt.useAlphaClip ? 1.0f : 0.0f);
if (newEnt.useAlphaClip)
newEnt.customMat.EnableKeyword("UNITY_UI_ALPHACLIP");
else
newEnt.customMat.DisableKeyword("UNITY_UI_ALPHACLIP");
m_List.Add(newEnt);
return newEnt.customMat;
}
/// <summary>
/// Remove an existing material, automatically cleaning it up if it's no longer in use.
/// </summary>
public static void Remove(Material customMat)
{
if (customMat == null)
return;
var listCount = m_List.Count;
for (int i = 0; i < listCount; ++i)
{
MatEntry ent = m_List[i];
if (ent.customMat != customMat)
continue;
if (--ent.count == 0)
{
Misc.DestroyImmediate(ent.customMat);
ent.baseMat = null;
m_List.RemoveAt(i);
}
return;
}
}
public static void ClearAll()
{
var listCount = m_List.Count;
for (int i = 0; i < listCount; ++i)
{
MatEntry ent = m_List[i];
Misc.DestroyImmediate(ent.customMat);
ent.baseMat = null;
}
m_List.Clear();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 69ae55f76840b2849ba56cfb53a17df0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,801 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.UI
{
[RequireComponent(typeof(CanvasRenderer))]
[AddComponentMenu("UI/Legacy/Text", 100)]
/// <summary>
/// The default Graphic to draw font data to screen.
/// </summary>
public class Text : MaskableGraphic, ILayoutElement
{
[SerializeField] private FontData m_FontData = FontData.defaultFontData;
#if UNITY_EDITOR
// needed to track font changes from the inspector
private Font m_LastTrackedFont;
#endif
[TextArea(3, 10)][SerializeField] protected string m_Text = String.Empty;
private TextGenerator m_TextCache;
private TextGenerator m_TextCacheForLayout;
static protected Material s_DefaultText = null;
// We use this flag instead of Unregistering/Registering the callback to avoid allocation.
[NonSerialized] protected bool m_DisableFontTextureRebuiltCallback = false;
protected Text()
{
useLegacyMeshGeneration = false;
}
/// <summary>
/// The cached TextGenerator used when generating visible Text.
/// </summary>
public TextGenerator cachedTextGenerator
{
get { return m_TextCache ?? (m_TextCache = (m_Text.Length != 0 ? new TextGenerator(m_Text.Length) : new TextGenerator())); }
}
/// <summary>
/// The cached TextGenerator used when determine Layout
/// </summary>
public TextGenerator cachedTextGeneratorForLayout
{
get { return m_TextCacheForLayout ?? (m_TextCacheForLayout = new TextGenerator()); }
}
/// <summary>
/// Text's texture comes from the font.
/// </summary>
public override Texture mainTexture
{
get
{
if (font != null && font.material != null && font.material.mainTexture != null)
return font.material.mainTexture;
if (m_Material != null)
return m_Material.mainTexture;
return base.mainTexture;
}
}
/// <summary>
/// Called by the FontUpdateTracker when the texture associated with a font is modified.
/// </summary>
public void FontTextureChanged()
{
// Only invoke if we are not destroyed.
if (!this)
return;
if (m_DisableFontTextureRebuiltCallback)
return;
cachedTextGenerator.Invalidate();
if (!IsActive())
return;
// this is a bit hacky, but it is currently the
// cleanest solution....
// if we detect the font texture has changed and are in a rebuild loop
// we just regenerate the verts for the new UV's
if (CanvasUpdateRegistry.IsRebuildingGraphics() || CanvasUpdateRegistry.IsRebuildingLayout())
UpdateGeometry();
else
{
SetAllDirty();
}
}
/// <summary>
/// The Font used by the text.
/// </summary>
/// <remarks>
/// This is the font used by the Text component. Use it to alter or return the font from the Text. There are many free fonts available online.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// //Create a new Text GameObject by going to Create>UI>Text in the Editor. Attach this script to the Text GameObject. Then, choose or click and drag your own font into the Font section in the Inspector window.
///
/// using UnityEngine;
/// using UnityEngine.UI;
///
/// public class TextFontExample : MonoBehaviour
/// {
/// Text m_Text;
/// //Attach your own Font in the Inspector
/// public Font m_Font;
///
/// void Start()
/// {
/// //Fetch the Text component from the GameObject
/// m_Text = GetComponent<Text>();
/// }
///
/// void Update()
/// {
/// if (Input.GetKey(KeyCode.Space))
/// {
/// //Change the Text Font to the Font attached in the Inspector
/// m_Text.font = m_Font;
/// //Change the Text to the message below
/// m_Text.text = "My Font Changed!";
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public Font font
{
get
{
return m_FontData.font;
}
set
{
if (m_FontData.font == value)
return;
if (isActiveAndEnabled)
FontUpdateTracker.UntrackText(this);
m_FontData.font = value;
if (isActiveAndEnabled)
FontUpdateTracker.TrackText(this);
#if UNITY_EDITOR
// needed to track font changes from the inspector
m_LastTrackedFont = value;
#endif
SetAllDirty();
}
}
/// <summary>
/// Text that's being displayed by the Text.
/// </summary>
/// <remarks>
/// This is the string value of a Text component. Use this to read or edit the message displayed in Text.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using UnityEngine.UI;
///
/// public class Example : MonoBehaviour
/// {
/// public Text m_MyText;
///
/// void Start()
/// {
/// //Text sets your text to say this message
/// m_MyText.text = "This is my text";
/// }
///
/// void Update()
/// {
/// //Press the space key to change the Text message
/// if (Input.GetKey(KeyCode.Space))
/// {
/// m_MyText.text = "My text has now changed.";
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public virtual string text
{
get
{
return m_Text;
}
set
{
if (String.IsNullOrEmpty(value))
{
if (String.IsNullOrEmpty(m_Text))
return;
m_Text = "";
SetVerticesDirty();
}
else if (m_Text != value)
{
m_Text = value;
SetVerticesDirty();
SetLayoutDirty();
}
}
}
/// <summary>
/// Whether this Text will support rich text.
/// </summary>
public bool supportRichText
{
get
{
return m_FontData.richText;
}
set
{
if (m_FontData.richText == value)
return;
m_FontData.richText = value;
SetVerticesDirty();
SetLayoutDirty();
}
}
/// <summary>
/// Should the text be allowed to auto resized.
/// </summary>
public bool resizeTextForBestFit
{
get
{
return m_FontData.bestFit;
}
set
{
if (m_FontData.bestFit == value)
return;
m_FontData.bestFit = value;
SetVerticesDirty();
SetLayoutDirty();
}
}
/// <summary>
/// The minimum size the text is allowed to be.
/// </summary>
public int resizeTextMinSize
{
get
{
return m_FontData.minSize;
}
set
{
if (m_FontData.minSize == value)
return;
m_FontData.minSize = value;
SetVerticesDirty();
SetLayoutDirty();
}
}
/// <summary>
/// The maximum size the text is allowed to be. 1 = infinitely large.
/// </summary>
public int resizeTextMaxSize
{
get
{
return m_FontData.maxSize;
}
set
{
if (m_FontData.maxSize == value)
return;
m_FontData.maxSize = value;
SetVerticesDirty();
SetLayoutDirty();
}
}
/// <summary>
/// The positioning of the text reliative to its [[RectTransform]].
/// </summary>
/// <remarks>
/// This is the positioning of the Text relative to its RectTransform. You can alter this via script or in the Inspector of a Text component using the buttons in the Alignment section.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// //Create a Text GameObject by going to __Create__>__UI__>__Text__. Attach this script to the GameObject to see it working.
///
/// using UnityEngine;
/// using UnityEngine.UI;
///
/// public class UITextAlignment : MonoBehaviour
/// {
/// Text m_Text;
///
/// void Start()
/// {
/// //Fetch the Text Component
/// m_Text = GetComponent<Text>();
/// //Switch the Text alignment to the middle
/// m_Text.alignment = TextAnchor.MiddleCenter;
/// }
///
/// //This is a legacy function used for an instant demonstration. See the <a href="https://unity3d.com/learn/tutorials/s/user-interface-ui">UI Tutorials pages </a> and [[wiki:UISystem|UI Section]] of the manual for more information on creating your own buttons etc.
/// void OnGUI()
/// {
/// //Press this Button to change the Text alignment to the lower right
/// if (GUI.Button(new Rect(0, 0, 100, 40), "Lower Right"))
/// {
/// m_Text.alignment = TextAnchor.LowerRight;
/// }
///
/// //Press this Button to change the Text alignment to the upper left
/// if (GUI.Button(new Rect(150, 0, 100, 40), "Upper Left"))
/// {
/// m_Text.alignment = TextAnchor.UpperLeft;
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public TextAnchor alignment
{
get
{
return m_FontData.alignment;
}
set
{
if (m_FontData.alignment == value)
return;
m_FontData.alignment = value;
SetVerticesDirty();
SetLayoutDirty();
}
}
/// <summary>
/// Use the extents of glyph geometry to perform horizontal alignment rather than glyph metrics.
/// </summary>
/// <remarks>
/// This can result in better fitting left and right alignment, but may result in incorrect positioning when attempting to overlay multiple fonts (such as a specialized outline font) on top of each other.
/// </remarks>
public bool alignByGeometry
{
get
{
return m_FontData.alignByGeometry;
}
set
{
if (m_FontData.alignByGeometry == value)
return;
m_FontData.alignByGeometry = value;
SetVerticesDirty();
}
}
/// <summary>
/// The size that the Font should render at. Unit of measure is Points.
/// </summary>
/// <remarks>
/// This is the size of the Font of the Text. Use this to fetch or change the size of the Font. When changing the Font size, remember to take into account the RectTransform of the Text. Larger Font sizes or messages may not fit in certain rectangle sizes and do not show in the Scene.
/// Note: Point size is not consistent from one font to another.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// //For this script to work, create a new Text GameObject by going to Create>U>Text. Attach the script to the Text GameObject. Make sure the GameObject has a RectTransform component.
///
/// using UnityEngine;
/// using UnityEngine.UI;
///
/// public class Example : MonoBehaviour
/// {
/// Text m_Text;
/// RectTransform m_RectTransform;
///
/// void Start()
/// {
/// //Fetch the Text and RectTransform components from the GameObject
/// m_Text = GetComponent<Text>();
/// m_RectTransform = GetComponent<RectTransform>();
/// }
///
/// void Update()
/// {
/// //Press the space key to change the Font size
/// if (Input.GetKey(KeyCode.Space))
/// {
/// changeFontSize();
/// }
/// }
///
/// void changeFontSize()
/// {
/// //Change the Font Size to 16
/// m_Text.fontSize = 30;
///
/// //Change the RectTransform size to allow larger fonts and sentences
/// m_RectTransform.sizeDelta = new Vector2(m_Text.fontSize * 10, 100);
///
/// //Change the m_Text text to the message below
/// m_Text.text = "I changed my Font size!";
/// }
/// }
/// ]]>
///</code>
/// </example>
public int fontSize
{
get
{
return m_FontData.fontSize;
}
set
{
if (m_FontData.fontSize == value)
return;
m_FontData.fontSize = value;
SetVerticesDirty();
SetLayoutDirty();
}
}
/// <summary>
/// Horizontal overflow mode.
/// </summary>
/// <remarks>
/// When set to HorizontalWrapMode.Overflow, text can exceed the horizontal boundaries of the Text graphic. When set to HorizontalWrapMode.Wrap, text will be word-wrapped to fit within the boundaries.
/// </remarks>
public HorizontalWrapMode horizontalOverflow
{
get
{
return m_FontData.horizontalOverflow;
}
set
{
if (m_FontData.horizontalOverflow == value)
return;
m_FontData.horizontalOverflow = value;
SetVerticesDirty();
SetLayoutDirty();
}
}
/// <summary>
/// Vertical overflow mode.
/// </summary>
public VerticalWrapMode verticalOverflow
{
get
{
return m_FontData.verticalOverflow;
}
set
{
if (m_FontData.verticalOverflow == value)
return;
m_FontData.verticalOverflow = value;
SetVerticesDirty();
SetLayoutDirty();
}
}
/// <summary>
/// Line spacing, specified as a factor of font line height. A value of 1 will produce normal line spacing.
/// </summary>
public float lineSpacing
{
get
{
return m_FontData.lineSpacing;
}
set
{
if (m_FontData.lineSpacing == value)
return;
m_FontData.lineSpacing = value;
SetVerticesDirty();
SetLayoutDirty();
}
}
/// <summary>
/// Font style used by the Text's text.
/// </summary>
public FontStyle fontStyle
{
get
{
return m_FontData.fontStyle;
}
set
{
if (m_FontData.fontStyle == value)
return;
m_FontData.fontStyle = value;
SetVerticesDirty();
SetLayoutDirty();
}
}
/// <summary>
/// Provides information about how fonts are scale to the screen.
/// </summary>
/// <remarks>
/// For dynamic fonts, the value is equivalent to the scale factor of the canvas. For non-dynamic fonts, the value is calculated from the requested text size and the size from the font.
/// </remarks>
public float pixelsPerUnit
{
get
{
var localCanvas = canvas;
if (!localCanvas)
return 1;
// For dynamic fonts, ensure we use one pixel per pixel on the screen.
if (!font || font.dynamic)
return localCanvas.scaleFactor;
// For non-dynamic fonts, calculate pixels per unit based on specified font size relative to font object's own font size.
if (m_FontData.fontSize <= 0 || font.fontSize <= 0)
return 1;
return font.fontSize / (float)m_FontData.fontSize;
}
}
protected override void OnEnable()
{
base.OnEnable();
cachedTextGenerator.Invalidate();
FontUpdateTracker.TrackText(this);
}
protected override void OnDisable()
{
FontUpdateTracker.UntrackText(this);
base.OnDisable();
}
protected override void UpdateGeometry()
{
if (font != null)
{
base.UpdateGeometry();
}
}
#if UNITY_EDITOR
protected override void Reset()
{
AssignDefaultFontIfNecessary();
}
#endif
internal void AssignDefaultFont()
{
font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
}
internal void AssignDefaultFontIfNecessary()
{
if (font == null)
font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
}
/// <summary>
/// Convenience function to populate the generation setting for the text.
/// </summary>
/// <param name="extents">The extents the text can draw in.</param>
/// <returns>Generated settings.</returns>
public TextGenerationSettings GetGenerationSettings(Vector2 extents)
{
var settings = new TextGenerationSettings();
settings.generationExtents = extents;
if (font != null && font.dynamic)
{
settings.fontSize = m_FontData.fontSize;
settings.resizeTextMinSize = m_FontData.minSize;
settings.resizeTextMaxSize = m_FontData.maxSize;
}
// Other settings
settings.textAnchor = m_FontData.alignment;
settings.alignByGeometry = m_FontData.alignByGeometry;
settings.scaleFactor = pixelsPerUnit;
settings.color = color;
settings.font = font;
settings.pivot = rectTransform.pivot;
settings.richText = m_FontData.richText;
settings.lineSpacing = m_FontData.lineSpacing;
settings.fontStyle = m_FontData.fontStyle;
settings.resizeTextForBestFit = m_FontData.bestFit;
settings.updateBounds = false;
settings.horizontalOverflow = m_FontData.horizontalOverflow;
settings.verticalOverflow = m_FontData.verticalOverflow;
return settings;
}
/// <summary>
/// Convenience function to determine the vector offset of the anchor.
/// </summary>
static public Vector2 GetTextAnchorPivot(TextAnchor anchor)
{
switch (anchor)
{
case TextAnchor.LowerLeft: return new Vector2(0, 0);
case TextAnchor.LowerCenter: return new Vector2(0.5f, 0);
case TextAnchor.LowerRight: return new Vector2(1, 0);
case TextAnchor.MiddleLeft: return new Vector2(0, 0.5f);
case TextAnchor.MiddleCenter: return new Vector2(0.5f, 0.5f);
case TextAnchor.MiddleRight: return new Vector2(1, 0.5f);
case TextAnchor.UpperLeft: return new Vector2(0, 1);
case TextAnchor.UpperCenter: return new Vector2(0.5f, 1);
case TextAnchor.UpperRight: return new Vector2(1, 1);
default: return Vector2.zero;
}
}
readonly UIVertex[] m_TempVerts = new UIVertex[4];
protected override void OnPopulateMesh(VertexHelper toFill)
{
if (font == null)
return;
// We don't care if we the font Texture changes while we are doing our Update.
// The end result of cachedTextGenerator will be valid for this instance.
// Otherwise we can get issues like Case 619238.
m_DisableFontTextureRebuiltCallback = true;
Vector2 extents = rectTransform.rect.size;
var settings = GetGenerationSettings(extents);
cachedTextGenerator.PopulateWithErrors(text, settings, gameObject);
// Apply the offset to the vertices
IList<UIVertex> verts = cachedTextGenerator.verts;
float unitsPerPixel = 1 / pixelsPerUnit;
int vertCount = verts.Count;
// We have no verts to process just return (case 1037923)
if (vertCount <= 0)
{
toFill.Clear();
return;
}
Vector2 roundingOffset = new Vector2(verts[0].position.x, verts[0].position.y) * unitsPerPixel;
roundingOffset = PixelAdjustPoint(roundingOffset) - roundingOffset;
toFill.Clear();
if (roundingOffset != Vector2.zero)
{
for (int i = 0; i < vertCount; ++i)
{
int tempVertsIndex = i & 3;
m_TempVerts[tempVertsIndex] = verts[i];
m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
m_TempVerts[tempVertsIndex].position.x += roundingOffset.x;
m_TempVerts[tempVertsIndex].position.y += roundingOffset.y;
if (tempVertsIndex == 3)
toFill.AddUIVertexQuad(m_TempVerts);
}
}
else
{
for (int i = 0; i < vertCount; ++i)
{
int tempVertsIndex = i & 3;
m_TempVerts[tempVertsIndex] = verts[i];
m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
if (tempVertsIndex == 3)
toFill.AddUIVertexQuad(m_TempVerts);
}
}
m_DisableFontTextureRebuiltCallback = false;
}
public virtual void CalculateLayoutInputHorizontal() {}
public virtual void CalculateLayoutInputVertical() {}
public virtual float minWidth
{
get { return 0; }
}
public virtual float preferredWidth
{
get
{
var settings = GetGenerationSettings(Vector2.zero);
return cachedTextGeneratorForLayout.GetPreferredWidth(m_Text, settings) / pixelsPerUnit;
}
}
public virtual float flexibleWidth { get { return -1; } }
public virtual float minHeight
{
get { return 0; }
}
public virtual float preferredHeight
{
get
{
var settings = GetGenerationSettings(new Vector2(GetPixelAdjustedRect().size.x, 0.0f));
return cachedTextGeneratorForLayout.GetPreferredHeight(m_Text, settings) / pixelsPerUnit;
}
}
public virtual float flexibleHeight { get { return -1; } }
public virtual int layoutPriority { get { return 0; } }
#if UNITY_EDITOR
public override void OnRebuildRequested()
{
// After a Font asset gets re-imported the managed side gets deleted and recreated,
// that means the delegates are not persisted.
// so we need to properly enforce a consistent state here.
if (isActiveAndEnabled)
{
FontUpdateTracker.UntrackText(this);
FontUpdateTracker.TrackText(this);
}
// Also the textgenerator is no longer valid.
cachedTextGenerator.Invalidate();
base.OnRebuildRequested();
}
// The Text inspector editor can change the font, and we need a way to track changes so that we get the appropriate rebuild callbacks
// We can intercept changes in OnValidate, and keep track of the previous font reference
protected override void OnValidate()
{
if (!IsActive())
{
base.OnValidate();
return;
}
if (m_FontData.font != m_LastTrackedFont)
{
Font newFont = m_FontData.font;
m_FontData.font = m_LastTrackedFont;
if (isActiveAndEnabled)
FontUpdateTracker.UntrackText(this);
m_FontData.font = newFont;
if (isActiveAndEnabled)
FontUpdateTracker.TrackText(this);
m_LastTrackedFont = newFont;
}
base.OnValidate();
}
#endif // if UNITY_EDITOR
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5f7201a12d95ffc409449d95f23cf332
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,336 @@
using System;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
namespace UnityEngine.UI
{
/// <summary>
/// A standard toggle that has an on / off state.
/// </summary>
/// <remarks>
/// The toggle component is a Selectable that controls a child graphic which displays the on / off state.
/// When a toggle event occurs a callback is sent to any registered listeners of UI.Toggle._onValueChanged.
/// </remarks>
[AddComponentMenu("UI/Toggle", 30)]
[RequireComponent(typeof(RectTransform))]
public class Toggle : Selectable, IPointerClickHandler, ISubmitHandler, ICanvasElement
{
/// <summary>
/// Display settings for when a toggle is activated or deactivated.
/// </summary>
public enum ToggleTransition
{
/// <summary>
/// Show / hide the toggle instantly
/// </summary>
None,
/// <summary>
/// Fade the toggle in / out smoothly.
/// </summary>
Fade
}
[Serializable]
/// <summary>
/// UnityEvent callback for when a toggle is toggled.
/// </summary>
public class ToggleEvent : UnityEvent<bool>
{}
/// <summary>
/// Transition mode for the toggle.
/// </summary>
public ToggleTransition toggleTransition = ToggleTransition.Fade;
/// <summary>
/// Graphic the toggle should be working with.
/// </summary>
public Graphic graphic;
[SerializeField]
private ToggleGroup m_Group;
/// <summary>
/// Group the toggle belongs to.
/// </summary>
public ToggleGroup group
{
get { return m_Group; }
set
{
SetToggleGroup(value, true);
PlayEffect(true);
}
}
/// <summary>
/// Allow for delegate-based subscriptions for faster events than 'eventReceiver', and allowing for multiple receivers.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// //Attach this script to a Toggle GameObject. To do this, go to Create>UI>Toggle.
/// //Set your own Text in the Inspector window
///
/// using UnityEngine;
/// using UnityEngine.UI;
///
/// public class Example : MonoBehaviour
/// {
/// Toggle m_Toggle;
/// public Text m_Text;
///
/// void Start()
/// {
/// //Fetch the Toggle GameObject
/// m_Toggle = GetComponent<Toggle>();
/// //Add listener for when the state of the Toggle changes, to take action
/// m_Toggle.onValueChanged.AddListener(delegate {
/// ToggleValueChanged(m_Toggle);
/// });
///
/// //Initialise the Text to say the first state of the Toggle
/// m_Text.text = "First Value : " + m_Toggle.isOn;
/// }
///
/// //Output the new state of the Toggle into Text
/// void ToggleValueChanged(Toggle change)
/// {
/// m_Text.text = "New Value : " + m_Toggle.isOn;
/// }
/// }
/// ]]>
///</code>
/// </example>
public ToggleEvent onValueChanged = new ToggleEvent();
// Whether the toggle is on
[Tooltip("Is the toggle currently on or off?")]
[SerializeField]
private bool m_IsOn;
protected Toggle()
{}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (!UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this) && !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 OnDestroy()
{
if (m_Group != null)
m_Group.EnsureValidState();
base.OnDestroy();
}
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<6F>t 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)
{
// 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 && isOn && IsActive())
newGroup.NotifyToggleOn(this);
}
/// <summary>
/// Whether the toggle is currently active.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// /Attach this script to a Toggle GameObject. To do this, go to Create>UI>Toggle.
/// //Set your own Text in the Inspector window
///
/// using UnityEngine;
/// using UnityEngine.UI;
///
/// public class Example : MonoBehaviour
/// {
/// Toggle m_Toggle;
/// public Text m_Text;
///
/// void Start()
/// {
/// //Fetch the Toggle GameObject
/// m_Toggle = GetComponent<Toggle>();
/// //Add listener for when the state of the Toggle changes, and output the state
/// m_Toggle.onValueChanged.AddListener(delegate {
/// ToggleValueChanged(m_Toggle);
/// });
///
/// //Initialize the Text to say whether the Toggle is in a positive or negative state
/// m_Text.text = "Toggle is : " + m_Toggle.isOn;
/// }
///
/// //Output the new state of the Toggle into Text when the user uses the Toggle
/// void ToggleValueChanged(Toggle change)
/// {
/// m_Text.text = "Toggle is : " + m_Toggle.isOn;
/// }
/// }
/// ]]>
///</code>
/// </example>
public bool isOn
{
get { return m_IsOn; }
set
{
Set(value);
}
}
/// <summary>
/// Set isOn without invoking onValueChanged callback.
/// </summary>
/// <param name="value">New Value for isOn.</param>
public void SetIsOnWithoutNotify(bool value)
{
Set(value, false);
}
void Set(bool value, bool sendCallback = true)
{
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 && m_Group.isActiveAndEnabled && IsActive())
{
if (m_IsOn || (!m_Group.AnyTogglesOn() && !m_Group.allowSwitchOff))
{
m_IsOn = true;
m_Group.NotifyToggleOn(this, sendCallback);
}
}
// 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();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9085046f02f69544eb97fd06b6048fe2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,185 @@
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine.EventSystems;
namespace UnityEngine.UI
{
[AddComponentMenu("UI/Toggle Group", 31)]
[DisallowMultipleComponent]
/// <summary>
/// A component that represents a group of UI.Toggles.
/// </summary>
/// <remarks>
/// When using a group reference the group from a UI.Toggle. Only one member of a group can be active at a time.
/// </remarks>
public class ToggleGroup : UIBehaviour
{
[SerializeField] private bool m_AllowSwitchOff = false;
/// <summary>
/// Is it allowed that no toggle is switched on?
/// </summary>
/// <remarks>
/// If this setting is enabled, pressing the toggle that is currently switched on will switch it off, so that no toggle is switched on. If this setting is disabled, pressing the toggle that is currently switched on will not change its state.
/// Note that even if allowSwitchOff is false, the Toggle Group will not enforce its constraint right away if no toggles in the group are switched on when the scene is loaded or when the group is instantiated. It will only prevent the user from switching a toggle off.
/// </remarks>
public bool allowSwitchOff { get { return m_AllowSwitchOff; } set { m_AllowSwitchOff = value; } }
protected List<Toggle> m_Toggles = new List<Toggle>();
protected ToggleGroup()
{}
/// <summary>
/// Because all the Toggles have registered themselves in the OnEnabled, Start should check to
/// make sure at least one Toggle is active in groups that do not AllowSwitchOff
/// </summary>
protected override void Start()
{
EnsureValidState();
base.Start();
}
protected override void OnEnable()
{
EnsureValidState();
base.OnEnable();
}
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}));
}
/// <summary>
/// Notify the group that the given toggle is enabled.
/// </summary>
/// <param name="toggle">The toggle that got triggered on.</param>
/// <param name="sendCallback">If other toggles should send onValueChanged.</param>
public void NotifyToggleOn(Toggle toggle, bool sendCallback = true)
{
ValidateToggleIsInGroup(toggle);
// disable all toggles in the group
for (var i = 0; i < m_Toggles.Count; i++)
{
if (m_Toggles[i] == toggle)
continue;
if (sendCallback)
m_Toggles[i].isOn = false;
else
m_Toggles[i].SetIsOnWithoutNotify(false);
}
}
/// <summary>
/// Unregister a toggle from the group.
/// </summary>
/// <param name="toggle">The toggle to remove.</param>
public void UnregisterToggle(Toggle toggle)
{
if (m_Toggles.Contains(toggle))
m_Toggles.Remove(toggle);
}
/// <summary>
/// Register a toggle with the toggle group so it is watched for changes and notified if another toggle in the group changes.
/// </summary>
/// <param name="toggle">The toggle to register with the group.</param>
public void RegisterToggle(Toggle toggle)
{
if (!m_Toggles.Contains(toggle))
m_Toggles.Add(toggle);
}
/// <summary>
/// Ensure that the toggle group still has a valid state. This is only relevant when a ToggleGroup is Started
/// or a Toggle has been deleted from the group.
/// </summary>
public void EnsureValidState()
{
if (!allowSwitchOff && !AnyTogglesOn() && m_Toggles.Count != 0)
{
m_Toggles[0].isOn = true;
NotifyToggleOn(m_Toggles[0]);
}
IEnumerable<Toggle> activeToggles = ActiveToggles();
if (activeToggles.Count() > 1)
{
Toggle firstActive = GetFirstActiveToggle();
foreach (Toggle toggle in activeToggles)
{
if (toggle == firstActive)
{
continue;
}
toggle.isOn = false;
}
}
}
/// <summary>
/// Are any of the toggles on?
/// </summary>
/// <returns>Are and of the toggles on?</returns>
public bool AnyTogglesOn()
{
return m_Toggles.Find(x => x.isOn) != null;
}
/// <summary>
/// Returns the toggles in this group that are active.
/// </summary>
/// <returns>The active toggles in the group.</returns>
/// <remarks>
/// Toggles belonging to this group but are not active either because their GameObject is inactive or because the Toggle component is disabled, are not returned as part of the list.
/// </remarks>
public IEnumerable<Toggle> ActiveToggles()
{
return m_Toggles.Where(x => x.isOn);
}
/// <summary>
/// Returns the toggle that is the first in the list of active toggles.
/// </summary>
/// <returns>The first active toggle from m_Toggles</returns>
/// <remarks>
/// Get the active toggle for this group. As the group
/// </remarks>
public Toggle GetFirstActiveToggle()
{
IEnumerable<Toggle> activeToggles = ActiveToggles();
return activeToggles.Count() > 0 ? activeToggles.First() : null;
}
/// <summary>
/// Switch all toggles off.
/// </summary>
/// <remarks>
/// This method can be used to switch all toggles off, regardless of whether the allowSwitchOff property is enabled or not.
/// </remarks>
public void SetAllTogglesOff(bool sendCallback = true)
{
bool oldAllowSwitchOff = m_AllowSwitchOff;
m_AllowSwitchOff = true;
if (sendCallback)
{
for (var i = 0; i < m_Toggles.Count; i++)
m_Toggles[i].isOn = false;
}
else
{
for (var i = 0; i < m_Toggles.Count; i++)
m_Toggles[i].SetIsOnWithoutNotify(false);
}
m_AllowSwitchOff = oldAllowSwitchOff;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2fafe2cfe61f6974895a912c3755e8f1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 10
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d63dd1a776d383248a21ec2a8a6e7868
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace UnityEngine.UI
{
internal class ReflectionMethodsCache
{
#if PACKAGE_PHYSICS
public delegate bool Raycast3DCallback(Ray r, out RaycastHit hit, float f, int i);
public delegate RaycastHit[] RaycastAllCallback(Ray r, float f, int i);
public delegate int GetRaycastNonAllocCallback(Ray r, RaycastHit[] results, float f, int i);
public Raycast3DCallback raycast3D = null;
public RaycastAllCallback raycast3DAll = null;
public GetRaycastNonAllocCallback getRaycastNonAlloc = null;
#endif
#if PACKAGE_PHYSICS2D
public delegate RaycastHit2D Raycast2DCallback(Vector2 p1, Vector2 p2, float f, int i);
public delegate RaycastHit2D[] GetRayIntersectionAllCallback(Ray r, float f, int i);
public delegate int GetRayIntersectionAllNonAllocCallback(Ray r, RaycastHit2D[] results, float f, int i);
public Raycast2DCallback raycast2D = null;
public GetRayIntersectionAllCallback getRayIntersectionAll = null;
public GetRayIntersectionAllNonAllocCallback getRayIntersectionAllNonAlloc = null;
#endif
// We call Physics.Raycast and Physics2D.Raycast through reflection to avoid creating a hard dependency from
// this class to the Physics/Physics2D modules, which would otherwise make it impossible to make content with UI
// without force-including both modules.
//
// *NOTE* If other methods are required ensure to add [RequiredByNativeCode] to the bindings for that function. It prevents
// the function from being stripped if required. See Dynamics.bindings.cs for examples (search for GraphicRaycaster.cs).
public ReflectionMethodsCache()
{
#if PACKAGE_PHYSICS
var raycast3DMethodInfo = typeof(Physics).GetMethod("Raycast", new[] {typeof(Ray), typeof(RaycastHit).MakeByRefType(), typeof(float), typeof(int)});
if (raycast3DMethodInfo != null)
raycast3D = (Raycast3DCallback)Delegate.CreateDelegate(typeof(Raycast3DCallback), raycast3DMethodInfo);
var raycastAllMethodInfo = typeof(Physics).GetMethod("RaycastAll", new[] {typeof(Ray), typeof(float), typeof(int)});
if (raycastAllMethodInfo != null)
raycast3DAll = (RaycastAllCallback)Delegate.CreateDelegate(typeof(RaycastAllCallback), raycastAllMethodInfo);
var getRaycastAllNonAllocMethodInfo = typeof(Physics).GetMethod("RaycastNonAlloc", new[] { typeof(Ray), typeof(RaycastHit[]), typeof(float), typeof(int) });
if (getRaycastAllNonAllocMethodInfo != null)
getRaycastNonAlloc = (GetRaycastNonAllocCallback)Delegate.CreateDelegate(typeof(GetRaycastNonAllocCallback), getRaycastAllNonAllocMethodInfo);
#endif
#if PACKAGE_PHYSICS2D
var raycast2DMethodInfo = typeof(Physics2D).GetMethod("Raycast", new[] { typeof(Vector2), typeof(Vector2), typeof(float), typeof(int) });
if (raycast2DMethodInfo != null)
raycast2D = (Raycast2DCallback)Delegate.CreateDelegate(typeof(Raycast2DCallback), raycast2DMethodInfo);
var getRayIntersectionAllMethodInfo = typeof(Physics2D).GetMethod("GetRayIntersectionAll", new[] {typeof(Ray), typeof(float), typeof(int)});
if (getRayIntersectionAllMethodInfo != null)
getRayIntersectionAll = (GetRayIntersectionAllCallback)Delegate.CreateDelegate(typeof(GetRayIntersectionAllCallback), getRayIntersectionAllMethodInfo);
var getRayIntersectionAllNonAllocMethodInfo = typeof(Physics2D).GetMethod("GetRayIntersectionNonAlloc", new[] { typeof(Ray), typeof(RaycastHit2D[]), typeof(float), typeof(int) });
if (getRayIntersectionAllNonAllocMethodInfo != null)
getRayIntersectionAllNonAlloc = (GetRayIntersectionAllNonAllocCallback)Delegate.CreateDelegate(typeof(GetRayIntersectionAllNonAllocCallback), getRayIntersectionAllNonAllocMethodInfo);
#endif
}
private static ReflectionMethodsCache s_ReflectionMethodsCache = null;
public static ReflectionMethodsCache Singleton
{
get
{
if (s_ReflectionMethodsCache == null)
s_ReflectionMethodsCache = new ReflectionMethodsCache();
return s_ReflectionMethodsCache;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fcae021c30eccf74790a4e816ba97c19
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,364 @@
using System;
using System.Collections.Generic;
using UnityEngine.Pool;
namespace UnityEngine.UI
{
/// <summary>
/// A utility class that can aid in the generation of meshes for the UI.
/// </summary>
/// <remarks>
/// This class implements IDisposable to aid with memory management.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// using UnityEngine;
/// using UnityEngine.UI;
///
/// public class ExampleClass : MonoBehaviour
/// {
/// Mesh m;
///
/// void Start()
/// {
/// Color32 color32 = Color.red;
/// using (var vh = new VertexHelper())
/// {
/// vh.AddVert(new Vector3(0, 0), color32, new Vector2(0f, 0f));
/// vh.AddVert(new Vector3(0, 100), color32, new Vector2(0f, 1f));
/// vh.AddVert(new Vector3(100, 100), color32, new Vector2(1f, 1f));
/// vh.AddVert(new Vector3(100, 0), color32, new Vector2(1f, 0f));
///
/// vh.AddTriangle(0, 1, 2);
/// vh.AddTriangle(2, 3, 0);
/// vh.FillMesh(m);
/// }
/// }
/// }
/// ]]>
///</code>
/// </example>
public class VertexHelper : IDisposable
{
private List<Vector3> m_Positions;
private List<Color32> m_Colors;
private List<Vector4> m_Uv0S;
private List<Vector4> m_Uv1S;
private List<Vector4> m_Uv2S;
private List<Vector4> m_Uv3S;
private List<Vector3> m_Normals;
private List<Vector4> m_Tangents;
private List<int> m_Indices;
private static readonly Vector4 s_DefaultTangent = new Vector4(1.0f, 0.0f, 0.0f, -1.0f);
private static readonly Vector3 s_DefaultNormal = Vector3.back;
private bool m_ListsInitalized = false;
public VertexHelper()
{}
public VertexHelper(Mesh m)
{
InitializeListIfRequired();
m_Positions.AddRange(m.vertices);
m_Colors.AddRange(m.colors32);
List<Vector4> tempUVList = new List<Vector4>();
m.GetUVs(0, tempUVList);
m_Uv0S.AddRange(tempUVList);
m.GetUVs(1, tempUVList);
m_Uv1S.AddRange(tempUVList);
m.GetUVs(2, tempUVList);
m_Uv2S.AddRange(tempUVList);
m.GetUVs(3, tempUVList);
m_Uv3S.AddRange(tempUVList);
m_Normals.AddRange(m.normals);
m_Tangents.AddRange(m.tangents);
m_Indices.AddRange(m.GetIndices(0));
}
private void InitializeListIfRequired()
{
if (!m_ListsInitalized)
{
m_Positions = ListPool<Vector3>.Get();
m_Colors = ListPool<Color32>.Get();
m_Uv0S = ListPool<Vector4>.Get();
m_Uv1S = ListPool<Vector4>.Get();
m_Uv2S = ListPool<Vector4>.Get();
m_Uv3S = ListPool<Vector4>.Get();
m_Normals = ListPool<Vector3>.Get();
m_Tangents = ListPool<Vector4>.Get();
m_Indices = ListPool<int>.Get();
m_ListsInitalized = true;
}
}
/// <summary>
/// Cleanup allocated memory.
/// </summary>
public void Dispose()
{
if (m_ListsInitalized)
{
ListPool<Vector3>.Release(m_Positions);
ListPool<Color32>.Release(m_Colors);
ListPool<Vector4>.Release(m_Uv0S);
ListPool<Vector4>.Release(m_Uv1S);
ListPool<Vector4>.Release(m_Uv2S);
ListPool<Vector4>.Release(m_Uv3S);
ListPool<Vector3>.Release(m_Normals);
ListPool<Vector4>.Release(m_Tangents);
ListPool<int>.Release(m_Indices);
m_Positions = null;
m_Colors = null;
m_Uv0S = null;
m_Uv1S = null;
m_Uv2S = null;
m_Uv3S = null;
m_Normals = null;
m_Tangents = null;
m_Indices = null;
m_ListsInitalized = false;
}
}
/// <summary>
/// Clear all vertices from the stream.
/// </summary>
public void Clear()
{
// Only clear if we have our lists created.
if (m_ListsInitalized)
{
m_Positions.Clear();
m_Colors.Clear();
m_Uv0S.Clear();
m_Uv1S.Clear();
m_Uv2S.Clear();
m_Uv3S.Clear();
m_Normals.Clear();
m_Tangents.Clear();
m_Indices.Clear();
}
}
/// <summary>
/// Current number of vertices in the buffer.
/// </summary>
public int currentVertCount
{
get { return m_Positions != null ? m_Positions.Count : 0; }
}
/// <summary>
/// Get the number of indices set on the VertexHelper.
/// </summary>
public int currentIndexCount
{
get { return m_Indices != null ? m_Indices.Count : 0; }
}
/// <summary>
/// Fill a UIVertex with data from index i of the stream.
/// </summary>
/// <param name="vertex">Vertex to populate</param>
/// <param name="i">Index to populate.</param>
public void PopulateUIVertex(ref UIVertex vertex, int i)
{
InitializeListIfRequired();
vertex.position = m_Positions[i];
vertex.color = m_Colors[i];
vertex.uv0 = m_Uv0S[i];
vertex.uv1 = m_Uv1S[i];
vertex.uv2 = m_Uv2S[i];
vertex.uv3 = m_Uv3S[i];
vertex.normal = m_Normals[i];
vertex.tangent = m_Tangents[i];
}
/// <summary>
/// Set a UIVertex at the given index.
/// </summary>
/// <param name="vertex">The vertex to fill</param>
/// <param name="i">the position in the current list to fill.</param>
public void SetUIVertex(UIVertex vertex, int i)
{
InitializeListIfRequired();
m_Positions[i] = vertex.position;
m_Colors[i] = vertex.color;
m_Uv0S[i] = vertex.uv0;
m_Uv1S[i] = vertex.uv1;
m_Uv2S[i] = vertex.uv2;
m_Uv3S[i] = vertex.uv3;
m_Normals[i] = vertex.normal;
m_Tangents[i] = vertex.tangent;
}
/// <summary>
/// Fill the given mesh with the stream data.
/// </summary>
public void FillMesh(Mesh mesh)
{
InitializeListIfRequired();
mesh.Clear();
if (m_Positions.Count >= 65000)
throw new ArgumentException("Mesh can not have more than 65000 vertices");
mesh.SetVertices(m_Positions);
mesh.SetColors(m_Colors);
mesh.SetUVs(0, m_Uv0S);
mesh.SetUVs(1, m_Uv1S);
mesh.SetUVs(2, m_Uv2S);
mesh.SetUVs(3, m_Uv3S);
mesh.SetNormals(m_Normals);
mesh.SetTangents(m_Tangents);
mesh.SetTriangles(m_Indices, 0);
mesh.RecalculateBounds();
}
/// <summary>
/// Add a single vertex to the stream.
/// </summary>
/// <param name="position">Position of the vert</param>
/// <param name="color">Color of the vert</param>
/// <param name="uv0">UV of the vert</param>
/// <param name="uv1">UV1 of the vert</param>
/// <param name="uv2">UV2 of the vert</param>
/// <param name="uv3">UV3 of the vert</param>
/// <param name="normal">Normal of the vert.</param>
/// <param name="tangent">Tangent of the vert</param>
public void AddVert(Vector3 position, Color32 color, Vector4 uv0, Vector4 uv1, Vector4 uv2, Vector4 uv3, Vector3 normal, Vector4 tangent)
{
InitializeListIfRequired();
m_Positions.Add(position);
m_Colors.Add(color);
m_Uv0S.Add(uv0);
m_Uv1S.Add(uv1);
m_Uv2S.Add(uv2);
m_Uv3S.Add(uv3);
m_Normals.Add(normal);
m_Tangents.Add(tangent);
}
/// <summary>
/// Add a single vertex to the stream.
/// </summary>
/// <param name="position">Position of the vert</param>
/// <param name="color">Color of the vert</param>
/// <param name="uv0">UV of the vert</param>
/// <param name="uv1">UV1 of the vert</param>
/// <param name="normal">Normal of the vert.</param>
/// <param name="tangent">Tangent of the vert</param>
public void AddVert(Vector3 position, Color32 color, Vector4 uv0, Vector4 uv1, Vector3 normal, Vector4 tangent)
{
AddVert(position, color, uv0, uv1, Vector4.zero, Vector4.zero, normal, tangent);
}
/// <summary>
/// Add a single vertex to the stream.
/// </summary>
/// <param name="position">Position of the vert</param>
/// <param name="color">Color of the vert</param>
/// <param name="uv0">UV of the vert</param>
public void AddVert(Vector3 position, Color32 color, Vector4 uv0)
{
AddVert(position, color, uv0, Vector4.zero, s_DefaultNormal, s_DefaultTangent);
}
/// <summary>
/// Add a single vertex to the stream.
/// </summary>
/// <param name="v">The vertex to add</param>
public void AddVert(UIVertex v)
{
AddVert(v.position, v.color, v.uv0, v.uv1, v.uv2, v.uv3, v.normal, v.tangent);
}
/// <summary>
/// Add a triangle to the buffer.
/// </summary>
/// <param name="idx0">index 0</param>
/// <param name="idx1">index 1</param>
/// <param name="idx2">index 2</param>
public void AddTriangle(int idx0, int idx1, int idx2)
{
InitializeListIfRequired();
m_Indices.Add(idx0);
m_Indices.Add(idx1);
m_Indices.Add(idx2);
}
/// <summary>
/// Add a quad to the stream.
/// </summary>
/// <param name="verts">4 Vertices representing the quad.</param>
public void AddUIVertexQuad(UIVertex[] verts)
{
int startIndex = currentVertCount;
for (int i = 0; i < 4; i++)
AddVert(verts[i].position, verts[i].color, verts[i].uv0, verts[i].uv1, verts[i].normal, verts[i].tangent);
AddTriangle(startIndex, startIndex + 1, startIndex + 2);
AddTriangle(startIndex + 2, startIndex + 3, startIndex);
}
/// <summary>
/// Add a stream of custom UIVertex and corresponding indices.
/// </summary>
/// <param name="verts">The custom stream of verts to add to the helpers internal data.</param>
/// <param name="indices">The custom stream of indices to add to the helpers internal data.</param>
public void AddUIVertexStream(List<UIVertex> verts, List<int> indices)
{
InitializeListIfRequired();
if (verts != null)
{
CanvasRenderer.AddUIVertexStream(verts, m_Positions, m_Colors, m_Uv0S, m_Uv1S, m_Uv2S, m_Uv3S, m_Normals, m_Tangents);
}
if (indices != null)
{
m_Indices.AddRange(indices);
}
}
/// <summary>
/// Add a list of triangles to the stream.
/// </summary>
/// <param name="verts">Vertices to add. Length should be divisible by 3.</param>
public void AddUIVertexTriangleStream(List<UIVertex> verts)
{
if (verts == null)
return;
InitializeListIfRequired();
CanvasRenderer.SplitUIVertexStreams(verts, m_Positions, m_Colors, m_Uv0S, m_Uv1S, m_Uv2S, m_Uv3S, m_Normals, m_Tangents, m_Indices);
}
/// <summary>
/// Create a stream of UI vertex (in triangles) from the stream.
/// </summary>
public void GetUIVertexStream(List<UIVertex> stream)
{
if (stream == null)
return;
InitializeListIfRequired();
CanvasRenderer.CreateUIVertexStream(stream, m_Positions, m_Colors, m_Uv0S, m_Uv1S, m_Uv2S, m_Uv3S, m_Normals, m_Tangents, m_Indices);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c459424081436584c8cec10767f9ac38
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cfa73a847eeff06448bb50a8ac6a94e0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using UnityEngine.EventSystems;
namespace UnityEngine.UI
{
[Obsolete("Use BaseMeshEffect instead", true)]
/// <summary>
/// Obsolete class use BaseMeshEffect instead.
/// </summary>
public abstract class BaseVertexEffect
{
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
[Obsolete("Use BaseMeshEffect.ModifyMeshes instead", true)] //We can't upgrade automatically since the signature changed.
public abstract void ModifyVertices(List<UIVertex> vertices);
}
/// <summary>
/// Base class for effects that modify the generated Mesh.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
///using UnityEngine;
///using UnityEngine.UI;
///
///public class PositionAsUV1 : BaseMeshEffect
///{
/// protected PositionAsUV1()
/// {}
///
/// public override void ModifyMesh(Mesh mesh)
/// {
/// if (!IsActive())
/// return;
///
/// var verts = mesh.vertices.ToList();
/// var uvs = ListPool<Vector2>.Get();
///
/// for (int i = 0; i < verts.Count; i++)
/// {
/// var vert = verts[i];
/// uvs.Add(new Vector2(verts[i].x, verts[i].y));
/// verts[i] = vert;
/// }
/// mesh.SetUVs(1, uvs);
/// ListPool<Vector2>.Release(uvs);
/// }
///}
/// ]]>
///</code>
///</example>
[ExecuteAlways]
public abstract class BaseMeshEffect : UIBehaviour, IMeshModifier
{
[NonSerialized]
private Graphic m_Graphic;
/// <summary>
/// The graphic component that the Mesh Effect will aplly to.
/// </summary>
protected Graphic graphic
{
get
{
if (m_Graphic == null)
m_Graphic = GetComponent<Graphic>();
return m_Graphic;
}
}
protected override void OnEnable()
{
base.OnEnable();
if (graphic != null)
graphic.SetVerticesDirty();
}
protected override void OnDisable()
{
if (graphic != null)
graphic.SetVerticesDirty();
base.OnDisable();
}
/// <summary>
/// Called from the native side any time a animation property is changed.
/// </summary>
protected override void OnDidApplyAnimationProperties()
{
if (graphic != null)
graphic.SetVerticesDirty();
base.OnDidApplyAnimationProperties();
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (graphic != null)
graphic.SetVerticesDirty();
}
#endif
/// <summary>
/// Function that is called when the Graphic is populating the mesh.
/// </summary>
/// <param name="mesh">The generated mesh of the Graphic element that needs modification.</param>
public virtual void ModifyMesh(Mesh mesh)
{
using (var vh = new VertexHelper(mesh))
{
ModifyMesh(vh);
vh.FillMesh(mesh);
}
}
public abstract void ModifyMesh(VertexHelper vh);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 718966f960cb50643986195e5fe953ca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.UI
{
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
[Obsolete("Use IMeshModifier instead", true)]
public interface IVertexModifier
{
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
[Obsolete("use IMeshModifier.ModifyMesh (VertexHelper verts) instead", true)]
void ModifyVertices(List<UIVertex> verts);
}
/// <summary>
/// Interface which allows for the modification of verticies in a Graphic before they are passed to the CanvasRenderer.
/// When a Graphic generates a list of vertices they are passed (in order) to any components on the GameObject that implement IMeshModifier. This component can modify the given Mesh.
/// </summary>
public interface IMeshModifier
{
[Obsolete("use IMeshModifier.ModifyMesh (VertexHelper verts) instead", false)]
void ModifyMesh(Mesh mesh);
/// <summary>
/// Call used to modify mesh.
/// Place any custom mesh processing in this function.
/// </summary>
void ModifyMesh(VertexHelper verts);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 85f3c3a3c2623cd4da1661b0479e2ef2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,47 @@
using UnityEngine.Pool;
namespace UnityEngine.UI
{
[AddComponentMenu("UI/Effects/Outline", 81)]
/// <summary>
/// Adds an outline to a graphic using IVertexModifier.
/// </summary>
public class Outline : Shadow
{
protected Outline()
{}
public override void ModifyMesh(VertexHelper vh)
{
if (!IsActive())
return;
var verts = ListPool<UIVertex>.Get();
vh.GetUIVertexStream(verts);
var neededCpacity = verts.Count * 5;
if (verts.Capacity < neededCpacity)
verts.Capacity = neededCpacity;
var start = 0;
var end = verts.Count;
ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, effectDistance.x, effectDistance.y);
start = end;
end = verts.Count;
ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, effectDistance.x, -effectDistance.y);
start = end;
end = verts.Count;
ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, -effectDistance.x, effectDistance.y);
start = end;
end = verts.Count;
ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, -effectDistance.x, -effectDistance.y);
vh.Clear();
vh.AddUIVertexTriangleStream(verts);
ListPool<UIVertex>.Release(verts);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e19747de3f5aca642ab2be37e372fb86
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,25 @@
using System.Linq;
namespace UnityEngine.UI
{
[AddComponentMenu("UI/Effects/Position As UV1", 82)]
/// <summary>
/// An IVertexModifier which sets the raw vertex position into UV1 of the generated verts.
/// </summary>
public class PositionAsUV1 : BaseMeshEffect
{
protected PositionAsUV1()
{}
public override void ModifyMesh(VertexHelper vh)
{
UIVertex vert = new UIVertex();
for (int i = 0; i < vh.currentVertCount; i++)
{
vh.PopulateUIVertex(ref vert, i);
vert.uv1 = new Vector2(vert.position.x, vert.position.y);
vh.SetUIVertex(vert, i);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 020ee4c1798a3d243a4b8cbfee6cac2d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,144 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
namespace UnityEngine.UI
{
[AddComponentMenu("UI/Effects/Shadow", 80)]
/// <summary>
/// Adds an outline to a graphic using IVertexModifier.
/// </summary>
public class Shadow : BaseMeshEffect
{
[SerializeField]
private Color m_EffectColor = new Color(0f, 0f, 0f, 0.5f);
[SerializeField]
private Vector2 m_EffectDistance = new Vector2(1f, -1f);
[SerializeField]
private bool m_UseGraphicAlpha = true;
private const float kMaxEffectDistance = 600f;
protected Shadow()
{}
#if UNITY_EDITOR
protected override void OnValidate()
{
effectDistance = m_EffectDistance;
base.OnValidate();
}
#endif
/// <summary>
/// Color for the effect
/// </summary>
public Color effectColor
{
get { return m_EffectColor; }
set
{
m_EffectColor = value;
if (graphic != null)
graphic.SetVerticesDirty();
}
}
/// <summary>
/// How far is the shadow from the graphic.
/// </summary>
public Vector2 effectDistance
{
get { return m_EffectDistance; }
set
{
if (value.x > kMaxEffectDistance)
value.x = kMaxEffectDistance;
if (value.x < -kMaxEffectDistance)
value.x = -kMaxEffectDistance;
if (value.y > kMaxEffectDistance)
value.y = kMaxEffectDistance;
if (value.y < -kMaxEffectDistance)
value.y = -kMaxEffectDistance;
if (m_EffectDistance == value)
return;
m_EffectDistance = value;
if (graphic != null)
graphic.SetVerticesDirty();
}
}
/// <summary>
/// Should the shadow inherit the alpha from the graphic?
/// </summary>
public bool useGraphicAlpha
{
get { return m_UseGraphicAlpha; }
set
{
m_UseGraphicAlpha = value;
if (graphic != null)
graphic.SetVerticesDirty();
}
}
protected void ApplyShadowZeroAlloc(List<UIVertex> verts, Color32 color, int start, int end, float x, float y)
{
UIVertex vt;
var neededCapacity = verts.Count + end - start;
if (verts.Capacity < neededCapacity)
verts.Capacity = neededCapacity;
for (int i = start; i < end; ++i)
{
vt = verts[i];
verts.Add(vt);
Vector3 v = vt.position;
v.x += x;
v.y += y;
vt.position = v;
var newColor = color;
if (m_UseGraphicAlpha)
newColor.a = (byte)((newColor.a * verts[i].color.a) / 255);
vt.color = newColor;
verts[i] = vt;
}
}
/// <summary>
/// Duplicate vertices from start to end and turn them into shadows with the given offset.
/// </summary>
/// <param name="verts">Vert list to copy</param>
/// <param name="color">Shadow color</param>
/// <param name="start">The start index in the verts list</param>
/// <param name="end">The end index in the vers list</param>
/// <param name="x">The shadows x offset</param>
/// <param name="y">The shadows y offset</param>
protected void ApplyShadow(List<UIVertex> verts, Color32 color, int start, int end, float x, float y)
{
ApplyShadowZeroAlloc(verts, color, start, end, x, y);
}
public override void ModifyMesh(VertexHelper vh)
{
if (!IsActive())
return;
var output = ListPool<UIVertex>.Get();
vh.GetUIVertexStream(output);
ApplyShadow(output, effectColor, 0, output.Count, effectDistance.x, effectDistance.y);
vh.Clear();
vh.AddUIVertexTriangleStream(output);
ListPool<UIVertex>.Release(output);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cfabb0440166ab443bba8876756fdfa9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,46 @@
{
"name": "UnityEngine.UI",
"references": [
"UnityEngine.UIElementsModule"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [
{
"name": "com.unity.modules.physics",
"expression": "1.0.0",
"define": "PACKAGE_PHYSICS"
},
{
"name": "com.unity.modules.physics2d",
"expression": "1.0.0",
"define": "PACKAGE_PHYSICS2D"
},
{
"name": "com.unity.modules.tilemap",
"expression": "1.0.0",
"define": "PACKAGE_TILEMAP"
},
{
"name": "com.unity.modules.animation",
"expression": "1.0.0",
"define": "PACKAGE_ANIMATION"
},
{
"name": "com.unity.modules.uielements",
"expression": "1.0.0",
"define": "PACKAGE_UITOOLKIT"
},
{
"name": "com.unity.inputsystem",
"expression": "1.7.0",
"define": "PACKAGE_INPUTSYSTEM"
}
],
"noEngineReferences": false
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 2bafac87e7f4b9b418d9448d219b01ab
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: