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> /// </summary>
public Type? TargetType { get; set; } 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) if (TargetType is null)
throw new InvalidOperationException("ControlTheme has no TargetType."); throw new InvalidOperationException("ControlTheme has no TargetType.");
return control.StyleKey == TargetType ? var result = BasedOn?.TryAttach(target, host) ?? SelectorMatchResult.NeverThisType;
SelectorMatch.AlwaysThisType :
SelectorMatch.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) 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) 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( throw new InvalidOperationException(

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

@ -28,7 +28,32 @@ namespace Avalonia.Styling
/// </summary> /// </summary>
public Selector? Selector { get; set; } 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> /// <summary>
/// Returns a string representation of the style. /// 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) internal override void SetParent(StyleBase? parent)
{ {
if (parent is Style parentStyle && parentStyle.Selector is not null) 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.Animation;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Styling.Activators;
namespace Avalonia.Styling namespace Avalonia.Styling
{ {
@ -64,43 +65,14 @@ namespace Avalonia.Styling
bool IResourceNode.HasResources => _resources?.Count > 0; bool IResourceNode.HasResources => _resources?.Count > 0;
IReadOnlyList<IStyle> IStyle.Children => (IReadOnlyList<IStyle>?)_children ?? Array.Empty<IStyle>(); 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(ISetter setter) => Setters.Add(setter);
public void Add(IStyle style) => Children.Add(style); public void Add(IStyle style) => Children.Add(style);
public event EventHandler? OwnerChanged; public event EventHandler? OwnerChanged;
public SelectorMatchResult TryAttach(IStyleable target, object? host) public abstract 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 bool TryGetResource(object key, out object? result) public bool TryGetResource(object key, out object? result)
{ {
@ -108,19 +80,20 @@ namespace Avalonia.Styling
return _resources?.TryGetResource(key, out result) ?? false; return _resources?.TryGetResource(key, out result) ?? false;
} }
/// <summary> internal void Attach(IStyleable target, IStyleActivator? activator)
/// Evaluates the style's selector against the specified target element. {
/// </summary> var instance = new StyleInstance(this, target, _setters, _animations, activator);
/// <param name="control">The control.</param> target.StyleApplied(instance);
/// <param name="host">The element that hosts the style.</param> instance.Start();
/// <param name="subscribe"> }
/// Whether the match should subscribe to changes in order to track the match over time,
/// or simply return an immediate result. internal SelectorMatchResult TryAttachChildren(IStyleable target, object? host)
/// </param> {
/// <returns> if (_children is null || _children.Count == 0)
/// A <see cref="SelectorMatchResult"/> describing how the style matches the control. return SelectorMatchResult.NeverThisType;
/// </returns> _childCache ??= new StyleCache();
internal abstract SelectorMatch Match(IStyleable control, object? host, bool subscribe); return _childCache.TryAttach(_children, target, host);
}
internal virtual void SetParent(StyleBase? parent) => Parent = parent; 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); Assert.NotNull(target.Template);
var border = Assert.IsType<Border>(target.VisualChild); var border = Assert.IsType<Border>(target.VisualChild);
Assert.Equal(border.Background, Brushes.Red); Assert.Equal(Brushes.Red, border.Background);
target.Classes.Add("foo"); target.Classes.Add("foo");
Assert.Equal(border.Background, Brushes.Green); Assert.Equal(Brushes.Green, border.Background);
} }
[Fact] [Fact]
@ -73,7 +73,7 @@ public class StyledElementTests_Theming
var root = CreateRoot(target); var root = CreateRoot(target);
var canvas = Assert.IsType<Canvas>(target.VisualChild); var canvas = Assert.IsType<Canvas>(target.VisualChild);
Assert.Equal(canvas.Background, Brushes.Red); Assert.Equal(Brushes.Red, canvas.Background);
target.Theme = null; target.Theme = null;
@ -97,7 +97,28 @@ public class StyledElementTests_Theming
var border = Assert.IsType<Border>(target.VisualChild); var border = Assert.IsType<Border>(target.VisualChild);
Assert.NotNull(target.Template); 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) 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 private class ThemedControl : TemplatedControl
{ {
public IVisual? VisualChild => VisualChildren?.SingleOrDefault(); public IVisual? VisualChild => VisualChildren?.SingleOrDefault();

Loading…
Cancel
Save