Skip to content

Instantly share code, notes, and snippets.

@scho

scho/IView.cs Secret

Last active April 8, 2024 13:15
Show Gist options
  • Save scho/d737ec33aa3b5b1e7fc8e04990d866d2 to your computer and use it in GitHub Desktop.
Save scho/d737ec33aa3b5b1e7fc8e04990d866d2 to your computer and use it in GitHub Desktop.
Minimalistic MVVM for Unity, UI Toolkit and UniRX
using System;
using UnityEngine.UIElements;
namespace ProjectHive.UI.UI
{
public interface IView<in TViewModel> : IView
{
IDisposable Bind(TViewModel viewModel);
}
public interface IView : IDisposable
{
VisualElement TemplateRoot { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine.UIElements;
namespace ProjectHive.UI.UI
{
[AttributeUsage(AttributeTargets.Field)]
public class QueryAttribute : Attribute
{
public string Q { get; }
public QueryAttribute(string q)
{
Q = q;
}
}
public static class QueryAttributeExtensions
{
private static readonly IDictionary<Type, IList<Tuple<FieldInfo, QueryAttribute>>> TypeCache =
new Dictionary<Type, IList<Tuple<FieldInfo, QueryAttribute>>>();
public static void ResolveQueryAttributes<T>(this T view) where T : IView
{
var viewType = typeof(T);
if (!TypeCache.ContainsKey(viewType))
{
var result = new List<Tuple<FieldInfo, QueryAttribute>>();
var fieldInfos = viewType.Assembly.GetTypes()
.Where(type => type.IsClass)
.Where(type => type.IsAssignableFrom(viewType))
.SelectMany(type => type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic));
foreach (var fieldInfo in fieldInfos)
{
var attribute = (QueryAttribute) fieldInfo.GetCustomAttributes(false)
.FirstOrDefault(attribute => attribute is QueryAttribute);
if (attribute != null)
{
result.Add(Tuple.Create(fieldInfo, attribute));
}
}
TypeCache.Add(viewType, result);
}
var fields = TypeCache[viewType];
for (var i = 0; i < fields.Count; i++)
{
var (fieldInfo, attribute) = fields[i];
var qParts = attribute.Q.Split('.');
var visualElement = view.TemplateRoot;
for (var iQPart = 0; iQPart < qParts.Length; iQPart++)
{
var qPart = qParts[iQPart];
if (iQPart < qParts.Length - 1 || fieldInfo.FieldType == typeof(VisualElement))
{
visualElement = visualElement.Q(qPart);
}
else
{
visualElement = visualElement.Query()
.Where(e => e.GetType() == fieldInfo.FieldType && qPart.Equals(e.name))
.Build()
.First();
}
}
fieldInfo.SetValue(view, visualElement);
}
}
}
}
using System;
using JetBrains.Annotations;
using UniRx;
using UnityEngine.UIElements;
namespace ProjectHive.UI.UI
{
public abstract class View<T> : IView<T>
{
private VisualElement _templateRoot;
public VisualElement TemplateRoot
{
get => _templateRoot;
set
{
_templateRoot = value;
Initialize();
}
}
private CompositeDisposable _disposables;
public CompositeDisposable Disposables => _disposables ??= new CompositeDisposable();
protected abstract void Initialize();
[MustUseReturnValue]
public abstract IDisposable Bind(T viewModel);
public void Dispose()
{
_disposables?.Dispose();
_disposables = null;
}
}
public static class ViewExtensions
{
public static T AddTo<T, TViewModel>(this T disposable, View<TViewModel> view) where T : IDisposable
{
view.Disposables.Add(disposable);
return disposable;
}
public static void Add(this VisualElement element, IView view)
{
element.Add(view.TemplateRoot);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment