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.
 
 
 

294 lines
9.0 KiB

// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls;
namespace Avalonia.Styling
{
/// <summary>
/// A style that consists of a number of child styles.
/// </summary>
public class Styles : AvaloniaObject, IAvaloniaList<IStyle>, IStyle, ISetResourceParent
{
private IResourceNode _parent;
private IResourceDictionary _resources;
private AvaloniaList<IStyle> _styles = new AvaloniaList<IStyle>();
private Dictionary<Type, List<IStyle>> _cache;
public Styles()
{
_styles.ResetBehavior = ResetBehavior.Remove;
_styles.ForEachItem(
x =>
{
if (x.ResourceParent == null && x is ISetResourceParent setParent)
{
setParent.SetParent(this);
setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
}
if (x.HasResources)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
x.ResourcesChanged += SubResourceChanged;
_cache = null;
},
x =>
{
if (x.ResourceParent == this && x is ISetResourceParent setParent)
{
setParent.SetParent(null);
setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
}
if (x.HasResources)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
x.ResourcesChanged -= SubResourceChanged;
_cache = null;
},
() => { });
}
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add => _styles.CollectionChanged += value;
remove => _styles.CollectionChanged -= value;
}
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <inheritdoc/>
public int Count => _styles.Count;
/// <inheritdoc/>
public bool HasResources => _resources?.Count > 0 || this.Any(x => x.HasResources);
/// <summary>
/// Gets or sets a dictionary of style resources.
/// </summary>
public IResourceDictionary Resources
{
get => _resources ?? (Resources = new ResourceDictionary());
set
{
Contract.Requires<ArgumentNullException>(value != null);
var hadResources = false;
if (_resources != null)
{
hadResources = _resources.Count > 0;
_resources.ResourcesChanged -= ResourceDictionaryChanged;
}
_resources = value;
_resources.ResourcesChanged += ResourceDictionaryChanged;
if (hadResources || _resources.Count > 0)
{
((ISetResourceParent)this).ParentResourcesChanged(new ResourcesChangedEventArgs());
}
}
}
/// <inheritdoc/>
IResourceNode IResourceNode.ResourceParent => _parent;
/// <inheritdoc/>
bool ICollection<IStyle>.IsReadOnly => false;
/// <inheritdoc/>
IStyle IReadOnlyList<IStyle>.this[int index] => _styles[index];
/// <inheritdoc/>
public IStyle this[int index]
{
get => _styles[index];
set => _styles[index] = value;
}
/// <summary>
/// Attaches the style to a control if the style's selector matches.
/// </summary>
/// <param name="control">The control to attach to.</param>
/// <param name="container">
/// The control that contains this style. May be null.
/// </param>
public bool Attach(IStyleable control, IStyleHost container)
{
if (_cache == null)
{
_cache = new Dictionary<Type, List<IStyle>>();
}
if (_cache.TryGetValue(control.StyleKey, out var cached))
{
if (cached != null)
{
foreach (var style in cached)
{
style.Attach(control, container);
}
return true;
}
return false;
}
else
{
List<IStyle> result = null;
foreach (var style in this)
{
if (style.Attach(control, container))
{
if (result == null)
{
result = new List<IStyle>();
}
result.Add(style);
}
}
_cache.Add(control.StyleKey, result);
return result != null;
}
}
public void Detach()
{
foreach (IStyle style in this)
{
style.Detach();
}
}
/// <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 ISetResourceParent.SetParent(IResourceNode parent)
{
if (_parent != null && parent != null)
{
throw new InvalidOperationException("The Style already has a parent.");
}
_parent = parent;
}
/// <inheritdoc/>
void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, e);
}
private void ResourceDictionaryChanged(object sender, ResourcesChangedEventArgs e)
{
foreach (var child in this)
{
(child as ISetResourceParent)?.ParentResourcesChanged(e);
}
ResourcesChanged?.Invoke(this, e);
}
private void SubResourceChanged(object sender, ResourcesChangedEventArgs e)
{
var foundSource = false;
foreach (var child in this)
{
if (foundSource)
{
(child as ISetResourceParent)?.ParentResourcesChanged(e);
}
foundSource |= child == sender;
}
ResourcesChanged?.Invoke(this, e);
}
}
}