init
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.Events;
|
||||
using frame8.Logic.Misc.Other.Extensions;
|
||||
using frame8.Logic.Misc.Visual.UI;
|
||||
using frame8.Logic.Misc.Visual.UI.MonoBehaviours;
|
||||
using Com.ForbiddenByte.OSA.Core;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// Important note: if used with ScrollbarFixer8 (which is true in the most cases,
|
||||
/// make sure <see cref="ScrollbarFixer8.minSize"/> is not too small
|
||||
/// </summary>
|
||||
public class DiscreteScrollbar : MonoBehaviour
|
||||
{
|
||||
public RectTransform slotPrefab;
|
||||
public RectTransform slotsParent;
|
||||
public UnityIntEvent OnSlotSelected;
|
||||
public Func<int> getItemsCountFunc;
|
||||
|
||||
Scrollbar _Scrollbar;
|
||||
RectTransform[] slots = new RectTransform[0];
|
||||
RectTransform _ScrollbarPanelRT;
|
||||
IScrollRectProxy _ScrollRectProxy;
|
||||
int _OneIfVert_ZeroIfHor;
|
||||
|
||||
const int MAX_COUNT = 100;
|
||||
bool _UpdatePending;
|
||||
|
||||
|
||||
void Awake()
|
||||
{
|
||||
// Get in parent, but ignore self
|
||||
_ScrollRectProxy = transform.parent.GetComponentInParent<IScrollRectProxy>();
|
||||
if (_ScrollRectProxy == null)
|
||||
throw new OSAException(GetType().Name + ": No IScrollRectProxy component found in parent");
|
||||
|
||||
_Scrollbar = GetComponent<Scrollbar>();
|
||||
_ScrollbarPanelRT = _Scrollbar.transform as RectTransform;
|
||||
_OneIfVert_ZeroIfHor = _ScrollRectProxy.IsHorizontal ? 0 : 1;
|
||||
|
||||
}
|
||||
|
||||
void OnEnable() { _UpdatePending = false; }
|
||||
|
||||
public void OnScrollbarSizeChanged()
|
||||
{
|
||||
StartCoroutine(UpdateSize());
|
||||
}
|
||||
|
||||
IEnumerator UpdateSize()
|
||||
{
|
||||
while (_UpdatePending) // wait for prev request to complete
|
||||
yield return null;
|
||||
|
||||
_UpdatePending = true;
|
||||
yield return null;
|
||||
|
||||
if (getItemsCountFunc == null)
|
||||
throw new OSAException(GetType().Name + "getItemsCountFunc==null. Please specify a count provider");
|
||||
|
||||
_UpdatePending = true;
|
||||
int count = getItemsCountFunc();
|
||||
if (count > MAX_COUNT)
|
||||
throw new OSAException(GetType().Name + ": count is " + count + ". Bigger than MAX_COUNT=" + MAX_COUNT + ". Are you sure you want to use a discrete scrollbar?");
|
||||
|
||||
Rebuild(count);
|
||||
_UpdatePending = false;
|
||||
}
|
||||
|
||||
public void Rebuild(int numSlots)
|
||||
{
|
||||
slotPrefab.gameObject.SetActive(true);
|
||||
|
||||
// Clear prev
|
||||
if (slots != null)
|
||||
foreach (var slot in slots)
|
||||
Destroy(slot.gameObject);
|
||||
|
||||
// Add new
|
||||
slots = new RectTransform[numSlots];
|
||||
float sizesCumu = 0;
|
||||
float slotSize = _ScrollbarPanelRT.rect.size[_OneIfVert_ZeroIfHor] / numSlots; // not using the handle's size because of rounding errors with higher <numSlots>
|
||||
RectTransform.Edge edgeToInsetFrom = _OneIfVert_ZeroIfHor == 1 ? RectTransform.Edge.Top : RectTransform.Edge.Left;
|
||||
for (int i = 0; i < numSlots; i++)
|
||||
{
|
||||
var slot = (Instantiate(slotPrefab.gameObject) as GameObject).GetComponent<RectTransform>();
|
||||
slots[i] = slot;
|
||||
slot.SetParent(slotsParent, false);
|
||||
slot.SetInsetAndSizeFromParentEdgeWithCurrentAnchors(edgeToInsetFrom, sizesCumu, slotSize);
|
||||
sizesCumu += slotSize;
|
||||
int copyOfI = i;
|
||||
slot.GetComponentInChildren<Button>().onClick.AddListener(() => { if (OnSlotSelected != null) OnSlotSelected.Invoke(copyOfI); });
|
||||
}
|
||||
slotPrefab.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
|
||||
[Serializable]
|
||||
public class UnityIntEvent : UnityEvent<int> { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b105aab89694f2040a2b9b7d81c9aa23
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,188 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEngine.UI;
|
||||
using System;
|
||||
using frame8.Logic.Misc.Other.Extensions;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility to expand an item when it's clicked, dispatching the size change request via <see cref="ISizeChangesHandler"/> for increased flexibility
|
||||
/// Known issue when used with OSA: when during collapsing the item goes outside viewport, the animation stales, since the views are recycled.
|
||||
/// This can be solved by having a separate resizing utility script that's not attached to a recycling-prone object.
|
||||
/// </summary>
|
||||
[Obsolete("This script will soon be deprecated. Please use the ExpandCollapseAnimationState class instead, which handles several edge cases better.")]
|
||||
public class ExpandCollapseOnClick : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The button to whose onClock to subscribe. If not specified, will try to GetComponent<Button> from the GO containing this script
|
||||
/// </summary>
|
||||
[Tooltip("will be taken from this object, if not specified")]
|
||||
public Button button = null;
|
||||
|
||||
/// <summary>When expanding, the initial size will be <see cref="nonExpandedSize"/> and the target size will be <see cref="nonExpandedSize"/> x <see cref="expandFactor"/>; opposite is true when collapsing</summary>
|
||||
[NonSerialized] // must be set through code
|
||||
public float expandFactor = 2f;
|
||||
|
||||
/// <summary>The duration of the expand(or collapse) animation</summary>
|
||||
public float animDuration = .2f;
|
||||
|
||||
public NonExpandedSizeSource nonExpandedSizeSource = NonExpandedSizeSource.WAIT_FOR_EXTERNAL;
|
||||
|
||||
[Tooltip("Used in conjunction with NonExpandedSizeSource.PREDEFINED")]
|
||||
public float nonExpandedSizePredefined = 0f;
|
||||
|
||||
public bool useUnscaledTime = true;
|
||||
|
||||
/// <summary>This is the size from which the item will start expanding</summary>
|
||||
[HideInInspector]
|
||||
public float nonExpandedSize = -1f;
|
||||
|
||||
/// <summary>This keeps track of the 'expanded' state. If true, on click the animation will set <see cref="nonExpandedSize"/> as the target size; else, <see cref="nonExpandedSize"/> x <see cref="expandFactor"/> </summary>
|
||||
[HideInInspector]
|
||||
public bool expanded = false;
|
||||
|
||||
[Tooltip("Returns a value between 0 and 1, 1 meaning end of the progress")]
|
||||
[FormerlySerializedAs("onExpandAmounChanged")] // correcting typo from pre-4.0 versions
|
||||
public UnityFloatEvent onExpandAmountChanged = null;
|
||||
|
||||
[Tooltip("Returns a value between nonExpandedSize and nonExpandedSize * expandFactor")]
|
||||
public UnityFloatEvent onExpandSizeChanged = null;
|
||||
|
||||
[Obsolete("Use onExpandAmountChanged, instead", true)]
|
||||
[HideInInspector] // just to be sure unity's serialization system won't behave buggy
|
||||
public UnityFloatEvent onExpandAmounChanged { get { return onExpandAmountChanged; } }
|
||||
|
||||
float Time { get { return useUnscaledTime ? UnityEngine.Time.unscaledTime : UnityEngine.Time.time; } }
|
||||
|
||||
float startSize;
|
||||
float endSize;
|
||||
float animStart;
|
||||
//float animEnd;
|
||||
bool animating = false;
|
||||
RectTransform rectTransform;
|
||||
|
||||
public ISizeChangesHandler sizeChangesHandler;
|
||||
|
||||
public enum NonExpandedSizeSource
|
||||
{
|
||||
WAIT_FOR_EXTERNAL,
|
||||
SELF_HEIGHT,
|
||||
SELF_WIDTH,
|
||||
PREDEFINED
|
||||
}
|
||||
|
||||
|
||||
void Awake()
|
||||
{
|
||||
rectTransform = transform as RectTransform;
|
||||
|
||||
if (button == null)
|
||||
button = GetComponent<Button>();
|
||||
|
||||
if (button)
|
||||
button.onClick.AddListener(OnClicked);
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (nonExpandedSizeSource == NonExpandedSizeSource.SELF_HEIGHT)
|
||||
nonExpandedSize = rectTransform.rect.height;
|
||||
else if (nonExpandedSizeSource == NonExpandedSizeSource.SELF_WIDTH)
|
||||
nonExpandedSize = rectTransform.rect.width;
|
||||
else if (nonExpandedSizeSource == NonExpandedSizeSource.PREDEFINED)
|
||||
nonExpandedSize = nonExpandedSizePredefined;
|
||||
}
|
||||
|
||||
public void OnClicked()
|
||||
{
|
||||
if (animating)
|
||||
return;
|
||||
|
||||
if (nonExpandedSize < 0f)
|
||||
return;
|
||||
|
||||
animating = true;
|
||||
animStart = Time;
|
||||
//animEnd = animStart + animDuration;
|
||||
|
||||
if (expanded) // shrinking
|
||||
{
|
||||
startSize = nonExpandedSize * expandFactor;
|
||||
endSize = nonExpandedSize;
|
||||
}
|
||||
else // expanding
|
||||
{
|
||||
startSize = nonExpandedSize;
|
||||
endSize = nonExpandedSize * expandFactor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (animating)
|
||||
{
|
||||
float elapsedTime = Time - animStart;
|
||||
float t01 = elapsedTime / animDuration;
|
||||
if (t01 >= 1f) // done
|
||||
{
|
||||
t01 = 1f; // fill/clamp animation
|
||||
animating = false;
|
||||
}
|
||||
else
|
||||
t01 = Mathf.Sqrt(t01); // fast-in, slow-out effect
|
||||
|
||||
float size = Mathf.Lerp(startSize, endSize, t01);
|
||||
if (sizeChangesHandler == null)
|
||||
{
|
||||
//// debug
|
||||
//rectTransform.SetInsetAndSizeFromParentEdgeWithCurrentAnchors(RectTransform.Edge.Top, rectTransform.GetInsetFromParentTopEdge(rectTransform.parent as RectTransform), size);
|
||||
if (t01 == 0f) // done
|
||||
expanded = !expanded;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool accepted = sizeChangesHandler.HandleSizeChangeRequest(rectTransform, size);
|
||||
|
||||
// Interruption
|
||||
if (!accepted)
|
||||
animating = false;
|
||||
|
||||
if (!animating) // done; even if it wasn't accepted, wether we should or shouldn't change the "expanded" state depends on the user's requirements. We chose to change it
|
||||
{
|
||||
expanded = !expanded;
|
||||
sizeChangesHandler.OnExpandedStateChanged(rectTransform, expanded);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (onExpandAmountChanged != null)
|
||||
onExpandAmountChanged.Invoke(t01);
|
||||
|
||||
if (onExpandSizeChanged != null)
|
||||
onExpandSizeChanged.Invoke(size);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Interface to implement by the class that'll handle the size changes when the animation runs</summary>
|
||||
public interface ISizeChangesHandler
|
||||
{
|
||||
/// <summary>Called each frame during animation</summary>
|
||||
/// <param name="rt">The animated RectTransform</param>
|
||||
/// <param name="newSize">The requested size</param>
|
||||
/// <returns>If it was accepted</returns>
|
||||
bool HandleSizeChangeRequest(RectTransform rt, float newSize);
|
||||
|
||||
/// <summary>Called when the animation ends and the item successfully expanded (<paramref name="expanded"/> is true) or collapsed (else)</summary>
|
||||
/// <param name="rt">The animated RectTransform</param>
|
||||
/// <param name="expanded">true if the item expanded. false, if collapsed</param>
|
||||
void OnExpandedStateChanged(RectTransform rt, bool expanded);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class UnityFloatEvent : UnityEvent<float> { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e022249f686fcbf4d8a3568253759b35
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 74ebe3a40395cd1489c719b031733c78
|
||||
folderAsset: yes
|
||||
timeCreated: 1532021437
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34b6cee54f21dde4a80098c1be41ee29
|
||||
folderAsset: yes
|
||||
timeCreated: 1552388739
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util.IO.Pools
|
||||
{
|
||||
/// <summary>
|
||||
/// First in, First out caching. Use it when it's very unlikely or impossible to scroll back to older items.
|
||||
/// </summary>
|
||||
/// <remarks>In most cases, this is not actually the best way of doing caching. Consider <see cref="LRUCachingPool"/> instead, as it's more versatile.</remarks>
|
||||
public class FIFOCachingPool : IPool
|
||||
{
|
||||
public delegate void ObjectDestroyer(object key, object value);
|
||||
|
||||
public int Capacity { get; private set; }
|
||||
public int CurrentCount { get { return _Keys.Count; } }
|
||||
|
||||
readonly Queue<object> _Keys;
|
||||
readonly Dictionary<object, object> _Cache;
|
||||
readonly ObjectDestroyer _ObjectDestroyer;
|
||||
|
||||
|
||||
/// <summary>First in, First out caching</summary>
|
||||
/// <param name="capacity"></param>
|
||||
/// <param name="objectDestroyer">
|
||||
/// When an object is kicked out of the cache, this will be used to process its destruction,
|
||||
/// in case special code needs to be executed. This is also called for each value when the cache is cleared usinc <see cref="Clear"/>
|
||||
/// </param>
|
||||
public FIFOCachingPool(int capacity, ObjectDestroyer objectDestroyer = null)
|
||||
{
|
||||
Capacity = capacity;
|
||||
_Keys = new Queue<object>(capacity);
|
||||
_Cache = new Dictionary<object, object>(capacity);
|
||||
_ObjectDestroyer = objectDestroyer;
|
||||
}
|
||||
|
||||
|
||||
public object Get(object key)
|
||||
{
|
||||
object value;
|
||||
if (_Cache.TryGetValue(key, out value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Put(object key, object value)
|
||||
{
|
||||
if (CurrentCount == Capacity)
|
||||
{
|
||||
object keyToDiscard = _Keys.Dequeue();
|
||||
object oldValue = _Cache[keyToDiscard];
|
||||
_Cache.Remove(keyToDiscard);
|
||||
|
||||
if (_ObjectDestroyer != null)
|
||||
_ObjectDestroyer(keyToDiscard, oldValue);
|
||||
}
|
||||
|
||||
_Keys.Enqueue(key);
|
||||
_Cache[key] = value;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (_ObjectDestroyer != null)
|
||||
{
|
||||
foreach (var kv in _Cache)
|
||||
{
|
||||
if (kv.Value != null)
|
||||
_ObjectDestroyer(kv.Key, kv.Value);
|
||||
}
|
||||
}
|
||||
_Cache.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e512af370b74dc43958a0abf0df1946
|
||||
timeCreated: 1552388740
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEngine.UI;
|
||||
using Com.ForbiddenByte.OSA.Util;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util.IO.Pools
|
||||
{
|
||||
public interface IPool
|
||||
{
|
||||
int Capacity { get; }
|
||||
|
||||
object Get(object key);
|
||||
void Put(object key, object value);
|
||||
void Clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00dd717d999bf284e97208b360a592a3
|
||||
timeCreated: 1552388739
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,89 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util.IO.Pools
|
||||
{
|
||||
/// <summary>
|
||||
/// Clears the least accessed items in favor of the most recently accessed ones.
|
||||
/// </summary>
|
||||
/// <remarks>This is more versatile than <see cref="FIFOCachingPool"/>.</remarks>
|
||||
public class LRUCachingPool : IPool
|
||||
{
|
||||
public delegate void ObjectDestroyer(object key, object value);
|
||||
|
||||
public int Capacity { get; private set; }
|
||||
public int CurrentCount { get { return _Cache.Count; } }
|
||||
|
||||
readonly Dictionary<object, LinkedListNode<CacheItem>> _Cache;
|
||||
readonly LinkedList<CacheItem> _LruOrderList;
|
||||
readonly ObjectDestroyer _ObjectDestroyer;
|
||||
|
||||
|
||||
public LRUCachingPool(int capacity, ObjectDestroyer objectDestroyer = null)
|
||||
{
|
||||
Capacity = capacity;
|
||||
_Cache = new Dictionary<object, LinkedListNode<CacheItem>>(capacity);
|
||||
_LruOrderList = new LinkedList<CacheItem>();
|
||||
_ObjectDestroyer = objectDestroyer;
|
||||
}
|
||||
|
||||
|
||||
public object Get(object key)
|
||||
{
|
||||
if (_Cache.TryGetValue(key, out var node))
|
||||
{
|
||||
// Move accessed node to the end to show that it was recently used
|
||||
_LruOrderList.Remove(node); // remove current node
|
||||
_LruOrderList.AddLast(node); // add it back to the end
|
||||
return node.Value.Value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Put(object key, object value)
|
||||
{
|
||||
// If the key already exists, remove it first
|
||||
if (_Cache.TryGetValue(key, out var existingNode))
|
||||
{
|
||||
_LruOrderList.Remove(existingNode);
|
||||
_Cache.Remove(key);
|
||||
}
|
||||
|
||||
if (_Cache.Count == Capacity)
|
||||
{
|
||||
// Remove the least recently used item
|
||||
LinkedListNode<CacheItem> oldestNode = _LruOrderList.First;
|
||||
_LruOrderList.RemoveFirst();
|
||||
_Cache.Remove(oldestNode.Value.Key);
|
||||
|
||||
if (_ObjectDestroyer != null)
|
||||
_ObjectDestroyer(oldestNode.Value.Key, oldestNode.Value.Value);
|
||||
}
|
||||
|
||||
var newNode = new LinkedListNode<CacheItem>(new CacheItem { Key = key, Value = value });
|
||||
_LruOrderList.AddLast(newNode);
|
||||
_Cache[key] = newNode;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (_ObjectDestroyer != null)
|
||||
{
|
||||
foreach (var cached in _LruOrderList)
|
||||
{
|
||||
if (cached.Value != null)
|
||||
_ObjectDestroyer(cached.Key, cached.Value);
|
||||
}
|
||||
}
|
||||
_Cache.Clear();
|
||||
_LruOrderList.Clear();
|
||||
}
|
||||
|
||||
|
||||
class CacheItem
|
||||
{
|
||||
public object Key { get; set; }
|
||||
public object Value { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 63c0c36a14e9f3d40927c554363757d7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,154 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEngine.UI;
|
||||
using Com.ForbiddenByte.OSA.Util;
|
||||
using System;
|
||||
using Com.ForbiddenByte.OSA.Util.IO.Pools;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util.IO
|
||||
{
|
||||
/// <summary>Utility behavior to be attached to a GameObject containing a RawImage for loading remote images using <see cref="SimpleImageDownloader"/>, optionally displaying a specific image during loading and/or on error</summary>
|
||||
[RequireComponent(typeof(RawImage))]
|
||||
public class RemoteImageBehaviour : MonoBehaviour
|
||||
{
|
||||
public delegate void LoadCompleteDelegate(bool fromCache, bool success);
|
||||
|
||||
[Tooltip("If not assigned, will try to find it in this game object")]
|
||||
[SerializeField] RawImage _RawImage = null;
|
||||
#pragma warning disable 0649
|
||||
[SerializeField] Texture2D _LoadingTexture = null;
|
||||
[SerializeField] Texture2D _ErrorTexture = null;
|
||||
#pragma warning restore 0649
|
||||
|
||||
string _CurrentRequestedURL;
|
||||
bool _DestroyPending;
|
||||
Texture2D _Texture;
|
||||
IPool _Pool;
|
||||
|
||||
|
||||
public void InitializeWithPool(IPool pool)
|
||||
{
|
||||
_Pool = pool;
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (!_RawImage)
|
||||
_RawImage = GetComponent<RawImage>();
|
||||
}
|
||||
|
||||
/// <summary>Starts the loading, setting the current image to <see cref="_LoadingTexture"/>, if available. If the image is already in cache, and <paramref name="loadCachedIfAvailable"/>==true, will load that instead</summary>
|
||||
public void Load(string imageURL, bool loadCachedIfAvailable = true, LoadCompleteDelegate onCompleted = null, Action onCanceled = null)
|
||||
{
|
||||
bool currentRequestedURLAlreadyLoaded = _CurrentRequestedURL == imageURL;
|
||||
_CurrentRequestedURL = imageURL;
|
||||
|
||||
if (loadCachedIfAvailable)
|
||||
{
|
||||
bool foundCached = false;
|
||||
// Don't re-request if the url is the same. This is useful if there's no pool provided
|
||||
if (currentRequestedURLAlreadyLoaded)
|
||||
foundCached = _Texture != null;
|
||||
else if (_Pool != null)
|
||||
{
|
||||
Texture2D cachedInPool = _Pool.Get(imageURL) as Texture2D;
|
||||
if (cachedInPool)
|
||||
{
|
||||
_Texture = cachedInPool;
|
||||
foundCached = true;
|
||||
_CurrentRequestedURL = imageURL;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundCached)
|
||||
{
|
||||
_RawImage.texture = _Texture;
|
||||
if (onCompleted != null)
|
||||
onCompleted(true, true);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_RawImage.texture = _LoadingTexture;
|
||||
var request = new SimpleImageDownloader.Request()
|
||||
{
|
||||
url = imageURL,
|
||||
onDone = result =>
|
||||
{
|
||||
if (!_DestroyPending && imageURL == _CurrentRequestedURL) // this will be false if a new request was done during downloading, case in which the result will be ignored
|
||||
{
|
||||
// Commented: not reusing textures to load data into them anymore, since in most cases we'll use a pool
|
||||
//result.LoadTextureInto(_Texture);
|
||||
|
||||
if (_Pool == null)
|
||||
{
|
||||
// Non-pooled textures should be destroyed
|
||||
if (_Texture)
|
||||
DisposeTexture(_Texture);
|
||||
|
||||
_Texture = result.CreateTextureFromReceivedData();
|
||||
}
|
||||
else
|
||||
{
|
||||
var textureAlreadyStoredMeanwhile = _Pool.Get(imageURL);
|
||||
bool someoneStoredTheImageSooner = textureAlreadyStoredMeanwhile != null;
|
||||
if (someoneStoredTheImageSooner)
|
||||
{
|
||||
// Happens when the same URL is requested multiple times for the first time, and of course only the first
|
||||
// downloaded image should be kept. In this case, someone else already have downloaded and cached the image, so we just discard the one we downloaded
|
||||
_Texture = textureAlreadyStoredMeanwhile as Texture2D;
|
||||
}
|
||||
else
|
||||
{
|
||||
// First time downloaded => cache
|
||||
_Texture = result.CreateTextureFromReceivedData();
|
||||
_Pool.Put(imageURL, _Texture);
|
||||
}
|
||||
}
|
||||
|
||||
_RawImage.texture = _Texture;
|
||||
|
||||
if (onCompleted != null)
|
||||
onCompleted(false, true);
|
||||
}
|
||||
else if (onCanceled != null)
|
||||
onCanceled();
|
||||
},
|
||||
onError = () =>
|
||||
{
|
||||
if (!_DestroyPending && imageURL == _CurrentRequestedURL) // this will be false if a new request was done during downloading, case in which the result will be ignored
|
||||
{
|
||||
_RawImage.texture = _ErrorTexture;
|
||||
|
||||
if (onCompleted != null)
|
||||
onCompleted(false, false);
|
||||
}
|
||||
else if (onCanceled != null)
|
||||
onCanceled();
|
||||
}
|
||||
};
|
||||
SimpleImageDownloader.Instance.Enqueue(request);
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
_DestroyPending = true;
|
||||
|
||||
// Non-pooled textures should be destroyed
|
||||
if (_Pool == null && _Texture)
|
||||
{
|
||||
DisposeTexture(_Texture);
|
||||
}
|
||||
}
|
||||
|
||||
void DisposeTexture(Texture2D texture)
|
||||
{
|
||||
try
|
||||
{
|
||||
Destroy(texture);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3acd27727b08d44fb5740f21dedfd14
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,121 @@
|
||||
// WWW class usage
|
||||
#pragma warning disable 0618
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEngine.UI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>A utility singleton class for downloading images using a LIFO queue for the requests. <see cref="MaxConcurrentRequests"/> can be used to limit the number of concurrent requests. </para>
|
||||
/// <para>Default is <see cref="DEFAULT_MAX_CONCURRENT_REQUESTS"/>. Each request is executed immediately if there's room for it. When the queue is full, the downloder starts checking each second if a slot is freed, after which re-enters the loop.</para>
|
||||
/// </summary>
|
||||
public class SimpleImageDownloader : MonoBehaviour
|
||||
{
|
||||
public static SimpleImageDownloader Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_Instance == null)
|
||||
_Instance = new GameObject(typeof(SimpleImageDownloader).Name).AddComponent<SimpleImageDownloader>();
|
||||
|
||||
return _Instance;
|
||||
}
|
||||
}
|
||||
|
||||
static SimpleImageDownloader _Instance;
|
||||
|
||||
|
||||
public int MaxConcurrentRequests { get; set; }
|
||||
|
||||
const int DEFAULT_MAX_CONCURRENT_REQUESTS = 20;
|
||||
|
||||
List<Request> _QueuedRequests = new List<Request>();
|
||||
List<Request> _ExecutingRequests = new List<Request>();
|
||||
WaitForSeconds _Wait1Sec = new WaitForSeconds(1f);
|
||||
|
||||
|
||||
IEnumerator Start()
|
||||
{
|
||||
if (MaxConcurrentRequests == 0)
|
||||
MaxConcurrentRequests = DEFAULT_MAX_CONCURRENT_REQUESTS;
|
||||
|
||||
while (true)
|
||||
{
|
||||
while (_ExecutingRequests.Count >= MaxConcurrentRequests)
|
||||
{
|
||||
yield return _Wait1Sec;
|
||||
}
|
||||
|
||||
int lastIndex = _QueuedRequests.Count - 1;
|
||||
if (lastIndex >= 0)
|
||||
{
|
||||
var lastRequest = _QueuedRequests[lastIndex];
|
||||
_QueuedRequests.RemoveAt(lastIndex);
|
||||
|
||||
StartCoroutine(DownloadCoroutine(lastRequest));
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
_Instance = null;
|
||||
}
|
||||
|
||||
public void Enqueue(Request request)
|
||||
{ _QueuedRequests.Add(request); }
|
||||
|
||||
IEnumerator DownloadCoroutine(Request request)
|
||||
{
|
||||
_ExecutingRequests.Add(request);
|
||||
var www = UnityWebRequestTexture.GetTexture(request.url);
|
||||
|
||||
yield return www.SendWebRequest();
|
||||
|
||||
if (string.IsNullOrEmpty(www.error))
|
||||
{
|
||||
if (request.onDone != null)
|
||||
{
|
||||
var result = new Result(www);
|
||||
request.onDone(result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (request.onError != null)
|
||||
request.onError();
|
||||
}
|
||||
www.Dispose();
|
||||
_ExecutingRequests.Remove(request);
|
||||
}
|
||||
|
||||
public class Request
|
||||
{
|
||||
public string url;
|
||||
public Action<Result> onDone;
|
||||
public Action onError;
|
||||
}
|
||||
|
||||
public class Result
|
||||
{
|
||||
UnityEngine.Networking.UnityWebRequest _UsedRequest;
|
||||
|
||||
|
||||
public Result(UnityWebRequest www)
|
||||
{ _UsedRequest = www; }
|
||||
|
||||
|
||||
public Texture2D CreateTextureFromReceivedData()
|
||||
{ return DownloadHandlerTexture.GetContent(_UsedRequest); }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore 0618
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4864fb691f2f269449b08caea7dc1284
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 12cb88043734d2340820fbc6e121ad74
|
||||
folderAsset: yes
|
||||
timeCreated: 1530790913
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,422 @@
|
||||
//#define DEBUG_EVENTS
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEngine.UI;
|
||||
using System;
|
||||
using frame8.Logic.Misc.Other.Extensions;
|
||||
using UnityEngine.EventSystems;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util.ItemDragging
|
||||
{
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Graphic))]
|
||||
public class DraggableItem : MonoBehaviour, IPointerDownHandler, IInitializePotentialDragHandler, IBeginDragHandler, IDragHandler, IEndDragHandler, IPointerUpHandler//, ICancelHandler
|
||||
{
|
||||
public float longClickTime = .7f;
|
||||
|
||||
public IDragDropListener dragDropListener;
|
||||
public StateEnum State { get { return _State; } }
|
||||
public RectTransform RT { get { return _RT; } }
|
||||
public OrphanedItemBundle OrphanedBundle { get { return _OrphanedBundle; } }
|
||||
public Vector2 CurrentOnDragEventWorldPosition { get { return _CurrentOnDragEventWorldPosition; } }
|
||||
public Vector2 DistancePointerToDraggedInCanvasSpace { get { return _DistancePointerToDraggedInCanvasSpace; } }
|
||||
public Camera CurrentPressEventCamera { get { return _CurrentPressEventCamera; } }
|
||||
|
||||
IInitializePotentialDragHandler _ParentToDelegateDragEventsTo;
|
||||
Vector2 _CurrentOnDragEventWorldPosition;
|
||||
Vector2 _DistancePointerToDraggedInCanvasSpace;
|
||||
Camera _CurrentPressEventCamera;
|
||||
RectTransform _RT;
|
||||
Canvas _Canvas;
|
||||
GraphicRaycaster _GraphicRaycaster;
|
||||
RectTransform _CanvasRT;
|
||||
Vector2 _CurrentPressEventWorldPosition;
|
||||
float _PressedTime;
|
||||
StateEnum _State;
|
||||
OrphanedItemBundle _OrphanedBundle;
|
||||
//int _PointerID;
|
||||
EventSystem _EventSystem;
|
||||
|
||||
EventSystem GetOrFindEventSystem()
|
||||
{
|
||||
if (_EventSystem == null)
|
||||
_EventSystem = FindObjectOfType<EventSystem>();
|
||||
|
||||
return _EventSystem;
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
_RT = transform as RectTransform;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (_State == StateEnum.PRESSING__WAITING_FOR_LONG_CLICK)
|
||||
{
|
||||
if (Time.unscaledTime - _PressedTime >= longClickTime)
|
||||
OnLongClick();
|
||||
}
|
||||
}
|
||||
|
||||
void OnLongClick()
|
||||
{
|
||||
EnterState_AfterLongClickDragAccepted_WaitingToBeginDrag();
|
||||
var evSystem = GetOrFindEventSystem();
|
||||
if (!evSystem)
|
||||
{
|
||||
EnterState_WaitingForPress();
|
||||
return;
|
||||
}
|
||||
|
||||
var canvas = GetComponentInParent<Canvas>();
|
||||
var raycaster = canvas.GetComponentInParent<GraphicRaycaster>();
|
||||
var raycastResults = new List<RaycastResult>();
|
||||
var pev = new PointerEventData(evSystem);
|
||||
pev.position = _CurrentPressEventWorldPosition;
|
||||
raycaster.Raycast(pev, raycastResults);
|
||||
bool foundThis = false;
|
||||
foreach (var res in raycastResults)
|
||||
{
|
||||
if (res.gameObject == gameObject)
|
||||
{
|
||||
foundThis = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Happens if the object is moved externally while the pointer remains still
|
||||
if (!foundThis)
|
||||
{
|
||||
EnterState_AfterLongClickDragDeclined_WaitingToBeginDrag();
|
||||
return;
|
||||
}
|
||||
|
||||
_Canvas = canvas;
|
||||
_CanvasRT = _Canvas.transform as RectTransform;
|
||||
_GraphicRaycaster = raycaster;
|
||||
var pos = RT.position;
|
||||
RT.SetParent(_CanvasRT, false);
|
||||
RT.position = pos; // preserving the pos
|
||||
|
||||
SetVisualMode(VisualMode.OVER_OWNER_OR_OUTSIDE);
|
||||
if (dragDropListener != null && !dragDropListener.OnPrepareToDragItem(this))
|
||||
{
|
||||
EnterState_AfterLongClickDragDeclined_WaitingToBeginDrag();
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelDragSilently()
|
||||
{
|
||||
EnterState_WaitingForPress();
|
||||
}
|
||||
|
||||
void SetVisualMode(VisualMode mode)
|
||||
{
|
||||
int intMode = (int)mode;
|
||||
var euler = RT.localEulerAngles;
|
||||
euler.x = 10f * intMode;
|
||||
euler.z = 4f * intMode;
|
||||
RT.localEulerAngles = euler;
|
||||
}
|
||||
|
||||
void EnterState_WaitingForPress()
|
||||
{
|
||||
_ParentToDelegateDragEventsTo = null;
|
||||
_DistancePointerToDraggedInCanvasSpace = Vector2.zero;
|
||||
_CurrentPressEventCamera = null;
|
||||
_Canvas = null;
|
||||
_CanvasRT = null;
|
||||
_GraphicRaycaster = null;
|
||||
SetVisualMode(VisualMode.NONE);
|
||||
_State = StateEnum.WAITING_FOR_PRESS;
|
||||
}
|
||||
|
||||
void EnterState_AfterLongClickDragAccepted_WaitingToBeginDrag()
|
||||
{
|
||||
_State = StateEnum.AFTER_LONG_CLICK_DRAG_ACCEPTED__WAITING_TO_BEGIN_DRAG;
|
||||
}
|
||||
|
||||
void EnterState_AfterLongClickDragDeclined_WaitingToBeginDrag()
|
||||
{
|
||||
_State = StateEnum.AFTER_LONG_CLICK_DRAG_DECLINED__WAITING_TO_BEGIN_DRAG;
|
||||
}
|
||||
|
||||
void EnterState_BusyDelegatingDragEventToParent()
|
||||
{
|
||||
_State = StateEnum.BUSY_DELEGATING_DRAG_TO_PARENT;
|
||||
}
|
||||
|
||||
#region Callbacks from Unity UI event handlers
|
||||
void IPointerDownHandler.OnPointerDown(PointerEventData eventData)
|
||||
{
|
||||
#if DEBUG_EVENTS
|
||||
Debug.Log("OnPointerDown: " + _State);
|
||||
#endif
|
||||
if (eventData.button != PointerEventData.InputButton.Left)
|
||||
return;
|
||||
|
||||
if (_State != StateEnum.WAITING_FOR_PRESS)
|
||||
return;
|
||||
|
||||
//_PointerID = eventData.pointerId;
|
||||
|
||||
_CurrentPressEventWorldPosition = eventData.position;
|
||||
_State = StateEnum.PRESSING__WAITING_FOR_LONG_CLICK;
|
||||
_PressedTime = Time.unscaledTime;
|
||||
}
|
||||
void IPointerUpHandler.OnPointerUp(PointerEventData eventData)
|
||||
{
|
||||
#if DEBUG_EVENTS
|
||||
Debug.Log("OnPointerUp: " + _State);
|
||||
#endif
|
||||
if (eventData.button != PointerEventData.InputButton.Left)
|
||||
return;
|
||||
|
||||
if (_State == StateEnum.PRESSING__WAITING_FOR_LONG_CLICK)
|
||||
{
|
||||
EnterState_WaitingForPress();
|
||||
}
|
||||
else if (_State == StateEnum.DRAGGING || _State == StateEnum.AFTER_LONG_CLICK_DRAG_ACCEPTED__WAITING_TO_BEGIN_DRAG)
|
||||
{
|
||||
var raycaster = _GraphicRaycaster;
|
||||
EnterState_WaitingForPress();
|
||||
DropAndCheckForOrphaned(eventData, raycaster);
|
||||
}
|
||||
}
|
||||
|
||||
void IInitializePotentialDragHandler.OnInitializePotentialDrag(PointerEventData eventData)
|
||||
{
|
||||
#if DEBUG_EVENTS
|
||||
Debug.Log("OnInitializePotentialDrag: " + _State);
|
||||
#endif
|
||||
_ParentToDelegateDragEventsTo = null;
|
||||
if (!RT.parent)
|
||||
return;
|
||||
_ParentToDelegateDragEventsTo = RT.parent.GetComponentInParent(typeof(IInitializePotentialDragHandler)) as IInitializePotentialDragHandler;
|
||||
if (_ParentToDelegateDragEventsTo != null)
|
||||
_ParentToDelegateDragEventsTo.OnInitializePotentialDrag(eventData);
|
||||
}
|
||||
|
||||
void IBeginDragHandler.OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
#if DEBUG_EVENTS
|
||||
Debug.Log("OnBeginDrag: " + _State);
|
||||
#endif
|
||||
if (eventData.button != PointerEventData.InputButton.Left
|
||||
|| _State != StateEnum.AFTER_LONG_CLICK_DRAG_ACCEPTED__WAITING_TO_BEGIN_DRAG)
|
||||
{
|
||||
|
||||
if (
|
||||
// A child was pressed, which forwarded the event to us
|
||||
_State == StateEnum.WAITING_FOR_PRESS
|
||||
// Long-click canceled
|
||||
|| _State == StateEnum.PRESSING__WAITING_FOR_LONG_CLICK
|
||||
// The OnPrepareToDragItem returned false (the listener declined the drag when the long click happened) or the item could not be dragged due to other reasons
|
||||
|| _State == StateEnum.AFTER_LONG_CLICK_DRAG_DECLINED__WAITING_TO_BEGIN_DRAG
|
||||
)
|
||||
{
|
||||
var casted = _ParentToDelegateDragEventsTo as IBeginDragHandler;
|
||||
if (casted != null)
|
||||
{
|
||||
EnterState_BusyDelegatingDragEventToParent(); // keep sending the current started drag event
|
||||
casted.OnBeginDrag(eventData);
|
||||
}
|
||||
else
|
||||
EnterState_WaitingForPress();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_CurrentPressEventCamera = eventData.pressEventCamera;
|
||||
Vector2 draggedVHPosScreen = frame8.Logic.Misc.Other.UIUtils8.Instance.WorldToScreenPointForCanvas(_Canvas, eventData.pressEventCamera, RT.position);
|
||||
_DistancePointerToDraggedInCanvasSpace = draggedVHPosScreen - eventData.position;
|
||||
|
||||
_State = StateEnum.DRAGGING;
|
||||
if (dragDropListener == null)
|
||||
{
|
||||
if (_OrphanedBundle == null)
|
||||
Debug.Log("OnBeginDrag: dragDropListener is null, but the item is not orphaned (_OrphanedBundle is null)");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dragDropListener.OnBeginDragItem(eventData);
|
||||
}
|
||||
|
||||
void IDragHandler.OnDrag(PointerEventData eventData)
|
||||
{
|
||||
//Debug.Log("OnDrag" + eventData.button);
|
||||
if (eventData.button != PointerEventData.InputButton.Left
|
||||
|| _State != StateEnum.DRAGGING)
|
||||
{
|
||||
if (_State != StateEnum.BUSY_DELEGATING_DRAG_TO_PARENT)
|
||||
return;
|
||||
|
||||
var casted = _ParentToDelegateDragEventsTo as IDragHandler;
|
||||
if (casted != null)
|
||||
casted.OnDrag(eventData);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_CurrentOnDragEventWorldPosition = eventData.position;
|
||||
|
||||
Vector3 worldPoint;
|
||||
RectTransformUtility.ScreenPointToWorldPointInRectangle(
|
||||
_CanvasRT,
|
||||
CurrentOnDragEventWorldPosition + DistancePointerToDraggedInCanvasSpace,
|
||||
eventData.pressEventCamera,
|
||||
out worldPoint
|
||||
);
|
||||
RT.position = worldPoint;
|
||||
|
||||
|
||||
if (dragDropListener == null && _OrphanedBundle == null)
|
||||
{
|
||||
Debug.Log("OnBeginDrag: dragDropListener is null, but the item is not orphaned (_OrphanedBundle is null)");
|
||||
return;
|
||||
}
|
||||
|
||||
var results = RaycastForDragDropListeners(_GraphicRaycaster, eventData);
|
||||
if (results.Count > 0)
|
||||
{
|
||||
// Just a visual feedback that another listener may accept this item
|
||||
if (dragDropListener == null || !results.Contains(dragDropListener))
|
||||
SetVisualMode(VisualMode.OVER_POTENTIAL_NEW_OWNER);
|
||||
else
|
||||
SetVisualMode(VisualMode.OVER_OWNER_OR_OUTSIDE);
|
||||
}
|
||||
else
|
||||
SetVisualMode(VisualMode.OVER_OWNER_OR_OUTSIDE);
|
||||
|
||||
if (dragDropListener != null)
|
||||
dragDropListener.OnDraggedItem(eventData);
|
||||
}
|
||||
|
||||
void IEndDragHandler.OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
#if DEBUG_EVENTS
|
||||
Debug.Log("OnEndDrag: " + _State);
|
||||
#endif
|
||||
if (eventData.button != PointerEventData.InputButton.Left
|
||||
|| _State != StateEnum.DRAGGING)
|
||||
{
|
||||
if (_State != StateEnum.BUSY_DELEGATING_DRAG_TO_PARENT)
|
||||
return;
|
||||
|
||||
var casted = _ParentToDelegateDragEventsTo as IEndDragHandler;
|
||||
EnterState_WaitingForPress(); // prevent setting _ParentToDelegateDragEventsTo to null
|
||||
if (casted != null)
|
||||
casted.OnEndDrag(eventData);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var raycaster = _GraphicRaycaster;
|
||||
EnterState_WaitingForPress();
|
||||
DropAndCheckForOrphaned(eventData, raycaster);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
void DropAndCheckForOrphaned(PointerEventData eventData, GraphicRaycaster raycaster)
|
||||
{
|
||||
if (dragDropListener == null && _OrphanedBundle == null)
|
||||
Destroy(gameObject);
|
||||
|
||||
var wasOrphanedBeforeDrag = dragDropListener == null;
|
||||
if (wasOrphanedBeforeDrag || (_OrphanedBundle = dragDropListener.OnDroppedItem(eventData)) != null)
|
||||
{
|
||||
if (dragDropListener != null)
|
||||
throw new InvalidOperationException("When orphaned, dragDropListener should be set to null");
|
||||
|
||||
// Find a listener among the raycasted ones, other that the current listener (since this is the listener that has orphaned the item anyway)
|
||||
var results = RaycastForDragDropListeners(raycaster, eventData);
|
||||
bool accepted = false;
|
||||
foreach (var listener in results)
|
||||
{
|
||||
if (!wasOrphanedBeforeDrag && _OrphanedBundle.previousOwner != null && listener == _OrphanedBundle.previousOwner)
|
||||
continue;
|
||||
accepted = listener.OnDroppedExternalItem(eventData, this);
|
||||
if (!accepted)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (accepted)
|
||||
{
|
||||
if (dragDropListener == null)
|
||||
throw new InvalidOperationException("When adopting an orphaned item, dragDropListener should be set to the new owner");
|
||||
|
||||
_OrphanedBundle = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just wait for another press event
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<IDragDropListener> RaycastForDragDropListeners(GraphicRaycaster raycaster, PointerEventData eventData)
|
||||
{
|
||||
List<IDragDropListener> listeners = new List<IDragDropListener>();
|
||||
List<RaycastResult> results = new List<RaycastResult>();
|
||||
raycaster.Raycast(eventData, results);
|
||||
// Find a listener among the raycasted ones
|
||||
IDragDropListener listener;
|
||||
foreach (var res in results)
|
||||
{
|
||||
listener = res.gameObject.GetComponent(typeof(IDragDropListener)) as IDragDropListener;
|
||||
if (listener == null)
|
||||
continue;
|
||||
listeners.Add(listener);
|
||||
}
|
||||
|
||||
return listeners;
|
||||
}
|
||||
|
||||
|
||||
public enum StateEnum
|
||||
{
|
||||
WAITING_FOR_PRESS,
|
||||
BUSY_DELEGATING_DRAG_TO_PARENT,
|
||||
PRESSING__WAITING_FOR_LONG_CLICK,
|
||||
AFTER_LONG_CLICK_DRAG_DECLINED__WAITING_TO_BEGIN_DRAG,
|
||||
AFTER_LONG_CLICK_DRAG_ACCEPTED__WAITING_TO_BEGIN_DRAG,
|
||||
DRAGGING,
|
||||
}
|
||||
|
||||
enum VisualMode
|
||||
{
|
||||
NONE = 0,
|
||||
OVER_OWNER_OR_OUTSIDE = 1,
|
||||
OVER_POTENTIAL_NEW_OWNER = -1
|
||||
}
|
||||
|
||||
|
||||
public class OrphanedItemBundle
|
||||
{
|
||||
public IDragDropListener previousOwner;
|
||||
public object views;
|
||||
public object model;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Interface to implement by the class that'll handle the drag events</summary>
|
||||
public interface IDragDropListener
|
||||
{
|
||||
/// <summary> Returns if the item drag was accepted </summary>
|
||||
bool OnPrepareToDragItem(DraggableItem item);
|
||||
void OnBeginDragItem(PointerEventData eventData);
|
||||
void OnDraggedItem(PointerEventData eventData);
|
||||
/// <summary> Returns null if the object was accepted. Otherwise, an <see cref="OrphanedItemBundle"/> </summary>
|
||||
OrphanedItemBundle OnDroppedItem(PointerEventData eventData);
|
||||
/// <summary> Returns if the item was accepted </summary>
|
||||
bool OnDroppedExternalItem(PointerEventData eventData, DraggableItem orphanedItemWithBundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e05b68d5a78fb4546949b44114cf847a
|
||||
timeCreated: 1530790917
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,82 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEngine.UI;
|
||||
using System;
|
||||
using frame8.Logic.Misc.Other.Extensions;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility to delegate the "long click" event to <see cref="longClickListener"/>
|
||||
/// It requires a graphic component (can be an image with zero alpha) that can be clicked in order to receive OnPointerDown, OnPointerUp etc.
|
||||
/// No other UI elements should be on top of this one in order to receive pointer callbacks
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Graphic))]
|
||||
public class LongClickableItem : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, ICancelHandler
|
||||
{
|
||||
public float longClickTime = .7f;
|
||||
|
||||
public IItemLongClickListener longClickListener;
|
||||
public StateEnum State { get { return _State; } }
|
||||
|
||||
float _PressedTime;
|
||||
StateEnum _State;
|
||||
//int _PointerID;
|
||||
|
||||
|
||||
public enum StateEnum
|
||||
{
|
||||
NOT_PRESSING,
|
||||
PRESSING_WAITING_FOR_LONG_CLICK,
|
||||
PRESSING_AFTER_LONG_CLICK
|
||||
}
|
||||
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (_State == StateEnum.PRESSING_WAITING_FOR_LONG_CLICK)
|
||||
{
|
||||
if (Time.unscaledTime - _PressedTime >= longClickTime)
|
||||
{
|
||||
_State = StateEnum.PRESSING_AFTER_LONG_CLICK;
|
||||
if (longClickListener != null)
|
||||
longClickListener.OnItemLongClicked(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Callbacks from Unity UI event handlers
|
||||
public void OnPointerDown(PointerEventData eventData)
|
||||
{
|
||||
//Debug.Log("OnPointerDown" + eventData.button);
|
||||
if (eventData.button != PointerEventData.InputButton.Left)
|
||||
return;
|
||||
|
||||
//_PointerID = eventData.pointerId;
|
||||
|
||||
_State = StateEnum.PRESSING_WAITING_FOR_LONG_CLICK;
|
||||
_PressedTime = Time.unscaledTime;
|
||||
}
|
||||
public void OnPointerUp(PointerEventData eventData)
|
||||
{
|
||||
//Debug.Log("OnPointerUp" + eventData.button);
|
||||
if (eventData.button != PointerEventData.InputButton.Left)
|
||||
return;
|
||||
|
||||
_State = StateEnum.NOT_PRESSING;
|
||||
}
|
||||
public void OnCancel(BaseEventData eventData)
|
||||
{
|
||||
//Debug.Log("OnCancel");
|
||||
_State = StateEnum.NOT_PRESSING;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>Interface to implement by the class that'll handle the long click events</summary>
|
||||
public interface IItemLongClickListener
|
||||
{
|
||||
void OnItemLongClicked(LongClickableItem longClickedItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 591f287cf6e51b8479d1544830586dd5
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4bc415364553c314a9f2baa241dad1dc
|
||||
folderAsset: yes
|
||||
timeCreated: 1563293174
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,60 @@
|
||||
// Pre-2019.1 Unity versions have performance problems with the Shadow script,
|
||||
// while 2019.{1,2,3,4} sometimes have problems with displaying an Image+Shadow
|
||||
#if UNITY_2019_1_OR_NEWER && !(UNITY_2019_1_0 || UNITY_2019_1_1 || UNITY_2019_1_2 || UNITY_2019_1_3 || UNITY_2019_1_4)
|
||||
#define ALLOW_SHADOWS
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util.Optimization
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple: If no Unity 2019, no shadow (the shadow will be destroyed).
|
||||
/// Seems like a lot of Shadow components used cause significant FPS drops
|
||||
/// </summary>
|
||||
[ExecuteInEditMode]
|
||||
public class ShadowRemover : MonoBehaviour
|
||||
{
|
||||
// If not on a compatible unity version, destroy the Shadow script and this script
|
||||
#if ALLOW_SHADOWS
|
||||
|
||||
#else
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
Shadow _Shadow;
|
||||
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (!_Shadow)
|
||||
_Shadow = GetComponent<Shadow>();
|
||||
|
||||
if (!Application.isPlaying)
|
||||
return;
|
||||
|
||||
// In play mode, destroy it
|
||||
if (_Shadow)
|
||||
{
|
||||
Destroy(_Shadow);
|
||||
_Shadow = null;
|
||||
}
|
||||
Destroy(this);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void Update()
|
||||
{
|
||||
// No checks during play mode
|
||||
if (Application.isPlaying)
|
||||
return;
|
||||
|
||||
if (!_Shadow)
|
||||
_Shadow = GetComponent<Shadow>();
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a97ab825bf3ba44d81da6a6ce5c66fa
|
||||
timeCreated: 1563293088
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,349 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util
|
||||
{
|
||||
[AddComponentMenu("Layout/Packed Grid Layout Group", 153)]
|
||||
/// <summary>
|
||||
/// Layout class to arrange children elements in a circular grid format. Circular in the . Items will try to occupy as much space as possible.
|
||||
/// </summary>
|
||||
public class PackedGridLayoutGroup : LayoutGroup
|
||||
{
|
||||
[SerializeField] protected float m_ForcedSpacing = 0f;
|
||||
|
||||
[Tooltip("The specified axis will have the 'Preferred' size set based on children")]
|
||||
[SerializeField] protected AxisOrNone m_childrenControlSize = AxisOrNone.Vertical;
|
||||
|
||||
[Tooltip("If true, the layout will start with bigger children. The starting position is defined by the 'Child Alignment' property")]
|
||||
[SerializeField] protected bool m_biggerChildrenFirst = true;
|
||||
|
||||
[Tooltip("Set to as many as possible, if the FPS allows")]
|
||||
[Range(1, (int)Packer2DBox.NodeChoosingStrategy.COUNT_)]
|
||||
[SerializeField] protected int m_numPasses = (int)Packer2DBox.NodeChoosingStrategy.COUNT_;
|
||||
|
||||
/// <summary>
|
||||
/// The spacing to use between layout elements in the grid on both axes.
|
||||
/// The spacing is created by shrinking the childrens' sizes rather than actually adding spaces.
|
||||
/// If you want true spacing, consider modifying the children themselves to also include some padding inside them
|
||||
/// </summary>
|
||||
public float ForcedSpacing { get { return m_ForcedSpacing; } set { SetProperty(ref m_ForcedSpacing, value); } }
|
||||
|
||||
/// <summary>
|
||||
/// The specified axis will have the 'Preferred' size set based on children
|
||||
/// </summary>
|
||||
public AxisOrNone ChildrenControlSize { get { return m_childrenControlSize; } set { SetProperty(ref m_childrenControlSize, value); } }
|
||||
|
||||
/// <summary>
|
||||
/// If true, the layout will start with bigger children. The starting position is defined by the 'Child Alignment' property
|
||||
/// </summary>
|
||||
public bool BiggerChildrenFirst { get { return m_biggerChildrenFirst; } set { SetProperty(ref m_biggerChildrenFirst, value); } }
|
||||
|
||||
/// <summary>
|
||||
/// <para>This refers to the number of different strategies to use when packing children. Set to as many as possible, if the FPS allows.
|
||||
/// See <see cref="Packer2DBox.NodeChoosingStrategy"/>.</para>
|
||||
/// <para> At the moment (15 Mar 2019), more than 1 pass is executing only if the boxes don't all fit in the available
|
||||
/// space, as the first strategy (<see cref="Packer2DBox.NodeChoosingStrategy.MAX_VOLUME"/>) seems to always perform the best</para>
|
||||
/// </summary>
|
||||
public int NumPasses { get { return m_numPasses; } set { SetProperty(ref m_numPasses, value); } }
|
||||
|
||||
|
||||
protected PackedGridLayoutGroup()
|
||||
{ }
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
}
|
||||
#endif
|
||||
public override void CalculateLayoutInputHorizontal()
|
||||
{
|
||||
base.CalculateLayoutInputHorizontal();
|
||||
|
||||
float minWidthToSet;
|
||||
float preferredWidthToSet;
|
||||
if (m_childrenControlSize == AxisOrNone.Horizontal)
|
||||
{
|
||||
float width, _;
|
||||
GetChildSetups(out width, out _);
|
||||
|
||||
minWidthToSet = preferredWidthToSet = width + padding.horizontal;
|
||||
}
|
||||
else
|
||||
{
|
||||
minWidthToSet = minWidth;
|
||||
preferredWidthToSet = preferredWidth;
|
||||
}
|
||||
|
||||
SetLayoutInputForAxis(minWidthToSet, preferredWidthToSet, -1, 0);
|
||||
}
|
||||
|
||||
public override void CalculateLayoutInputVertical()
|
||||
{
|
||||
float minHeightToSet;
|
||||
float preferredHeightToSet;
|
||||
if (m_childrenControlSize == AxisOrNone.Vertical)
|
||||
{
|
||||
float _, height;
|
||||
GetChildSetups(out _, out height);
|
||||
|
||||
minHeightToSet = preferredHeightToSet = height + padding.vertical;
|
||||
}
|
||||
else
|
||||
{
|
||||
minHeightToSet = minHeight;
|
||||
preferredHeightToSet = preferredHeight;
|
||||
}
|
||||
|
||||
SetLayoutInputForAxis(minHeightToSet, preferredHeightToSet, -1, 1);
|
||||
}
|
||||
|
||||
public override void SetLayoutHorizontal()
|
||||
{
|
||||
LayoutChildren(true, false);
|
||||
}
|
||||
|
||||
public override void SetLayoutVertical()
|
||||
{
|
||||
LayoutChildren(false, true);
|
||||
}
|
||||
|
||||
void GetInsetAndEdges(float chWidth, float chHeight, out RectTransform.Edge xInsetEdge, out RectTransform.Edge yInsetEdge, out float xAddInset, out float yAddInset)
|
||||
{
|
||||
var gridRect = rectTransform.rect;
|
||||
|
||||
xInsetEdge = RectTransform.Edge.Left;
|
||||
yInsetEdge = RectTransform.Edge.Top;
|
||||
xAddInset = 0f;
|
||||
yAddInset = 0f;
|
||||
if (m_childrenControlSize != AxisOrNone.Horizontal)
|
||||
{
|
||||
switch (childAlignment)
|
||||
{
|
||||
case TextAnchor.UpperLeft:
|
||||
case TextAnchor.LowerLeft:
|
||||
case TextAnchor.MiddleLeft:
|
||||
xInsetEdge = RectTransform.Edge.Left;
|
||||
xAddInset = padding.left;
|
||||
break;
|
||||
|
||||
case TextAnchor.UpperRight:
|
||||
case TextAnchor.LowerRight:
|
||||
case TextAnchor.MiddleRight:
|
||||
xInsetEdge = RectTransform.Edge.Right;
|
||||
xAddInset = padding.right;
|
||||
break;
|
||||
|
||||
case TextAnchor.UpperCenter:
|
||||
case TextAnchor.MiddleCenter:
|
||||
case TextAnchor.LowerCenter:
|
||||
xAddInset = Mathf.Max((gridRect.width - chWidth), padding.horizontal) / 2f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_childrenControlSize != AxisOrNone.Vertical)
|
||||
{
|
||||
switch (childAlignment)
|
||||
{
|
||||
case TextAnchor.UpperLeft:
|
||||
case TextAnchor.UpperCenter:
|
||||
case TextAnchor.UpperRight:
|
||||
yInsetEdge = RectTransform.Edge.Top;
|
||||
yAddInset = padding.top;
|
||||
break;
|
||||
|
||||
case TextAnchor.LowerLeft:
|
||||
case TextAnchor.LowerCenter:
|
||||
case TextAnchor.LowerRight:
|
||||
yInsetEdge = RectTransform.Edge.Bottom;
|
||||
yAddInset = padding.bottom;
|
||||
break;
|
||||
|
||||
case TextAnchor.MiddleLeft:
|
||||
case TextAnchor.MiddleCenter:
|
||||
case TextAnchor.MiddleRight:
|
||||
yAddInset = Mathf.Max((gridRect.height - chHeight), padding.vertical) / 2f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LayoutChildren(bool hor, bool vert)
|
||||
{
|
||||
float chWidth, chHeight;
|
||||
var setups = GetChildSetups(out chWidth, out chHeight);
|
||||
|
||||
RectTransform.Edge xInsetEdge, yInsetEdge;
|
||||
float xAddInset, yAddInset;
|
||||
GetInsetAndEdges(chWidth, chHeight, out xInsetEdge, out yInsetEdge, out xAddInset, out yAddInset);
|
||||
|
||||
foreach (var child in setups)
|
||||
{
|
||||
var c = child as ChildSetup;
|
||||
if (c.box.position == null)
|
||||
continue;
|
||||
|
||||
if (hor)
|
||||
c.child.SetInsetAndSizeFromParentEdge(xInsetEdge, (float)c.box.position.x + xAddInset, (float)c.box.width - ForcedSpacing);
|
||||
|
||||
if (vert)
|
||||
c.child.SetInsetAndSizeFromParentEdge(yInsetEdge, (float)c.box.position.y + yAddInset, (float)c.box.height - ForcedSpacing);
|
||||
}
|
||||
}
|
||||
|
||||
List<ChildSetup> GetChildSetups(out float width, out float height)
|
||||
{
|
||||
var list = new List<ChildSetup>(rectChildren.Count);
|
||||
foreach (var child in rectChildren)
|
||||
{
|
||||
float chWidth = LayoutUtility.GetPreferredSize(child, 0);
|
||||
float chHeight = LayoutUtility.GetPreferredSize(child, 1);
|
||||
list.Add(new ChildSetup(chWidth, chHeight, child));
|
||||
}
|
||||
|
||||
if (BiggerChildrenFirst)
|
||||
{
|
||||
// Biggest boxes first with maxside, then secondarily by volume
|
||||
// More info: https://codeincomplete.com/posts/bin-packing/
|
||||
list.Sort((a, b) =>
|
||||
{
|
||||
var aMax = System.Math.Max(a.box.width, a.box.height);
|
||||
var bMax = System.Math.Max(b.box.width, b.box.height);
|
||||
|
||||
if (aMax != bMax)
|
||||
return (int)(bMax - aMax);
|
||||
|
||||
return (int)(b.box.volume - a.box.volume);
|
||||
});
|
||||
}
|
||||
|
||||
float availableWidth, availableHeight;
|
||||
if (m_childrenControlSize == AxisOrNone.Horizontal)
|
||||
{
|
||||
availableWidth = float.MaxValue;
|
||||
availableHeight = rectTransform.rect.height - padding.vertical;
|
||||
}
|
||||
else if (m_childrenControlSize == AxisOrNone.Vertical)
|
||||
{
|
||||
availableWidth = rectTransform.rect.width - padding.horizontal;
|
||||
availableHeight = float.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
availableHeight = rectTransform.rect.height - padding.vertical;
|
||||
availableWidth = rectTransform.rect.width - padding.horizontal;
|
||||
}
|
||||
|
||||
|
||||
// Spacing usually creates mode big empty spaces where no item can fit
|
||||
float spacingToUse = 0f;
|
||||
var packer = new Packer2DBox(availableWidth, availableHeight, spacingToUse);
|
||||
|
||||
int maxStrategiesToUse = m_numPasses;
|
||||
List<Packer2DBox.Box>[] boxesPerStrategy = new List<Packer2DBox.Box>[maxStrategiesToUse];
|
||||
int[] nullPositionsPerStrategy = new int[maxStrategiesToUse];
|
||||
double[] totalWidthsPerStrategy = new double[maxStrategiesToUse];
|
||||
double[] totalHeightsPerStrategy = new double[maxStrategiesToUse];
|
||||
|
||||
int iBest = -1;
|
||||
bool copyBoxesForFinalResult = maxStrategiesToUse > 1;
|
||||
for (int i = 0; i < maxStrategiesToUse; i++)
|
||||
{
|
||||
var boxesThisPass = i == 0 ? list.ConvertAll(c => c.box) : list.ConvertAll(c => { c.ReinitBox(); return c.box; });
|
||||
double totalWidthThisPass;
|
||||
double totalHeightThisPass;
|
||||
packer.Pack(boxesThisPass, false, (Packer2DBox.NodeChoosingStrategy)i, out totalWidthThisPass, out totalHeightThisPass);
|
||||
int thisPassNullPositions = 0;
|
||||
for (int j = 0; j < boxesThisPass.Count; j++)
|
||||
{
|
||||
var b = boxesThisPass[j];
|
||||
if (b.position == null)
|
||||
++thisPassNullPositions;
|
||||
}
|
||||
|
||||
boxesPerStrategy[i] = boxesThisPass;
|
||||
nullPositionsPerStrategy[i] = thisPassNullPositions;
|
||||
totalWidthsPerStrategy[i] = totalWidthThisPass;
|
||||
totalHeightsPerStrategy[i] = totalHeightThisPass;
|
||||
|
||||
if (iBest == -1)
|
||||
{
|
||||
iBest = i;
|
||||
|
||||
if (thisPassNullPositions == 0)
|
||||
{
|
||||
// Boxes won't be overridden by next strategies, so no need to copy them
|
||||
copyBoxesForFinalResult = false;
|
||||
|
||||
// First pass is Packer2DBox.NodeChoosingStrategy.MAX_VOLUME and all boxes were fit => there's no better strategy
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (thisPassNullPositions < nullPositionsPerStrategy[iBest])
|
||||
{
|
||||
iBest = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (thisPassNullPositions > nullPositionsPerStrategy[iBest])
|
||||
continue;
|
||||
|
||||
if (totalWidthThisPass * totalHeightThisPass < totalWidthsPerStrategy[i] * totalHeightsPerStrategy[i])
|
||||
{
|
||||
iBest = i;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (copyBoxesForFinalResult)
|
||||
{
|
||||
var bestBoxes = boxesPerStrategy[iBest];
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
list[i].box = bestBoxes[i];
|
||||
}
|
||||
|
||||
////Testing whether first strategy is always better when no nulls are found
|
||||
//if (nullPositionsPerStrategy[0] == 0 && iBest != 0)
|
||||
// throw new System.Exception(nullPositionsPerStrategy[0] + ", " + (Packer2DBox.NodeChoosingStrategy)iBest + ", " + nullPositionsPerStrategy[iBest]);
|
||||
|
||||
//Debug.Log("Strategy used: " + (Packer2DBox.NodeChoosingStrategy)iBest);
|
||||
|
||||
width = (float)totalWidthsPerStrategy[iBest];
|
||||
height = (float)totalHeightsPerStrategy[iBest];
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
public enum AxisOrNone
|
||||
{
|
||||
Horizontal = RectTransform.Axis.Horizontal,
|
||||
Vertical = RectTransform.Axis.Vertical,
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
class ChildSetup
|
||||
{
|
||||
public RectTransform child;
|
||||
public Packer2DBox.Box box;
|
||||
|
||||
|
||||
public ChildSetup(double width, double height, RectTransform child)
|
||||
{
|
||||
this.child = child;
|
||||
|
||||
box = new Packer2DBox.Box(width, height);
|
||||
}
|
||||
|
||||
|
||||
public void ReinitBox() { box = new Packer2DBox.Box(box.width, box.height); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9bdf01a11e37bd46b06dce794365dba
|
||||
timeCreated: 1552575321
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,293 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util
|
||||
{
|
||||
/*
|
||||
License type: MIT
|
||||
Quoted from github: "A short and simple permissive license with conditions
|
||||
only requiring preservation of copyright and license notices. Licensed works, modifications,
|
||||
and larger works may be distributed under different terms and without source code"
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 Jake Gordon and contributors
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Heavily modified version of https://github.com/cariquitanmac/2D-Bin-Pack-Binary-Search,
|
||||
/// which is a C# implementation of Jakes Gordon Binary Tree Algorithm for 2D Bin Packing https://github.com/jakesgordon/bin-packing/
|
||||
/// <para>All rights go to the original author.</para>
|
||||
/// </summary>
|
||||
public class Packer2DBox
|
||||
{
|
||||
List<Box> _Boxes;
|
||||
Node _RootNode;
|
||||
double _Spacing;
|
||||
bool _AlternatingStrategyBiggerRightNode;
|
||||
bool _AlternatingOtherStrategyBiggerRightNode;
|
||||
double _ContainerWidth;
|
||||
double _ContainerHeight;
|
||||
NodeChoosingStrategy _ChoosingStrategy;
|
||||
|
||||
|
||||
public Packer2DBox(double containerWidth, double containerHeight, double spacing)
|
||||
{
|
||||
this._ContainerWidth = containerWidth;
|
||||
this._ContainerHeight = containerHeight;
|
||||
this._Spacing = spacing;
|
||||
}
|
||||
|
||||
|
||||
public void Pack(List<Box> boxes, bool sort, NodeChoosingStrategy choosingStrategy, out double totalWidth, out double totalHeight)
|
||||
{
|
||||
_ChoosingStrategy = choosingStrategy;
|
||||
|
||||
_AlternatingStrategyBiggerRightNode = _ChoosingStrategy == NodeChoosingStrategy.ALTERNATING_START_WITH_RIGHT;
|
||||
|
||||
_RootNode = new Node(0d, 0d) { height = _ContainerHeight, width = _ContainerWidth };
|
||||
_Boxes = boxes;
|
||||
if (sort)
|
||||
{
|
||||
// Biggest boxes first with maxside, then secondarily by volume
|
||||
// More info: https://codeincomplete.com/posts/bin-packing/
|
||||
_Boxes.Sort((a, b) =>
|
||||
{
|
||||
var aMax = System.Math.Max(a.width, a.height);
|
||||
var bMax = System.Math.Max(b.width, b.height);
|
||||
|
||||
if (aMax != bMax)
|
||||
return (int)(bMax - aMax);
|
||||
|
||||
return (int)(b.volume - a.volume);
|
||||
});
|
||||
//_Boxes = _Boxes.Sort((a, b) =>
|
||||
//{
|
||||
// var aMax = Math.Max(a.width, a.height);
|
||||
// var bMax = Math.Max(b.width, b.height);
|
||||
|
||||
// if (aMax != bMax)
|
||||
// return (int)(bMax - aMax);
|
||||
|
||||
// return (int)(b.volume - a.volume);
|
||||
//});
|
||||
//_Boxes = _Boxes.OrderByDescending(x => Math.Max(x.width, x.height)).ToList();
|
||||
////_Boxes = _Boxes.OrderByDescending(x => x.volume).ToList();
|
||||
}
|
||||
|
||||
totalWidth = 0f;
|
||||
totalHeight = 0f;
|
||||
foreach (var box in _Boxes)
|
||||
{
|
||||
Node node = null;
|
||||
FindNode(_RootNode, box.width, box.height, ref node);
|
||||
|
||||
if (node != null)
|
||||
{
|
||||
// Split rectangles
|
||||
box.position = SplitNode(node, box.width, box.height);
|
||||
|
||||
double width = box.position.x + box.width;
|
||||
if (width > totalWidth)
|
||||
totalWidth = width;
|
||||
|
||||
double height = box.position.y + box.height;
|
||||
if (height > totalHeight)
|
||||
totalHeight = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FindNode(Node rootNode, double boxWidth, double boxHeight, ref Node node)
|
||||
{
|
||||
if (rootNode.isOccupied)
|
||||
{
|
||||
FindNode(rootNode.rightNode, boxWidth, boxHeight, ref node);
|
||||
FindNode(rootNode.bottomNode, boxWidth, boxHeight, ref node);
|
||||
}
|
||||
else if (boxWidth <= rootNode.width && boxHeight <= rootNode.height)
|
||||
{
|
||||
if (node == null || rootNode.distFromOrigin < node.distFromOrigin)
|
||||
node = rootNode;
|
||||
}
|
||||
}
|
||||
|
||||
Node SplitNode(Node node, double boxWidth, double boxHeight)
|
||||
{
|
||||
node.isOccupied = true;
|
||||
|
||||
double rightNodeFullWidth = node.width - (boxWidth + _Spacing);
|
||||
double rightNodeFullHeight = node.height;
|
||||
|
||||
double bottomNodeFullWidth = node.width;
|
||||
double bottomNodeFullHeight = node.height - (boxHeight + _Spacing);
|
||||
|
||||
bool biggerRightNode;
|
||||
|
||||
var localStrategy = _ChoosingStrategy;
|
||||
if (localStrategy == NodeChoosingStrategy.MAX_VOLUME)
|
||||
{
|
||||
double rightVolume = rightNodeFullWidth * rightNodeFullHeight;
|
||||
double bottomVolume = bottomNodeFullWidth * bottomNodeFullHeight;
|
||||
|
||||
if (rightVolume == bottomVolume)
|
||||
{
|
||||
// In case of equality, alternate between what we chose the last time
|
||||
biggerRightNode = _AlternatingOtherStrategyBiggerRightNode;
|
||||
_AlternatingOtherStrategyBiggerRightNode = !_AlternatingOtherStrategyBiggerRightNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
biggerRightNode = rightVolume > bottomVolume;
|
||||
}
|
||||
}
|
||||
else if (localStrategy == NodeChoosingStrategy.MAX_SIDE)
|
||||
{
|
||||
double rightMaxSide = Math.Max(rightNodeFullWidth, rightNodeFullHeight);
|
||||
double bottomMaxSide = Math.Max(bottomNodeFullWidth, bottomNodeFullHeight);
|
||||
|
||||
if (rightMaxSide == bottomMaxSide)
|
||||
{
|
||||
// In case of equality, alternate between what we chose the last time
|
||||
biggerRightNode = _AlternatingOtherStrategyBiggerRightNode;
|
||||
_AlternatingOtherStrategyBiggerRightNode = !_AlternatingOtherStrategyBiggerRightNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
biggerRightNode = rightMaxSide > bottomMaxSide;
|
||||
}
|
||||
}
|
||||
else if (localStrategy == NodeChoosingStrategy.MAX_SIDE)
|
||||
{
|
||||
double rightMaxSide = Math.Max(rightNodeFullWidth, rightNodeFullHeight);
|
||||
double bottomMaxSide = Math.Max(bottomNodeFullWidth, bottomNodeFullHeight);
|
||||
|
||||
if (rightMaxSide == bottomMaxSide)
|
||||
{
|
||||
// In case of equality, alternate between what we chose the last time
|
||||
biggerRightNode = _AlternatingOtherStrategyBiggerRightNode;
|
||||
_AlternatingOtherStrategyBiggerRightNode = !_AlternatingOtherStrategyBiggerRightNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
biggerRightNode = rightMaxSide > bottomMaxSide;
|
||||
}
|
||||
}
|
||||
else if (localStrategy == NodeChoosingStrategy.MAX_SUM)
|
||||
{
|
||||
double rightSum = rightNodeFullWidth + rightNodeFullHeight;
|
||||
double bottomSum = bottomNodeFullWidth + bottomNodeFullHeight;
|
||||
|
||||
if (rightSum == bottomSum)
|
||||
{
|
||||
// In case of equality, alternate between what we chose the last time
|
||||
biggerRightNode = _AlternatingOtherStrategyBiggerRightNode;
|
||||
_AlternatingOtherStrategyBiggerRightNode = !_AlternatingOtherStrategyBiggerRightNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
biggerRightNode = rightSum > bottomSum;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_ChoosingStrategy == NodeChoosingStrategy.RIGHT)
|
||||
biggerRightNode = true;
|
||||
else if (_ChoosingStrategy == NodeChoosingStrategy.BOTTOM)
|
||||
biggerRightNode = false;
|
||||
else
|
||||
{
|
||||
// Alternating
|
||||
biggerRightNode = _AlternatingStrategyBiggerRightNode;
|
||||
_AlternatingStrategyBiggerRightNode = !_AlternatingStrategyBiggerRightNode;
|
||||
}
|
||||
}
|
||||
|
||||
node.rightNode = new Node(node.x + (boxWidth + _Spacing), node.y)
|
||||
{
|
||||
depth = node.depth + 1,
|
||||
width = rightNodeFullWidth,
|
||||
height = biggerRightNode ? node.height : boxHeight
|
||||
};
|
||||
node.bottomNode = new Node(node.x, node.y + (boxHeight + _Spacing))
|
||||
{
|
||||
depth = node.depth + 1,
|
||||
width = biggerRightNode ? boxWidth : node.width,
|
||||
height = bottomNodeFullHeight
|
||||
};
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
public class Node
|
||||
{
|
||||
public int depth;
|
||||
public Node rightNode;
|
||||
public Node bottomNode;
|
||||
public double x;
|
||||
public double y;
|
||||
public double width;
|
||||
public double height;
|
||||
readonly public double distFromOrigin;
|
||||
public bool isOccupied;
|
||||
|
||||
|
||||
public Node(double x, double y)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
distFromOrigin = Math.Sqrt(x * x + y * y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class Box
|
||||
{
|
||||
public double height;
|
||||
public double width;
|
||||
public double volume;
|
||||
public Node position;
|
||||
|
||||
|
||||
public Box(double width, double height)
|
||||
{
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
volume = width * height;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Note expanding choices, in order of success rate</summary>
|
||||
public enum NodeChoosingStrategy
|
||||
{
|
||||
MAX_VOLUME,
|
||||
MAX_SUM,
|
||||
MAX_SIDE,
|
||||
ALTERNATING_START_WITH_BOTTOM,
|
||||
RIGHT,
|
||||
BOTTOM,
|
||||
ALTERNATING_START_WITH_RIGHT, // same as BOTTOM, in 99% of cases
|
||||
COUNT_
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 363daeb6cdf9ec14185bcc2f8f2f6fc6
|
||||
timeCreated: 1552581283
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1fb02192dc3a1c2409414afb729b192b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
+385
@@ -0,0 +1,385 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using System;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.Events;
|
||||
using frame8.Logic.Misc.Visual.UI;
|
||||
using frame8.Logic.Misc.Other.Extensions;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util.PullToRefresh
|
||||
{
|
||||
/// <summary>
|
||||
/// Attach it to your ScrollView where the pull to refresh functionality is needed.
|
||||
/// Browse the PullToRefreshExample scene to see how the gizmo should be set up. An image is better than 1k words.
|
||||
/// </summary>
|
||||
public class PullToRefreshBehaviour : MonoBehaviour, IScrollRectProxy, IBeginDragHandler, IDragHandler, IEndDragHandler
|
||||
{
|
||||
#region Serialized fields
|
||||
/// <summary>The normalized distance relative to screen size. Always between 0 and 1</summary>
|
||||
[SerializeField] [Range(.1f, 1f)] [Tooltip("The normalized distance relative to screen size. Always between 0 and 1")]
|
||||
float _PullAmountNormalized = .25f;
|
||||
|
||||
/// <summary>The reference of the gizmo to use. If null, will try to GetComponentInChildren<PullToRefreshGizmo>()</summary>
|
||||
[SerializeField] [Tooltip("If null, will try to GetComponentInChildren()")]
|
||||
PullToRefreshGizmo _RefreshGizmo = null;
|
||||
|
||||
//[SerializeField]
|
||||
//RectTransform.Axis _Axis;
|
||||
|
||||
/// <summary></summary>
|
||||
[SerializeField]
|
||||
bool _AllowPullFromEnd = false;
|
||||
|
||||
/// <summary>If false, you'll need to call HideGizmo() manually after pull. Subscribe to PullToRefreshBehaviour.OnRefresh event to know when a refresh event occurred. This method is used when the gizmo should do an animation while the refresh is executing (for ex., when some data is downloading)</summary>
|
||||
[SerializeField] [Tooltip("If false, you'll need to call HideGizmo() manually after pull. Subscribe to PullToRefreshBehaviour.OnRefresh event to know when a refresh event occurred")]
|
||||
bool _AutoHideRefreshGizmo = true;
|
||||
#pragma warning disable 0649
|
||||
/// <summary>Quick way of playing a sound effect when the pull power reaches 1f</summary>
|
||||
[SerializeField]
|
||||
AudioClip _SoundOnPreRefresh = null;
|
||||
|
||||
/// <summary>Quick way of playing a sound effect when the refresh occurred</summary>
|
||||
[SerializeField]
|
||||
AudioClip _SoundOnRefresh = null;
|
||||
#endregion
|
||||
#pragma warning restore 0649
|
||||
|
||||
#region Unity events
|
||||
[Tooltip("Unity event fired when the pull was released")]
|
||||
/// <summary>Unity event (editable in inspector) fired when the refresh occurred</summary>
|
||||
public UnityEvent OnRefresh = null;
|
||||
|
||||
[Tooltip("Same as OnRefresh, but also gives you the refresh sign.\n" +
|
||||
"1 = top, -1 = bottom")]
|
||||
/// <summary>Same as <see cref="OnRefresh"/>, but also gives you the refresh sign. 1 = top, -1 = bottom</summary>
|
||||
public UnityEventFloat OnRefreshWithSign = null;
|
||||
|
||||
[Tooltip("Unity event (editable in inspector) fired when each frame the click/finger is dragged after it has touched the ScrollView.\n" +
|
||||
"Negative values indicate pulling from end")]
|
||||
/// <summary>
|
||||
/// Unity event (editable in inspector) fired when each frame the click/finger is dragged after it has touched the ScrollView.
|
||||
/// Negative values indicate pulling from end
|
||||
/// </summary>
|
||||
public UnityEventFloat OnPullProgress = null;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Will be retrieved from the scrollrect. If not found, it can be assigned anytime before the first Update.
|
||||
/// If not assigned, a default proxy will be used. The purpose of this is to allow custom implementations of ScrollRect to be used
|
||||
/// </summary>
|
||||
public IScrollRectProxy externalScrollRectProxy;
|
||||
|
||||
#region IScrollRectProxy properties implementation
|
||||
public bool IsInitialized { get { return _ScrollRect != null; } }
|
||||
public Vector2 Velocity { get; set; }
|
||||
public bool IsHorizontal { get { return _ScrollRect.horizontal; } }
|
||||
public bool IsVertical { get { return _ScrollRect.vertical; } }
|
||||
public RectTransform Content { get { return _ScrollRect.content; } }
|
||||
public RectTransform Viewport { get { return _ScrollRect.viewport; } }
|
||||
double IScrollRectProxy.ContentInsetFromViewportStart { get { return Content.GetInsetFromParentEdge(Viewport, ScrollRectProxy.GetStartEdge()); } }
|
||||
double IScrollRectProxy.ContentInsetFromViewportEnd { get { return Content.GetInsetFromParentEdge(Viewport, ScrollRectProxy.GetEndEdge()); } }
|
||||
#endregion
|
||||
|
||||
IScrollRectProxy ScrollRectProxy { get { return externalScrollRectProxy == null ? this : externalScrollRectProxy; } }
|
||||
|
||||
ScrollRect _ScrollRect;
|
||||
float _ResolvedAVGScreenSize;
|
||||
bool _PlayedPreSoundForCurrentDrag;
|
||||
//bool _IgnoreCurrentDrag;
|
||||
RectTransform _RT;
|
||||
int _CurrentDragSign;
|
||||
StateEnum _State;
|
||||
|
||||
|
||||
/// <summary>Not used in this default interface implementation</summary>
|
||||
#pragma warning disable 0067
|
||||
public event Action<double> ScrollPositionChanged = delegate { };
|
||||
#pragma warning restore 0067
|
||||
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_RT = transform as RectTransform;
|
||||
_ResolvedAVGScreenSize = (Screen.width + Screen.height) / 2f;
|
||||
_ScrollRect = GetComponent<ScrollRect>();
|
||||
_RefreshGizmo = GetComponentInChildren<PullToRefreshGizmo>(); // self or children
|
||||
if (_ScrollRect)
|
||||
{
|
||||
// May be null
|
||||
externalScrollRectProxy = _ScrollRect.GetComponent(typeof(IScrollRectProxy)) as IScrollRectProxy;
|
||||
}
|
||||
else
|
||||
{
|
||||
externalScrollRectProxy = GetComponentInParent(typeof(IScrollRectProxy)) as IScrollRectProxy;
|
||||
if (externalScrollRectProxy == null)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
Debug.Log(GetType().Name + ": no scrollRect provided and found no " + typeof(IScrollRectProxy).Name + " component among ancestors. Disabling...");
|
||||
enabled = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region IScrollRectProxy methods implementation (used if external proxy is not manually assigned)
|
||||
public void SetNormalizedPosition(double normalizedPosition) { }
|
||||
|
||||
public double GetNormalizedPosition()
|
||||
{
|
||||
if (_ScrollRect.horizontal)
|
||||
return _ScrollRect.horizontalNormalizedPosition;
|
||||
return _ScrollRect.verticalNormalizedPosition;
|
||||
}
|
||||
|
||||
public double GetContentSize() { return _RT.rect.size[_ScrollRect.horizontal ? 0 : 1]; }
|
||||
public double GetViewportSize() { return Viewport.rect.size[_ScrollRect.horizontal ? 0 : 1]; }
|
||||
|
||||
public void StopMovement() { _ScrollRect.StopMovement(); }
|
||||
#endregion
|
||||
|
||||
#region UI callbacks
|
||||
public void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
if (eventData.button != PointerEventData.InputButton.Left)
|
||||
return;
|
||||
|
||||
if (!isActiveAndEnabled)
|
||||
return;
|
||||
|
||||
if (_State != StateEnum.NONE)
|
||||
return;
|
||||
|
||||
if (_RefreshGizmo.IsShown)
|
||||
return;
|
||||
|
||||
if (!ScrollRectProxy.IsInitialized)
|
||||
return;
|
||||
|
||||
double dragAmountNorm, _;
|
||||
GetDragAmountNormalized(eventData, out dragAmountNorm, out _);
|
||||
if (!_AllowPullFromEnd && dragAmountNorm < 0d)
|
||||
return;
|
||||
|
||||
int curDragSign = Math.Sign(dragAmountNorm);
|
||||
_CurrentDragSign = curDragSign;
|
||||
_PlayedPreSoundForCurrentDrag = false;
|
||||
|
||||
_State = StateEnum.DRAGGING_WAITING_FOR_PULL;
|
||||
}
|
||||
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
if (eventData.button != PointerEventData.InputButton.Left)
|
||||
return;
|
||||
|
||||
if (!isActiveAndEnabled)
|
||||
return;
|
||||
|
||||
if (!ScrollRectProxy.IsInitialized)
|
||||
return;
|
||||
|
||||
|
||||
switch (_State)
|
||||
{
|
||||
case StateEnum.DRAGGING_WAITING_FOR_PULL:
|
||||
if (!IsContentBiggerThanViewport() || IsScrollRectAtTarget(_CurrentDragSign))
|
||||
{
|
||||
_State = StateEnum.PULLING_WAITING_FOR_RELEASE;
|
||||
goto case StateEnum.PULLING_WAITING_FOR_RELEASE;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
case StateEnum.PULLING_WAITING_FOR_RELEASE:
|
||||
if (IsContentBiggerThanViewport() && !IsScrollRectAtTarget(_CurrentDragSign))
|
||||
{
|
||||
HideGizmoInternal();
|
||||
_State = StateEnum.DRAGGING_WAITING_FOR_PULL;
|
||||
return;
|
||||
}
|
||||
|
||||
double dragAmountNorm, deltaNorm;
|
||||
GetDragAmountNormalized(eventData, out dragAmountNorm, out deltaNorm);
|
||||
if (Math.Sign(dragAmountNorm) != _CurrentDragSign)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//if (!_AllowPullFromEnd && dragAmountNorm < 0d)
|
||||
//{
|
||||
// HideGizmoInternal();
|
||||
// return;
|
||||
//}
|
||||
|
||||
double pullPower = dragAmountNorm;
|
||||
|
||||
ShowGizmoIfNeeded();
|
||||
if (_RefreshGizmo)
|
||||
_RefreshGizmo.OnPull(pullPower);
|
||||
|
||||
if (OnPullProgress != null)
|
||||
OnPullProgress.Invoke((float)pullPower);
|
||||
|
||||
if (Math.Abs(pullPower) >= 1d && !_PlayedPreSoundForCurrentDrag)
|
||||
{
|
||||
_PlayedPreSoundForCurrentDrag = true;
|
||||
|
||||
if (_SoundOnPreRefresh)
|
||||
AudioSource.PlayClipAtPoint(_SoundOnPreRefresh, Camera.main.transform.position);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//Debug.Log("eventData.pressPosition=" + eventData.pressPosition + "\n eventData.position=" + eventData.position + "\neventData.scrollDelta="+ eventData.scrollDelta);
|
||||
}
|
||||
|
||||
public void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
if (eventData.button != PointerEventData.InputButton.Left)
|
||||
return;
|
||||
|
||||
if (!ScrollRectProxy.IsInitialized)
|
||||
return;
|
||||
|
||||
if (_State != StateEnum.PULLING_WAITING_FOR_RELEASE)
|
||||
{
|
||||
if (_State == StateEnum.DRAGGING_WAITING_FOR_PULL)
|
||||
{
|
||||
HideGizmoInternal();
|
||||
_State = StateEnum.NONE;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool proceedRefresh = false;
|
||||
if (isActiveAndEnabled)
|
||||
{
|
||||
proceedRefresh = true;
|
||||
if (IsContentBiggerThanViewport())
|
||||
proceedRefresh = IsScrollRectAtTarget(_CurrentDragSign);
|
||||
|
||||
if (proceedRefresh)
|
||||
{
|
||||
double dragAmount, _;
|
||||
GetDragAmountNormalized(eventData, out dragAmount, out _);
|
||||
if (Math.Sign(dragAmount) != _CurrentDragSign)
|
||||
proceedRefresh = false;
|
||||
else if (Math.Abs(dragAmount) < 1d)
|
||||
proceedRefresh = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (proceedRefresh)
|
||||
{
|
||||
if (OnRefresh != null)
|
||||
OnRefresh.Invoke();
|
||||
|
||||
if (OnRefreshWithSign != null)
|
||||
OnRefreshWithSign.Invoke(_CurrentDragSign);
|
||||
|
||||
if (_RefreshGizmo)
|
||||
_RefreshGizmo.OnRefreshed(_AutoHideRefreshGizmo);
|
||||
|
||||
if (_SoundOnRefresh)
|
||||
AudioSource.PlayClipAtPoint(_SoundOnRefresh, Camera.main.transform.position);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_RefreshGizmo)
|
||||
_RefreshGizmo.OnRefreshCancelled();
|
||||
_State = StateEnum.NONE;
|
||||
}
|
||||
|
||||
if (_RefreshGizmo && _RefreshGizmo.IsShown)
|
||||
{
|
||||
if (_AutoHideRefreshGizmo)
|
||||
{
|
||||
HideGizmoInternal();
|
||||
_State = StateEnum.NONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
_State = StateEnum.AFTER_RELEASE_WAITING_FOR_GIZMO_TO_HIDE;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
// sign: 1=start(top or left), -1=end(bottom or right);
|
||||
bool IsScrollRectAtTarget(int targetDragSign)
|
||||
{
|
||||
double normPos = ScrollRectProxy.GetNormalizedPosition();
|
||||
if (ScrollRectProxy.IsHorizontal)
|
||||
normPos = 1d - normPos;
|
||||
|
||||
if (targetDragSign == 1 && normPos >= 1d)
|
||||
return true;
|
||||
if (targetDragSign == -1 && normPos <= 0d)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsContentBiggerThanViewport() { return ScrollRectProxy.GetContentSize() > _RT.rect.size[ScrollRectProxy.IsHorizontal ? 0 : 1]; }
|
||||
|
||||
public void ShowGizmoIfNeeded()
|
||||
{
|
||||
if (_RefreshGizmo && !_RefreshGizmo.IsShown)
|
||||
_RefreshGizmo.IsShown = true;
|
||||
}
|
||||
|
||||
public void HideGizmo()
|
||||
{
|
||||
HideGizmoInternal();
|
||||
if (_State == StateEnum.AFTER_RELEASE_WAITING_FOR_GIZMO_TO_HIDE)
|
||||
_State = StateEnum.NONE;
|
||||
}
|
||||
|
||||
void HideGizmoInternal()
|
||||
{
|
||||
if (_RefreshGizmo)
|
||||
_RefreshGizmo.IsShown = false;
|
||||
}
|
||||
|
||||
void GetDragAmountNormalized(PointerEventData eventData, out double total, out double delta)
|
||||
{
|
||||
total = 0d;
|
||||
delta = 0d;
|
||||
float pos;
|
||||
float maxPullAmount = _PullAmountNormalized * _ResolvedAVGScreenSize;
|
||||
if (ScrollRectProxy.IsVertical)
|
||||
{
|
||||
pos = eventData.position.y;
|
||||
float worldDragVec = pos - eventData.pressPosition.y;
|
||||
total = -worldDragVec;
|
||||
delta = -eventData.delta.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
pos = eventData.position.x;
|
||||
float worldDragVec = pos - eventData.pressPosition.x;
|
||||
total = worldDragVec;
|
||||
delta = eventData.delta.x;
|
||||
}
|
||||
total /= maxPullAmount;
|
||||
delta /= maxPullAmount;
|
||||
}
|
||||
|
||||
|
||||
enum StateEnum
|
||||
{
|
||||
NONE,
|
||||
DRAGGING_WAITING_FOR_PULL,
|
||||
PULLING_WAITING_FOR_RELEASE,
|
||||
AFTER_RELEASE_WAITING_FOR_GIZMO_TO_HIDE
|
||||
}
|
||||
|
||||
|
||||
[Serializable]
|
||||
public class UnityEventFloat : UnityEvent<float> { }
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a7fcb71e0128534fb984a6449319591
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,50 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util.PullToRefresh
|
||||
{
|
||||
/// <summary> Base class for gizmos that can be used with <see cref="PullToRefreshBehaviour"/> (see it for more details). Attach it to your ScrollView </summary>
|
||||
public class PullToRefreshGizmo : MonoBehaviour
|
||||
{
|
||||
/// <summary>Property that can be overriden by the inheritors. The default implementation is to set whether the game object is active or not</summary>
|
||||
public virtual bool IsShown
|
||||
{
|
||||
get { return _IsShown; }
|
||||
set
|
||||
{
|
||||
_IsShown = value;
|
||||
|
||||
gameObject.SetActive(_IsShown);
|
||||
}
|
||||
}
|
||||
|
||||
bool _IsShown;
|
||||
|
||||
|
||||
public virtual void Awake()
|
||||
{}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <para>Called for each OnDrag event on the ScrollView. In other words, it's called continuously during moving the mouse/finger after the click</para>
|
||||
/// </summary>
|
||||
/// <param name="power">0d = didn't drag at all; .5d = dragged half-way from start; 1d = dragged from start exactly at the minimum needed point in
|
||||
/// order for a refresh event to occur; values will exceed 1f after this minimum drag amount is exceeded
|
||||
/// (which can be used to visualize the fact that after the click/finger is released, the refresh will occur).
|
||||
/// Negative values indicate a pull from the end instead of start, and the same rules apply
|
||||
/// </param>
|
||||
public virtual void OnPull(double power)
|
||||
{}
|
||||
|
||||
/// <summary> Called when the refresh did occur (dragged with at least 1f power and released)</summary>
|
||||
/// <param name="autoHide">A hint for the gizmo to know whether it should hide itself or something will hide it externally by setting <see cref="IsShown"/>=false </param>
|
||||
public virtual void OnRefreshed(bool autoHide)
|
||||
{
|
||||
if (autoHide)
|
||||
IsShown = false;
|
||||
}
|
||||
|
||||
/// <summary> Called when the click/finger was released before the pullPower reached 1f</summary>
|
||||
public virtual void OnRefreshCancelled()
|
||||
{ IsShown = false; }
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3a396e6841ce6343adc9ea23c888ab8
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util.PullToRefresh
|
||||
{
|
||||
/// <summary>
|
||||
/// <para> Implementation of <see cref="PullToRefreshGizmo"/> that uses a rotating image to show the pull progress. </para>
|
||||
/// <para>The image is rotated by the amount of distance traveled by the click/finger.</para>
|
||||
/// <para>When enough pulling distance is covered the gizmo enters the "ready to refresh" state,</para>
|
||||
/// <para>the rotation amount applied is damped by <see cref="_ExcessPullRotationDamping"/> (i.e. a value of 1f won't apply any furter rotation, </para>
|
||||
/// <para>while a value of 0f will apply the same amount of rotation per distance traveled by the click/finger as before the "ready to refresh" state).</para>
|
||||
/// <para>When <see cref="OnRefreshed(bool)"/> is called with true, the gizmo will disappear; if it'll be called with false, </para>
|
||||
/// <para>it'll start auto-rotating with a speed of <see cref="_AutoRotationDegreesPerSec"/> degrees per second, until <see cref="IsShown"/> is set to false.</para>
|
||||
/// <para>This last use-case is very common for when the refresh event actually takes time (i.e. retrieving items from a server).</para>
|
||||
/// </summary>
|
||||
public class PullToRefreshRotateGizmo : PullToRefreshGizmo
|
||||
{
|
||||
#pragma warning disable 0649
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("_StartingPoint")]
|
||||
RectTransform _PullFromStartInitial = null;
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("_EndingPoint")]
|
||||
RectTransform _PullFromStartTarget = null;
|
||||
|
||||
[SerializeField]
|
||||
RectTransform _PullFromEndInitial = null;
|
||||
[SerializeField]
|
||||
RectTransform _PullFromEndTarget = null;
|
||||
#pragma warning restore 0649
|
||||
|
||||
//[Tooltip("When pulling is done from the end, this gizmo will also appear at the end, not at the start. " +
|
||||
// "\nThe gizmo's position in this case is inferred using the parent's size and _StartingPoint & _EndingPoint ")]
|
||||
//[SerializeField]
|
||||
//bool _AllowAppearingFromEnd = true;
|
||||
|
||||
[SerializeField]
|
||||
[Range(0f, 1f)]
|
||||
float _ExcessPullRotationDamping = .95f;
|
||||
|
||||
[SerializeField]
|
||||
float _AutoRotationDegreesPerSec = 200;
|
||||
|
||||
[Tooltip("Will also interpolate its own scale between the Initial's and Target's scale")]
|
||||
[SerializeField]
|
||||
bool _ScaleWithTarget = true;
|
||||
|
||||
[Tooltip("If true, it won't be affected by Time.timeScale")]
|
||||
[SerializeField]
|
||||
bool _UseUnscaledTime = true;
|
||||
|
||||
bool _WaitingForManualHide;
|
||||
|
||||
|
||||
/// <summary>Calls base implementation + resets the rotation to default each time is assigned, regardless if true or false</summary>
|
||||
public override bool IsShown
|
||||
{
|
||||
get { return base.IsShown; }
|
||||
|
||||
set
|
||||
{
|
||||
base.IsShown = value;
|
||||
|
||||
// Reset to default rotation
|
||||
transform.localRotation = Quaternion.Euler(_InitialLocalRotation);
|
||||
|
||||
if (!value)
|
||||
_WaitingForManualHide = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Vector3 _InitialLocalRotation, _InitialLocalScale;
|
||||
Transform _TR;
|
||||
|
||||
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
_TR = transform;
|
||||
|
||||
_InitialLocalRotation = _TR.localRotation.eulerAngles;
|
||||
_InitialLocalScale = _TR.localScale;
|
||||
}
|
||||
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (_WaitingForManualHide)
|
||||
{
|
||||
SetLocalZRotation((_TR.localEulerAngles.z - (_UseUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime) * _AutoRotationDegreesPerSec) % 360);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnPull(double power)
|
||||
{
|
||||
base.OnPull(power);
|
||||
|
||||
double powerAbs = Math.Abs(power);
|
||||
int powerSign = Math.Sign(power);
|
||||
float powerAbsClamped01 = Mathf.Clamp01((float)powerAbs);
|
||||
float excess = Mathf.Max(0f, (float)powerAbs - 1f);
|
||||
|
||||
float dampedExcess = excess * (1f - _ExcessPullRotationDamping);
|
||||
|
||||
SetLocalZRotation((_InitialLocalRotation.z - 360 * (powerAbsClamped01 + dampedExcess)) % 360);
|
||||
|
||||
//_TR.position = LerpUnclamped(_StartingPoint.position, _EndingPoint.position, power <= 1f ? (power - (1f - power/2)*(1f-power/2)) : (1 - 1/(1 + excess) ));
|
||||
Vector3 start, end;
|
||||
Vector3 scaleStart, scaleEnd;
|
||||
if (powerSign < 0 && _PullFromEndInitial && _PullFromEndTarget)
|
||||
{
|
||||
start = _PullFromEndInitial.position;
|
||||
end = _PullFromEndTarget.position;
|
||||
scaleStart = _PullFromEndInitial.localScale;
|
||||
scaleEnd = _PullFromEndTarget.localScale;
|
||||
}
|
||||
else
|
||||
{
|
||||
start = _PullFromStartInitial.position;
|
||||
end = _PullFromStartTarget.position;
|
||||
scaleStart = _PullFromStartInitial.localScale;
|
||||
scaleEnd = _PullFromStartTarget.localScale;
|
||||
}
|
||||
|
||||
var t01Unclamped = 2 - 2 / (1 + powerAbsClamped01);
|
||||
_TR.position = LerpUnclamped(start, end, t01Unclamped);
|
||||
if (_ScaleWithTarget)
|
||||
_TR.localScale = LerpUnclamped(scaleStart, scaleEnd, t01Unclamped);
|
||||
else
|
||||
_TR.localScale = _InitialLocalScale;
|
||||
}
|
||||
|
||||
public override void OnRefreshCancelled()
|
||||
{
|
||||
base.OnRefreshCancelled();
|
||||
|
||||
_WaitingForManualHide = false;
|
||||
}
|
||||
|
||||
public override void OnRefreshed(bool autoHide)
|
||||
{
|
||||
base.OnRefreshed(autoHide);
|
||||
|
||||
_WaitingForManualHide = !autoHide;
|
||||
}
|
||||
|
||||
Vector3 LerpUnclamped(Vector3 from, Vector3 to, float t) { return (1f - t) * from + t * to ; }
|
||||
|
||||
void SetLocalZRotation(float zRotation)
|
||||
{
|
||||
var rotE = _TR.localRotation.eulerAngles;
|
||||
rotE.z = zRotation;
|
||||
_TR.localRotation = Quaternion.Euler(rotE);
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26e83b34c01bd224294affa1aee5a14d
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,270 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using frame8.Logic.Misc.Other.Extensions;
|
||||
using UnityEngine.EventSystems;
|
||||
using frame8.Logic.Misc.Other;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util
|
||||
{
|
||||
public class RectTransformEdgeDragger : MonoBehaviour, IDragHandler, IPointerDownHandler, IPointerUpHandler
|
||||
{
|
||||
public event Action TargetDragged;
|
||||
|
||||
[FormerlySerializedAs("draggedRectTransform")]
|
||||
[SerializeField]
|
||||
RectTransform _DraggedRectTransform = null;
|
||||
[FormerlySerializedAs("draggedEdge")]
|
||||
[SerializeField]
|
||||
RectTransform.Edge _DraggedEdge = RectTransform.Edge.Left;
|
||||
[SerializeField]
|
||||
RectTransform _StartPoint = null;
|
||||
[SerializeField]
|
||||
RectTransform _EndPoint = null;
|
||||
[SerializeField]
|
||||
[Tooltip("Set to false if the dragger will be automatically dragged by the exact same amount, as a result of being a direct child of the dragged recttransform")]
|
||||
bool _DragSelf = true;
|
||||
[SerializeField]
|
||||
float _DraggedRectTransformMinSize = 1f;
|
||||
[SerializeField]
|
||||
float _DraggedRectTransformMaxSize = 0f;
|
||||
|
||||
public RectTransform DraggedRectTransform { get { return _DraggedRectTransform; } }
|
||||
public float DragNormalizedAmount { get { return GetNormPosOnDraggingSegment(GetVEndpointStartToMe()); } }
|
||||
|
||||
float DragAreaSize { get { return Vector3.Distance(_StartPoint.localPosition, _EndPoint.localPosition); } }
|
||||
|
||||
RectTransform _RT;
|
||||
RectTransform _MyParent;
|
||||
Vector2 _StartDragPosInMySpace;
|
||||
//Vector2 _MyInitialLocalPos;
|
||||
float _MyInitialInset;
|
||||
float _DraggedRTInitialInset;
|
||||
//float _DraggedRTStartInset;
|
||||
//float _DraggedRTStartSize;
|
||||
float _DraggedRTInitialSize;
|
||||
Canvas _Canvas;
|
||||
bool _Dragging;
|
||||
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_RT = (transform as RectTransform);
|
||||
_MyParent = _RT.parent as RectTransform;
|
||||
|
||||
if (_StartPoint.parent != _MyParent || _EndPoint.parent != _MyParent)
|
||||
throw new UnityException("_StartPoint and _EndPoint should have the same parent as the dragger");
|
||||
|
||||
// Get the root canvas
|
||||
var c = _Canvas = transform.parent.GetComponentInParent<Canvas>();
|
||||
while (c && c.transform.parent)
|
||||
{
|
||||
_Canvas = c;
|
||||
c = c.transform.parent.GetComponentInParent<Canvas>();
|
||||
}
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
//_MyInitialLocalPos = _RT.localPosition;
|
||||
Reinitialize();
|
||||
|
||||
//SetNormalizedPosition(0);
|
||||
}
|
||||
|
||||
public void Reinitialize()
|
||||
{
|
||||
_MyInitialInset = GetMyCurrentInsetFromDraggedEdge();
|
||||
_DraggedRTInitialInset = GetDraggedRTCurrentInsetFromDraggedEdge();
|
||||
_DraggedRTInitialSize = GetRTSize(_DraggedRectTransform);
|
||||
}
|
||||
|
||||
void IPointerDownHandler.OnPointerDown(PointerEventData ped)
|
||||
{
|
||||
var localPos = UIUtils8.Instance.WorldToCanvasLocalPosition(_Canvas, _RT.parent as RectTransform, Camera.main, _RT.position);
|
||||
_Dragging = localPos != null;
|
||||
if (!_Dragging)
|
||||
return;
|
||||
|
||||
_StartDragPosInMySpace = localPos.Value;
|
||||
|
||||
if (!_DragSelf)
|
||||
{
|
||||
Reinitialize();
|
||||
}
|
||||
//_DraggedRTStartInset = GetDraggedRTCurrentInsetFromDraggedEdge();
|
||||
//_DraggedRTStartSize = GetRTSize(_DraggedRectTransform);
|
||||
}
|
||||
|
||||
void IDragHandler.OnDrag(PointerEventData ped)
|
||||
{
|
||||
if (!_Dragging)
|
||||
return;
|
||||
|
||||
var cam = ped.pressEventCamera;
|
||||
Vector2 posInMySpace;
|
||||
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(_MyParent, ped.position, cam, out posInMySpace))
|
||||
return;
|
||||
|
||||
Vector2 pressPosInMySpace;
|
||||
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(_MyParent, ped.pressPosition, cam, out pressPosInMySpace))
|
||||
return;
|
||||
|
||||
var dragVectorInMySpace = posInMySpace - pressPosInMySpace;
|
||||
|
||||
var parentOfDragged = _DraggedRectTransform.parent as RectTransform;
|
||||
Vector2 posInDraggedRTSpace;
|
||||
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(parentOfDragged, ped.position, cam, out posInDraggedRTSpace))
|
||||
return;
|
||||
|
||||
Vector2 pressPosInDraggedRTSpace;
|
||||
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(parentOfDragged, ped.pressPosition, cam, out pressPosInDraggedRTSpace))
|
||||
return;
|
||||
|
||||
//var dragVectorInDraggedRTSpace = posInDraggedRTSpace - pressPosInDraggedRTSpace;
|
||||
|
||||
var rtNewPos = _StartDragPosInMySpace;
|
||||
var rtNewPosUnclamped = _StartDragPosInMySpace;
|
||||
rtNewPosUnclamped += dragVectorInMySpace;
|
||||
|
||||
//float amount;
|
||||
//float rectMoveAmount;
|
||||
float _DraggedRTInsetDelta;
|
||||
if (_DraggedEdge == RectTransform.Edge.Left || _DraggedEdge == RectTransform.Edge.Right)
|
||||
{
|
||||
rtNewPos.x += dragVectorInMySpace.x;
|
||||
_DraggedRTInsetDelta = dragVectorInMySpace.x * (_DraggedEdge == RectTransform.Edge.Left ? 1f : -1f);
|
||||
}
|
||||
else
|
||||
{
|
||||
rtNewPos.y += dragVectorInMySpace.y;
|
||||
_DraggedRTInsetDelta = dragVectorInMySpace.y * (_DraggedEdge == RectTransform.Edge.Bottom ? 1f : -1f);
|
||||
}
|
||||
float normPos = GetNormPosOnDraggingSegment(GetVEndPointStartTo(rtNewPosUnclamped));
|
||||
if (_DragSelf)
|
||||
{
|
||||
SetNormalizedPosition(normPos, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug.Log(normPos);
|
||||
//// TODO see why normPos reports at boundary when it actually isn't
|
||||
//if (normPos == 0f)
|
||||
//{
|
||||
// if (_DraggedRTInsetDelta > 0)
|
||||
// return;
|
||||
//}
|
||||
//else if (normPos == 1f)
|
||||
//{
|
||||
// if (_DraggedRTInsetDelta < 0)
|
||||
// return;
|
||||
//}
|
||||
|
||||
float newInset = _DraggedRTInitialInset + _DraggedRTInsetDelta;
|
||||
float newSize = _DraggedRTInitialSize - _DraggedRTInsetDelta;
|
||||
if (newSize < _DraggedRectTransformMinSize)
|
||||
{
|
||||
float excess = _DraggedRectTransformMinSize - newSize;
|
||||
newInset -= excess;
|
||||
newSize += excess;
|
||||
}
|
||||
|
||||
if (_DraggedRectTransformMaxSize != 0f && newSize > _DraggedRectTransformMaxSize)
|
||||
{
|
||||
float excess = newSize - _DraggedRectTransformMaxSize;
|
||||
newInset += excess;
|
||||
newSize -= excess;
|
||||
}
|
||||
|
||||
SetDraggedRTInsetAndSize(newInset, newSize);
|
||||
}
|
||||
|
||||
if (TargetDragged != null)
|
||||
TargetDragged();
|
||||
}
|
||||
|
||||
void IPointerUpHandler.OnPointerUp(PointerEventData eventData)
|
||||
{
|
||||
if (!_Dragging)
|
||||
return;
|
||||
// TODO test if this is still needed
|
||||
//Reinitialize();
|
||||
}
|
||||
|
||||
Vector2 GetVEndpointStartToMe() { return GetVEndPointStartTo(_RT.localPosition); }
|
||||
|
||||
Vector2 GetVEndPointStartTo(Vector2 localPoint)
|
||||
{
|
||||
Vector2 endV2 = _EndPoint.localPosition;
|
||||
return localPoint - endV2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Segment start is the end point (it was at the bottom on the moment of the implementation and it was easier to visualize this way)
|
||||
/// </summary>
|
||||
float GetNormPosOnDraggingSegment(Vector2 vSegmentStartToPoint)
|
||||
{
|
||||
Vector2 endV2 = _EndPoint.localPosition;
|
||||
Vector2 startV2 = _StartPoint.localPosition;
|
||||
|
||||
// O = end point, A = my pos, B = start point
|
||||
var oa = vSegmentStartToPoint;
|
||||
var ob = startV2 - endV2;
|
||||
return GetNormPosOnSegment(ob, oa);
|
||||
}
|
||||
|
||||
float GetNormPosOnSegment(Vector2 segmentVector, Vector2 vSegmentStartToPoint)
|
||||
{
|
||||
var oa = vSegmentStartToPoint;
|
||||
var ob = segmentVector;
|
||||
var obNorm = ob / ob.magnitude;
|
||||
var oaInOBSpace = oa / ob.magnitude; // i.e. considering ob as unit vector
|
||||
|
||||
//float dot = Vector2.Dot(oaNorm, obNorm);
|
||||
float dot = Vector2.Dot(obNorm, oaInOBSpace);
|
||||
|
||||
float normPos = 1f - Mathf.Clamp01(dot);
|
||||
|
||||
return normPos;
|
||||
}
|
||||
|
||||
public void SetNormalizedPosition(float normalizedPos, bool updateDraggedRT)
|
||||
{
|
||||
//var prevLocalPos = transform.localPosition;
|
||||
//Debug.Log("SetNormalizedPosition " + normalizedPos);
|
||||
transform.position = Vector3.Lerp(_StartPoint.position, _EndPoint.position, normalizedPos);
|
||||
if (updateDraggedRT)
|
||||
UpdateDraggedRTFromDraggerPos();
|
||||
|
||||
// Commented: doesn't work very well in the current form
|
||||
//if (!_DragSelf)
|
||||
// transform.localPosition = prevLocalPos;
|
||||
}
|
||||
|
||||
void UpdateDraggedRTFromDraggerPos()
|
||||
{
|
||||
float myCurrentInset = GetMyCurrentInsetFromDraggedEdge();
|
||||
float deltaInset = myCurrentInset - _MyInitialInset;
|
||||
SetDraggedRTInsetAndSize(_DraggedRTInitialInset + deltaInset, _DraggedRTInitialSize - deltaInset);
|
||||
}
|
||||
|
||||
float GetDraggedRTCurrentInsetFromDraggedEdge() { return GetRTCurrentInsetFromDraggedEdge(_DraggedRectTransform); }
|
||||
float GetMyCurrentInsetFromDraggedEdge() { return GetRTCurrentInsetFromDraggedEdge(_RT); }
|
||||
float GetRTCurrentInsetFromDraggedEdge(RectTransform rt) { return rt.GetInsetFromParentEdge(rt.parent as RectTransform, _DraggedEdge); }
|
||||
float GetRTSize(RectTransform rt)
|
||||
{
|
||||
float s;
|
||||
if (_DraggedEdge == RectTransform.Edge.Left || _DraggedEdge == RectTransform.Edge.Right)
|
||||
s = _DraggedRectTransform.rect.width;
|
||||
else
|
||||
s = _DraggedRectTransform.rect.height;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void SetDraggedRTInsetAndSize(float inset, float size)
|
||||
{
|
||||
_DraggedRectTransform.SetInsetAndSizeFromParentEdgeWithCurrentAnchors(_DraggedEdge, inset, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3b69d91db991124a895acd8f3b60111
|
||||
timeCreated: 1495890159
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b531d38311ab2f2418f3af57c56e95a9
|
||||
folderAsset: yes
|
||||
timeCreated: 1532607286
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.AdditionalComponents
|
||||
{
|
||||
public class InputFieldInScrollRectFixer : InputFieldInScrollRectFixerBase
|
||||
{
|
||||
MethodInfo _ActivateInputFieldMI;
|
||||
PropertyInfo _isFocusedPI;
|
||||
|
||||
/// <summary>Using reflection so you won't get compile-time errors</summary>
|
||||
protected override void CacheMethods()
|
||||
{
|
||||
var type = _InputField.GetType();
|
||||
string reqComp = "UnityEngine.UI.InputField";
|
||||
if (type.FullName != reqComp)
|
||||
throw new InvalidOperationException("This script can only be attached to a GameObject containing a " + reqComp);
|
||||
|
||||
_ActivateInputFieldMI = type.GetMethod("ActivateInputField");
|
||||
_isFocusedPI = type.GetProperty("isFocused");
|
||||
}
|
||||
|
||||
protected override void ActivateInputField()
|
||||
{
|
||||
if (_ActivateInputFieldMI != null)
|
||||
_ActivateInputFieldMI.Invoke(_InputField, null);
|
||||
}
|
||||
|
||||
protected override bool IsInputFieldFocused() { return (bool)_isFocusedPI.GetValue(_InputField, null); }
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 55f8d870c6da97e45ab5020ef4ae817e
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using System;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.AdditionalComponents
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility that allows dragging a ScrollRect even if the PointerDown event has started inside a child InputField (which cancels the dragging by default)
|
||||
/// </summary>
|
||||
public abstract class InputFieldInScrollRectFixerBase : MonoBehaviour, IInitializePotentialDragHandler, IBeginDragHandler, IDragHandler, IEndDragHandler, IPointerClickHandler, IPointerDownHandler, IPointerUpHandler, IScrollHandler
|
||||
{
|
||||
protected Selectable _InputField;
|
||||
Image _ImageOnMeIfChild;
|
||||
const string CHILD_NAME = "InputFieldFixer-Child";
|
||||
|
||||
bool _IAmChild;
|
||||
bool _DragInProgress;
|
||||
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
_InputField = GetComponent<Selectable>();
|
||||
_IAmChild = _InputField == null;
|
||||
if (_IAmChild)
|
||||
{
|
||||
InitAsChild();
|
||||
}
|
||||
else
|
||||
{
|
||||
CacheMethods();
|
||||
InitAsParent();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void CacheMethods();
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
_DragInProgress = false;
|
||||
}
|
||||
|
||||
void InitAsParent()
|
||||
{
|
||||
var inputFieldImg = _InputField.image;
|
||||
if (inputFieldImg)
|
||||
inputFieldImg.raycastTarget = false;
|
||||
|
||||
var tr = transform.Find(CHILD_NAME);
|
||||
GameObject go;
|
||||
RectTransform goRT;
|
||||
|
||||
// The child may already exist if you'll instantiate an existing InputField with InputFieldInScrollRectFixer attached
|
||||
if (tr == null)
|
||||
{
|
||||
go = new GameObject(CHILD_NAME, typeof(RectTransform));
|
||||
goRT = go.transform as RectTransform;
|
||||
goRT.SetParent(_InputField.transform, false);
|
||||
go.AddComponent(GetType() /*add the same component as <this>'s type, i.e. the one for InputField or TMPro.TMP_InputField*/);
|
||||
}
|
||||
|
||||
// Parent not needed anymore
|
||||
Destroy(this);
|
||||
}
|
||||
|
||||
void InitAsChild()
|
||||
{
|
||||
name = CHILD_NAME;
|
||||
_InputField = transform.parent.GetComponent<Selectable>();
|
||||
if (!_InputField)
|
||||
throw new InvalidOperationException("Child InputFieldInScrollRectFixer: InputField not found in parent");
|
||||
|
||||
CacheMethods();
|
||||
|
||||
var inputFieldImg = _InputField.image;
|
||||
if (!inputFieldImg)
|
||||
throw new InvalidOperationException("Child InputFieldInScrollRectFixer: InputField must have an image attached (can be invisible)");
|
||||
|
||||
// May have already been created if this is an instance of a another runtime instance
|
||||
_ImageOnMeIfChild = GetComponent<Image>();
|
||||
if (!_ImageOnMeIfChild)
|
||||
{
|
||||
_ImageOnMeIfChild = gameObject.AddComponent<Image>();
|
||||
_ImageOnMeIfChild.sprite = inputFieldImg.sprite;
|
||||
}
|
||||
|
||||
var goRT = transform as RectTransform;
|
||||
|
||||
goRT.SetAsLastSibling();
|
||||
goRT.anchorMin = Vector2.zero;
|
||||
goRT.anchorMax = Vector2.one;
|
||||
goRT.sizeDelta = Vector2.zero;
|
||||
|
||||
_ImageOnMeIfChild.color = Color.clear;
|
||||
}
|
||||
|
||||
protected abstract void ActivateInputField();
|
||||
protected abstract bool IsInputFieldFocused();
|
||||
|
||||
void IPointerDownHandler.OnPointerDown(PointerEventData eventData)
|
||||
{
|
||||
var par = GetInputFieldIfActiveOrNextComponentInItsParents<IPointerDownHandler>();
|
||||
if (par != null)
|
||||
par.OnPointerDown(eventData);
|
||||
}
|
||||
|
||||
void IPointerUpHandler.OnPointerUp(PointerEventData eventData)
|
||||
{
|
||||
var par = GetInputFieldIfActiveOrNextComponentInItsParents<IPointerUpHandler>();
|
||||
if (par != null)
|
||||
par.OnPointerUp(eventData);
|
||||
}
|
||||
|
||||
void IPointerClickHandler.OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
if (InputFieldActiveAndFocused())
|
||||
{
|
||||
(_InputField as IPointerClickHandler).OnPointerClick(eventData);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_DragInProgress)
|
||||
return;
|
||||
|
||||
if (eventData.useDragThreshold)
|
||||
{
|
||||
var dragDist = Vector2.Distance(eventData.pressPosition, eventData.position);
|
||||
if (dragDist > EventSystem.current.pixelDragThreshold)
|
||||
return;
|
||||
}
|
||||
|
||||
if (CanInputFieldBeFocused())
|
||||
{
|
||||
ActivateInputField();
|
||||
return;
|
||||
}
|
||||
|
||||
var par = GetComponentInInputFieldParents<IPointerClickHandler>();
|
||||
if (par != null)
|
||||
par.OnPointerClick(eventData);
|
||||
}
|
||||
|
||||
void IInitializePotentialDragHandler.OnInitializePotentialDrag(PointerEventData eventData)
|
||||
{
|
||||
if (!InputFieldActiveAndFocused())
|
||||
{
|
||||
var par = GetComponentInInputFieldParents<IInitializePotentialDragHandler>();
|
||||
if (par != null)
|
||||
par.OnInitializePotentialDrag(eventData);
|
||||
}
|
||||
}
|
||||
|
||||
void IBeginDragHandler.OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
_DragInProgress = true;
|
||||
var par = GetInputFieldIfActiveOrNextComponentInItsParents<IBeginDragHandler>();
|
||||
if (par != null)
|
||||
par.OnBeginDrag(eventData);
|
||||
}
|
||||
|
||||
void IDragHandler.OnDrag(PointerEventData eventData)
|
||||
{
|
||||
var par = GetInputFieldIfActiveOrNextComponentInItsParents<IDragHandler>();
|
||||
if (par != null)
|
||||
par.OnDrag(eventData);
|
||||
}
|
||||
|
||||
void IScrollHandler.OnScroll(PointerEventData eventData)
|
||||
{
|
||||
var par = GetInputFieldIfActiveOrNextComponentInItsParents<IScrollHandler>();
|
||||
if (par != null)
|
||||
par.OnScroll(eventData);
|
||||
}
|
||||
|
||||
void IEndDragHandler.OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
_DragInProgress = false;
|
||||
var par = GetInputFieldIfActiveOrNextComponentInItsParents<IEndDragHandler>();
|
||||
if (par != null)
|
||||
par.OnEndDrag(eventData);
|
||||
}
|
||||
|
||||
bool InputFieldActiveAndFocused() { return CanInputFieldBeFocused() && IsInputFieldFocused(); }
|
||||
bool CanInputFieldBeFocused() { return _InputField.isActiveAndEnabled && _InputField.interactable; }
|
||||
|
||||
T GetInputFieldIfActiveOrNextComponentInItsParents<T>()
|
||||
{
|
||||
if (InputFieldActiveAndFocused())
|
||||
return (T)(object)_InputField;
|
||||
|
||||
return GetComponentInInputFieldParents<T>();
|
||||
}
|
||||
|
||||
T GetComponentInInputFieldParents<T>() { return (T)(object)_InputField.transform.parent.GetComponentInParent(typeof(T)); }
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 800d0ae0d22df834fad82405cbc91e8a
|
||||
timeCreated: 1563646918
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Uncomment this if you have TMPro installed and want to use this script
|
||||
//#define TMPRO_AVAILABLE
|
||||
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.AdditionalComponents
|
||||
{
|
||||
public class InputFieldInScrollRectFixerTMPro : InputFieldInScrollRectFixerBase
|
||||
{
|
||||
MethodInfo _ActivateInputFieldMI;
|
||||
PropertyInfo _isFocusedPI;
|
||||
|
||||
|
||||
/// <summary>Using reflection so you won't get compile-time errors</summary>
|
||||
protected override void CacheMethods()
|
||||
{
|
||||
var type = _InputField.GetType();
|
||||
string reqComp = "TMPro.TMP_InputField";
|
||||
if (type.FullName != reqComp)
|
||||
throw new InvalidOperationException("This script can only be attached to a GameObject containing a " + reqComp);
|
||||
|
||||
_ActivateInputFieldMI = type.GetMethod("ActivateInputField");
|
||||
_isFocusedPI = type.GetProperty("isFocused");
|
||||
}
|
||||
|
||||
protected override void ActivateInputField()
|
||||
{
|
||||
if (_ActivateInputFieldMI != null)
|
||||
_ActivateInputFieldMI.Invoke(_InputField, null);
|
||||
}
|
||||
|
||||
protected override bool IsInputFieldFocused() { return (bool)_isFocusedPI.GetValue(_InputField, null); }
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ba1a9fc18218a848a11e8cddd64bdb3
|
||||
timeCreated: 1563646918
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.Events;
|
||||
using frame8.Logic.Misc.Other.Extensions;
|
||||
using frame8.Logic.Misc.Visual.UI;
|
||||
using frame8.Logic.Misc.Visual.UI.MonoBehaviours;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util.ScrollViews
|
||||
{
|
||||
public class NonInteractableScrollRect : ScrollRect
|
||||
{
|
||||
public override void OnInitializePotentialDrag(PointerEventData eventData) { }
|
||||
public override void OnBeginDrag(PointerEventData eventData) { }
|
||||
public override void OnDrag(PointerEventData eventData) { }
|
||||
public override void OnEndDrag(PointerEventData eventData) { }
|
||||
public override void OnScroll(PointerEventData data) { }
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 45de622d467858c4d9599c5c2ac8ec4d
|
||||
timeCreated: 1532623175
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.Events;
|
||||
using frame8.Logic.Misc.Other.Extensions;
|
||||
using frame8.Logic.Misc.Visual.UI;
|
||||
using frame8.Logic.Misc.Visual.UI.MonoBehaviours;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util.ScrollViews
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to a Unity's ScrollRect through <see cref="IScrollRectProxy"/>.
|
||||
/// For example, it can be added to a regular ScrollRect so <see cref="ScrollbarFixer8"/> can communicate with it, in case you want to use the <see cref="ScrollbarFixer8"/> without OSA.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(ScrollRect))]
|
||||
public class UnityScrollRectProxy : MonoBehaviour, IScrollRectProxy
|
||||
{
|
||||
#region IScrollRectProxy properties implementation
|
||||
public bool IsInitialized { get { return ScrollRect != null; } }
|
||||
public Vector2 Velocity { get { return ScrollRect.velocity; } set { ScrollRect.velocity = value; } }
|
||||
public bool IsHorizontal { get { return ScrollRect.horizontal; } }
|
||||
public bool IsVertical { get { return ScrollRect.vertical; } }
|
||||
public RectTransform Content { get { return ScrollRect.content; } }
|
||||
public RectTransform Viewport { get { return ScrollRect.viewport; } }
|
||||
public double ContentInsetFromViewportStart { get { return Content.GetInsetFromParentEdge(Viewport, (this as IScrollRectProxy).GetStartEdge()); } }
|
||||
public double ContentInsetFromViewportEnd { get { return Content.GetInsetFromParentEdge(Viewport, (this as IScrollRectProxy).GetEndEdge()); } }
|
||||
#endregion
|
||||
|
||||
ScrollRect ScrollRect { get { if (!_ScrollRect) _ScrollRect = GetComponent<ScrollRect>(); return _ScrollRect; } }
|
||||
ScrollRect _ScrollRect;
|
||||
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (ScrollRect == null)
|
||||
throw new UnityException(GetType().Name + ": No ScrollRect component found");
|
||||
}
|
||||
|
||||
|
||||
#region IScrollRectProxy methods implementation
|
||||
#pragma warning disable 0067
|
||||
public event System.Action<double> ScrollPositionChanged;
|
||||
#pragma warning restore 0067
|
||||
public void SetNormalizedPosition(double normalizedPosition) { if (IsHorizontal) ScrollRect.horizontalNormalizedPosition = (float)normalizedPosition; else ScrollRect.verticalNormalizedPosition = (float)normalizedPosition; }
|
||||
public double GetNormalizedPosition() { return IsHorizontal ? ScrollRect.horizontalNormalizedPosition : ScrollRect.verticalNormalizedPosition; }
|
||||
public double GetContentSize() { return IsHorizontal ? Content.rect.width : Content.rect.height; }
|
||||
public double GetViewportSize() { return IsHorizontal ? Viewport.rect.width : Viewport.rect.height; }
|
||||
public void StopMovement() { ScrollRect.StopMovement(); }
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b56953da65ca4d40aa44696aca06058
|
||||
timeCreated: 1532607872
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,83 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using frame8.Logic.Misc.Visual.UI;
|
||||
using Com.ForbiddenByte.OSA.Core;
|
||||
|
||||
namespace Com.ForbiddenByte.OSA.Util
|
||||
{
|
||||
[RequireComponent(typeof(Scrollbar))]
|
||||
public class ScrollbarRotateOnPull : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
float _DegreesOfFreedom = 5f;
|
||||
[SerializeField]
|
||||
float _RotationSensivity = .5f;
|
||||
|
||||
IOSA _Adapter;
|
||||
RectTransform _HandleRT;
|
||||
Vector2 _SrollbarPivotOnInit;
|
||||
|
||||
|
||||
void Start()
|
||||
{
|
||||
_Adapter = GetComponentInParent<IOSA>();
|
||||
_HandleRT = transform as RectTransform;
|
||||
//_HandleRT = _Scrollbar.handleRect;
|
||||
_SrollbarPivotOnInit = _HandleRT.pivot;
|
||||
}
|
||||
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (_Adapter == null)
|
||||
return;
|
||||
|
||||
float pullAmount01 = 0f;
|
||||
var piv = _SrollbarPivotOnInit;
|
||||
int sign = 1;
|
||||
if (_Adapter.GetContentSizeToViewportRatio() > 1d)
|
||||
{
|
||||
var insetStart = _Adapter.ContentVirtualInsetFromViewportStart;
|
||||
if (insetStart > 0d)
|
||||
{
|
||||
if (_Adapter.IsHorizontal)
|
||||
{
|
||||
pullAmount01 = (float)(insetStart / _Adapter.BaseParameters.Viewport.rect.width);
|
||||
piv.x = 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
pullAmount01 = (float)(insetStart / _Adapter.BaseParameters.Viewport.rect.height);
|
||||
piv.y = 1f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var insetEnd = _Adapter.ContentVirtualInsetFromViewportEnd;
|
||||
if (insetEnd > 0d)
|
||||
{
|
||||
sign = -1;
|
||||
pullAmount01 = (float)(insetEnd / _Adapter.GetContentSize());
|
||||
if (_Adapter.IsHorizontal)
|
||||
{
|
||||
pullAmount01 = (float)(insetEnd / _Adapter.BaseParameters.Viewport.rect.width);
|
||||
piv.x = 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
pullAmount01 = (float)(insetEnd / _Adapter.BaseParameters.Viewport.rect.height);
|
||||
piv.y = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_HandleRT.pivot != piv)
|
||||
_HandleRT.pivot = piv;
|
||||
|
||||
var euler = _HandleRT.localEulerAngles;
|
||||
// Multiplying argument by _Speed to speed up sine function growth
|
||||
euler.z = Mathf.Sin(pullAmount01 * _RotationSensivity * Mathf.PI) * _DegreesOfFreedom * sign;
|
||||
_HandleRT.localEulerAngles = euler;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 04b05756efdf6204aa6664d62e060e57
|
||||
timeCreated: 1529355226
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user