14 changed files with 253 additions and 25 deletions
@ -0,0 +1,29 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Styling |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The `&` nesting style selector.
|
||||
|
/// </summary>
|
||||
|
internal class NestingSelector : Selector |
||||
|
{ |
||||
|
public override bool InTemplate => false; |
||||
|
public override bool IsCombinator => false; |
||||
|
public override Type? TargetType => null; |
||||
|
|
||||
|
public override string ToString() => "&"; |
||||
|
|
||||
|
protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) |
||||
|
{ |
||||
|
if (parent is Style s && s.Selector is Selector selector) |
||||
|
{ |
||||
|
return selector.Match(control, null, subscribe); |
||||
|
} |
||||
|
|
||||
|
throw new InvalidOperationException( |
||||
|
"Nesting selector was specified but cannot determine parent selector."); |
||||
|
} |
||||
|
|
||||
|
protected override Selector? MovePrevious() => null; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
using System.Collections.ObjectModel; |
||||
|
|
||||
|
namespace Avalonia.Styling |
||||
|
{ |
||||
|
internal class StyleChildren : Collection<IStyle> |
||||
|
{ |
||||
|
private readonly Style _owner; |
||||
|
|
||||
|
public StyleChildren(Style owner) => _owner = owner; |
||||
|
|
||||
|
protected override void InsertItem(int index, IStyle item) |
||||
|
{ |
||||
|
base.InsertItem(index, item); |
||||
|
(item as Style)?.SetParent(_owner); |
||||
|
} |
||||
|
|
||||
|
protected override void RemoveItem(int index) |
||||
|
{ |
||||
|
(Items[index] as Style)?.SetParent(null); |
||||
|
base.RemoveItem(index); |
||||
|
} |
||||
|
|
||||
|
protected override void SetItem(int index, IStyle item) |
||||
|
{ |
||||
|
base.SetItem(index, item); |
||||
|
(item as Style)?.SetParent(_owner); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,132 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Styling; |
||||
|
using Avalonia.Styling.Activators; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Base.UnitTests.Styling |
||||
|
{ |
||||
|
public class SelectorTests_Nesting |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Parent_Selector_Doesnt_Match_OfType() |
||||
|
{ |
||||
|
var control = new Control2(); |
||||
|
Style nested; |
||||
|
var parent = new Style(x => x.OfType<Control1>()) |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
(nested = new Style(x => x.Nesting().Class("foo"))), |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
var match = nested.Selector.Match(control, parent); |
||||
|
Assert.Equal(SelectorMatchResult.NeverThisType, match.Result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Nested_Class_Selector() |
||||
|
{ |
||||
|
var control = new Control1 { Classes = { "foo" } }; |
||||
|
Style nested; |
||||
|
var parent = new Style(x => x.OfType<Control1>()) |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
(nested = new Style(x => x.Nesting().Class("foo"))), |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
var match = nested.Selector.Match(control, parent); |
||||
|
Assert.Equal(SelectorMatchResult.Sometimes, match.Result); |
||||
|
|
||||
|
var sink = new ActivatorSink(match.Activator); |
||||
|
|
||||
|
Assert.True(sink.Active); |
||||
|
control.Classes.Clear(); |
||||
|
Assert.False(sink.Active); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Nesting_With_No_Parent_Style_Fails() |
||||
|
{ |
||||
|
var control = new Control1(); |
||||
|
var style = new Style(x => x.Nesting().OfType<Control1>()); |
||||
|
|
||||
|
Assert.Throws<InvalidOperationException>(() => style.Selector.Match(control, null)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Nesting_With_No_Parent_Selector_Fails() |
||||
|
{ |
||||
|
var control = new Control1(); |
||||
|
Style nested; |
||||
|
var parent = new Style |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
(nested = new Style(x => x.Nesting().Class("foo"))), |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
Assert.Throws<InvalidOperationException>(() => nested.Selector.Match(control, parent)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Nesting_Must_Appear_At_Start_Of_Selector() |
||||
|
{ |
||||
|
var control = new Control1(); |
||||
|
Assert.Throws<InvalidOperationException>(() => new Style(x => x.OfType<Control1>().Nesting())); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Nesting_Must_Appear() |
||||
|
{ |
||||
|
var control = new Control1(); |
||||
|
Style nested; |
||||
|
var parent = new Style |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
(nested = new Style(x => x.OfType<Control1>().Class("foo"))), |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
Assert.Throws<InvalidOperationException>(() => nested.Selector.Match(control, parent)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Nesting_Must_Appear_In_All_Or_Arguments() |
||||
|
{ |
||||
|
var control = new Control1(); |
||||
|
Style nested; |
||||
|
var parent = new Style(x => x.OfType<Control1>()) |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
(nested = new Style(x => Selectors.Or( |
||||
|
x.Nesting().Class("foo"), |
||||
|
x.Class("bar")))) |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
Assert.Throws<InvalidOperationException>(() => nested.Selector.Match(control, parent)); |
||||
|
} |
||||
|
|
||||
|
public class Control1 : Control |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public class Control2 : Control |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
private class ActivatorSink : IStyleActivatorSink |
||||
|
{ |
||||
|
public ActivatorSink(IStyleActivator source) => source.Subscribe(this); |
||||
|
public bool Active { get; private set; } |
||||
|
public void OnNext(bool value, int tag) => Active = value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue