Browse Source

feat(Style): Allow to using attached properties in attribute selector

pull/6992/head
Giuseppe Lippolis 5 years ago
parent
commit
4870fc0d4d
  1. 68
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
  2. 55
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs

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

@ -25,6 +25,7 @@ namespace Avalonia.Markup.Parsers
Traversal,
TypeName,
Property,
AttachedProperty,
Template,
End,
}
@ -74,6 +75,9 @@ namespace Avalonia.Markup.Parsers
case State.Name:
(state, syntax) = ParseName(ref r);
break;
case State.AttachedProperty:
(state, syntax) = ParseAttachedProperty(ref r);
break;
}
if (syntax != null)
{
@ -270,11 +274,15 @@ namespace Avalonia.Markup.Parsers
return (State.CanHaveType, ParseType(ref r, new OfTypeSyntax()));
}
private static (State, ISyntax) ParseProperty(ref CharacterReader r)
private static (State, ISyntax?) ParseProperty(ref CharacterReader r)
{
var property = r.ParseIdentifier();
if (!r.TakeIf('='))
if (r.TakeIf('('))
{
return (State.AttachedProperty, default);
}
else if (!r.TakeIf('='))
{
throw new ExpressionParseException(r.Position, $"Expected '=', got '{r.Peek}'");
}
@ -286,6 +294,42 @@ namespace Avalonia.Markup.Parsers
return (State.CanHaveType, new PropertySyntax { Property = property.ToString(), Value = value.ToString() });
}
private static (State, ISyntax) ParseAttachedProperty(ref CharacterReader r)
{
var syntax = ParseType(ref r, new AttachedPropertySyntax());
if (!r.TakeIf('.'))
{
throw new ExpressionParseException(r.Position, $"Expected '.', got '{r.Peek}'");
}
var property = r.ParseIdentifier();
if (property.IsEmpty)
{
throw new ExpressionParseException(r.Position, $"Expected Attached Property Name, got '{r.Peek}'");
}
syntax.Property = property.ToString();
if (!r.TakeIf(')'))
{
throw new ExpressionParseException(r.Position, $"Expected ')', got '{r.Peek}'");
}
if (!r.TakeIf('='))
{
throw new ExpressionParseException(r.Position, $"Expected '=', got '{r.Peek}'");
}
var value = r.TakeUntil(']');
syntax.Value = value.ToString();
r.Take();
var state = r.End
? State.End
: State.Middle;
return (state, syntax);
}
private static TSyntax ParseType<TSyntax>(ref CharacterReader r, TSyntax syntax)
where TSyntax : ITypeSyntax
{
@ -461,6 +505,26 @@ namespace Avalonia.Markup.Parsers
}
}
public class AttachedPropertySyntax : ISyntax, ITypeSyntax
{
public string Xmlns { get; set; } = string.Empty;
public string TypeName { get; set; } = string.Empty;
public string Property { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
public override bool Equals(object obj)
{
return obj is AttachedPropertySyntax syntax
&& syntax.Xmlns == Xmlns
&& syntax.TypeName == syntax.TypeName
&& syntax.Property == syntax.Property
&& syntax.Value == syntax.Value;
}
}
public class IsSyntax : ISyntax, ITypeSyntax
{
public string TypeName { get; set; } = string.Empty;

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

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using Avalonia.Styling;
using Avalonia.Utilities;
using System.Linq;
namespace Avalonia.Markup.Parsers
{
@ -75,23 +76,67 @@ namespace Avalonia.Markup.Parsers
throw new InvalidOperationException($"Cannot find '{property.Property}' on '{type}");
}
{
object typedValue;
if (TypeUtilities.TryConvert(
targetProperty.PropertyType,
property.Value,
CultureInfo.InvariantCulture,
out typedValue))
{
result = result.PropertyEquals(targetProperty, typedValue);
}
else
{
throw new InvalidOperationException(
$"Could not convert '{property.Value}' to '{targetProperty.PropertyType}");
}
}
break;
}
case SelectorGrammar.AttachedPropertySyntax attachedProperty:
var targetType = result?.TargetType;
if (targetType == null)
{
throw new InvalidOperationException("Attached Property selectors must be applied to a type.");
}
var attachedPropertyOwnerType = Resolve(attachedProperty.Xmlns, attachedProperty.TypeName);
if (attachedPropertyOwnerType is null)
{
throw new InvalidOperationException($"Cannot find '{attachedProperty.Xmlns}:{attachedProperty.TypeName}");
}
var targetAttachedProperty = AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(targetType)
.FirstOrDefault(ap => ap.OwnerType == attachedPropertyOwnerType && ap.Name == attachedProperty.Property);
if (targetAttachedProperty == null)
{
throw new InvalidOperationException($"Cannot find '{attachedProperty.Property}' on '{attachedPropertyOwnerType}");
}
{
object typedValue;
if (TypeUtilities.TryConvert(
targetProperty.PropertyType,
property.Value,
targetAttachedProperty.PropertyType,
attachedProperty.Value,
CultureInfo.InvariantCulture,
out typedValue))
{
result = result.PropertyEquals(targetProperty, typedValue);
result = result.PropertyEquals(targetAttachedProperty, typedValue);
}
else
{
throw new InvalidOperationException(
$"Could not convert '{property.Value}' to '{targetProperty.PropertyType}");
$"Could not convert '{attachedProperty.Value}' to '{targetAttachedProperty.PropertyType}");
}
break;
}
break;
case SelectorGrammar.ChildSyntax child:
result = result.Child();
break;

Loading…
Cancel
Save