A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

312 lines
8.9 KiB

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using Avalonia.Collections;
using Avalonia.Controls;
#nullable enable
namespace Avalonia.Styling
{
/// <summary>
/// A style that consists of a number of child styles.
/// </summary>
public class Styles : AvaloniaObject,
IAvaloniaList<IStyle>,
IStyle,
IResourceProvider
{
private readonly AvaloniaList<IStyle> _styles = new();
private IResourceHost? _owner;
private IResourceDictionary? _resources;
private StyleCache? _cache;
public Styles()
{
_styles.ResetBehavior = ResetBehavior.Remove;
_styles.CollectionChanged += OnCollectionChanged;
}
public Styles(IResourceHost owner)
: this()
{
Owner = owner;
}
public event NotifyCollectionChangedEventHandler? CollectionChanged;
public event EventHandler? OwnerChanged;
public int Count => _styles.Count;
public IResourceHost? Owner
{
get => _owner;
private set
{
if (_owner != value)
{
_owner = value;
OwnerChanged?.Invoke(this, EventArgs.Empty);
}
}
}
/// <summary>
/// Gets or sets a dictionary of style resources.
/// </summary>
public IResourceDictionary Resources
{
get => _resources ?? (Resources = new ResourceDictionary());
set
{
value = value ?? throw new ArgumentNullException(nameof(Resources));
var currentOwner = Owner;
if (currentOwner is not null)
{
_resources?.RemoveOwner(currentOwner);
}
_resources = value;
if (currentOwner is not null)
{
_resources.AddOwner(currentOwner);
}
}
}
bool ICollection<IStyle>.IsReadOnly => false;
bool IResourceNode.HasResources
{
get
{
if (_resources?.Count > 0)
{
return true;
}
foreach (var i in this)
{
if (i is IResourceProvider { HasResources: true })
{
return true;
}
}
return false;
}
}
IStyle IReadOnlyList<IStyle>.this[int index] => _styles[index];
IReadOnlyList<IStyle> IStyle.Children => this;
public IStyle this[int index]
{
get => _styles[index];
set => _styles[index] = value;
}
public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host)
{
_cache ??= new StyleCache();
return _cache.TryAttach(this, target, host);
}
/// <inheritdoc/>
public bool TryGetResource(object key, out object? value)
{
if (_resources != null && _resources.TryGetResource(key, out value))
{
return true;
}
for (var i = Count - 1; i >= 0; --i)
{
if (this[i].TryGetResource(key, out value))
{
return true;
}
}
value = null;
return false;
}
/// <inheritdoc/>
public void AddRange(IEnumerable<IStyle> items) => _styles.AddRange(items);
/// <inheritdoc/>
public void InsertRange(int index, IEnumerable<IStyle> items) => _styles.InsertRange(index, items);
/// <inheritdoc/>
public void Move(int oldIndex, int newIndex) => _styles.Move(oldIndex, newIndex);
/// <inheritdoc/>
public void MoveRange(int oldIndex, int count, int newIndex) => _styles.MoveRange(oldIndex, count, newIndex);
/// <inheritdoc/>
public void RemoveAll(IEnumerable<IStyle> items) => _styles.RemoveAll(items);
/// <inheritdoc/>
public void RemoveRange(int index, int count) => _styles.RemoveRange(index, count);
/// <inheritdoc/>
public int IndexOf(IStyle item) => _styles.IndexOf(item);
/// <inheritdoc/>
public void Insert(int index, IStyle item) => _styles.Insert(index, item);
/// <inheritdoc/>
public void RemoveAt(int index) => _styles.RemoveAt(index);
/// <inheritdoc/>
public void Add(IStyle item) => _styles.Add(item);
/// <inheritdoc/>
public void Clear() => _styles.Clear();
/// <inheritdoc/>
public bool Contains(IStyle item) => _styles.Contains(item);
/// <inheritdoc/>
public void CopyTo(IStyle[] array, int arrayIndex) => _styles.CopyTo(array, arrayIndex);
/// <inheritdoc/>
public bool Remove(IStyle item) => _styles.Remove(item);
public AvaloniaList<IStyle>.Enumerator GetEnumerator() => _styles.GetEnumerator();
/// <inheritdoc/>
IEnumerator<IStyle> IEnumerable<IStyle>.GetEnumerator() => _styles.GetEnumerator();
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => _styles.GetEnumerator();
/// <inheritdoc/>
void IResourceProvider.AddOwner(IResourceHost owner)
{
owner = owner ?? throw new ArgumentNullException(nameof(owner));
if (Owner is not null)
{
throw new InvalidOperationException("The Styles already has a owner.");
}
Owner = owner;
_resources?.AddOwner(owner);
foreach (var child in this)
{
if (child is IResourceProvider r)
{
r.AddOwner(owner);
}
}
}
/// <inheritdoc/>
void IResourceProvider.RemoveOwner(IResourceHost owner)
{
owner = owner ?? throw new ArgumentNullException(nameof(owner));
if (Owner == owner)
{
Owner = null;
_resources?.RemoveOwner(owner);
foreach (var child in this)
{
if (child is IResourceProvider r)
{
r.RemoveOwner(owner);
}
}
}
}
private static IReadOnlyList<T> ToReadOnlyList<T>(ICollection list)
{
if (list is IReadOnlyList<T> readOnlyList)
{
return readOnlyList;
}
var result = new T[list.Count];
list.CopyTo(result, 0);
return result;
}
private static void InternalAdd(IList items, IResourceHost? owner, ref StyleCache? cache)
{
if (owner is not null)
{
for (var i = 0; i < items.Count; ++i)
{
if (items[i] is IResourceProvider provider)
{
provider.AddOwner(owner);
}
}
(owner as IStyleHost)?.StylesAdded(ToReadOnlyList<IStyle>(items));
}
if (items.Count > 0)
{
cache = null;
}
}
private static void InternalRemove(IList items, IResourceHost? owner, ref StyleCache? cache)
{
if (owner is not null)
{
for (var i = 0; i < items.Count; ++i)
{
if (items[i] is IResourceProvider provider)
{
provider.RemoveOwner(owner);
}
}
(owner as IStyleHost)?.StylesRemoved(ToReadOnlyList<IStyle>(items));
}
if (items.Count > 0)
{
cache = null;
}
}
private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Reset)
{
throw new InvalidOperationException("Reset should not be called on Styles.");
}
var currentOwner = Owner;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
InternalAdd(e.NewItems!, currentOwner, ref _cache);
break;
case NotifyCollectionChangedAction.Remove:
InternalRemove(e.OldItems!, currentOwner, ref _cache);
break;
case NotifyCollectionChangedAction.Replace:
InternalRemove(e.OldItems!, currentOwner, ref _cache);
InternalAdd(e.NewItems!, currentOwner, ref _cache);
break;
}
CollectionChanged?.Invoke(this, e);
}
}
}