Browse Source

Initial implementation of add/remove styles.

Only currently works on top-level `Styles` collections, not working in `Application.Styles`.
pull/3647/head
Steven Kirk 6 years ago
parent
commit
e9256a9e40
  1. 10
      src/Avalonia.Controls/Application.cs
  2. 7
      src/Avalonia.Layout/Layoutable.cs
  3. 102
      src/Avalonia.Styling/StyledElement.cs
  4. 16
      src/Avalonia.Styling/Styling/IStyleHost.cs
  5. 11
      src/Avalonia.Styling/Styling/IStyleable.cs
  6. 130
      src/Avalonia.Styling/Styling/Styles.cs
  7. 11
      tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs

10
src/Avalonia.Controls/Application.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Reactive.Concurrency;
using System.Threading;
using Avalonia.Animation;
@ -214,6 +215,14 @@ namespace Avalonia
Styles.TryGetResource(key, out value);
}
void IStyleHost.StylesAdded(IReadOnlyList<IStyle> styles)
{
}
void IStyleHost.StylesRemoved(IReadOnlyList<IStyle> styles)
{
}
/// <summary>
/// Register's the services needed by Avalonia.
/// </summary>
@ -286,6 +295,5 @@ namespace Avalonia
get => _name;
set => SetAndRaise(NameProperty, ref _name, value);
}
}
}

7
src/Avalonia.Layout/Layoutable.cs

@ -507,6 +507,7 @@ namespace Avalonia.Layout
{
var margin = Margin;
ApplyStyling();
ApplyTemplate();
var constrained = LayoutHelper.ApplyLayoutConstraints(
@ -692,6 +693,12 @@ namespace Avalonia.Layout
return finalSize;
}
protected sealed override void InvalidateStyles()
{
base.InvalidateStyles();
InvalidateMeasure();
}
/// <inheritdoc/>
protected sealed override void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
{

102
src/Avalonia.Styling/StyledElement.cs

@ -352,21 +352,34 @@ namespace Avalonia
if (--_initCount == 0 && _logicalRoot != null)
{
InitializeStylesIfNeeded();
ApplyStyling();
InitializeIfNeeded();
}
}
private void InitializeStylesIfNeeded(bool force = false)
/// <summary>
/// Applies styling to the control if the control is initialized and styling is not
/// already applied.
/// </summary>
/// <returns>
/// A value indicating whether styling is now applied to the control.
/// </returns>
protected bool ApplyStyling()
{
if (_initCount == 0 && (!_styled || force))
if (_initCount == 0 && !_styled)
{
ApplyStyling();
AvaloniaLocator.Current.GetService<IStyler>()?.ApplyStyles(this);
_styled = true;
}
return _styled;
}
/// <summary>
/// Detaches all styles from the element and queues a restyle.
/// </summary>
protected virtual void InvalidateStyles() => DetachStyles();
protected void InitializeIfNeeded()
{
if (_initCount == 0 && !IsInitialized)
@ -480,6 +493,20 @@ namespace Avalonia
void IStyleable.DetachStyles() => DetachStyles();
void IStyleable.DetachStyles(IReadOnlyList<IStyle> styles) => DetachStyles(styles);
void IStyleable.InvalidateStyles() => InvalidateStyles();
void IStyleHost.StylesAdded(IReadOnlyList<IStyle> styles)
{
InvalidateStylesOnThisAndDescendents();
}
void IStyleHost.StylesRemoved(IReadOnlyList<IStyle> styles)
{
DetachStylesFromThisAndDescendents(styles);
}
protected virtual void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
@ -604,11 +631,6 @@ namespace Avalonia
return null;
}
private void ApplyStyling()
{
AvaloniaLocator.Current.GetService<IStyler>()?.ApplyStyles(this);
}
private static void ValidateLogicalChild(ILogical c)
{
if (c == null)
@ -635,7 +657,7 @@ namespace Avalonia
{
_logicalRoot = e.Root;
InitializeStylesIfNeeded(true);
ApplyStyling();
OnAttachedToLogicalTree(e);
AttachedToLogicalTree?.Invoke(this, e);
@ -713,6 +735,64 @@ namespace Avalonia
_appliedStyles.Clear();
}
_styled = false;
}
private void DetachStyles(IReadOnlyList<IStyle> styles)
{
styles = styles ?? throw new ArgumentNullException(nameof(styles));
if (_appliedStyles is null)
{
return;
}
var count = styles.Count;
for (var i = 0; i < count; ++i)
{
for (var j = _appliedStyles.Count - 1; j >= 0; --j)
{
var applied = _appliedStyles[j];
if (applied.Source == styles[i])
{
applied.Dispose();
_appliedStyles.RemoveAt(j);
}
}
}
}
private void InvalidateStylesOnThisAndDescendents()
{
InvalidateStyles();
if (_logicalChildren is object)
{
var childCount = _logicalChildren.Count;
for (var i = 0; i < childCount; ++i)
{
(_logicalChildren[i] as StyledElement)?.InvalidateStylesOnThisAndDescendents();
}
}
}
private void DetachStylesFromThisAndDescendents(IReadOnlyList<IStyle> styles)
{
DetachStyles(styles);
if (_logicalChildren is object)
{
var childCount = _logicalChildren.Count;
for (var i = 0; i < childCount; ++i)
{
(_logicalChildren[i] as StyledElement)?.DetachStylesFromThisAndDescendents(styles);
}
}
}
private void ClearLogicalParent(IEnumerable<ILogical> children)

16
src/Avalonia.Styling/Styling/IStyleHost.cs

@ -2,6 +2,10 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
#nullable enable
namespace Avalonia.Styling
{
/// <summary>
@ -27,5 +31,17 @@ namespace Avalonia.Styling
/// Gets the parent style host element.
/// </summary>
IStyleHost StylingParent { get; }
/// <summary>
/// Called when styles are added to <see cref="Styles"/>.
/// </summary>
/// <param name="styles">The added styles.</param>
void StylesAdded(IReadOnlyList<IStyle> styles);
/// <summary>
/// Called when styles are removed from <see cref="Styles"/>.
/// </summary>
/// <param name="styles">The removed styles.</param>
void StylesRemoved(IReadOnlyList<IStyle> styles);
}
}

11
src/Avalonia.Styling/Styling/IStyleable.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using Avalonia.Collections;
#nullable enable
@ -38,5 +39,15 @@ namespace Avalonia.Styling
/// Detaches all styles applied to the element.
/// </summary>
void DetachStyles();
/// <summary>
/// Detaches a collection of styles, if applied to the element.
/// </summary>
void DetachStyles(IReadOnlyList<IStyle> styles);
/// <summary>
/// Detaches all styles from the element and queues a restyle.
/// </summary>
void InvalidateStyles();
}
}

130
src/Avalonia.Styling/Styling/Styles.cs

@ -27,40 +27,7 @@ namespace Avalonia.Styling
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 += NotifyResourcesChanged;
_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 -= NotifyResourcesChanged;
_cache = null;
},
() => { });
_styles.CollectionChanged += OnCollectionChanged;
}
public Styles(IResourceNode parent)
@ -69,11 +36,7 @@ namespace Avalonia.Styling
_parent = parent;
}
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add => _styles.CollectionChanged += value;
remove => _styles.CollectionChanged -= value;
}
public event NotifyCollectionChangedEventHandler? CollectionChanged;
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs>? ResourcesChanged;
@ -257,6 +220,95 @@ namespace Avalonia.Styling
NotifyResourcesChanged(e);
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
static IReadOnlyList<T> ToReadOnlyList<T>(IList list)
{
if (list is IReadOnlyList<T>)
{
return (IReadOnlyList<T>)list;
}
else
{
var result = new T[list.Count];
list.CopyTo(result, 0);
return result;
}
}
void Add(IList items)
{
for (var i = 0; i < items.Count; ++i)
{
var style = (IStyle)items[i];
if (style.ResourceParent == null && style is ISetResourceParent setParent)
{
setParent.SetParent(this);
setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
}
if (style.HasResources)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
style.ResourcesChanged += NotifyResourcesChanged;
_cache = null;
}
if (_parent is IStyleHost host)
{
host.StylesAdded(ToReadOnlyList<IStyle>(items));
}
}
void Remove(IList items)
{
for (var i = 0; i < items.Count; ++i)
{
var style = (IStyle)items[i];
if (style.ResourceParent == this && style is ISetResourceParent setParent)
{
setParent.SetParent(null);
setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
}
if (style.HasResources)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
style.ResourcesChanged -= NotifyResourcesChanged;
_cache = null;
}
if (_parent is IStyleHost host)
{
host.StylesRemoved(ToReadOnlyList<IStyle>(items));
}
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
Add(e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
Remove(e.OldItems);
break;
case NotifyCollectionChangedAction.Replace:
Remove(e.OldItems);
Add(e.NewItems);
break;
case NotifyCollectionChangedAction.Reset:
throw new InvalidOperationException("Reset should not be called on Styles.");
}
CollectionChanged?.Invoke(this, e);
}
private void NotifyResourcesChanged(object sender, ResourcesChangedEventArgs e)
{
NotifyResourcesChanged(e);

11
tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs

@ -9,6 +9,7 @@ using Avalonia.Styling;
using Xunit;
using System.ComponentModel;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
using System.Collections.Generic;
namespace Avalonia.Markup.Xaml.UnitTests.Converters
{
@ -144,6 +145,16 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters
throw new NotImplementedException();
}
public void DetachStyles(IReadOnlyList<IStyle> styles)
{
throw new NotImplementedException();
}
public void InvalidateStyles()
{
throw new NotImplementedException();
}
public void StyleApplied(IStyleInstance instance)
{
throw new NotImplementedException();

Loading…
Cancel
Save