canto/Assets/Scripts/Editor/Windows/DatabaseEditor.cs

288 lines
6.9 KiB
C#

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<IDatabase> databases = new();
public IReadOnlyObservableList<IDatabase> 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<IDatabase>();
this.databases.AddRange(databases);
OnPropertyChanged(nameof(Databases));
}
public long GetViewHashCode()
{
return databases.GetHashCode();
}
}
[ObservableObject]
public partial class DatabaseEditorViewModel
{
[ObservableProperty]
[AlsoNotifyChangeFor(nameof(Rows))]
private IOption<IDatabase> selected = Option.None<IDatabase>();
[CreateProperty(ReadOnly = true)]
public IEnumerable<object> Rows => selected.MapOr(
Enumerable.Empty<object>(),
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<IDatabase> 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<IDatabase> 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<IDatabase> xs) => (IList)xs.ToList());
SetBinding(nameof(itemsSource), binding);
}
public DatabaseListView() : base()
{
makeItem = MakeItem;
bindItem = BindItem;
unbindItem = UnbindItem;
selectionChanged += OnSelectedItem;
}
private void OnSelectedItem(IEnumerable<object> 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<IServiceProvider> provider = new(() =>
{
var services = new ServiceCollection();
services.AddSingleton<DatabaseModel>()
.AddTransient<DatabaseEditorViewModel>()
.AddTransient<DatabaseListViewModel>()
.AddTransient<DatabaseEditorView>()
.AddTransient<DatabaseListView>();
return services.BuildServiceProvider();
});
[MenuItem("Tools/KitsuneCafe/Database")]
public static void Init()
{
DatabaseEditorWindow wnd = GetWindow<DatabaseEditorWindow>();
wnd.titleContent = new GUIContent("Database");
Vector2 size = new(800, 600);
wnd.minSize = size;
}
public void CreateGUI()
{
rootVisualElement.Add(provider.Value.GetRequiredService<DatabaseEditorView>());
}
}
}