use UniverseLib

This commit is contained in:
Sinai
2021-12-02 18:35:46 +11:00
parent 077a2b434a
commit 3334549902
111 changed files with 540 additions and 7819 deletions

View File

@ -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(); });
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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());
}

View File

@ -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);
}

View File

@ -7,6 +7,7 @@ using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Config;
using UnityExplorer.Inspectors;
using UniverseLib.UI;
namespace UnityExplorer.UI.Panels
{

View File

@ -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;
}

View File

@ -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()

View File

@ -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
{

View File

@ -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>();

View File

@ -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);

View File

@ -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
{

View File

@ -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);
}
}

View File

@ -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
}
}

View File

@ -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;
}
}
}

View File

@ -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
{

View File

@ -4,6 +4,7 @@ using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UniverseLib.UI;
namespace UnityExplorer.UI.Widgets.AutoComplete
{

View File

@ -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
{

View File

@ -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();
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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
{

View File

@ -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
{