committed by
GitHub
71 changed files with 1738 additions and 881 deletions
@ -1,245 +0,0 @@ |
|||||
using System; |
|
||||
using System.Diagnostics.CodeAnalysis; |
|
||||
using System.Globalization; |
|
||||
using static System.Char; |
|
||||
|
|
||||
namespace Avalonia.Utilities |
|
||||
{ |
|
||||
// TODO12: Remove this struct in 12.0 (breaking change)
|
|
||||
|
|
||||
[Obsolete("This type has been superseded by SpanStringTokenizer.")] |
|
||||
#if !BUILDTASK
|
|
||||
public |
|
||||
#endif
|
|
||||
record struct StringTokenizer : IDisposable |
|
||||
{ |
|
||||
private const char DefaultSeparatorChar = ','; |
|
||||
|
|
||||
private readonly string _s; |
|
||||
private readonly int _length; |
|
||||
private readonly char _separator; |
|
||||
private readonly string? _exceptionMessage; |
|
||||
private readonly IFormatProvider _formatProvider; |
|
||||
private int _index; |
|
||||
private int _tokenIndex; |
|
||||
private int _tokenLength; |
|
||||
|
|
||||
public StringTokenizer(string s, IFormatProvider formatProvider, string? exceptionMessage = null) |
|
||||
: this(s, GetSeparatorFromFormatProvider(formatProvider), exceptionMessage) |
|
||||
{ |
|
||||
_formatProvider = formatProvider; |
|
||||
} |
|
||||
|
|
||||
public StringTokenizer(string s, char separator = DefaultSeparatorChar, string? exceptionMessage = null) |
|
||||
{ |
|
||||
_s = s ?? throw new ArgumentNullException(nameof(s)); |
|
||||
_length = s?.Length ?? 0; |
|
||||
_separator = separator; |
|
||||
_exceptionMessage = exceptionMessage; |
|
||||
_formatProvider = CultureInfo.InvariantCulture; |
|
||||
_index = 0; |
|
||||
_tokenIndex = -1; |
|
||||
_tokenLength = 0; |
|
||||
|
|
||||
while (_index < _length && IsWhiteSpace(_s, _index)) |
|
||||
{ |
|
||||
_index++; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public string? CurrentToken => _tokenIndex < 0 ? null : _s.Substring(_tokenIndex, _tokenLength); |
|
||||
|
|
||||
public ReadOnlySpan<char> CurrentTokenSpan => _tokenIndex < 0 ? ReadOnlySpan<char>.Empty : _s.AsSpan().Slice(_tokenIndex, _tokenLength); |
|
||||
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
if (_index != _length) |
|
||||
{ |
|
||||
throw GetFormatException(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public bool TryReadInt32(out Int32 result, char? separator = null) |
|
||||
{ |
|
||||
if (TryReadSpan(out var stringResult, separator) && |
|
||||
SpanHelpers.TryParseInt(stringResult, NumberStyles.Integer, _formatProvider, out result)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
result = default; |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public int ReadInt32(char? separator = null) |
|
||||
{ |
|
||||
if (!TryReadInt32(out var result, separator)) |
|
||||
{ |
|
||||
throw GetFormatException(); |
|
||||
} |
|
||||
|
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
public bool TryReadDouble(out double result, char? separator = null) |
|
||||
{ |
|
||||
if (TryReadSpan(out var stringResult, separator) && |
|
||||
SpanHelpers.TryParseDouble(stringResult, NumberStyles.Float, _formatProvider, out result)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
result = default; |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public double ReadDouble(char? separator = null) |
|
||||
{ |
|
||||
if (!TryReadDouble(out var result, separator)) |
|
||||
{ |
|
||||
throw GetFormatException(); |
|
||||
} |
|
||||
|
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
public bool TryReadString([NotNull] out string result, char? separator = null) |
|
||||
{ |
|
||||
var success = TryReadToken(separator ?? _separator); |
|
||||
result = CurrentTokenSpan.ToString(); |
|
||||
return success; |
|
||||
} |
|
||||
|
|
||||
public string ReadString(char? separator = null) |
|
||||
{ |
|
||||
if (!TryReadString(out var result, separator)) |
|
||||
{ |
|
||||
throw GetFormatException(); |
|
||||
} |
|
||||
|
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
public bool TryReadSpan(out ReadOnlySpan<char> result, char? separator = null) |
|
||||
{ |
|
||||
var success = TryReadToken(separator ?? _separator); |
|
||||
result = CurrentTokenSpan; |
|
||||
return success; |
|
||||
} |
|
||||
|
|
||||
public ReadOnlySpan<char> ReadSpan(char? separator = null) |
|
||||
{ |
|
||||
if (!TryReadSpan(out var result, separator)) |
|
||||
{ |
|
||||
throw GetFormatException(); |
|
||||
} |
|
||||
|
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
private bool TryReadToken(char separator) |
|
||||
{ |
|
||||
_tokenIndex = -1; |
|
||||
|
|
||||
if (_index >= _length) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
var c = _s[_index]; |
|
||||
|
|
||||
var index = _index; |
|
||||
var length = 0; |
|
||||
|
|
||||
while (_index < _length) |
|
||||
{ |
|
||||
c = _s[_index]; |
|
||||
|
|
||||
if (IsWhiteSpace(c) || c == separator) |
|
||||
{ |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
_index++; |
|
||||
length++; |
|
||||
} |
|
||||
|
|
||||
SkipToNextToken(separator); |
|
||||
|
|
||||
_tokenIndex = index; |
|
||||
_tokenLength = length; |
|
||||
|
|
||||
if (_tokenLength < 1) |
|
||||
{ |
|
||||
throw GetFormatException(); |
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
private void SkipToNextToken(char separator) |
|
||||
{ |
|
||||
if (_index < _length) |
|
||||
{ |
|
||||
var c = _s[_index]; |
|
||||
|
|
||||
if (c != separator && !IsWhiteSpace(c)) |
|
||||
{ |
|
||||
throw GetFormatException(); |
|
||||
} |
|
||||
|
|
||||
var length = 0; |
|
||||
|
|
||||
while (_index < _length) |
|
||||
{ |
|
||||
c = _s[_index]; |
|
||||
|
|
||||
if (c == separator) |
|
||||
{ |
|
||||
length++; |
|
||||
_index++; |
|
||||
|
|
||||
if (length > 1) |
|
||||
{ |
|
||||
throw GetFormatException(); |
|
||||
} |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
if (!IsWhiteSpace(c)) |
|
||||
{ |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
_index++; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (length > 0 && _index >= _length) |
|
||||
{ |
|
||||
throw GetFormatException(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private FormatException GetFormatException() => |
|
||||
_exceptionMessage != null ? new FormatException(_exceptionMessage) : new FormatException(); |
|
||||
|
|
||||
private static char GetSeparatorFromFormatProvider(IFormatProvider provider) |
|
||||
{ |
|
||||
var c = DefaultSeparatorChar; |
|
||||
|
|
||||
var formatInfo = NumberFormatInfo.GetInstance(provider); |
|
||||
if (formatInfo.NumberDecimalSeparator.Length > 0 && c == formatInfo.NumberDecimalSeparator[0]) |
|
||||
{ |
|
||||
c = ';'; |
|
||||
} |
|
||||
|
|
||||
return c; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,69 @@ |
|||||
|
using System.Linq; |
||||
|
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; |
||||
|
using XamlX.Ast; |
||||
|
using XamlX.Emit; |
||||
|
using XamlX.IL; |
||||
|
using XamlX.Transform; |
||||
|
|
||||
|
namespace Avalonia.Markup.Xaml.Loader.CompilerExtensions.Transformers |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// An XAMLIL AST transformer that injects <see cref="Avalonia.Markup.Xaml.Diagnostics.XamlSourceInfo"/> metadata into the generated XAML code.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// This transformer wraps object creation nodes with a manipulation node that adds source information.
|
||||
|
/// This source information includes line number, position, and document name, which can be useful for debugging and diagnostics.
|
||||
|
/// Note: ResourceDictionary source info is handled separately in <see cref="AvaloniaXamlResourceTransformer"/>.
|
||||
|
/// </remarks>
|
||||
|
internal class AvaloniaXamlIlAddSourceInfoTransformer : IXamlAstTransformer |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets or sets a value indicating whether source information should be generated
|
||||
|
/// and injected into the compiled XAML output.
|
||||
|
/// </summary>
|
||||
|
public bool CreateSourceInfo { get; set; } |
||||
|
|
||||
|
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) |
||||
|
{ |
||||
|
if (CreateSourceInfo |
||||
|
&& node is XamlAstNewClrObjectNode objNode |
||||
|
&& context.ParentNodes().FirstOrDefault() is not XamlValueWithManipulationNode { Manipulation: XamlSourceInfoValueManipulation } |
||||
|
&& !objNode.Type.GetClrType().IsValueType) |
||||
|
{ |
||||
|
var avaloniaTypes = context.GetAvaloniaTypes(); |
||||
|
|
||||
|
return new XamlValueWithManipulationNode( |
||||
|
objNode, objNode, |
||||
|
new XamlSourceInfoValueManipulation(avaloniaTypes, objNode, context.Document)); |
||||
|
} |
||||
|
|
||||
|
return node; |
||||
|
} |
||||
|
|
||||
|
private class XamlSourceInfoValueManipulation( |
||||
|
AvaloniaXamlIlWellKnownTypes avaloniaTypes, |
||||
|
XamlAstNewClrObjectNode objNode, string? document) |
||||
|
: XamlAstNode(objNode), IXamlAstManipulationNode, IXamlAstILEmitableNode |
||||
|
{ |
||||
|
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen) |
||||
|
{ |
||||
|
// Target object is already on stack.
|
||||
|
|
||||
|
// var info = new XamlSourceInfo(Line, Position, Document);
|
||||
|
codeGen.Ldc_I4(Line); |
||||
|
codeGen.Ldc_I4(Position); |
||||
|
if (document is not null) |
||||
|
codeGen.Ldstr(document); |
||||
|
else |
||||
|
codeGen.Ldnull(); |
||||
|
codeGen.Newobj(avaloniaTypes.XamlSourceInfoConstructor); |
||||
|
|
||||
|
// Set the XamlSourceInfo property on the current object
|
||||
|
// XamlSourceInfo.SetValue(@this, info);
|
||||
|
codeGen.EmitCall(avaloniaTypes.XamlSourceInfoSetter); |
||||
|
|
||||
|
return XamlILNodeEmitResult.Void(1); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,191 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors; |
|
||||
using XamlX; |
|
||||
using XamlX.Ast; |
|
||||
using XamlX.Emit; |
|
||||
using XamlX.IL; |
|
||||
using XamlX.Transform; |
|
||||
using XamlX.TypeSystem; |
|
||||
|
|
||||
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers |
|
||||
{ |
|
||||
internal class AvaloniaXamlIlDeferredResourceTransformer : IXamlAstTransformer |
|
||||
{ |
|
||||
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) |
|
||||
{ |
|
||||
if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2) |
|
||||
return node; |
|
||||
|
|
||||
var types = context.GetAvaloniaTypes(); |
|
||||
|
|
||||
if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content" |
|
||||
&& ShouldBeDeferred(pa.Values[1])) |
|
||||
{ |
|
||||
IXamlMethod addMethod = TryGetSharedValue(pa.Values[1], out var isShared) && !isShared |
|
||||
? types.ResourceDictionaryNotSharedDeferredAdd |
|
||||
: types.ResourceDictionaryDeferredAdd; |
|
||||
|
|
||||
pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration); |
|
||||
pa.PossibleSetters = new List<IXamlPropertySetter> |
|
||||
{ |
|
||||
new XamlDirectCallPropertySetter(addMethod), |
|
||||
}; |
|
||||
} |
|
||||
else if (pa.Property.Name == "Resources" && pa.Property.Getter?.ReturnType.Equals(types.IResourceDictionary) == true |
|
||||
&& ShouldBeDeferred(pa.Values[1])) |
|
||||
{ |
|
||||
IXamlMethod addMethod = TryGetSharedValue(pa.Values[1], out var isShared) && !isShared |
|
||||
? types.ResourceDictionaryNotSharedDeferredAdd |
|
||||
: types.ResourceDictionaryDeferredAdd; |
|
||||
|
|
||||
pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration); |
|
||||
pa.PossibleSetters = new List<IXamlPropertySetter> |
|
||||
{ |
|
||||
new AdderSetter(pa.Property.Getter, addMethod), |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
return node; |
|
||||
|
|
||||
bool TryGetSharedValue(IXamlAstValueNode valueNode, out bool value) |
|
||||
{ |
|
||||
value = default; |
|
||||
if (valueNode is XamlAstConstructableObjectNode co) |
|
||||
{ |
|
||||
// Try find x:Share directive
|
|
||||
if (co.Children.Find(d => d is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: "Shared" }) is XamlAstXmlDirective sharedDirective) |
|
||||
{ |
|
||||
if (sharedDirective.Values.Count == 1 && sharedDirective.Values[0] is XamlAstTextNode text) |
|
||||
{ |
|
||||
if (bool.TryParse(text.Text, out var parseValue)) |
|
||||
{ |
|
||||
// If the parser succeeds, remove the x:Share directive
|
|
||||
co.Children.Remove(sharedDirective); |
|
||||
return true; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
context.ReportTransformError("Invalid argument type for x:Shared directive.", node); |
|
||||
} |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
context.ReportTransformError("Invalid number of arguments for x:Shared directive.", node); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private static bool ShouldBeDeferred(IXamlAstValueNode node) |
|
||||
{ |
|
||||
var clrType = node.Type.GetClrType(); |
|
||||
|
|
||||
// XAML compiler is currently strict about value types, allowing them to be created only through converters.
|
|
||||
// At the moment it should be safe to not defer structs.
|
|
||||
if (clrType.IsValueType) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
// Never defer strings.
|
|
||||
if (clrType.FullName == "System.String") |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
// Do not defer resources, if it has any x:Name registration, as it cannot be delayed.
|
|
||||
// This visitor will count x:Name registrations, ignoring nested NestedScopeMetadataNode scopes.
|
|
||||
// We set target scope level to 0, assuming that this resource node is a scope of itself.
|
|
||||
var nameRegistrationsVisitor = new NameScopeRegistrationVisitor( |
|
||||
targetMetadataScopeLevel: 0); |
|
||||
node.Visit(nameRegistrationsVisitor); |
|
||||
if (nameRegistrationsVisitor.Count > 0) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable<AdderSetter> |
|
||||
{ |
|
||||
private readonly IXamlMethod _getter; |
|
||||
private readonly IXamlMethod _adder; |
|
||||
|
|
||||
public AdderSetter(IXamlMethod getter, IXamlMethod adder) |
|
||||
{ |
|
||||
_getter = getter; |
|
||||
_adder = adder; |
|
||||
TargetType = getter.DeclaringType; |
|
||||
Parameters = adder.ParametersWithThis().Skip(1).ToList(); |
|
||||
|
|
||||
bool allowNull = Parameters.Last().AcceptsNull(); |
|
||||
BinderParameters = new PropertySetterBinderParameters |
|
||||
{ |
|
||||
AllowMultiple = true, |
|
||||
AllowXNull = allowNull, |
|
||||
AllowRuntimeNull = allowNull, |
|
||||
AllowAttributeSyntax = false, |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
public IXamlType TargetType { get; } |
|
||||
|
|
||||
public PropertySetterBinderParameters BinderParameters { get; } |
|
||||
|
|
||||
public IReadOnlyList<IXamlType> Parameters { get; } |
|
||||
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes => _adder.CustomAttributes; |
|
||||
|
|
||||
public void Emit(IXamlILEmitter emitter) |
|
||||
{ |
|
||||
var locals = new Stack<XamlLocalsPool.PooledLocal>(); |
|
||||
// Save all "setter" parameters
|
|
||||
for (var c = Parameters.Count - 1; c >= 0; c--) |
|
||||
{ |
|
||||
var loc = emitter.LocalsPool.GetLocal(Parameters[c]); |
|
||||
locals.Push(loc); |
|
||||
emitter.Stloc(loc.Local); |
|
||||
} |
|
||||
|
|
||||
emitter.EmitCall(_getter); |
|
||||
while (locals.Count>0) |
|
||||
using (var loc = locals.Pop()) |
|
||||
emitter.Ldloc(loc.Local); |
|
||||
emitter.EmitCall(_adder, true); |
|
||||
} |
|
||||
|
|
||||
public void EmitWithArguments( |
|
||||
XamlEmitContextWithLocals<IXamlILEmitter, XamlILNodeEmitResult> context, |
|
||||
IXamlILEmitter emitter, |
|
||||
IReadOnlyList<IXamlAstValueNode> arguments) |
|
||||
{ |
|
||||
emitter.EmitCall(_getter); |
|
||||
|
|
||||
for (var i = 0; i < arguments.Count; ++i) |
|
||||
context.Emit(arguments[i], emitter, Parameters[i]); |
|
||||
|
|
||||
emitter.EmitCall(_adder, true); |
|
||||
} |
|
||||
|
|
||||
public bool Equals(AdderSetter? other) |
|
||||
{ |
|
||||
if (ReferenceEquals(null, other)) |
|
||||
return false; |
|
||||
if (ReferenceEquals(this, other)) |
|
||||
return true; |
|
||||
|
|
||||
return _getter.Equals(other._getter) && _adder.Equals(other._adder); |
|
||||
} |
|
||||
|
|
||||
public override bool Equals(object? obj) |
|
||||
=> Equals(obj as AdderSetter); |
|
||||
|
|
||||
public override int GetHashCode() |
|
||||
=> (_getter.GetHashCode() * 397) ^ _adder.GetHashCode(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,343 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors; |
||||
|
using XamlX; |
||||
|
using XamlX.Ast; |
||||
|
using XamlX.Emit; |
||||
|
using XamlX.IL; |
||||
|
using XamlX.Transform; |
||||
|
using XamlX.TypeSystem; |
||||
|
|
||||
|
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Transforms ResourceDictionary and IResourceDictionary property assignments
|
||||
|
/// to use Add method calls with deferred content where applicable.
|
||||
|
/// Additionally, handles x:Shared on assignments and injects XamlSourceInfo.
|
||||
|
/// </summary>
|
||||
|
internal class AvaloniaXamlResourceTransformer : IXamlAstTransformer |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets or sets a value indicating whether source information should be generated
|
||||
|
/// and injected into the compiled XAML output.
|
||||
|
/// </summary>
|
||||
|
public bool CreateSourceInfo { get; set; } = true; |
||||
|
|
||||
|
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) |
||||
|
{ |
||||
|
if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2) |
||||
|
return node; |
||||
|
|
||||
|
var types = context.GetAvaloniaTypes(); |
||||
|
var document = context.Document; |
||||
|
|
||||
|
if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content") |
||||
|
{ |
||||
|
var value = pa.Values[1]; |
||||
|
(var adder, value) = ResolveAdderAndValue(value); |
||||
|
|
||||
|
pa.Values[1] = value; |
||||
|
pa.PossibleSetters = new List<IXamlPropertySetter> |
||||
|
{ |
||||
|
new AdderSetter(adder, CreateSourceInfo, types, value.Line, value.Position, document), |
||||
|
}; |
||||
|
} |
||||
|
else if (pa.Property.Name == "Resources" && pa.Property.Getter?.ReturnType.Equals(types.IResourceDictionary) == true) |
||||
|
{ |
||||
|
var value = pa.Values[1]; |
||||
|
(var adder, value) = ResolveAdderAndValue(value); |
||||
|
|
||||
|
pa.Values[1] = value; |
||||
|
pa.PossibleSetters = new List<IXamlPropertySetter> |
||||
|
{ |
||||
|
new AdderSetter(pa.Property.Getter, adder, CreateSourceInfo, types, value.Line, value.Position, document), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
return node; |
||||
|
|
||||
|
(IXamlMethod adder, IXamlAstValueNode newValue) ResolveAdderAndValue(IXamlAstValueNode valueNode) |
||||
|
{ |
||||
|
if (ShouldBeDeferred(valueNode)) |
||||
|
{ |
||||
|
var adder = TryGetSharedValue(valueNode, out var isShared) && !isShared |
||||
|
? types.ResourceDictionaryNotSharedDeferredAdd |
||||
|
: types.ResourceDictionaryDeferredAdd; |
||||
|
var deferredNode = new XamlDeferredContentNode(valueNode, types.XamlIlTypes.Object, context.Configuration); |
||||
|
return (adder, deferredNode); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var adder = XamlTransformHelpers.FindPossibleAdders(context, types.IResourceDictionary) |
||||
|
.FirstOrDefault() ?? throw new XamlTransformException("No suitable Add method found for IResourceDictionary.", node); |
||||
|
return (adder, valueNode); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool TryGetSharedValue(IXamlAstValueNode valueNode, out bool value) |
||||
|
{ |
||||
|
value = default; |
||||
|
if (valueNode is XamlAstConstructableObjectNode co) |
||||
|
{ |
||||
|
// Try find x:Share directive
|
||||
|
if (co.Children.Find(d => d is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: "Shared" }) is XamlAstXmlDirective sharedDirective) |
||||
|
{ |
||||
|
if (sharedDirective.Values.Count == 1 && sharedDirective.Values[0] is XamlAstTextNode text) |
||||
|
{ |
||||
|
if (bool.TryParse(text.Text, out var parseValue)) |
||||
|
{ |
||||
|
// If the parser succeeds, remove the x:Share directive
|
||||
|
co.Children.Remove(sharedDirective); |
||||
|
return true; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
context.ReportTransformError("Invalid argument type for x:Shared directive.", node); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
context.ReportTransformError("Invalid number of arguments for x:Shared directive.", node); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static bool ShouldBeDeferred(IXamlAstValueNode node) |
||||
|
{ |
||||
|
var clrType = node.Type.GetClrType(); |
||||
|
|
||||
|
// XAML compiler is currently strict about value types, allowing them to be created only through converters.
|
||||
|
// At the moment it should be safe to not defer structs.
|
||||
|
if (clrType.IsValueType) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// Never defer strings.
|
||||
|
if (clrType.FullName == "System.String") |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// Do not defer resources, if it has any x:Name registration, as it cannot be delayed.
|
||||
|
// This visitor will count x:Name registrations, ignoring nested NestedScopeMetadataNode scopes.
|
||||
|
// We set target scope level to 0, assuming that this resource node is a scope of itself.
|
||||
|
var nameRegistrationsVisitor = new NameScopeRegistrationVisitor( |
||||
|
targetMetadataScopeLevel: 0); |
||||
|
node.Visit(nameRegistrationsVisitor); |
||||
|
if (nameRegistrationsVisitor.Count > 0) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable<AdderSetter> |
||||
|
{ |
||||
|
private readonly IXamlMethod? _getter; |
||||
|
private readonly IXamlMethod _adder; |
||||
|
private readonly bool _emitSourceInfo; |
||||
|
private readonly AvaloniaXamlIlWellKnownTypes _avaloniaTypes; |
||||
|
private readonly string? _document; |
||||
|
private readonly int _line, _position; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates an adder-only setter. Target is assumed to be already on the stack before emit.
|
||||
|
/// For example:
|
||||
|
/// var resourceDictionary = ...
|
||||
|
/// resourceDictionary.Add(key, value);
|
||||
|
/// resourceDictionary.Add(key2, value2);
|
||||
|
/// </summary>
|
||||
|
public AdderSetter( |
||||
|
IXamlMethod adder, |
||||
|
bool emitSourceInfo, |
||||
|
AvaloniaXamlIlWellKnownTypes avaloniaTypes, |
||||
|
int line, int position, string? document) |
||||
|
{ |
||||
|
_adder = adder; |
||||
|
_emitSourceInfo = emitSourceInfo; |
||||
|
_avaloniaTypes = avaloniaTypes; |
||||
|
_line = line; |
||||
|
_position = position; |
||||
|
_document = document; |
||||
|
|
||||
|
TargetType = adder.ThisOrFirstParameter(); |
||||
|
Parameters = adder.ParametersWithThis().Skip(1).ToList(); |
||||
|
bool allowNull = Parameters.Last().AcceptsNull(); |
||||
|
BinderParameters = new PropertySetterBinderParameters |
||||
|
{ |
||||
|
AllowMultiple = true, |
||||
|
AllowXNull = allowNull, |
||||
|
AllowRuntimeNull = allowNull |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Explicit target getter - target will be obtained by calling the getter first.
|
||||
|
///
|
||||
|
/// </summary>
|
||||
|
public AdderSetter( |
||||
|
IXamlMethod getter, IXamlMethod adder, |
||||
|
bool emitSourceInfo, |
||||
|
AvaloniaXamlIlWellKnownTypes avaloniaTypes, |
||||
|
int line, int position, string? document) |
||||
|
: this(adder, emitSourceInfo, avaloniaTypes, line, position, document) |
||||
|
{ |
||||
|
_getter = getter; |
||||
|
TargetType = getter.DeclaringType; |
||||
|
BinderParameters.AllowMultiple = false; |
||||
|
BinderParameters.AllowAttributeSyntax = false; |
||||
|
} |
||||
|
|
||||
|
public IXamlType TargetType { get; } |
||||
|
|
||||
|
public PropertySetterBinderParameters BinderParameters { get; } |
||||
|
|
||||
|
public IReadOnlyList<IXamlType> Parameters { get; } |
||||
|
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes => _adder.CustomAttributes; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Emits the setter with arguments already on the stack.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// If _getter is null - assume target is already on the stack.
|
||||
|
/// In this case, we can just call Emit. Unless _emitSourceInfo is true.
|
||||
|
///
|
||||
|
/// If _emitSourceInfo is true - we need to make sure that target and key are on the stack for XamlSourceInfo setting,
|
||||
|
/// so we need to store parameters to locals first regardless.
|
||||
|
/// </remarks>
|
||||
|
public void Emit(IXamlILEmitter emitter) |
||||
|
{ |
||||
|
using var keyLocal = emitter.LocalsPool.GetLocal(Parameters[0]); |
||||
|
|
||||
|
if (_getter is not null || _emitSourceInfo) |
||||
|
{ |
||||
|
var locals = new Stack<XamlLocalsPool.PooledLocal>(); |
||||
|
// Save all "setter" parameters
|
||||
|
for (var c = Parameters.Count - 1; c >= 0; c--) |
||||
|
{ |
||||
|
var loc = emitter.LocalsPool.GetLocal(Parameters[c]); |
||||
|
locals.Push(loc); |
||||
|
emitter.Stloc(loc.Local); |
||||
|
|
||||
|
if (c == 0 && _emitSourceInfo) |
||||
|
{ |
||||
|
// Store the key argument for XamlSourceInfo later
|
||||
|
emitter.Ldloc(loc.Local); |
||||
|
emitter.Stloc(keyLocal.Local); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (_getter is not null) |
||||
|
{ |
||||
|
emitter.EmitCall(_getter); |
||||
|
} |
||||
|
|
||||
|
// Duplicate the target object on stack for setting XamlSourceInfo later
|
||||
|
emitter.Dup(); |
||||
|
|
||||
|
while (locals.Count > 0) |
||||
|
using (var loc = locals.Pop()) |
||||
|
emitter.Ldloc(loc.Local); |
||||
|
} |
||||
|
|
||||
|
emitter.EmitCall(_adder, true); |
||||
|
|
||||
|
if (_emitSourceInfo) |
||||
|
{ |
||||
|
// Target is already on stack (dup)
|
||||
|
// Load the key argument from local
|
||||
|
emitter.Ldloc(keyLocal.Local); |
||||
|
EmitSetSourceInfo(emitter); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Emits the setter with provided arguments that are not yet on the stack.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// If _getter is null - assume target is already on the stack.
|
||||
|
/// If _emitSourceInfo is true - we need to make sure that target and key are on the stack for XamlSourceInfo setting.
|
||||
|
/// </remarks>
|
||||
|
public void EmitWithArguments( |
||||
|
XamlEmitContextWithLocals<IXamlILEmitter, XamlILNodeEmitResult> context, |
||||
|
IXamlILEmitter emitter, |
||||
|
IReadOnlyList<IXamlAstValueNode> arguments) |
||||
|
{ |
||||
|
using var keyLocal = _emitSourceInfo ? emitter.LocalsPool.GetLocal(Parameters[0]) : null; |
||||
|
|
||||
|
if (_getter is not null) |
||||
|
{ |
||||
|
emitter.EmitCall(_getter); |
||||
|
} |
||||
|
|
||||
|
if (_emitSourceInfo) |
||||
|
{ |
||||
|
// Duplicate the target object on stack for setting XamlSourceInfo later
|
||||
|
emitter.Dup(); |
||||
|
} |
||||
|
|
||||
|
for (var i = 0; i < arguments.Count; ++i) |
||||
|
{ |
||||
|
context.Emit(arguments[i], emitter, Parameters[i]); |
||||
|
|
||||
|
// Store the key argument for XamlSourceInfo later
|
||||
|
if (i == 0 && _emitSourceInfo) |
||||
|
{ |
||||
|
emitter.Stloc(keyLocal!.Local); |
||||
|
emitter.Ldloc(keyLocal.Local); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
emitter.EmitCall(_adder, true); |
||||
|
|
||||
|
if (_emitSourceInfo) |
||||
|
{ |
||||
|
// Target is already on stack (dub)
|
||||
|
// Load the key argument from local
|
||||
|
emitter.Ldloc(keyLocal!.Local); |
||||
|
|
||||
|
EmitSetSourceInfo(emitter); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void EmitSetSourceInfo(IXamlILEmitter emitter) |
||||
|
{ |
||||
|
// Assumes the target object and key are already on the stack
|
||||
|
|
||||
|
emitter.Ldc_I4(_line); |
||||
|
emitter.Ldc_I4(_position); |
||||
|
if (_document is not null) |
||||
|
emitter.Ldstr(_document); |
||||
|
else |
||||
|
emitter.Ldnull(); |
||||
|
emitter.Newobj(_avaloniaTypes.XamlSourceInfoConstructor); |
||||
|
|
||||
|
// Set the XamlSourceInfo property on the current object
|
||||
|
// XamlSourceInfo.SetXamlSourceInfo(@this, key, info);
|
||||
|
emitter.EmitCall(_avaloniaTypes.XamlSourceInfoDictionarySetter); |
||||
|
} |
||||
|
|
||||
|
public bool Equals(AdderSetter? other) |
||||
|
{ |
||||
|
if (ReferenceEquals(null, other)) |
||||
|
return false; |
||||
|
if (ReferenceEquals(this, other)) |
||||
|
return true; |
||||
|
|
||||
|
return _getter?.Equals(other._getter) == true && _adder.Equals(other._adder); |
||||
|
} |
||||
|
|
||||
|
public override bool Equals(object? obj) |
||||
|
=> Equals(obj as AdderSetter); |
||||
|
|
||||
|
public override int GetHashCode() |
||||
|
=> (_getter, _adder).GetHashCode(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
using XamlX.Ast; |
||||
|
|
||||
|
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; |
||||
|
|
||||
|
internal static class XamlAstNewClrObjectHelper |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Tries to resolve the underlying value of a <see cref="XamlValueWithManipulationNode"/>,
|
||||
|
/// unwrapping any nested <see cref="XamlValueWithManipulationNode"/> instances.
|
||||
|
/// </summary>
|
||||
|
public static TXamlAstValueNode? UnwrapValue<TXamlAstValueNode>(this XamlValueWithManipulationNode node) |
||||
|
where TXamlAstValueNode : class, IXamlAstValueNode |
||||
|
{ |
||||
|
var current = node.Value; |
||||
|
while (current is XamlValueWithManipulationNode valueWithManipulation) |
||||
|
{ |
||||
|
current = valueWithManipulation.Value; |
||||
|
if (current is TXamlAstValueNode typedValue) |
||||
|
{ |
||||
|
return typedValue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return current as TXamlAstValueNode; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,151 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Avalonia.Markup.Xaml.Diagnostics |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents source location information for an element within a XAML or code file.
|
||||
|
/// </summary>
|
||||
|
// ReSharper disable once ClassNeverInstantiated.Global //This class is instantiated through the XAML compiler.
|
||||
|
public record XamlSourceInfo |
||||
|
{ |
||||
|
private static readonly AttachedProperty<XamlSourceInfo?> s_xamlSourceInfo = |
||||
|
AvaloniaProperty.RegisterAttached<AvaloniaObject, XamlSourceInfo?>( |
||||
|
"XamlSourceInfo", typeof(XamlSourceInfo)); |
||||
|
|
||||
|
private static readonly ConditionalWeakTable<object, XamlSourceInfo?> s_sourceInfo = []; |
||||
|
private static readonly ConditionalWeakTable<IResourceDictionary, Dictionary<object, XamlSourceInfo?>> s_keyedSourceInfo = []; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the full path of the source file containing the element, or <c>null</c> if unavailable.
|
||||
|
/// </summary>
|
||||
|
public Uri? SourceUri { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the 1-based line number in the source file where the element is defined.
|
||||
|
/// </summary>
|
||||
|
public int LineNumber { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the 1-based column number in the source file where the element is defined.
|
||||
|
/// </summary>
|
||||
|
public int LinePosition { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="XamlSourceInfo"/> class
|
||||
|
/// with a specified line, column, and file path.
|
||||
|
/// </summary>
|
||||
|
/// <param name="line">The line number of the source element.</param>
|
||||
|
/// <param name="column">The column number of the source element.</param>
|
||||
|
/// <param name="filePath">The full path of the source file.</param>
|
||||
|
public XamlSourceInfo(int line, int column, string? filePath) |
||||
|
{ |
||||
|
LineNumber = line; |
||||
|
LinePosition = column; |
||||
|
SourceUri = filePath is not null ? new UriBuilder("file", "") { Path = filePath }.Uri : null; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Associates XAML source information with the specified object for debugging or diagnostic purposes.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>This method is typically used to enable enhanced debugging or diagnostics by tracking
|
||||
|
/// the origin of XAML elements at runtime. If the same object is passed multiple times, the most recent source
|
||||
|
/// information will overwrite any previous value.</remarks>
|
||||
|
/// <param name="obj">The object to associate with the XAML source information. Cannot be null.</param>
|
||||
|
/// <param name="info">The XAML source information to associate with the object, or null to remove any existing association.</param>
|
||||
|
public static void SetXamlSourceInfo(object obj, XamlSourceInfo? info) |
||||
|
{ |
||||
|
if (obj is null) |
||||
|
throw new ArgumentNullException(nameof(obj)); |
||||
|
|
||||
|
if (obj is AvaloniaObject avaloniaObject) |
||||
|
{ |
||||
|
avaloniaObject.SetValue(s_xamlSourceInfo, info); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
s_sourceInfo.AddOrUpdate(obj, info); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Associates XAML source information with the specified key in the given resource dictionary.
|
||||
|
/// </summary>
|
||||
|
/// <param name="dictionary"> The resource dictionary to associate with the XAML source information.</param>
|
||||
|
/// <param name="key">The key associated with the source info.</param>
|
||||
|
/// <param name="info">The XAML source information to associate with the object, or null to remove any existing association.</param>
|
||||
|
public static void SetXamlSourceInfo(IResourceDictionary dictionary, object key, XamlSourceInfo? info) |
||||
|
{ |
||||
|
if (dictionary is null) |
||||
|
throw new ArgumentNullException(nameof(dictionary)); |
||||
|
|
||||
|
var dict = s_keyedSourceInfo.GetOrCreateValue(dictionary); |
||||
|
if (info == null) |
||||
|
{ |
||||
|
_ = dict.Remove(key); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
dict[key] = info; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Retrieves the XAML source information associated with the specified object, if available.
|
||||
|
/// </summary>
|
||||
|
/// <param name="obj">The object for which to obtain XAML source information. Cannot be null.</param>
|
||||
|
/// <returns>A <see cref="XamlSourceInfo"/> instance containing the XAML source information for the specified object, or
|
||||
|
/// <see langword="null"/> if no source information is available.</returns>
|
||||
|
public static XamlSourceInfo? GetXamlSourceInfo(object obj) |
||||
|
{ |
||||
|
if (obj is null) |
||||
|
throw new ArgumentNullException(nameof(obj)); |
||||
|
|
||||
|
if (obj is AvaloniaObject avaloniaObject) |
||||
|
{ |
||||
|
return avaloniaObject.GetValue(s_xamlSourceInfo); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
s_sourceInfo.TryGetValue(obj, out var info); |
||||
|
return info; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Retrieves the XAML source information associated with the specified key in the given resource dictionary, if available.
|
||||
|
/// </summary>
|
||||
|
/// <param name="dictionary"> The resource dictionary associated with the XAML source information.</param>
|
||||
|
/// <param name="key">The key associated with the source info.</param>
|
||||
|
/// <returns>A <see cref="XamlSourceInfo"/> instance containing the XAML source information for the specified key, or
|
||||
|
/// <see langword="null"/> if no source information is available.</returns>
|
||||
|
public static XamlSourceInfo? GetXamlSourceInfo(IResourceDictionary dictionary, object key) |
||||
|
{ |
||||
|
if (dictionary is null) |
||||
|
throw new ArgumentNullException(nameof(dictionary)); |
||||
|
|
||||
|
if (s_keyedSourceInfo.TryGetValue(dictionary, out var dict) |
||||
|
&& dict.TryGetValue(key, out var info)) |
||||
|
{ |
||||
|
return info; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns a string that represents the current <see cref="XamlSourceInfo"/>.
|
||||
|
/// </summary>
|
||||
|
/// <returns>
|
||||
|
/// A formatted string in the form <c>"FilePath:Line,Column"</c>,
|
||||
|
/// or <c>"(unknown):Line,Column"</c> if the file path is not set.
|
||||
|
/// </returns>
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
var filePath = SourceUri?.LocalPath ?? "(unknown)"; |
||||
|
return $"{filePath}:{LineNumber},{LinePosition}"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,81 +0,0 @@ |
|||||
using System; |
|
||||
using Avalonia.Utilities; |
|
||||
using Xunit; |
|
||||
|
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
|
||||
|
|
||||
namespace Avalonia.Base.UnitTests.Utilities |
|
||||
{ |
|
||||
public class StringTokenizerTests |
|
||||
{ |
|
||||
[Fact] |
|
||||
public void ReadInt32_Reads_Values() |
|
||||
{ |
|
||||
var target = new StringTokenizer("123,456"); |
|
||||
|
|
||||
Assert.Equal(123, target.ReadInt32()); |
|
||||
Assert.Equal(456, target.ReadInt32()); |
|
||||
Assert.Throws<FormatException>(() => target.ReadInt32()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void ReadDouble_Reads_Values() |
|
||||
{ |
|
||||
var target = new StringTokenizer("12.3,45.6"); |
|
||||
|
|
||||
Assert.Equal(12.3, target.ReadDouble()); |
|
||||
Assert.Equal(45.6, target.ReadDouble()); |
|
||||
Assert.Throws<FormatException>(() => target.ReadDouble()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void TryReadInt32_Reads_Values() |
|
||||
{ |
|
||||
var target = new StringTokenizer("123,456"); |
|
||||
|
|
||||
Assert.True(target.TryReadInt32(out var value)); |
|
||||
Assert.Equal(123, value); |
|
||||
Assert.True(target.TryReadInt32(out value)); |
|
||||
Assert.Equal(456, value); |
|
||||
Assert.False(target.TryReadInt32(out value)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void TryReadInt32_Doesnt_Throw() |
|
||||
{ |
|
||||
var target = new StringTokenizer("abc"); |
|
||||
|
|
||||
Assert.False(target.TryReadInt32(out var value)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void TryReadDouble_Reads_Values() |
|
||||
{ |
|
||||
var target = new StringTokenizer("12.3,45.6"); |
|
||||
|
|
||||
Assert.True(target.TryReadDouble(out var value)); |
|
||||
Assert.Equal(12.3, value); |
|
||||
Assert.True(target.TryReadDouble(out value)); |
|
||||
Assert.Equal(45.6, value); |
|
||||
Assert.False(target.TryReadDouble(out value)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void TryReadDouble_Doesnt_Throw() |
|
||||
{ |
|
||||
var target = new StringTokenizer("abc"); |
|
||||
|
|
||||
Assert.False(target.TryReadDouble(out var value)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void ReadSpan_And_ReadString_Reads_Same() |
|
||||
{ |
|
||||
var target1 = new StringTokenizer("abc,def"); |
|
||||
var target2 = new StringTokenizer("abc,def"); |
|
||||
|
|
||||
Assert.Equal(target1.ReadString(), target2.ReadSpan().ToString()); |
|
||||
Assert.True(target1.ReadSpan().SequenceEqual(target2.ReadString())); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,583 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Animation; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.Shapes; |
||||
|
using Avalonia.Controls.Templates; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.GestureRecognizers; |
||||
|
using Avalonia.Markup.Xaml.Diagnostics; |
||||
|
using Avalonia.Markup.Xaml.Templates; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Styling; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Markup.Xaml.UnitTests.Xaml |
||||
|
{ |
||||
|
public class XamlSourceInfoTests : XamlTestBase |
||||
|
{ |
||||
|
private static readonly RuntimeXamlLoaderConfiguration s_configuration = new RuntimeXamlLoaderConfiguration |
||||
|
{ |
||||
|
CreateSourceInfo = true |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData(@"C:\TestFolder\TestFile.xaml")] // Windows-style path
|
||||
|
[InlineData("/TestFolder/TestFile.xaml")] // Unix-style path
|
||||
|
public void Root_UserControl_With_BaseUri_Gets_XamlSourceInfo_SourceUri_Set(string document) |
||||
|
{ |
||||
|
var xamlDocument = new RuntimeXamlLoaderDocument( |
||||
|
"""
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
</UserControl> |
||||
|
""")
|
||||
|
{ |
||||
|
Document = document |
||||
|
}; |
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xamlDocument, s_configuration); |
||||
|
|
||||
|
var sourceInfo = XamlSourceInfo.GetXamlSourceInfo(userControl); |
||||
|
|
||||
|
Assert.NotNull(sourceInfo); |
||||
|
Assert.Equal("file", sourceInfo.SourceUri!.Scheme); |
||||
|
Assert.True(sourceInfo.SourceUri!.IsAbsoluteUri); |
||||
|
Assert.Equal(new UriBuilder("file", "") {Path = document}.Uri, sourceInfo.SourceUri); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Root_UserControl_Gets_XamlSourceInfo_Set() |
||||
|
{ |
||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
</UserControl>");
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration); |
||||
|
|
||||
|
var sourceInfo = XamlSourceInfo.GetXamlSourceInfo(userControl); |
||||
|
|
||||
|
Assert.NotNull(sourceInfo); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Nested_Controls_All_Get_XamlSourceInfo_Set() |
||||
|
{ |
||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<StackPanel> |
||||
|
<Button /> |
||||
|
<TextBlock /> |
||||
|
</StackPanel> |
||||
|
</UserControl>");
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration); |
||||
|
var stackPanel = (StackPanel)userControl.Content!; |
||||
|
var button = (Button)stackPanel.Children[0]; |
||||
|
var textblock = (TextBlock)stackPanel.Children[1]; |
||||
|
|
||||
|
var userControlSourceInfo = XamlSourceInfo.GetXamlSourceInfo(userControl); |
||||
|
Assert.NotNull(userControlSourceInfo); |
||||
|
|
||||
|
var stackPanelSourceInfo = XamlSourceInfo.GetXamlSourceInfo(stackPanel); |
||||
|
Assert.NotNull(stackPanelSourceInfo); |
||||
|
|
||||
|
var buttonSourceInfo = XamlSourceInfo.GetXamlSourceInfo(button); |
||||
|
Assert.NotNull(buttonSourceInfo); |
||||
|
|
||||
|
var textblockSourceInfo = XamlSourceInfo.GetXamlSourceInfo(textblock); |
||||
|
Assert.NotNull(textblockSourceInfo); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Property_Elements_Get_XamlSourceInfo_Set() |
||||
|
{ |
||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<Rectangle Fill=""Blue"" Width=""63"" Height=""41""> |
||||
|
<Rectangle.OpacityMask> |
||||
|
<LinearGradientBrush StartPoint=""0%,0%"" EndPoint=""100%,100%""> |
||||
|
<LinearGradientBrush.GradientStops> |
||||
|
<GradientStop Offset=""0"" Color=""Black""/> |
||||
|
<GradientStop Offset=""1"" Color=""Transparent""/> |
||||
|
</LinearGradientBrush.GradientStops> |
||||
|
</LinearGradientBrush> |
||||
|
</Rectangle.OpacityMask> |
||||
|
</Rectangle> |
||||
|
</UserControl>");
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration); |
||||
|
var rect = (Rectangle)userControl.Content!; |
||||
|
var gradient = (LinearGradientBrush)rect.OpacityMask!; |
||||
|
var stopOne = (GradientStop)gradient.GradientStops.First(); |
||||
|
var stopTwo = (GradientStop)gradient.GradientStops.Last(); |
||||
|
|
||||
|
var rectSourceInfo = XamlSourceInfo.GetXamlSourceInfo(rect); |
||||
|
Assert.NotNull(rectSourceInfo); |
||||
|
|
||||
|
var gradientSourceInfo = XamlSourceInfo.GetXamlSourceInfo(gradient); |
||||
|
Assert.NotNull(gradientSourceInfo); |
||||
|
|
||||
|
var stopOneSourceInfo = XamlSourceInfo.GetXamlSourceInfo(stopOne); |
||||
|
Assert.NotNull(stopOneSourceInfo); |
||||
|
|
||||
|
var stopTwoSourceInfo = XamlSourceInfo.GetXamlSourceInfo(stopTwo); |
||||
|
Assert.NotNull(stopTwoSourceInfo); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Shapes_Get_XamlSourceInfo_Set() |
||||
|
{ |
||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<Canvas Name=""TheCanvas"" Background=""Yellow"" Width=""300"" Height=""400""> |
||||
|
<Ellipse Fill=""Green"" Width=""58"" Height=""58"" Canvas.Left=""88"" Canvas.Top=""100""/> |
||||
|
<Path Fill=""Orange"" Canvas.Left=""30"" Canvas.Top=""250""/> |
||||
|
<Path Fill=""OrangeRed"" Canvas.Left=""180"" Canvas.Top=""250""> |
||||
|
<Path.Data> |
||||
|
<PathGeometry> |
||||
|
<PathFigure StartPoint=""0,0"" IsClosed=""True""> |
||||
|
<QuadraticBezierSegment Point1=""50,0"" Point2=""50,-50"" /> |
||||
|
<QuadraticBezierSegment Point1=""100,-50"" Point2=""100,0"" /> |
||||
|
<LineSegment Point=""50,0"" /> |
||||
|
<LineSegment Point=""50,50"" /> |
||||
|
</PathFigure> |
||||
|
</PathGeometry> |
||||
|
</Path.Data> |
||||
|
</Path> |
||||
|
<Line StartPoint=""120,185"" EndPoint=""30,115"" Stroke=""Red"" StrokeThickness=""2""/> |
||||
|
<Polygon Points=""75,0 120,120 0,45 150,45 30,120"" Stroke=""DarkBlue"" StrokeThickness=""1"" Fill=""Violet"" Canvas.Left=""150"" Canvas.Top=""31""/> |
||||
|
</Canvas> |
||||
|
</UserControl>");
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration); |
||||
|
var canvas = (Canvas)userControl.Content!; |
||||
|
var ellipse = (Ellipse)canvas.Children[0]; |
||||
|
var path1 = (Path)canvas.Children[1]; |
||||
|
var path2 = (Path)canvas.Children[2]; |
||||
|
var geometry = (PathGeometry)path2.Data!; |
||||
|
var figure = (PathFigure)geometry.Figures![0]; |
||||
|
var segment1 = figure.Segments![0]; |
||||
|
var segment2 = figure.Segments![1]; |
||||
|
var segment3 = figure.Segments![2]; |
||||
|
var segment4 = figure.Segments![3]; |
||||
|
var line = (Line)canvas.Children[3]; |
||||
|
var polygon = (Polygon)canvas.Children[4]; |
||||
|
|
||||
|
var canvasSourceInfo = XamlSourceInfo.GetXamlSourceInfo(canvas); |
||||
|
Assert.NotNull(canvasSourceInfo); |
||||
|
|
||||
|
var ellipseSourceInfo = XamlSourceInfo.GetXamlSourceInfo(ellipse); |
||||
|
Assert.NotNull(ellipseSourceInfo); |
||||
|
|
||||
|
var path1SourceInfo = XamlSourceInfo.GetXamlSourceInfo(path1); |
||||
|
Assert.NotNull(path1SourceInfo); |
||||
|
|
||||
|
var path2SourceInfo = XamlSourceInfo.GetXamlSourceInfo(path2); |
||||
|
Assert.NotNull(path2SourceInfo); |
||||
|
|
||||
|
var geometrySourceInfo = XamlSourceInfo.GetXamlSourceInfo(geometry); |
||||
|
Assert.NotNull(geometrySourceInfo); |
||||
|
|
||||
|
var figureSourceInfo = XamlSourceInfo.GetXamlSourceInfo(figure); |
||||
|
Assert.NotNull(figureSourceInfo); |
||||
|
|
||||
|
var segment1SourceInfo = XamlSourceInfo.GetXamlSourceInfo(segment1); |
||||
|
Assert.NotNull(segment1SourceInfo); |
||||
|
|
||||
|
var segment2SourceInfo = XamlSourceInfo.GetXamlSourceInfo(segment2); |
||||
|
Assert.NotNull(segment2SourceInfo); |
||||
|
|
||||
|
var segment3SourceInfo = XamlSourceInfo.GetXamlSourceInfo(segment3); |
||||
|
Assert.NotNull(segment3SourceInfo); |
||||
|
|
||||
|
var segment4SourceInfo = XamlSourceInfo.GetXamlSourceInfo(segment4); |
||||
|
Assert.NotNull(segment4SourceInfo); |
||||
|
|
||||
|
var lineSourceInfo = XamlSourceInfo.GetXamlSourceInfo(line); |
||||
|
Assert.NotNull(lineSourceInfo); |
||||
|
|
||||
|
var polygonSourceInfo = XamlSourceInfo.GetXamlSourceInfo(polygon); |
||||
|
Assert.NotNull(polygonSourceInfo); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Styles_Get_XamlSourceInfo_Set() |
||||
|
{ |
||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<UserControl.Styles> |
||||
|
<Style Selector=""Button""> |
||||
|
<Setter Property=""Margin"" Value=""5"" /> |
||||
|
</Style> |
||||
|
<ContainerQuery Name=""container"" |
||||
|
Query=""max-width:400""> |
||||
|
<Style Selector=""Button""> |
||||
|
<Setter Property=""Background"" |
||||
|
Value=""Red""/> |
||||
|
</Style> |
||||
|
</ContainerQuery> |
||||
|
</UserControl.Styles> |
||||
|
</UserControl>");
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration); |
||||
|
var style = (Style)userControl.Styles[0]; |
||||
|
var query = (ContainerQuery)userControl.Styles[1]; |
||||
|
|
||||
|
var styleSourceInfo = XamlSourceInfo.GetXamlSourceInfo(style); |
||||
|
Assert.NotNull(styleSourceInfo); |
||||
|
|
||||
|
var querySourceInfo = XamlSourceInfo.GetXamlSourceInfo(query); |
||||
|
Assert.NotNull(querySourceInfo); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Animations_Get_XamlSourceInfo_Set() |
||||
|
{ |
||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<UserControl.Styles> |
||||
|
<Style Selector=""Rectangle.red""> |
||||
|
<Setter Property=""Fill"" Value=""Red""/> |
||||
|
<Style.Animations> |
||||
|
<Animation Duration=""0:0:3""> |
||||
|
<KeyFrame Cue=""0%""> |
||||
|
<Setter Property=""Opacity"" Value=""0.0""/> |
||||
|
</KeyFrame> |
||||
|
<KeyFrame Cue=""100%""> |
||||
|
<Setter Property=""Opacity"" Value=""1.0""/> |
||||
|
</KeyFrame> |
||||
|
</Animation> |
||||
|
</Style.Animations> |
||||
|
</Style> |
||||
|
</UserControl.Styles> |
||||
|
</UserControl>");
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration); |
||||
|
var style = (Style)userControl.Styles[0]; |
||||
|
var animation = (Animation.Animation)style.Animations[0]; |
||||
|
var frame1 = animation.Children[0]; |
||||
|
var frame2 = animation.Children[1]; |
||||
|
|
||||
|
var styleSourceInfo = XamlSourceInfo.GetXamlSourceInfo(style); |
||||
|
Assert.NotNull(styleSourceInfo); |
||||
|
|
||||
|
var animationSourceInfo = XamlSourceInfo.GetXamlSourceInfo(animation); |
||||
|
Assert.NotNull(animationSourceInfo); |
||||
|
|
||||
|
var frameOneSourceInfo = XamlSourceInfo.GetXamlSourceInfo(frame1); |
||||
|
Assert.NotNull(frameOneSourceInfo); |
||||
|
|
||||
|
var frameTwoSourceInfo = XamlSourceInfo.GetXamlSourceInfo(frame2); |
||||
|
Assert.NotNull(frameTwoSourceInfo); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void DataTemplates_And_Deferred_Contents_Get_XamlSourceInfo_Set() |
||||
|
{ |
||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
||||
|
xmlns:local='using:Avalonia.Markup.Xaml.UnitTests.Xaml'> |
||||
|
<UserControl.DataTemplates> |
||||
|
<DataTemplate DataType=""local:SourceInfoTestViewModel""> |
||||
|
<Border Background=""Red"" CornerRadius=""8""> |
||||
|
<TextBox Text=""{Binding Name}""/> |
||||
|
</Border> |
||||
|
</DataTemplate> |
||||
|
</UserControl.DataTemplates> |
||||
|
</UserControl>");
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration); |
||||
|
var datatemplate = (DataTemplate)userControl.DataTemplates[0]; |
||||
|
|
||||
|
Border border; |
||||
|
|
||||
|
// The template and it's content is deferred as not used (yet)
|
||||
|
if (datatemplate.Content is IDeferredContent deferredContent) |
||||
|
{ |
||||
|
var templateResult = (ITemplateResult)deferredContent.Build(null)!; |
||||
|
border = (Border)templateResult.Result!; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
border = (Border)datatemplate.Content!; |
||||
|
} |
||||
|
|
||||
|
var textBox = (TextBox)border!.Child!; |
||||
|
|
||||
|
var datatemplateSourceInfo = XamlSourceInfo.GetXamlSourceInfo(datatemplate); |
||||
|
Assert.NotNull(datatemplateSourceInfo); |
||||
|
|
||||
|
var borderSourceInfo = XamlSourceInfo.GetXamlSourceInfo(border); |
||||
|
Assert.NotNull(borderSourceInfo); |
||||
|
|
||||
|
var textBoxSourceInfo = XamlSourceInfo.GetXamlSourceInfo(textBox); |
||||
|
Assert.NotNull(textBoxSourceInfo); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Resources_Get_XamlSourceInfo_Set() |
||||
|
{ |
||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<UserControl.Resources> |
||||
|
<ResourceDictionary> |
||||
|
<ResourceDictionary.ThemeDictionaries> |
||||
|
<ResourceDictionary x:Key='Light'> |
||||
|
<SolidColorBrush x:Key='BackgroundBrush' Color='White'/> |
||||
|
<SolidColorBrush x:Key='ForegroundBrush' Color='Black'/> |
||||
|
</ResourceDictionary> |
||||
|
<ResourceDictionary x:Key='Dark'> |
||||
|
<SolidColorBrush x:Key='BackgroundBrush' Color='Black'/> |
||||
|
<SolidColorBrush x:Key='ForegroundBrush' Color='White'/> |
||||
|
</ResourceDictionary> |
||||
|
</ResourceDictionary.ThemeDictionaries> |
||||
|
|
||||
|
<SolidColorBrush x:Key=""Background"" Color=""Yellow"" /> |
||||
|
<SolidColorBrush x:Key='OtherBrush'>Black</SolidColorBrush> |
||||
|
|
||||
|
</ResourceDictionary> |
||||
|
</UserControl.Resources> |
||||
|
</UserControl>");
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration); |
||||
|
var backgroundBrush = userControl.Resources["Background"]; |
||||
|
var lightDictionary = (ResourceDictionary)userControl.Resources.ThemeDictionaries[ThemeVariant.Light]; |
||||
|
var darkDictionary = (ResourceDictionary)userControl.Resources.ThemeDictionaries[ThemeVariant.Dark]; |
||||
|
var lightForeground = lightDictionary["ForegroundBrush"]; |
||||
|
var darkBackground = lightDictionary["BackgroundBrush"]; |
||||
|
var otherBrush = userControl.Resources["OtherBrush"]; |
||||
|
|
||||
|
var backgroundBrushSourceInfo = XamlSourceInfo.GetXamlSourceInfo(backgroundBrush!); |
||||
|
Assert.NotNull(backgroundBrushSourceInfo); |
||||
|
|
||||
|
var lightDictionarySourceInfo = XamlSourceInfo.GetXamlSourceInfo(lightDictionary!); |
||||
|
Assert.NotNull(lightDictionarySourceInfo); |
||||
|
|
||||
|
var darkDictionarySourceInfo = XamlSourceInfo.GetXamlSourceInfo(darkDictionary!); |
||||
|
Assert.NotNull(darkDictionarySourceInfo); |
||||
|
|
||||
|
var lightForegroundSourceInfo = XamlSourceInfo.GetXamlSourceInfo(lightForeground!); |
||||
|
Assert.NotNull(lightForegroundSourceInfo); |
||||
|
|
||||
|
var darkBackgroundSourceInfo = XamlSourceInfo.GetXamlSourceInfo(darkBackground!); |
||||
|
Assert.NotNull(darkBackgroundSourceInfo); |
||||
|
|
||||
|
var otherBrushSourceInfo = XamlSourceInfo.GetXamlSourceInfo(otherBrush!); |
||||
|
Assert.NotNull(otherBrushSourceInfo); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void ResourceDictionary_Value_Types_Do_Not_Set_XamlSourceInfo() |
||||
|
{ |
||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<UserControl.Resources> |
||||
|
<x:String x:Key='text'>foobar</x:String> |
||||
|
<x:Double x:Key=""A_Double"">123.3</x:Double> |
||||
|
<x:Int16 x:Key=""An_Int16"">123</x:Int16> |
||||
|
<x:Int32 x:Key=""An_Int32"">37434323</x:Int32> |
||||
|
<Thickness x:Key=""PreferredPadding"">10,20,10,0</Thickness> |
||||
|
</UserControl.Resources> |
||||
|
</UserControl>");
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration); |
||||
|
var foobarString = userControl.Resources["text"]; |
||||
|
var aDouble = userControl.Resources["A_Double"]; |
||||
|
var anInt16 = userControl.Resources["An_Int16"]; |
||||
|
var anInt32 = userControl.Resources["An_Int32"]; |
||||
|
var padding = userControl.Resources["PreferredPadding"]; |
||||
|
|
||||
|
// Value types shouldn't get source info
|
||||
|
var foobarStringSourceInfo = XamlSourceInfo.GetXamlSourceInfo(foobarString!); |
||||
|
Assert.Null(foobarStringSourceInfo); |
||||
|
|
||||
|
var aDoubleSourceInfo = XamlSourceInfo.GetXamlSourceInfo(aDouble!); |
||||
|
Assert.Null(aDoubleSourceInfo); |
||||
|
|
||||
|
var anInt16SourceInfo = XamlSourceInfo.GetXamlSourceInfo(anInt16!); |
||||
|
Assert.Null(anInt16SourceInfo); |
||||
|
|
||||
|
var anInt32SourceInfo = XamlSourceInfo.GetXamlSourceInfo(anInt32!); |
||||
|
Assert.Null(anInt32SourceInfo); |
||||
|
|
||||
|
var paddingSourceInfo = XamlSourceInfo.GetXamlSourceInfo(padding!); |
||||
|
Assert.Null(paddingSourceInfo); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void ResourceDictionary_Set_Resource_Source_Info() |
||||
|
{ |
||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<UserControl.Resources> |
||||
|
<x:String x:Key='text'>foobar</x:String> |
||||
|
<x:Double x:Key=""A_Double"">123.3</x:Double> |
||||
|
<x:Int16 x:Key=""An_Int16"">123</x:Int16> |
||||
|
<x:Int32 x:Key=""An_Int32"">37434323</x:Int32> |
||||
|
<Thickness x:Key=""PreferredPadding"">10,20,10,0</Thickness> |
||||
|
<x:Uri x:Key='homepage'>http://avaloniaui.net</x:Uri>
|
||||
|
<SolidColorBrush x:Key='MyBrush' Color='Red'/> |
||||
|
</UserControl.Resources> |
||||
|
</UserControl>");
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration); |
||||
|
var resources = userControl.Resources; |
||||
|
|
||||
|
var foobarStringSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "text"); |
||||
|
Assert.NotNull(foobarStringSourceInfo); |
||||
|
|
||||
|
var aDoubleSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "A_Double"); |
||||
|
Assert.NotNull(aDoubleSourceInfo); |
||||
|
|
||||
|
var anInt16SourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "An_Int16"); |
||||
|
Assert.NotNull(anInt16SourceInfo); |
||||
|
|
||||
|
var anInt32SourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "An_Int32"); |
||||
|
Assert.NotNull(anInt32SourceInfo); |
||||
|
|
||||
|
var paddingSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "PreferredPadding"); |
||||
|
Assert.NotNull(paddingSourceInfo); |
||||
|
|
||||
|
var homepageSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "homepage"); |
||||
|
Assert.NotNull(homepageSourceInfo); |
||||
|
|
||||
|
var myBrushSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "MyBrush"); |
||||
|
Assert.NotNull(myBrushSourceInfo); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void ResourceDictionary_Set_Resource_Source_Info_With_Nested_Dictionaries() |
||||
|
{ |
||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<UserControl.Resources> |
||||
|
<ResourceDictionary> |
||||
|
<x:String x:Key='text'>foobar</x:String> |
||||
|
<x:Double x:Key=""A_Double"">123.3</x:Double> |
||||
|
<x:Int16 x:Key=""An_Int16"">123</x:Int16> |
||||
|
<x:Int32 x:Key=""An_Int32"">37434323</x:Int32> |
||||
|
<ResourceDictionary.MergedDictionaries> |
||||
|
<ResourceDictionary> |
||||
|
<Thickness x:Key=""PreferredPadding"">10,20,10,0</Thickness> |
||||
|
<x:Uri x:Key='homepage'>http://avaloniaui.net</x:Uri>
|
||||
|
</ResourceDictionary> |
||||
|
</ResourceDictionary.MergedDictionaries> |
||||
|
<ResourceDictionary.ThemeDictionaries> |
||||
|
<ResourceDictionary x:Key='Light'> |
||||
|
<SolidColorBrush x:Key='MyBrush' Color='Red'/> |
||||
|
</ResourceDictionary> |
||||
|
</ResourceDictionary.ThemeDictionaries> |
||||
|
</ResourceDictionary> |
||||
|
</UserControl.Resources> |
||||
|
</UserControl>");
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration); |
||||
|
var resources = userControl.Resources; |
||||
|
var innerResources = (IResourceDictionary)resources.MergedDictionaries[0]; |
||||
|
var themeResources = (IResourceDictionary)resources.ThemeDictionaries[ThemeVariant.Light]; |
||||
|
|
||||
|
// Outer define source info
|
||||
|
var foobarStringSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "text"); |
||||
|
Assert.NotNull(foobarStringSourceInfo); |
||||
|
|
||||
|
var aDoubleSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "A_Double"); |
||||
|
Assert.NotNull(aDoubleSourceInfo); |
||||
|
|
||||
|
var anInt16SourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "An_Int16"); |
||||
|
Assert.NotNull(anInt16SourceInfo); |
||||
|
|
||||
|
var anInt32SourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "An_Int32"); |
||||
|
Assert.NotNull(anInt32SourceInfo); |
||||
|
|
||||
|
// Outer one should not have source info for inner resources
|
||||
|
var paddingSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "PreferredPadding"); |
||||
|
Assert.Null(paddingSourceInfo); |
||||
|
|
||||
|
var homepageSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "homepage"); |
||||
|
Assert.Null(homepageSourceInfo); |
||||
|
|
||||
|
var myBrushSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "MyBrush"); |
||||
|
Assert.Null(myBrushSourceInfo); |
||||
|
|
||||
|
// Inner defined source info
|
||||
|
homepageSourceInfo = XamlSourceInfo.GetXamlSourceInfo(innerResources, "homepage"); |
||||
|
Assert.NotNull(homepageSourceInfo); |
||||
|
|
||||
|
myBrushSourceInfo = XamlSourceInfo.GetXamlSourceInfo(themeResources, "MyBrush"); |
||||
|
Assert.NotNull(myBrushSourceInfo); |
||||
|
|
||||
|
// Non-value types should have source info themselves
|
||||
|
var homepage = XamlSourceInfo.GetXamlSourceInfo(innerResources["homepage"]!); |
||||
|
Assert.NotNull(homepage); |
||||
|
|
||||
|
var myBrush = XamlSourceInfo.GetXamlSourceInfo(themeResources["MyBrush"]!); |
||||
|
Assert.NotNull(myBrush); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Gestures_Get_XamlSourceInfo_Set() |
||||
|
{ |
||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<UserControl.GestureRecognizers> |
||||
|
<ScrollGestureRecognizer CanHorizontallyScroll=""True"" |
||||
|
CanVerticallyScroll=""True""/> |
||||
|
<PullGestureRecognizer PullDirection=""TopToBottom""/> |
||||
|
</UserControl.GestureRecognizers> |
||||
|
</UserControl>");
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration); |
||||
|
var scroll = (ScrollGestureRecognizer)userControl.GestureRecognizers.First(); |
||||
|
var pull = (PullGestureRecognizer)userControl.GestureRecognizers.Last(); |
||||
|
|
||||
|
var scrollSourceInfo = XamlSourceInfo.GetXamlSourceInfo(scroll); |
||||
|
Assert.NotNull(scrollSourceInfo); |
||||
|
|
||||
|
var pullSourceInfo = XamlSourceInfo.GetXamlSourceInfo(pull); |
||||
|
Assert.NotNull(pullSourceInfo); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Transitions_Get_XamlSourceInfo_Set() |
||||
|
{ |
||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<UserControl.Transitions> |
||||
|
<Transitions> |
||||
|
<DoubleTransition Property=""Width"" Duration=""0:0:1.5""/> |
||||
|
<DoubleTransition Property=""Height"" Duration=""0:0:1.5""/> |
||||
|
</Transitions> |
||||
|
</UserControl.Transitions> |
||||
|
</UserControl>");
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration); |
||||
|
var width = (DoubleTransition)userControl.Transitions!.First(); |
||||
|
var height = (DoubleTransition)userControl.Transitions!.Last(); |
||||
|
|
||||
|
var widthSourceInfo = XamlSourceInfo.GetXamlSourceInfo(width); |
||||
|
Assert.NotNull(widthSourceInfo); |
||||
|
|
||||
|
var heightSourceInfo = XamlSourceInfo.GetXamlSourceInfo(height); |
||||
|
Assert.NotNull(heightSourceInfo); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class SourceInfoTestViewModel |
||||
|
{ |
||||
|
public string? Name { get; set; } |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue