3.3.0 rewrite

* Huge restructure/rewrite. No real changes to any functionality, just a cleaner and more manageable project.
This commit is contained in:
Sinai
2021-03-30 19:50:04 +11:00
parent f66a04c93f
commit 0555a644b7
90 changed files with 4184 additions and 5635 deletions

View File

@ -1,244 +0,0 @@
using System;
using UnityEngine;
using UnityExplorer.Core.Unity;
using UnityEngine.EventSystems;
using UnityExplorer.Core.Input;
using BF = System.Reflection.BindingFlags;
using UnityExplorer.Core.Config;
using UnityExplorer.Core;
#if ML
using Harmony;
#else
using HarmonyLib;
#endif
namespace UnityExplorer.UI.Utility
{
public class CursorUnlocker
{
public static bool Unlock
{
get => m_forceUnlock;
set => SetForceUnlock(value);
}
private static bool m_forceUnlock;
private static void SetForceUnlock(bool unlock)
{
m_forceUnlock = unlock;
UpdateCursorControl();
}
public static bool ShouldForceMouse => UIManager.ShowMenu && Unlock;
private static CursorLockMode m_lastLockMode;
private static bool m_lastVisibleState;
private static bool m_currentlySettingCursor = false;
private static Type CursorType
=> m_cursorType
?? (m_cursorType = ReflectionUtility.GetTypeByName("UnityEngine.Cursor"));
private static Type m_cursorType;
public static void Init()
{
ExplorerConfig.OnConfigChanged += ModConfig_OnConfigChanged;
SetupPatches();
Unlock = true;
}
internal static void ModConfig_OnConfigChanged()
{
Unlock = ExplorerConfig.Instance.Force_Unlock_Mouse;
}
private static void SetupPatches()
{
try
{
if (CursorType == null)
{
throw new Exception("Could not find Type 'UnityEngine.Cursor'!");
}
// Get current cursor state and enable cursor
try
{
//m_lastLockMode = Cursor.lockState;
m_lastLockMode = (CursorLockMode?)typeof(Cursor).GetProperty("lockState", BF.Public | BF.Static)?.GetValue(null, null)
?? CursorLockMode.None;
//m_lastVisibleState = Cursor.visible;
m_lastVisibleState = (bool?)typeof(Cursor).GetProperty("visible", BF.Public | BF.Static)?.GetValue(null, null)
?? false;
}
catch { }
// Setup Harmony Patches
TryPatch(typeof(Cursor),
"lockState",
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(Prefix_set_lockState))),
true);
TryPatch(typeof(Cursor),
"visible",
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(Prefix_set_visible))),
true);
TryPatch(typeof(EventSystem),
"current",
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(Prefix_EventSystem_set_current))),
true);
}
catch (Exception e)
{
ExplorerCore.Log($"Exception on ForceUnlockCursor.Init! {e.GetType()}, {e.Message}");
}
}
private static void TryPatch(Type type, string property, HarmonyMethod patch, bool setter)
{
try
{
var harmony = ExplorerCore.Loader.HarmonyInstance;
System.Reflection.PropertyInfo prop = type.GetProperty(property);
if (setter) // setter is prefix
{
harmony.Patch(prop.GetSetMethod(), prefix: patch);
}
else // getter is postfix
{
harmony.Patch(prop.GetGetMethod(), postfix: patch);
}
}
catch (Exception e)
{
string suf = setter ? "set_" : "get_";
ExplorerCore.Log($"Unable to patch {type.Name}.{suf}{property}: {e.Message}");
}
}
public static void UpdateCursorControl()
{
try
{
m_currentlySettingCursor = true;
if (ShouldForceMouse)
{
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
}
else
{
Cursor.lockState = m_lastLockMode;
Cursor.visible = m_lastVisibleState;
}
m_currentlySettingCursor = false;
}
catch (Exception e)
{
ExplorerCore.Log($"Exception setting Cursor state: {e.GetType()}, {e.Message}");
}
}
// Event system overrides
private static bool m_settingEventSystem;
private static EventSystem m_lastEventSystem;
private static BaseInputModule m_lastInputModule;
public static void SetEventSystem()
{
// temp disabled for new InputSystem
if (InputManager.CurrentType == InputType.InputSystem)
return;
// Disable current event system object
if (m_lastEventSystem || EventSystem.current)
{
if (!m_lastEventSystem)
m_lastEventSystem = EventSystem.current;
//ExplorerCore.Log("Disabling current event system...");
m_lastEventSystem.enabled = false;
//m_lastEventSystem.gameObject.SetActive(false);
}
// Set to our current system
m_settingEventSystem = true;
EventSystem.current = UIManager.EventSys;
UIManager.EventSys.enabled = true;
InputManager.ActivateUIModule();
m_settingEventSystem = false;
}
public static void ReleaseEventSystem()
{
if (InputManager.CurrentType == InputType.InputSystem)
return;
if (m_lastEventSystem)
{
m_lastEventSystem.enabled = true;
//m_lastEventSystem.gameObject.SetActive(true);
m_settingEventSystem = true;
EventSystem.current = m_lastEventSystem;
m_lastInputModule?.ActivateModule();
m_settingEventSystem = false;
}
}
[HarmonyPrefix]
public static void Prefix_EventSystem_set_current(ref EventSystem value)
{
if (!m_settingEventSystem)
{
m_lastEventSystem = value;
m_lastInputModule = value?.currentInputModule;
if (UIManager.ShowMenu)
{
value = UIManager.EventSys;
}
}
}
// Force mouse to stay unlocked and visible while UnlockMouse and ShowMenu are true.
// Also keep track of when anything else tries to set Cursor state, this will be the
// value that we set back to when we close the menu or disable force-unlock.
[HarmonyPrefix]
public static void Prefix_set_lockState(ref CursorLockMode value)
{
if (!m_currentlySettingCursor)
{
m_lastLockMode = value;
if (ShouldForceMouse)
{
value = CursorLockMode.None;
}
}
}
[HarmonyPrefix]
public static void Prefix_set_visible(ref bool value)
{
if (!m_currentlySettingCursor)
{
m_lastVisibleState = value;
if (ShouldForceMouse)
{
value = true;
}
}
}
}
}

View File

@ -0,0 +1,133 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityExplorer.Core.Unity;
namespace UnityExplorer.UI.Utility
{
// To fix an issue with Input Fields and allow them to go inside a ScrollRect nicely.
public class InputFieldScroller
{
public static readonly List<InputFieldScroller> Instances = new List<InputFieldScroller>();
public static void UpdateInstances()
{
if (!Instances.Any())
return;
for (int i = 0; i < Instances.Count; i++)
{
var input = Instances[i];
if (input.CheckDestroyed())
i--;
else
input.Update();
}
}
internal SliderScrollbar sliderScroller;
internal InputField inputField;
internal RectTransform inputRect;
internal LayoutElement layoutElement;
internal VerticalLayoutGroup parentLayoutGroup;
internal static CanvasScaler canvasScaler;
public InputFieldScroller(SliderScrollbar sliderScroller, InputField inputField)
{
Instances.Add(this);
this.sliderScroller = sliderScroller;
this.inputField = inputField;
sliderScroller.m_parentInputScroller = this;
inputField.onValueChanged.AddListener(OnTextChanged);
inputRect = inputField.GetComponent<RectTransform>();
layoutElement = inputField.gameObject.AddComponent<LayoutElement>();
parentLayoutGroup = inputField.transform.parent.GetComponent<VerticalLayoutGroup>();
layoutElement.minHeight = 25;
layoutElement.minWidth = 100;
if (!canvasScaler)
canvasScaler = UIManager.CanvasRoot.GetComponent<CanvasScaler>();
}
internal string m_lastText;
internal bool m_updateWanted;
// only done once, to fix height on creation.
internal bool heightInitAfterLayout;
public void Update()
{
if (!heightInitAfterLayout)
{
heightInitAfterLayout = true;
var height = sliderScroller.m_scrollRect.parent.parent.GetComponent<RectTransform>().rect.height;
layoutElement.preferredHeight = height;
}
if (m_updateWanted && inputField.gameObject.activeInHierarchy)
{
m_updateWanted = false;
RefreshUI();
}
}
internal bool CheckDestroyed()
{
if (sliderScroller == null || sliderScroller.CheckDestroyed())
{
Instances.Remove(this);
return true;
}
return false;
}
internal void OnTextChanged(string text)
{
m_lastText = text;
m_updateWanted = true;
}
internal void RefreshUI()
{
var curInputRect = inputField.textComponent.rectTransform.rect;
var scaleFactor = canvasScaler.scaleFactor;
// Current text settings
var texGenSettings = inputField.textComponent.GetGenerationSettings(curInputRect.size);
texGenSettings.generateOutOfBounds = false;
texGenSettings.scaleFactor = scaleFactor;
// Preferred text rect height
var textGen = inputField.textComponent.cachedTextGeneratorForLayout;
float preferredHeight = textGen.GetPreferredHeight(m_lastText, texGenSettings) + 10;
// Default text rect height (fit to scroll parent or expand to fit text)
float minHeight = Mathf.Max(preferredHeight, sliderScroller.m_scrollRect.rect.height - 25);
layoutElement.preferredHeight = minHeight;
if (inputField.caretPosition == inputField.text.Length
&& inputField.text.Length > 0
&& inputField.text[inputField.text.Length - 1] == '\n')
{
sliderScroller.m_slider.value = 0f;
}
}
}
}

View File

@ -0,0 +1,200 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityExplorer.Core.Config;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Unity;
namespace UnityExplorer.UI.Utility
{
public enum Turn
{
Left,
Right
}
public class PageHandler : IEnumerator
{
public PageHandler(SliderScrollbar scroll)
{
ItemsPerPage = ConfigManager.Default_Page_Limit?.Value ?? 20;
m_scrollbar = scroll;
}
public event Action OnPageChanged;
private readonly SliderScrollbar m_scrollbar;
// For now this is just set when the PageHandler is created, based on config.
// At some point I might make it possible to change this after creation again.
public int ItemsPerPage { get; }
// IEnumerator.Current
public object Current => m_currentIndex;
private int m_currentIndex = 0;
public int CurrentPage
{
get => m_currentPage;
set
{
if (value < PageCount)
m_currentPage = value;
}
}
private int m_currentPage;
// ui
private GameObject m_pageUIHolder;
private Text m_currentPageLabel;
// set and maintained by owner of list
private int m_listCount;
public int ListCount
{
get => m_listCount;
set
{
m_listCount = value;
if (PageCount <= 0 && m_pageUIHolder.activeSelf)
{
m_pageUIHolder.SetActive(false);
}
else if (PageCount > 0 && !m_pageUIHolder.activeSelf)
{
m_pageUIHolder.SetActive(true);
}
RefreshUI();
}
}
public int PageCount => (int)Math.Ceiling(ListCount / (decimal)ItemsPerPage) - 1;
// The index of the first element of the current page
public int StartIndex
{
get
{
int offset = m_currentPage * ItemsPerPage;
if (offset >= ListCount)
{
offset = 0;
m_currentPage = 0;
}
return offset;
}
}
public int EndIndex
{
get
{
int end = StartIndex + ItemsPerPage;
if (end >= ListCount)
end = ListCount - 1;
return end;
}
}
// IEnumerator.MoveNext()
public bool MoveNext()
{
m_currentIndex++;
return m_currentIndex < StartIndex + ItemsPerPage;
}
// IEnumerator.Reset()
public void Reset()
{
m_currentIndex = StartIndex - 1;
}
public IEnumerator<int> GetEnumerator()
{
Reset();
while (MoveNext())
{
yield return m_currentIndex;
}
}
public void TurnPage(Turn direction)
{
bool didTurn = false;
if (direction == Turn.Left)
{
if (m_currentPage > 0)
{
m_currentPage--;
didTurn = true;
}
}
else
{
if (m_currentPage < PageCount)
{
m_currentPage++;
didTurn = true;
}
}
if (didTurn)
{
if (m_scrollbar != null)
m_scrollbar.m_scrollbar.value = 1;
OnPageChanged?.Invoke();
RefreshUI();
}
}
#region UI CONSTRUCTION
public void Show() => m_pageUIHolder?.SetActive(true);
public void Hide() => m_pageUIHolder?.SetActive(false);
public void RefreshUI()
{
m_currentPageLabel.text = $"Page {m_currentPage + 1} / {PageCount + 1}";
}
public void ConstructUI(GameObject parent)
{
m_pageUIHolder = UIFactory.CreateHorizontalGroup(parent, "PageHandlerButtons", false, true, true, true);
Image image = m_pageUIHolder.GetComponent<Image>();
image.color = new Color(0.2f, 0.2f, 0.2f, 0.5f);
UIFactory.SetLayoutElement(m_pageUIHolder, minHeight: 25, minWidth: 100, flexibleWidth: 5000);
var leftBtnObj = UIFactory.CreateButton(m_pageUIHolder,
"BackBtn",
"◄",
() => { TurnPage(Turn.Left); },
new Color(0.15f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(leftBtnObj.gameObject, flexibleWidth: 1500, minWidth: 25, minHeight: 25);
m_currentPageLabel = UIFactory.CreateLabel(m_pageUIHolder, "PageLabel", "Page 1 / TODO", TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(m_currentPageLabel.gameObject, minWidth: 100, flexibleWidth: 40);
Button rightBtn = UIFactory.CreateButton(m_pageUIHolder,
"RightBtn",
"►",
() => { TurnPage(Turn.Right); },
new Color(0.15f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(rightBtn.gameObject, flexibleWidth: 1500, minWidth: 25, minHeight: 25);
ListCount = 0;
}
#endregion
}
}

View File

@ -130,7 +130,7 @@ namespace UnityExplorer.UI.Utility
var args = "<";
for (int i = 0; i < gArgs.Length; i++)
{
if (i > 0)
if (i > 0)
args += ", ";
var arg = gArgs[i];

View File

@ -0,0 +1,201 @@
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.Core.Unity;
using UnityExplorer.UI;
namespace UnityExplorer.UI.Utility
{
// Basically just to fix an issue with Scrollbars, instead we use a Slider as the scrollbar.
public class SliderScrollbar
{
internal static readonly List<SliderScrollbar> Instances = new List<SliderScrollbar>();
public static void UpdateInstances()
{
if (!Instances.Any())
return;
for (int i = 0; i < Instances.Count; i++)
{
var slider = Instances[i];
if (slider.CheckDestroyed())
i--;
else
slider.Update();
}
}
public bool IsActive { get; private set; }
internal readonly Scrollbar m_scrollbar;
internal readonly Slider m_slider;
internal readonly RectTransform m_scrollRect;
internal InputFieldScroller m_parentInputScroller;
public SliderScrollbar(Scrollbar scrollbar, Slider slider)
{
Instances.Add(this);
this.m_scrollbar = scrollbar;
this.m_slider = slider;
this.m_scrollRect = scrollbar.transform.parent.GetComponent<RectTransform>();
this.m_scrollbar.onValueChanged.AddListener(this.OnScrollbarValueChanged);
this.m_slider.onValueChanged.AddListener(this.OnSliderValueChanged);
this.RefreshVisibility();
this.m_slider.Set(1f, false);
}
internal bool CheckDestroyed()
{
if (!m_slider || !m_scrollbar)
{
Instances.Remove(this);
return true;
}
return false;
}
internal void Update()
{
this.RefreshVisibility();
}
internal void RefreshVisibility()
{
if (!m_slider.gameObject.activeInHierarchy)
{
IsActive = false;
return;
}
bool shouldShow = !Mathf.Approximately(this.m_scrollbar.size, 1);
var obj = this.m_slider.handleRect.gameObject;
if (IsActive != shouldShow)
{
IsActive = shouldShow;
obj.SetActive(IsActive);
if (IsActive)
this.m_slider.Set(this.m_scrollbar.value, false);
else
m_slider.Set(1f, false);
}
}
public void OnScrollbarValueChanged(float _value)
{
if (this.m_slider.value != _value)
this.m_slider.Set(_value, false);
}
public void OnSliderValueChanged(float _value)
{
this.m_scrollbar.value = _value;
}
#region UI CONSTRUCTION
public static GameObject CreateSliderScrollbar(GameObject parent, out Slider slider)
{
GameObject sliderObj = UIFactory.CreateUIObject("SliderScrollbar", parent, UIFactory._smallElementSize);
GameObject bgObj = UIFactory.CreateUIObject("Background", sliderObj);
GameObject fillAreaObj = UIFactory.CreateUIObject("Fill Area", sliderObj);
GameObject fillObj = UIFactory.CreateUIObject("Fill", fillAreaObj);
GameObject handleSlideAreaObj = UIFactory.CreateUIObject("Handle Slide Area", sliderObj);
GameObject handleObj = UIFactory.CreateUIObject("Handle", handleSlideAreaObj);
Image bgImage = bgObj.AddComponent<Image>();
bgImage.type = Image.Type.Sliced;
bgImage.color = new Color(0.05f, 0.05f, 0.05f, 1.0f);
RectTransform bgRect = bgObj.GetComponent<RectTransform>();
bgRect.anchorMin = Vector2.zero;
bgRect.anchorMax = Vector2.one;
bgRect.sizeDelta = Vector2.zero;
bgRect.offsetMax = new Vector2(-10f, 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 = Color.clear;
fillObj.GetComponent<RectTransform>().sizeDelta = new Vector2(10f, 0f);
RectTransform handleSlideRect = handleSlideAreaObj.GetComponent<RectTransform>();
handleSlideRect.anchorMin = new Vector2(0f, 0f);
handleSlideRect.anchorMax = new Vector2(1f, 1f);
handleSlideRect.offsetMin = new Vector2(15f, 30f);
handleSlideRect.offsetMax = new Vector2(-15f, 0f);
handleSlideRect.sizeDelta = new Vector2(-30f, -30f);
Image handleImage = handleObj.AddComponent<Image>();
handleImage.color = new Color(0.5f, 0.5f, 0.5f, 1.0f);
var handleRect = handleObj.GetComponent<RectTransform>();
handleRect.sizeDelta = new Vector2(15f, 30f);
handleRect.offsetMin = new Vector2(-13f, -28f);
handleRect.offsetMax = new Vector2(3f, -2f);
var sliderBarLayout = sliderObj.AddComponent<LayoutElement>();
sliderBarLayout.minWidth = 25;
sliderBarLayout.flexibleWidth = 0;
sliderBarLayout.minHeight = 30;
sliderBarLayout.flexibleHeight = 5000;
slider = sliderObj.AddComponent<Slider>();
slider.fillRect = fillObj.GetComponent<RectTransform>();
slider.handleRect = handleObj.GetComponent<RectTransform>();
slider.targetGraphic = handleImage;
slider.direction = Slider.Direction.BottomToTop;
UIFactory.SetDefaultSelectableColors(slider);
return sliderObj;
}
#endregion
}
#if MONO
public static class SliderExtensions
{
// il2cpp can just use the orig method directly (forced public)
private static MethodInfo m_setMethod;
private static MethodInfo SetMethod
{
get
{
if (m_setMethod == null)
{
m_setMethod = typeof(Slider).GetMethod("Set", ReflectionUtility.CommonFlags, null, new[] { typeof(float), typeof(bool) }, null);
}
return m_setMethod;
}
}
public static void Set(this Slider slider, float value, bool invokeCallback)
{
SetMethod.Invoke(slider, new object[] { value, invokeCallback });
}
}
#endif
}