30 changed files with 1614 additions and 72 deletions
@ -0,0 +1,51 @@ |
|||||
|
<UserControl x:Class="ControlCatalog.Pages.PlatformInfoPage" |
||||
|
xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
|
d:DesignHeight="800" |
||||
|
d:DesignWidth="400" |
||||
|
mc:Ignorable="d"> |
||||
|
<StackPanel Spacing="20"> |
||||
|
<TextBlock Text="{Binding PlatformInfo}" /> |
||||
|
|
||||
|
<StackPanel TextElement.Foreground="White"> |
||||
|
<StackPanel Orientation="Horizontal"> |
||||
|
<Border Height="100" Width="100" Background="{OnFormFactor Gray, Desktop=Green}"> |
||||
|
<TextBlock Text="Desktop" /> |
||||
|
</Border> |
||||
|
<Border Height="100" Width="100" Background="{OnFormFactor Gray, Mobile=Green}"> |
||||
|
<TextBlock Text="Mobile" /> |
||||
|
</Border> |
||||
|
</StackPanel> |
||||
|
<WrapPanel> |
||||
|
<Border Height="100" Width="100" Background="{OnPlatform Gray, Windows=Green}"> |
||||
|
<TextBlock Text="Windows" /> |
||||
|
</Border> |
||||
|
<Border Height="100" Width="100" Background="{OnPlatform Gray, macOS=Green}"> |
||||
|
<TextBlock Text="macOS" /> |
||||
|
</Border> |
||||
|
<Border Height="100" Width="100" Background="{OnPlatform Gray, Linux=Green}"> |
||||
|
<TextBlock Text="Linux" /> |
||||
|
</Border> |
||||
|
<Border Height="100" Width="100" Background="{OnPlatform Gray, Browser=Green}"> |
||||
|
<TextBlock Text="Browser" /> |
||||
|
</Border> |
||||
|
<Border Height="100" Width="100" Background="{OnPlatform Gray, iOS=Green}"> |
||||
|
<TextBlock Text="iOS" /> |
||||
|
</Border> |
||||
|
<Border Height="100" Width="100" Background="{OnPlatform Gray, Android=Green}"> |
||||
|
<TextBlock Text="Android" /> |
||||
|
</Border> |
||||
|
<Border Height="100" Width="100"> |
||||
|
<Border.Background> |
||||
|
<OnPlatform Default="Gray" > |
||||
|
<On Options="macOS, Linux, Windows" Content="Green" /> |
||||
|
</OnPlatform> |
||||
|
</Border.Background> |
||||
|
<TextBlock Text="Win, Lin or Mac" /> |
||||
|
</Border> |
||||
|
</WrapPanel> |
||||
|
</StackPanel> |
||||
|
</StackPanel> |
||||
|
</UserControl> |
||||
@ -0,0 +1,20 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
using ControlCatalog.ViewModels; |
||||
|
|
||||
|
namespace ControlCatalog.Pages |
||||
|
{ |
||||
|
public class PlatformInfoPage : UserControl |
||||
|
{ |
||||
|
public PlatformInfoPage() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
DataContext = new PlatformInformationViewModel(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
using Avalonia; |
||||
|
using Avalonia.Platform; |
||||
|
using MiniMvvm; |
||||
|
|
||||
|
namespace ControlCatalog.ViewModels; |
||||
|
#nullable enable |
||||
|
|
||||
|
public class PlatformInformationViewModel : ViewModelBase |
||||
|
{ |
||||
|
public PlatformInformationViewModel() |
||||
|
{ |
||||
|
var runtimeInfo = AvaloniaLocator.Current.GetService<IRuntimePlatform>()?.GetRuntimeInfo(); |
||||
|
|
||||
|
if (runtimeInfo is { } info) |
||||
|
{ |
||||
|
if (info.IsBrowser) |
||||
|
{ |
||||
|
if (info.IsDesktop) |
||||
|
{ |
||||
|
PlatformInfo = "Platform: Desktop (browser)"; |
||||
|
} |
||||
|
else if (info.IsMobile) |
||||
|
{ |
||||
|
PlatformInfo = "Platform: Mobile (browser)"; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
PlatformInfo = "Platform: Unknown (browser) - please report"; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (info.IsDesktop) |
||||
|
{ |
||||
|
PlatformInfo = "Platform: Desktop (native)"; |
||||
|
} |
||||
|
else if (info.IsMobile) |
||||
|
{ |
||||
|
PlatformInfo = "Platform: Mobile (native)"; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
PlatformInfo = "Platform: Unknown (native) - please report"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public string PlatformInfo { get; } |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Metadata; |
||||
|
|
||||
|
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] |
||||
|
public sealed class MarkupExtensionOptionAttribute : Attribute |
||||
|
{ |
||||
|
public MarkupExtensionOptionAttribute(object value) |
||||
|
{ |
||||
|
Value = value; |
||||
|
} |
||||
|
|
||||
|
public object Value { get; } |
||||
|
|
||||
|
public int Priority { get; set; } = 0; |
||||
|
} |
||||
|
|
||||
|
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] |
||||
|
public sealed class MarkupExtensionDefaultOptionAttribute : Attribute |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,403 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Reflection.Emit; |
||||
|
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 AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransformer |
||||
|
{ |
||||
|
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) |
||||
|
{ |
||||
|
if (node is XamlMarkupExtensionNode |
||||
|
{ |
||||
|
Value: XamlAstObjectNode { Type: XamlAstClrTypeReference { Type: { } type } } objectNode |
||||
|
} markupExtensionNode |
||||
|
&& type.FindMethods(m => m.IsPublic && m.Parameters.Count is 1 or 2 && m.ReturnType == context.Configuration.WellKnownTypes.Boolean && m.Name == "ShouldProvideOption").ToArray() is { } methods |
||||
|
&& methods.Any()) |
||||
|
{ |
||||
|
var optionAttribute = context.GetAvaloniaTypes().MarkupExtensionOptionAttribute; |
||||
|
var defaultOptionAttribute = context.GetAvaloniaTypes().MarkupExtensionDefaultOptionAttribute; |
||||
|
|
||||
|
var typeArgument = type.GenericArguments?.FirstOrDefault(); |
||||
|
|
||||
|
IXamlAstValueNode defaultValue = null; |
||||
|
var values = new List<OptionsMarkupExtensionBranch>(); |
||||
|
|
||||
|
if (objectNode.Arguments.FirstOrDefault() is { } argument) |
||||
|
{ |
||||
|
var hasDefaultProp = objectNode.Type.GetClrType().GetAllProperties().Any(p => |
||||
|
p.CustomAttributes.Any(a => a.Type == defaultOptionAttribute)); |
||||
|
if (hasDefaultProp) |
||||
|
{ |
||||
|
if (objectNode.Arguments.Count > 1) |
||||
|
{ |
||||
|
throw new XamlParseException("Options MarkupExtensions allow only single argument", objectNode); |
||||
|
} |
||||
|
|
||||
|
defaultValue = TransformNode(new[] { argument }, typeArgument, objectNode); |
||||
|
objectNode.Arguments.Remove(argument); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
foreach (var extProp in objectNode.Children.OfType<XamlAstXamlPropertyValueNode>().ToArray()) |
||||
|
{ |
||||
|
if (!extProp.Values.Any()) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
var shouldRemoveProp = false; |
||||
|
var onObjs = extProp.Values.OfType<XamlAstObjectNode>() |
||||
|
.Where(o => o.Type.GetClrType() == context.GetAvaloniaTypes().OnExtensionType).ToArray(); |
||||
|
if (onObjs.Any()) |
||||
|
{ |
||||
|
shouldRemoveProp = true; |
||||
|
foreach (var onObj in onObjs) |
||||
|
{ |
||||
|
var optionsPropNode = onObj.Children.OfType<XamlAstXamlPropertyValueNode>() |
||||
|
.SingleOrDefault(v => v.Property.GetClrProperty().Name == "Options") |
||||
|
?.Values.Single(); |
||||
|
var options = (optionsPropNode as XamlAstTextNode)?.Text?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) |
||||
|
?? Array.Empty<string>(); |
||||
|
if (options.Length == 0) |
||||
|
{ |
||||
|
throw new XamlParseException("On.Options string must be set", onObj); |
||||
|
} |
||||
|
|
||||
|
var content = onObj.Children.OfType<XamlAstXamlPropertyValueNode>() |
||||
|
.SingleOrDefault(v => v.Property.GetClrProperty().Name == "Content"); |
||||
|
if (content is null) |
||||
|
{ |
||||
|
throw new XamlParseException("On content object must be set", onObj); |
||||
|
} |
||||
|
|
||||
|
var propertiesSet = options |
||||
|
.Select(o => type.GetAllProperties() |
||||
|
.FirstOrDefault(p => o.Equals(p.Name, StringComparison.Ordinal)) |
||||
|
?? throw new XamlParseException($"Property \"{o}\" wasn't found on the \"{type.Name}\" type", onObj)) |
||||
|
.ToArray(); |
||||
|
foreach (var propertySet in propertiesSet) |
||||
|
{ |
||||
|
AddBranchNode(content.Values, propertySet.CustomAttributes, content); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
shouldRemoveProp = AddBranchNode(extProp.Values, extProp.Property.GetClrProperty().CustomAttributes, extProp); |
||||
|
} |
||||
|
|
||||
|
if (shouldRemoveProp) |
||||
|
{ |
||||
|
objectNode.Children.Remove(extProp); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (defaultValue is null && !values.Any()) |
||||
|
{ |
||||
|
throw new XamlParseException("Options markup extension requires at least one option to be set", objectNode); |
||||
|
} |
||||
|
|
||||
|
return new OptionsMarkupExtensionNode( |
||||
|
markupExtensionNode, values.ToArray(), defaultValue, |
||||
|
context.Configuration.TypeMappings.ServiceProvider); |
||||
|
|
||||
|
bool AddBranchNode( |
||||
|
IReadOnlyCollection<IXamlAstValueNode> valueNodes, |
||||
|
IReadOnlyCollection<IXamlCustomAttribute> propAttributes, |
||||
|
IXamlLineInfo li) |
||||
|
{ |
||||
|
var transformed = TransformNode(valueNodes, typeArgument, li); |
||||
|
if (propAttributes.FirstOrDefault(a => a.Type == defaultOptionAttribute) is { } defAttr) |
||||
|
{ |
||||
|
defaultValue = transformed; |
||||
|
return true; |
||||
|
} |
||||
|
else if (propAttributes.FirstOrDefault(a => a.Type == optionAttribute) is { } optAttr) |
||||
|
{ |
||||
|
var option = optAttr.Parameters.Single(); |
||||
|
if (option is null) |
||||
|
{ |
||||
|
throw new XamlParseException("MarkupExtension option must not be null", li); |
||||
|
} |
||||
|
|
||||
|
var optionAsString = option.ToString(); |
||||
|
IXamlAstValueNode optionNode = null; |
||||
|
foreach (var method in methods) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var targetType = method.Parameters.Last(); |
||||
|
if (targetType.FullName == "System.Type") |
||||
|
{ |
||||
|
if (option is IXamlType typeOption) |
||||
|
{ |
||||
|
optionNode = new XamlTypeExtensionNode(li, |
||||
|
new XamlAstClrTypeReference(li, typeOption, false), targetType); |
||||
|
} |
||||
|
} |
||||
|
else if (targetType == context.Configuration.WellKnownTypes.String) |
||||
|
{ |
||||
|
optionNode = new XamlConstantNode(li, targetType, optionAsString); |
||||
|
} |
||||
|
else if (targetType.IsEnum) |
||||
|
{ |
||||
|
if (TypeSystemHelpers.TryGetEnumValueNode(targetType, optionAsString, li, false, |
||||
|
out var enumConstantNode)) |
||||
|
{ |
||||
|
optionNode = enumConstantNode; |
||||
|
} |
||||
|
} |
||||
|
else if (TypeSystemHelpers.ParseConstantIfTypeAllows(optionAsString, targetType, li, |
||||
|
out var constantNode)) |
||||
|
{ |
||||
|
optionNode = constantNode; |
||||
|
} |
||||
|
} |
||||
|
catch (FormatException) |
||||
|
{ |
||||
|
// try next method overload
|
||||
|
} |
||||
|
|
||||
|
if (optionNode is not null) |
||||
|
{ |
||||
|
values.Add(new OptionsMarkupExtensionBranch(optionNode, transformed, method)); |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
throw new XamlParseException($"Option value \"{optionAsString}\" is not assignable to any of existing ShouldProvideOption methods", li); |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return node; |
||||
|
|
||||
|
IXamlAstValueNode TransformNode( |
||||
|
IReadOnlyCollection<IXamlAstValueNode> values, |
||||
|
IXamlType suggestedType, |
||||
|
IXamlLineInfo line) |
||||
|
{ |
||||
|
if (suggestedType is not null) |
||||
|
{ |
||||
|
values = values |
||||
|
.Select(v => XamlTransformHelpers |
||||
|
.TryGetCorrectlyTypedValue(context, v, suggestedType, out var converted) |
||||
|
? converted : v) |
||||
|
.ToArray(); |
||||
|
} |
||||
|
|
||||
|
if (values.Count > 1) |
||||
|
{ |
||||
|
throw new XamlParseException("Options markup extension supports only a singular value", line); |
||||
|
} |
||||
|
|
||||
|
return values.Single(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal sealed class OptionsMarkupExtensionNode : XamlMarkupExtensionNode, IXamlAstValueNode |
||||
|
{ |
||||
|
private readonly IXamlType _contextParameter; |
||||
|
|
||||
|
public OptionsMarkupExtensionNode( |
||||
|
XamlMarkupExtensionNode original, |
||||
|
OptionsMarkupExtensionBranch[] branches, |
||||
|
IXamlAstValueNode defaultNode, |
||||
|
IXamlType contextParameter) |
||||
|
: base( |
||||
|
original.Value, |
||||
|
new OptionsMarkupExtensionMethod(new OptionsMarkupExtensionNodesContainer(branches, defaultNode), original.Value.Type.GetClrType(), contextParameter), |
||||
|
original.Value) |
||||
|
{ |
||||
|
_contextParameter = contextParameter; |
||||
|
} |
||||
|
|
||||
|
public new OptionsMarkupExtensionMethod ProvideValue => (OptionsMarkupExtensionMethod)base.ProvideValue; |
||||
|
|
||||
|
IXamlAstTypeReference IXamlAstValueNode.Type => new XamlAstClrTypeReference(this, ProvideValue.ReturnType, false); |
||||
|
|
||||
|
public override void VisitChildren(IXamlAstVisitor visitor) |
||||
|
{ |
||||
|
ProvideValue.ExtensionNodeContainer.Visit(visitor); |
||||
|
base.VisitChildren(visitor); |
||||
|
} |
||||
|
|
||||
|
public bool ConvertToReturnType(AstTransformationContext context, IXamlType type, out OptionsMarkupExtensionNode res) |
||||
|
{ |
||||
|
IXamlAstValueNode convertedDefaultNode = null; |
||||
|
|
||||
|
if (ProvideValue.ExtensionNodeContainer.DefaultNode is { } defaultNode) |
||||
|
{ |
||||
|
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, defaultNode, type, out convertedDefaultNode)) |
||||
|
{ |
||||
|
res = null; |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var convertedBranches = ProvideValue.ExtensionNodeContainer.Branches.Select(b => XamlTransformHelpers |
||||
|
.TryGetCorrectlyTypedValue(context, b.Value, type, out var convertedValue) ? |
||||
|
new OptionsMarkupExtensionBranch(b.Option, convertedValue, b.ConditionMethod) : |
||||
|
null).ToArray(); |
||||
|
if (convertedBranches.Any(b => b is null)) |
||||
|
{ |
||||
|
res = null; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
res = new OptionsMarkupExtensionNode(this, convertedBranches, convertedDefaultNode, _contextParameter); |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal sealed class OptionsMarkupExtensionNodesContainer : XamlAstNode |
||||
|
{ |
||||
|
public OptionsMarkupExtensionNodesContainer( |
||||
|
OptionsMarkupExtensionBranch[] branches, |
||||
|
IXamlAstValueNode defaultNode) : base(branches.FirstOrDefault()?.Value ?? defaultNode) |
||||
|
{ |
||||
|
Branches = branches; |
||||
|
DefaultNode = defaultNode; |
||||
|
} |
||||
|
|
||||
|
public OptionsMarkupExtensionBranch[] Branches { get; } |
||||
|
public IXamlAstValueNode DefaultNode { get; private set; } |
||||
|
|
||||
|
public override void VisitChildren(IXamlAstVisitor visitor) |
||||
|
{ |
||||
|
VisitList(Branches, visitor); |
||||
|
DefaultNode = (IXamlAstValueNode)DefaultNode?.Visit(visitor); |
||||
|
} |
||||
|
|
||||
|
public IXamlType GetReturnType() |
||||
|
{ |
||||
|
var types = Branches.Select(b => b.Value.Type); |
||||
|
if (DefaultNode?.Type is { } type) |
||||
|
{ |
||||
|
types = types.Concat(new [] { type }); |
||||
|
} |
||||
|
return types.Select(t => t.GetClrType()).ToArray().GetCommonBaseClass(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal sealed class OptionsMarkupExtensionBranch : XamlAstNode |
||||
|
{ |
||||
|
public OptionsMarkupExtensionBranch(IXamlAstValueNode option, IXamlAstValueNode value, IXamlMethod conditionMethod) : base(value) |
||||
|
{ |
||||
|
Option = option; |
||||
|
Value = value; |
||||
|
ConditionMethod = conditionMethod; |
||||
|
} |
||||
|
|
||||
|
public IXamlAstValueNode Option { get; set; } |
||||
|
public IXamlAstValueNode Value { get; set; } |
||||
|
public IXamlMethod ConditionMethod { get; } |
||||
|
|
||||
|
public bool HasContext => ConditionMethod.Parameters.Count > 1; |
||||
|
|
||||
|
public override void VisitChildren(IXamlAstVisitor visitor) |
||||
|
{ |
||||
|
Option = (IXamlAstValueNode)Option.Visit(visitor); |
||||
|
Value = (IXamlAstValueNode)Value.Visit(visitor); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal sealed class OptionsMarkupExtensionMethod : IXamlCustomEmitMethodWithContext<IXamlILEmitter, XamlILNodeEmitResult> |
||||
|
{ |
||||
|
public OptionsMarkupExtensionMethod( |
||||
|
OptionsMarkupExtensionNodesContainer extensionNodeContainer, |
||||
|
IXamlType declaringType, |
||||
|
IXamlType contextParameter) |
||||
|
{ |
||||
|
ExtensionNodeContainer = extensionNodeContainer; |
||||
|
DeclaringType = declaringType; |
||||
|
Parameters = extensionNodeContainer.Branches.Any(c => c.HasContext) ? |
||||
|
new[] { contextParameter } : |
||||
|
Array.Empty<IXamlType>(); |
||||
|
} |
||||
|
|
||||
|
public OptionsMarkupExtensionNodesContainer ExtensionNodeContainer { get; } |
||||
|
|
||||
|
public string Name => "ProvideValue"; |
||||
|
public bool IsPublic => true; |
||||
|
public bool IsStatic => false; |
||||
|
public IXamlType ReturnType => ExtensionNodeContainer.GetReturnType(); |
||||
|
public IReadOnlyList<IXamlType> Parameters { get; } |
||||
|
public IXamlType DeclaringType { get; } |
||||
|
public IXamlMethod MakeGenericMethod(IReadOnlyList<IXamlType> typeArguments) => throw new NotImplementedException(); |
||||
|
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes => Array.Empty<IXamlCustomAttribute>(); |
||||
|
|
||||
|
public void EmitCall(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen) |
||||
|
{ |
||||
|
// At this point this extension will be called from MarkupExtensionEmitter.
|
||||
|
// Since it's a "fake" method, we share stack and locals with parent method.
|
||||
|
// Real ProvideValue method would pop 2 parameters from the stack and return one. This method should do the same.
|
||||
|
// At this point we will have on stack:
|
||||
|
// - context (if parameters > 1)
|
||||
|
// - markup ext "@this" instance (always)
|
||||
|
// We always pop context from the stack, as this method decide by itself either context is needed.
|
||||
|
// We store "@this" as a local variable. But only if any conditional method is an instance method.
|
||||
|
IXamlLocal @this = null; |
||||
|
if (Parameters.Count > 0) |
||||
|
{ |
||||
|
codeGen.Pop(); |
||||
|
} |
||||
|
if (ExtensionNodeContainer.Branches.Any(b => !b.ConditionMethod.IsStatic)) |
||||
|
{ |
||||
|
codeGen.Stloc(@this = codeGen.DefineLocal(DeclaringType)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
codeGen.Pop(); |
||||
|
} |
||||
|
|
||||
|
// Iterate over all branches and push prepared locals into the stack if needed.
|
||||
|
var ret = codeGen.DefineLabel(); |
||||
|
foreach (var branch in ExtensionNodeContainer.Branches) |
||||
|
{ |
||||
|
var next = codeGen.DefineLabel(); |
||||
|
if (branch.HasContext) |
||||
|
{ |
||||
|
codeGen.Ldloc(context.ContextLocal); |
||||
|
} |
||||
|
if (!branch.ConditionMethod.IsStatic) |
||||
|
{ |
||||
|
codeGen.Ldloc(@this); |
||||
|
} |
||||
|
context.Emit(branch.Option, codeGen, branch.Option.Type.GetClrType()); |
||||
|
codeGen.EmitCall(branch.ConditionMethod); |
||||
|
codeGen.Brfalse(next); |
||||
|
|
||||
|
context.Emit(branch.Value, codeGen, branch.Value.Type.GetClrType()); |
||||
|
codeGen.Br(ret); |
||||
|
codeGen.MarkLabel(next); |
||||
|
} |
||||
|
|
||||
|
if (ExtensionNodeContainer.DefaultNode is {} defaultNode) |
||||
|
{ |
||||
|
// Nop is needed, otherwise Label wouldn't be set on nested CALL op (limitation of our IL validator).
|
||||
|
codeGen.Emit(OpCodes.Nop); |
||||
|
context.Emit(defaultNode, codeGen, defaultNode.Type.GetClrType()); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
codeGen.EmitDefault(ReturnType); |
||||
|
} |
||||
|
|
||||
|
codeGen.MarkLabel(ret); |
||||
|
} |
||||
|
|
||||
|
public bool Equals(IXamlMethod other) => ReferenceEquals(this, other); |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -1 +1 @@ |
|||||
Subproject commit c1c0594ec2c35b08988183b1a5b3e34dfa19179d |
Subproject commit 880ba5742e52b67afda048c4023cf7e3c3c16a46 |
||||
@ -0,0 +1,15 @@ |
|||||
|
#nullable enable |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Metadata; |
||||
|
|
||||
|
namespace Avalonia.Markup.Xaml.MarkupExtensions; |
||||
|
|
||||
|
public class On<TReturn> |
||||
|
{ |
||||
|
public IReadOnlyList<string> Options { get; } = new List<string>(); |
||||
|
|
||||
|
[Content] |
||||
|
public TReturn? Content { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class On : On<object> {} |
||||
@ -0,0 +1,60 @@ |
|||||
|
#nullable enable |
||||
|
using System; |
||||
|
|
||||
|
using Avalonia.Metadata; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Markup.Xaml.MarkupExtensions; |
||||
|
|
||||
|
public class OnFormFactorExtension : OnFormFactorExtensionBase<object, On> |
||||
|
{ |
||||
|
public OnFormFactorExtension() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public OnFormFactorExtension(object defaultValue) |
||||
|
{ |
||||
|
Default = defaultValue; |
||||
|
} |
||||
|
|
||||
|
public static bool ShouldProvideOption(IServiceProvider serviceProvider, FormFactorType option) |
||||
|
{ |
||||
|
return serviceProvider.GetService<IRuntimePlatform>().GetRuntimeInfo().FormFactor == option; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class OnFormFactorExtension<TReturn> : OnFormFactorExtensionBase<TReturn, On<TReturn>> |
||||
|
{ |
||||
|
public OnFormFactorExtension() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public OnFormFactorExtension(TReturn defaultValue) |
||||
|
{ |
||||
|
Default = defaultValue; |
||||
|
} |
||||
|
|
||||
|
public static bool ShouldProvideOption(IServiceProvider serviceProvider, FormFactorType option) |
||||
|
{ |
||||
|
return serviceProvider.GetService<IRuntimePlatform>().GetRuntimeInfo().FormFactor == option; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public abstract class OnFormFactorExtensionBase<TReturn, TOn> : IAddChild<TOn> |
||||
|
where TOn : On<TReturn> |
||||
|
{ |
||||
|
[MarkupExtensionDefaultOption] |
||||
|
public TReturn? Default { get; set; } |
||||
|
|
||||
|
[MarkupExtensionOption(FormFactorType.Desktop)] |
||||
|
public TReturn? Desktop { get; set; } |
||||
|
|
||||
|
[MarkupExtensionOption(FormFactorType.Mobile)] |
||||
|
public TReturn? Mobile { get; set; } |
||||
|
|
||||
|
// Required for the compiler, will be replaced with actual method compile time.
|
||||
|
public object ProvideValue() { return this; } |
||||
|
void IAddChild<TOn>.AddChild(TOn child) {} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
#nullable enable |
||||
|
using System; |
||||
|
using Avalonia.Metadata; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Markup.Xaml.MarkupExtensions; |
||||
|
|
||||
|
public class OnPlatformExtension : OnPlatformExtensionBase<object, On> |
||||
|
{ |
||||
|
public OnPlatformExtension() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public OnPlatformExtension(object defaultValue) |
||||
|
{ |
||||
|
Default = defaultValue; |
||||
|
} |
||||
|
|
||||
|
public static bool ShouldProvideOption(IServiceProvider serviceProvider, OperatingSystemType option) |
||||
|
{ |
||||
|
return serviceProvider.GetService<IRuntimePlatform>().GetRuntimeInfo().OperatingSystem == option; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class OnPlatformExtension<TReturn> : OnPlatformExtensionBase<TReturn, On<TReturn>> |
||||
|
{ |
||||
|
public OnPlatformExtension() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public OnPlatformExtension(TReturn defaultValue) |
||||
|
{ |
||||
|
Default = defaultValue; |
||||
|
} |
||||
|
|
||||
|
public static bool ShouldProvideOption(IServiceProvider serviceProvider, OperatingSystemType option) |
||||
|
{ |
||||
|
return serviceProvider.GetService<IRuntimePlatform>().GetRuntimeInfo().OperatingSystem == option; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public abstract class OnPlatformExtensionBase<TReturn, TOn> : IAddChild<TOn> |
||||
|
where TOn : On<TReturn> |
||||
|
{ |
||||
|
[MarkupExtensionDefaultOption] |
||||
|
public TReturn? Default { get; set; } |
||||
|
|
||||
|
[MarkupExtensionOption(OperatingSystemType.WinNT)] |
||||
|
public TReturn? Windows { get; set; } |
||||
|
|
||||
|
[MarkupExtensionOption(OperatingSystemType.OSX)] |
||||
|
// ReSharper disable once InconsistentNaming
|
||||
|
public TReturn? macOS { get; set; } |
||||
|
|
||||
|
[MarkupExtensionOption(OperatingSystemType.Linux)] |
||||
|
public TReturn? Linux { get; set; } |
||||
|
|
||||
|
[MarkupExtensionOption(OperatingSystemType.Android)] |
||||
|
public TReturn? Android { get; set; } |
||||
|
|
||||
|
[MarkupExtensionOption(OperatingSystemType.iOS)] |
||||
|
// ReSharper disable once InconsistentNaming
|
||||
|
public TReturn? iOS { get; set; } |
||||
|
|
||||
|
[MarkupExtensionOption(OperatingSystemType.Browser)] |
||||
|
public TReturn? Browser { get; set; } |
||||
|
|
||||
|
// Required for the compiler, will be replaced with actual method compile time.
|
||||
|
public object ProvideValue() { return this; } |
||||
|
void IAddChild<TOn>.AddChild(TOn child) {} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
using System; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.InteropServices.JavaScript; |
||||
|
using System.Text.RegularExpressions; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Web.Interop; |
||||
|
|
||||
|
namespace Avalonia.Web; |
||||
|
|
||||
|
internal class BrowserRuntimePlatform : StandardRuntimePlatform |
||||
|
{ |
||||
|
private static readonly Lazy<RuntimePlatformInfo> Info = new(() => |
||||
|
{ |
||||
|
var result = new RuntimePlatformInfo |
||||
|
{ |
||||
|
IsCoreClr = true, // WASM browser is always CoreCLR
|
||||
|
IsBrowser = true, // BrowserRuntimePlatform only runs on Browser.
|
||||
|
OperatingSystem = OperatingSystemType.Browser, |
||||
|
IsMobile = AvaloniaModule.IsMobile() |
||||
|
}; |
||||
|
|
||||
|
return result; |
||||
|
}); |
||||
|
|
||||
|
public override RuntimePlatformInfo GetRuntimeInfo() => Info.Value; |
||||
|
} |
||||
@ -1,13 +1,25 @@ |
|||||
export class Caniuse { |
export class Caniuse { |
||||
public static canShowOpenFilePicker(): boolean { |
public static canShowOpenFilePicker(): boolean { |
||||
return typeof window.showOpenFilePicker !== "undefined"; |
return typeof globalThis.showOpenFilePicker !== "undefined"; |
||||
} |
} |
||||
|
|
||||
public static canShowSaveFilePicker(): boolean { |
public static canShowSaveFilePicker(): boolean { |
||||
return typeof window.showSaveFilePicker !== "undefined"; |
return typeof globalThis.showSaveFilePicker !== "undefined"; |
||||
} |
} |
||||
|
|
||||
public static canShowDirectoryPicker(): boolean { |
public static canShowDirectoryPicker(): boolean { |
||||
return typeof window.showDirectoryPicker !== "undefined"; |
return typeof globalThis.showDirectoryPicker !== "undefined"; |
||||
|
} |
||||
|
|
||||
|
public static isMobile(): boolean { |
||||
|
const userAgentData = (globalThis.navigator as any)?.userAgentData; |
||||
|
if (userAgentData) { |
||||
|
return userAgentData.mobile; |
||||
|
} |
||||
|
|
||||
|
const userAgent = navigator.userAgent; |
||||
|
const regex1 = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i; |
||||
|
const regex2 = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw(n|u)|c55\/|capi|ccwa|cdm|cell|chtm|cldc|cmd|co(mp|nd)|craw|da(it|ll|ng)|dbte|dcs|devi|dica|dmob|do(c|p)o|ds(12|d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(|_)|g1 u|g560|gene|gf5|gmo|go(\.w|od)|gr(ad|un)|haie|hcit|hd(m|p|t)|hei|hi(pt|ta)|hp( i|ip)|hsc|ht(c(| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i(20|go|ma)|i230|iac( ||\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|[a-w])|libw|lynx|m1w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|mcr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|([1-8]|c))|phil|pire|pl(ay|uc)|pn2|po(ck|rt|se)|prox|psio|ptg|qaa|qc(07|12|21|32|60|[2-7]|i)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h|oo|p)|sdk\/|se(c(|0|1)|47|mc|nd|ri)|sgh|shar|sie(|m)|sk0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h|v|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl|tdg|tel(i|m)|tim|tmo|to(pl|sh)|ts(70|m|m3|m5)|tx9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas|your|zeto|zte/i; |
||||
|
return regex1.test(userAgent) || regex2.test(userAgent.substr(0, 4)); |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,71 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Platform; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions; |
||||
|
|
||||
|
public class OnFormFactorExtensionTests : XamlTestBase |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Should_Resolve_Default_Value() |
||||
|
{ |
||||
|
using (AvaloniaLocator.EnterScope()) |
||||
|
{ |
||||
|
AvaloniaLocator.CurrentMutable.Bind<IRuntimePlatform>() |
||||
|
.ToConstant(new TestRuntimePlatform(false, false)); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<TextBlock Text='{OnFormFactor Default=""Hello World""}'/> |
||||
|
</UserControl>";
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
var textBlock = (TextBlock)userControl.Content!; |
||||
|
|
||||
|
Assert.Equal("Hello World", textBlock.Text); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData(false, true, "Im Mobile")] |
||||
|
[InlineData(true, false, "Im Desktop")] |
||||
|
[InlineData(false, false, "Default value")] |
||||
|
public void Should_Resolve_Expected_Value_Per_Platform(bool isDesktop, bool isMobile, string expectedResult) |
||||
|
{ |
||||
|
using (AvaloniaLocator.EnterScope()) |
||||
|
{ |
||||
|
AvaloniaLocator.CurrentMutable.Bind<IRuntimePlatform>() |
||||
|
.ToConstant(new TestRuntimePlatform(isDesktop, isMobile)); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<TextBlock Text='{OnFormFactor ""Default value"", |
||||
|
Mobile=""Im Mobile"", Desktop=""Im Desktop""}'/> |
||||
|
</UserControl>";
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
var textBlock = (TextBlock)userControl.Content!; |
||||
|
|
||||
|
Assert.Equal(expectedResult, textBlock.Text); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private class TestRuntimePlatform : StandardRuntimePlatform |
||||
|
{ |
||||
|
private readonly bool _isDesktop; |
||||
|
private readonly bool _isMobile; |
||||
|
|
||||
|
public TestRuntimePlatform(bool isDesktop, bool isMobile) |
||||
|
{ |
||||
|
_isDesktop = isDesktop; |
||||
|
_isMobile = isMobile; |
||||
|
} |
||||
|
|
||||
|
public override RuntimePlatformInfo GetRuntimeInfo() |
||||
|
{ |
||||
|
return new RuntimePlatformInfo() { IsDesktop = _isDesktop, IsMobile = _isMobile }; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,75 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Platform; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions; |
||||
|
|
||||
|
public class OnPlatformExtensionTests : XamlTestBase |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Should_Resolve_Default_Value() |
||||
|
{ |
||||
|
using (AvaloniaLocator.EnterScope()) |
||||
|
{ |
||||
|
AvaloniaLocator.CurrentMutable.Bind<IRuntimePlatform>() |
||||
|
.ToConstant(new TestRuntimePlatform(OperatingSystemType.Unknown)); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<TextBlock Text='{OnPlatform Default=""Hello World""}'/> |
||||
|
</UserControl>";
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
var textBlock = (TextBlock)userControl.Content!; |
||||
|
|
||||
|
Assert.Equal("Hello World", textBlock.Text); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData(OperatingSystemType.WinNT, "Im Windows")] |
||||
|
[InlineData(OperatingSystemType.OSX, "Im macOS")] |
||||
|
[InlineData(OperatingSystemType.Linux, "Im Linux")] |
||||
|
[InlineData(OperatingSystemType.Android, "Im Android")] |
||||
|
[InlineData(OperatingSystemType.iOS, "Im iOS")] |
||||
|
[InlineData(OperatingSystemType.Browser, "Im Browser")] |
||||
|
[InlineData(OperatingSystemType.Unknown, "Default value")] |
||||
|
public void Should_Resolve_Expected_Value_Per_Platform(OperatingSystemType currentPlatform, string expectedResult) |
||||
|
{ |
||||
|
using (AvaloniaLocator.EnterScope()) |
||||
|
{ |
||||
|
AvaloniaLocator.CurrentMutable.Bind<IRuntimePlatform>() |
||||
|
.ToConstant(new TestRuntimePlatform(currentPlatform)); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<TextBlock Text='{OnPlatform ""Default value"", |
||||
|
Windows=""Im Windows"", macOS=""Im macOS"", |
||||
|
Linux=""Im Linux"", Android=""Im Android"", |
||||
|
iOS=""Im iOS"", Browser=""Im Browser""}'/> |
||||
|
</UserControl>";
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
var textBlock = (TextBlock)userControl.Content!; |
||||
|
|
||||
|
Assert.Equal(expectedResult, textBlock.Text); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private class TestRuntimePlatform : StandardRuntimePlatform |
||||
|
{ |
||||
|
private readonly OperatingSystemType _operatingSystemType; |
||||
|
|
||||
|
public TestRuntimePlatform(OperatingSystemType operatingSystemType) |
||||
|
{ |
||||
|
_operatingSystemType = operatingSystemType; |
||||
|
} |
||||
|
|
||||
|
public override RuntimePlatformInfo GetRuntimeInfo() |
||||
|
{ |
||||
|
return new RuntimePlatformInfo() { OperatingSystem = _operatingSystemType }; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,605 @@ |
|||||
|
using System; |
||||
|
using System.Reactive.Disposables; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml.MarkupExtensions; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Metadata; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions; |
||||
|
|
||||
|
public class OptionsMarkupExtensionTests : XamlTestBase |
||||
|
{ |
||||
|
public static Func<object, bool> RaisedOption; |
||||
|
public static int? ObjectsCreated; |
||||
|
|
||||
|
[Fact] |
||||
|
public void Resolve_Default_Value() |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals("default"); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<TextBlock xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
||||
|
Text='{local:OptionsMarkupExtension Default=""Hello World""}' />";
|
||||
|
|
||||
|
var textBlock = (TextBlock)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
|
||||
|
Assert.Equal("Hello World", textBlock.Text); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Resolve_Default_Value_From_Ctor() |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals("default"); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<TextBlock xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
||||
|
Text='{local:OptionsMarkupExtension ""Hello World"", OptionB=""Im Android""}' />";
|
||||
|
|
||||
|
var textBlock = (TextBlock)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
|
||||
|
Assert.Equal("Hello World", textBlock.Text); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Resolve_Implicit_Default_Value_Ref_Type() |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals("default"); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
||||
|
Tag='{local:OptionsMarkupExtension OptionA=""Hello World"", x:DataType=x:String}' />";
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
|
||||
|
Assert.Equal(null, userControl.Tag); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Resolve_Implicit_Default_Value_Val_Type() |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals("default"); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
||||
|
Height='{local:OptionsMarkupExtension OptionA=10}' />";
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
|
||||
|
Assert.Equal(0d, userControl.Height); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Resolve_Implicit_Default_Value_Avalonia_Val_Type() |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals("default"); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
||||
|
Margin='{local:OptionsMarkupExtension OptionA=10}' />";
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
|
||||
|
Assert.Equal(new Thickness(0), userControl.Margin); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData("option 1", "Im Option 1")] |
||||
|
[InlineData("option 2", "Im Option 2")] |
||||
|
[InlineData("3", "Im Option 3")] |
||||
|
[InlineData("unknown", "Default value")] |
||||
|
public void Resolve_Expected_Value_Per_Option(object option, string expectedResult) |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals(option); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<TextBlock xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
||||
|
Text='{local:OptionsMarkupExtension ""Default value"", |
||||
|
OptionA=""Im Option 1"", OptionB=""Im Option 2"", |
||||
|
OptionNumber=""Im Option 3""}' />";
|
||||
|
|
||||
|
var textBlock = (TextBlock)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
|
||||
|
Assert.Equal(expectedResult, textBlock.Text); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData("option 1", "Im Option 1")] |
||||
|
[InlineData("option 2", "Im Option 2")] |
||||
|
[InlineData("3", "Im Option 3")] |
||||
|
[InlineData("unknown", "Default value")] |
||||
|
public void Resolve_Expected_Value_Per_Option_Create_Single_Object(object option, string expectedResult) |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals(option); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<ContentControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<ContentControl.Content> |
||||
|
<local:OptionsMarkupExtension> |
||||
|
<local:OptionsMarkupExtension.Default> |
||||
|
<local:ChildObject Name=""Default value"" /> |
||||
|
</local:OptionsMarkupExtension.Default> |
||||
|
<local:OptionsMarkupExtension.OptionA> |
||||
|
<local:ChildObject Name=""Im Option 1"" /> |
||||
|
</local:OptionsMarkupExtension.OptionA> |
||||
|
<local:OptionsMarkupExtension.OptionB> |
||||
|
<local:ChildObject Name=""Im Option 2"" /> |
||||
|
</local:OptionsMarkupExtension.OptionB> |
||||
|
<local:OptionsMarkupExtension.OptionNumber> |
||||
|
<local:ChildObject Name=""Im Option 3"" /> |
||||
|
</local:OptionsMarkupExtension.OptionNumber> |
||||
|
</local:OptionsMarkupExtension> |
||||
|
</ContentControl.Content> |
||||
|
</ContentControl>";
|
||||
|
|
||||
|
var contentControl = (ContentControl)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
var obj = Assert.IsType<ChildObject>(contentControl.Content); |
||||
|
|
||||
|
Assert.Equal(expectedResult, obj.Name); |
||||
|
Assert.Equal(1, ObjectsCreated); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Convert_Bcl_Type() |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals("option 1"); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<Border xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
||||
|
Height='{local:OptionsMarkupExtension OptionA=50.1}' />";
|
||||
|
|
||||
|
var border = (Border)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
|
||||
|
Assert.Equal(50.1, border.Height); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Convert_Avalonia_Type() |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals("option 1"); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<Border xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
||||
|
Padding='{local:OptionsMarkupExtension OptionA=""10, 8, 10, 8""}' />";
|
||||
|
|
||||
|
var border = (Border)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
|
||||
|
Assert.Equal(new Thickness(10, 8, 10, 8), border.Padding); |
||||
|
} |
||||
|
|
||||
|
[PlatformFact(TestPlatforms.Windows | TestPlatforms.Linux, "TypeArguments test is failing on macOS from SRE emit")] |
||||
|
public void Respect_Custom_TypeArgument() |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals("option 1"); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<TextBlock xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
||||
|
Tag='{local:OptionsMarkupExtensionWithGeneric Default=20, OptionA=""10, 10, 10, 10"", x:TypeArguments=Thickness}' />";
|
||||
|
|
||||
|
var textBlock = (TextBlock)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
|
||||
|
Assert.Equal(new Thickness(10, 10, 10, 10), textBlock.Tag); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Allow_Nester_Markup_Extensions() |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals("option 1"); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<UserControl.Resources> |
||||
|
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
||||
|
</UserControl.Resources> |
||||
|
<Border Background='{local:OptionsMarkupExtension OptionA={StaticResource brush}}'/> |
||||
|
</UserControl>";
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
var border = (Border)userControl.Content!; |
||||
|
|
||||
|
Assert.Equal(Color.Parse("#ff506070"), ((ISolidColorBrush)border.Background!).Color); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Allow_Nester_On_Platform_Markup_Extensions() |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals("option 1"); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<Border xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
||||
|
Margin='{local:OptionsMarkupExtension OptionA={local:OptionsMarkupExtensionMinimal OptionA=""10,10,10,10""}}' />";
|
||||
|
|
||||
|
var border = (Border)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
|
||||
|
Assert.Equal(new Thickness(10), border.Margin); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Support_Xml_Syntax() |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals("option 1"); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<Border xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<Border.Background> |
||||
|
<local:OptionsMarkupExtension> |
||||
|
<local:OptionsMarkupExtension.OptionA> |
||||
|
<SolidColorBrush Color='#ff506070' /> |
||||
|
</local:OptionsMarkupExtension.OptionA> |
||||
|
</local:OptionsMarkupExtension> |
||||
|
</Border.Background> |
||||
|
</Border>";
|
||||
|
|
||||
|
var border = (Border)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
|
||||
|
Assert.Equal(Color.Parse("#ff506070"), ((ISolidColorBrush)border.Background!).Color); |
||||
|
} |
||||
|
|
||||
|
[PlatformFact(TestPlatforms.Windows | TestPlatforms.Linux, "TypeArguments test is failing on macOS from SRE emit")] |
||||
|
public void Support_Xml_Syntax_With_Custom_TypeArguments() |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals("option 1"); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<Border xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<Border.Tag> |
||||
|
<local:OptionsMarkupExtensionWithGeneric x:TypeArguments='Thickness' OptionA='10, 10, 10, 10' Default='20' /> |
||||
|
</Border.Tag> |
||||
|
</Border>";
|
||||
|
|
||||
|
var border = (Border)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
|
||||
|
Assert.Equal(new Thickness(10, 10, 10, 10), border.Tag); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData("option 1", "#ff506070")] |
||||
|
[InlineData("3", "#000")] |
||||
|
public void Support_Special_On_Syntax(object option, string color) |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals(option); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<Border xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<Border.Background> |
||||
|
<local:OptionsMarkupExtension> |
||||
|
<On Options='OptionA, OptionB'> |
||||
|
<SolidColorBrush Color='#ff506070' /> |
||||
|
</On> |
||||
|
<On Options=' OptionNumber '> |
||||
|
<SolidColorBrush Color='#000' /> |
||||
|
</On> |
||||
|
</local:OptionsMarkupExtension> |
||||
|
</Border.Background> |
||||
|
</Border>";
|
||||
|
|
||||
|
var border = (Border)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
|
||||
|
Assert.Equal(Color.Parse(color), ((ISolidColorBrush)border.Background!).Color); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Support_Control_Inside_Xml_Syntax() |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals("option 1"); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<local:OptionsMarkupExtension> |
||||
|
<local:OptionsMarkupExtension.OptionA> |
||||
|
<Button Content='Hello World' /> |
||||
|
</local:OptionsMarkupExtension.OptionA> |
||||
|
</local:OptionsMarkupExtension> |
||||
|
</UserControl>";
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
var button = (Button)userControl.Content!; |
||||
|
|
||||
|
Assert.Equal("Hello World", button.Content); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Support_Default_Control_Inside_Xml_Syntax() |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals("unknown"); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<local:OptionsMarkupExtension> |
||||
|
<local:OptionsMarkupExtension.Default> |
||||
|
<Button Content='Hello World' /> |
||||
|
</local:OptionsMarkupExtension.Default> |
||||
|
</local:OptionsMarkupExtension> |
||||
|
</UserControl>";
|
||||
|
|
||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
var button = (Button)userControl.Content!; |
||||
|
|
||||
|
Assert.Equal("Hello World", button.Content); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Support_Complex_Property_Setters_Dictionary() |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals("option 1"); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<ResourceDictionary xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<Color x:Key='Color1'>Black</Color> |
||||
|
<local:OptionsMarkupExtension x:Key='MyKey'> |
||||
|
<local:OptionsMarkupExtension.OptionA> |
||||
|
<Button Content='Hello World' /> |
||||
|
</local:OptionsMarkupExtension.OptionA> |
||||
|
</local:OptionsMarkupExtension> |
||||
|
<Color x:Key='Color2'>White</Color> |
||||
|
</ResourceDictionary>";
|
||||
|
|
||||
|
var resourceDictionary = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
var button = Assert.IsType<Button>(resourceDictionary["MyKey"]); |
||||
|
Assert.Equal("Hello World", button.Content); |
||||
|
Assert.Equal(Colors.Black, resourceDictionary["Color1"]); |
||||
|
Assert.Equal(Colors.White, resourceDictionary["Color2"]); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Support_Complex_Property_Setters_List() |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals("option 1"); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<Panel xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<TextBlock /> |
||||
|
<local:OptionsMarkupExtension> |
||||
|
<local:OptionsMarkupExtension.OptionA> |
||||
|
<Button Content='Hello World' /> |
||||
|
</local:OptionsMarkupExtension.OptionA> |
||||
|
</local:OptionsMarkupExtension> |
||||
|
<TextBox /> |
||||
|
</Panel>";
|
||||
|
|
||||
|
var panel = (Panel)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
Assert.Equal(3, panel.Children.Count); |
||||
|
Assert.IsType<Button>(panel.Children[1]); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData("option 1", "foo")] |
||||
|
[InlineData("option 2", "bar")] |
||||
|
public void BindingExtension_Works_Inside_Of_OptionsMarkupExtension(string option, string expected) |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals(option); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
|
<UserControl.Resources> |
||||
|
<x:String x:Key='text'>foo</x:String> |
||||
|
</UserControl.Resources> |
||||
|
|
||||
|
<TextBlock Name='textBlock' Text='{local:OptionsMarkupExtension OptionA={CompiledBinding Source={StaticResource text}}, OptionB=bar}'/> |
||||
|
</UserControl>";
|
||||
|
|
||||
|
var window = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
var textBlock = window.FindControl<TextBlock>("textBlock"); |
||||
|
|
||||
|
Assert.Equal(expected, textBlock.Text); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Resolve_Expected_Value_With_Method_Without_ServiceProvider() |
||||
|
{ |
||||
|
using var _ = SetupTestGlobals(2); |
||||
|
|
||||
|
var xaml = @"
|
||||
|
<TextBlock xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
||||
|
Text='{local:OptionsMarkupExtensionNoServiceProvider OptionB=""Im Option 2""}' />";
|
||||
|
|
||||
|
var textBlock = (TextBlock)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
|
||||
|
Assert.Equal("Im Option 2", textBlock.Text); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Resolve_Expected_Value_Minimal_Extension() |
||||
|
{ |
||||
|
var xaml = @"
|
||||
|
<TextBlock xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
||||
|
IsVisible='{local:OptionsMarkupExtensionMinimal OptionA=True}' />";
|
||||
|
|
||||
|
var textBlock = (TextBlock)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
|
||||
|
Assert.True(textBlock.IsSet(Visual.IsVisibleProperty)); |
||||
|
Assert.True(textBlock.IsVisible); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Resolve_Expected_Value_Extension_With_Property() |
||||
|
{ |
||||
|
var xaml = @"
|
||||
|
<TextBlock xmlns='https://github.com/avaloniaui'
|
||||
|
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' |
||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
||||
|
IsVisible='{local:OptionsMarkupExtensionWithProperty OptionA=True, Property=5}' />";
|
||||
|
|
||||
|
var textBlock = (TextBlock)AvaloniaRuntimeXamlLoader.Load(xaml); |
||||
|
|
||||
|
Assert.True(textBlock.IsSet(Visual.IsVisibleProperty)); |
||||
|
Assert.True(textBlock.IsVisible); |
||||
|
} |
||||
|
|
||||
|
private static IDisposable SetupTestGlobals(object acceptedOption) |
||||
|
{ |
||||
|
RaisedOption = o => o.Equals(acceptedOption); |
||||
|
ObjectsCreated = 0; |
||||
|
return Disposable.Create(() => |
||||
|
{ |
||||
|
RaisedOption = null; |
||||
|
ObjectsCreated = null; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class OptionsMarkupExtension : OptionsMarkupExtensionBase<object, On> |
||||
|
{ |
||||
|
public OptionsMarkupExtension() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public OptionsMarkupExtension(object defaultValue) |
||||
|
{ |
||||
|
Default = defaultValue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class OptionsMarkupExtension<TReturn> : OptionsMarkupExtensionBase<TReturn, On<TReturn>> |
||||
|
{ |
||||
|
public OptionsMarkupExtension() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public OptionsMarkupExtension(TReturn defaultValue) |
||||
|
{ |
||||
|
Default = defaultValue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class OptionsMarkupExtensionBase<TReturn, TOn> : IAddChild<TOn> |
||||
|
where TOn : On<TReturn> |
||||
|
{ |
||||
|
[MarkupExtensionOption("option 1")] |
||||
|
public TReturn OptionA { get; set; } |
||||
|
|
||||
|
[MarkupExtensionOption("option 2")] |
||||
|
public TReturn OptionB { get; set; } |
||||
|
|
||||
|
[MarkupExtensionOption(3)] |
||||
|
public TReturn OptionNumber { get; set; } |
||||
|
|
||||
|
[Content] |
||||
|
[MarkupExtensionDefaultOption] |
||||
|
public TReturn Default { get; set; } |
||||
|
|
||||
|
public bool ShouldProvideOption(IServiceProvider serviceProvider, string option) |
||||
|
{ |
||||
|
return OptionsMarkupExtensionTests.RaisedOption(option); |
||||
|
} |
||||
|
|
||||
|
public TReturn ProvideValue(IServiceProvider serviceProvider) { throw null; } |
||||
|
|
||||
|
public void AddChild(TOn child) {} |
||||
|
} |
||||
|
|
||||
|
public class OptionsMarkupExtensionNoServiceProvider |
||||
|
{ |
||||
|
[MarkupExtensionOption(1)] |
||||
|
public object OptionA { get; set; } |
||||
|
|
||||
|
[MarkupExtensionOption(2)] |
||||
|
public object OptionB { get; set; } |
||||
|
|
||||
|
[Content] |
||||
|
[MarkupExtensionDefaultOption] |
||||
|
public object Default { get; set; } |
||||
|
|
||||
|
public static bool ShouldProvideOption(int option) |
||||
|
{ |
||||
|
return OptionsMarkupExtensionTests.RaisedOption(option); |
||||
|
} |
||||
|
|
||||
|
public object ProvideValue(IServiceProvider serviceProvider) { throw null; } |
||||
|
} |
||||
|
|
||||
|
public class OptionsMarkupExtensionMinimal |
||||
|
{ |
||||
|
[MarkupExtensionOption(11.0)] |
||||
|
public bool OptionA { get; set; } |
||||
|
|
||||
|
public static bool ShouldProvideOption(double option) => option > 0; |
||||
|
|
||||
|
public object ProvideValue() { throw null; } |
||||
|
} |
||||
|
|
||||
|
public class OptionsMarkupExtensionWithProperty |
||||
|
{ |
||||
|
[MarkupExtensionOption(5)] |
||||
|
public bool OptionA { get; set; } |
||||
|
|
||||
|
public int Property { get; set; } |
||||
|
|
||||
|
public bool ShouldProvideOption(int option) => option == Property; |
||||
|
|
||||
|
public object ProvideValue() { throw null; } |
||||
|
} |
||||
|
|
||||
|
public class OptionsMarkupExtensionWithGeneric<TResult> |
||||
|
{ |
||||
|
[MarkupExtensionOption("option 1")] |
||||
|
public TResult OptionA { get; set; } |
||||
|
|
||||
|
[Content] |
||||
|
[MarkupExtensionDefaultOption] |
||||
|
public TResult Default { get; set; } |
||||
|
|
||||
|
public bool ShouldProvideOption(string option) |
||||
|
{ |
||||
|
return OptionsMarkupExtensionTests.RaisedOption(option); |
||||
|
} |
||||
|
|
||||
|
public TResult ProvideValue() { throw null; } |
||||
|
} |
||||
|
|
||||
|
public class ChildObject |
||||
|
{ |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public ChildObject() |
||||
|
{ |
||||
|
OptionsMarkupExtensionTests.ObjectsCreated++; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue