mirror of
https://github.com/GrahamKracker/UnityExplorer.git
synced 2025-07-10 14:03:30 +08:00
use UniverseLib
This commit is contained in:
@ -1,27 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.Models;
|
||||
|
||||
namespace UnityExplorer.UI
|
||||
{
|
||||
// A simple helper class to handle a button's OnClick more effectively.
|
||||
|
||||
public class ButtonRef
|
||||
{
|
||||
public Action OnClick;
|
||||
|
||||
public Button Component { get; }
|
||||
public Text ButtonText { get; }
|
||||
|
||||
public ButtonRef(Button button)
|
||||
{
|
||||
this.Component = button;
|
||||
this.ButtonText = button.GetComponentInChildren<Text>();
|
||||
|
||||
button.onClick.AddListener(() => { OnClick?.Invoke(); });
|
||||
}
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.Models;
|
||||
|
||||
namespace UnityExplorer.UI
|
||||
{
|
||||
public class InputFieldRef : UIModel
|
||||
{
|
||||
public static readonly HashSet<InputFieldRef> inputsPendingUpdate = new HashSet<InputFieldRef>();
|
||||
|
||||
public static void UpdateInstances()
|
||||
{
|
||||
if (inputsPendingUpdate.Any())
|
||||
{
|
||||
var array = inputsPendingUpdate.ToArray();
|
||||
|
||||
for (int i = array.Length - 1; i >= 0; i--)
|
||||
{
|
||||
var entry = array[i];
|
||||
LayoutRebuilder.MarkLayoutForRebuild(entry.Rect);
|
||||
entry.OnValueChanged?.Invoke(entry.Component.text);
|
||||
}
|
||||
|
||||
inputsPendingUpdate.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public InputFieldRef(InputField component)
|
||||
{
|
||||
this.Component = component;
|
||||
Rect = component.GetComponent<RectTransform>();
|
||||
PlaceholderText = component.placeholder.TryCast<Text>();
|
||||
component.onValueChanged.AddListener(OnInputChanged);
|
||||
}
|
||||
|
||||
public event Action<string> OnValueChanged;
|
||||
|
||||
public InputField Component;
|
||||
public Text PlaceholderText;
|
||||
public RectTransform Rect;
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => Component.text;
|
||||
set => Component.text = value;
|
||||
}
|
||||
|
||||
public TextGenerator TextGenerator => Component.cachedInputTextGenerator;
|
||||
|
||||
public bool ReachedMaxVerts => TextGenerator.vertexCount >= UIManager.MAX_TEXT_VERTS;
|
||||
|
||||
private void OnInputChanged(string value)
|
||||
{
|
||||
if (!inputsPendingUpdate.Contains(this))
|
||||
inputsPendingUpdate.Add(this);
|
||||
}
|
||||
|
||||
public override GameObject UIRoot => Component.gameObject;
|
||||
|
||||
public override void ConstructUI(GameObject parent)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.UI.Models
|
||||
{
|
||||
public abstract class UIBehaviourModel : UIModel
|
||||
{
|
||||
private static readonly List<UIBehaviourModel> Instances = new List<UIBehaviourModel>();
|
||||
|
||||
public static void UpdateInstances()
|
||||
{
|
||||
if (!Instances.Any())
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = Instances.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var instance = Instances[i];
|
||||
if (instance == null || !instance.UIRoot)
|
||||
{
|
||||
Instances.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
if (instance.Enabled)
|
||||
instance.Update();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.Log(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public UIBehaviourModel()
|
||||
{
|
||||
Instances.Add(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default empty method, override and implement if NeedsUpdateTick is true.
|
||||
/// </summary>
|
||||
public virtual void Update()
|
||||
{
|
||||
}
|
||||
|
||||
public override void Destroy()
|
||||
{
|
||||
if (Instances.Contains(this))
|
||||
Instances.Remove(this);
|
||||
|
||||
base.Destroy();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.UI.Models
|
||||
{
|
||||
public abstract class UIModel
|
||||
{
|
||||
public abstract GameObject UIRoot { get; }
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => UIRoot && UIRoot.activeInHierarchy;
|
||||
set
|
||||
{
|
||||
if (!UIRoot || Enabled == value)
|
||||
return;
|
||||
UIRoot.SetActive(value);
|
||||
OnToggleEnabled?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<bool> OnToggleEnabled;
|
||||
|
||||
public abstract void ConstructUI(GameObject parent);
|
||||
|
||||
public virtual void Toggle() => SetActive(!Enabled);
|
||||
|
||||
public virtual void SetActive(bool active)
|
||||
{
|
||||
UIRoot?.SetActive(active);
|
||||
}
|
||||
|
||||
public virtual void Destroy()
|
||||
{
|
||||
if (UIRoot)
|
||||
GameObject.Destroy(UIRoot);
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,9 @@ using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.CSConsole;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
@ -39,8 +42,8 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
private void InvokeOnValueChanged(string value)
|
||||
{
|
||||
if (value.Length == UIManager.MAX_INPUTFIELD_CHARS)
|
||||
ExplorerCore.LogWarning($"Reached maximum InputField character length! ({UIManager.MAX_INPUTFIELD_CHARS})");
|
||||
if (value.Length == UniversalUI.MAX_INPUTFIELD_CHARS)
|
||||
ExplorerCore.LogWarning($"Reached maximum InputField character length! ({UniversalUI.MAX_INPUTFIELD_CHARS})");
|
||||
|
||||
OnInputChanged?.Invoke(value);
|
||||
}
|
||||
@ -148,7 +151,7 @@ namespace UnityExplorer.UI.Panels
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(linesHolder, true, true, true, true);
|
||||
|
||||
LineNumberText = UIFactory.CreateLabel(linesHolder, "LineNumbers", "1", TextAnchor.UpperCenter, Color.grey, fontSize: 16);
|
||||
LineNumberText.font = UIManager.ConsoleFont;
|
||||
LineNumberText.font = UniversalUI.ConsoleFont;
|
||||
|
||||
// input field
|
||||
|
||||
@ -192,9 +195,9 @@ namespace UnityExplorer.UI.Panels
|
||||
HighlightText.fontSize = fontSize;
|
||||
|
||||
// Set fonts
|
||||
InputText.font = UIManager.ConsoleFont;
|
||||
Input.PlaceholderText.font = UIManager.ConsoleFont;
|
||||
HighlightText.font = UIManager.ConsoleFont;
|
||||
InputText.font = UniversalUI.ConsoleFont;
|
||||
Input.PlaceholderText.font = UniversalUI.ConsoleFont;
|
||||
HighlightText.font = UniversalUI.ConsoleFont;
|
||||
|
||||
RuntimeProvider.Instance.StartCoroutine(DelayedLayoutSetup());
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.Hooks;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UnityExplorer.UI.Widgets.AutoComplete;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
@ -185,9 +187,9 @@ namespace UnityExplorer.UI.Panels
|
||||
EditorHighlightText.fontSize = fontSize;
|
||||
|
||||
// Set fonts
|
||||
EditorInputText.font = UIManager.ConsoleFont;
|
||||
EditorInput.PlaceholderText.font = UIManager.ConsoleFont;
|
||||
EditorHighlightText.font = UIManager.ConsoleFont;
|
||||
EditorInputText.font = UniversalUI.ConsoleFont;
|
||||
EditorInput.PlaceholderText.font = UniversalUI.ConsoleFont;
|
||||
EditorHighlightText.font = UniversalUI.ConsoleFont;
|
||||
|
||||
editorPanel.SetActive(false);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.Inspectors;
|
||||
using UniverseLib.UI;
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
|
@ -8,6 +8,9 @@ using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
@ -233,8 +236,8 @@ namespace UnityExplorer.UI.Panels
|
||||
Input.Component.readOnly = true;
|
||||
Input.Component.textComponent.supportRichText = true;
|
||||
Input.Component.lineType = InputField.LineType.MultiLineNewline;
|
||||
Input.Component.textComponent.font = UIManager.ConsoleFont;
|
||||
Input.PlaceholderText.font = UIManager.ConsoleFont;
|
||||
Input.Component.textComponent.font = UniversalUI.ConsoleFont;
|
||||
Input.PlaceholderText.font = UniversalUI.ConsoleFont;
|
||||
|
||||
return UIRoot;
|
||||
}
|
||||
|
@ -10,9 +10,11 @@ using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UniverseLib.UI.Models;
|
||||
using UnityExplorer.ObjectExplorer;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib;
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
@ -42,7 +44,7 @@ namespace UnityExplorer.UI.Panels
|
||||
content.SetActive(true);
|
||||
|
||||
var button = tabButtons[tabIndex];
|
||||
RuntimeProvider.Instance.SetColorBlock(button.Component, UIManager.enabledButtonColor, UIManager.enabledButtonColor * 1.2f);
|
||||
RuntimeProvider.Instance.SetColorBlock(button.Component, UniversalUI.enabledButtonColor, UniversalUI.enabledButtonColor * 1.2f);
|
||||
|
||||
SelectedTab = tabIndex;
|
||||
SaveToConfigManager();
|
||||
@ -51,7 +53,7 @@ namespace UnityExplorer.UI.Panels
|
||||
private void DisableTab(int tabIndex)
|
||||
{
|
||||
tabPages[tabIndex].SetActive(false);
|
||||
RuntimeProvider.Instance.SetColorBlock(tabButtons[tabIndex].Component, UIManager.disabledButtonColor, UIManager.disabledButtonColor * 1.2f);
|
||||
RuntimeProvider.Instance.SetColorBlock(tabButtons[tabIndex].Component, UniversalUI.disabledButtonColor, UniversalUI.disabledButtonColor * 1.2f);
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
|
@ -8,6 +8,8 @@ using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.CacheObject;
|
||||
using UnityExplorer.CacheObject.Views;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UniverseLib.UI.Widgets;
|
||||
using UniverseLib.UI;
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
|
@ -5,9 +5,11 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UniverseLib.Input;
|
||||
using UniverseLib.UI.Models;
|
||||
using UnityExplorer.UI.Widgets.AutoComplete;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib;
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
@ -472,7 +474,7 @@ namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
try
|
||||
{
|
||||
var text = UIFactory.CreateLabel(UIManager.CanvasRoot, "ResizeCursor", "↔", TextAnchor.MiddleCenter, Color.white, true, 35);
|
||||
var text = UIFactory.CreateLabel(UIManager.UIRoot, "ResizeCursor", "↔", TextAnchor.MiddleCenter, Color.white, true, 35);
|
||||
s_resizeCursorObj = text.gameObject;
|
||||
|
||||
RectTransform rect = s_resizeCursorObj.GetComponent<RectTransform>();
|
||||
|
@ -6,9 +6,11 @@ using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UniverseLib.Input;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib;
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
@ -35,6 +37,7 @@ namespace UnityExplorer.UI.Panels
|
||||
int count = UIManager.PanelHolder.transform.childCount;
|
||||
var mousePos = InputManager.MousePosition;
|
||||
bool clickedInAny = false;
|
||||
|
||||
for (int i = count - 1; i >= 0; i--)
|
||||
{
|
||||
// make sure this is a real recognized panel
|
||||
@ -106,7 +109,7 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
public override void SetActive(bool active)
|
||||
{
|
||||
if (this.Enabled.Equals(active))
|
||||
if (this.Enabled == active)
|
||||
return;
|
||||
|
||||
base.SetActive(active);
|
||||
@ -116,7 +119,7 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
if (NavButtonWanted)
|
||||
{
|
||||
var color = active ? UIManager.enabledButtonColor : UIManager.disabledButtonColor;
|
||||
var color = active ? UniversalUI.enabledButtonColor : UniversalUI.disabledButtonColor;
|
||||
RuntimeProvider.Instance.SetColorBlock(NavButton.Component, color, color * 1.2f);
|
||||
}
|
||||
|
||||
@ -232,7 +235,7 @@ namespace UnityExplorer.UI.Panels
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(navBtn, false, true, true, true, 0, 0, 0, 5, 5, TextAnchor.MiddleCenter);
|
||||
UIFactory.SetLayoutElement(navBtn, minWidth: 80);
|
||||
|
||||
RuntimeProvider.Instance.SetColorBlock(NavButton.Component, UIManager.disabledButtonColor, UIManager.disabledButtonColor * 1.2f);
|
||||
RuntimeProvider.Instance.SetColorBlock(NavButton.Component, UniversalUI.disabledButtonColor, UniversalUI.disabledButtonColor * 1.2f);
|
||||
NavButton.OnClick += () => { UIManager.TogglePanel(PanelType); };
|
||||
|
||||
var txtObj = navBtn.transform.Find("Text").gameObject;
|
||||
@ -240,7 +243,7 @@ namespace UnityExplorer.UI.Panels
|
||||
}
|
||||
|
||||
// create core canvas
|
||||
uiRoot = UIFactory.CreatePanel(Name, out GameObject panelContent);
|
||||
uiRoot = UIFactory.CreatePanel(Name, UIManager.PanelHolder, out GameObject panelContent);
|
||||
Rect = this.uiRoot.GetComponent<RectTransform>();
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(this.uiRoot, false, false, true, true, 0, 2, 2, 2, 2, TextAnchor.UpperLeft);
|
||||
|
||||
|
@ -5,6 +5,9 @@ using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Inspectors.MouseInspectors;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
|
129
src/UI/Pool.cs
129
src/UI/Pool.cs
@ -1,129 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.UI
|
||||
{
|
||||
public interface IPooledObject
|
||||
{
|
||||
GameObject UIRoot { get; set; }
|
||||
float DefaultHeight { get; }
|
||||
|
||||
GameObject CreateContent(GameObject parent);
|
||||
}
|
||||
|
||||
public abstract class Pool
|
||||
{
|
||||
protected static readonly Dictionary<Type, Pool> pools = new Dictionary<Type, Pool>();
|
||||
|
||||
public static Pool GetPool(Type type)
|
||||
{
|
||||
if (!pools.TryGetValue(type, out Pool pool))
|
||||
pool = CreatePool(type);
|
||||
return pool;
|
||||
}
|
||||
|
||||
protected static Pool CreatePool(Type type)
|
||||
{
|
||||
Pool pool = (Pool)Activator.CreateInstance(typeof(Pool<>).MakeGenericType(new[] { type }));
|
||||
pools.Add(type, pool);
|
||||
return pool;
|
||||
}
|
||||
|
||||
public static IPooledObject Borrow(Type type)
|
||||
{
|
||||
return GetPool(type).TryBorrow();
|
||||
}
|
||||
|
||||
public static void Return(Type type, IPooledObject obj)
|
||||
{
|
||||
GetPool(type).TryReturn(obj);
|
||||
}
|
||||
|
||||
protected abstract IPooledObject TryBorrow();
|
||||
protected abstract void TryReturn(IPooledObject obj);
|
||||
}
|
||||
|
||||
public class Pool<T> : Pool where T : IPooledObject
|
||||
{
|
||||
public static Pool<T> GetPool() => (Pool<T>)GetPool(typeof(T));
|
||||
|
||||
public static T Borrow()
|
||||
{
|
||||
return GetPool().BorrowObject();
|
||||
}
|
||||
|
||||
public static void Return(T obj)
|
||||
{
|
||||
GetPool().ReturnObject(obj);
|
||||
}
|
||||
|
||||
// Instance
|
||||
|
||||
public static Pool<T> Instance
|
||||
{
|
||||
get => s_instance ?? (Pool<T>)CreatePool(typeof(T));
|
||||
}
|
||||
private static Pool<T> s_instance;
|
||||
|
||||
public Pool()
|
||||
{
|
||||
s_instance = this;
|
||||
|
||||
//ExplorerCore.LogWarning("Creating Pool<" + typeof(T).Name + ">");
|
||||
|
||||
InactiveHolder = new GameObject($"PoolHolder_{typeof(T).Name}");
|
||||
InactiveHolder.transform.parent = UIManager.PoolHolder.transform;
|
||||
InactiveHolder.hideFlags |= HideFlags.HideAndDontSave;
|
||||
InactiveHolder.SetActive(false);
|
||||
|
||||
// Create an instance (not content) to grab the default height
|
||||
var obj = (T)Activator.CreateInstance(typeof(T));
|
||||
DefaultHeight = obj.DefaultHeight;
|
||||
}
|
||||
|
||||
public GameObject InactiveHolder { get; }
|
||||
public float DefaultHeight { get; }
|
||||
|
||||
private readonly HashSet<T> available = new HashSet<T>();
|
||||
private readonly HashSet<T> borrowed = new HashSet<T>();
|
||||
|
||||
public int AvailableCount => available.Count;
|
||||
|
||||
private void IncrementPool()
|
||||
{
|
||||
var obj = (T)Activator.CreateInstance(typeof(T));
|
||||
obj.CreateContent(InactiveHolder);
|
||||
available.Add(obj);
|
||||
}
|
||||
|
||||
public T BorrowObject()
|
||||
{
|
||||
if (available.Count <= 0)
|
||||
IncrementPool();
|
||||
|
||||
var obj = available.First();
|
||||
available.Remove(obj);
|
||||
borrowed.Add(obj);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
public void ReturnObject(T obj)
|
||||
{
|
||||
if (!borrowed.Contains(obj))
|
||||
ExplorerCore.LogWarning($"Returning an item to object pool ({typeof(T).Name}) but the item didn't exist in the borrowed list?");
|
||||
else
|
||||
borrowed.Remove(obj);
|
||||
|
||||
available.Add(obj);
|
||||
obj.UIRoot.transform.SetParent(InactiveHolder.transform, false);
|
||||
}
|
||||
|
||||
protected override IPooledObject TryBorrow() => Borrow();
|
||||
|
||||
protected override void TryReturn(IPooledObject obj) => Return((T)obj);
|
||||
}
|
||||
}
|
@ -1,970 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.UI
|
||||
{
|
||||
public static class UIFactory
|
||||
{
|
||||
#region Init, Core
|
||||
|
||||
internal static Vector2 _largeElementSize = new Vector2(100, 30);
|
||||
internal static Vector2 _smallElementSize = new Vector2(25, 25);
|
||||
internal static Color _defaultTextColor = Color.white;
|
||||
|
||||
public static GameObject CreateUIObject(string name, GameObject parent, Vector2 size = default)
|
||||
{
|
||||
if (!parent)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Warning: Creating {name} but parent is null");
|
||||
ExplorerCore.Log(Environment.StackTrace);
|
||||
}
|
||||
|
||||
var obj = new GameObject(name)
|
||||
{
|
||||
layer = 5,
|
||||
hideFlags = HideFlags.HideAndDontSave,
|
||||
};
|
||||
|
||||
if (parent)
|
||||
obj.transform.SetParent(parent.transform, false);
|
||||
|
||||
RectTransform rect = obj.AddComponent<RectTransform>();
|
||||
rect.sizeDelta = size;
|
||||
return obj;
|
||||
}
|
||||
|
||||
internal static void SetDefaultTextValues(Text text)
|
||||
{
|
||||
text.color = _defaultTextColor;
|
||||
text.font = UIManager.DefaultFont;
|
||||
text.fontSize = 14;
|
||||
}
|
||||
|
||||
internal static void SetDefaultSelectableColors(Selectable selectable)
|
||||
{
|
||||
RuntimeProvider.Instance.SetColorBlock(selectable, new Color(0.2f, 0.2f, 0.2f),
|
||||
new Color(0.3f, 0.3f, 0.3f), new Color(0.15f, 0.15f, 0.15f));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Default Layout Components
|
||||
|
||||
/// <summary>
|
||||
/// Get and/or Add a LayoutElement component to the GameObject, and set any of the values on it.
|
||||
/// </summary>
|
||||
public static LayoutElement SetLayoutElement(GameObject gameObject, int? minWidth = null, int? minHeight = null,
|
||||
int? flexibleWidth = null, int? flexibleHeight = null, int? preferredWidth = null, int? preferredHeight = null,
|
||||
bool? ignoreLayout = null)
|
||||
{
|
||||
var layout = gameObject.GetComponent<LayoutElement>();
|
||||
if (!layout)
|
||||
layout = gameObject.AddComponent<LayoutElement>();
|
||||
|
||||
if (minWidth != null)
|
||||
layout.minWidth = (int)minWidth;
|
||||
|
||||
if (minHeight != null)
|
||||
layout.minHeight = (int)minHeight;
|
||||
|
||||
if (flexibleWidth != null)
|
||||
layout.flexibleWidth = (int)flexibleWidth;
|
||||
|
||||
if (flexibleHeight != null)
|
||||
layout.flexibleHeight = (int)flexibleHeight;
|
||||
|
||||
if (preferredWidth != null)
|
||||
layout.preferredWidth = (int)preferredWidth;
|
||||
|
||||
if (preferredHeight != null)
|
||||
layout.preferredHeight = (int)preferredHeight;
|
||||
|
||||
if (ignoreLayout != null)
|
||||
layout.ignoreLayout = (bool)ignoreLayout;
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get and/or Add a HorizontalOrVerticalLayoutGroup (must pick one) to the GameObject, and set the values on it.
|
||||
/// </summary>
|
||||
public static T SetLayoutGroup<T>(GameObject gameObject, bool? forceWidth = null, bool? forceHeight = null,
|
||||
bool? childControlWidth = null, bool? childControlHeight = null, int? spacing = null, int? padTop = null,
|
||||
int? padBottom = null, int? padLeft = null, int? padRight = null, TextAnchor? childAlignment = null)
|
||||
where T : HorizontalOrVerticalLayoutGroup
|
||||
{
|
||||
var group = gameObject.GetComponent<T>();
|
||||
if (!group)
|
||||
group = gameObject.AddComponent<T>();
|
||||
|
||||
return SetLayoutGroup(group, forceWidth, forceHeight, childControlWidth, childControlHeight, spacing, padTop,
|
||||
padBottom, padLeft, padRight, childAlignment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the values on a HorizontalOrVerticalLayoutGroup.
|
||||
/// </summary>
|
||||
public static T SetLayoutGroup<T>(T group, bool? forceWidth = null, bool? forceHeight = null,
|
||||
bool? childControlWidth = null, bool? childControlHeight = null, int? spacing = null, int? padTop = null,
|
||||
int? padBottom = null, int? padLeft = null, int? padRight = null, TextAnchor? childAlignment = null)
|
||||
where T : HorizontalOrVerticalLayoutGroup
|
||||
{
|
||||
if (forceWidth != null)
|
||||
group.childForceExpandWidth = (bool)forceWidth;
|
||||
if (forceHeight != null)
|
||||
group.childForceExpandHeight = (bool)forceHeight;
|
||||
if (childControlWidth != null)
|
||||
group.SetChildControlWidth((bool)childControlWidth);
|
||||
if (childControlHeight != null)
|
||||
group.SetChildControlHeight((bool)childControlHeight);
|
||||
if (spacing != null)
|
||||
group.spacing = (int)spacing;
|
||||
if (padTop != null)
|
||||
group.padding.top = (int)padTop;
|
||||
if (padBottom != null)
|
||||
group.padding.bottom = (int)padBottom;
|
||||
if (padLeft != null)
|
||||
group.padding.left = (int)padLeft;
|
||||
if (padRight != null)
|
||||
group.padding.right = (int)padRight;
|
||||
if (childAlignment != null)
|
||||
group.childAlignment = (TextAnchor)childAlignment;
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a Panel on the UI Canvas.
|
||||
/// </summary>
|
||||
public static GameObject CreatePanel(string name, out GameObject contentHolder, Color? bgColor = null)
|
||||
{
|
||||
var panelObj = CreateUIObject(name, UIManager.PanelHolder);
|
||||
SetLayoutGroup<VerticalLayoutGroup>(panelObj, true, true, true, true);
|
||||
|
||||
var rect = panelObj.GetComponent<RectTransform>();
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.anchoredPosition = Vector2.zero;
|
||||
rect.sizeDelta = Vector2.zero;
|
||||
|
||||
var maskImg = panelObj.AddComponent<Image>();
|
||||
maskImg.color = Color.black;
|
||||
panelObj.AddComponent<Mask>().showMaskGraphic = true;
|
||||
|
||||
contentHolder = CreateUIObject("Content", panelObj);
|
||||
|
||||
Image bgImage = contentHolder.AddComponent<Image>();
|
||||
bgImage.type = Image.Type.Filled;
|
||||
if (bgColor == null)
|
||||
bgImage.color = new Color(0.07f, 0.07f, 0.07f);
|
||||
else
|
||||
bgImage.color = (Color)bgColor;
|
||||
|
||||
SetLayoutGroup<VerticalLayoutGroup>(contentHolder, true, true, true, true, 3, 3, 3, 3, 3);
|
||||
|
||||
return panelObj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a VerticalLayoutGroup object.
|
||||
/// </summary>
|
||||
public static GameObject CreateVerticalGroup(GameObject parent, string name, bool forceWidth, bool forceHeight,
|
||||
bool childControlWidth, bool childControlHeight, int spacing = 0, Vector4 padding = default, Color bgColor = default,
|
||||
TextAnchor? childAlignment = null)
|
||||
{
|
||||
GameObject groupObj = CreateUIObject(name, parent);
|
||||
|
||||
SetLayoutGroup<VerticalLayoutGroup>(groupObj, forceWidth, forceHeight, childControlWidth, childControlHeight,
|
||||
spacing, (int)padding.x, (int)padding.y, (int)padding.z, (int)padding.w, childAlignment);
|
||||
|
||||
Image image = groupObj.AddComponent<Image>();
|
||||
image.color = bgColor == default
|
||||
? new Color(0.17f, 0.17f, 0.17f)
|
||||
: bgColor;
|
||||
|
||||
return groupObj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a HorizontalLayoutGroup object.
|
||||
/// </summary>
|
||||
public static GameObject CreateHorizontalGroup(GameObject parent, string name, bool forceExpandWidth, bool forceExpandHeight,
|
||||
bool childControlWidth, bool childControlHeight, int spacing = 0, Vector4 padding = default, Color bgColor = default,
|
||||
TextAnchor? childAlignment = null)
|
||||
{
|
||||
GameObject groupObj = CreateUIObject(name, parent);
|
||||
|
||||
SetLayoutGroup<HorizontalLayoutGroup>(groupObj, forceExpandWidth, forceExpandHeight, childControlWidth, childControlHeight,
|
||||
spacing, (int)padding.x, (int)padding.y, (int)padding.z, (int)padding.w, childAlignment);
|
||||
|
||||
Image image = groupObj.AddComponent<Image>();
|
||||
image.color = bgColor == default
|
||||
? new Color(0.17f, 0.17f, 0.17f)
|
||||
: bgColor;
|
||||
|
||||
return groupObj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a GridLayoutGroup object.
|
||||
/// </summary>
|
||||
public static GameObject CreateGridGroup(GameObject parent, string name, Vector2 cellSize, Vector2 spacing, Color bgColor = default)
|
||||
{
|
||||
var groupObj = CreateUIObject(name, parent);
|
||||
|
||||
GridLayoutGroup gridGroup = groupObj.AddComponent<GridLayoutGroup>();
|
||||
gridGroup.childAlignment = TextAnchor.UpperLeft;
|
||||
gridGroup.cellSize = cellSize;
|
||||
gridGroup.spacing = spacing;
|
||||
|
||||
Image image = groupObj.AddComponent<Image>();
|
||||
|
||||
image.color = bgColor == default
|
||||
? new Color(0.17f, 0.17f, 0.17f)
|
||||
: bgColor;
|
||||
|
||||
return groupObj;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Default Control Elements
|
||||
|
||||
/// <summary>
|
||||
/// Create a Label object.
|
||||
/// </summary>
|
||||
public static Text CreateLabel(GameObject parent, string name, string text, TextAnchor alignment,
|
||||
Color color = default, bool supportRichText = true, int fontSize = 14)
|
||||
{
|
||||
var obj = CreateUIObject(name, parent);
|
||||
var textComp = obj.AddComponent<Text>();
|
||||
|
||||
SetDefaultTextValues(textComp);
|
||||
|
||||
textComp.text = text;
|
||||
textComp.color = color == default ? _defaultTextColor : color;
|
||||
textComp.supportRichText = supportRichText;
|
||||
textComp.alignment = alignment;
|
||||
textComp.fontSize = fontSize;
|
||||
|
||||
return textComp;
|
||||
}
|
||||
|
||||
public static ButtonRef CreateButton(GameObject parent, string name, string text, Color? normalColor = null)
|
||||
{
|
||||
var colors = new ColorBlock();
|
||||
normalColor = normalColor ?? new Color(0.25f, 0.25f, 0.25f);
|
||||
|
||||
var btn = CreateButton(parent, name, text, colors);
|
||||
|
||||
RuntimeProvider.Instance.SetColorBlock(btn.Component, normalColor, normalColor * 1.2f, normalColor * 0.7f);
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
public static ButtonRef CreateButton(GameObject parent, string name, string text, ColorBlock colors)
|
||||
{
|
||||
GameObject buttonObj = CreateUIObject(name, parent, _smallElementSize);
|
||||
|
||||
var textObj = CreateUIObject("Text", buttonObj);
|
||||
|
||||
Image image = buttonObj.AddComponent<Image>();
|
||||
image.type = Image.Type.Sliced;
|
||||
image.color = new Color(1, 1, 1, 1);
|
||||
|
||||
var button = buttonObj.AddComponent<Button>();
|
||||
SetDefaultSelectableColors(button);
|
||||
|
||||
colors.colorMultiplier = 1;
|
||||
RuntimeProvider.Instance.SetColorBlock(button, colors);
|
||||
|
||||
Text textComp = textObj.AddComponent<Text>();
|
||||
textComp.text = text;
|
||||
SetDefaultTextValues(textComp);
|
||||
textComp.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
RectTransform rect = textObj.GetComponent<RectTransform>();
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.sizeDelta = Vector2.zero;
|
||||
|
||||
SetButtonDeselectListener(button);
|
||||
|
||||
return new ButtonRef(button);
|
||||
}
|
||||
|
||||
public static void SetButtonDeselectListener(Button button)
|
||||
{
|
||||
button.onClick.AddListener(() =>
|
||||
{
|
||||
button.OnDeselect(null);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a Slider control.
|
||||
/// </summary>
|
||||
public static GameObject CreateSlider(GameObject parent, string name, out Slider slider)
|
||||
{
|
||||
GameObject sliderObj = CreateUIObject(name, parent, _smallElementSize);
|
||||
|
||||
GameObject bgObj = CreateUIObject("Background", sliderObj);
|
||||
GameObject fillAreaObj = CreateUIObject("Fill Area", sliderObj);
|
||||
GameObject fillObj = CreateUIObject("Fill", fillAreaObj);
|
||||
GameObject handleSlideAreaObj = CreateUIObject("Handle Slide Area", sliderObj);
|
||||
GameObject handleObj = CreateUIObject("Handle", handleSlideAreaObj);
|
||||
|
||||
Image bgImage = bgObj.AddComponent<Image>();
|
||||
bgImage.type = Image.Type.Sliced;
|
||||
bgImage.color = new Color(0.15f, 0.15f, 0.15f, 1.0f);
|
||||
|
||||
RectTransform bgRect = bgObj.GetComponent<RectTransform>();
|
||||
bgRect.anchorMin = new Vector2(0f, 0.25f);
|
||||
bgRect.anchorMax = new Vector2(1f, 0.75f);
|
||||
bgRect.sizeDelta = new Vector2(0f, 0f);
|
||||
|
||||
RectTransform fillAreaRect = fillAreaObj.GetComponent<RectTransform>();
|
||||
fillAreaRect.anchorMin = new Vector2(0f, 0.25f);
|
||||
fillAreaRect.anchorMax = new Vector2(1f, 0.75f);
|
||||
fillAreaRect.anchoredPosition = new Vector2(-5f, 0f);
|
||||
fillAreaRect.sizeDelta = new Vector2(-20f, 0f);
|
||||
|
||||
Image fillImage = fillObj.AddComponent<Image>();
|
||||
fillImage.type = Image.Type.Sliced;
|
||||
fillImage.color = new Color(0.3f, 0.3f, 0.3f, 1.0f);
|
||||
|
||||
fillObj.GetComponent<RectTransform>().sizeDelta = new Vector2(10f, 0f);
|
||||
|
||||
RectTransform handleSlideRect = handleSlideAreaObj.GetComponent<RectTransform>();
|
||||
handleSlideRect.sizeDelta = new Vector2(-20f, 0f);
|
||||
handleSlideRect.anchorMin = new Vector2(0f, 0f);
|
||||
handleSlideRect.anchorMax = new Vector2(1f, 1f);
|
||||
|
||||
Image handleImage = handleObj.AddComponent<Image>();
|
||||
handleImage.color = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
|
||||
handleObj.GetComponent<RectTransform>().sizeDelta = new Vector2(20f, 0f);
|
||||
|
||||
slider = sliderObj.AddComponent<Slider>();
|
||||
slider.fillRect = fillObj.GetComponent<RectTransform>();
|
||||
slider.handleRect = handleObj.GetComponent<RectTransform>();
|
||||
slider.targetGraphic = handleImage;
|
||||
slider.direction = Slider.Direction.LeftToRight;
|
||||
|
||||
RuntimeProvider.Instance.SetColorBlock(slider, new Color(0.4f, 0.4f, 0.4f),
|
||||
new Color(0.55f, 0.55f, 0.55f), new Color(0.3f, 0.3f, 0.3f));
|
||||
|
||||
return sliderObj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a Scrollbar control.
|
||||
/// </summary>
|
||||
public static GameObject CreateScrollbar(GameObject parent, string name, out Scrollbar scrollbar)
|
||||
{
|
||||
GameObject scrollObj = CreateUIObject(name, parent, _smallElementSize);
|
||||
|
||||
GameObject slideAreaObj = CreateUIObject("Sliding Area", scrollObj);
|
||||
GameObject handleObj = CreateUIObject("Handle", slideAreaObj);
|
||||
|
||||
Image scrollImage = scrollObj.AddComponent<Image>();
|
||||
scrollImage.type = Image.Type.Sliced;
|
||||
scrollImage.color = new Color(0.1f, 0.1f, 0.1f);
|
||||
|
||||
Image handleImage = handleObj.AddComponent<Image>();
|
||||
handleImage.type = Image.Type.Sliced;
|
||||
handleImage.color = new Color(0.4f, 0.4f, 0.4f);
|
||||
|
||||
RectTransform slideAreaRect = slideAreaObj.GetComponent<RectTransform>();
|
||||
slideAreaRect.sizeDelta = new Vector2(-20f, -20f);
|
||||
slideAreaRect.anchorMin = Vector2.zero;
|
||||
slideAreaRect.anchorMax = Vector2.one;
|
||||
|
||||
RectTransform handleRect = handleObj.GetComponent<RectTransform>();
|
||||
handleRect.sizeDelta = new Vector2(20f, 20f);
|
||||
|
||||
scrollbar = scrollObj.AddComponent<Scrollbar>();
|
||||
scrollbar.handleRect = handleRect;
|
||||
scrollbar.targetGraphic = handleImage;
|
||||
|
||||
SetDefaultSelectableColors(scrollbar);
|
||||
|
||||
return scrollObj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a Toggle control.
|
||||
/// </summary>
|
||||
public static GameObject CreateToggle(GameObject parent, string name, out Toggle toggle, out Text text, Color bgColor = default,
|
||||
int checkWidth = 20, int checkHeight = 20)
|
||||
{
|
||||
// Main obj
|
||||
GameObject toggleObj = CreateUIObject(name, parent, _smallElementSize);
|
||||
SetLayoutGroup<HorizontalLayoutGroup>(toggleObj, false, false, true, true, 5, 0,0,0,0, childAlignment: TextAnchor.MiddleLeft);
|
||||
toggle = toggleObj.AddComponent<Toggle>();
|
||||
toggle.isOn = true;
|
||||
SetDefaultSelectableColors(toggle);
|
||||
// need a second reference so we can use it inside the lambda, since 'toggle' is an out var.
|
||||
Toggle t2 = toggle;
|
||||
toggle.onValueChanged.AddListener((bool _) => { t2.OnDeselect(null); });
|
||||
|
||||
// Check mark background
|
||||
|
||||
GameObject checkBgObj = CreateUIObject("Background", toggleObj);
|
||||
Image bgImage = checkBgObj.AddComponent<Image>();
|
||||
bgImage.color = bgColor == default ? new Color(0.04f, 0.04f, 0.04f, 0.75f) : bgColor;
|
||||
|
||||
SetLayoutGroup<HorizontalLayoutGroup>(checkBgObj, true, true, true, true, 0, 2, 2, 2, 2);
|
||||
SetLayoutElement(checkBgObj, minWidth: checkWidth, flexibleWidth: 0, minHeight: checkHeight, flexibleHeight: 0);
|
||||
|
||||
// Check mark image
|
||||
|
||||
GameObject checkMarkObj = CreateUIObject("Checkmark", checkBgObj);
|
||||
Image checkImage = checkMarkObj.AddComponent<Image>();
|
||||
checkImage.color = new Color(0.8f, 1, 0.8f, 0.3f);
|
||||
|
||||
// Label
|
||||
|
||||
GameObject labelObj = CreateUIObject("Label", toggleObj);
|
||||
text = labelObj.AddComponent<Text>();
|
||||
text.text = "";
|
||||
text.alignment = TextAnchor.MiddleLeft;
|
||||
SetDefaultTextValues(text);
|
||||
|
||||
SetLayoutElement(labelObj, minWidth: 0, flexibleWidth: 0, minHeight: checkHeight, flexibleHeight: 0);
|
||||
|
||||
// References
|
||||
|
||||
toggle.graphic = checkImage;
|
||||
toggle.targetGraphic = bgImage;
|
||||
|
||||
return toggleObj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a standard InputField control.
|
||||
/// </summary>
|
||||
public static InputFieldRef CreateInputField(GameObject parent, string name, string placeHolderText)
|
||||
{
|
||||
GameObject mainObj = CreateUIObject(name, parent);
|
||||
|
||||
Image mainImage = mainObj.AddComponent<Image>();
|
||||
mainImage.type = Image.Type.Sliced;
|
||||
mainImage.color = new Color(0, 0, 0, 0.5f);
|
||||
|
||||
var inputField = mainObj.AddComponent<InputField>();
|
||||
Navigation nav = inputField.navigation;
|
||||
nav.mode = Navigation.Mode.None;
|
||||
inputField.navigation = nav;
|
||||
inputField.lineType = InputField.LineType.SingleLine;
|
||||
inputField.interactable = true;
|
||||
inputField.transition = Selectable.Transition.ColorTint;
|
||||
inputField.targetGraphic = mainImage;
|
||||
|
||||
RuntimeProvider.Instance.SetColorBlock(inputField, new Color(1, 1, 1, 1),
|
||||
new Color(0.95f, 0.95f, 0.95f, 1.0f), new Color(0.78f, 0.78f, 0.78f, 1.0f));
|
||||
|
||||
GameObject textArea = CreateUIObject("TextArea", mainObj);
|
||||
textArea.AddComponent<RectMask2D>();
|
||||
|
||||
RectTransform textAreaRect = textArea.GetComponent<RectTransform>();
|
||||
textAreaRect.anchorMin = Vector2.zero;
|
||||
textAreaRect.anchorMax = Vector2.one;
|
||||
textAreaRect.offsetMin = Vector2.zero;
|
||||
textAreaRect.offsetMax = Vector2.zero;
|
||||
|
||||
GameObject placeHolderObj = CreateUIObject("Placeholder", textArea);
|
||||
Text placeholderText = placeHolderObj.AddComponent<Text>();
|
||||
SetDefaultTextValues(placeholderText);
|
||||
placeholderText.text = placeHolderText ?? "...";
|
||||
placeholderText.color = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
placeholderText.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
placeholderText.alignment = TextAnchor.MiddleLeft;
|
||||
placeholderText.fontSize = 14;
|
||||
|
||||
RectTransform placeHolderRect = placeHolderObj.GetComponent<RectTransform>();
|
||||
placeHolderRect.anchorMin = Vector2.zero;
|
||||
placeHolderRect.anchorMax = Vector2.one;
|
||||
placeHolderRect.offsetMin = Vector2.zero;
|
||||
placeHolderRect.offsetMax = Vector2.zero;
|
||||
|
||||
inputField.placeholder = placeholderText;
|
||||
|
||||
GameObject inputTextObj = CreateUIObject("Text", textArea);
|
||||
Text inputText = inputTextObj.AddComponent<Text>();
|
||||
SetDefaultTextValues(inputText);
|
||||
inputText.text = "";
|
||||
inputText.color = new Color(1f, 1f, 1f, 1f);
|
||||
inputText.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
inputText.alignment = TextAnchor.MiddleLeft;
|
||||
inputText.fontSize = 14;
|
||||
|
||||
RectTransform inputTextRect = inputTextObj.GetComponent<RectTransform>();
|
||||
inputTextRect.anchorMin = Vector2.zero;
|
||||
inputTextRect.anchorMax = Vector2.one;
|
||||
inputTextRect.offsetMin = Vector2.zero;
|
||||
inputTextRect.offsetMax = Vector2.zero;
|
||||
|
||||
inputField.textComponent = inputText;
|
||||
inputField.characterLimit = UIManager.MAX_INPUTFIELD_CHARS;
|
||||
|
||||
return new InputFieldRef(inputField);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a DropDown control.
|
||||
/// </summary>
|
||||
public static GameObject CreateDropdown(GameObject parent, out Dropdown dropdown, string defaultItemText, int itemFontSize,
|
||||
Action<int> onValueChanged, string[] defaultOptions = null)
|
||||
{
|
||||
GameObject dropdownObj = CreateUIObject("Dropdown", parent, _largeElementSize);
|
||||
|
||||
GameObject labelObj = CreateUIObject("Label", dropdownObj);
|
||||
GameObject arrowObj = CreateUIObject("Arrow", dropdownObj);
|
||||
GameObject templateObj = CreateUIObject("Template", dropdownObj);
|
||||
GameObject viewportObj = CreateUIObject("Viewport", templateObj);
|
||||
GameObject contentObj = CreateUIObject("Content", viewportObj);
|
||||
GameObject itemObj = CreateUIObject("Item", contentObj);
|
||||
GameObject itemBgObj = CreateUIObject("Item Background", itemObj);
|
||||
GameObject itemCheckObj = CreateUIObject("Item Checkmark", itemObj);
|
||||
GameObject itemLabelObj = CreateUIObject("Item Label", itemObj);
|
||||
|
||||
GameObject scrollbarObj = CreateScrollbar(templateObj, "DropdownScroll", out Scrollbar scrollbar);
|
||||
scrollbar.SetDirection(Scrollbar.Direction.BottomToTop, true);
|
||||
RuntimeProvider.Instance.SetColorBlock(scrollbar, new Color(0.45f, 0.45f, 0.45f), new Color(0.6f, 0.6f, 0.6f), new Color(0.4f, 0.4f, 0.4f));
|
||||
|
||||
RectTransform scrollRectTransform = scrollbarObj.GetComponent<RectTransform>();
|
||||
scrollRectTransform.anchorMin = Vector2.right;
|
||||
scrollRectTransform.anchorMax = Vector2.one;
|
||||
scrollRectTransform.pivot = Vector2.one;
|
||||
scrollRectTransform.sizeDelta = new Vector2(scrollRectTransform.sizeDelta.x, 0f);
|
||||
|
||||
Text itemLabelText = itemLabelObj.AddComponent<Text>();
|
||||
SetDefaultTextValues(itemLabelText);
|
||||
itemLabelText.alignment = TextAnchor.MiddleLeft;
|
||||
itemLabelText.text = defaultItemText;
|
||||
itemLabelText.fontSize = itemFontSize;
|
||||
|
||||
var arrowText = arrowObj.AddComponent<Text>();
|
||||
SetDefaultTextValues(arrowText);
|
||||
arrowText.text = "▼";
|
||||
var arrowRect = arrowObj.GetComponent<RectTransform>();
|
||||
arrowRect.anchorMin = new Vector2(1f, 0.5f);
|
||||
arrowRect.anchorMax = new Vector2(1f, 0.5f);
|
||||
arrowRect.sizeDelta = new Vector2(20f, 20f);
|
||||
arrowRect.anchoredPosition = new Vector2(-15f, 0f);
|
||||
|
||||
Image itemBgImage = itemBgObj.AddComponent<Image>();
|
||||
itemBgImage.color = new Color(0.25f, 0.35f, 0.25f, 1.0f);
|
||||
|
||||
Toggle itemToggle = itemObj.AddComponent<Toggle>();
|
||||
itemToggle.targetGraphic = itemBgImage;
|
||||
itemToggle.isOn = true;
|
||||
RuntimeProvider.Instance.SetColorBlock(itemToggle,
|
||||
new Color(0.35f, 0.35f, 0.35f, 1.0f), new Color(0.25f, 0.55f, 0.25f, 1.0f));
|
||||
|
||||
itemToggle.onValueChanged.AddListener((bool val) => { itemToggle.OnDeselect(null); });
|
||||
Image templateImage = templateObj.AddComponent<Image>();
|
||||
templateImage.type = Image.Type.Sliced;
|
||||
templateImage.color = Color.black;
|
||||
|
||||
var scrollRect = templateObj.AddComponent<ScrollRect>();
|
||||
scrollRect.scrollSensitivity = 35;
|
||||
scrollRect.content = contentObj.GetComponent<RectTransform>();
|
||||
scrollRect.viewport = viewportObj.GetComponent<RectTransform>();
|
||||
scrollRect.horizontal = false;
|
||||
scrollRect.movementType = ScrollRect.MovementType.Clamped;
|
||||
scrollRect.verticalScrollbar = scrollbar;
|
||||
scrollRect.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHideAndExpandViewport;
|
||||
scrollRect.verticalScrollbarSpacing = -3f;
|
||||
|
||||
viewportObj.AddComponent<Mask>().showMaskGraphic = false;
|
||||
|
||||
Image viewportImage = viewportObj.AddComponent<Image>();
|
||||
viewportImage.type = Image.Type.Sliced;
|
||||
|
||||
Text labelText = labelObj.AddComponent<Text>();
|
||||
SetDefaultTextValues(labelText);
|
||||
labelText.alignment = TextAnchor.MiddleLeft;
|
||||
|
||||
Image dropdownImage = dropdownObj.AddComponent<Image>();
|
||||
dropdownImage.color = new Color(0.04f, 0.04f, 0.04f, 0.75f);
|
||||
dropdownImage.type = Image.Type.Sliced;
|
||||
|
||||
dropdown = dropdownObj.AddComponent<Dropdown>();
|
||||
dropdown.targetGraphic = dropdownImage;
|
||||
dropdown.template = templateObj.GetComponent<RectTransform>();
|
||||
dropdown.captionText = labelText;
|
||||
dropdown.itemText = itemLabelText;
|
||||
//itemLabelText.text = "DEFAULT";
|
||||
|
||||
dropdown.RefreshShownValue();
|
||||
|
||||
RectTransform labelRect = labelObj.GetComponent<RectTransform>();
|
||||
labelRect.anchorMin = Vector2.zero;
|
||||
labelRect.anchorMax = Vector2.one;
|
||||
labelRect.offsetMin = new Vector2(10f, 2f);
|
||||
labelRect.offsetMax = new Vector2(-28f, -2f);
|
||||
|
||||
RectTransform templateRect = templateObj.GetComponent<RectTransform>();
|
||||
templateRect.anchorMin = new Vector2(0f, 0f);
|
||||
templateRect.anchorMax = new Vector2(1f, 0f);
|
||||
templateRect.pivot = new Vector2(0.5f, 1f);
|
||||
templateRect.anchoredPosition = new Vector2(0f, 2f);
|
||||
templateRect.sizeDelta = new Vector2(0f, 150f);
|
||||
|
||||
RectTransform viewportRect = viewportObj.GetComponent<RectTransform>();
|
||||
viewportRect.anchorMin = new Vector2(0f, 0f);
|
||||
viewportRect.anchorMax = new Vector2(1f, 1f);
|
||||
viewportRect.sizeDelta = new Vector2(-18f, 0f);
|
||||
viewportRect.pivot = new Vector2(0f, 1f);
|
||||
|
||||
RectTransform contentRect = contentObj.GetComponent<RectTransform>();
|
||||
contentRect.anchorMin = new Vector2(0f, 1f);
|
||||
contentRect.anchorMax = new Vector2(1f, 1f);
|
||||
contentRect.pivot = new Vector2(0.5f, 1f);
|
||||
contentRect.anchoredPosition = new Vector2(0f, 0f);
|
||||
contentRect.sizeDelta = new Vector2(0f, 28f);
|
||||
|
||||
RectTransform itemRect = itemObj.GetComponent<RectTransform>();
|
||||
itemRect.anchorMin = new Vector2(0f, 0.5f);
|
||||
itemRect.anchorMax = new Vector2(1f, 0.5f);
|
||||
itemRect.sizeDelta = new Vector2(0f, 25f);
|
||||
|
||||
RectTransform itemBgRect = itemBgObj.GetComponent<RectTransform>();
|
||||
itemBgRect.anchorMin = Vector2.zero;
|
||||
itemBgRect.anchorMax = Vector2.one;
|
||||
itemBgRect.sizeDelta = Vector2.zero;
|
||||
|
||||
RectTransform itemLabelRect = itemLabelObj.GetComponent<RectTransform>();
|
||||
itemLabelRect.anchorMin = Vector2.zero;
|
||||
itemLabelRect.anchorMax = Vector2.one;
|
||||
itemLabelRect.offsetMin = new Vector2(20f, 1f);
|
||||
itemLabelRect.offsetMax = new Vector2(-10f, -2f);
|
||||
templateObj.SetActive(false);
|
||||
|
||||
if (onValueChanged != null)
|
||||
dropdown.onValueChanged.AddListener(onValueChanged);
|
||||
|
||||
if (defaultOptions != null)
|
||||
{
|
||||
foreach (var option in defaultOptions)
|
||||
dropdown.options.Add(new Dropdown.OptionData(option));
|
||||
}
|
||||
|
||||
return dropdownObj;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Custom Scroll Components
|
||||
|
||||
/// <summary>
|
||||
/// Create a ScrollPool for the <typeparamref name="T"/> ICell.
|
||||
/// </summary>
|
||||
public static ScrollPool<T> CreateScrollPool<T>(GameObject parent, string name, out GameObject uiRoot,
|
||||
out GameObject content, Color? bgColor = null) where T : ICell
|
||||
{
|
||||
var mainObj = CreateUIObject(name, parent, new Vector2(1, 1));
|
||||
mainObj.AddComponent<Image>().color = bgColor ?? new Color(0.12f, 0.12f, 0.12f);
|
||||
SetLayoutGroup<HorizontalLayoutGroup>(mainObj, false, true, true, true);
|
||||
|
||||
GameObject viewportObj = CreateUIObject("Viewport", mainObj);
|
||||
SetLayoutElement(viewportObj, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
var viewportRect = viewportObj.GetComponent<RectTransform>();
|
||||
viewportRect.anchorMin = Vector2.zero;
|
||||
viewportRect.anchorMax = Vector2.one;
|
||||
viewportRect.pivot = new Vector2(0.0f, 1.0f);
|
||||
viewportRect.sizeDelta = new Vector2(0f, 0.0f);
|
||||
viewportRect.offsetMax = new Vector2(-10.0f, 0.0f);
|
||||
viewportObj.AddComponent<Image>().color = Color.white;
|
||||
viewportObj.AddComponent<Mask>().showMaskGraphic = false;
|
||||
|
||||
content = CreateUIObject("Content", viewportObj);
|
||||
var contentRect = content.GetComponent<RectTransform>();
|
||||
contentRect.anchorMin = Vector2.zero;
|
||||
contentRect.anchorMax = Vector2.one;
|
||||
contentRect.pivot = new Vector2(0.5f, 1f);
|
||||
contentRect.sizeDelta = new Vector2(0f, 0f);
|
||||
contentRect.offsetMax = new Vector2(0f, 0f);
|
||||
SetLayoutGroup<VerticalLayoutGroup>(content, true, false, true, true, 0, 2, 2, 2, 2, TextAnchor.UpperCenter);
|
||||
content.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
var scrollRect = mainObj.AddComponent<ScrollRect>();
|
||||
scrollRect.movementType = ScrollRect.MovementType.Clamped;
|
||||
//scrollRect.inertia = false;
|
||||
scrollRect.inertia = true;
|
||||
scrollRect.elasticity = 0.125f;
|
||||
scrollRect.scrollSensitivity = 25;
|
||||
scrollRect.horizontal = false;
|
||||
scrollRect.vertical = true;
|
||||
|
||||
scrollRect.viewport = viewportRect;
|
||||
scrollRect.content = contentRect;
|
||||
|
||||
// Slider
|
||||
|
||||
var sliderContainer = CreateVerticalGroup(mainObj, "SliderContainer",
|
||||
false, false, true, true, 0, default, new Color(0.05f, 0.05f, 0.05f));
|
||||
SetLayoutElement(sliderContainer, minWidth: 25, flexibleWidth: 0, flexibleHeight: 9999);
|
||||
sliderContainer.AddComponent<Mask>();
|
||||
|
||||
CreateSliderScrollbar(sliderContainer, out Slider slider);
|
||||
|
||||
RuntimeProvider.Instance.SetColorBlock(slider, disabled: new Color(0.1f, 0.1f, 0.1f));
|
||||
|
||||
// finalize and create ScrollPool
|
||||
|
||||
uiRoot = mainObj;
|
||||
var scrollPool = new ScrollPool<T>(scrollRect);
|
||||
|
||||
return scrollPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a SliderScrollbar, using a Slider to mimic a scrollbar.
|
||||
/// </summary>
|
||||
public static GameObject CreateSliderScrollbar(GameObject parent, out Slider slider)
|
||||
{
|
||||
GameObject mainObj = CreateUIObject("SliderScrollbar", parent, _smallElementSize);
|
||||
mainObj.AddComponent<Mask>();
|
||||
mainObj.AddComponent<Image>().color = Color.white;
|
||||
|
||||
GameObject bgImageObj = CreateUIObject("Background", mainObj);
|
||||
GameObject handleSlideAreaObj = CreateUIObject("Handle Slide Area", mainObj);
|
||||
GameObject handleObj = CreateUIObject("Handle", handleSlideAreaObj);
|
||||
|
||||
Image bgImage = bgImageObj.AddComponent<Image>();
|
||||
bgImage.type = Image.Type.Sliced;
|
||||
bgImage.color = new Color(0.05f, 0.05f, 0.05f, 1.0f);
|
||||
|
||||
bgImageObj.AddComponent<Mask>();
|
||||
|
||||
RectTransform bgRect = bgImageObj.GetComponent<RectTransform>();
|
||||
bgRect.pivot = new Vector2(0, 1);
|
||||
bgRect.anchorMin = Vector2.zero;
|
||||
bgRect.anchorMax = Vector2.one;
|
||||
bgRect.sizeDelta = Vector2.zero;
|
||||
bgRect.offsetMax = new Vector2(0f, 0f);
|
||||
|
||||
RectTransform handleSlideRect = handleSlideAreaObj.GetComponent<RectTransform>();
|
||||
handleSlideRect.anchorMin = Vector3.zero;
|
||||
handleSlideRect.anchorMax = Vector3.one;
|
||||
handleSlideRect.pivot = new Vector3(0.5f, 0.5f);
|
||||
|
||||
Image handleImage = handleObj.AddComponent<Image>();
|
||||
handleImage.color = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
|
||||
var handleRect = handleObj.GetComponent<RectTransform>();
|
||||
handleRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
SetLayoutElement(handleObj, minWidth: 21, flexibleWidth: 0);
|
||||
|
||||
var sliderBarLayout = mainObj.AddComponent<LayoutElement>();
|
||||
sliderBarLayout.minWidth = 25;
|
||||
sliderBarLayout.flexibleWidth = 0;
|
||||
sliderBarLayout.minHeight = 30;
|
||||
sliderBarLayout.flexibleHeight = 9999;
|
||||
|
||||
slider = mainObj.AddComponent<Slider>();
|
||||
slider.handleRect = handleObj.GetComponent<RectTransform>();
|
||||
slider.targetGraphic = handleImage;
|
||||
slider.direction = Slider.Direction.TopToBottom;
|
||||
|
||||
SetLayoutElement(mainObj, minWidth: 25, flexibleWidth: 0, flexibleHeight: 9999);
|
||||
|
||||
RuntimeProvider.Instance.SetColorBlock(slider,
|
||||
new Color(0.4f, 0.4f, 0.4f),
|
||||
new Color(0.5f, 0.5f, 0.5f),
|
||||
new Color(0.3f, 0.3f, 0.3f),
|
||||
new Color(0.5f, 0.5f, 0.5f));
|
||||
|
||||
return mainObj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a ScrollView and a SliderScrollbar for non-pooled content.
|
||||
/// </summary>
|
||||
public static GameObject CreateScrollView(GameObject parent, string name, out GameObject content, out AutoSliderScrollbar autoScrollbar,
|
||||
Color color = default)
|
||||
{
|
||||
GameObject mainObj = CreateUIObject(name, parent);
|
||||
var mainRect = mainObj.GetComponent<RectTransform>();
|
||||
mainRect.anchorMin = Vector2.zero;
|
||||
mainRect.anchorMax = Vector2.one;
|
||||
Image mainImage = mainObj.AddComponent<Image>();
|
||||
mainImage.type = Image.Type.Filled;
|
||||
mainImage.color = (color == default) ? new Color(0.3f, 0.3f, 0.3f, 1f) : color;
|
||||
|
||||
GameObject viewportObj = CreateUIObject("Viewport", mainObj);
|
||||
var viewportRect = viewportObj.GetComponent<RectTransform>();
|
||||
viewportRect.anchorMin = Vector2.zero;
|
||||
viewportRect.anchorMax = Vector2.one;
|
||||
viewportRect.pivot = new Vector2(0.0f, 1.0f);
|
||||
viewportRect.offsetMax = new Vector2(-28, 0);
|
||||
viewportObj.AddComponent<Image>().color = Color.white;
|
||||
viewportObj.AddComponent<Mask>().showMaskGraphic = false;
|
||||
|
||||
content = CreateUIObject("Content", viewportObj);
|
||||
SetLayoutGroup<VerticalLayoutGroup>(content, true, false, true, true, childAlignment: TextAnchor.UpperLeft);
|
||||
SetLayoutElement(content, flexibleHeight: 9999);
|
||||
var contentRect = content.GetComponent<RectTransform>();
|
||||
contentRect.anchorMin = Vector2.zero;
|
||||
contentRect.anchorMax = Vector2.one;
|
||||
contentRect.pivot = new Vector2(0.0f, 1.0f);
|
||||
content.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
// Slider
|
||||
|
||||
GameObject scrollBarObj = CreateUIObject("AutoSliderScrollbar", mainObj);
|
||||
var scrollBarRect = scrollBarObj.GetComponent<RectTransform>();
|
||||
scrollBarRect.anchorMin = new Vector2(1, 0);
|
||||
scrollBarRect.anchorMax = Vector2.one;
|
||||
scrollBarRect.offsetMin = new Vector2(-25, 0);
|
||||
SetLayoutGroup<VerticalLayoutGroup>(scrollBarObj, false, true, true, true);
|
||||
scrollBarObj.AddComponent<Image>().color = Color.white;
|
||||
scrollBarObj.AddComponent<Mask>().showMaskGraphic = false;
|
||||
|
||||
GameObject hiddenBar = CreateScrollbar(scrollBarObj, "HiddenScrollviewScroller", out var hiddenScrollbar);
|
||||
hiddenScrollbar.SetDirection(Scrollbar.Direction.BottomToTop, true);
|
||||
|
||||
for (int i = 0; i < hiddenBar.transform.childCount; i++)
|
||||
{
|
||||
var child = hiddenBar.transform.GetChild(i);
|
||||
child.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
CreateSliderScrollbar(scrollBarObj, out Slider scrollSlider);
|
||||
|
||||
autoScrollbar = new AutoSliderScrollbar(hiddenScrollbar, scrollSlider, contentRect, viewportRect);
|
||||
|
||||
// Set up the ScrollRect component
|
||||
|
||||
var scrollRect = mainObj.AddComponent<ScrollRect>();
|
||||
scrollRect.horizontal = false;
|
||||
scrollRect.vertical = true;
|
||||
scrollRect.verticalScrollbar = hiddenScrollbar;
|
||||
scrollRect.movementType = ScrollRect.MovementType.Clamped;
|
||||
scrollRect.scrollSensitivity = 35;
|
||||
scrollRect.horizontalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHideAndExpandViewport;
|
||||
scrollRect.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.Permanent;
|
||||
|
||||
scrollRect.viewport = viewportRect;
|
||||
scrollRect.content = contentRect;
|
||||
|
||||
return mainObj;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a Scrollable Input Field control
|
||||
/// </summary>
|
||||
public static GameObject CreateScrollInputField(GameObject parent, string name, string placeHolderText, out InputFieldScroller inputScroll,
|
||||
int fontSize = 14, Color color = default)
|
||||
{
|
||||
if (color == default)
|
||||
color = new Color(0.12f, 0.12f, 0.12f);
|
||||
|
||||
GameObject mainObj = CreateUIObject(name, parent);
|
||||
SetLayoutElement(mainObj, minWidth: 100, minHeight: 30, flexibleWidth: 5000, flexibleHeight: 5000);
|
||||
SetLayoutGroup<HorizontalLayoutGroup>(mainObj, false, true, true, true, 2);
|
||||
Image mainImage = mainObj.AddComponent<Image>();
|
||||
mainImage.type = Image.Type.Filled;
|
||||
mainImage.color = (color == default) ? new Color(0.3f, 0.3f, 0.3f, 1f) : color;
|
||||
|
||||
GameObject viewportObj = CreateUIObject("Viewport", mainObj);
|
||||
SetLayoutElement(viewportObj, minWidth: 1, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
var viewportRect = viewportObj.GetComponent<RectTransform>();
|
||||
viewportRect.anchorMin = Vector2.zero;
|
||||
viewportRect.anchorMax = Vector2.one;
|
||||
viewportRect.pivot = new Vector2(0.0f, 1.0f);
|
||||
viewportObj.AddComponent<Image>().color = Color.white;
|
||||
viewportObj.AddComponent<Mask>().showMaskGraphic = false;
|
||||
|
||||
// Input Field
|
||||
|
||||
var inputField = CreateInputField(viewportObj, "InputField", placeHolderText);
|
||||
var content = inputField.UIRoot;
|
||||
var textComp = inputField.Component.textComponent;
|
||||
textComp.alignment = TextAnchor.UpperLeft;
|
||||
textComp.fontSize = fontSize;
|
||||
textComp.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
inputField.Component.lineType = InputField.LineType.MultiLineNewline;
|
||||
inputField.Component.targetGraphic.color = color;
|
||||
inputField.PlaceholderText.alignment = TextAnchor.UpperLeft;
|
||||
inputField.PlaceholderText.fontSize = fontSize;
|
||||
inputField.PlaceholderText.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
|
||||
//var content = CreateInputField(viewportObj, name, placeHolderText ?? "...", out InputField inputField, fontSize, 0);
|
||||
SetLayoutElement(content, flexibleHeight: 9999, flexibleWidth: 9999);
|
||||
var contentRect = content.GetComponent<RectTransform>();
|
||||
contentRect.pivot = new Vector2(0, 1);
|
||||
contentRect.anchorMin = new Vector2(0, 1);
|
||||
contentRect.anchorMax = new Vector2(1, 1);
|
||||
contentRect.offsetMin = new Vector2(2, 0);
|
||||
contentRect.offsetMax = new Vector2(2, 0);
|
||||
inputField.Component.lineType = InputField.LineType.MultiLineNewline;
|
||||
inputField.Component.targetGraphic.color = color;
|
||||
|
||||
// Slider
|
||||
|
||||
GameObject scrollBarObj = CreateUIObject("AutoSliderScrollbar", mainObj);
|
||||
SetLayoutGroup<VerticalLayoutGroup>(scrollBarObj, true, true, true, true);
|
||||
SetLayoutElement(scrollBarObj, minWidth: 25, flexibleWidth: 0, flexibleHeight: 9999);
|
||||
scrollBarObj.AddComponent<Image>().color = Color.white;
|
||||
scrollBarObj.AddComponent<Mask>().showMaskGraphic = false;
|
||||
|
||||
GameObject hiddenBar = CreateScrollbar(scrollBarObj, "HiddenScrollviewScroller", out var hiddenScrollbar);
|
||||
hiddenScrollbar.SetDirection(Scrollbar.Direction.BottomToTop, true);
|
||||
|
||||
for (int i = 0; i < hiddenBar.transform.childCount; i++)
|
||||
{
|
||||
var child = hiddenBar.transform.GetChild(i);
|
||||
child.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
CreateSliderScrollbar(scrollBarObj, out Slider scrollSlider);
|
||||
|
||||
// Set up the AutoSliderScrollbar module
|
||||
|
||||
var autoScroller = new AutoSliderScrollbar(hiddenScrollbar, scrollSlider, contentRect, viewportRect);
|
||||
|
||||
var sliderContainer = autoScroller.Slider.m_HandleContainerRect.gameObject;
|
||||
SetLayoutElement(sliderContainer, minWidth: 25, flexibleWidth: 0, flexibleHeight: 9999);
|
||||
sliderContainer.AddComponent<Mask>();
|
||||
|
||||
// Set up the InputFieldScroller module
|
||||
|
||||
inputScroll = new InputFieldScroller(autoScroller, inputField);
|
||||
inputScroll.ProcessInputText();
|
||||
|
||||
// Set up the ScrollRect component
|
||||
|
||||
var scrollRect = mainObj.AddComponent<ScrollRect>();
|
||||
scrollRect.horizontal = false;
|
||||
scrollRect.vertical = true;
|
||||
scrollRect.verticalScrollbar = hiddenScrollbar;
|
||||
scrollRect.movementType = ScrollRect.MovementType.Clamped;
|
||||
scrollRect.scrollSensitivity = 35;
|
||||
scrollRect.horizontalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHideAndExpandViewport;
|
||||
scrollRect.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.Permanent;
|
||||
|
||||
scrollRect.viewport = viewportRect;
|
||||
scrollRect.content = contentRect;
|
||||
|
||||
|
||||
return mainObj;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -10,13 +10,15 @@ using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.CSConsole;
|
||||
using UnityExplorer.Inspectors;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UnityExplorer.UI.Widgets.AutoComplete;
|
||||
using UniverseLib;
|
||||
using UniverseLib.Input;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.UI
|
||||
{
|
||||
@ -41,22 +43,16 @@ namespace UnityExplorer.UI
|
||||
Bottom
|
||||
}
|
||||
|
||||
public static bool Initializing { get; internal set; } = true;
|
||||
|
||||
public static VerticalAnchor NavbarAnchor = VerticalAnchor.Top;
|
||||
|
||||
public static GameObject CanvasRoot { get; private set; }
|
||||
public static Canvas Canvas { get; private set; }
|
||||
public static EventSystem EventSys { get; private set; }
|
||||
public static bool Initializing { get; internal set; } = true;
|
||||
|
||||
private static UIBase uiBase;
|
||||
public static GameObject UIRoot => uiBase?.RootObject;
|
||||
|
||||
internal static GameObject PoolHolder { get; private set; }
|
||||
internal static GameObject PanelHolder { get; private set; }
|
||||
private static readonly Dictionary<Panels, UIPanel> UIPanels = new Dictionary<Panels, UIPanel>();
|
||||
|
||||
internal static Font ConsoleFont { get; private set; }
|
||||
internal static Font DefaultFont { get; private set; }
|
||||
internal static Shader BackupShader { get; private set; }
|
||||
|
||||
public static RectTransform NavBarRect;
|
||||
public static GameObject NavbarTabButtonHolder;
|
||||
public static Dropdown MouseInspectDropdown;
|
||||
@ -67,40 +63,25 @@ namespace UnityExplorer.UI
|
||||
private static bool pauseButtonPausing;
|
||||
private static float lastTimeScale;
|
||||
|
||||
// defaults
|
||||
internal static readonly Color enabledButtonColor = new Color(0.2f, 0.4f, 0.28f);
|
||||
internal static readonly Color disabledButtonColor = new Color(0.25f, 0.25f, 0.25f);
|
||||
|
||||
public const int MAX_INPUTFIELD_CHARS = 16000;
|
||||
public const int MAX_TEXT_VERTS = 65000;
|
||||
|
||||
public static bool ShowMenu
|
||||
{
|
||||
get => s_showMenu;
|
||||
get => uiBase != null && uiBase.Enabled;
|
||||
set
|
||||
{
|
||||
if (s_showMenu == value || !CanvasRoot)
|
||||
if (uiBase == null || !UIRoot || uiBase.Enabled == value)
|
||||
return;
|
||||
|
||||
s_showMenu = value;
|
||||
CanvasRoot.SetActive(value);
|
||||
CursorUnlocker.UpdateCursorControl();
|
||||
UniversalUI.SetUIActive(ExplorerCore.GUID, value);
|
||||
}
|
||||
}
|
||||
public static bool s_showMenu = true;
|
||||
|
||||
// Initialization
|
||||
|
||||
internal static void InitUI()
|
||||
{
|
||||
LoadBundle();
|
||||
uiBase = UniversalUI.RegisterUI(ExplorerCore.GUID, Update);
|
||||
|
||||
CreateRootCanvas();
|
||||
|
||||
// Global UI Pool Holder
|
||||
PoolHolder = new GameObject("PoolHolder");
|
||||
PoolHolder.transform.parent = CanvasRoot.transform;
|
||||
PoolHolder.SetActive(false);
|
||||
CreatePanelHolder();
|
||||
|
||||
CreateTopNavBar();
|
||||
|
||||
@ -125,11 +106,13 @@ namespace UnityExplorer.UI
|
||||
lastScreenHeight = Screen.height;
|
||||
|
||||
// Failsafe fix
|
||||
foreach (var dropdown in CanvasRoot.GetComponentsInChildren<Dropdown>(true))
|
||||
foreach (var dropdown in UIRoot.GetComponentsInChildren<Dropdown>(true))
|
||||
dropdown.RefreshShownValue();
|
||||
timeInput.Text = string.Empty;
|
||||
timeInput.Text = Time.timeScale.ToString();
|
||||
|
||||
ScrollPool<ICell>.writingLockedListeners.Add(() => !PanelDragger.Resizing);
|
||||
|
||||
Initializing = false;
|
||||
}
|
||||
|
||||
@ -140,7 +123,7 @@ namespace UnityExplorer.UI
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
if (!CanvasRoot || Initializing)
|
||||
if (!UIRoot)
|
||||
return;
|
||||
|
||||
// if doing Mouse Inspect, update that and return.
|
||||
@ -150,28 +133,13 @@ namespace UnityExplorer.UI
|
||||
return;
|
||||
}
|
||||
|
||||
// check master toggle
|
||||
if (InputManager.GetKeyDown(ConfigManager.Master_Toggle.Value))
|
||||
ShowMenu = !ShowMenu;
|
||||
|
||||
// return if menu closed
|
||||
if (!ShowMenu)
|
||||
return;
|
||||
|
||||
// Check forceUnlockMouse toggle
|
||||
if (InputManager.GetKeyDown(ConfigManager.Force_Unlock_Toggle.Value))
|
||||
CursorUnlocker.Unlock = !CursorUnlocker.Unlock;
|
||||
|
||||
// check event system state
|
||||
if (!ConfigManager.Disable_EventSystem_Override.Value && EventSystem.current != EventSys)
|
||||
CursorUnlocker.SetEventSystem();
|
||||
UniverseLib.Config.ConfigManager.Force_Unlock_Mouse = !UniverseLib.Config.ConfigManager.Force_Unlock_Mouse;
|
||||
|
||||
// update focused panel
|
||||
UIPanel.UpdateFocus();
|
||||
// update UI model instances
|
||||
PanelDragger.UpdateInstances();
|
||||
InputFieldRef.UpdateInstances();
|
||||
UIBehaviourModel.UpdateInstances();
|
||||
|
||||
// update the timescale value
|
||||
if (!timeInput.Component.isFocused && lastTimeScale != Time.timeScale)
|
||||
@ -311,35 +279,10 @@ namespace UnityExplorer.UI
|
||||
|
||||
// UI Construction
|
||||
|
||||
private static void CreateRootCanvas()
|
||||
private static void CreatePanelHolder()
|
||||
{
|
||||
CanvasRoot = new GameObject("ExplorerCanvas");
|
||||
UnityEngine.Object.DontDestroyOnLoad(CanvasRoot);
|
||||
CanvasRoot.hideFlags |= HideFlags.HideAndDontSave;
|
||||
CanvasRoot.layer = 5;
|
||||
CanvasRoot.transform.position = new Vector3(0f, 0f, 1f);
|
||||
|
||||
CanvasRoot.SetActive(false);
|
||||
|
||||
EventSys = CanvasRoot.AddComponent<EventSystem>();
|
||||
InputManager.AddUIModule();
|
||||
|
||||
EventSys.enabled = false;
|
||||
CanvasRoot.SetActive(true);
|
||||
|
||||
Canvas = CanvasRoot.AddComponent<Canvas>();
|
||||
Canvas.renderMode = RenderMode.ScreenSpaceCamera;
|
||||
Canvas.referencePixelsPerUnit = 100;
|
||||
Canvas.sortingOrder = 999;
|
||||
|
||||
CanvasScaler scaler = CanvasRoot.AddComponent<CanvasScaler>();
|
||||
scaler.referenceResolution = new Vector2(1920, 1080);
|
||||
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.Expand;
|
||||
|
||||
CanvasRoot.AddComponent<GraphicRaycaster>();
|
||||
|
||||
PanelHolder = new GameObject("PanelHolder");
|
||||
PanelHolder.transform.SetParent(CanvasRoot.transform, false);
|
||||
PanelHolder.transform.SetParent(UIRoot.transform, false);
|
||||
PanelHolder.layer = 5;
|
||||
var rect = PanelHolder.AddComponent<RectTransform>();
|
||||
rect.sizeDelta = Vector2.zero;
|
||||
@ -352,7 +295,7 @@ namespace UnityExplorer.UI
|
||||
|
||||
private static void CreateTopNavBar()
|
||||
{
|
||||
var navbarPanel = UIFactory.CreateUIObject("MainNavbar", CanvasRoot);
|
||||
var navbarPanel = UIFactory.CreateUIObject("MainNavbar", UIRoot);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(navbarPanel, false, false, true, true, 5, 4, 4, 4, 4, TextAnchor.MiddleCenter);
|
||||
navbarPanel.AddComponent<Image>().color = new Color(0.1f, 0.1f, 0.1f);
|
||||
NavBarRect = navbarPanel.GetComponent<RectTransform>();
|
||||
@ -411,141 +354,5 @@ namespace UnityExplorer.UI
|
||||
ConfigManager.Master_Toggle.OnValueChanged += Master_Toggle_OnValueChanged;
|
||||
closeBtn.OnClick += OnCloseButtonClicked;
|
||||
}
|
||||
|
||||
// UI AssetBundle
|
||||
|
||||
internal static AssetBundle ExplorerBundle;
|
||||
|
||||
private static void LoadBundle()
|
||||
{
|
||||
SetupAssetBundlePatches();
|
||||
|
||||
try
|
||||
{
|
||||
// Get the Major and Minor of the Unity version
|
||||
var split = Application.unityVersion.Split('.');
|
||||
int major = int.Parse(split[0]);
|
||||
int minor = int.Parse(split[1]);
|
||||
|
||||
// Use appropriate AssetBundle for Unity version
|
||||
// >= 2017
|
||||
if (major >= 2017)
|
||||
ExplorerBundle = LoadBundle("modern");
|
||||
// 5.6.0 to <2017
|
||||
else if (major == 5 && minor >= 6)
|
||||
ExplorerBundle = LoadBundle("legacy.5.6");
|
||||
// < 5.6.0
|
||||
else
|
||||
ExplorerBundle = LoadBundle("legacy");
|
||||
}
|
||||
catch
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception parsing Unity version, falling back to old AssetBundle load method...");
|
||||
ExplorerBundle = LoadBundle("modern") ?? LoadBundle("legacy.5.6") ?? LoadBundle("legacy");
|
||||
}
|
||||
|
||||
AssetBundle LoadBundle(string id)
|
||||
{
|
||||
var bundle = AssetBundle.LoadFromMemory(ReadFully(typeof(ExplorerCore)
|
||||
.Assembly
|
||||
.GetManifestResourceStream($"UnityExplorer.Resources.{id}.bundle")));
|
||||
if (bundle)
|
||||
ExplorerCore.Log($"Loaded {id} bundle for Unity {Application.unityVersion}");
|
||||
return bundle;
|
||||
}
|
||||
|
||||
if (ExplorerBundle == null)
|
||||
{
|
||||
ExplorerCore.LogWarning("Could not load the UnityExplorer UI Bundle!");
|
||||
DefaultFont = ConsoleFont = Resources.GetBuiltinResource<Font>("Arial.ttf");
|
||||
return;
|
||||
}
|
||||
|
||||
// Bundle loaded
|
||||
|
||||
ConsoleFont = ExplorerBundle.LoadAsset<Font>("CONSOLA");
|
||||
ConsoleFont.hideFlags = HideFlags.HideAndDontSave;
|
||||
UnityEngine.Object.DontDestroyOnLoad(ConsoleFont);
|
||||
|
||||
DefaultFont = ExplorerBundle.LoadAsset<Font>("arial");
|
||||
DefaultFont.hideFlags = HideFlags.HideAndDontSave;
|
||||
UnityEngine.Object.DontDestroyOnLoad(DefaultFont);
|
||||
|
||||
BackupShader = ExplorerBundle.LoadAsset<Shader>("DefaultUI");
|
||||
BackupShader.hideFlags = HideFlags.HideAndDontSave;
|
||||
UnityEngine.Object.DontDestroyOnLoad(BackupShader);
|
||||
// Fix for games which don't ship with 'UI/Default' shader.
|
||||
if (Graphic.defaultGraphicMaterial.shader?.name != "UI/Default")
|
||||
{
|
||||
ExplorerCore.Log("This game does not ship with the 'UI/Default' shader, using manual Default Shader...");
|
||||
Graphic.defaultGraphicMaterial.shader = BackupShader;
|
||||
}
|
||||
else
|
||||
BackupShader = Graphic.defaultGraphicMaterial.shader;
|
||||
}
|
||||
|
||||
private static byte[] ReadFully(Stream input)
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
byte[] buffer = new byte[81920];
|
||||
int read;
|
||||
while ((read = input.Read(buffer, 0, buffer.Length)) != 0)
|
||||
ms.Write(buffer, 0, read);
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
// AssetBundle patch
|
||||
|
||||
private static Type TypeofAssetBundle => ReflectionUtility.GetTypeByName("UnityEngine.AssetBundle");
|
||||
|
||||
private static void SetupAssetBundlePatches()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (TypeofAssetBundle.GetMethod("UnloadAllAssetBundles", AccessTools.all) is MethodInfo unloadAllBundles)
|
||||
{
|
||||
#if CPP
|
||||
// if IL2CPP, ensure method wasn't stripped
|
||||
if (UnhollowerBaseLib.UnhollowerUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(unloadAllBundles) == null)
|
||||
return;
|
||||
#endif
|
||||
var processor = ExplorerCore.Harmony.CreateProcessor(unloadAllBundles);
|
||||
var prefix = new HarmonyMethod(typeof(UIManager).GetMethod(nameof(Prefix_UnloadAllAssetBundles), AccessTools.all));
|
||||
processor.AddPrefix(prefix);
|
||||
processor.Patch();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception setting up AssetBundle.UnloadAllAssetBundles patch: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool Prefix_UnloadAllAssetBundles(bool unloadAllObjects)
|
||||
{
|
||||
try
|
||||
{
|
||||
var method = typeof(AssetBundle).GetMethod("GetAllLoadedAssetBundles", AccessTools.all);
|
||||
if (method == null)
|
||||
return true;
|
||||
var bundles = method.Invoke(null, ArgumentUtility.EmptyArgs) as AssetBundle[];
|
||||
foreach (var obj in bundles)
|
||||
{
|
||||
if (obj.m_CachedPtr == ExplorerBundle.m_CachedPtr)
|
||||
continue;
|
||||
|
||||
obj.Unload(unloadAllObjects);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception unloading AssetBundles: {ex}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,10 +4,13 @@ using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UniverseLib.Input;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UniverseLib.UI.Widgets;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets.AutoComplete
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UniverseLib.UI;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets.AutoComplete
|
||||
{
|
||||
|
@ -1,14 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets.AutoComplete
|
||||
{
|
||||
|
@ -1,136 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer;
|
||||
using UnityExplorer.Core;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Models;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets
|
||||
{
|
||||
public class AutoSliderScrollbar : UIBehaviourModel
|
||||
{
|
||||
public override GameObject UIRoot
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Slider)
|
||||
return Slider.gameObject;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//public event Action<float> OnValueChanged;
|
||||
|
||||
internal readonly Scrollbar Scrollbar;
|
||||
internal readonly Slider Slider;
|
||||
internal RectTransform ContentRect;
|
||||
internal RectTransform ViewportRect;
|
||||
|
||||
//internal InputFieldScroller m_parentInputScroller;
|
||||
|
||||
public AutoSliderScrollbar(Scrollbar scrollbar, Slider slider, RectTransform contentRect, RectTransform viewportRect)
|
||||
{
|
||||
this.Scrollbar = scrollbar;
|
||||
this.Slider = slider;
|
||||
this.ContentRect = contentRect;
|
||||
this.ViewportRect = viewportRect;
|
||||
|
||||
this.Scrollbar.onValueChanged.AddListener(this.OnScrollbarValueChanged);
|
||||
this.Slider.onValueChanged.AddListener(this.OnSliderValueChanged);
|
||||
|
||||
//this.RefreshVisibility();
|
||||
this.Slider.Set(0f, false);
|
||||
}
|
||||
|
||||
private float lastAnchorPosition;
|
||||
private float lastContentHeight;
|
||||
private float lastViewportHeight;
|
||||
private bool _refreshWanted;
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
_refreshWanted = false;
|
||||
if (ContentRect.localPosition.y != lastAnchorPosition)
|
||||
{
|
||||
lastAnchorPosition = ContentRect.localPosition.y;
|
||||
_refreshWanted = true;
|
||||
}
|
||||
if (ContentRect.rect.height != lastContentHeight)
|
||||
{
|
||||
lastContentHeight = ContentRect.rect.height;
|
||||
_refreshWanted = true;
|
||||
}
|
||||
if (ViewportRect.rect.height != lastViewportHeight)
|
||||
{
|
||||
lastViewportHeight = ViewportRect.rect.height;
|
||||
_refreshWanted = true;
|
||||
}
|
||||
|
||||
if (_refreshWanted)
|
||||
UpdateSliderHandle();
|
||||
}
|
||||
|
||||
public void UpdateSliderHandle()
|
||||
{
|
||||
// calculate handle size based on viewport / total data height
|
||||
var totalHeight = ContentRect.rect.height;
|
||||
var viewportHeight = ViewportRect.rect.height;
|
||||
|
||||
if (totalHeight <= viewportHeight)
|
||||
{
|
||||
Slider.handleRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 0f);
|
||||
Slider.value = 0f;
|
||||
Slider.interactable = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var handleHeight = viewportHeight * Math.Min(1, viewportHeight / totalHeight);
|
||||
handleHeight = Math.Max(15f, handleHeight);
|
||||
|
||||
// resize the handle container area for the size of the handle (bigger handle = smaller container)
|
||||
var container = Slider.m_HandleContainerRect;
|
||||
container.offsetMax = new Vector2(container.offsetMax.x, -(handleHeight * 0.5f));
|
||||
container.offsetMin = new Vector2(container.offsetMin.x, handleHeight * 0.5f);
|
||||
|
||||
// set handle size
|
||||
Slider.handleRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, handleHeight);
|
||||
|
||||
// if slider is 100% height then make it not interactable
|
||||
Slider.interactable = !Mathf.Approximately(handleHeight, viewportHeight);
|
||||
|
||||
float val = 0f;
|
||||
if (totalHeight > 0f)
|
||||
val = (float)((decimal)ContentRect.localPosition.y / (decimal)(totalHeight - ViewportRect.rect.height));
|
||||
|
||||
Slider.value = val;
|
||||
}
|
||||
|
||||
public void OnScrollbarValueChanged(float value)
|
||||
{
|
||||
value = 1f - value;
|
||||
if (this.Slider.value != value)
|
||||
this.Slider.Set(value, false);
|
||||
//OnValueChanged?.Invoke(value);
|
||||
}
|
||||
|
||||
public void OnSliderValueChanged(float value)
|
||||
{
|
||||
value = 1f - value;
|
||||
this.Scrollbar.value = value;
|
||||
//OnValueChanged?.Invoke(value);
|
||||
}
|
||||
|
||||
public override void ConstructUI(GameObject parent)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets
|
||||
{
|
||||
public class ButtonCell : ICell
|
||||
{
|
||||
public float DefaultHeight => 25f;
|
||||
|
||||
public Action<int> OnClick;
|
||||
public int CurrentDataIndex;
|
||||
|
||||
public ButtonRef Button;
|
||||
|
||||
#region ICell
|
||||
|
||||
public bool Enabled => m_enabled;
|
||||
private bool m_enabled;
|
||||
|
||||
public GameObject UIRoot { get; set; }
|
||||
public RectTransform Rect { get; set; }
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
m_enabled = false;
|
||||
UIRoot.SetActive(false);
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
m_enabled = true;
|
||||
UIRoot.SetActive(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public virtual GameObject CreateContent(GameObject parent)
|
||||
{
|
||||
UIRoot = UIFactory.CreateHorizontalGroup(parent, "ButtonCell", true, false, true, true, 2, default,
|
||||
new Color(0.11f, 0.11f, 0.11f), TextAnchor.MiddleCenter);
|
||||
Rect = UIRoot.GetComponent<RectTransform>();
|
||||
Rect.anchorMin = new Vector2(0, 1);
|
||||
Rect.anchorMax = new Vector2(0, 1);
|
||||
Rect.pivot = new Vector2(0.5f, 1);
|
||||
Rect.sizeDelta = new Vector2(25, 25);
|
||||
UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 0);
|
||||
|
||||
UIRoot.SetActive(false);
|
||||
|
||||
this.Button = UIFactory.CreateButton(UIRoot, "NameButton", "Name");
|
||||
UIFactory.SetLayoutElement(Button.Component.gameObject, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 0);
|
||||
var buttonText = Button.Component.GetComponentInChildren<Text>();
|
||||
buttonText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||
buttonText.alignment = TextAnchor.MiddleLeft;
|
||||
|
||||
Color normal = new Color(0.11f, 0.11f, 0.11f);
|
||||
Color highlight = new Color(0.16f, 0.16f, 0.16f);
|
||||
Color pressed = new Color(0.05f, 0.05f, 0.05f);
|
||||
Color disabled = new Color(1, 1, 1, 0);
|
||||
RuntimeProvider.Instance.SetColorBlock(Button.Component, normal, highlight, pressed, disabled);
|
||||
|
||||
Button.OnClick += () => { OnClick?.Invoke(CurrentDataIndex); };
|
||||
|
||||
return UIRoot;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets
|
||||
{
|
||||
public class ButtonListHandler<TData, TCell> : ICellPoolDataSource<TCell> where TCell : ButtonCell
|
||||
{
|
||||
internal ScrollPool<TCell> ScrollPool;
|
||||
|
||||
public int ItemCount => currentEntries.Count;
|
||||
public readonly List<TData> currentEntries = new List<TData>();
|
||||
|
||||
public Func<List<TData>> GetEntries;
|
||||
public Action<TCell, int> SetICell;
|
||||
public Func<TData, string, bool> ShouldDisplay;
|
||||
public Action<int> OnCellClicked;
|
||||
|
||||
public string CurrentFilter
|
||||
{
|
||||
get => currentFilter;
|
||||
set => currentFilter = value ?? "";
|
||||
}
|
||||
private string currentFilter;
|
||||
|
||||
public ButtonListHandler(ScrollPool<TCell> scrollPool, Func<List<TData>> getEntriesMethod,
|
||||
Action<TCell, int> setICellMethod, Func<TData, string, bool> shouldDisplayMethod,
|
||||
Action<int> onCellClickedMethod)
|
||||
{
|
||||
ScrollPool = scrollPool;
|
||||
|
||||
GetEntries = getEntriesMethod;
|
||||
SetICell = setICellMethod;
|
||||
ShouldDisplay = shouldDisplayMethod;
|
||||
OnCellClicked = onCellClickedMethod;
|
||||
}
|
||||
|
||||
public void RefreshData()
|
||||
{
|
||||
var allEntries = GetEntries();
|
||||
currentEntries.Clear();
|
||||
|
||||
foreach (var entry in allEntries)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(currentFilter))
|
||||
{
|
||||
if (!ShouldDisplay(entry, currentFilter))
|
||||
continue;
|
||||
|
||||
currentEntries.Add(entry);
|
||||
}
|
||||
else
|
||||
currentEntries.Add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnCellBorrowed(TCell cell)
|
||||
{
|
||||
cell.OnClick += OnCellClicked;
|
||||
}
|
||||
|
||||
public virtual void SetCell(TCell cell, int index)
|
||||
{
|
||||
if (currentEntries == null)
|
||||
RefreshData();
|
||||
|
||||
if (index < 0 || index >= currentEntries.Count)
|
||||
cell.Disable();
|
||||
else
|
||||
{
|
||||
cell.Enable();
|
||||
cell.CurrentDataIndex = index;
|
||||
SetICell(cell, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.Models;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets
|
||||
{
|
||||
// To fix an issue with Input Fields and allow them to go inside a ScrollRect nicely.
|
||||
|
||||
public class InputFieldScroller : UIBehaviourModel
|
||||
{
|
||||
public override GameObject UIRoot
|
||||
{
|
||||
get
|
||||
{
|
||||
if (InputField.UIRoot)
|
||||
return InputField.UIRoot;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Action OnScroll;
|
||||
|
||||
internal AutoSliderScrollbar Slider;
|
||||
internal InputFieldRef InputField;
|
||||
|
||||
internal RectTransform ContentRect;
|
||||
internal RectTransform ViewportRect;
|
||||
|
||||
internal static CanvasScaler RootScaler;
|
||||
|
||||
public InputFieldScroller(AutoSliderScrollbar sliderScroller, InputFieldRef inputField)
|
||||
{
|
||||
this.Slider = sliderScroller;
|
||||
this.InputField = inputField;
|
||||
|
||||
inputField.OnValueChanged += OnTextChanged;
|
||||
|
||||
ContentRect = inputField.UIRoot.GetComponent<RectTransform>();
|
||||
ViewportRect = ContentRect.transform.parent.GetComponent<RectTransform>();
|
||||
|
||||
if (!RootScaler)
|
||||
RootScaler = UIManager.CanvasRoot.GetComponent<CanvasScaler>();
|
||||
}
|
||||
|
||||
internal string m_lastText;
|
||||
internal bool m_updateWanted;
|
||||
internal bool m_wantJumpToBottom;
|
||||
private float m_desiredContentHeight;
|
||||
|
||||
private float lastContentPosition;
|
||||
private float lastViewportHeight;
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (this.ContentRect.localPosition.y != lastContentPosition)
|
||||
{
|
||||
lastContentPosition = ContentRect.localPosition.y;
|
||||
OnScroll?.Invoke();
|
||||
}
|
||||
|
||||
if (ViewportRect.rect.height != lastViewportHeight)
|
||||
{
|
||||
lastViewportHeight = ViewportRect.rect.height;
|
||||
m_updateWanted = true;
|
||||
}
|
||||
|
||||
if (m_updateWanted)
|
||||
{
|
||||
m_updateWanted = false;
|
||||
ProcessInputText();
|
||||
|
||||
float desiredHeight = Math.Max(m_desiredContentHeight, ViewportRect.rect.height);
|
||||
|
||||
if (ContentRect.rect.height < desiredHeight)
|
||||
{
|
||||
ContentRect.sizeDelta = new Vector2(ContentRect.sizeDelta.x, desiredHeight);
|
||||
this.Slider.UpdateSliderHandle();
|
||||
}
|
||||
else if (ContentRect.rect.height > desiredHeight)
|
||||
{
|
||||
ContentRect.sizeDelta = new Vector2(ContentRect.sizeDelta.x, desiredHeight);
|
||||
this.Slider.UpdateSliderHandle();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_wantJumpToBottom)
|
||||
{
|
||||
Slider.Slider.value = 1f;
|
||||
m_wantJumpToBottom = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnTextChanged(string text)
|
||||
{
|
||||
m_lastText = text;
|
||||
m_updateWanted = true;
|
||||
}
|
||||
|
||||
internal void ProcessInputText()
|
||||
{
|
||||
var curInputRect = InputField.Component.textComponent.rectTransform.rect;
|
||||
var scaleFactor = RootScaler.scaleFactor;
|
||||
|
||||
// Current text settings
|
||||
var texGenSettings = InputField.Component.textComponent.GetGenerationSettings(curInputRect.size);
|
||||
texGenSettings.generateOutOfBounds = false;
|
||||
texGenSettings.scaleFactor = scaleFactor;
|
||||
|
||||
// Preferred text rect height
|
||||
var textGen = InputField.Component.textComponent.cachedTextGeneratorForLayout;
|
||||
m_desiredContentHeight = textGen.GetPreferredHeight(m_lastText, texGenSettings) + 10;
|
||||
}
|
||||
|
||||
public override void ConstructUI(GameObject parent)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,279 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets
|
||||
{
|
||||
public class DataHeightCache<T> where T : ICell
|
||||
{
|
||||
private ScrollPool<T> ScrollPool { get; }
|
||||
|
||||
public DataHeightCache(ScrollPool<T> scrollPool)
|
||||
{
|
||||
ScrollPool = scrollPool;
|
||||
}
|
||||
|
||||
private readonly List<DataViewInfo> heightCache = new List<DataViewInfo>();
|
||||
|
||||
public DataViewInfo this[int index]
|
||||
{
|
||||
get => heightCache[index];
|
||||
set => SetIndex(index, value);
|
||||
}
|
||||
|
||||
public int Count => heightCache.Count;
|
||||
|
||||
public float TotalHeight => totalHeight;
|
||||
private float totalHeight;
|
||||
|
||||
public float DefaultHeight => m_defaultHeight ?? (float)(m_defaultHeight = ScrollPool.PrototypeHeight);
|
||||
private float? m_defaultHeight;
|
||||
|
||||
/// <summary>
|
||||
/// Lookup table for "which data index first appears at this position"<br/>
|
||||
/// Index: DefaultHeight * index from top of data<br/>
|
||||
/// Value: the first data index at this position<br/>
|
||||
/// </summary>
|
||||
private readonly List<int> rangeCache = new List<int>();
|
||||
|
||||
/// <summary>Same as GetRangeIndexOfPosition, except this rounds up to the next division if there was remainder from the previous cell.</summary>
|
||||
private int GetRangeCeilingOfPosition(float position) => (int)Math.Ceiling((decimal)position / (decimal)DefaultHeight);
|
||||
|
||||
/// <summary>Get the first range (division of DefaultHeight) which the position appears in.</summary>
|
||||
private int GetRangeFloorOfPosition(float position) => (int)Math.Floor((decimal)position / (decimal)DefaultHeight);
|
||||
|
||||
public int GetFirstDataIndexAtPosition(float desiredHeight)
|
||||
{
|
||||
if (!heightCache.Any())
|
||||
return 0;
|
||||
|
||||
int rangeIndex = GetRangeFloorOfPosition(desiredHeight);
|
||||
|
||||
// probably shouldnt happen but just in case
|
||||
if (rangeIndex < 0)
|
||||
return 0;
|
||||
if (rangeIndex >= rangeCache.Count)
|
||||
{
|
||||
int idx = ScrollPool.DataSource.ItemCount - 1;
|
||||
return idx;
|
||||
}
|
||||
|
||||
int dataIndex = rangeCache[rangeIndex];
|
||||
var cache = heightCache[dataIndex];
|
||||
|
||||
// if the DataViewInfo is outdated, need to rebuild
|
||||
int expectedMin = GetRangeCeilingOfPosition(cache.startPosition);
|
||||
int expectedMax = expectedMin + cache.normalizedSpread - 1;
|
||||
if (rangeIndex < expectedMin || rangeIndex > expectedMax)
|
||||
{
|
||||
RecalculateStartPositions(ScrollPool.DataSource.ItemCount - 1);
|
||||
|
||||
rangeIndex = GetRangeFloorOfPosition(desiredHeight);
|
||||
dataIndex = rangeCache[rangeIndex];
|
||||
}
|
||||
|
||||
return dataIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the spread of the height, starting from the start position.<br/><br/>
|
||||
/// The "spread" begins at the start of the next interval of the DefaultHeight, then increases for
|
||||
/// every interval beyond that.
|
||||
/// </summary>
|
||||
private int GetRangeSpread(float startPosition, float height)
|
||||
{
|
||||
// get the remainder of the start position divided by min height
|
||||
float rem = startPosition % DefaultHeight;
|
||||
|
||||
// if there is a remainder, this means the previous cell started in our first cell and
|
||||
// they take priority, so reduce our height by (minHeight - remainder) to account for that.
|
||||
// We need to fill that gap and reach the next cell before we take priority.
|
||||
if (rem != 0.0f)
|
||||
height -= (DefaultHeight - rem);
|
||||
|
||||
return (int)Math.Ceiling((decimal)height / (decimal)DefaultHeight);
|
||||
}
|
||||
|
||||
/// <summary>Append a data index to the cache with the provided height value.</summary>
|
||||
public void Add(float value)
|
||||
{
|
||||
value = Math.Max(DefaultHeight, value);
|
||||
|
||||
int spread = GetRangeSpread(totalHeight, value);
|
||||
|
||||
heightCache.Add(new DataViewInfo(heightCache.Count, value, totalHeight, spread));
|
||||
|
||||
int dataIdx = heightCache.Count - 1;
|
||||
for (int i = 0; i < spread; i++)
|
||||
rangeCache.Add(dataIdx);
|
||||
|
||||
totalHeight += value;
|
||||
}
|
||||
|
||||
/// <summary>Remove the last (highest count) index from the height cache.</summary>
|
||||
public void RemoveLast()
|
||||
{
|
||||
if (!heightCache.Any())
|
||||
return;
|
||||
|
||||
totalHeight -= heightCache[heightCache.Count - 1];
|
||||
heightCache.RemoveAt(heightCache.Count - 1);
|
||||
|
||||
int idx = heightCache.Count;
|
||||
while (rangeCache.Count > 0 && rangeCache[rangeCache.Count - 1] == idx)
|
||||
rangeCache.RemoveAt(rangeCache.Count - 1);
|
||||
}
|
||||
|
||||
/// <summary>Set a given data index with the specified value.</summary>
|
||||
public void SetIndex(int dataIndex, float height)
|
||||
{
|
||||
height = (float)Math.Floor(height);
|
||||
height = Math.Max(DefaultHeight, height);
|
||||
|
||||
// If the index being set is beyond the DataSource item count, prune and return.
|
||||
if (dataIndex >= ScrollPool.DataSource.ItemCount)
|
||||
{
|
||||
while (heightCache.Count > dataIndex)
|
||||
RemoveLast();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the data index exceeds our cache count, fill the gap.
|
||||
// This is done by the ScrollPool when the DataSource sets its initial count, or the count increases.
|
||||
if (dataIndex >= heightCache.Count)
|
||||
{
|
||||
while (dataIndex > heightCache.Count)
|
||||
Add(DefaultHeight);
|
||||
Add(height);
|
||||
return;
|
||||
}
|
||||
|
||||
// We are actually updating an index. First, update the height and the totalHeight.
|
||||
var cache = heightCache[dataIndex];
|
||||
if (cache.height != height)
|
||||
{
|
||||
var diff = height - cache.height;
|
||||
totalHeight += diff;
|
||||
cache.height = height;
|
||||
}
|
||||
|
||||
// update our start position using the previous cell (if it exists)
|
||||
if (dataIndex > 0)
|
||||
{
|
||||
var prev = heightCache[dataIndex - 1];
|
||||
cache.startPosition = prev.startPosition + prev.height;
|
||||
}
|
||||
|
||||
// Get the normalized range index (actually ceiling) and spread based on our start position and height
|
||||
int rangeIndex = GetRangeCeilingOfPosition(cache.startPosition);
|
||||
int spread = GetRangeSpread(cache.startPosition, height);
|
||||
|
||||
// If the previous item in the range cache is not the previous data index, there is a gap.
|
||||
if (rangeCache[rangeIndex] != dataIndex)
|
||||
{
|
||||
// Recalculate start positions up to this index. The gap could be anywhere before here.
|
||||
RecalculateStartPositions(ScrollPool.DataSource.ItemCount - 1);
|
||||
// Get the range index and spread again after rebuilding
|
||||
rangeIndex = GetRangeCeilingOfPosition(cache.startPosition);
|
||||
spread = GetRangeSpread(cache.startPosition, height);
|
||||
}
|
||||
|
||||
if (rangeCache[rangeIndex] != dataIndex)
|
||||
throw new IndexOutOfRangeException($"Trying to set dataIndex {dataIndex} at rangeIndex {rangeIndex}, but cache is corrupt or invalid!");
|
||||
|
||||
if (spread != cache.normalizedSpread)
|
||||
{
|
||||
int spreadDiff = spread - cache.normalizedSpread;
|
||||
cache.normalizedSpread = spread;
|
||||
|
||||
UpdateSpread(dataIndex, rangeIndex, spreadDiff);
|
||||
}
|
||||
|
||||
// set the struct back to the array
|
||||
heightCache[dataIndex] = cache;
|
||||
}
|
||||
|
||||
private void UpdateSpread(int dataIndex, int rangeIndex, int spreadDiff)
|
||||
{
|
||||
if (spreadDiff > 0)
|
||||
{
|
||||
while (rangeCache[rangeIndex] == dataIndex && spreadDiff > 0)
|
||||
{
|
||||
rangeCache.Insert(rangeIndex, dataIndex);
|
||||
spreadDiff--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (rangeCache[rangeIndex] == dataIndex && spreadDiff < 0)
|
||||
{
|
||||
rangeCache.RemoveAt(rangeIndex);
|
||||
spreadDiff++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RecalculateStartPositions(int toIndex)
|
||||
{
|
||||
if (heightCache.Count <= 1)
|
||||
return;
|
||||
|
||||
rangeCache.Clear();
|
||||
|
||||
DataViewInfo cache;
|
||||
DataViewInfo prev = DataViewInfo.None;
|
||||
for (int idx = 0; idx <= toIndex && idx < heightCache.Count; idx++)
|
||||
{
|
||||
cache = heightCache[idx];
|
||||
|
||||
if (!prev.Equals(DataViewInfo.None))
|
||||
cache.startPosition = prev.startPosition + prev.height;
|
||||
else
|
||||
cache.startPosition = 0;
|
||||
|
||||
cache.normalizedSpread = GetRangeSpread(cache.startPosition, cache.height);
|
||||
for (int i = 0; i < cache.normalizedSpread; i++)
|
||||
rangeCache.Add(cache.dataIndex);
|
||||
|
||||
heightCache[idx] = cache;
|
||||
|
||||
prev = cache;
|
||||
}
|
||||
}
|
||||
|
||||
public struct DataViewInfo
|
||||
{
|
||||
// static
|
||||
public static DataViewInfo None => s_default;
|
||||
private static DataViewInfo s_default = default;
|
||||
|
||||
public static implicit operator float(DataViewInfo it) => it.height;
|
||||
|
||||
public DataViewInfo(int index, float height, float startPos, int spread)
|
||||
{
|
||||
this.dataIndex = index;
|
||||
this.height = height;
|
||||
this.startPosition = startPos;
|
||||
this.normalizedSpread = spread;
|
||||
}
|
||||
|
||||
// instance
|
||||
public int dataIndex, normalizedSpread;
|
||||
public float height, startPosition;
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = (DataViewInfo)obj;
|
||||
|
||||
return this.dataIndex == other.dataIndex
|
||||
&& this.height == other.height
|
||||
&& this.startPosition == other.startPosition
|
||||
&& this.normalizedSpread == other.normalizedSpread;
|
||||
}
|
||||
|
||||
public override int GetHashCode() => base.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.UI.Models;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets
|
||||
{
|
||||
public interface ICell : IPooledObject
|
||||
{
|
||||
bool Enabled { get; }
|
||||
|
||||
RectTransform Rect { get; set; }
|
||||
|
||||
void Enable();
|
||||
void Disable();
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets
|
||||
{
|
||||
public interface ICellPoolDataSource<T> where T : ICell
|
||||
{
|
||||
int ItemCount { get; }
|
||||
|
||||
void OnCellBorrowed(T cell);
|
||||
//void ReleaseCell(T cell);
|
||||
|
||||
void SetCell(T cell, int index);
|
||||
//void DisableCell(T cell, int index);
|
||||
}
|
||||
}
|
@ -1,703 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.UI.Panels;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets
|
||||
{
|
||||
public struct CellInfo
|
||||
{
|
||||
public int cellIndex, dataIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An object-pooled ScrollRect, attempts to support content of any size and provide a scrollbar for it.
|
||||
/// </summary>
|
||||
public class ScrollPool<T> : UIBehaviourModel, IEnumerable<CellInfo> where T : ICell
|
||||
{
|
||||
public ScrollPool(ScrollRect scrollRect)
|
||||
{
|
||||
this.ScrollRect = scrollRect;
|
||||
}
|
||||
|
||||
public ICellPoolDataSource<T> DataSource { get; set; }
|
||||
|
||||
public readonly List<T> CellPool = new List<T>();
|
||||
|
||||
internal DataHeightCache<T> HeightCache;
|
||||
|
||||
public float PrototypeHeight => _protoHeight ?? (float)(_protoHeight = Pool<T>.Instance.DefaultHeight);
|
||||
private float? _protoHeight;
|
||||
|
||||
public int ExtraPoolCells => 10;
|
||||
public float RecycleThreshold => PrototypeHeight * ExtraPoolCells;
|
||||
public float HalfThreshold => RecycleThreshold * 0.5f;
|
||||
|
||||
// UI
|
||||
|
||||
public override GameObject UIRoot
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ScrollRect)
|
||||
return ScrollRect.gameObject;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public RectTransform Viewport => ScrollRect.viewport;
|
||||
public RectTransform Content => ScrollRect.content;
|
||||
|
||||
internal Slider slider;
|
||||
internal ScrollRect ScrollRect;
|
||||
internal VerticalLayoutGroup contentLayout;
|
||||
|
||||
// Cache / tracking
|
||||
|
||||
private Vector2 RecycleViewBounds;
|
||||
private Vector2 NormalizedScrollBounds;
|
||||
|
||||
/// <summary>
|
||||
/// The first and last pooled indices relative to the DataSource's list
|
||||
/// </summary>
|
||||
private int bottomDataIndex;
|
||||
private int TopDataIndex => Math.Max(0, bottomDataIndex - CellPool.Count + 1);
|
||||
private int CurrentDataCount => bottomDataIndex + 1;
|
||||
|
||||
private float TotalDataHeight => HeightCache.TotalHeight + contentLayout.padding.top + contentLayout.padding.bottom;
|
||||
|
||||
/// <summary>
|
||||
/// The first and last indices of our CellPool in the transform heirarchy
|
||||
/// </summary>
|
||||
private int topPoolIndex, bottomPoolIndex;
|
||||
|
||||
private Vector2 prevAnchoredPos;
|
||||
private float prevViewportHeight;
|
||||
|
||||
#region Internal set tracking and update
|
||||
|
||||
// A sanity check so only one thing is setting the value per frame.
|
||||
public bool WritingLocked
|
||||
{
|
||||
get => writingLocked || PanelDragger.Resizing;
|
||||
internal set
|
||||
{
|
||||
if (writingLocked == value)
|
||||
return;
|
||||
timeofLastWriteLock = Time.realtimeSinceStartup;
|
||||
writingLocked = value;
|
||||
}
|
||||
}
|
||||
private bool writingLocked;
|
||||
private float timeofLastWriteLock;
|
||||
|
||||
private float prevContentHeight = 1.0f;
|
||||
private event Action OnHeightChanged;
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (!ScrollRect || DataSource == null)
|
||||
return;
|
||||
|
||||
if (writingLocked && timeofLastWriteLock.OccuredEarlierThanDefault())
|
||||
writingLocked = false;
|
||||
|
||||
if (!writingLocked)
|
||||
{
|
||||
bool viewChange = CheckRecycleViewBounds(true);
|
||||
|
||||
if (viewChange || Content.rect.height != prevContentHeight)
|
||||
{
|
||||
prevContentHeight = Content.rect.height;
|
||||
OnValueChangedListener(Vector2.zero);
|
||||
|
||||
OnHeightChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
// Public methods
|
||||
|
||||
public void Refresh(bool setCellData, bool jumpToTop = false)
|
||||
{
|
||||
if (jumpToTop)
|
||||
{
|
||||
bottomDataIndex = CellPool.Count - 1;
|
||||
Content.anchoredPosition = Vector2.zero;
|
||||
}
|
||||
|
||||
RefreshCells(setCellData, true);
|
||||
}
|
||||
|
||||
public void JumpToIndex(int index, Action<T> onJumped)
|
||||
{
|
||||
RefreshCells(true, true);
|
||||
|
||||
// Slide to the normalized position of the index
|
||||
var cache = HeightCache[index];
|
||||
float normalized = (cache.startPosition + (cache.height * 0.5f)) / HeightCache.TotalHeight;
|
||||
|
||||
RuntimeProvider.Instance.StartCoroutine(ForceDelayedJump(index, normalized, onJumped));
|
||||
}
|
||||
|
||||
private IEnumerator ForceDelayedJump(int dataIndex, float normalizedPos, Action<T> onJumped)
|
||||
{
|
||||
// Yielding two frames seems necessary in case the Explorer tab had not been opened before the jump.
|
||||
yield return null;
|
||||
yield return null;
|
||||
slider.value = normalizedPos;
|
||||
|
||||
// Get the cell containing the data index and invoke the onJumped listener for it
|
||||
foreach (var cellInfo in this)
|
||||
{
|
||||
if (cellInfo.dataIndex == dataIndex)
|
||||
{
|
||||
onJumped?.Invoke(CellPool[cellInfo.cellIndex]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IEnumerable
|
||||
|
||||
public IEnumerator<CellInfo> GetEnumerator() => EnumerateCellPool();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => EnumerateCellPool();
|
||||
|
||||
// Initialize
|
||||
|
||||
/// <summary>Should be called only once, when the scroll pool is created.</summary>
|
||||
public void Initialize(ICellPoolDataSource<T> dataSource, Action onHeightChangedListener = null)
|
||||
{
|
||||
this.DataSource = dataSource;
|
||||
HeightCache = new DataHeightCache<T>(this);
|
||||
|
||||
// Ensure the pool for the cell type is initialized.
|
||||
Pool<T>.GetPool();
|
||||
|
||||
this.contentLayout = ScrollRect.content.GetComponent<VerticalLayoutGroup>();
|
||||
this.slider = ScrollRect.GetComponentInChildren<Slider>();
|
||||
slider.onValueChanged.AddListener(OnSliderValueChanged);
|
||||
|
||||
ScrollRect.vertical = true;
|
||||
ScrollRect.horizontal = false;
|
||||
|
||||
RuntimeProvider.Instance.StartCoroutine(InitCoroutine(onHeightChangedListener));
|
||||
}
|
||||
|
||||
private IEnumerator InitCoroutine(Action onHeightChangedListener)
|
||||
{
|
||||
ScrollRect.content.anchoredPosition = Vector2.zero;
|
||||
yield return null;
|
||||
yield return null;
|
||||
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(Content);
|
||||
|
||||
// set intial bounds
|
||||
prevAnchoredPos = Content.anchoredPosition;
|
||||
CheckRecycleViewBounds(false);
|
||||
|
||||
// create initial cell pool and set cells
|
||||
CreateCellPool();
|
||||
|
||||
foreach (var cell in this)
|
||||
SetCell(CellPool[cell.cellIndex], cell.dataIndex);
|
||||
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(Content);
|
||||
prevContentHeight = Content.rect.height;
|
||||
// update slider
|
||||
SetScrollBounds();
|
||||
UpdateSliderHandle();
|
||||
|
||||
// add onValueChanged listener after setup
|
||||
ScrollRect.onValueChanged.AddListener(OnValueChangedListener);
|
||||
|
||||
OnHeightChanged += onHeightChangedListener;
|
||||
onHeightChangedListener?.Invoke();
|
||||
}
|
||||
|
||||
private void SetScrollBounds()
|
||||
{
|
||||
NormalizedScrollBounds = new Vector2(Viewport.rect.height * 0.5f, TotalDataHeight - (Viewport.rect.height * 0.5f));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// return value = viewport changed height
|
||||
/// </summary>
|
||||
private bool CheckRecycleViewBounds(bool extendPoolIfGrown)
|
||||
{
|
||||
RecycleViewBounds = new Vector2(Viewport.MinY() + HalfThreshold, Viewport.MaxY() - HalfThreshold);
|
||||
|
||||
if (extendPoolIfGrown && prevViewportHeight < Viewport.rect.height && prevViewportHeight != 0.0f)
|
||||
CheckExtendCellPool();
|
||||
|
||||
bool ret = prevViewportHeight == Viewport.rect.height;
|
||||
prevViewportHeight = Viewport.rect.height;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Cell pool
|
||||
|
||||
private CellInfo _current;
|
||||
|
||||
private IEnumerator<CellInfo> EnumerateCellPool()
|
||||
{
|
||||
int cellIdx = topPoolIndex;
|
||||
int dataIndex = TopDataIndex;
|
||||
int iterated = 0;
|
||||
while (iterated < CellPool.Count)
|
||||
{
|
||||
_current.cellIndex = cellIdx;
|
||||
_current.dataIndex = dataIndex;
|
||||
yield return _current;
|
||||
|
||||
cellIdx++;
|
||||
if (cellIdx >= CellPool.Count)
|
||||
cellIdx = 0;
|
||||
|
||||
dataIndex++;
|
||||
iterated++;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateCellPool()
|
||||
{
|
||||
//ReleaseCells();
|
||||
|
||||
CheckDataSourceCountChange(out _);
|
||||
|
||||
float currentPoolCoverage = 0f;
|
||||
float requiredCoverage = ScrollRect.viewport.rect.height + RecycleThreshold;
|
||||
|
||||
topPoolIndex = 0;
|
||||
bottomPoolIndex = -1;
|
||||
|
||||
WritingLocked = true;
|
||||
// create cells until the Pool area is covered.
|
||||
// use minimum default height so that maximum pool count is reached.
|
||||
while (currentPoolCoverage <= requiredCoverage)
|
||||
{
|
||||
bottomPoolIndex++;
|
||||
|
||||
var cell = Pool<T>.Borrow();
|
||||
CellPool.Add(cell);
|
||||
DataSource.OnCellBorrowed(cell);
|
||||
cell.Rect.SetParent(ScrollRect.content, false);
|
||||
|
||||
currentPoolCoverage += PrototypeHeight;
|
||||
}
|
||||
|
||||
bottomDataIndex = CellPool.Count - 1;
|
||||
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(Content);
|
||||
}
|
||||
|
||||
private bool CheckExtendCellPool()
|
||||
{
|
||||
CheckDataSourceCountChange();
|
||||
|
||||
var requiredCoverage = Math.Abs(RecycleViewBounds.y - RecycleViewBounds.x);
|
||||
var currentCoverage = CellPool.Count * PrototypeHeight;
|
||||
int cellsRequired = (int)Math.Floor((decimal)(requiredCoverage - currentCoverage) / (decimal)PrototypeHeight);
|
||||
if (cellsRequired > 0)
|
||||
{
|
||||
WritingLocked = true;
|
||||
|
||||
bottomDataIndex += cellsRequired;
|
||||
|
||||
// TODO sometimes still jumps a litte bit, need to figure out why.
|
||||
float prevAnchor = Content.localPosition.y;
|
||||
float prevHeight = Content.rect.height;
|
||||
|
||||
for (int i = 0; i < cellsRequired; i++)
|
||||
{
|
||||
var cell = Pool<T>.Borrow();
|
||||
DataSource.OnCellBorrowed(cell);
|
||||
cell.Rect.SetParent(ScrollRect.content, false);
|
||||
CellPool.Add(cell);
|
||||
|
||||
if (CellPool.Count > 1)
|
||||
{
|
||||
int index = CellPool.Count - 1 - (topPoolIndex % (CellPool.Count - 1));
|
||||
cell.Rect.SetSiblingIndex(index + 1);
|
||||
|
||||
if (bottomPoolIndex == index - 1)
|
||||
bottomPoolIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
RefreshCells(true, true);
|
||||
|
||||
//ExplorerCore.Log("Anchor: " + Content.localPosition.y + ", prev: " + prevAnchor);
|
||||
//ExplorerCore.Log("Height: " + Content.rect.height + ", prev:" + prevHeight);
|
||||
|
||||
if (Content.localPosition.y != prevAnchor)
|
||||
{
|
||||
var diff = Content.localPosition.y - prevAnchor;
|
||||
Content.localPosition = new Vector3(Content.localPosition.x, Content.localPosition.y - diff);
|
||||
}
|
||||
|
||||
if (Content.rect.height != prevHeight)
|
||||
{
|
||||
var diff = Content.rect.height - prevHeight;
|
||||
//ExplorerCore.Log("Height diff: " + diff);
|
||||
//Content.localPosition = new Vector3(Content.localPosition.x, Content.localPosition.y - diff);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Refresh methods
|
||||
|
||||
private bool CheckDataSourceCountChange() => CheckDataSourceCountChange(out _);
|
||||
|
||||
private bool CheckDataSourceCountChange(out bool shouldJumpToBottom)
|
||||
{
|
||||
shouldJumpToBottom = false;
|
||||
|
||||
int count = DataSource.ItemCount;
|
||||
if (bottomDataIndex > count && bottomDataIndex >= CellPool.Count)
|
||||
{
|
||||
bottomDataIndex = Math.Max(count - 1, CellPool.Count - 1);
|
||||
shouldJumpToBottom = true;
|
||||
}
|
||||
|
||||
if (HeightCache.Count < count)
|
||||
{
|
||||
HeightCache.SetIndex(count - 1, PrototypeHeight);
|
||||
return true;
|
||||
}
|
||||
else if (HeightCache.Count > count)
|
||||
{
|
||||
while (HeightCache.Count > count)
|
||||
HeightCache.RemoveLast();
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void RefreshCells(bool andReloadFromDataSource, bool setSlider)
|
||||
{
|
||||
if (!CellPool.Any()) return;
|
||||
|
||||
CheckRecycleViewBounds(true);
|
||||
|
||||
CheckDataSourceCountChange(out bool jumpToBottom);
|
||||
|
||||
// update date height cache, and set cells if 'andReload'
|
||||
foreach (var cellInfo in this)
|
||||
{
|
||||
var cell = CellPool[cellInfo.cellIndex];
|
||||
if (andReloadFromDataSource)
|
||||
SetCell(cell, cellInfo.dataIndex);
|
||||
else
|
||||
HeightCache.SetIndex(cellInfo.dataIndex, cell.Rect.rect.height);
|
||||
}
|
||||
|
||||
// force check recycles
|
||||
if (andReloadFromDataSource)
|
||||
{
|
||||
RecycleBottomToTop();
|
||||
RecycleTopToBottom();
|
||||
}
|
||||
|
||||
if (setSlider)
|
||||
UpdateSliderHandle();
|
||||
|
||||
if (jumpToBottom)
|
||||
{
|
||||
var diff = Viewport.MaxY() - CellPool[bottomPoolIndex].Rect.MaxY();
|
||||
Content.anchoredPosition += Vector2.up * diff;
|
||||
}
|
||||
|
||||
if (andReloadFromDataSource)
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(Content);
|
||||
|
||||
SetScrollBounds();
|
||||
ScrollRect.UpdatePrevData();
|
||||
}
|
||||
|
||||
private void RefreshCellHeightsFast()
|
||||
{
|
||||
foreach (var cellInfo in this)
|
||||
HeightCache.SetIndex(cellInfo.dataIndex, CellPool[cellInfo.cellIndex].Rect.rect.height);
|
||||
}
|
||||
|
||||
private void SetCell(T cachedCell, int dataIndex)
|
||||
{
|
||||
cachedCell.Enable();
|
||||
DataSource.SetCell(cachedCell, dataIndex);
|
||||
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(cachedCell.Rect);
|
||||
HeightCache.SetIndex(dataIndex, cachedCell.Rect.rect.height);
|
||||
}
|
||||
|
||||
// Value change processor
|
||||
|
||||
private void OnValueChangedListener(Vector2 val)
|
||||
{
|
||||
if (WritingLocked || DataSource == null)
|
||||
return;
|
||||
|
||||
if (InputManager.MouseScrollDelta != Vector2.zero)
|
||||
ScrollRect.StopMovement();
|
||||
|
||||
RefreshCellHeightsFast();
|
||||
|
||||
CheckRecycleViewBounds(true);
|
||||
|
||||
float yChange = ((Vector2)ScrollRect.content.localPosition - prevAnchoredPos).y;
|
||||
float adjust = 0f;
|
||||
|
||||
if (yChange > 0) // Scrolling down
|
||||
{
|
||||
if (ShouldRecycleTop)
|
||||
adjust = RecycleTopToBottom();
|
||||
|
||||
}
|
||||
else if (yChange < 0) // Scrolling up
|
||||
{
|
||||
if (ShouldRecycleBottom)
|
||||
adjust = RecycleBottomToTop();
|
||||
}
|
||||
|
||||
var vector = new Vector2(0, adjust);
|
||||
ScrollRect.m_ContentStartPosition += vector;
|
||||
ScrollRect.m_PrevPosition += vector;
|
||||
|
||||
prevAnchoredPos = ScrollRect.content.anchoredPosition;
|
||||
|
||||
SetScrollBounds();
|
||||
UpdateSliderHandle();
|
||||
}
|
||||
|
||||
private bool ShouldRecycleTop => GetCellExtent(CellPool[topPoolIndex].Rect) > RecycleViewBounds.x
|
||||
&& GetCellExtent(CellPool[bottomPoolIndex].Rect) > RecycleViewBounds.y;
|
||||
|
||||
private bool ShouldRecycleBottom => CellPool[bottomPoolIndex].Rect.position.y < RecycleViewBounds.y
|
||||
&& CellPool[topPoolIndex].Rect.position.y < RecycleViewBounds.x;
|
||||
|
||||
private float GetCellExtent(RectTransform cell) => cell.MaxY() - contentLayout.spacing;
|
||||
|
||||
private float RecycleTopToBottom()
|
||||
{
|
||||
float recycledheight = 0f;
|
||||
|
||||
while (ShouldRecycleTop && CurrentDataCount < DataSource.ItemCount)
|
||||
{
|
||||
WritingLocked = true;
|
||||
var cell = CellPool[topPoolIndex];
|
||||
|
||||
//Move top cell to bottom
|
||||
cell.Rect.SetAsLastSibling();
|
||||
var prevHeight = cell.Rect.rect.height;
|
||||
|
||||
// update content position
|
||||
Content.anchoredPosition -= Vector2.up * prevHeight;
|
||||
recycledheight += prevHeight + contentLayout.spacing;
|
||||
|
||||
//set Cell
|
||||
SetCell(cell, CurrentDataCount);
|
||||
|
||||
//set new indices
|
||||
bottomDataIndex++;
|
||||
|
||||
bottomPoolIndex = topPoolIndex;
|
||||
topPoolIndex = (topPoolIndex + 1) % CellPool.Count;
|
||||
}
|
||||
|
||||
return -recycledheight;
|
||||
}
|
||||
|
||||
private float RecycleBottomToTop()
|
||||
{
|
||||
float recycledheight = 0f;
|
||||
|
||||
while (ShouldRecycleBottom && CurrentDataCount > CellPool.Count)
|
||||
{
|
||||
WritingLocked = true;
|
||||
var cell = CellPool[bottomPoolIndex];
|
||||
|
||||
//Move bottom cell to top
|
||||
cell.Rect.SetAsFirstSibling();
|
||||
var prevHeight = cell.Rect.rect.height;
|
||||
|
||||
// update content position
|
||||
Content.anchoredPosition += Vector2.up * prevHeight;
|
||||
recycledheight += prevHeight + contentLayout.spacing;
|
||||
|
||||
//set new index
|
||||
bottomDataIndex--;
|
||||
|
||||
//set Cell
|
||||
SetCell(cell, TopDataIndex);
|
||||
|
||||
// move content again for new cell size
|
||||
var newHeight = cell.Rect.rect.height;
|
||||
var diff = newHeight - prevHeight;
|
||||
if (diff != 0.0f)
|
||||
{
|
||||
Content.anchoredPosition += Vector2.up * diff;
|
||||
recycledheight += diff;
|
||||
}
|
||||
|
||||
//set new indices
|
||||
topPoolIndex = bottomPoolIndex;
|
||||
bottomPoolIndex = (bottomPoolIndex - 1 + CellPool.Count) % CellPool.Count;
|
||||
}
|
||||
|
||||
return recycledheight;
|
||||
}
|
||||
|
||||
// Slider
|
||||
|
||||
private void OnSliderValueChanged(float val)
|
||||
{
|
||||
// Prevent spam invokes unless value is 0 or 1 (so we dont skip over the start/end)
|
||||
if (DataSource == null || (WritingLocked && val != 0 && val != 1))
|
||||
return;
|
||||
//this.WritingLocked = true;
|
||||
|
||||
ScrollRect.StopMovement();
|
||||
RefreshCellHeightsFast();
|
||||
|
||||
// normalize the scroll position for the scroll bounds.
|
||||
// point at the center of the viewport
|
||||
var desiredPosition = val * (NormalizedScrollBounds.y - NormalizedScrollBounds.x) + NormalizedScrollBounds.x;
|
||||
|
||||
// add offset above it for viewport height
|
||||
var halfView = Viewport.rect.height * 0.5f;
|
||||
var desiredMinY = desiredPosition - halfView;
|
||||
|
||||
// get the data index at the top of the viewport
|
||||
int topViewportIndex = HeightCache.GetFirstDataIndexAtPosition(desiredMinY);
|
||||
topViewportIndex = Math.Max(0, topViewportIndex);
|
||||
topViewportIndex = Math.Min(DataSource.ItemCount - 1, topViewportIndex);
|
||||
|
||||
// get the real top pooled data index to display our content
|
||||
int poolStartIndex = Math.Max(0, topViewportIndex - (int)(ExtraPoolCells * 0.5f));
|
||||
poolStartIndex = Math.Min(Math.Max(0, DataSource.ItemCount - CellPool.Count), poolStartIndex);
|
||||
|
||||
var topStartPos = HeightCache[poolStartIndex].startPosition;
|
||||
|
||||
float desiredAnchor;
|
||||
if (desiredMinY < HalfThreshold)
|
||||
desiredAnchor = desiredMinY;
|
||||
else
|
||||
desiredAnchor = desiredMinY - topStartPos;
|
||||
Content.anchoredPosition = new Vector2(0, desiredAnchor);
|
||||
|
||||
int desiredBottomIndex = poolStartIndex + CellPool.Count - 1;
|
||||
|
||||
// check if our pool indices contain the desired index. If so, rotate and set
|
||||
if (bottomDataIndex == desiredBottomIndex)
|
||||
{
|
||||
// cells will be the same, do nothing
|
||||
}
|
||||
else if (TopDataIndex > poolStartIndex && TopDataIndex < desiredBottomIndex)
|
||||
{
|
||||
// top cell falls within the new range, rotate around that
|
||||
int rotate = TopDataIndex - poolStartIndex;
|
||||
for (int i = 0; i < rotate; i++)
|
||||
{
|
||||
CellPool[bottomPoolIndex].Rect.SetAsFirstSibling();
|
||||
|
||||
//set new indices
|
||||
topPoolIndex = bottomPoolIndex;
|
||||
bottomPoolIndex = (bottomPoolIndex - 1 + CellPool.Count) % CellPool.Count;
|
||||
bottomDataIndex--;
|
||||
|
||||
SetCell(CellPool[topPoolIndex], TopDataIndex);
|
||||
}
|
||||
}
|
||||
else if (bottomDataIndex > poolStartIndex && bottomDataIndex < desiredBottomIndex)
|
||||
{
|
||||
// bottom cells falls within the new range, rotate around that
|
||||
int rotate = desiredBottomIndex - bottomDataIndex;
|
||||
for (int i = 0; i < rotate; i++)
|
||||
{
|
||||
CellPool[topPoolIndex].Rect.SetAsLastSibling();
|
||||
|
||||
//set new indices
|
||||
bottomPoolIndex = topPoolIndex;
|
||||
topPoolIndex = (topPoolIndex + 1) % CellPool.Count;
|
||||
bottomDataIndex++;
|
||||
|
||||
SetCell(CellPool[bottomPoolIndex], bottomDataIndex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bottomDataIndex = desiredBottomIndex;
|
||||
foreach (var info in this)
|
||||
{
|
||||
var cell = CellPool[info.cellIndex];
|
||||
SetCell(cell, info.dataIndex);
|
||||
}
|
||||
}
|
||||
|
||||
CheckRecycleViewBounds(true);
|
||||
|
||||
SetScrollBounds();
|
||||
ScrollRect.UpdatePrevData();
|
||||
|
||||
UpdateSliderHandle();
|
||||
}
|
||||
|
||||
private void UpdateSliderHandle()// bool forcePositionValue = true)
|
||||
{
|
||||
CheckDataSourceCountChange(out _);
|
||||
|
||||
var dataHeight = TotalDataHeight;
|
||||
|
||||
// calculate handle size based on viewport / total data height
|
||||
var viewportHeight = Viewport.rect.height;
|
||||
var handleHeight = viewportHeight * Math.Min(1, viewportHeight / dataHeight);
|
||||
handleHeight = Math.Max(15f, handleHeight);
|
||||
|
||||
// resize the handle container area for the size of the handle (bigger handle = smaller container)
|
||||
var container = slider.m_HandleContainerRect;
|
||||
container.offsetMax = new Vector2(container.offsetMax.x, -(handleHeight * 0.5f));
|
||||
container.offsetMin = new Vector2(container.offsetMin.x, handleHeight * 0.5f);
|
||||
|
||||
// set handle size
|
||||
slider.handleRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, handleHeight);
|
||||
|
||||
// if slider is 100% height then make it not interactable
|
||||
slider.interactable = !Mathf.Approximately(handleHeight, viewportHeight);
|
||||
|
||||
float val = 0f;
|
||||
if (TotalDataHeight > 0f)
|
||||
{
|
||||
float topPos = 0f;
|
||||
if (HeightCache.Count > 0)
|
||||
topPos = HeightCache[TopDataIndex].startPosition;
|
||||
|
||||
var scrollPos = topPos + Content.anchoredPosition.y;
|
||||
|
||||
var viewHeight = TotalDataHeight - Viewport.rect.height;
|
||||
if (viewHeight != 0.0f)
|
||||
val = (float)((decimal)scrollPos / (decimal)(viewHeight));
|
||||
else
|
||||
val = 0f;
|
||||
}
|
||||
|
||||
slider.Set(val, false);
|
||||
}
|
||||
|
||||
/// <summary>Use <see cref="UIFactory.CreateScrollPool"/></summary>
|
||||
public override void ConstructUI(GameObject parent) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.UI
|
||||
{
|
||||
public static class UIExtension
|
||||
{
|
||||
public static void GetCorners(this RectTransform rect, Vector3[] corners)
|
||||
{
|
||||
Vector3 bottomLeft = new Vector3(rect.position.x, rect.position.y - rect.rect.height, 0);
|
||||
|
||||
corners[0] = bottomLeft;
|
||||
corners[1] = bottomLeft + new Vector3(0, rect.rect.height, 0);
|
||||
corners[2] = bottomLeft + new Vector3(rect.rect.width, rect.rect.height, 0);
|
||||
corners[3] = bottomLeft + new Vector3(rect.rect.width, 0, 0);
|
||||
}
|
||||
|
||||
// again, using position and rect instead of
|
||||
|
||||
public static float MaxY(this RectTransform rect) => rect.position.y - rect.rect.height;
|
||||
|
||||
public static float MinY(this RectTransform rect) => rect.position.y;
|
||||
|
||||
public static float MaxX(this RectTransform rect) => rect.position.x + rect.rect.width;
|
||||
|
||||
public static float MinX(this RectTransform rect) => rect.position.x;
|
||||
}
|
||||
}
|
@ -6,6 +6,9 @@ using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Inspectors;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets
|
||||
{
|
||||
|
@ -6,6 +6,8 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.UI.Widgets
|
||||
{
|
||||
|
Reference in New Issue
Block a user