Browse Source
The comma selector can be used to separate a number different selectors, all of which will be applied to the control with an OR. Fixes #1742pull/2345/head
7 changed files with 323 additions and 9 deletions
@ -0,0 +1,131 @@ |
|||||
|
// 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.Generic; |
||||
|
|
||||
|
namespace Avalonia.Styling |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The OR style selector.
|
||||
|
/// </summary>
|
||||
|
internal class OrSelector : Selector |
||||
|
{ |
||||
|
private readonly IReadOnlyList<Selector> _selectors; |
||||
|
private string _selectorString; |
||||
|
private Type _targetType; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="OrSelector"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="selectors">The selectors to OR.</param>
|
||||
|
public OrSelector(IReadOnlyList<Selector> selectors) |
||||
|
{ |
||||
|
Contract.Requires<ArgumentNullException>(selectors != null); |
||||
|
Contract.Requires<ArgumentException>(selectors.Count > 1); |
||||
|
|
||||
|
_selectors = selectors; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override bool InTemplate => false; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override bool IsCombinator => false; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override Type TargetType |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (_targetType == null) |
||||
|
{ |
||||
|
_targetType = EvaluateTargetType(); |
||||
|
} |
||||
|
|
||||
|
return _targetType; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
if (_selectorString == null) |
||||
|
{ |
||||
|
_selectorString = string.Join(", ", _selectors); |
||||
|
} |
||||
|
|
||||
|
return _selectorString; |
||||
|
} |
||||
|
|
||||
|
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) |
||||
|
{ |
||||
|
var activators = new List<IObservable<bool>>(); |
||||
|
var neverThisInstance = false; |
||||
|
|
||||
|
foreach (var selector in _selectors) |
||||
|
{ |
||||
|
var match = selector.Match(control, subscribe); |
||||
|
|
||||
|
switch (match.Result) |
||||
|
{ |
||||
|
case SelectorMatchResult.AlwaysThisType: |
||||
|
case SelectorMatchResult.AlwaysThisInstance: |
||||
|
return match; |
||||
|
case SelectorMatchResult.NeverThisInstance: |
||||
|
neverThisInstance = true; |
||||
|
break; |
||||
|
case SelectorMatchResult.Sometimes: |
||||
|
activators.Add(match.Activator); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (activators.Count > 1) |
||||
|
{ |
||||
|
return new SelectorMatch(StyleActivator.Or(activators)); |
||||
|
} |
||||
|
else if (activators.Count == 1) |
||||
|
{ |
||||
|
return new SelectorMatch(activators[0]); |
||||
|
} |
||||
|
else if (neverThisInstance) |
||||
|
{ |
||||
|
return SelectorMatch.NeverThisInstance; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return SelectorMatch.NeverThisType; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override Selector MovePrevious() => null; |
||||
|
|
||||
|
private Type EvaluateTargetType() |
||||
|
{ |
||||
|
var result = default(Type); |
||||
|
|
||||
|
foreach (var selector in _selectors) |
||||
|
{ |
||||
|
if (selector.TargetType == null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
else if (result == null) |
||||
|
{ |
||||
|
result = selector.TargetType; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
while (!result.IsAssignableFrom(selector.TargetType)) |
||||
|
{ |
||||
|
result = result.BaseType; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,106 @@ |
|||||
|
// 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 Xunit; |
||||
|
|
||||
|
namespace Avalonia.Styling.UnitTests |
||||
|
{ |
||||
|
public class SelectorTests_Or |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Or_Selector_Should_Have_Correct_String_Representation() |
||||
|
{ |
||||
|
var target = Selectors.Or( |
||||
|
default(Selector).OfType<Control1>().Class("foo"), |
||||
|
default(Selector).OfType<Control2>().Class("bar")); |
||||
|
|
||||
|
Assert.Equal("Control1.foo, Control2.bar", target.ToString()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Or_Selector_Matches_Control_Of_Correct_Type() |
||||
|
{ |
||||
|
var target = Selectors.Or( |
||||
|
default(Selector).OfType<Control1>(), |
||||
|
default(Selector).OfType<Control2>().Class("bar")); |
||||
|
var control = new Control1(); |
||||
|
|
||||
|
Assert.Equal(SelectorMatchResult.AlwaysThisType, target.Match(control).Result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Or_Selector_Matches_Control_Of_Correct_Type_With_Class() |
||||
|
{ |
||||
|
var target = Selectors.Or( |
||||
|
default(Selector).OfType<Control1>(), |
||||
|
default(Selector).OfType<Control2>().Class("bar")); |
||||
|
var control = new Control2(); |
||||
|
|
||||
|
Assert.Equal(SelectorMatchResult.Sometimes, target.Match(control).Result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Or_Selector_Doesnt_Match_Control_Of_Incorrect_Type() |
||||
|
{ |
||||
|
var target = Selectors.Or( |
||||
|
default(Selector).OfType<Control1>(), |
||||
|
default(Selector).OfType<Control2>().Class("bar")); |
||||
|
var control = new Control3(); |
||||
|
|
||||
|
Assert.Equal(SelectorMatchResult.NeverThisType, target.Match(control).Result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Or_Selector_Doesnt_Match_Control_With_Incorrect_Name() |
||||
|
{ |
||||
|
var target = Selectors.Or( |
||||
|
default(Selector).OfType<Control1>().Name("foo"), |
||||
|
default(Selector).OfType<Control2>().Name("foo")); |
||||
|
var control = new Control1 { Name = "bar" }; |
||||
|
|
||||
|
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(control).Result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Returns_Correct_TargetType_When_Types_Same() |
||||
|
{ |
||||
|
var target = Selectors.Or( |
||||
|
default(Selector).OfType<Control1>().Class("foo"), |
||||
|
default(Selector).OfType<Control1>().Class("bar")); |
||||
|
|
||||
|
Assert.Equal(typeof(Control1), target.TargetType); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Returns_Common_TargetType() |
||||
|
{ |
||||
|
var target = Selectors.Or( |
||||
|
default(Selector).OfType<Control1>().Class("foo"), |
||||
|
default(Selector).OfType<Control2>().Class("bar")); |
||||
|
|
||||
|
Assert.Equal(typeof(TestControlBase), target.TargetType); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Returns_Null_TargetType_When_A_Selector_Has_No_TargetType() |
||||
|
{ |
||||
|
var target = Selectors.Or( |
||||
|
default(Selector).OfType<Control1>().Class("foo"), |
||||
|
default(Selector).Class("bar")); |
||||
|
|
||||
|
Assert.Equal(null, target.TargetType); |
||||
|
} |
||||
|
|
||||
|
public class Control1 : TestControlBase |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public class Control2 : TestControlBase |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public class Control3 : TestControlBase |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue