using System; using System.Collections; using System.Collections.Generic; using System.Linq; using KitsuneCafe.Extension; using KitsuneCafe.Sys; using ObservableCollections; using Unity.AppUI.MVVM; using Unity.Properties; using UnityEditor; using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; namespace KitsuneCafe.SOAP { [ObservableObject] public partial class DatabaseModel : INotifyBindablePropertyChanged, IDataSourceViewHashProvider { private readonly ObservableList databases = new(); public IReadOnlyObservableList Databases => databases; public DatabaseModel() { RefreshDatabaseCache(); } public void RefreshDatabaseCache() { OnPropertyChanging(nameof(Databases)); this.databases.Clear(); var databases = AssetDatabase.FindAssets( $"t:{typeof(Database<>).Name} a:assets", new[] { "Assets/" } ) .Select(AssetDatabase.GUIDToAssetPath) .Select(path => AssetDatabase.LoadAssetAtPath(path, typeof(IDatabase))) .Cast(); this.databases.AddRange(databases); OnPropertyChanged(nameof(Databases)); } public long GetViewHashCode() { return databases.GetHashCode(); } } [ObservableObject] public partial class DatabaseEditorViewModel { [ObservableProperty] [AlsoNotifyChangeFor(nameof(Rows))] private IOption selected = Option.None(); [CreateProperty(ReadOnly = true)] public IEnumerable Rows => selected.MapOr( Enumerable.Empty(), db => ((dynamic)db).Rows ); private readonly DatabaseModel model; public DatabaseEditorViewModel(DatabaseModel model) { this.model = model; } [ICommand] private void Select(IDatabase database) { Selected = Option.Some(database); } [ICommand] private void Refresh() { model.RefreshDatabaseCache(); } } [ObservableObject] public partial class DatabaseListViewModel : INotifyBindablePropertyChanged, IDataSourceViewHashProvider { private readonly DatabaseModel model; [ObservableProperty] private List databases; public DatabaseListViewModel(DatabaseModel model) { this.model = model; databases = model.Databases.ToList(); } public long GetViewHashCode() { return databases.GetHashCode(); } } [UxmlElement] public partial class DatabaseListView : ListView { private readonly DatabaseListViewModel viewModel; public event Action DatabaseSelected = delegate { }; public DatabaseListView(DatabaseListViewModel viewModel) : this() { this.viewModel = viewModel; dataSource = viewModel; var binding = new DataBinding { dataSourcePath = PropertyPath.FromName(nameof(DatabaseListViewModel.Databases)) }; binding.sourceToUiConverters.AddConverter((ref IReadOnlyObservableList xs) => (IList)xs.ToList()); SetBinding(nameof(itemsSource), binding); } public DatabaseListView() : base() { makeItem = MakeItem; bindItem = BindItem; unbindItem = UnbindItem; selectionChanged += OnSelectedItem; } private void OnSelectedItem(IEnumerable enumerable) { DatabaseSelected.Invoke((IDatabase)selectedItem); } private void UnbindItem(VisualElement element, int index) { } private void BindItem(VisualElement element, int index) { if (element is Label label) { label.text = viewModel.Databases[index].Name; } } private VisualElement MakeItem() { return new Label(); } } public class DatabaseItemEditorView : VisualElement { private SerializedObject serializedObject; [CreateProperty] public SerializedObject SerializedObject { get => serializedObject; set { if (serializedObject != value) { serializedObject = value; Rebuild(); } } } public DatabaseItemEditorView() { } public void Rebuild() { if (serializedObject == null) { return; } foreach (var property in serializedObject.Enumerate()) { Add(new PropertyField(property)); } } } public class DatabaseEditorView : VisualElement { private readonly DatabaseEditorViewModel viewModel; public DatabaseEditorView( DatabaseEditorViewModel viewModel, DatabaseListView databaseListView ) { this.viewModel = viewModel; var toolbar = new Toolbar(); var refresh = new ToolbarButton(viewModel.RefreshCommand.Execute) { text = "Refresh" }; toolbar.Add(refresh); Add(toolbar); var container = new VisualElement(); container.style.flexDirection = FlexDirection.Row; databaseListView.DatabaseSelected += viewModel.SelectCommand.Execute; container.Add(databaseListView); var rowListView = new ListView { dataSource = viewModel, makeItem = () => new Label(), bindItem = (ve, i) => ((Label)ve).text = ((UnityEngine.Object)viewModel.Rows.ElementAt(i)).name }; var itemsBinding = new DataBinding { dataSourcePath = PropertyPath.FromName(nameof(DatabaseEditorViewModel.Rows)) }; rowListView.SetBinding( nameof(ListView.itemsSource), itemsBinding ); container.Add(rowListView); var editor = new DatabaseItemEditorView { dataSource = rowListView }; var selectedBinding = new DataBinding { dataSourcePath = PropertyPath.FromName(nameof(ListView.selectedItem)) }; selectedBinding.sourceToUiConverters.AddConverter( (ref object selected) => selected == null ? null : new SerializedObject((UnityEngine.Object)selected) ); editor.SetBinding( nameof(DatabaseItemEditorView.SerializedObject), selectedBinding ); Add(editor); Add(container); } } public class DatabaseEditorWindow : EditorWindow { public static Lazy provider = new(() => { var services = new ServiceCollection(); services.AddSingleton() .AddTransient() .AddTransient() .AddTransient() .AddTransient(); return services.BuildServiceProvider(); }); [MenuItem("Tools/KitsuneCafe/Database")] public static void Init() { DatabaseEditorWindow wnd = GetWindow(); wnd.titleContent = new GUIContent("Database"); Vector2 size = new(800, 600); wnd.minSize = size; } public void CreateGUI() { rootVisualElement.Add(provider.Value.GetRequiredService()); } } }