diff --git a/src/UI/Panels/InspectorTest.cs b/src/UI/Panels/InspectorTest.cs index 7e3a50b..9c75f91 100644 --- a/src/UI/Panels/InspectorTest.cs +++ b/src/UI/Panels/InspectorTest.cs @@ -53,19 +53,19 @@ namespace UnityExplorer.UI.Panels //scrollPool.ReloadData(); } - //scrollPool.Refresh(); + scrollPool.RefreshCells(true); } public override void SetDefaultPosAndAnchors() { - mainPanelRect.localPosition = Vector2.zero; - mainPanelRect.anchorMin = new Vector2(1, 0); - mainPanelRect.anchorMax = new Vector2(1, 1); - mainPanelRect.sizeDelta = new Vector2(-300f, mainPanelRect.sizeDelta.y); - mainPanelRect.anchoredPosition = new Vector2(-200, 0); + mainPanelRect.localPosition = Vector2.zero; + mainPanelRect.pivot = new Vector2(0.5f, 0.5f); + mainPanelRect.anchorMin = new Vector2(0.5f, 0); + mainPanelRect.anchorMax = new Vector2(0.5f, 1); mainPanelRect.offsetMin = new Vector2(mainPanelRect.offsetMin.x, 100); // bottom mainPanelRect.offsetMax = new Vector2(mainPanelRect.offsetMax.x, -50); // top - mainPanelRect.pivot = new Vector2(0.5f, 0.5f); + mainPanelRect.sizeDelta = new Vector2(700f, mainPanelRect.sizeDelta.y); + mainPanelRect.anchoredPosition = new Vector2(-150, 0); } private ScrollPool scrollPool; @@ -83,14 +83,14 @@ namespace UnityExplorer.UI.Panels var test = new DynamicListTest(scrollPool, this); test.Init(); - var prototype = DynamicCellTest.CreatePrototypeCell(scrollContent); + var prototype = DynamicCell.CreatePrototypeCell(scrollContent); scrollPool.PrototypeCell = prototype.GetComponent(); dummyContentHolder = new GameObject("DummyHolder"); dummyContentHolder.SetActive(false); GameObject.DontDestroyOnLoad(dummyContentHolder); - for (int i = 0; i < 10; i++) + for (int i = 0; i < 100; i++) { dummyContents.Add(CreateDummyContent()); } @@ -104,15 +104,25 @@ namespace UnityExplorer.UI.Panels private GameObject CreateDummyContent() { var obj = UIFactory.CreateVerticalGroup(dummyContentHolder, "Content", true, true, true, true, 2, new Vector4(2, 2, 2, 2)); - UIFactory.SetLayoutElement(obj, minHeight: 25, flexibleHeight: 9999); + //UIFactory.SetLayoutElement(obj, minHeight: 25, flexibleHeight: 9999); + obj.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; var label = UIFactory.CreateLabel(obj, "label", "Dummy " + dummyContents.Count, TextAnchor.MiddleCenter); UIFactory.SetLayoutElement(label.gameObject, minHeight: 25, flexibleHeight: 0); - var inputObj = UIFactory.CreateInputField(obj, "input", "...", out InputField inputField); - inputField.lineType = InputField.LineType.MultiLineNewline; + //var input = UIFactory.CreateSrollInputField(obj, "input2", "...", out InputFieldScroller inputScroller); + //UIFactory.SetLayoutElement(input, minHeight: 50, flexibleHeight: 9999); + //input.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + + var inputObj = UIFactory.CreateInputField(obj, "input", "...", out var inputField); UIFactory.SetLayoutElement(inputObj, minHeight: 25, flexibleHeight: 9999); - //inputObj.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + inputObj.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + inputField.lineType = InputField.LineType.MultiLineNewline; + + int numLines = UnityEngine.Random.Range(0, 10); + inputField.text = "This field has " + numLines + " lines"; + for (int i = 0; i < numLines; i++) + inputField.text += "\r\n"; return obj; } @@ -138,7 +148,7 @@ namespace UnityExplorer.UI.Panels Scroller.Initialize(this); } - public ICell CreateCell(RectTransform cellTransform) => new DynamicCellTest(cellTransform.gameObject); + public ICell CreateCell(RectTransform cellTransform) => new DynamicCell(cellTransform.gameObject); public void SetCell(ICell icell, int index) { @@ -148,58 +158,16 @@ namespace UnityExplorer.UI.Panels return; } - var root = (icell as DynamicCellTest).uiRoot; + var root = (icell as DynamicCell).uiRoot; + var content = Inspector.dummyContents[index]; + + if (content.transform.parent.ReferenceEqual(root.transform)) + return; if (root.transform.Find("Content") is Transform existing) - { - ExplorerCore.Log("removing existing content"); existing.transform.SetParent(Inspector.dummyContentHolder.transform, false); - } - var content = Inspector.dummyContents[index]; content.transform.SetParent(root.transform, false); } } - - public class DynamicCellTest : ICell - { - public DynamicCellTest(GameObject uiRoot) - { - this.uiRoot = uiRoot; - } - - public bool Enabled => m_enabled; - private bool m_enabled; - - public GameObject uiRoot; - public InputField input; - - public void Disable() - { - m_enabled = false; - uiRoot.SetActive(false); - } - - public void Enable() - { - m_enabled = true; - uiRoot.SetActive(true); - } - - public static GameObject CreatePrototypeCell(GameObject parent) - { - var prototype = UIFactory.CreateVerticalGroup(parent, "PrototypeCell", true, true, true, true, 2, default, - new Color(0.15f, 0.15f, 0.15f), TextAnchor.MiddleCenter); - var rect = prototype.GetComponent(); - 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(prototype, minWidth: 100, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 9999); - - prototype.SetActive(false); - - return prototype; - } - } } diff --git a/src/UI/UIFactory.cs b/src/UI/UIFactory.cs index 5a59dac..9f20d47 100644 --- a/src/UI/UIFactory.cs +++ b/src/UI/UIFactory.cs @@ -461,7 +461,7 @@ namespace UnityExplorer.UI var mainObj = CreateScrollView(parent, "InputFieldScrollView", out GameObject scrollContent, out SliderScrollbar scroller, color); - var inputObj = CreateInputField(scrollContent, name, placeHolderText ?? "...", out InputField inputField, fontSize, 0); + CreateInputField(scrollContent, name, placeHolderText ?? "...", out InputField inputField, fontSize, 0); inputField.lineType = InputField.LineType.MultiLineNewline; inputField.targetGraphic.color = color; @@ -471,6 +471,31 @@ namespace UnityExplorer.UI return mainObj; } + // Little helper class to force rebuild of an input field's layout on value change. + // This is limited to once per frame per input field, so its not too expensive. + private class InputFieldRefresher + { + private float timeOfLastRebuild; + private readonly RectTransform rectTransform; + + public InputFieldRefresher(InputField inputField) + { + if (!inputField) + return; + + rectTransform = inputField.GetComponent(); + + inputField.onValueChanged.AddListener((string val) => + { + if (Time.time > timeOfLastRebuild) + { + timeOfLastRebuild = Time.time; + LayoutRebuilder.ForceRebuildLayoutImmediate(rectTransform); + } + }); + } + } + /// /// Create a standard InputField control. /// @@ -546,6 +571,8 @@ namespace UnityExplorer.UI inputField.textComponent = inputText; + new InputFieldRefresher(inputField); + return mainObj; } @@ -715,14 +742,17 @@ namespace UnityExplorer.UI var contentRect = content.GetComponent(); contentRect.anchorMin = Vector2.zero; contentRect.anchorMax = Vector2.one; - contentRect.pivot = new Vector2(0.0f, 1.0f); + contentRect.pivot = new Vector2(0.5f, 1f); contentRect.sizeDelta = new Vector2(0f, 0f); contentRect.offsetMax = new Vector2(0f, 0f); + SetLayoutGroup(content, true, false, true, true, 0, 2, 2, 2, 2, TextAnchor.UpperCenter); + + content.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + var scrollRect = mainObj.AddComponent(); scrollRect.movementType = ScrollRect.MovementType.Clamped; scrollRect.inertia = false; - //scrollRect.decelerationRate = 0.135f; scrollRect.scrollSensitivity = 15; scrollRect.horizontal = false; scrollRect.vertical = true; @@ -730,6 +760,8 @@ namespace UnityExplorer.UI 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); @@ -753,6 +785,8 @@ namespace UnityExplorer.UI container.pivot = new Vector3(0.5f, 0.5f); } + // finalize and create ScrollPool + uiRoot = mainObj; var scrollPool = new ScrollPool(scrollRect) @@ -760,14 +794,8 @@ namespace UnityExplorer.UI AutoResizeHandleRect = autoResizeSliderHandle }; - SetLayoutGroup(content, true, false, true, false, 0, 2, 2, 2, 2, - TextAnchor.UpperCenter); - //viewportObj.GetComponent().enabled = false; - var rect = content.GetComponent(); - rect.pivot = new Vector2(0.5f, 1f); - return scrollPool; } diff --git a/src/UI/Widgets/ButtonList/ButtonCell.cs b/src/UI/Widgets/ButtonList/ButtonCell.cs index 86960de..205f290 100644 --- a/src/UI/Widgets/ButtonList/ButtonCell.cs +++ b/src/UI/Widgets/ButtonList/ButtonCell.cs @@ -14,13 +14,13 @@ namespace UnityExplorer.UI.Widgets public Action> OnClick; - public ButtonListCell list; + public ButtonListSource list; public GameObject uiRoot; public Text buttonText; public Button button; - public ButtonCell(ButtonListCell list, GameObject uiRoot, Button button, Text text) + public ButtonCell(ButtonListSource list, GameObject uiRoot, Button button, Text text) { this.list = list; this.uiRoot = uiRoot; diff --git a/src/UI/Widgets/ButtonList/ButtonListCell.cs b/src/UI/Widgets/ButtonList/ButtonListSource.cs similarity index 94% rename from src/UI/Widgets/ButtonList/ButtonListCell.cs rename to src/UI/Widgets/ButtonList/ButtonListSource.cs index f187d40..e0dcca1 100644 --- a/src/UI/Widgets/ButtonList/ButtonListCell.cs +++ b/src/UI/Widgets/ButtonList/ButtonListSource.cs @@ -9,7 +9,7 @@ using UnityExplorer.UI.Widgets; namespace UnityExplorer.UI.Widgets { - public class ButtonListCell : IPoolDataSource + public class ButtonListSource : IPoolDataSource { internal ScrollPool Scroller; @@ -28,7 +28,7 @@ namespace UnityExplorer.UI.Widgets } private string currentFilter; - public ButtonListCell(ScrollPool infiniteScroller, Func> getEntriesMethod, + public ButtonListSource(ScrollPool infiniteScroller, Func> getEntriesMethod, Action, int> setICellMethod, Func shouldDisplayMethod, Action> onCellClickedMethod) { diff --git a/src/UI/Widgets/ScrollPool/DynamicCell.cs b/src/UI/Widgets/ScrollPool/DynamicCell.cs new file mode 100644 index 0000000..2a6f60d --- /dev/null +++ b/src/UI/Widgets/ScrollPool/DynamicCell.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; + +namespace UnityExplorer.UI.Widgets +{ + public class DynamicCell : ICell + { + public DynamicCell(GameObject uiRoot) + { + this.uiRoot = uiRoot; + } + + public bool Enabled => m_enabled; + private bool m_enabled; + + public GameObject uiRoot; + public InputField input; + + public void Disable() + { + m_enabled = false; + uiRoot.SetActive(false); + } + + public void Enable() + { + m_enabled = true; + uiRoot.SetActive(true); + } + + public static GameObject CreatePrototypeCell(GameObject parent) + { + var prototype = UIFactory.CreateVerticalGroup(parent, "PrototypeCell", true, true, true, true, 0, new Vector4(1,1,1,1), + new Color(0.15f, 0.15f, 0.15f), TextAnchor.MiddleCenter); + var rect = prototype.GetComponent(); + 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(prototype, minWidth: 100, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 9999); + + prototype.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + + prototype.SetActive(false); + + return prototype; + } + } +} diff --git a/src/UI/Widgets/ScrollPool/ScrollPool.cs b/src/UI/Widgets/ScrollPool/ScrollPool.cs index 144d120..cf7333b 100644 --- a/src/UI/Widgets/ScrollPool/ScrollPool.cs +++ b/src/UI/Widgets/ScrollPool/ScrollPool.cs @@ -47,7 +47,7 @@ namespace UnityExplorer.UI.Widgets if (index >= heightCache.Count) { while (index > heightCache.Count) - heightCache.Add(defaultCellHeight); + Add(defaultCellHeight); Add(value); return; } @@ -138,7 +138,7 @@ namespace UnityExplorer.UI.Widgets if (TotalCellHeight.Equals(value)) return; m_totalCellHeight = value; - SetContentHeight(); + //SetContentHeight(); } } private float m_totalCellHeight; @@ -153,28 +153,27 @@ namespace UnityExplorer.UI.Widgets private Vector2 _prevAnchoredPos; private Vector2 _prevViewportSize; // TODO track viewport height and rebuild on change - #region internal set tracking and update + #region Internal set tracking and update - //private bool _recycling; - - public bool ExternallySetting + // A sanity check so only one thing is setting the value per frame. + public bool WritingLocked { - get => externallySetting; + get => writingLocked; internal set { - if (externallySetting == value) + if (writingLocked == value) return; - timeOfLastExternalSet = Time.time; - externallySetting = value; + timeofLastWriteLock = Time.time; + writingLocked = value; } } - private bool externallySetting; - private float timeOfLastExternalSet; + private bool writingLocked; + private float timeofLastWriteLock; public override void Update() { - if (externallySetting && timeOfLastExternalSet < Time.time) - externallySetting = false; + if (writingLocked && timeofLastWriteLock < Time.time) + writingLocked = false; } #endregion @@ -214,7 +213,7 @@ namespace UnityExplorer.UI.Widgets BuildInitialHeightCache(); CreateCellPool(); - SetContentHeight(); + //SetContentHeight(); UpdateSliderPositionAndSize(); @@ -241,14 +240,6 @@ namespace UnityExplorer.UI.Widgets RecycleViewBounds = new Vector2(Viewport.MinY() + extra, Viewport.MaxY() - extra); } - private void SetContentHeight() - { - var viewRect = scrollRect.viewport; - scrollRect.content.sizeDelta = new Vector2( - scrollRect.content.sizeDelta.x, - AdjustedTotalCellHeight - viewRect.rect.height); - } - // Refresh methods private struct CellInfo { public int cellIndex, dataIndex; } @@ -275,8 +266,6 @@ namespace UnityExplorer.UI.Widgets } } - // TODO this is not quite right, it can move the content, it shouldnt move it - public void RefreshCells(bool andReloadFromDataSource = false) { if (!CellPool.Any()) return; @@ -310,7 +299,6 @@ namespace UnityExplorer.UI.Widgets } SetRecycleViewBounds(); - SetContentHeight(); if (andReloadFromDataSource) { @@ -318,7 +306,6 @@ namespace UnityExplorer.UI.Widgets RecycleTopToBottom(); } - SetContentHeight(); UpdateSliderPositionAndSize(); if (jumpToBottom) @@ -335,49 +322,14 @@ namespace UnityExplorer.UI.Widgets LayoutRebuilder.ForceRebuildLayoutImmediate(Content); - // ExplorerCore.Log("Set cell, real height is " + cachedCell.Rect.rect.height + ", pref height is " + cachedCell.Rect.GetComponent().preferredHeight); - cachedCell.Height = cachedCell.Cell.Enabled ? cachedCell.Rect.rect.height : 0f; DataHeightCache[dataIndex] = cachedCell.Height; - //ExplorerCore.Log("set to cache as " + cachedCell.Height); } - //private void UpdateDisplayedHeightCache() - //{ - // if (!CellPool.Any()) return; - - // var enumerator = GetPoolEnumerator(); - // while (enumerator.MoveNext()) - // { - // var curr = enumerator.Current; - // var cell = CellPool[curr.cellIndex]; - // cell.Height = cell.Rect.rect.height; - // DataHeightCache[curr.dataIndex] = cell.Height; - // } - - // //int cellIdx = topPoolCellIndex; - // //int dataIndex = topDataIndex; - // //int iterated = 0; - // //while (iterated < CellPool.Count) - // //{ - // // var cell = CellPool[cellIdx]; - // // cellIdx++; - // // if (cellIdx >= CellPool.Count) - // // cellIdx = 0; - // // - // // cell.Height = cell.Rect.rect.height; - // // DataHeightCache[dataIndex] = cell.Height; - // // - // // dataIndex++; - // // iterated++; - // //} - //} - // Cell pool private void CreateCellPool() { - //Reseting Pool if (CellPool.Any()) { foreach (var cell in CellPool) @@ -386,10 +338,7 @@ namespace UnityExplorer.UI.Widgets } if (!PrototypeCell) - { - ExplorerCore.Log("no prototype cell, cannot initialize"); - return; - } + throw new Exception("No prototype cell set, cannot initialize"); //Set the prototype cell active and set cell anchor as top PrototypeCell.gameObject.SetActive(true); @@ -398,7 +347,6 @@ namespace UnityExplorer.UI.Widgets float requiredCoverage = scrollRect.viewport.rect.height * ExtraPoolCoverageRatio; topPoolCellIndex = 0; - //topDataIndex = 0; bottomPoolIndex = -1; // create cells until the Pool area is covered. @@ -414,13 +362,14 @@ namespace UnityExplorer.UI.Widgets CellPool.Add(new CachedCell(this, rect, cell)); rect.SetParent(scrollRect.content, false); + cell.Disable(); + currentPoolCoverage += rect.rect.height + this.contentLayout.spacing; } bottomDataIndex = bottomPoolIndex; // after creating pool, set displayed cells. - //posY = 0f; for (int i = 0; i < CellPool.Count; i++) { var cell = CellPool[i]; @@ -436,7 +385,7 @@ namespace UnityExplorer.UI.Widgets private void OnValueChangedListener(Vector2 val) { - if (ExternallySetting) + if (WritingLocked) return; SetRecycleViewBounds(); @@ -468,23 +417,20 @@ namespace UnityExplorer.UI.Widgets } private bool ShouldRecycleTop => GetCellExtent(CellPool[topPoolCellIndex]) >= RecycleViewBounds.x - //&& CellPool[topMostCellIndex].Rect.position.y >= Viewport.MinY() && CellPool[bottomPoolIndex].Rect.position.y > RecycleViewBounds.y; private bool ShouldRecycleBottom => GetCellExtent(CellPool[bottomPoolIndex]) < RecycleViewBounds.y - //&& CellPool[bottomMostCellIndex].Rect.position.y < Viewport.MaxY() && CellPool[topPoolCellIndex].Rect.position.y < RecycleViewBounds.x; private float GetCellExtent(CachedCell cell) => cell.Rect.MaxY() - contentLayout.spacing; private float RecycleTopToBottom() { - ExternallySetting = true; + WritingLocked = true; float recycledheight = 0f; while (ShouldRecycleTop && CurrentDataCount < DataSource.ItemCount) - //while (GetCellExtent(CellPool[topMostCellIndex]) > Viewport.MinY() && CurrentDataCount < DataSource.ItemCount) { var cell = CellPool[topPoolCellIndex]; @@ -500,7 +446,6 @@ namespace UnityExplorer.UI.Widgets SetCell(cell, CurrentDataCount); //set new indices - //topDataIndex++; bottomDataIndex++; bottomPoolIndex = topPoolCellIndex; @@ -512,12 +457,10 @@ namespace UnityExplorer.UI.Widgets private float RecycleBottomToTop() { - ExternallySetting = true; + WritingLocked = true; float recycledheight = 0f; - // works, except when moving+resizing a cell at the top, that seems to cause issues, need to fix that. - while (ShouldRecycleBottom && CurrentDataCount > CellPool.Count) { var cell = CellPool[bottomPoolIndex]; @@ -531,7 +474,6 @@ namespace UnityExplorer.UI.Widgets recycledheight += prevHeight + contentLayout.spacing; //set new index - //topDataIndex--; bottomDataIndex--; //set Cell @@ -542,7 +484,6 @@ namespace UnityExplorer.UI.Widgets var diff = newHeight - prevHeight; if (diff != 0.0f) { - SetContentHeight(); Content.anchoredPosition += Vector2.up * diff; recycledheight += diff; } @@ -601,9 +542,9 @@ namespace UnityExplorer.UI.Widgets private void OnSliderValueChanged(float val) { - if (this.ExternallySetting) + if (this.WritingLocked) return; - this.ExternallySetting = true; + this.WritingLocked = true; // TODO this cant work until we have a cache of all data heights. // will need to maintain that as we go and assume default height for indeterminate cells. diff --git a/src/UI/Widgets/ScrollPool/ScrollPoolBak.cs b/src/UI/Widgets/ScrollPool/ScrollPool_bak.cs similarity index 100% rename from src/UI/Widgets/ScrollPool/ScrollPoolBak.cs rename to src/UI/Widgets/ScrollPool/ScrollPool_bak.cs diff --git a/src/UnityExplorer.csproj b/src/UnityExplorer.csproj index 9dc30d6..76aad54 100644 --- a/src/UnityExplorer.csproj +++ b/src/UnityExplorer.csproj @@ -272,14 +272,15 @@ + - + - + @@ -290,9 +291,7 @@ - - - +