Files
SourMesen 40335ce735 UI: Update to Avalonia 12, .NET 10 and replace ReactiveUI with MVVM community toolkit (#142)
Also fixes DX11.0 support (for Win7 PCs that aren't fully updated), which stopped working after the recent variable refresh rate changes
2026-07-03 16:04:42 +09:00

204 lines
6.3 KiB
C#

using Avalonia;
using Avalonia.Reactive;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
namespace Mesen.Utilities
{
public static class ReactiveHelper
{
public static IDisposable RegisterObserver<T>(T target, string propertyName, Action action) where T : INotifyPropertyChanged
{
return RegisterObserver([target], [propertyName], action);
}
public static IDisposable RegisterObserver<T>(T[] targets, string propertyName, Action action) where T : INotifyPropertyChanged
{
return RegisterObserver(targets, [propertyName], action);
}
public static IDisposable RegisterObserver<T>(T target, string[] properties, Action action) where T : INotifyPropertyChanged
{
return RegisterObserver([target], properties, action);
}
public static IDisposable RegisterObserver<T>(T[] targets, string[] properties, Action action) where T : INotifyPropertyChanged
{
HashSet<string> props = new(properties);
PropertyChangedEventHandler handler = (s, e) => {
if(e.PropertyName != null && props.Contains(e.PropertyName)) {
action();
}
};
foreach(T target in targets) {
target.PropertyChanged += handler;
}
action();
return new DisposableObserver(() => {
foreach(INotifyPropertyChanged target in targets) {
target.PropertyChanged -= handler;
}
});
}
public static IDisposable RegisterForeignObserver((Func<ObservableObject>, string)[] targets, Action action)
{
List<Action> elementsToDispose = new();
Action dispose = () => {
foreach(Action disposeAction in elementsToDispose) {
disposeAction();
}
};
Action? initObserver = null;
initObserver = () => {
foreach((Func<ObservableObject> targetFunc, string prop) in targets) {
PropertyChangedEventHandler handler = (s, e) => {
if(e.PropertyName == prop) {
action();
dispose();
initObserver?.Invoke();
}
};
ObservableObject target = targetFunc();
target.PropertyChanged += handler;
elementsToDispose.Add(() => { target.PropertyChanged -= handler; });
}
};
initObserver();
action();
return new DisposableObserver(dispose);
}
private static PropertyInfo[] GetProperties(ObservableObject target)
{
#pragma warning disable IL2075 // 'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The return value of the source method does not have matching annotations.
return target.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
#pragma warning restore IL2075 // 'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The return value of the source method does not have matching annotations.
}
public static IDisposable RegisterRecursiveObserver(ObservableObject target, PropertyChangedEventHandler handler)
{
Dictionary<string, ObservableObject> observableObjects = new();
Dictionary<string, PropertyInfo> props = new();
foreach(PropertyInfo prop in GetProperties(target)) {
if(prop.GetCustomAttribute<ObservablePropertyAttribute>() != null) {
object? value = prop.GetValue(target);
if(value is ObservableObject propValue) {
observableObjects[prop.Name] = propValue;
props[prop.Name] = prop;
ReactiveHelper.RegisterRecursiveObserver(propValue, handler);
} else if(value is IList list) {
foreach(object listValue in list) {
if(listValue is ObservableObject) {
ReactiveHelper.RegisterRecursiveObserver((ObservableObject)listValue, handler);
}
}
}
}
}
target.PropertyChanged += (s, e) => {
handler(s, e);
//Reset change handlers if an object is replaced with another object
if(e.PropertyName != null && observableObjects.TryGetValue(e.PropertyName, out ObservableObject? obj)) {
//Remove handlers on the old object
ReactiveHelper.UnregisterRecursiveObserver(obj, handler);
if(props.TryGetValue(e.PropertyName, out PropertyInfo? prop)) {
object? value = prop.GetValue(target);
if(value is ObservableObject propValue) {
observableObjects[prop.Name] = propValue;
//Register change handlers on the new object
ReactiveHelper.RegisterRecursiveObserver(propValue, handler);
}
}
}
};
return new RecursiveObserver(target, handler);
}
public static void UnregisterRecursiveObserver(ObservableObject target, PropertyChangedEventHandler handler)
{
foreach(PropertyInfo prop in GetProperties(target)) {
if(prop.GetCustomAttribute<ObservablePropertyAttribute>() != null) {
object? value = prop.GetValue(target);
if(value is ObservableObject propValue) {
ReactiveHelper.UnregisterRecursiveObserver(propValue, handler);
} else if(value is IList list) {
foreach(object listValue in list) {
if(listValue is ObservableObject) {
ReactiveHelper.UnregisterRecursiveObserver((ObservableObject)listValue, handler);
}
}
}
}
}
target.PropertyChanged -= handler;
}
}
public class RecursiveObserver : IDisposable
{
private ObservableObject _target;
private PropertyChangedEventHandler _handler;
public RecursiveObserver(ObservableObject target, PropertyChangedEventHandler handler)
{
_target = target;
_handler = handler;
}
public void Dispose()
{
ReactiveHelper.UnregisterRecursiveObserver(_target, _handler);
}
}
public class DisposableObserver : IDisposable
{
private Action DisposeCallback { get; }
public DisposableObserver(Action dispose)
{
DisposeCallback = dispose;
}
public void Dispose()
{
DisposeCallback();
}
}
public static class ReactiveExtensions
{
public static IDisposable ObserveProp(this AvaloniaObject obj, AvaloniaProperty prop, Action<AvaloniaPropertyChangedEventArgs> action)
{
return obj.GetPropertyChangedObservable(prop).Subscribe(new AnonymousObserver<AvaloniaPropertyChangedEventArgs>(action));
}
public static IDisposable ObserveProp(this INotifyPropertyChanged obj, string property, Action action)
{
return ReactiveHelper.RegisterObserver(obj, property, action);
}
public static IDisposable ObserveProp(this INotifyPropertyChanged obj, string[] properties, Action action)
{
return ReactiveHelper.RegisterObserver(obj, properties, action);
}
}
}