Amend Navigation

This commit is contained in:
Daniel Clark
2022-12-22 20:31:25 +00:00
parent 757349285a
commit 894bbbd177
11 changed files with 424 additions and 151 deletions
+1 -7
View File
@@ -1,12 +1,6 @@
using Avalonia.Styling;
namespace Toolkit.Controls.Avalonia;
namespace Toolkit.Controls.Avalonia;
public class FAPathIcon : FluentAvalonia.UI.Controls.FAPathIcon
{
}
public class SymbolIcon : FluentAvalonia.UI.Controls.SymbolIcon
{
}
@@ -0,0 +1,6 @@
namespace Toolkit.Controls.Avalonia;
public class FontIcon : FluentAvalonia.UI.Controls.FontIcon
{
}
@@ -0,0 +1,6 @@
namespace Toolkit.Controls.Avalonia;
public class SymbolIcon : FluentAvalonia.UI.Controls.SymbolIcon
{
}
+42 -77
View File
@@ -2,6 +2,7 @@
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Markup.Xaml;
using System.Xml.Linq;
using Toolkit.Framework.Foundation;
namespace Toolkit.Framework.Avalonia;
@@ -13,55 +14,50 @@ public class NavigateExtension : TriggerExtension
private static readonly AttachedProperty<object> ParameterProperty =
AvaloniaProperty.RegisterAttached<NavigateExtension, Control, object>("Parameter");
private static readonly AvaloniaProperty RouteProperty =
AvaloniaProperty.RegisterAttached<NavigateExtension, Control, object>("Route");
private static readonly AvaloniaProperty ToProperty =
AvaloniaProperty.RegisterAttached<NavigateExtension, Control, object>("To");
private static readonly AvaloniaProperty PathProperty =
AvaloniaProperty.RegisterAttached<NavigateExtension, Control, string>("Path");
private readonly Binding? mediatorBinding;
private readonly List<object?> parameters = new();
private readonly Binding? toBinding;
private object? route;
private Binding? routeBinding;
private readonly Binding? pathBinding;
public NavigateExtension(object mediator,
object to)
object path)
{
mediatorBinding = mediator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
this.pathBinding = path is Binding pathBinding ? pathBinding : path.ToBinding();
}
public NavigateExtension(object mediator,
object to,
object path,
object args1)
{
mediatorBinding = mediator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
this.pathBinding = path is Binding pathBinding ? pathBinding : path.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
}
public NavigateExtension(object mediator,
object to,
object path,
object args1,
object args2)
{
mediatorBinding = mediator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
this.pathBinding = path is Binding pathBinding ? pathBinding : path.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
}
public NavigateExtension(object mediator,
object to,
object path,
object args1,
object args2,
object args3)
{
mediatorBinding = mediator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
this.pathBinding = path is Binding pathBinding ? pathBinding : path.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
@@ -69,14 +65,14 @@ public class NavigateExtension : TriggerExtension
}
public NavigateExtension(object mediator,
object to,
object path,
object args1,
object args2,
object args3,
object args4)
{
mediatorBinding = mediator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
this.pathBinding = path is Binding pathBinding ? pathBinding : path.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
@@ -85,7 +81,7 @@ public class NavigateExtension : TriggerExtension
}
public NavigateExtension(object mediator,
object to,
object path,
object args1,
object args2,
object args3,
@@ -93,7 +89,7 @@ public class NavigateExtension : TriggerExtension
object args5)
{
mediatorBinding = mediator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
this.pathBinding = path is Binding pathBinding ? pathBinding : path.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
@@ -103,7 +99,7 @@ public class NavigateExtension : TriggerExtension
}
public NavigateExtension(object mediator,
object to,
object path,
object args1,
object args2,
object args3,
@@ -112,7 +108,7 @@ public class NavigateExtension : TriggerExtension
object args6)
{
mediatorBinding = mediator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
this.pathBinding = path is Binding pathBinding ? pathBinding : path.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
@@ -123,7 +119,7 @@ public class NavigateExtension : TriggerExtension
}
public NavigateExtension(object mediator,
object to,
object path,
object args1,
object args2,
object args3,
@@ -133,7 +129,7 @@ public class NavigateExtension : TriggerExtension
object args7)
{
mediatorBinding = mediator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
this.pathBinding = path is Binding pathBinding ? pathBinding : path.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
@@ -145,7 +141,7 @@ public class NavigateExtension : TriggerExtension
}
public NavigateExtension(object mediator,
object to,
object path,
object args1,
object args2,
object args3,
@@ -156,7 +152,7 @@ public class NavigateExtension : TriggerExtension
object args8)
{
mediatorBinding = mediator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
this.pathBinding = path is Binding pathBinding ? pathBinding : path.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
@@ -169,7 +165,7 @@ public class NavigateExtension : TriggerExtension
}
public NavigateExtension(object mediator,
object to,
object path,
object args1,
object args2,
object args3,
@@ -181,7 +177,7 @@ public class NavigateExtension : TriggerExtension
object args9)
{
mediatorBinding = mediator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
this.pathBinding = path is Binding pathBinding ? pathBinding : path.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
@@ -195,7 +191,7 @@ public class NavigateExtension : TriggerExtension
}
public NavigateExtension(object mediator,
object to,
object path,
object args1,
object args2,
object args3,
@@ -208,7 +204,7 @@ public class NavigateExtension : TriggerExtension
object args10)
{
mediatorBinding = mediator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
this.pathBinding = path is Binding pathBinding ? pathBinding : path.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
@@ -223,7 +219,7 @@ public class NavigateExtension : TriggerExtension
}
public NavigateExtension(object mediator,
object to,
object path,
object args1,
object args2,
object args3,
@@ -237,7 +233,7 @@ public class NavigateExtension : TriggerExtension
object args11)
{
mediatorBinding = mediator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
this.pathBinding = path is Binding pathBinding ? pathBinding : path.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
@@ -253,7 +249,7 @@ public class NavigateExtension : TriggerExtension
}
public NavigateExtension(object mediator,
object to,
object path,
object args1,
object args2,
object args3,
@@ -268,7 +264,7 @@ public class NavigateExtension : TriggerExtension
object args12)
{
mediatorBinding = mediator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
this.pathBinding = path is Binding pathBinding ? pathBinding : path.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
@@ -285,7 +281,7 @@ public class NavigateExtension : TriggerExtension
}
public NavigateExtension(object mediator,
object to,
object path,
object args1,
object args2,
object args3,
@@ -301,7 +297,7 @@ public class NavigateExtension : TriggerExtension
object args13)
{
mediatorBinding = mediator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
this.pathBinding = path is Binding pathBinding ? pathBinding : path.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
@@ -319,7 +315,7 @@ public class NavigateExtension : TriggerExtension
}
public NavigateExtension(object mediator,
object to,
object path,
object args1,
object args2,
object args3,
@@ -336,7 +332,7 @@ public class NavigateExtension : TriggerExtension
object args14)
{
mediatorBinding = mediator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
this.pathBinding = path is Binding pathBinding ? pathBinding : path.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
@@ -355,7 +351,7 @@ public class NavigateExtension : TriggerExtension
}
public NavigateExtension(object mediator,
object to,
object path,
object args1,
object args2,
object args3,
@@ -373,7 +369,7 @@ public class NavigateExtension : TriggerExtension
object args15)
{
mediatorBinding = mediator.ToBinding();
this.toBinding = to is Binding toBinding ? toBinding : to.ToBinding();
this.pathBinding = path is Binding pathBinding ? pathBinding : path.ToBinding();
parameters.Add(args1 is MarkupExtension ? args1 : args1.ToBinding());
parameters.Add(args2 is MarkupExtension ? args2 : args2.ToBinding());
@@ -392,22 +388,6 @@ public class NavigateExtension : TriggerExtension
parameters.Add(args15 is MarkupExtension ? args15 : args15.ToBinding());
}
public object? Route
{
get
{
return route;
}
set
{
route = value;
if (route is not null)
{
routeBinding = route.ToBinding();
}
}
}
protected override void OnInvoked(object sender, EventArgs args)
{
if (TargetObject is not null && mediatorBinding is not null)
@@ -415,10 +395,10 @@ public class NavigateExtension : TriggerExtension
TargetObject.Bind(MediatorProperty, mediatorBinding);
if (TargetObject.GetValue(MediatorProperty) is IMediator mediator)
{
if (toBinding is not null)
if (pathBinding is not null)
{
TargetObject.Bind(ToProperty, toBinding);
if (TargetObject.GetValue(ToProperty) is { } to)
TargetObject.Bind(PathProperty, pathBinding);
if (TargetObject.GetValue(PathProperty) is string path)
{
List<object>? parameters = new();
foreach (object? parameter in this.parameters)
@@ -449,27 +429,12 @@ public class NavigateExtension : TriggerExtension
}
}
object? route = null;
if (routeBinding is not null)
if (pathBinding?.StringFormat is string format)
{
TargetObject.Bind(RouteProperty, routeBinding);
route = TargetObject.GetValue(RouteProperty);
path = string.Format(format, path);
}
if (to is string name)
{
if (toBinding?.StringFormat is string format)
{
name = string.Format(format, name);
}
mediator.Send(new Navigate(name, parameters.ToArray()) { Route = route });
}
if (to is Type type)
{
mediator.Send(new Navigate(type, parameters.ToArray()) { Route = route });
}
mediator.Send(new Navigate(path, parameters.ToArray()));
}
}
}
@@ -1,7 +1,8 @@
using Avalonia.Controls;
using System.Diagnostics;
using Toolkit.Controls.Avalonia;
using Toolkit.Framework.Foundation;
using Microsoft.Extensions.Primitives;
using System.Diagnostics;
namespace Toolkit.Framework.Avalonia;
@@ -30,6 +31,8 @@ public class NavigateHandler : IRequestHandler<Navigate>
}
public ValueTask<Unit> Handle(Navigate request, CancellationToken cancellationToken)
{
foreach (NavigationSegment segment in NavigationSegment.FromPath(request.Path))
{
object? content = null;
object? template = null;
@@ -52,22 +55,16 @@ public class NavigateHandler : IRequestHandler<Navigate>
}
}
if (request.Name is { Length: > 0 } name)
if (segment.Name is { Length: > 0 } name)
{
content = namedContentFactory.Create(name, parameters.ToArray());
template = namedContentTemplateFactory.Create(name);
}
if (request.Type is Type type)
{
content = typedContentFactory.Create(type, parameters.ToArray());
template = contentTemplateFactory.Create(content);
}
if (template is not null)
{
object? target = null;
if (descriptors.FirstOrDefault(x => request.Route is string { } name && name == x.Name) is NavigationRouteDescriptor descriptor)
if (descriptors.FirstOrDefault(x => segment.Target is string { } name && name == x.Name) is NavigationRouteDescriptor descriptor)
{
target = descriptor.Route;
}
@@ -91,7 +88,7 @@ public class NavigateHandler : IRequestHandler<Navigate>
}
else
{
if (descriptors.FirstOrDefault(x => request.Route is string { } name && name == x.Name) is NavigationRouteDescriptor descriptor)
if (descriptors.FirstOrDefault(x => segment.Target is string { } name && name == x.Name) is NavigationRouteDescriptor descriptor)
{
if (descriptor.Route is ContentControl contentControl)
{
@@ -99,6 +96,7 @@ public class NavigateHandler : IRequestHandler<Navigate>
}
}
}
}
return default;
}
+1
View File
@@ -6,6 +6,7 @@
<Nullable>enable</Nullable>
<AssemblyName>Toolkit.Framework.Foundation</AssemblyName>
<RootNamespace>Toolkit.Framework.Foundation</RootNamespace>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
@@ -0,0 +1,66 @@
using Microsoft.Extensions.Primitives;
namespace Toolkit.Framework.Foundation;
public struct KeyValueAccumulator
{
private Dictionary<string, StringValues> accumulator;
private Dictionary<string, List<string>> expandingAccumulator;
public void Append(string key, string value)
{
accumulator ??= new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
if (accumulator.TryGetValue(key, out StringValues values))
{
if (values.Count == 0)
{
expandingAccumulator[key].Add(value);
}
else if (values.Count == 1)
{
accumulator[key] = new string[] { values[0]!, value };
}
else
{
accumulator[key] = default;
expandingAccumulator ??= new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
List<string> list = new(8);
string?[] array = values.ToArray();
list.Add(array[0]!);
list.Add(array[1]!);
list.Add(value);
expandingAccumulator[key] = list;
}
}
else
{
accumulator[key] = new StringValues(value);
}
ValueCount++;
}
public bool HasValues => ValueCount > 0;
public int KeyCount => accumulator?.Count ?? 0;
public int ValueCount { get; private set; }
public Dictionary<string, StringValues> GetResults()
{
if (expandingAccumulator != null)
{
foreach (var entry in expandingAccumulator)
{
accumulator[entry.Key] = new StringValues(entry.Value.ToArray());
}
}
return accumulator ?? new Dictionary<string, StringValues>(0, StringComparer.OrdinalIgnoreCase);
}
}
@@ -0,0 +1,37 @@
using Microsoft.Extensions.Primitives;
using System.Text;
using System.Text.Encodings.Web;
namespace Toolkit.Framework.Foundation;
public static class QueryHelpers
{
public static Dictionary<string, StringValues>? ParseNullableQuery(string? queryString)
{
KeyValueAccumulator accumulator = new();
QueryStringEnumerable enumerable = new(queryString);
foreach (QueryStringEnumerable.EncodedNameValuePair pair in enumerable)
{
accumulator.Append(pair.DecodeName().ToString(), pair.DecodeValue().ToString());
}
if (!accumulator.HasValues)
{
return null;
}
return accumulator.GetResults();
}
public static Dictionary<string, StringValues> ParseQuery(string? queryString)
{
Dictionary<string, StringValues>? result = ParseNullableQuery(queryString);
if (result == null)
{
return new Dictionary<string, StringValues>();
}
return result;
}
}
@@ -0,0 +1,157 @@
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
readonly struct QueryStringEnumerable
{
private readonly ReadOnlyMemory<char> queryString;
public QueryStringEnumerable(string? queryString) : this(queryString.AsMemory())
{
}
public QueryStringEnumerable(ReadOnlyMemory<char> queryString)
{
this.queryString = queryString;
}
public Enumerator GetEnumerator() => new(queryString);
public readonly struct EncodedNameValuePair
{
internal EncodedNameValuePair(ReadOnlyMemory<char> encodedName, ReadOnlyMemory<char> encodedValue)
{
EncodedName = encodedName;
EncodedValue = encodedValue;
}
public readonly ReadOnlyMemory<char> EncodedName { get; }
public readonly ReadOnlyMemory<char> EncodedValue { get; }
public ReadOnlyMemory<char> DecodeName() => Decode(EncodedName);
public ReadOnlyMemory<char> DecodeValue() => Decode(EncodedValue);
private static ReadOnlyMemory<char> Decode(ReadOnlyMemory<char> chars)
{
return chars.Length < 16 && chars.Span.IndexOfAny('%', '+') < 0 ? chars : Uri.UnescapeDataString(SpanHelper.ReplacePlusWithSpace(chars.Span)).AsMemory();
}
}
public struct Enumerator
{
private ReadOnlyMemory<char> query;
internal Enumerator(ReadOnlyMemory<char> query)
{
Current = default;
this.query = query.IsEmpty || query.Span[0] != '?' ? query : query[1..];
}
public EncodedNameValuePair Current { get; private set; }
public bool MoveNext()
{
while (!query.IsEmpty)
{
ReadOnlyMemory<char> segment;
var delimiterIndex = query.Span.IndexOf('&');
if (delimiterIndex >= 0)
{
segment = query[..delimiterIndex];
query = query[(delimiterIndex + 1)..];
}
else
{
segment = query;
query = default;
}
int equalIndex = segment.Span.IndexOf('=');
if (equalIndex >= 0)
{
Current = new EncodedNameValuePair(segment[..equalIndex], segment[(equalIndex + 1)..]);
return true;
}
else if (!segment.IsEmpty)
{
Current = new EncodedNameValuePair(segment, default);
return true;
}
}
Current = default;
return false;
}
}
private static class SpanHelper
{
private static readonly SpanAction<char, IntPtr> replacePlusWithSpace = ReplacePlusWithSpaceCore;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe string ReplacePlusWithSpace(ReadOnlySpan<char> span)
{
fixed (char* ptr = &MemoryMarshal.GetReference(span))
{
return string.Create(span.Length, (IntPtr)ptr, replacePlusWithSpace);
}
}
private static unsafe void ReplacePlusWithSpaceCore(Span<char> buffer, IntPtr state)
{
fixed (char* ptr = &MemoryMarshal.GetReference(buffer))
{
ushort* input = (ushort*)state.ToPointer();
ushort* output = (ushort*)ptr;
nint i = 0;
nint n = (nint)(uint)buffer.Length;
if (Vector256.IsHardwareAccelerated && n >= Vector256<ushort>.Count)
{
Vector256<ushort> vecPlus = Vector256.Create((ushort)'+');
Vector256<ushort> vecSpace = Vector256.Create((ushort)' ');
do
{
Vector256<ushort> vec = Vector256.Load(input + i);
Vector256<ushort> mask = Vector256.Equals(vec, vecPlus);
Vector256<ushort> res = Vector256.ConditionalSelect(mask, vecSpace, vec);
res.Store(output + i);
i += Vector256<ushort>.Count;
} while (i <= n - Vector256<ushort>.Count);
}
if (Vector128.IsHardwareAccelerated && n - i >= Vector128<ushort>.Count)
{
do
{
Vector128<ushort> vec = Vector128.Load(input + i);
Vector128<ushort> vecPlus = Vector128.Create((ushort)'+');
Vector128<ushort> mask = Vector128.Equals(vec, vecPlus);
Vector128<ushort> vecSpace = Vector128.Create((ushort)' ');
Vector128<ushort> res = Vector128.ConditionalSelect(mask, vecSpace, vec);
res.Store(output + i);
i += Vector128<ushort>.Count;
} while (i <= n - Vector128<ushort>.Count);
}
for (; i < n; ++i)
{
if (input[i] != '+')
{
output[i] = input[i];
}
else
{
output[i] = ' ';
}
}
}
}
}
}
+3 -13
View File
@@ -3,23 +3,13 @@ namespace Toolkit.Framework.Foundation;
public record Navigate : IRequest
{
public Navigate(string name, params object?[] parameters)
public Navigate(string path, params object?[] parameters)
{
Name = name;
Path = path;
Parameters = parameters;
}
public Navigate(Type type, params object?[] parameters)
{
Type = type;
Parameters = parameters;
}
public Type? Type { get; }
public object? Route { get; init; }
public string? Name { get; }
public string? Path { get; }
public object?[] Parameters { get; }
}
@@ -0,0 +1,53 @@
using Microsoft.Extensions.Primitives;
using System.Diagnostics;
namespace Toolkit.Framework.Foundation;
public class NavigationSegment
{
private NavigationSegment(string name, IDictionary<string, StringValues> parameters, string target)
{
Name = name;
Parameters = parameters;
Target = target;
}
public string Name { get; }
public IDictionary<string, StringValues> Parameters { get; }
public string Target { get; }
public static IReadOnlyCollection<NavigationSegment> FromPath(string? path)
{
List<NavigationSegment> result = new();
if (path is null)
{
return result;
}
string[] pathParts = path.Split('?');
string[] segments = pathParts is { Length: >= 1 } ? pathParts[0].Split('/', StringSplitOptions.RemoveEmptyEntries)
: Array.Empty<string>();
string query = pathParts is { Length: 2 } ? pathParts[1] : "";
Dictionary<string, StringValues> parameters = QueryHelpers.ParseQuery(query);
foreach (string segment in segments)
{
string[] segmentParts = segment.Split('#');
string name = segmentParts is { Length: >= 1 } ? segmentParts[0] : "";
string target = segmentParts is { Length: 2 } ? segmentParts[1] : "";
Trace.WriteLine(name);
Trace.WriteLine(target);
result.Add(new NavigationSegment(name, parameters, target));
}
return result;
}
}