Browse Source

Add ControlTheme.BasedOn.

pull/8263/head
Steven Kirk 4 years ago
parent
commit
05fdc04464
  1. 26
      src/Avalonia.Base/Styling/ControlTheme.cs
  2. 12
      src/Avalonia.Base/Styling/NestingSelector.cs
  3. 35
      src/Avalonia.Base/Styling/Style.cs
  4. 61
      src/Avalonia.Base/Styling/StyleBase.cs
  5. 59
      tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs

26
src/Avalonia.Base/Styling/ControlTheme.cs

@ -23,16 +23,32 @@ namespace Avalonia.Styling
/// </summary>
public Type? TargetType { get; set; }
internal override bool HasSelector => TargetType is not null;
/// <summary>
/// Gets or sets a control theme that is the basis of the current theme.
/// </summary>
public ControlTheme? BasedOn { get; set; }
internal override SelectorMatch Match(IStyleable control, object? host, bool subscribe)
public override SelectorMatchResult TryAttach(IStyleable target, object? host)
{
_ = target ?? throw new ArgumentNullException(nameof(target));
if (TargetType is null)
throw new InvalidOperationException("ControlTheme has no TargetType.");
return control.StyleKey == TargetType ?
SelectorMatch.AlwaysThisType :
SelectorMatch.NeverThisType;
var result = BasedOn?.TryAttach(target, host) ?? SelectorMatchResult.NeverThisType;
if (HasSettersOrAnimations && target.StyleKey == TargetType)
{
Attach(target, null);
result = SelectorMatchResult.AlwaysThisType;
}
var childResult = TryAttachChildren(target, host);
if (childResult > result)
result = childResult;
return result;
}
internal override void SetParent(StyleBase? parent)

12
src/Avalonia.Base/Styling/NestingSelector.cs

@ -15,9 +15,17 @@ namespace Avalonia.Styling
protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe)
{
if (parent is StyleBase s && s.HasSelector)
if (parent is Style s && s.Selector is not null)
{
return s.Match(control, null, subscribe);
return s.Selector.Match(control, s.Parent, subscribe);
}
else if (parent is ControlTheme theme)
{
if (theme.TargetType is null)
throw new InvalidOperationException("ControlTheme has no TargetType.");
return control.StyleKey == theme.TargetType ?
SelectorMatch.AlwaysThisType :
SelectorMatch.NeverThisType;
}
throw new InvalidOperationException(

35
src/Avalonia.Base/Styling/Style.cs

@ -28,7 +28,32 @@ namespace Avalonia.Styling
/// </summary>
public Selector? Selector { get; set; }
internal override bool HasSelector => Selector is not null;
public override SelectorMatchResult TryAttach(IStyleable target, object? host)
{
_ = target ?? throw new ArgumentNullException(nameof(target));
var result = SelectorMatchResult.NeverThisType;
if (HasSettersOrAnimations)
{
var match = Selector?.Match(target, Parent, true) ??
(target == host ?
SelectorMatch.AlwaysThisInstance :
SelectorMatch.NeverThisInstance);
if (match.IsMatch)
Attach(target, match.Activator);
result = match.Result;
}
var childResult = TryAttachChildren(target, host);
if (childResult > result)
result = childResult;
return result;
}
/// <summary>
/// Returns a string representation of the style.
@ -46,14 +71,6 @@ namespace Avalonia.Styling
}
}
internal override SelectorMatch Match(IStyleable control, object? host, bool subscribe)
{
return Selector?.Match(control, Parent, subscribe) ??
(control == host ?
SelectorMatch.AlwaysThisInstance :
SelectorMatch.NeverThisInstance);
}
internal override void SetParent(StyleBase? parent)
{
if (parent is Style parentStyle && parentStyle.Selector is not null)

61
src/Avalonia.Base/Styling/StyleBase.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Metadata;
using Avalonia.Styling.Activators;
namespace Avalonia.Styling
{
@ -64,43 +65,14 @@ namespace Avalonia.Styling
bool IResourceNode.HasResources => _resources?.Count > 0;
IReadOnlyList<IStyle> IStyle.Children => (IReadOnlyList<IStyle>?)_children ?? Array.Empty<IStyle>();
internal abstract bool HasSelector { get; }
internal bool HasSettersOrAnimations => _setters?.Count > 0 || _animations?.Count > 0;
public void Add(ISetter setter) => Setters.Add(setter);
public void Add(IStyle style) => Children.Add(style);
public event EventHandler? OwnerChanged;
public SelectorMatchResult TryAttach(IStyleable target, object? host)
{
target = target ?? throw new ArgumentNullException(nameof(target));
var result = SelectorMatchResult.NeverThisType;
if (_setters?.Count > 0 || _animations?.Count > 0)
{
var match = Match(target, host, subscribe: true);
if (match.IsMatch)
{
var instance = new StyleInstance(this, target, _setters, _animations, match.Activator);
target.StyleApplied(instance);
instance.Start();
}
result = match.Result;
}
if (_children is not null)
{
_childCache ??= new StyleCache();
var childResult = _childCache.TryAttach(_children, target, host);
if (childResult > result)
result = childResult;
}
return result;
}
public abstract SelectorMatchResult TryAttach(IStyleable target, object? host);
public bool TryGetResource(object key, out object? result)
{
@ -108,19 +80,20 @@ namespace Avalonia.Styling
return _resources?.TryGetResource(key, out result) ?? false;
}
/// <summary>
/// Evaluates the style's selector against the specified target element.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="host">The element that hosts the style.</param>
/// <param name="subscribe">
/// Whether the match should subscribe to changes in order to track the match over time,
/// or simply return an immediate result.
/// </param>
/// <returns>
/// A <see cref="SelectorMatchResult"/> describing how the style matches the control.
/// </returns>
internal abstract SelectorMatch Match(IStyleable control, object? host, bool subscribe);
internal void Attach(IStyleable target, IStyleActivator? activator)
{
var instance = new StyleInstance(this, target, _setters, _animations, activator);
target.StyleApplied(instance);
instance.Start();
}
internal SelectorMatchResult TryAttachChildren(IStyleable target, object? host)
{
if (_children is null || _children.Count == 0)
return SelectorMatchResult.NeverThisType;
_childCache ??= new StyleCache();
return _childCache.TryAttach(_children, target, host);
}
internal virtual void SetParent(StyleBase? parent) => Parent = parent;

59
tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs

@ -28,10 +28,10 @@ public class StyledElementTests_Theming
Assert.NotNull(target.Template);
var border = Assert.IsType<Border>(target.VisualChild);
Assert.Equal(border.Background, Brushes.Red);
Assert.Equal(Brushes.Red, border.Background);
target.Classes.Add("foo");
Assert.Equal(border.Background, Brushes.Green);
Assert.Equal(Brushes.Green, border.Background);
}
[Fact]
@ -73,7 +73,7 @@ public class StyledElementTests_Theming
var root = CreateRoot(target);
var canvas = Assert.IsType<Canvas>(target.VisualChild);
Assert.Equal(canvas.Background, Brushes.Red);
Assert.Equal(Brushes.Red, canvas.Background);
target.Theme = null;
@ -97,7 +97,28 @@ public class StyledElementTests_Theming
var border = Assert.IsType<Border>(target.VisualChild);
Assert.NotNull(target.Template);
Assert.Equal(border.Background, Brushes.Red);
Assert.Equal(Brushes.Red, border.Background);
}
[Fact]
public void BasedOn_Theme_Is_Applied_When_Attached_To_Logical_Tree()
{
using var app = UnitTestApplication.Start(TestServices.RealStyler);
var target = CreateTarget(CreateDerivedTheme());
Assert.Null(target.Template);
var root = CreateRoot(target);
Assert.NotNull(target.Template);
Assert.Equal(Brushes.Blue, target.BorderBrush);
var border = Assert.IsType<Border>(target.VisualChild);
Assert.Equal(Brushes.Red, border.Background);
Assert.Equal(Brushes.Yellow, border.BorderBrush);
target.Classes.Add("foo");
Assert.Equal(Brushes.Green, border.Background);
Assert.Equal(Brushes.Cyan, border.BorderBrush);
}
private static ThemedControl CreateTarget(ControlTheme? theme = null)
@ -197,6 +218,36 @@ public class StyledElementTests_Theming
};
}
private static ControlTheme CreateDerivedTheme()
{
return new ControlTheme
{
TargetType = typeof(ThemedControl),
BasedOn = CreateTheme(),
Setters =
{
new Setter(Border.BorderBrushProperty, Brushes.Blue),
},
Children =
{
new Style(x => x.Nesting().Template().OfType<Border>())
{
Setters =
{
new Setter(Border.BorderBrushProperty, Brushes.Yellow),
}
},
new Style(x => x.Nesting().Class("foo").Template().OfType<Border>())
{
Setters =
{
new Setter(Border.BorderBrushProperty, Brushes.Cyan),
}
},
}
};
}
private class ThemedControl : TemplatedControl
{
public IVisual? VisualChild => VisualChildren?.SingleOrDefault();

Loading…
Cancel
Save