private static bool ListCreate(CustomSelectListCtrl __instance, CustomSelectListCtrl.OnChangeItemFunc _onChangeItemFunc,
                                           List <CustomSelectInfo> ___lstSelectInfo, GameObject ___objContent, GameObject ___objTemp)
            {
                __instance.onChangeItemFunc = _onChangeItemFunc;

                // Remove old list items if recreating the list
                foreach (Transform c in ___objContent.transform)
                {
                    Destroy(c.gameObject);
                }

                var toggleGroup = ___objContent.GetComponent <ToggleGroup>();

                toggleGroup.allowSwitchOff = true;

                // Figure out how many items fit in the window at most
                // Need to use this specific RT because it's the most reliable
                var windowRt = __instance.GetComponent <RectTransform>();

                if (ListWidth.Value != 3)
                {
                    windowRt.sizeDelta += new Vector2((ListWidth.Value - 3) * 120, 0);
                }

                var itemsInRow        = ((int)windowRt.rect.width - 33) / 120;
                var itemsInColumn     = ((int)windowRt.rect.height - 105) / 120 + 2;
                var totalVisibleItems = itemsInRow * itemsInColumn;

                // Create a cache of list items for the virtual list
                var spawnedItems     = new List <CustomSelectInfoComponent>();
                var setHandlerMethod = Traverse.Create(__instance).Method("SetToggleHandler", new[] { typeof(GameObject) });

                for (int i = 0; i < totalVisibleItems; i++)
                {
                    var copy         = Instantiate(___objTemp, ___objContent.transform, false);
                    var copyInfoComp = copy.GetComponent <CustomSelectInfoComponent>();

                    copyInfoComp.tgl.group = toggleGroup;
                    copyInfoComp.tgl.isOn  = false;

                    setHandlerMethod.GetValue(copy);

                    copyInfoComp.img = copy.GetComponent <Image>();

                    var newTr = copy.transform.Find("New");
                    if (newTr)
                    {
                        copyInfoComp.objNew = newTr.gameObject;
                    }

                    spawnedItems.Add(copyInfoComp);
                    copy.SetActive(false);
                }

                __instance.imgRaycast = spawnedItems.Select(x => x.img).ToArray();

                _listCache[__instance] = new VirtualListData(itemsInRow, spawnedItems, ___objContent.GetComponent <RectTransform>(), ___lstSelectInfo);

                return(false);
            }
            private static void ChangeItem(CustomSelectListCtrl __instance, CustomSelectInfo itemInfo, VirtualListData listData)
            {
                // Calling original whenever possible is probably better for interop since any hooks will run
                if (itemInfo.sic != null)
                {
                    __instance.ChangeItem(itemInfo.sic.gameObject);
                    return;
                }

                __instance.onChangeItemFunc?.Invoke(itemInfo.index);

                var tv = new Traverse(__instance);

                tv.Field <string>("selectDrawName").Value = itemInfo.name;
#if KK
                var tmp = tv.Field <TMPro.TextMeshProUGUI>("textDrawName").Value;
                if (tmp)
                {
                    tmp.text = itemInfo.name;
                }
#endif

                if (VirtualListData.IsItemNew(itemInfo))
                {
                    VirtualListData.MarkItemAsNotNew(itemInfo);
                    MarkListDirty(__instance);
                }

                listData.SelectedItem = itemInfo;
            }
            private static void ListUpdate(CustomSelectListCtrl __instance)
            {
                if (!_listCache.TryGetValue(__instance, out var listData))
                {
                    return;
                }

                var scrollPosition = listData.ScrollPositionY;
                // How many items are not visible in current view
                var visibleItemCount   = listData.ItemList.Count(x => !x.disvisible);
                var offscreenItemCount = Mathf.Max(0, visibleItemCount - listData.ItemCache.Count);
                // How many items are above current view rect and not visible
                var rowsAboveViewRect  = Mathf.FloorToInt(Mathf.Clamp(scrollPosition / listData.ItemHeight, 0, offscreenItemCount));
                var itemsAboveViewRect = rowsAboveViewRect * listData.ItemsInRow;

                if (listData.LastItemsAbove == itemsAboveViewRect && !listData.IsDirty)
                {
                    return;
                }

                listData.LastItemsAbove = itemsAboveViewRect;
                listData.IsDirty        = false;

                // Store selected item to preserve selection when moving the list with mouse
                var selectedItem = listData.ItemList.Find(x => x.sic != null && x.sic.gameObject == EventSystem.current.currentSelectedGameObject);

                listData.ItemList.ForEach(x => x.sic = null);
                // Apply visible list items to actual cache items
                var count = 0;

                foreach (var item in listData.ItemList.Where(x => !x.disvisible).Skip(itemsAboveViewRect))
                {
                    if (count >= listData.ItemCache.Count)
                    {
                        break;
                    }

                    var cachedEntry = listData.ItemCache[count];

                    count++;

                    cachedEntry.info = item;
                    item.sic         = cachedEntry;

                    cachedEntry.Disable(item.disable);

                    cachedEntry.objNew.SetActiveIfDifferent(VirtualListData.IsItemNew(item));

                    var thumb = listData.GetThumbSprite(item);
                    cachedEntry.img.sprite = thumb;

                    if (ReferenceEquals(selectedItem, item))
                    {
                        EventSystem.current.SetSelectedGameObject(cachedEntry.gameObject);
                    }

                    cachedEntry.gameObject.SetActiveIfDifferent(true);
                }

                // Disable unused cache items
                if (count < listData.ItemCache.Count)
                {
                    foreach (var cacheEntry in listData.ItemCache.Skip(count))
                    {
                        cacheEntry.gameObject.SetActiveIfDifferent(false);
                    }
                }

                listData.UpdateSelection();

                // Apply top and bottom offsets to create the illusion of having all of the list items
                var topOffset = Mathf.RoundToInt(rowsAboveViewRect * listData.ItemHeight);

                listData.LayoutGroup.padding.top = listData.InitialTopPadding + topOffset;

                var totalHeight        = Mathf.CeilToInt((float)visibleItemCount / listData.ItemsInRow) * listData.ItemHeight;
                var cacheEntriesHeight = Mathf.CeilToInt((float)listData.ItemCache.Count / listData.ItemsInRow) * listData.ItemHeight;
                var trailingHeight     = totalHeight - cacheEntriesHeight - topOffset;

                listData.LayoutGroup.padding.bottom = Mathf.FloorToInt(Mathf.Max(0, trailingHeight) + listData.InitialBotPadding);

                // Needed after changing padding since it doesn't make the object dirty
                LayoutRebuilder.MarkLayoutForRebuild(listData.Content);
            }