mirror of
https://github.com/sinai-dev/UnityExplorer.git
synced 2025-06-24 01:12:41 +08:00
Lots of fixes, everything basically done except Reflection Inspector
This commit is contained in:
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
using UnityExplorer.Console;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.PageModel;
|
||||
using UnityExplorer.UI.Modules;
|
||||
using UnityExplorer.Config;
|
||||
|
||||
namespace UnityExplorer.UI
|
||||
@ -56,7 +56,6 @@ namespace UnityExplorer.UI
|
||||
|
||||
Pages.Add(new HomePage());
|
||||
Pages.Add(new SearchPage());
|
||||
// TODO remove page on games where it failed to init?
|
||||
Pages.Add(new ConsolePage());
|
||||
Pages.Add(new OptionsPage());
|
||||
|
||||
@ -65,14 +64,35 @@ namespace UnityExplorer.UI
|
||||
foreach (Page page in Pages)
|
||||
{
|
||||
page.Init();
|
||||
page.Content?.SetActive(false);
|
||||
}
|
||||
|
||||
SetPage(Pages[0]);
|
||||
// hide menu until each page has init layout (bit of a hack)
|
||||
initPos = MainPanel.transform.position;
|
||||
MainPanel.transform.position = new Vector3(9999, 9999);
|
||||
}
|
||||
|
||||
internal Vector3 initPos;
|
||||
internal bool pageLayoutInit;
|
||||
internal int layoutInitIndex;
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (!pageLayoutInit)
|
||||
{
|
||||
if (layoutInitIndex < Pages.Count)
|
||||
{
|
||||
SetPage(Pages[layoutInitIndex]);
|
||||
layoutInitIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
pageLayoutInit = true;
|
||||
MainPanel.transform.position = initPos;
|
||||
SetPage(Pages[0]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
m_activePage?.Update();
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityExplorer.Console;
|
||||
|
||||
namespace UnityExplorer.UI.PageModel
|
||||
namespace UnityExplorer.UI.Modules
|
||||
{
|
||||
public class ConsolePage : MainMenu.Page
|
||||
{
|
||||
@ -25,11 +25,15 @@ namespace UnityExplorer.UI.PageModel
|
||||
public static readonly string[] DefaultUsing = new string[]
|
||||
{
|
||||
"System",
|
||||
"UnityEngine",
|
||||
"System.Linq",
|
||||
"System.Collections",
|
||||
"System.Collections.Generic",
|
||||
"System.Reflection"
|
||||
"System.Reflection",
|
||||
"UnityEngine",
|
||||
#if CPP
|
||||
"UnhollowerBaseLib",
|
||||
"UnhollowerRuntimeLib",
|
||||
#endif
|
||||
};
|
||||
|
||||
public override void Init()
|
||||
@ -42,21 +46,14 @@ namespace UnityExplorer.UI.PageModel
|
||||
|
||||
AutoCompleter.Init();
|
||||
|
||||
try
|
||||
{
|
||||
ResetConsole();
|
||||
ResetConsole();
|
||||
|
||||
// Make sure compiler is supported on this platform
|
||||
m_evaluator.Compile("");
|
||||
// Make sure compiler is supported on this platform
|
||||
m_evaluator.Compile("");
|
||||
|
||||
foreach (string use in DefaultUsing)
|
||||
{
|
||||
AddUsing(use);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
foreach (string use in DefaultUsing)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception setting up Console: {e.GetType()}, {e.Message}\r\n{e.StackTrace}");
|
||||
AddUsing(use);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
@ -5,8 +5,9 @@ using UnityExplorer.Unstrip;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Config;
|
||||
using UnityExplorer.UI.Shared;
|
||||
|
||||
namespace UnityExplorer.UI.PageModel
|
||||
namespace UnityExplorer.UI.Modules
|
||||
{
|
||||
public class DebugConsole
|
||||
{
|
||||
@ -98,51 +99,49 @@ namespace UnityExplorer.UI.PageModel
|
||||
logAreaLayout.preferredHeight = 190;
|
||||
logAreaLayout.flexibleHeight = 0;
|
||||
|
||||
var inputObj = UIFactory.CreateInputField(logAreaObj, 14, 0, 1);
|
||||
//var inputObj = UIFactory.CreateInputField(logAreaObj, 14, 0, 1);
|
||||
|
||||
var mainInputGroup = inputObj.GetComponent<VerticalLayoutGroup>();
|
||||
mainInputGroup.padding.left = 8;
|
||||
mainInputGroup.padding.right = 8;
|
||||
mainInputGroup.padding.top = 5;
|
||||
mainInputGroup.padding.bottom = 5;
|
||||
//var mainInputGroup = inputObj.GetComponent<VerticalLayoutGroup>();
|
||||
//mainInputGroup.padding.left = 8;
|
||||
//mainInputGroup.padding.right = 8;
|
||||
//mainInputGroup.padding.top = 5;
|
||||
//mainInputGroup.padding.bottom = 5;
|
||||
|
||||
var inputLayout = inputObj.AddComponent<LayoutElement>();
|
||||
inputLayout.preferredWidth = 500;
|
||||
inputLayout.flexibleWidth = 9999;
|
||||
//var inputLayout = inputObj.AddComponent<LayoutElement>();
|
||||
//inputLayout.preferredWidth = 500;
|
||||
//inputLayout.flexibleWidth = 9999;
|
||||
|
||||
var inputImage = inputObj.GetComponent<Image>();
|
||||
inputImage.color = new Color(0.05f, 0.05f, 0.05f, 1.0f);
|
||||
//var inputImage = inputObj.GetComponent<Image>();
|
||||
//inputImage.color = new Color(0.05f, 0.05f, 0.05f, 1.0f);
|
||||
|
||||
var scroll = UIFactory.CreateScrollbar(logAreaObj);
|
||||
//var scroll = UIFactory.CreateScrollbar(logAreaObj);
|
||||
|
||||
var scrollLayout = scroll.AddComponent<LayoutElement>();
|
||||
scrollLayout.preferredWidth = 25;
|
||||
scrollLayout.flexibleWidth = 0;
|
||||
//var scrollLayout = scroll.AddComponent<LayoutElement>();
|
||||
//scrollLayout.preferredWidth = 25;
|
||||
//scrollLayout.flexibleWidth = 0;
|
||||
|
||||
var scroller = scroll.GetComponent<Scrollbar>();
|
||||
scroller.direction = Scrollbar.Direction.TopToBottom;
|
||||
var scrollColors = scroller.colors;
|
||||
scrollColors.normalColor = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
scroller.colors = scrollColors;
|
||||
//var scroller = scroll.GetComponent<Scrollbar>();
|
||||
//scroller.direction = Scrollbar.Direction.TopToBottom;
|
||||
//var scrollColors = scroller.colors;
|
||||
//scrollColors.normalColor = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
//scroller.colors = scrollColors;
|
||||
|
||||
var tmpInput = inputObj.GetComponent<InputField>();
|
||||
//tmpInput.scrollSensitivity = 15;
|
||||
//tmpInput.verticalScrollbar = scroller;
|
||||
tmpInput.readOnly = true;
|
||||
//var tmpInput = inputObj.GetComponent<InputField>();
|
||||
//tmpInput.readOnly = true;
|
||||
|
||||
if (UIManager.ConsoleFont != null)
|
||||
{
|
||||
tmpInput.textComponent.font = UIManager.ConsoleFont;
|
||||
#if MONO
|
||||
(tmpInput.placeholder as Text).font = UIManager.ConsoleFont;
|
||||
#else
|
||||
tmpInput.placeholder.TryCast<Text>().font = UIManager.ConsoleFont;
|
||||
#endif
|
||||
}
|
||||
//if (UIManager.ConsoleFont)
|
||||
//{
|
||||
// tmpInput.textComponent.font = UIManager.ConsoleFont;
|
||||
//}
|
||||
|
||||
tmpInput.readOnly = true;
|
||||
//tmpInput.readOnly = true;
|
||||
|
||||
m_textInput = inputObj.GetComponent<InputField>();
|
||||
var inputScrollerObj = UIFactory.CreateSrollInputField(logAreaObj, out InputFieldScroller inputScroll, 14, new Color(0.05f, 0.05f, 0.05f));
|
||||
|
||||
inputScroll.inputField.textComponent.font = UIManager.ConsoleFont;
|
||||
inputScroll.inputField.readOnly = true;
|
||||
|
||||
m_textInput = inputScroll.inputField;
|
||||
|
||||
#endregion
|
||||
|
@ -5,7 +5,7 @@ using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Inspectors;
|
||||
|
||||
namespace UnityExplorer.UI.PageModel
|
||||
namespace UnityExplorer.UI.Modules
|
||||
{
|
||||
public class HomePage : MainMenu.Page
|
||||
{
|
@ -8,7 +8,7 @@ using UnityExplorer.Config;
|
||||
using UnityExplorer.UI.Shared;
|
||||
using UnityExplorer.Unstrip;
|
||||
|
||||
namespace UnityExplorer.UI.PageModel
|
||||
namespace UnityExplorer.UI.Modules
|
||||
{
|
||||
public class OptionsPage : MainMenu.Page
|
||||
{
|
@ -13,7 +13,7 @@ using UnityExplorer.Unstrip;
|
||||
using UnhollowerRuntimeLib;
|
||||
#endif
|
||||
|
||||
namespace UnityExplorer.UI.PageModel
|
||||
namespace UnityExplorer.UI.Modules
|
||||
{
|
||||
internal enum SearchContext
|
||||
{
|
108
src/UI/Shared/InputFieldScroller.cs
Normal file
108
src/UI/Shared/InputFieldScroller.cs
Normal file
@ -0,0 +1,108 @@
|
||||
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.Helpers;
|
||||
|
||||
namespace UnityExplorer.UI.Shared
|
||||
{
|
||||
// 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>();
|
||||
|
||||
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;
|
||||
|
||||
#if MONO
|
||||
inputField.onValueChanged.AddListener(OnTextChanged);
|
||||
#else
|
||||
inputField.onValueChanged.AddListener(new Action<string>(OnTextChanged));
|
||||
#endif
|
||||
|
||||
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 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) / scaleFactor) + 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ public class SliderScrollbar
|
||||
|
||||
internal readonly Scrollbar m_scrollbar;
|
||||
internal readonly Slider m_slider;
|
||||
internal readonly RectTransform m_scrollRect;
|
||||
|
||||
public SliderScrollbar(Scrollbar scrollbar, Slider slider)
|
||||
{
|
||||
@ -22,6 +23,7 @@ public class SliderScrollbar
|
||||
|
||||
this.m_scrollbar = scrollbar;
|
||||
this.m_slider = slider;
|
||||
this.m_scrollRect = scrollbar.transform.parent.GetComponent<RectTransform>();
|
||||
|
||||
#if MONO
|
||||
this.m_scrollbar.onValueChanged.AddListener(this.OnScrollbarValueChanged);
|
||||
@ -73,7 +75,7 @@ public class SliderScrollbar
|
||||
public void OnScrollbarValueChanged(float _value)
|
||||
{
|
||||
if (this.m_slider.value != _value)
|
||||
this.m_slider.Set(_value, false);
|
||||
this.m_slider.Set(_value, false);
|
||||
}
|
||||
|
||||
public void OnSliderValueChanged(float _value)
|
||||
|
@ -2,6 +2,7 @@
|
||||
//using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.Shared;
|
||||
|
||||
namespace UnityExplorer.UI
|
||||
{
|
||||
@ -402,21 +403,40 @@ namespace UnityExplorer.UI
|
||||
return toggleObj;
|
||||
}
|
||||
|
||||
public static GameObject CreateSrollInputField(GameObject parent, out InputFieldScroller inputScroll, int fontSize = 14, Color color = default)
|
||||
{
|
||||
if (color == default)
|
||||
color = new Color(0.15f, 0.15f, 0.15f);
|
||||
|
||||
var mainObj = CreateScrollView(parent, out GameObject scrollContent, out SliderScrollbar scroller, color);
|
||||
|
||||
var inputObj = CreateInputField(scrollContent, fontSize, 0);
|
||||
|
||||
var inputField = inputObj.GetComponent<InputField>();
|
||||
inputField.lineType = InputField.LineType.MultiLineNewline;
|
||||
inputField.targetGraphic.color = color;
|
||||
|
||||
inputScroll = new InputFieldScroller(scroller, inputField);
|
||||
|
||||
return mainObj;
|
||||
}
|
||||
|
||||
public static GameObject CreateInputField(GameObject parent, int fontSize = 14, int alignment = 3, int wrap = 0)
|
||||
{
|
||||
GameObject mainObj = CreateUIObject("InputField", parent);
|
||||
|
||||
Image mainImage = mainObj.AddGraphic<Image>();
|
||||
mainImage.type = Image.Type.Sliced;
|
||||
mainImage.color = new Color(38f / 255f, 38f / 255f, 38f / 255f, 1.0f);
|
||||
mainImage.color = new Color(0.15f, 0.15f, 0.15f);
|
||||
|
||||
InputField mainInput = mainObj.AddComponent<InputField>();
|
||||
Navigation nav = mainInput.navigation;
|
||||
nav.mode = Navigation.Mode.None;
|
||||
mainInput.navigation = nav;
|
||||
mainInput.lineType = InputField.LineType.MultiLineNewline;
|
||||
mainInput.lineType = InputField.LineType.SingleLine;
|
||||
mainInput.interactable = true;
|
||||
mainInput.transition = Selectable.Transition.ColorTint;
|
||||
mainInput.targetGraphic = mainImage;
|
||||
|
||||
ColorBlock mainColors = mainInput.colors;
|
||||
mainColors.normalColor = new Color(1, 1, 1, 1);
|
||||
@ -458,8 +478,8 @@ namespace UnityExplorer.UI
|
||||
placeHolderRect.offsetMax = Vector2.zero;
|
||||
|
||||
LayoutElement placeholderLayout = placeHolderObj.AddComponent<LayoutElement>();
|
||||
placeholderLayout.preferredWidth = 990;
|
||||
placeholderLayout.flexibleWidth = 500;
|
||||
placeholderLayout.minWidth = 500;
|
||||
placeholderLayout.flexibleWidth = 5000;
|
||||
|
||||
mainInput.placeholder = placeholderText;
|
||||
|
||||
@ -479,8 +499,8 @@ namespace UnityExplorer.UI
|
||||
inputTextRect.offsetMax = Vector2.zero;
|
||||
|
||||
LayoutElement inputTextLayout = inputTextObj.AddComponent<LayoutElement>();
|
||||
inputTextLayout.preferredWidth = 990;
|
||||
inputTextLayout.flexibleWidth = 500;
|
||||
inputTextLayout.minWidth = 500;
|
||||
inputTextLayout.flexibleWidth = 5000;
|
||||
|
||||
mainInput.textComponent = inputText;
|
||||
|
||||
@ -671,19 +691,19 @@ namespace UnityExplorer.UI
|
||||
contentLayout.padding.bottom = 5;
|
||||
contentLayout.spacing = 5;
|
||||
|
||||
GameObject scrollBar = CreateUIObject("DynamicScrollbar", mainObj);
|
||||
GameObject scrollBarObj = CreateUIObject("DynamicScrollbar", mainObj);
|
||||
|
||||
var scrollbarLayout = scrollBar.AddComponent<VerticalLayoutGroup>();
|
||||
var scrollbarLayout = scrollBarObj.AddComponent<VerticalLayoutGroup>();
|
||||
scrollbarLayout.childForceExpandHeight = true;
|
||||
scrollbarLayout.childControlHeight = true;
|
||||
|
||||
RectTransform scrollBarRect = scrollBar.GetComponent<RectTransform>();
|
||||
RectTransform scrollBarRect = scrollBarObj.GetComponent<RectTransform>();
|
||||
scrollBarRect.anchorMin = new Vector2(1.0f, 0.0f);
|
||||
scrollBarRect.anchorMax = new Vector2(1.0f, 1.0f);
|
||||
scrollBarRect.sizeDelta = new Vector2(15.0f, 0.0f);
|
||||
scrollBarRect.offsetMin = new Vector2(-15.0f, 0.0f);
|
||||
|
||||
GameObject hiddenBar = CreateScrollbar(scrollBar);
|
||||
GameObject hiddenBar = CreateScrollbar(scrollBarObj);
|
||||
var hiddenScroll = hiddenBar.GetComponent<Scrollbar>();
|
||||
hiddenScroll.SetDirection(Scrollbar.Direction.BottomToTop, true);
|
||||
|
||||
@ -693,7 +713,7 @@ namespace UnityExplorer.UI
|
||||
child.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
SliderScrollbar.CreateSliderScrollbar(scrollBar, out Slider scrollSlider);
|
||||
SliderScrollbar.CreateSliderScrollbar(scrollBarObj, out Slider scrollSlider);
|
||||
|
||||
// Back to the main scrollview ScrollRect, setting it up now that we have all references.
|
||||
|
||||
|
@ -2,11 +2,12 @@
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Inspectors;
|
||||
using UnityExplorer.UI.PageModel;
|
||||
using UnityExplorer.UI.Modules;
|
||||
using System.IO;
|
||||
//using TMPro;
|
||||
using System.Reflection;
|
||||
using UnityExplorer.Helpers;
|
||||
using UnityExplorer.UI.Shared;
|
||||
#if CPP
|
||||
using UnityExplorer.Unstrip;
|
||||
#endif
|
||||
@ -22,7 +23,6 @@ namespace UnityExplorer.UI
|
||||
internal static Material UIMaterial { get; private set; }
|
||||
internal static Sprite ResizeCursor { get; private set; }
|
||||
internal static Font ConsoleFont { get; private set; }
|
||||
internal static Font DefaultFont { get; private set; }
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
@ -31,11 +31,12 @@ namespace UnityExplorer.UI
|
||||
{
|
||||
var bundle = AssetBundle.LoadFromFile(bundlePath);
|
||||
|
||||
UIMaterial = bundle.LoadAsset<Material>("UIMaterial");
|
||||
// Fix for games which don't ship with 'UI/Default' shader.
|
||||
Graphic.defaultGraphicMaterial.shader = bundle.LoadAsset<Shader>("DefaultUI");
|
||||
|
||||
ResizeCursor = bundle.LoadAsset<Sprite>("cursor");
|
||||
|
||||
ConsoleFont = bundle.LoadAsset<Font>("CONSOLA");
|
||||
DefaultFont = bundle.LoadAsset<Font>("CONSOLA");
|
||||
|
||||
ExplorerCore.Log("Loaded UI bundle");
|
||||
}
|
||||
@ -54,8 +55,8 @@ namespace UnityExplorer.UI
|
||||
|
||||
// Force refresh of anchors
|
||||
Canvas.ForceUpdateCanvases();
|
||||
CanvasRoot.SetActive(false);
|
||||
CanvasRoot.SetActive(true);
|
||||
//CanvasRoot.SetActive(false);
|
||||
//CanvasRoot.SetActive(true);
|
||||
}
|
||||
|
||||
public static void SetEventSystem()
|
||||
@ -109,6 +110,16 @@ namespace UnityExplorer.UI
|
||||
else
|
||||
slider.Update();
|
||||
}
|
||||
|
||||
for (int i = 0; i < InputFieldScroller.Instances.Count; i++)
|
||||
{
|
||||
var input = InputFieldScroller.Instances[i];
|
||||
|
||||
if (input.sliderScroller.CheckDestroyed())
|
||||
i--;
|
||||
else
|
||||
input.Update();
|
||||
}
|
||||
}
|
||||
|
||||
private static GameObject CreateRootCanvas()
|
||||
|
Reference in New Issue
Block a user