using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Templates;
using Avalonia.Media;
using Avalonia.Styling;
namespace Avalonia.Controls
{
///
/// An indexed dictionary of resources.
///
public class ResourceDictionary : IResourceDictionary
{
private object? lastDeferredItemKey;
private Dictionary? _inner;
private IResourceHost? _owner;
private AvaloniaList? _mergedDictionaries;
private AvaloniaDictionary? _themeDictionary;
///
/// Initializes a new instance of the class.
///
public ResourceDictionary() { }
///
/// Initializes a new instance of the class.
///
public ResourceDictionary(IResourceHost owner) => Owner = owner;
public int Count => _inner?.Count ?? 0;
public object? this[object key]
{
get
{
TryGetValue(key, out var value);
return value;
}
set
{
Inner[key] = value;
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
}
public ICollection Keys => (ICollection?)_inner?.Keys ?? Array.Empty();
public ICollection Values => (ICollection?)_inner?.Values ?? Array.Empty();
public IResourceHost? Owner
{
get => _owner;
private set
{
if (_owner != value)
{
_owner = value;
OwnerChanged?.Invoke(this, EventArgs.Empty);
}
}
}
public IList MergedDictionaries
{
get
{
if (_mergedDictionaries == null)
{
_mergedDictionaries = new AvaloniaList();
_mergedDictionaries.ResetBehavior = ResetBehavior.Remove;
_mergedDictionaries.ForEachItem(
x =>
{
if (Owner is not null)
{
x.AddOwner(Owner);
}
},
x =>
{
if (Owner is not null)
{
x.RemoveOwner(Owner);
}
},
() => throw new NotSupportedException("Dictionary reset not supported"));
}
return _mergedDictionaries;
}
}
public IDictionary ThemeDictionaries
{
get
{
if (_themeDictionary == null)
{
_themeDictionary = new AvaloniaDictionary(2);
_themeDictionary.ForEachItem(
(_, x) =>
{
if (Owner is not null)
{
x.AddOwner(Owner);
}
},
(_, x) =>
{
if (Owner is not null)
{
x.RemoveOwner(Owner);
}
},
() => throw new NotSupportedException("Dictionary reset not supported"));
}
return _themeDictionary;
}
}
bool IResourceNode.HasResources
{
get
{
if (_inner?.Count > 0)
{
return true;
}
if (_mergedDictionaries?.Count > 0)
{
foreach (var i in _mergedDictionaries)
{
if (i.HasResources)
{
return true;
}
}
}
return false;
}
}
bool ICollection>.IsReadOnly => false;
private Dictionary Inner => _inner ??= new();
public event EventHandler? OwnerChanged;
public void Add(object key, object? value)
{
Inner.Add(key, value);
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
public void AddDeferred(object key, Func factory)
{
Inner.Add(key, new DeferredItem(factory));
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
public void Clear()
{
if (_inner?.Count > 0)
{
_inner.Clear();
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
}
public bool ContainsKey(object key) => _inner?.ContainsKey(key) ?? false;
public bool Remove(object key)
{
if (_inner?.Remove(key) == true)
{
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
return true;
}
return false;
}
public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
{
if (TryGetValue(key, out value))
return true;
if (_themeDictionary is not null)
{
IResourceProvider? themeResourceProvider;
if (theme is not null && theme != ThemeVariant.Default)
{
if (_themeDictionary.TryGetValue(theme, out themeResourceProvider)
&& themeResourceProvider.TryGetResource(key, theme, out value))
{
return true;
}
var themeInherit = theme.InheritVariant;
while (themeInherit is not null)
{
if (_themeDictionary.TryGetValue(themeInherit, out themeResourceProvider)
&& themeResourceProvider.TryGetResource(key, theme, out value))
{
return true;
}
themeInherit = themeInherit.InheritVariant;
}
}
if (_themeDictionary.TryGetValue(ThemeVariant.Default, out themeResourceProvider)
&& themeResourceProvider.TryGetResource(key, theme, out value))
{
return true;
}
}
if (_mergedDictionaries != null)
{
for (var i = _mergedDictionaries.Count - 1; i >= 0; --i)
{
if (_mergedDictionaries[i].TryGetResource(key, theme, out value))
{
return true;
}
}
}
value = null;
return false;
}
public bool TryGetValue(object key, out object? value)
{
if (_inner is not null && _inner.TryGetValue(key, out value))
{
if (value is DeferredItem deffered)
{
// Avoid simple reentrancy, which could commonly occur on redefining the resource.
if (lastDeferredItemKey == key)
{
value = null;
return false;
}
try
{
lastDeferredItemKey = key;
_inner[key] = value = deffered.Factory(null) switch
{
ITemplateResult t => t.Result,
{ } v => v,
_ => null,
};
}
finally
{
lastDeferredItemKey = null;
}
}
return true;
}
value = null;
return false;
}
public IEnumerator> GetEnumerator()
{
return _inner?.GetEnumerator() ?? Enumerable.Empty>().GetEnumerator();
}
void ICollection>.Add(KeyValuePair item)
{
Add(item.Key, item.Value);
}
bool ICollection>.Contains(KeyValuePair item)
{
return (_inner as ICollection>)?.Contains(item) ?? false;
}
void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
{
(_inner as ICollection>)?.CopyTo(array, arrayIndex);
}
bool ICollection>.Remove(KeyValuePair item)
{
if ((_inner as ICollection>)?.Remove(item) == true)
{
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
return true;
}
return false;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
internal bool ContainsDeferredKey(object key)
{
if (_inner is not null && _inner.TryGetValue(key, out var result))
{
return result is DeferredItem;
}
return false;
}
void IResourceProvider.AddOwner(IResourceHost owner)
{
owner = owner ?? throw new ArgumentNullException(nameof(owner));
if (Owner != null)
{
throw new InvalidOperationException("The ResourceDictionary already has a parent.");
}
Owner = owner;
var hasResources = _inner?.Count > 0;
if (_mergedDictionaries is not null)
{
foreach (var i in _mergedDictionaries)
{
i.AddOwner(owner);
hasResources |= i.HasResources;
}
}
if (_themeDictionary is not null)
{
foreach (var i in _themeDictionary.Values)
{
i.AddOwner(owner);
hasResources |= i.HasResources;
}
}
if (hasResources)
{
owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
}
void IResourceProvider.RemoveOwner(IResourceHost owner)
{
owner = owner ?? throw new ArgumentNullException(nameof(owner));
if (Owner == owner)
{
Owner = null;
var hasResources = _inner?.Count > 0;
if (_mergedDictionaries is not null)
{
foreach (var i in _mergedDictionaries)
{
i.RemoveOwner(owner);
hasResources |= i.HasResources;
}
}
if (_themeDictionary is not null)
{
foreach (var i in _themeDictionary.Values)
{
i.RemoveOwner(owner);
hasResources |= i.HasResources;
}
}
if (hasResources)
{
owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
}
}
private class DeferredItem
{
public DeferredItem(Func factory) => Factory = factory;
public Func Factory { get; }
}
}
}