using System; using Avalonia.Reactive; using Avalonia.Styling; #nullable enable namespace Avalonia.Controls { public static class ResourceNodeExtensions { /// /// Finds the specified resource by searching up the logical tree and then global styles. /// /// The control. /// The resource key. /// The resource, or if not found. public static object? FindResource(this IResourceHost control, object key) { control = control ?? throw new ArgumentNullException(nameof(control)); key = key ?? throw new ArgumentNullException(nameof(key)); if (control.TryFindResource(key, out var value)) { return value; } return AvaloniaProperty.UnsetValue; } /// /// Tries to the specified resource by searching up the logical tree and then global styles. /// /// The control. /// The resource key. /// On return, contains the resource if found, otherwise null. /// True if the resource was found; otherwise false. public static bool TryFindResource(this IResourceHost control, object key, out object? value) { control = control ?? throw new ArgumentNullException(nameof(control)); key = key ?? throw new ArgumentNullException(nameof(key)); return control.TryFindResource(key, null, out value); } /// /// Finds the specified resource by searching up the logical tree and then global styles. /// /// The control. /// Theme used to select theme dictionary. /// The resource key. /// The resource, or if not found. public static object? FindResource(this IResourceHost control, ThemeVariant? theme, object key) { control = control ?? throw new ArgumentNullException(nameof(control)); key = key ?? throw new ArgumentNullException(nameof(key)); if (control.TryFindResource(key, theme, out var value)) { return value; } return AvaloniaProperty.UnsetValue; } /// /// Tries to the specified resource by searching up the logical tree and then global styles. /// /// The control. /// The resource key. /// Theme used to select theme dictionary. /// On return, contains the resource if found, otherwise null. /// True if the resource was found; otherwise false. public static bool TryFindResource(this IResourceHost control, object key, ThemeVariant? theme, out object? value) { control = control ?? throw new ArgumentNullException(nameof(control)); key = key ?? throw new ArgumentNullException(nameof(key)); IResourceHost? current = control; while (current != null) { if (current.TryGetResource(key, theme, out value)) { return true; } current = (current as IStyleHost)?.StylingParent as IResourceHost; } value = null; return false; } /// public static bool TryGetResource(this IResourceHost control, object key, out object? value) { control = control ?? throw new ArgumentNullException(nameof(control)); key = key ?? throw new ArgumentNullException(nameof(key)); return control.TryGetResource(key, null, out value); } public static IObservable GetResourceObservable( this IResourceHost control, object key, Func? converter = null) { control = control ?? throw new ArgumentNullException(nameof(control)); key = key ?? throw new ArgumentNullException(nameof(key)); return new ResourceObservable(control, key, converter); } public static IObservable GetResourceObservable( this IResourceProvider resourceProvider, object key, Func? converter = null) { resourceProvider = resourceProvider ?? throw new ArgumentNullException(nameof(resourceProvider)); key = key ?? throw new ArgumentNullException(nameof(key)); return new FloatingResourceObservable(resourceProvider, key, converter); } private class ResourceObservable : LightweightObservableBase { private readonly IResourceHost _target; private readonly object _key; private readonly Func? _converter; public ResourceObservable(IResourceHost target, object key, Func? converter) { _target = target; _key = key; _converter = converter; } protected override void Initialize() { _target.ResourcesChanged += ResourcesChanged; if (_target is IThemeVariantHost themeVariantHost) { themeVariantHost.ActualThemeVariantChanged += ActualThemeVariantChanged; } } protected override void Deinitialize() { _target.ResourcesChanged -= ResourcesChanged; if (_target is IThemeVariantHost themeVariantHost) { themeVariantHost.ActualThemeVariantChanged -= ActualThemeVariantChanged; } } protected override void Subscribed(IObserver observer, bool first) { observer.OnNext(GetValue()); } private void ResourcesChanged(object? sender, ResourcesChangedEventArgs e) { PublishNext(GetValue()); } private void ActualThemeVariantChanged(object? sender, EventArgs e) { PublishNext(GetValue()); } private object? GetValue() { if (_target is not IThemeVariantHost themeVariantHost || !_target.TryFindResource(_key, themeVariantHost.ActualThemeVariant, out var value)) { value = _target.FindResource(_key) ?? AvaloniaProperty.UnsetValue; } return _converter?.Invoke(value) ?? value; } } private class FloatingResourceObservable : LightweightObservableBase { private readonly IResourceProvider _target; private readonly object _key; private readonly Func? _converter; private IResourceHost? _owner; public FloatingResourceObservable(IResourceProvider target, object key, Func? converter) { _target = target; _key = key; _converter = converter; } protected override void Initialize() { _target.OwnerChanged += OwnerChanged; _owner = _target.Owner; if (_owner is not null) { _owner.ResourcesChanged += ResourcesChanged; } } protected override void Deinitialize() { _target.OwnerChanged -= OwnerChanged; _owner = null; } protected override void Subscribed(IObserver observer, bool first) { if (_target.Owner is not null) { observer.OnNext(GetValue()); } } private void PublishNext() { if (_target.Owner is not null) { PublishNext(GetValue()); } } private void OwnerChanged(object? sender, EventArgs e) { if (_owner is not null) { _owner.ResourcesChanged -= ResourcesChanged; } if (_owner is IThemeVariantHost themeVariantHost) { themeVariantHost.ActualThemeVariantChanged += ActualThemeVariantChanged; } _owner = _target.Owner; if (_owner is not null) { _owner.ResourcesChanged += ResourcesChanged; } if (_owner is IThemeVariantHost themeVariantHost2) { themeVariantHost2.ActualThemeVariantChanged -= ActualThemeVariantChanged; } PublishNext(); } private void ActualThemeVariantChanged(object? sender, EventArgs e) { PublishNext(); } private void ResourcesChanged(object? sender, ResourcesChangedEventArgs e) { PublishNext(); } private object? GetValue() { if (!(_target.Owner is IThemeVariantHost themeVariantHost) || !_target.Owner.TryFindResource(_key, themeVariantHost.ActualThemeVariant, out var value)) { value = _target.Owner?.FindResource(_key) ?? AvaloniaProperty.UnsetValue; } return _converter?.Invoke(value) ?? value; } } } }