using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using UnityEngine; namespace RosettaUI { public static partial class UI { #region targetExpression public static Element List ( Expression> targetExpression, in ListViewOption option ) where TList : IList => List(ExpressionUtility.CreateLabelString(targetExpression), targetExpression, null, option); public static Element List( Expression> targetExpression, Func createItemElement = null, in ListViewOption? option = null ) where TList : IList => List(ExpressionUtility.CreateLabelString(targetExpression), targetExpression, createItemElement, option); public static Element List( LabelElement label, Expression> targetExpression, in ListViewOption option ) where TList : IList => List(label, targetExpression, null, option); public static Element List( LabelElement label, Expression> targetExpression, Func createItemElement = null, in ListViewOption? option = null ) where TList : IList { return List( label, ExpressionUtility.CreateBinder(targetExpression), createItemElement, option ?? CalcDefaultOptionOf(targetExpression) ); } #endregion #region readValue/writeValue public static Element List( Expression> targetExpression, Action writeValue, in ListViewOption option ) where TList : IList => List(targetExpression, writeValue, null, option); public static Element List( LabelElement label, Func readValue, Action writeValue, in ListViewOption option ) where TList : IList => List(label, readValue, writeValue, null, option); public static Element List( Expression> targetExpression, Action writeValue, Func createItemElement = null, in ListViewOption? option = null ) where TList : IList { return List( ExpressionUtility.CreateLabelString(targetExpression), targetExpression.Compile(), writeValue, createItemElement, option ?? CalcDefaultOptionOf(targetExpression)); } public static Element List( LabelElement label, Func readValue, Action writeValue, Func createItemElement = null, in ListViewOption? option = null ) where TList : IList => List(label, Binder.Create(readValue, writeValue), createItemElement, option); #endregion #region ReadOnly public static Element ListReadOnly( Expression> targetExpression, in ListViewOption option ) where TList : IList => ListReadOnly(targetExpression, null, option); public static Element ListReadOnly( Expression> targetExpression, Func createItemElement = null, in ListViewOption? option = null ) where TList : IList { return List( ExpressionUtility.CreateLabelString(targetExpression), UIInternalUtility.CreateReadOnlyBinder(targetExpression), createItemElement, option ?? CalcDefaultOptionOf(targetExpression)); } public static Element ListReadOnly( LabelElement label, Func readValue, in ListViewOption option ) where TList : IList => ListReadOnly(label, readValue, null, option); public static Element ListReadOnly( LabelElement label, Func readValue, Func createItemElement = null, in ListViewOption? option = null ) where TList : IList { var binder = Binder.Create(readValue, null); return List(label, binder, createItemElement, option); } #endregion #region Core private static readonly Dictionary> ListItemLabelGetterCache = new(); public static Element List(LabelElement label, IBinder listBinder, Func createItemElement = null, in ListViewOption? optionNullable = null) { var option = optionNullable ?? ListViewOption.Default; var listItemContainer = ListItemContainer(listBinder, createItemElement, option); var ret = listItemContainer; if (option.header) { var countField = ListCounterField(listBinder, listItemContainer, option); ret = Fold( Row(label, Space(), countField).AddClipboardMenu(listBinder, FieldOption.Default), new[] { listItemContainer } ).Open(); } UIInternalUtility.SetInteractableWithBinder(ret, listBinder); return ret; } public static Element ListCounterField(IBinder listBinder, Element itemContainerElement, in ListViewOption option) { var interactable = !ListBinder.IsReadOnly(listBinder) && !option.fixedSize; return Field(null, () => ListBinder.GetCount(listBinder), count => { // ListViewItemContainerElementが存在していたら新しいcountを通知 // NullGuardで存在してない場合もありそのときはlistBinderに直接セットする var containerElement = itemContainerElement.Query().FirstOrDefault(); if (containerElement != null) { containerElement.ListItemCount = count; } else { ListBinder.SetCount(listBinder, count); } }, new FieldOption { delayInput = true } ).SetMinWidth(32f).SetInteractable(interactable); } private static Element ListItemContainer(IBinder listBinder, Func createItemElement, in ListViewOption option) { var optionCaptured = option; return NullGuard(null, listBinder, () => new ListViewItemContainerElement( listBinder, createItemElement ?? ListItemDefault, optionCaptured) ).SetFlexShrink(1f); } public static Element ListItemDefault(IBinder binder, int index) { #if !ENABLE_IL2CPP var valueType = binder.ValueType; if (valueType is { IsClass: true } or { IsValueType: true, IsPrimitive: false, IsEnum: false } && valueType != typeof(string)) { // The first field of the class/struct type is string, use that value as the label if (!ListItemLabelGetterCache.TryGetValue(valueType, out var getter)) { // Search for the first field of string var firstField = TypeUtility.GetUITargetFieldNames(valueType) .Select(n => TypeUtility.GetMemberInfo(valueType, n)) .OfType() .FirstOrDefault(); if (firstField != null && firstField.FieldType == typeof(string)) { // Expression Tree: object obj => ((valueType) obj).firstField var inputParam = Expression.Parameter(typeof(object), "obj"); // object obj var getFieldExp = Expression.Field(Expression.Convert(inputParam, valueType), firstField); // ((valueType) obj).firstField getter = Expression.Lambda>(getFieldExp, inputParam).Compile(); // obj => getFieldExp } else { getter = _ => null; } ListItemLabelGetterCache[valueType] = getter; } return Field(Label(() => { object obj = binder.GetObject(); string label = obj == null ? string.Empty : getter(obj); return string.IsNullOrEmpty(label) ? $"Item {index}" : label; }), binder); } #endif return Field($"Item {index}", binder); } private static ListViewOption? CalcDefaultOptionOf(LambdaExpression expression) => ExpressionUtility.GetAttribute(expression) != null ? new ListViewOption(reorderable: false) : null; #endregion } }