namespace UIWidgets
{
using System;
using System.Collections.Generic;
using UIWidgets.Attributes;
using UIWidgets.Styles;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
///
/// Base class for the ScrollBlock.
///
[RequireComponent(typeof(EasyLayoutNS.EasyLayout))]
public abstract class ScrollBlockBase : UIBehaviourConditional, IStylable, ILateUpdatable,
IBeginDragHandler, IDragHandler, IEndDragHandler, IInitializePotentialDragHandler, IScrollHandler
{
///
/// Do nothing.
///
public static void DoNothing()
{
}
///
/// Default function to get value.
///
/// Step.
/// Value.
public static string DefaultValue(int step)
{
return string.Format("Index: {0}", step.ToString());
}
///
/// Default function to check is interactable.
///
/// true.
protected static bool DefaultInteractable()
{
return true;
}
///
/// Default function to check is action allowed.
///
/// true.
protected static bool DefaultAllow()
{
return true;
}
///
/// Action to increase the value.
///
public Action Increase = DoNothing;
///
/// Action to decrease the value.
///
public Action Decrease = DoNothing;
///
/// Function to check is increase allowed.
///
public Func AllowIncrease = DefaultAllow;
///
/// Function to check is decrease allowed.
///
public Func AllowDecrease = DefaultAllow;
///
/// Convert index to the displayed string.
///
public Func Value = DefaultValue;
///
/// Is interactable?
///
public Func IsInteractable = DefaultInteractable;
///
/// Size of the DefaultItem.
///
public Vector2 DefaultItemSize
{
get;
protected set;
}
///
/// Layout.
///
[NonSerialized]
protected EasyLayoutBridge Layout;
///
/// Count of the visible items.
///
public abstract int Count
{
get;
}
///
/// Is horizontal scroll?
///
[SerializeField]
protected bool IsHorizontal;
///
/// Scroll sensitivity.
///
[SerializeField]
public float ScrollSensitivity = 15f;
///
/// Layout internal padding.
///
public float Padding
{
get
{
return Layout.GetFiller().x;
}
set
{
var padding = ClampPadding(value);
Layout.SetFiller(padding, 0f);
}
}
///
/// Animate inertia scroll with unscaled time.
///
[SerializeField]
public bool UnscaledTime = true;
///
/// Auto-scroll to center after scroll/drag.
///
[SerializeField]
public bool AlwaysCenter = true;
///
/// Time to stop.
///
[SerializeField]
[EditorConditionBool("AlwaysCenter")]
public float TimeToStop = 0.5f;
///
/// Velocity.
///
[NonSerialized]
protected float ScrollVelocity;
///
/// Inertia velocity.
///
[NonSerialized]
protected float IntertiaVelocity;
///
/// Current deceleration rate.
///
[NonSerialized]
protected float CurrentDecelerationRate;
///
/// Inertia distance.
///
[NonSerialized]
protected float InertiaDistance;
///
/// Is drag event occurring?
///
[NonSerialized]
protected bool IsDragging;
///
/// Is scrolling occurring?
///
[NonSerialized]
protected bool IsScrolling;
///
/// Previous scroll value.
///
[NonSerialized]
protected float PrevScrollValue;
///
/// Current scroll value.
///
[NonSerialized]
protected float CurrentScrollValue;
RectTransform rectTransform;
///
/// Current RectTransformn.
///
protected RectTransform RectTransform
{
get
{
if (rectTransform == null)
{
rectTransform = transform as RectTransform;
}
return rectTransform;
}
}
///
/// Median index of the components.
///
protected abstract int ComponentsMedian
{
get;
}
///
/// Distance to center.
///
public float DistanceToCenter
{
get
{
return GetCenter() - Padding;
}
}
///
/// Is inited?
///
protected bool IsInited
{
get;
private set;
}
///
/// Start this instance.
///
protected override void Start()
{
Init();
}
///
/// Init this instance.
///
public void Init()
{
if (IsInited)
{
return;
}
IsInited = true;
UpdateLayout();
var resizer = Utilities.GetOrAddComponent(this);
resizer.OnResizeNextFrame.AddListener(Resize);
Resize();
AlignComponents();
}
///
/// Clamp padding value.
///
/// Padding.
/// Clamped value.
protected float ClampPadding(float padding)
{
var current = Layout.GetFiller().x;
var delta = current - padding;
if ((delta < 0) && (!AllowDecrease()))
{
return current;
}
if ((delta > 0) && (!AllowIncrease()))
{
return current;
}
var size = ItemFullSize();
var center = GetCenter();
if (Mathf.Round(padding) >= (-size / 2f))
{
var d = Mathf.Round(padding) - center;
var n = Mathf.RoundToInt(d / size);
if (n > 0)
{
for (int i = 0; i < n; i++)
{
if (!AllowDecrease())
{
break;
}
padding -= size;
Decrease();
}
UpdateView();
}
}
else if (Mathf.Round(padding) < (-size / 2f))
{
var d = center - Mathf.Round(padding);
var n = Mathf.RoundToInt(d / size);
if (n > 0)
{
for (int i = 0; i < n; i++)
{
if (!AllowIncrease())
{
break;
}
padding += size;
Increase();
}
UpdateView();
}
}
return padding;
}
///
/// Update the layout.
///
protected abstract void UpdateLayout();
///
/// Container size.
///
/// Size.
protected float ContainerSize()
{
return (IsHorizontal ? RectTransform.rect.width : RectTransform.rect.height) - Layout.GetFullMargin();
}
///
/// Content size.
///
/// Size.
protected float ContentSize()
{
return (ItemFullSize() * Count) - Layout.GetSpacing();
}
///
/// Item size.
///
/// Size.
protected float ItemSize()
{
return IsHorizontal ? DefaultItemSize.x : DefaultItemSize.y;
}
///
/// Item size with spacing.
///
/// Size.
protected float ItemFullSize()
{
return ItemSize() + Layout.GetSpacing();
}
///
/// Calculate the maximum count of the visible components.
///
/// Maximum count of the visible components.
protected int CalculateMax()
{
var result = Mathf.CeilToInt((ContainerSize() + Layout.GetSpacing()) / ItemFullSize()) + 1;
if (result < 0)
{
result = 0;
}
if ((result % 2) == 0)
{
result += 1;
}
return result;
}
///
/// Is component is null?
///
/// Component.
/// true if component is null; otherwise, false.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Reviewed.")]
protected virtual bool IsNullComponent(ScrollBlockItem component)
{
return component == null;
}
///
/// Process RectTransform resize.
///
protected abstract void Resize();
///
/// Update view.
///
public abstract void UpdateView();
///
/// Returns true if the GameObject and the Component are active.
///
/// true if the GameObject and the Component are active; otherwise false.
public override bool IsActive()
{
return base.IsActive() && IsInited && IsInteractable();
}
///
/// Process the begin drag event.
///
/// Event data.
public virtual void OnBeginDrag(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
{
return;
}
if (!IsActive())
{
return;
}
IsDragging = true;
PrevScrollValue = Padding;
CurrentScrollValue = Padding;
StopInertia();
}
///
/// Process the drag event.
///
/// Event data.
public virtual void OnDrag(PointerEventData eventData)
{
if (!IsDragging)
{
return;
}
if (eventData.button != PointerEventData.InputButton.Left)
{
return;
}
if (!IsActive())
{
return;
}
StopInertia();
var scroll_delta = IsHorizontal ? eventData.delta.x : -eventData.delta.y;
Scroll(scroll_delta);
}
///
/// Process scroll event.
///
/// Event data.
public void OnScroll(PointerEventData eventData)
{
if (!IsActive())
{
return;
}
IsScrolling = true;
var scroll_delta = IsHorizontal ? eventData.scrollDelta.x : -eventData.scrollDelta.y;
Scroll(scroll_delta * ScrollSensitivity);
}
///
/// Scroll.
///
/// Delta.
protected virtual void Scroll(float delta)
{
Padding += delta;
CurrentScrollValue += delta;
var time_delta = UtilitiesTime.DefaultGetDeltaTime(UnscaledTime);
var new_velocity = (PrevScrollValue - CurrentScrollValue) / time_delta;
ScrollVelocity = Mathf.Lerp(ScrollVelocity, new_velocity, time_delta * 10);
PrevScrollValue = CurrentScrollValue;
}
///
/// Process the end drag event.
///
/// Event data.
public virtual void OnEndDrag(PointerEventData eventData)
{
if (!IsDragging)
{
return;
}
if (eventData.button != PointerEventData.InputButton.Left)
{
return;
}
IsDragging = false;
InitIntertia();
}
///
/// Init inertia.
///
protected virtual void InitIntertia()
{
IntertiaVelocity = -ScrollVelocity;
CurrentDecelerationRate = -IntertiaVelocity / TimeToStop;
var direction = Mathf.Sign(IntertiaVelocity);
var time_to_stop_sq = Mathf.Pow(TimeToStop, 2f);
var distance = ((-Mathf.Abs(CurrentDecelerationRate) * time_to_stop_sq) / 2f) + (Mathf.Abs(IntertiaVelocity) * TimeToStop);
InertiaDistance = ClampDistance(distance, direction);
IntertiaVelocity = (InertiaDistance - (-Mathf.Abs(CurrentDecelerationRate) * (TimeToStop * TimeToStop) / 2f)) / TimeToStop;
IntertiaVelocity *= direction;
}
///
/// Process the enable event.
///
protected override void OnEnable()
{
base.OnEnable();
Updater.AddLateUpdate(this);
}
///
/// Process the disable event.
///
protected override void OnDisable()
{
base.OnDisable();
Updater.RemoveLateUpdate(this);
}
///
/// Late update.
///
public virtual void RunLateUpdate()
{
if (IsScrolling)
{
IsScrolling = false;
InitIntertia();
}
else if (!IsDragging && (InertiaDistance > 0f) && AlwaysCenter)
{
var delta = UtilitiesTime.DefaultGetDeltaTime(UnscaledTime);
var distance = IntertiaVelocity > 0f
? Mathf.Min(InertiaDistance, IntertiaVelocity * delta)
: Mathf.Max(-InertiaDistance, IntertiaVelocity * delta);
Padding += distance;
InertiaDistance -= Mathf.Abs(distance);
if (InertiaDistance > 0f)
{
IntertiaVelocity += CurrentDecelerationRate * delta;
ScrollVelocity = -IntertiaVelocity;
}
else
{
StopInertia();
}
}
}
///
/// Stop inertia.
///
protected void StopInertia()
{
CurrentDecelerationRate = 0f;
InertiaDistance = 0f;
}
///
/// Clamp distance to stop right at value.
///
/// Distance.
/// Scroll direction.
/// Clamped distance.
protected float ClampDistance(float distance, float direction)
{
var extra = (GetCenter() - Padding) * direction;
var steps = Mathf.Round((Mathf.Abs(distance) - extra) / ItemFullSize());
var new_distance = (steps * ItemFullSize()) + extra;
return new_distance;
}
///
/// Get center.
///
/// Center.
protected float GetCenter()
{
return -(ContentSize() - ContainerSize()) / 2f;
}
///
/// Align components.
///
protected void AlignComponents()
{
Padding = GetCenter();
}
///
/// Process the destroy event.
///
protected override void OnDestroy()
{
var resizer = GetComponent();
if (resizer != null)
{
resizer.OnResizeNextFrame.RemoveListener(Resize);
}
}
///
/// Called by a BaseInputModule when a drag has been found but before it is valid to begin the drag.
///
/// Event data.
public virtual void OnInitializePotentialDrag(PointerEventData eventData)
{
}
#region IStylable implementation
///
public abstract bool SetStyle(Style style);
///
public abstract bool GetStyle(Style style);
#endregion
}
}