Browse Source

add media tags

pull/7938/head
Emmanuel Hansen 3 years ago
parent
commit
1ca55b4288
  1. 109
      samples/ControlCatalog/MainView.xaml
  2. 2
      src/Avalonia.Base/PropertyStore/ValueFrame.cs
  3. 3
      src/Avalonia.Base/StyledElement.cs
  4. 2
      src/Avalonia.Base/Styling/Activators/DeviceActivator.cs
  5. 10
      src/Avalonia.Base/Styling/Activators/ScreenActivator.cs
  6. 8
      src/Avalonia.Base/Styling/DeviceMediaSelector.cs
  7. 79
      src/Avalonia.Base/Styling/Media.cs
  8. 29
      src/Avalonia.Base/Styling/MediaQuery.cs
  9. 33
      src/Avalonia.Base/Styling/MediaSelector.cs
  10. 95
      src/Avalonia.Base/Styling/OrQuery.cs
  11. 58
      src/Avalonia.Base/Styling/Queries.cs
  12. 154
      src/Avalonia.Base/Styling/Query.cs
  13. 40
      src/Avalonia.Base/Styling/ScreenQueries.cs
  14. 20
      src/Avalonia.Base/Styling/Selector.cs
  15. 30
      src/Avalonia.Base/Styling/Selectors.cs
  16. 1
      src/Avalonia.Base/Styling/Styles.cs
  17. 6
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  18. 1
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  19. 3
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
  20. 403
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlQueryTransformer.cs
  21. 131
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
  22. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  23. 1
      src/Markup/Avalonia.Markup.Xaml.Loader/IncludeXamlIlSre.props
  24. 21
      src/Markup/Avalonia.Markup/Markup/Parsers/ISyntax.cs
  25. 271
      src/Markup/Avalonia.Markup/Markup/Parsers/MediaQueryGrammar.cs
  26. 102
      src/Markup/Avalonia.Markup/Markup/Parsers/MediaQueryParser.cs
  27. 129
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
  28. 20
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs
  29. 68
      tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs

109
samples/ControlCatalog/MainView.xaml

@ -8,43 +8,78 @@
x:DataType="viewModels:MainWindowViewModel">
<Grid>
<Grid.Styles>
<Style Selector=":min-width(600) TextBlock#MinWidth">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style Selector=":max-width(800) TextBlock#MaxWidth">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style Selector=":min-height(600) TextBlock#MinHeight">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style Selector=":max-height(800) TextBlock#MaxHeight">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style Selector=":orientation(landscape) TextBlock#Landscape">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style Selector=":orientation(portrait) TextBlock#Portrait">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style Selector=":is-os(windows) TextBlock#Windows">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style Selector=":is-os(linux) TextBlock#Linux">
<Setter Property="Foreground"
Value="Red" />
</Style>
<Style Selector=":is-os(osx) TextBlock#OSX">
<Setter Property="Foreground"
Value="Red" />
</Style>
<Style Selector=":is-os(android) TextBlock#Android">
<Setter Property="Foreground"
Value="Red" />
</Style>
<Style Selector=":is-os(ios) TextBlock#iOS">
<Setter Property="Foreground"
Value="Red" />
</Style>
<Media Query=":min-width(600)">
<Style Selector="TextBlock#MinWidth">
<Setter Property="Foreground"
Value="Red" />
</Style>
</Media>
<Media Query=":min-width(600)">
<Style Selector="TextBlock#MinWidth">
<Setter Property="Foreground"
Value="Red" />
</Style>
</Media>
<Media Query=":max-width(800)">
<Style Selector="TextBlock#MaxWidth">
<Setter Property="Foreground"
Value="Red" />
</Style>
</Media>
<Media Query=":min-height(600)">
<Style Selector="TextBlock#MinHeight">
<Setter Property="Foreground"
Value="Red" />
</Style>
</Media>
<Media Query=":max-height(800)">
<Style Selector="TextBlock#MaxHeight">
<Setter Property="Foreground"
Value="Red" />
</Style>
</Media>
<Media Query=":orientation(landscape)">
<Style Selector="TextBlock#Landscape">
<Setter Property="Foreground"
Value="Red" />
</Style>
</Media>
<Media Query=":orientation(portrait)">
<Style Selector="TextBlock#Portrait">
<Setter Property="Foreground"
Value="Red" />
</Style>
</Media>
<Media Query=":is-os(windows)">
<Style Selector="TextBlock#Windows">
<Setter Property="Foreground"
Value="Red" />
</Style>
</Media>
<Media Query=":is-os(linux)">
<Style Selector="TextBlock#Linux">
<Setter Property="Foreground"
Value="Red" />
</Style>
</Media>
<Media Query=":is-os(osx)">
<Style Selector="TextBlock#OSX">
<Setter Property="Foreground"
Value="Red" />
</Style>
</Media>
<Media Query=":is-os(android)">
<Style Selector="TextBlock#Android">
<Setter Property="Foreground"
Value="Red" />
</Style>
</Media>
<Media Query=":is-os(ios)">
<Style Selector="TextBlock#iOS">
<Setter Property="Foreground"
Value="Red" />
</Style>
</Media>
</Grid.Styles>
<StackPanel Spacing="10">
<TextBlock Name="MinWidth" Classes="h2" Text="min-width" />

2
src/Avalonia.Base/PropertyStore/ValueFrame.cs

@ -10,7 +10,7 @@ namespace Avalonia.PropertyStore
{
Style,
TemplatedParentTheme,
Theme,
Theme
}
internal abstract class ValueFrame

3
src/Avalonia.Base/StyledElement.cs

@ -845,6 +845,9 @@ namespace Avalonia
private void ApplyStyle(IStyle style, IStyleHost? host, FrameType type)
{
if (style is Styling.Media m)
m.TryAttach(this, host, type);
if (style is Style s)
s.TryAttach(this, host, type);

2
src/Avalonia.Base/Styling/Activators/DeviceActivator.cs

@ -9,6 +9,6 @@
_argument = argument;
}
protected override bool EvaluateIsActive() => CurrentMediaInfoProvider != null && IsOsMediaSelector.Evaluate(CurrentMediaInfoProvider, _argument).IsMatch;
protected override bool EvaluateIsActive() => CurrentMediaInfoProvider != null && IsOsMediaQuery.Evaluate(CurrentMediaInfoProvider, _argument).IsMatch;
}
}

10
src/Avalonia.Base/Styling/Activators/ScreenActivator.cs

@ -13,7 +13,7 @@ namespace Avalonia.Styling.Activators
_argument = argument;
}
protected override bool EvaluateIsActive() => CurrentMediaInfoProvider != null && MinWidthMediaSelector.Evaluate(CurrentMediaInfoProvider, _argument).IsMatch;
protected override bool EvaluateIsActive() => CurrentMediaInfoProvider != null && MinWidthMediaQuery.Evaluate(CurrentMediaInfoProvider, _argument).IsMatch;
}
internal sealed class MaxWidthActivator : MediaQueryActivatorBase
@ -25,7 +25,7 @@ namespace Avalonia.Styling.Activators
_argument = argument;
}
protected override bool EvaluateIsActive() => CurrentMediaInfoProvider != null && MaxWidthMediaSelector.Evaluate(CurrentMediaInfoProvider, _argument).IsMatch;
protected override bool EvaluateIsActive() => CurrentMediaInfoProvider != null && MaxWidthMediaQuery.Evaluate(CurrentMediaInfoProvider, _argument).IsMatch;
}
internal sealed class MinHeightActivator : MediaQueryActivatorBase
@ -37,7 +37,7 @@ namespace Avalonia.Styling.Activators
_argument = argument;
}
protected override bool EvaluateIsActive() => CurrentMediaInfoProvider != null && MinHeightMediaSelector.Evaluate(CurrentMediaInfoProvider, _argument).IsMatch;
protected override bool EvaluateIsActive() => CurrentMediaInfoProvider != null && MinHeightMediaQuery.Evaluate(CurrentMediaInfoProvider, _argument).IsMatch;
}
internal sealed class MaxHeightActivator : MediaQueryActivatorBase
@ -49,7 +49,7 @@ namespace Avalonia.Styling.Activators
_argument = argument;
}
protected override bool EvaluateIsActive() => CurrentMediaInfoProvider != null && MaxHeightMediaSelector.Evaluate(CurrentMediaInfoProvider, _argument).IsMatch;
protected override bool EvaluateIsActive() => CurrentMediaInfoProvider != null && MaxHeightMediaQuery.Evaluate(CurrentMediaInfoProvider, _argument).IsMatch;
}
internal sealed class OrientationActivator : MediaQueryActivatorBase
@ -61,6 +61,6 @@ namespace Avalonia.Styling.Activators
_argument = argument;
}
protected override bool EvaluateIsActive() => CurrentMediaInfoProvider != null && OrientationMediaSelector.Evaluate(CurrentMediaInfoProvider, _argument).IsMatch;
protected override bool EvaluateIsActive() => CurrentMediaInfoProvider != null && OrientationMediaQuery.Evaluate(CurrentMediaInfoProvider, _argument).IsMatch;
}
}

8
src/Avalonia.Base/Styling/DeviceMediaSelector.cs

@ -4,13 +4,13 @@ using Avalonia.Styling.Activators;
namespace Avalonia.Styling
{
internal sealed class IsOsMediaSelector : MediaSelector<string>
internal sealed class IsOsMediaQuery : MediaQuery<string>
{
public IsOsMediaSelector(Selector? previous, string argument) : base(previous, argument)
public IsOsMediaQuery(Query? previous, string argument) : base(previous, argument)
{
}
private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
internal override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{
if (!(control is Visual visual))
{
@ -37,7 +37,7 @@ namespace Avalonia.Styling
public override string ToString() => "is-os";
public override string ToString(Style? owner)
public override string ToString(Media? owner)
{
throw new NotImplementedException();
}

79
src/Avalonia.Base/Styling/Media.cs

@ -0,0 +1,79 @@
using System;
using Avalonia.Controls;
using Avalonia.PropertyStore;
namespace Avalonia.Styling
{
/// <summary>
/// Defines a media.
/// </summary>
public class Media : StyleBase
{
private Query? _query;
/// <summary>
/// Initializes a new instance of the <see cref="Media"/> class.
/// </summary>
public Media()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Media"/> class.
/// </summary>
/// <param name="selector">The media selector.</param>
public Media(Func<Query?, Query> selector)
{
Query = selector(null);
}
/// <summary>
/// Gets or sets the media's selector.
/// </summary>
public Query? Query
{
get => _query;
set => _query = value;
}
/// <summary>
/// Returns a string representation of the media.
/// </summary>
/// <returns>A string representation of the media.</returns>
public override string ToString() => Query?.ToString(this) ?? "Media";
internal override void SetParent(StyleBase? parent)
{
if (parent is Style)
{
throw new InvalidOperationException("Media can not be children of Style.");
}
base.SetParent(parent);
}
internal SelectorMatchResult TryAttach(StyledElement target, object? host, FrameType type)
{
_ = target ?? throw new ArgumentNullException(nameof(target));
var result = SelectorMatchResult.NeverThisType;
if (HasChildren)
{
var match = Query?.Match(target, Parent, true) ??
(target == host ?
SelectorMatch.AlwaysThisInstance :
SelectorMatch.NeverThisInstance);
if (match.IsMatch)
{
Attach(target, match.Activator, type);
}
result = match.Result;
}
return result;
}
}
}

29
src/Avalonia.Base/Styling/MediaQuery.cs

@ -0,0 +1,29 @@
using System;
namespace Avalonia.Styling
{
public abstract class MediaQuery<T> : Query
{
private readonly Query? _previous;
private T _argument;
public MediaQuery(Query? previous, T argument)
{
_previous = previous;
_argument = argument;
}
protected T Argument => _argument;
internal override bool IsCombinator => false;
public override string ToString(Media? owner)
{
throw new NotImplementedException();
}
private protected override Query? MovePrevious() => _previous;
private protected override Query? MovePreviousOrParent() => _previous;
}
}

33
src/Avalonia.Base/Styling/MediaSelector.cs

@ -1,33 +0,0 @@
using System;
namespace Avalonia.Styling
{
public abstract class MediaSelector<T> : Selector
{
private readonly Selector? _previous;
private T _argument;
public MediaSelector(Selector? previous, T argument)
{
_previous = previous;
_argument = argument;
}
protected T Argument => _argument;
internal override bool InTemplate => _previous?.InTemplate ?? false;
internal override bool IsCombinator => false;
internal override Type? TargetType => _previous?.TargetType;
public override string ToString(Style? owner)
{
throw new NotImplementedException();
}
private protected override Selector? MovePrevious() => _previous;
private protected override Selector? MovePreviousOrParent() => _previous;
}
}

95
src/Avalonia.Base/Styling/OrQuery.cs

@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Styling.Activators;
#nullable enable
namespace Avalonia.Styling
{
/// <summary>
/// The OR style query.
/// </summary>
internal sealed class OrQuery : Query
{
private readonly IReadOnlyList<Query> _queries;
private string? _queryString;
private Type? _targetType;
/// <summary>
/// Initializes a new instance of the <see cref="OrQuery"/> class.
/// </summary>
/// <param name="queries">The querys to OR.</param>
public OrQuery(IReadOnlyList<Query> queries)
{
if (queries is null)
{
throw new ArgumentNullException(nameof(queries));
}
if (queries.Count <= 1)
{
throw new ArgumentException("Need more than one query to OR.");
}
_queries = queries;
}
/// <inheritdoc/>
internal override bool IsCombinator => false;
/// <inheritdoc/>
public override string ToString(Media? owner)
{
if (_queryString == null)
{
_queryString = string.Join(", ", _queries.Select(x => x.ToString(owner)));
}
return _queryString;
}
internal override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{
var activators = new OrActivatorBuilder();
var neverThisInstance = false;
var count = _queries.Count;
for (var i = 0; i < count; i++)
{
var match = _queries[i].Match(control, parent, 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 > 0)
{
return new SelectorMatch(activators.Get());
}
else if (neverThisInstance)
{
return SelectorMatch.NeverThisInstance;
}
else
{
return SelectorMatch.NeverThisType;
}
}
private protected override Query? MovePrevious() => null;
private protected override Query? MovePreviousOrParent() => null;
}
}

58
src/Avalonia.Base/Styling/Queries.cs

@ -0,0 +1,58 @@
using Avalonia.Platform;
using System.Collections.Generic;
namespace Avalonia.Styling
{
internal static class Queries
{
public static Query IsOs(this Query? previous, string argument)
{
return new IsOsMediaQuery(previous, argument);
}
public static Query MaxHeight(this Query? previous, double argument)
{
return new MaxHeightMediaQuery(previous, argument);
}
public static Query MaxWidth(this Query? previous, double argument)
{
return new MaxWidthMediaQuery(previous, argument);
}
public static Query MinHeight(this Query? previous, double argument)
{
return new MinHeightMediaQuery(previous, argument);
}
public static Query MinWidth(this Query? previous, double argument)
{
return new MinWidthMediaQuery(previous, argument);
}
public static Query Orientation(this Query? previous, DeviceOrientation argument)
{
return new OrientationMediaQuery(previous, argument);
}
/// <summary>
/// Returns a query which ORs queries.
/// </summary>
/// <param name="queries">The queries to be OR'd.</param>
/// <returns>The query.</returns>
public static Query Or(params Query[] queries)
{
return new OrQuery(queries);
}
/// <summary>
/// Returns a query which ORs queries.
/// </summary>
/// <param name="query">The queries to be OR'd.</param>
/// <returns>The query.</returns>
public static Query Or(IReadOnlyList<Query> query)
{
return new OrQuery(query);
}
}
}

154
src/Avalonia.Base/Styling/Query.cs

@ -0,0 +1,154 @@
using System;
using Avalonia.Styling.Activators;
#nullable enable
namespace Avalonia.Styling
{
/// <summary>
/// A query in a <see cref="Style"/>.
/// </summary>
public abstract class Query
{
/// <summary>
/// Gets a value indicating whether this query is a combinator.
/// </summary>
/// <remarks>
/// A combinator is a query such as Child or Descendent which links simple querys.
/// </remarks>
internal abstract bool IsCombinator { get; }
/// <summary>
/// Tries to match the query with a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="parent">
/// The parent media, if the media containing the query is a nested media.
/// </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="SelectorMatch"/>.</returns>
internal virtual SelectorMatch Match(StyledElement control, IStyle? parent = null, bool subscribe = true)
{
// First match the query until a combinator is found. Selectors are stored from
// right-to-left, so MatchUntilCombinator reverses this order because the type query
// will be on the left.
var match = MatchUntilCombinator(control, this, parent, subscribe, out var combinator);
// If the pre-combinator query matches, we can now match the combinator, if any.
if (match.IsMatch && combinator is object)
{
match = match.And(combinator.Match(control, parent, subscribe));
// If we have a combinator then we can never say that we always match a control of
// this type, because by definition the combinator matches on things outside of the
// control.
match = match.Result switch
{
SelectorMatchResult.AlwaysThisType => SelectorMatch.AlwaysThisInstance,
SelectorMatchResult.NeverThisType => SelectorMatch.NeverThisInstance,
_ => match
};
}
return match;
}
public override string ToString() => ToString(null);
/// <summary>
/// Gets a string representing the query, with the nesting separator (`^`) replaced with
/// the parent query.
/// </summary>
/// <param name="owner">The owner media.</param>
public abstract string ToString(Media? owner);
/// <summary>
/// Evaluates the query for a match.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="parent">
/// The parent media, if the media containing the query is a nested media.
/// </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="SelectorMatch"/>.</returns>
internal abstract SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe);
/// <summary>
/// Moves to the previous query.
/// </summary>
private protected abstract Query? MovePrevious();
/// <summary>
/// Moves to the previous query or the parent query.
/// </summary>
private protected abstract Query? MovePreviousOrParent();
private static SelectorMatch MatchUntilCombinator(
StyledElement control,
Query start,
IStyle? parent,
bool subscribe,
out Query? combinator)
{
combinator = null;
var activators = new AndActivatorBuilder();
var result = Match(control, start, parent, subscribe, ref activators, ref combinator);
return result == SelectorMatchResult.Sometimes ?
new SelectorMatch(activators.Get()) :
new SelectorMatch(result);
}
private static SelectorMatchResult Match(
StyledElement control,
Query query,
IStyle? parent,
bool subscribe,
ref AndActivatorBuilder activators,
ref Query? combinator)
{
var previous = query.MovePrevious();
// Selectors are stored from right-to-left, so we recurse into the query in order to
// reverse this order, because the type query will be on the left and is our best
// opportunity to exit early.
if (previous != null && !previous.IsCombinator)
{
var previousMatch = Match(control, previous, parent, subscribe, ref activators, ref combinator);
if (previousMatch < SelectorMatchResult.Sometimes)
{
return previousMatch;
}
}
// Match this query.
var match = query.Evaluate(control, parent, subscribe);
if (!match.IsMatch)
{
combinator = null;
return match.Result;
}
else if (match.Activator is object)
{
activators.Add(match.Activator!);
}
if (previous?.IsCombinator == true)
{
combinator = previous;
}
return match.Result;
}
}
}

40
src/Avalonia.Base/Styling/ScreenSelector.cs → src/Avalonia.Base/Styling/ScreenQueries.cs

@ -5,13 +5,13 @@ using Avalonia.Styling.Activators;
namespace Avalonia.Styling
{
internal sealed class MinWidthMediaSelector : MediaSelector<double>
internal sealed class MinWidthMediaQuery : MediaQuery<double>
{
public MinWidthMediaSelector(Selector? previous, double argument) : base(previous, argument)
public MinWidthMediaQuery(Query? previous, double argument) : base(previous, argument)
{
}
private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
internal override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{
if (!(control is Visual visual))
{
@ -38,19 +38,19 @@ namespace Avalonia.Styling
public override string ToString() => "min-width";
public override string ToString(Style? owner)
public override string ToString(Media? owner)
{
throw new NotImplementedException();
}
}
public sealed class MaxWidthMediaSelector : MediaSelector<double>
public sealed class MaxWidthMediaQuery : MediaQuery<double>
{
public MaxWidthMediaSelector(Selector? previous, double argument) : base(previous, argument)
public MaxWidthMediaQuery(Query? previous, double argument) : base(previous, argument)
{
}
private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
internal override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{
if (!(control is Visual visual))
{
@ -77,19 +77,19 @@ namespace Avalonia.Styling
public override string ToString() => "max-width";
public override string ToString(Style? owner)
public override string ToString(Media? owner)
{
throw new NotImplementedException();
}
}
public sealed class MinHeightMediaSelector : MediaSelector<double>
public sealed class MinHeightMediaQuery : MediaQuery<double>
{
public MinHeightMediaSelector(Selector? previous, double argument) : base(previous, argument)
public MinHeightMediaQuery(Query? previous, double argument) : base(previous, argument)
{
}
private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
internal override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{
if (!(control is Visual visual))
{
@ -116,19 +116,19 @@ namespace Avalonia.Styling
public override string ToString() => "min-height";
public override string ToString(Style? owner)
public override string ToString(Media? owner)
{
throw new NotImplementedException();
}
}
public sealed class MaxHeightMediaSelector : MediaSelector<double>
public sealed class MaxHeightMediaQuery : MediaQuery<double>
{
public MaxHeightMediaSelector(Selector? previous, double argument) : base(previous, argument)
public MaxHeightMediaQuery(Query? previous, double argument) : base(previous, argument)
{
}
private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
internal override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{
if (!(control is Visual visual))
{
@ -155,19 +155,19 @@ namespace Avalonia.Styling
public override string ToString() => "max-height";
public override string ToString(Style? owner)
public override string ToString(Media? owner)
{
throw new NotImplementedException();
}
}
internal sealed class OrientationMediaSelector : MediaSelector<DeviceOrientation>
internal sealed class OrientationMediaQuery : MediaQuery<DeviceOrientation>
{
public OrientationMediaSelector(Selector? previous, DeviceOrientation argument) : base(previous, argument)
public OrientationMediaQuery(Query? previous, DeviceOrientation argument) : base(previous, argument)
{
}
private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
internal override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{
if (!(control is Visual visual))
{
@ -194,7 +194,7 @@ namespace Avalonia.Styling
public override string ToString() => "orientation";
public override string ToString(Style? owner)
public override string ToString(Media? owner)
{
throw new NotImplementedException();
}

20
src/Avalonia.Base/Styling/Selector.cs

@ -166,8 +166,24 @@ namespace Avalonia.Styling
}
}
SelectorMatch match = SelectorMatch.NeverThisInstance;
bool mediaMatchesSometimes = false;
// Match any parent Media query
if (parent is Media media)
{
match = media.Query?.Evaluate(control, media.Parent, subscribe) ?? SelectorMatch.NeverThisInstance;
if (!match.IsMatch)
return match.Result;
mediaMatchesSometimes = match.Result == SelectorMatchResult.Sometimes;
if (mediaMatchesSometimes)
activators.Add(match.Activator!);
}
// Match this selector.
var match = selector.Evaluate(control, parent, subscribe);
match = selector.Evaluate(control, parent, subscribe);
if (!match.IsMatch)
{
@ -184,7 +200,7 @@ namespace Avalonia.Styling
combinator = previous;
}
return match.Result;
return mediaMatchesSometimes ? SelectorMatchResult.Sometimes : match.Result;
}
}
}

30
src/Avalonia.Base/Styling/Selectors.cs

@ -137,26 +137,6 @@ namespace Avalonia.Styling
return new NotSelector(previous, argument);
}
public static Selector MinWidth(this Selector? previous, double argument)
{
return new MinWidthMediaSelector(previous, argument);
}
public static Selector MaxWidth(this Selector? previous, double argument)
{
return new MaxWidthMediaSelector(previous, argument);
}
public static Selector MinHeight(this Selector? previous, double argument)
{
return new MinHeightMediaSelector(previous, argument);
}
public static Selector MaxHeight(this Selector? previous, double argument)
{
return new MaxHeightMediaSelector(previous, argument);
}
/// <inheritdoc cref="NthChildSelector"/>
/// <inheritdoc cref="NthChildSelector(Selector?, int, int)"/>
/// <returns>The selector.</returns>
@ -216,16 +196,6 @@ namespace Avalonia.Styling
{
return new OrSelector(selectors);
}
public static Selector Orientation(this Selector? previous, DeviceOrientation argument)
{
return new OrientationMediaSelector(previous, (DeviceOrientation)argument);
}
public static Selector IsOs(this Selector? previous, string argument)
{
return new IsOsMediaSelector(previous, argument);
}
/// <summary>
/// Returns a selector which matches a control with the specified property value.

1
src/Avalonia.Base/Styling/Styles.cs

@ -17,6 +17,7 @@ namespace Avalonia.Styling
IResourceProvider
{
private readonly AvaloniaList<IStyle> _styles = new();
private readonly AvaloniaList<Media> _medias = new();
private IResourceHost? _owner;
private IResourceDictionary? _resources;

6
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -37,6 +37,12 @@
<Compile Include="../Markup/Avalonia.Markup\Markup\Parsers\SelectorGrammar.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Markup/Avalonia.Markup\Markup\Parsers\ISyntax.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Markup/Avalonia.Markup\Markup\Parsers\MediaQueryGrammar.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Markup/Avalonia.Markup\Markup\Parsers\PropertyPathGrammar.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>

1
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -50,6 +50,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
InsertBefore<ContentConvertTransformer>(
new AvaloniaXamlIlControlThemeTransformer(),
new AvaloniaXamlIlSelectorTransformer(),
new AvaloniaXamlIlQueryTransformer(),
new AvaloniaXamlIlDuplicateSettersChecker(),
new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
new AvaloniaXamlIlBindingPathParser(),

3
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs

@ -57,7 +57,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
Style,
ControlTemplate,
Transitions
Transitions,
Media
}
public AvaloniaXamlIlTargetTypeMetadataNode(IXamlAstValueNode value, IXamlAstTypeReference targetType,

403
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlQueryTransformer.cs

@ -0,0 +1,403 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia.Markup.Parsers;
using Avalonia.Platform;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
using XamlParseException = XamlX.XamlParseException;
using XamlLoadException = XamlX.XamlLoadException;
class AvaloniaXamlIlQueryTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is not XamlAstObjectNode on ||
!context.GetAvaloniaTypes().Media.IsAssignableFrom(on.Type.GetClrType()))
return node;
var pn = on.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(p => p.Property.GetClrProperty().Name == "Query");
if (pn == null)
return node;
if (pn.Values.Count != 1)
throw new XamlParseException("Query property should should have exactly one value", node);
if (pn.Values[0] is XamlIlQueryNode)
//Deja vu. I've just been in this place before
return node;
if (!(pn.Values[0] is XamlAstTextNode tn))
throw new XamlParseException("Query property should be a text node", node);
var queryType = pn.Property.GetClrProperty().Getter.ReturnType;
var initialNode = new XamlIlQueryInitialNode(node, queryType);
var avaloniaAttachedPropertyT = context.GetAvaloniaTypes().AvaloniaAttachedPropertyT;
XamlIlQueryNode Create(IEnumerable<ISyntax> syntax)
{
XamlIlQueryNode result = initialNode;
XamlIlOrQueryNode results = null;
foreach (var i in syntax)
{
switch (i)
{
case MediaQueryGrammar.MinWidthSyntax minWidth:
result = new XamlIlMinWidthQuery(result, minWidth.Argument);
break;
case MediaQueryGrammar.MaxWidthSyntax maxWidth:
result = new XamlIlMaxWidthQuery(result, maxWidth.Argument);
break;
case MediaQueryGrammar.MinHeightSyntax minHeight:
result = new XamlIlMinHeightQuery(result, minHeight.Argument);
break;
case MediaQueryGrammar.MaxHeightSyntax maxHeight:
result = new XamlIlMaxHeightQuery(result, maxHeight.Argument);
break;
case MediaQueryGrammar.OrientationSyntax orientation:
result = new XamlIlOrientationQuery(result, orientation.Argument);
break;
case MediaQueryGrammar.IsOsSyntax isOs:
result = new XamlIlIsOsQuery(result, isOs.Argument);
break;
case MediaQueryGrammar.CommaSyntax comma:
if (results == null)
results = new XamlIlOrQueryNode(node, queryType);
results.Add(result);
result = initialNode;
break;
default:
throw new XamlParseException($"Unsupported query grammar '{i.GetType()}'.", node);
}
}
if (results != null && result != null)
{
results.Add(result);
}
return results ?? result;
}
IEnumerable<ISyntax> parsed;
try
{
parsed = MediaQueryGrammar.Parse(tn.Text);
}
catch (Exception e)
{
throw new XamlParseException("Unable to parse query: " + e.Message, node);
}
var query = Create(parsed);
pn.Values[0] = query;
return new AvaloniaXamlIlTargetTypeMetadataNode(on,
new XamlAstClrTypeReference(query, query.TargetType, false),
AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
}
}
abstract class XamlIlQueryNode : XamlAstNode, IXamlAstValueNode, IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
{
internal XamlIlQueryNode Previous { get; }
public abstract IXamlType TargetType { get; }
public XamlIlQueryNode(XamlIlQueryNode previous,
IXamlLineInfo info = null,
IXamlType queryType = null) : base(info ?? previous)
{
Previous = previous;
Type = queryType == null ? previous.Type : new XamlAstClrTypeReference(this, queryType, false);
}
public IXamlAstTypeReference Type { get; }
public virtual XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
if (Previous != null)
context.Emit(Previous, codeGen, Type.GetClrType());
DoEmit(context, codeGen);
return XamlILNodeEmitResult.Type(0, Type.GetClrType());
}
protected abstract void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen);
protected void EmitCall(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen, Func<IXamlMethod, bool> method)
{
var queries = context.Configuration.TypeSystem.GetType("Avalonia.Styling.Queries");
var found = queries.FindMethod(m => m.IsStatic && m.Parameters.Count > 0 && method(m));
codeGen.EmitCall(found);
}
}
class XamlIlQueryInitialNode : XamlIlQueryNode
{
public XamlIlQueryInitialNode(IXamlLineInfo info,
IXamlType queryType) : base(null, info, queryType)
{
}
public override IXamlType TargetType => null;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen) => codeGen.Ldnull();
}
class XamlIlTypeQuery : XamlIlQueryNode
{
public bool Concrete { get; }
public XamlIlTypeQuery(XamlIlQueryNode previous, IXamlType type, bool concrete) : base(previous)
{
TargetType = type;
Concrete = concrete;
}
public override IXamlType TargetType { get; }
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
var name = Concrete ? "OfType" : "Is";
codeGen.Ldtype(TargetType);
EmitCall(context, codeGen,
m => m.Name == name && m.Parameters.Count == 2 && m.Parameters[1].FullName == "System.Type");
}
}
class XamlIlStringQuery : XamlIlQueryNode
{
public string String { get; set; }
public enum QueryType
{
Class,
Name
}
private QueryType _type;
public XamlIlStringQuery(XamlIlQueryNode previous, QueryType type, string s) : base(previous)
{
_type = type;
String = s;
}
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldstr(String);
var name = _type.ToString();
EmitCall(context, codeGen,
m => m.Name == name && m.Parameters.Count == 2 && m.Parameters[1].FullName == "System.String");
}
}
class XamlIlCombinatorQuery : XamlIlQueryNode
{
private readonly CombinatorQueryType _type;
public enum CombinatorQueryType
{
Child,
Descendant,
Template
}
public XamlIlCombinatorQuery(XamlIlQueryNode previous, CombinatorQueryType type) : base(previous)
{
_type = type;
}
public CombinatorQueryType QueryType => _type;
public override IXamlType TargetType => null;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
var name = _type.ToString();
EmitCall(context, codeGen,
m => m.Name == name && m.Parameters.Count == 1);
}
}
class XamlIlMinWidthQuery : XamlIlQueryNode
{
private double _argument;
public XamlIlMinWidthQuery(XamlIlQueryNode previous, double argument) : base(previous)
{
_argument = argument;
}
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldc_R8(_argument);
EmitCall(context, codeGen,
m => m.Name == "MinWidth" && m.Parameters.Count == 2);
}
}
class XamlIlMaxWidthQuery : XamlIlQueryNode
{
private double _argument;
public XamlIlMaxWidthQuery(XamlIlQueryNode previous, double argument) : base(previous)
{
_argument = argument;
}
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldc_R8(_argument);
EmitCall(context, codeGen,
m => m.Name == "MaxWidth" && m.Parameters.Count == 2);
}
}
class XamlIlMinHeightQuery : XamlIlQueryNode
{
private double _argument;
public XamlIlMinHeightQuery(XamlIlQueryNode previous, double argument) : base(previous)
{
_argument = argument;
}
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldc_R8(_argument);
EmitCall(context, codeGen,
m => m.Name == "MinHeight" && m.Parameters.Count == 2);
}
}
class XamlIlMaxHeightQuery : XamlIlQueryNode
{
private double _argument;
public XamlIlMaxHeightQuery(XamlIlQueryNode previous, double argument) : base(previous)
{
_argument = argument;
}
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldc_R8(_argument);
EmitCall(context, codeGen,
m => m.Name == "MaxHeight" && m.Parameters.Count == 2);
}
}
class XamlIlOrientationQuery : XamlIlQueryNode
{
private DeviceOrientation _argument;
public XamlIlOrientationQuery(XamlIlQueryNode previous, DeviceOrientation argument) : base(previous)
{
_argument = argument;
}
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldc_I4((int)_argument);
EmitCall(context, codeGen,
m => m.Name == "Orientation" && m.Parameters.Count == 2);
}
}
class XamlIlIsOsQuery : XamlIlQueryNode
{
private string _argument;
public XamlIlIsOsQuery(XamlIlQueryNode previous, string argument) : base(previous)
{
_argument = argument;
}
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldstr(_argument);
EmitCall(context, codeGen,
m => m.Name == "IsOs" && m.Parameters.Count == 2);
}
}
class XamlIlOrQueryNode : XamlIlQueryNode
{
List<XamlIlQueryNode> _queries = new List<XamlIlQueryNode>();
public XamlIlOrQueryNode(IXamlLineInfo info, IXamlType queryType) : base(null, info, queryType)
{
}
public void Add(XamlIlQueryNode node)
{
_queries.Add(node);
}
public override IXamlType TargetType
{
get
{
IXamlType result = null;
foreach (var query in _queries)
{
if (query.TargetType == null)
{
return null;
}
else if (result == null)
{
result = query.TargetType;
}
else
{
while (!result.IsAssignableFrom(query.TargetType))
{
result = result.BaseType;
}
}
}
return result;
}
}
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
if (_queries.Count == 0)
throw new XamlLoadException("Invalid query count", this);
if (_queries.Count == 1)
{
_queries[0].Emit(context, codeGen);
return;
}
var listType = context.Configuration.TypeSystem.FindType("System.Collections.Generic.List`1")
.MakeGenericType(base.Type.GetClrType());
var add = listType.FindMethod("Add", context.Configuration.WellKnownTypes.Void, false, Type.GetClrType());
codeGen
.Newobj(listType.FindConstructor());
foreach (var s in _queries)
{
codeGen.Dup();
context.Emit(s, codeGen, Type.GetClrType());
codeGen.EmitCall(add, true);
}
EmitCall(context, codeGen,
m => m.Name == "Or" && m.Parameters.Count == 1 && m.Parameters[0].Name.StartsWith("IReadOnlyList"));
}
}
}

131
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs

@ -43,7 +43,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var selectorType = pn.Property.GetClrProperty().Getter.ReturnType;
var initialNode = new XamlIlSelectorInitialNode(node, selectorType);
var avaloniaAttachedPropertyT = context.GetAvaloniaTypes().AvaloniaAttachedPropertyT;
XamlIlSelectorNode Create(IEnumerable<SelectorGrammar.ISyntax> syntax,
XamlIlSelectorNode Create(IEnumerable<ISyntax> syntax,
Func<string, string, XamlAstClrTypeReference> typeResolver)
{
XamlIlSelectorNode result = initialNode;
@ -141,30 +141,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
case SelectorGrammar.NotSyntax not:
result = new XamlIlNotSelector(result, Create(not.Argument, typeResolver));
break;
case SelectorGrammar.MinWidthSyntax minWidth:
result = new XamlIlMinWidthSelector(result, minWidth.Argument);
break;
case SelectorGrammar.MaxWidthSyntax maxWidth:
result = new XamlIlMaxWidthSelector(result, maxWidth.Argument);
break;
case SelectorGrammar.MinHeightSyntax minHeight:
result = new XamlIlMinHeightSelector(result, minHeight.Argument);
break;
case SelectorGrammar.MaxHeightSyntax maxHeight:
result = new XamlIlMaxHeightSelector(result, maxHeight.Argument);
break;
case SelectorGrammar.NthChildSyntax nth:
result = new XamlIlNthChildSelector(result, nth.Step, nth.Offset, XamlIlNthChildSelector.SelectorType.NthChild);
break;
case SelectorGrammar.NthLastChildSyntax nth:
result = new XamlIlNthChildSelector(result, nth.Step, nth.Offset, XamlIlNthChildSelector.SelectorType.NthLastChild);
break;
case SelectorGrammar.OrientationSyntax orientation:
result = new XamlIlOrientationSelector(result, orientation.Argument);
break;
case SelectorGrammar.IsOsSyntax isOs:
result = new XamlIlIsOsSelector(result, isOs.Argument);
break;
case SelectorGrammar.CommaSyntax comma:
if (results == null)
results = new XamlIlOrSelectorNode(node, selectorType);
@ -192,7 +174,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return results ?? result;
}
IEnumerable<SelectorGrammar.ISyntax> parsed;
IEnumerable<ISyntax> parsed;
try
{
parsed = SelectorGrammar.Parse(tn.Text);
@ -403,115 +385,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
class XamlIlMinWidthSelector : XamlIlSelectorNode
{
private double _argument;
public XamlIlMinWidthSelector(XamlIlSelectorNode previous, double argument) : base(previous)
{
_argument = argument;
}
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldc_R8(_argument);
EmitCall(context, codeGen,
m => m.Name == "MinWidth" && m.Parameters.Count == 2);
}
}
class XamlIlMaxWidthSelector : XamlIlSelectorNode
{
private double _argument;
public XamlIlMaxWidthSelector(XamlIlSelectorNode previous, double argument) : base(previous)
{
_argument = argument;
}
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldc_R8(_argument);
EmitCall(context, codeGen,
m => m.Name == "MaxWidth" && m.Parameters.Count == 2);
}
}
class XamlIlMinHeightSelector : XamlIlSelectorNode
{
private double _argument;
public XamlIlMinHeightSelector(XamlIlSelectorNode previous, double argument) : base(previous)
{
_argument = argument;
}
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldc_R8(_argument);
EmitCall(context, codeGen,
m => m.Name == "MinHeight" && m.Parameters.Count == 2);
}
}
class XamlIlMaxHeightSelector : XamlIlSelectorNode
{
private double _argument;
public XamlIlMaxHeightSelector(XamlIlSelectorNode previous, double argument) : base(previous)
{
_argument = argument;
}
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldc_R8(_argument);
EmitCall(context, codeGen,
m => m.Name == "MaxHeight" && m.Parameters.Count == 2);
}
}
class XamlIlOrientationSelector : XamlIlSelectorNode
{
private DeviceOrientation _argument;
public XamlIlOrientationSelector(XamlIlSelectorNode previous, DeviceOrientation argument) : base(previous)
{
_argument = argument;
}
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldc_I4((int)_argument);
EmitCall(context, codeGen,
m => m.Name == "Orientation" && m.Parameters.Count == 2);
}
}
class XamlIlIsOsSelector : XamlIlSelectorNode
{
private string _argument;
public XamlIlIsOsSelector(XamlIlSelectorNode previous, string argument) : base(previous)
{
_argument = argument;
}
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldstr(_argument);
EmitCall(context, codeGen,
m => m.Name == "IsOs" && m.Parameters.Count == 2);
}
}
class XamlIlPropertyEqualsSelector : XamlIlSelectorNode
{
public XamlIlPropertyEqualsSelector(XamlIlSelectorNode previous,

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -116,6 +116,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType UriKind { get; }
public IXamlConstructor UriConstructor { get; }
public IXamlType Style { get; }
public IXamlType Media { get; }
public IXamlType ControlTheme { get; }
public IXamlType WindowTransparencyLevel { get; }
public IXamlType IReadOnlyListOfT { get; }
@ -262,6 +263,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
UriKind = cfg.TypeSystem.GetType("System.UriKind");
UriConstructor = Uri.GetConstructor(new List<IXamlType>() { cfg.WellKnownTypes.String, UriKind });
Style = cfg.TypeSystem.GetType("Avalonia.Styling.Style");
Media = cfg.TypeSystem.GetType("Avalonia.Styling.Media");
ControlTheme = cfg.TypeSystem.GetType("Avalonia.Styling.ControlTheme");
IReadOnlyListOfT = cfg.TypeSystem.GetType("System.Collections.Generic.IReadOnlyList`1");
}

1
src/Markup/Avalonia.Markup.Xaml.Loader/IncludeXamlIlSre.props

@ -6,6 +6,7 @@
<Compile Include="$(MSBuildThisFileDirectory)\xamlil.github\src\XamlX\**\*.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)\xamlil.github\**\obj\**\*.cs" />
<Compile Include="$(MSBuildThisFileDirectory)\..\Avalonia.Markup\Markup\Parsers\SelectorGrammar.cs" />
<Compile Include="$(MSBuildThisFileDirectory)\..\Avalonia.Markup\Markup\Parsers\MediaQueryGrammar.cs" />
<Compile Include="$(MSBuildThisFileDirectory)\..\Avalonia.Markup.Xaml\Parsers\PropertyParser.cs" />
<Compile Include="$(MSBuildThisFileDirectory)\..\Avalonia.Markup\Markup\Parsers\BindingExpressionGrammar.cs" />
</ItemGroup>

21
src/Markup/Avalonia.Markup/Markup/Parsers/ISyntax.cs

@ -0,0 +1,21 @@
// Don't need to override GetHashCode as the ISyntax objects will not be stored in a hash; the
// only reason they have overridden Equals methods is for unit testing.
#pragma warning disable 659
using System.Collections.Generic;
using System.Linq;
using Avalonia.Platform;
namespace Avalonia.Markup.Parsers
{
public interface ISyntax
{
}
public interface ITypeSyntax
{
string TypeName { get; set; }
string Xmlns { get; set; }
}
}

271
src/Markup/Avalonia.Markup/Markup/Parsers/MediaQueryGrammar.cs

@ -0,0 +1,271 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Data.Core;
using Avalonia.Platform;
using Avalonia.Utilities;
// Don't need to override GetHashCode as the ISyntax objects will not be stored in a hash; the
// only reason they have overridden Equals methods is for unit testing.
#pragma warning disable 659
namespace Avalonia.Markup.Parsers
{
internal static class MediaQueryGrammar
{
private enum State
{
Start,
Middle,
Colon,
End,
}
public static IEnumerable<ISyntax> Parse(string s)
{
var r = new CharacterReader(s.AsSpan());
return Parse(ref r, null);
}
private static IEnumerable<ISyntax> Parse(ref CharacterReader r, char? end)
{
var state = State.Start;
var selector = new List<ISyntax>();
while (!r.End && state != State.End)
{
ISyntax? syntax = null;
switch (state)
{
case State.Start:
(state, syntax) = ParseStart(ref r);
break;
case State.Middle:
(state, syntax) = ParseMiddle(ref r, end);
break;
case State.Colon:
(state, syntax) = ParseColon(ref r);
break;
}
if (syntax != null)
{
selector.Add(syntax);
}
}
if (state != State.Start && state != State.Middle && state != State.End)
{
throw new ExpressionParseException(r.Position, "Unexpected end of selector");
}
return selector;
}
private static (State, ISyntax?) ParseStart(ref CharacterReader r)
{
r.SkipWhitespace();
if (r.End)
{
return (State.End, null);
}
if (r.TakeIf(':'))
{
return (State.Colon, null);
}
throw new InvalidOperationException("Invalid syntax found");
}
private static (State, ISyntax?) ParseMiddle(ref CharacterReader r, char? end)
{
if (r.TakeIf(':'))
{
return (State.Colon, null);
}
else if (r.TakeIf(','))
{
return (State.Start, new CommaSyntax());
}
else if (end.HasValue && !r.End && r.Peek == end.Value)
{
return (State.End, null);
}
throw new InvalidOperationException("Invalid syntax found");
}
private static (State, ISyntax) ParseColon(ref CharacterReader r)
{
var identifier = r.ParseStyleClass();
if (identifier.IsEmpty)
{
throw new ExpressionParseException(r.Position, "Expected class name, is, nth-child or nth-last-child selector after ':'.");
}
const string MinWidthKeyword = "min-width";
const string MaxWidthKeyword = "max-width";
const string MinHeightKeyword = "min-height";
const string MaxHeightKeyword = "max-height";
const string OrientationKeyword = "orientation";
const string IsOsKeyword = "is-os";
if(identifier.SequenceEqual(MinWidthKeyword.AsSpan()) && r.TakeIf('('))
{
var argument = ParseDecimal(ref r);
Expect(ref r, ')');
var syntax = new MinWidthSyntax { Argument = argument };
return (State.Middle, syntax);
}
if(identifier.SequenceEqual(MaxWidthKeyword.AsSpan()) && r.TakeIf('('))
{
var argument = ParseDecimal(ref r);
Expect(ref r, ')');
var syntax = new MaxWidthSyntax { Argument = argument };
return (State.Middle, syntax);
}
if(identifier.SequenceEqual(MinHeightKeyword.AsSpan()) && r.TakeIf('('))
{
var argument = ParseDecimal(ref r);
Expect(ref r, ')');
var syntax = new MinHeightSyntax { Argument = argument };
return (State.Middle, syntax);
}
if(identifier.SequenceEqual(MaxHeightKeyword.AsSpan()) && r.TakeIf('('))
{
var argument = ParseDecimal(ref r);
Expect(ref r, ')');
var syntax = new MaxHeightSyntax { Argument = argument };
return (State.Middle, syntax);
}
if (identifier.SequenceEqual(OrientationKeyword.AsSpan()) && r.TakeIf('('))
{
var argument = ParseEnum<DeviceOrientation>(ref r);
Expect(ref r, ')');
var syntax = new OrientationSyntax { Argument = argument };
return (State.Middle, syntax);
}
if (identifier.SequenceEqual(IsOsKeyword.AsSpan()) && r.TakeIf('('))
{
var argument = ParseString(ref r);
Expect(ref r, ')');
var syntax = new IsOsSyntax { Argument = argument };
return (State.Middle, syntax);
}
throw new InvalidOperationException("Invalid syntax found");
}
private static double ParseDecimal(ref CharacterReader r)
{
var number = r.ParseNumber();
if (number.IsEmpty)
{
throw new ExpressionParseException(r.Position, $"Expected a number after.");
}
return double.Parse(number.ToString());
}
private static T ParseEnum<T>(ref CharacterReader r) where T: struct
{
var identifier = r.ParseIdentifier();
if (Enum.TryParse<T>(identifier.ToString(), true, out T value))
return value;
throw new ExpressionParseException(r.Position, $"Expected a {typeof(T)} after.");
}
private static string ParseString(ref CharacterReader r)
{
return r.ParseIdentifier().ToString();
}
private static void Expect(ref CharacterReader r, char c)
{
if (r.End)
{
throw new ExpressionParseException(r.Position, $"Expected '{c}', got end of selector.");
}
else if (!r.TakeIf(')'))
{
throw new ExpressionParseException(r.Position, $"Expected '{c}', got '{r.Peek}'.");
}
}
public class CommaSyntax : ISyntax
{
public override bool Equals(object? obj)
{
return obj is CommaSyntax;
}
}
public class OrientationSyntax : ISyntax
{
public DeviceOrientation Argument { get; set; }
public override bool Equals(object? obj)
{
return (obj is OrientationSyntax orientation) && orientation.Argument == Argument;
}
}
public class IsOsSyntax : ISyntax
{
public string Argument { get; set; } = string.Empty;
public override bool Equals(object? obj)
{
return (obj is IsOsSyntax orientation) && orientation.Argument == Argument;
}
}
public class MinWidthSyntax : ISyntax
{
public double Argument { get; set; }
public override bool Equals(object? obj)
{
return (obj is MinWidthSyntax minwidth) && minwidth.Argument == Argument;
}
}
public class MinHeightSyntax : ISyntax
{
public double Argument { get; set; }
public override bool Equals(object? obj)
{
return (obj is MinHeightSyntax minwidth) && minwidth.Argument == Argument;
}
}
public class MaxWidthSyntax : ISyntax
{
public double Argument { get; set; }
public override bool Equals(object? obj)
{
return (obj is MaxWidthSyntax maxwidth) && maxwidth.Argument == Argument;
}
}
public class MaxHeightSyntax : ISyntax
{
public double Argument { get; set; }
public override bool Equals(object? obj)
{
return (obj is MaxHeightSyntax maxHeight) && maxHeight.Argument == Argument;
}
}
}
}

102
src/Markup/Avalonia.Markup/Markup/Parsers/MediaQueryParser.cs

@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Styling;
using Avalonia.Utilities;
using System.Linq;
using System.Diagnostics.CodeAnalysis;
namespace Avalonia.Markup.Parsers
{
/// <summary>
/// Parses a <see cref="Selector"/> from text.
/// </summary>
internal class MediaQueryParser
{
private readonly Func<string, string, Type> _typeResolver;
/// <summary>
/// Initializes a new instance of the <see cref="MediaQueryParser"/> class.
/// </summary>
/// <param name="typeResolver">
/// The type resolver to use. The type resolver is a function which accepts two strings:
/// a type name and a XML namespace prefix and a type name, and should return the resolved
/// type or throw an exception.
/// </param>
public MediaQueryParser(Func<string, string, Type> typeResolver)
{
_typeResolver = typeResolver;
}
/// <summary>
/// Parses a <see cref="Selector"/> from a string.
/// </summary>
/// <param name="s">The string.</param>
/// <returns>The parsed selector.</returns>
[RequiresUnreferencedCode(TrimmingMessages.SelectorsParseRequiresUnreferencedCodeMessage)]
public Query? Parse(string s)
{
var syntax = MediaQueryGrammar.Parse(s);
return Create(syntax);
}
[RequiresUnreferencedCode(TrimmingMessages.SelectorsParseRequiresUnreferencedCodeMessage)]
private Query? Create(IEnumerable<ISyntax> syntax)
{
var result = default(Query);
var results = default(List<Query>);
foreach (var i in syntax)
{
switch (i)
{
case MediaQueryGrammar.MinWidthSyntax minWidth:
result = Styling.Queries.MinWidth(result, minWidth.Argument);
break;
case MediaQueryGrammar.MaxWidthSyntax maxWidth:
result = Styling.Queries.MaxWidth(result, maxWidth.Argument);
break;
case MediaQueryGrammar.MinHeightSyntax minHeight:
result = Styling.Queries.MinHeight(result, minHeight.Argument);
break;
case MediaQueryGrammar.MaxHeightSyntax maxHeight:
result = Styling.Queries.MaxHeight(result, maxHeight.Argument);
break;
case MediaQueryGrammar.OrientationSyntax orientation:
result = Styling.Queries.Orientation(result, orientation.Argument);
break;
/*case MediaQueryGrammar.IsOsSyntax isOs:
result = Queries.IsOs(result, isOs.Argument);
break;*/
default:
throw new NotSupportedException($"Unsupported selector grammar '{i.GetType()}'.");
}
}
if (results != null)
{
if (result != null)
{
results.Add(result);
}
result = results.Count > 1 ? Queries.Or(results) : results[0];
}
return result;
}
private Type Resolve(string xmlns, string typeName)
{
var result = _typeResolver(xmlns, typeName);
if (result == null)
{
var type = string.IsNullOrWhiteSpace(xmlns) ? typeName : xmlns + ':' + typeName;
throw new InvalidOperationException($"Could not resolve type '{type}'");
}
return result;
}
}
}

129
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs

@ -176,14 +176,8 @@ namespace Avalonia.Markup.Parsers
const string IsKeyword = "is";
const string NotKeyword = "not";
const string MinWidthKeyword = "min-width";
const string MaxWidthKeyword = "max-width";
const string MinHeightKeyword = "min-height";
const string MaxHeightKeyword = "max-height";
const string NthChildKeyword = "nth-child";
const string NthLastChildKeyword = "nth-last-child";
const string OrientationKeyword = "orientation";
const string IsOsKeyword = "is-os";
if (identifier.SequenceEqual(IsKeyword.AsSpan()) && r.TakeIf('('))
{
@ -200,38 +194,6 @@ namespace Avalonia.Markup.Parsers
var syntax = new NotSyntax { Argument = argument };
return (State.Middle, syntax);
}
if(identifier.SequenceEqual(MinWidthKeyword.AsSpan()) && r.TakeIf('('))
{
var argument = ParseDecimal(ref r);
Expect(ref r, ')');
var syntax = new MinWidthSyntax { Argument = argument };
return (State.Middle, syntax);
}
if(identifier.SequenceEqual(MaxWidthKeyword.AsSpan()) && r.TakeIf('('))
{
var argument = ParseDecimal(ref r);
Expect(ref r, ')');
var syntax = new MaxWidthSyntax { Argument = argument };
return (State.Middle, syntax);
}
if(identifier.SequenceEqual(MinHeightKeyword.AsSpan()) && r.TakeIf('('))
{
var argument = ParseDecimal(ref r);
Expect(ref r, ')');
var syntax = new MinHeightSyntax { Argument = argument };
return (State.Middle, syntax);
}
if(identifier.SequenceEqual(MaxHeightKeyword.AsSpan()) && r.TakeIf('('))
{
var argument = ParseDecimal(ref r);
Expect(ref r, ')');
var syntax = new MaxHeightSyntax { Argument = argument };
return (State.Middle, syntax);
}
if (identifier.SequenceEqual(NthChildKeyword.AsSpan()) && r.TakeIf('('))
{
var (step, offset) = ParseNthChildArguments(ref r);
@ -246,22 +208,6 @@ namespace Avalonia.Markup.Parsers
var syntax = new NthLastChildSyntax { Step = step, Offset = offset };
return (State.Middle, syntax);
}
if (identifier.SequenceEqual(OrientationKeyword.AsSpan()) && r.TakeIf('('))
{
var argument = ParseEnum<DeviceOrientation>(ref r);
Expect(ref r, ')');
var syntax = new OrientationSyntax { Argument = argument };
return (State.Middle, syntax);
}
if (identifier.SequenceEqual(IsOsKeyword.AsSpan()) && r.TakeIf('('))
{
var argument = ParseString(ref r);
Expect(ref r, ')');
var syntax = new IsOsSyntax { Argument = argument };
return (State.Middle, syntax);
}
else
{
return (
@ -568,17 +514,6 @@ namespace Avalonia.Markup.Parsers
}
}
public interface ISyntax
{
}
public interface ITypeSyntax
{
string TypeName { get; set; }
string Xmlns { get; set; }
}
public class OfTypeSyntax : ISyntax, ITypeSyntax
{
public string TypeName { get; set; } = string.Empty;
@ -646,11 +581,11 @@ namespace Avalonia.Markup.Parsers
syntax.Name == Name;
}
}
public class DecimalSyntax : ISyntax
{
public decimal Number { get; set; }
public override bool Equals(object? obj)
{
return obj is DecimalSyntax dec && dec.Number == Number;
@ -705,46 +640,6 @@ namespace Avalonia.Markup.Parsers
}
}
public class MinWidthSyntax : ISyntax
{
public double Argument { get; set; }
public override bool Equals(object? obj)
{
return (obj is MinWidthSyntax minwidth) && minwidth.Argument == Argument;
}
}
public class MinHeightSyntax : ISyntax
{
public double Argument { get; set; }
public override bool Equals(object? obj)
{
return (obj is MinHeightSyntax minwidth) && minwidth.Argument == Argument;
}
}
public class MaxWidthSyntax : ISyntax
{
public double Argument { get; set; }
public override bool Equals(object? obj)
{
return (obj is MaxWidthSyntax maxwidth) && maxwidth.Argument == Argument;
}
}
public class MaxHeightSyntax : ISyntax
{
public double Argument { get; set; }
public override bool Equals(object? obj)
{
return (obj is MaxHeightSyntax maxHeight) && maxHeight.Argument == Argument;
}
}
public class NthChildSyntax : ISyntax
{
public int Offset { get; set; }
@ -767,26 +662,6 @@ namespace Avalonia.Markup.Parsers
}
}
public class OrientationSyntax : ISyntax
{
public DeviceOrientation Argument { get; set; }
public override bool Equals(object? obj)
{
return (obj is OrientationSyntax orientation) && orientation.Argument == Argument;
}
}
public class IsOsSyntax : ISyntax
{
public string Argument { get; set; } = string.Empty;
public override bool Equals(object? obj)
{
return (obj is IsOsSyntax orientation) && orientation.Argument == Argument;
}
}
public class CommaSyntax : ISyntax
{
public override bool Equals(object? obj)

20
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs

@ -41,7 +41,7 @@ namespace Avalonia.Markup.Parsers
}
[RequiresUnreferencedCode(TrimmingMessages.SelectorsParseRequiresUnreferencedCodeMessage)]
private Selector? Create(IEnumerable<SelectorGrammar.ISyntax> syntax)
private Selector? Create(IEnumerable<ISyntax> syntax)
{
var result = default(Selector);
var results = default(List<Selector>);
@ -152,30 +152,12 @@ namespace Avalonia.Markup.Parsers
case SelectorGrammar.NotSyntax not:
result = result.Not(x => Create(not.Argument)!);
break;
case SelectorGrammar.MinWidthSyntax minWidth:
result = result.MinWidth(minWidth.Argument);
break;
case SelectorGrammar.MaxWidthSyntax maxWidth:
result = result.MaxWidth(maxWidth.Argument);
break;
case SelectorGrammar.MinHeightSyntax minHeight:
result = result.MinHeight(minHeight.Argument);
break;
case SelectorGrammar.MaxHeightSyntax maxHeight:
result = result.MaxHeight(maxHeight.Argument);
break;
case SelectorGrammar.NthChildSyntax nth:
result = result.NthChild(nth.Step, nth.Offset);
break;
case SelectorGrammar.NthLastChildSyntax nth:
result = result.NthLastChild(nth.Step, nth.Offset);
break;
case SelectorGrammar.OrientationSyntax orientation:
result = result.Orientation(orientation.Argument);
break;
case SelectorGrammar.IsOsSyntax isOs:
result = result.IsOs(isOs.Argument);
break;
case SelectorGrammar.CommaSyntax comma:
if (results == null)
{

68
tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs

@ -43,7 +43,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("Button#foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.NameSyntax { Name = "foo" },
@ -67,7 +67,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse(":is(Button)#foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.IsSyntax { TypeName = "Button" },
new SelectorGrammar.NameSyntax { Name = "foo" },
@ -81,7 +81,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse(":is(x|Button)#foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.IsSyntax { TypeName = "Button", Xmlns = "x" },
new SelectorGrammar.NameSyntax { Name = "foo" },
@ -115,7 +115,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("Button.foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.ClassSyntax { Class = "foo" },
@ -129,7 +129,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("Button > .foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.ChildSyntax { },
@ -144,7 +144,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("Button>.foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.ChildSyntax { },
@ -159,7 +159,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("Button .foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.DescendantSyntax { },
@ -174,7 +174,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("Button /template/ .foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.TemplateSyntax { },
@ -189,7 +189,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("Button[Foo=bar]");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.PropertySyntax { Property = "Foo", Value = "bar" },
@ -203,7 +203,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("Button[(Grid.Column)=1]");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.AttachedPropertySyntax {
@ -221,7 +221,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("Button[(x|Grid.Column)=1]");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.AttachedPropertySyntax {
@ -239,11 +239,11 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse(":not(Button)");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.NotSyntax
{
Argument = new SelectorGrammar.ISyntax[]
Argument = new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
},
@ -258,12 +258,12 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("Button:not(.foo)");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.NotSyntax
{
Argument = new SelectorGrammar.ISyntax[]
Argument = new ISyntax[]
{
new SelectorGrammar.ClassSyntax { Class = "foo" },
},
@ -301,7 +301,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse(input);
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.NthChildSyntax()
{
@ -330,7 +330,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse(input);
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.NthLastChildSyntax()
{
@ -347,7 +347,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("Button:nth-child(2n+1)");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.NthChildSyntax()
@ -365,7 +365,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("Button:nth-child(2147483647n)");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.NthChildSyntax()
@ -383,7 +383,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("Button:nth-last-child(2n+1)");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.NthLastChildSyntax()
@ -401,7 +401,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("Button:nth-child(odd)");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.NthChildSyntax()
@ -419,7 +419,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("Button:nth-child(even)");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.NthChildSyntax()
@ -437,13 +437,13 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse(":is(Control) :not(Button.foo)");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.IsSyntax { TypeName = "Control" },
new SelectorGrammar.DescendantSyntax { },
new SelectorGrammar.NotSyntax
{
Argument = new SelectorGrammar.ISyntax[]
Argument = new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.ClassSyntax { Class = "foo" },
@ -459,7 +459,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("TextBlock, :is(Button).foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "TextBlock" },
new SelectorGrammar.CommaSyntax(),
@ -475,7 +475,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("^.foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.NestingSyntax(),
new SelectorGrammar.ClassSyntax { Class = "foo" },
@ -489,7 +489,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("^ > .foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.NestingSyntax(),
new SelectorGrammar.ChildSyntax { },
@ -504,7 +504,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("^ .foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.NestingSyntax(),
new SelectorGrammar.DescendantSyntax { },
@ -519,7 +519,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("^ /template/ .foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.NestingSyntax(),
new SelectorGrammar.TemplateSyntax { },
@ -534,7 +534,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("Button /template/ ^");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.TemplateSyntax { },
@ -549,7 +549,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("^[Foo=bar]");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.NestingSyntax(),
new SelectorGrammar.PropertySyntax { Property = "Foo", Value = "bar" },
@ -563,7 +563,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse(":not(^)");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.NotSyntax
{
@ -579,7 +579,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("^:nth-child(2n+1)");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.NestingSyntax(),
new SelectorGrammar.NthChildSyntax()
@ -597,7 +597,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = SelectorGrammar.Parse("^, ^.foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
new ISyntax[]
{
new SelectorGrammar.NestingSyntax(),
new SelectorGrammar.CommaSyntax(),

Loading…
Cancel
Save