Improvement to navigation regions

This commit is contained in:
TheXamlGuy
2024-05-09 22:37:36 +01:00
parent 711353c8e9
commit 54d2b5374d
31 changed files with 173 additions and 184 deletions
@@ -5,7 +5,7 @@ using Toolkit.Foundation;
namespace Toolkit.Avalonia;
public class ClassicDesktopStyleApplicationHandler(INavigationContext navigationContext) :
public class ClassicDesktopStyleApplicationHandler :
INavigateHandler<IClassicDesktopStyleApplicationLifetime>
{
public Task Handle(Navigate<IClassicDesktopStyleApplicationLifetime> args,
@@ -18,8 +18,6 @@ public class ClassicDesktopStyleApplicationHandler(INavigationContext navigation
{
lifeTime.MainWindow = window;
window.DataContext = args.Content;
navigationContext.Set(window);
}
}
+1 -3
View File
@@ -4,7 +4,7 @@ using Toolkit.Foundation;
namespace Toolkit.Avalonia;
public class ContentControlHandler(INavigationContext navigationContext) :
public class ContentControlHandler :
INavigateHandler<ContentControl>
{
public async Task Handle(Navigate<ContentControl> args,
@@ -51,8 +51,6 @@ public class ContentControlHandler(INavigationContext navigationContext) :
control.DataContext = args.Content;
contentControl.Content = control;
navigationContext.Set(control);
await taskCompletionSource.Task;
}
}
+2 -2
View File
@@ -17,7 +17,7 @@ public class ContentTemplate :
if (observableViewModel.Provider is IServiceProvider provider)
{
IContentTemplateDescriptorProvider? contentTemplateProvider = provider.GetService<IContentTemplateDescriptorProvider>();
INavigationContext? viewModelContentBinder = provider.GetService<INavigationContext>();
INavigationRegion? viewModelContentBinder = provider.GetService<INavigationRegion>();
if (contentTemplateProvider?.Get(item.GetType().Name) is IContentTemplateDescriptor descriptor)
{
@@ -55,7 +55,7 @@ public class ContentTemplate :
control.Loaded += HandleLoaded;
control.Unloaded += HandleUnloaded;
viewModelContentBinder?.Set(control);
//viewModelContentBinder?.Register(control);
return control;
}
+1 -56
View File
@@ -8,7 +8,7 @@ using Toolkit.UI.Controls.Avalonia;
namespace Toolkit.Avalonia;
public class FrameHandler(INavigationContext navigationContext) :
public class FrameHandler :
INavigateHandler<Frame>,
INavigateBackHandler<Frame>
{
@@ -45,32 +45,6 @@ public class FrameHandler(INavigationContext navigationContext) :
{
await deactivating.Deactivating();
}
Type contentType = content.GetType();
if (contentType.GetInterfaces() is Type[] contracts)
{
foreach (Type contract in contracts)
{
if (contract.Name == typeof(IDeactivating<>).Name &&
contract.GetGenericArguments() is { Length: 1 } arguments)
{
if (contentType.GetMethods().FirstOrDefault(x =>
x.Name == "Deactivating" && x.ReturnType == typeof(Task<>)
.MakeGenericType(arguments[0]))
is MethodInfo methodInfo)
{
if (methodInfo.GetCustomAttribute<NavigationContextAttribute>()
is NavigationContextAttribute attribute)
{
if (await methodInfo.InvokeAsync<object?>(content) is object result)
{
results.Add(attribute.Name, result);
}
}
}
}
}
}
}
}
}
@@ -100,33 +74,6 @@ public class FrameHandler(INavigationContext navigationContext) :
{
await deactivated.Deactivated();
}
Type contentType = content.GetType();
if (contentType.GetInterfaces() is Type[] contracts)
{
foreach (Type contract in contracts)
{
if (contract.Name == typeof(IActivated<>).Name &&
contract.GetGenericArguments() is { Length: 1 } arguments)
{
if (contentType.GetMethods().FirstOrDefault(x =>
x.Name == "NavigatedToAsync" &&
x.GetCustomAttribute<NavigationContextAttribute>()
is NavigationContextAttribute attribute && results.ContainsKey(attribute.Name))
is MethodInfo methodInfo)
{
if (methodInfo.GetCustomAttribute<NavigationContextAttribute>()
is NavigationContextAttribute attribute)
{
if (results.TryGetValue(attribute.Name, out object? value))
{
await methodInfo.InvokeAsync(content, value);
}
}
}
}
}
}
}
}
@@ -181,8 +128,6 @@ public class FrameHandler(INavigationContext navigationContext) :
}
control.DataContext = args.Content;
navigationContext.Set(control);
NavigatedTo(args.Sender, control);
frame.NavigateFromObject(control, new FrameNavigationOptions { TransitionInfoOverride = new SuppressNavigationTransitionInfo() });
}
-8
View File
@@ -1,8 +0,0 @@
using Avalonia.Controls;
namespace Toolkit.Avalonia;
public interface INavigationContext
{
void Set(Control control);
}
@@ -126,7 +126,7 @@ public static class IServiceCollectionExtensions
services.AddTransient<IDispatcher, AvaloniaDispatcher>();
services.AddTransient<IContentTemplate, ContentTemplate>();
services.AddTransient<INavigationContext, NavigationContext>();
services.AddTransient<INavigationRegion, NavigationRegion>();
services.AddNavigateHandler<ClassicDesktopStyleApplicationHandler>();
services.AddNavigateHandler<SingleViewApplicationHandler>();
@@ -134,7 +134,7 @@ public static class IServiceCollectionExtensions
services.AddNavigateHandler<FrameHandler>();
services.AddNavigateHandler<ContentDialogHandler>();
services.AddScoped<INavigationContextCollection, NavigationContextCollection>(provider => new NavigationContextCollection
services.AddScoped<INavigationRegionCollection, NavigationRegionCollection>(provider => new NavigationRegionCollection
{
{ typeof(IClassicDesktopStyleApplicationLifetime), typeof(IClassicDesktopStyleApplicationLifetime) },
{ typeof(ISingleViewApplicationLifetime), typeof(ISingleViewApplicationLifetime) }
@@ -148,7 +148,7 @@ public static class IServiceCollectionExtensions
services.AddTransient<IContentTemplateDescriptorProvider, ContentTemplateDescriptorProvider>();
services.AddTransient<IContentTemplate, ContentTemplate>();
services.AddTransient<INavigationContext, NavigationContext>();
services.AddTransient<INavigationRegion, NavigationRegion>();
services.AddNavigateHandler<ContentControlHandler>();
services.AddNavigateHandler<FrameHandler>();
-36
View File
@@ -1,36 +0,0 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using System.Reflection;
using Toolkit.Foundation;
namespace Toolkit.Avalonia;
public class NavigationContext(INavigationContextCollection contexts) :
INavigationContext
{
public void Set(Control control)
{
if (control.GetType().GetCustomAttributes<NavigationTargetAttribute>()
is IEnumerable<NavigationTargetAttribute> attributes)
{
foreach (NavigationTargetAttribute attribute in attributes)
{
if (!contexts.ContainsKey(attribute.Name))
{
if (control.Find<TemplatedControl>(attribute.Name) is TemplatedControl content)
{
contexts.Add(attribute.Name, content);
void HandleUnloaded(object? sender, RoutedEventArgs args)
{
control.Unloaded -= HandleUnloaded;
contexts.Remove(attribute.Name);
}
control.Unloaded += HandleUnloaded;
}
}
}
}
}
}
+28
View File
@@ -0,0 +1,28 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Toolkit.Foundation;
namespace Toolkit.Avalonia;
public class NavigationRegion(INavigationRegionCollection collection) :
INavigationRegion
{
public void Register(string name,
object target)
{
if (target is Control control)
{
if (!collection.ContainsKey(name))
{
collection.Add(name, control);
void HandleUnloaded(object? sender, RoutedEventArgs args)
{
control.Unloaded -= HandleUnloaded;
collection.Remove(name);
}
control.Unloaded += HandleUnloaded;
}
}
}
}
@@ -5,7 +5,7 @@ using Toolkit.Foundation;
namespace Toolkit.Avalonia;
public class SingleViewApplicationHandler(INavigationContext navigationContext) :
public class SingleViewApplicationHandler :
INavigateHandler<ISingleViewApplicationLifetime>
{
public Task Handle(Navigate<ISingleViewApplicationLifetime> args,
@@ -18,8 +18,6 @@ public class SingleViewApplicationHandler(INavigationContext navigationContext)
{
lifeTime.MainView = control;
control.DataContext = args.Content;
navigationContext.Set(control);
}
}
+2 -2
View File
@@ -39,8 +39,8 @@ public class ComponentBuilder :
services.AddTransient<INavigationScope, NavigationScope>();
services.AddTransient<INavigationProvider, NavigationProvider>();
services.AddScoped<INavigationContextCollection, NavigationContextCollection>();
services.AddTransient<INavigationContextProvider, NavigationContextProvider>();
services.AddScoped<INavigationRegionCollection, NavigationRegionCollection>();
services.AddTransient<INavigationRegionProvider, NavigationRegionProvider>();
services.AddHandler<NavigateHandler>();
services.AddHandler<NavigateBackHandler>();
+4 -4
View File
@@ -25,10 +25,10 @@ public class ComponentFactory(IServiceProvider provider,
provider.GetRequiredService<IProxyService<IComponentHostCollection>>());
services.AddScoped(_ =>
provider.GetRequiredService<INavigationContextCollection>());
provider.GetRequiredService<INavigationRegionCollection>());
services.AddScoped(_ =>
provider.GetRequiredService<INavigationContextProvider>());
provider.GetRequiredService<INavigationRegionProvider>());
services.AddScoped(_ =>
provider.GetRequiredService<IComponentScopeCollection>());
@@ -37,7 +37,7 @@ public class ComponentFactory(IServiceProvider provider,
provider.GetRequiredService<IComponentScopeProvider>());
services.AddRange(proxy.Services);
services.AddSingleton(new ComponentScope(name));
services.AddSingleton(new NamedComponent(name));
if (servicesDelegate is not null)
{
@@ -45,7 +45,7 @@ public class ComponentFactory(IServiceProvider provider,
}
});
builder.AddConfiguration<TConfiguration>(name, configuration);
builder.AddConfiguration(name, configuration);
IComponentHost host = builder.Build();
scopes.Add(new ComponentScopeDescriptor(name,
+3 -3
View File
@@ -23,10 +23,10 @@ public class ComponentInitializer(IEnumerable<IComponent> components,
provider.GetRequiredService<IProxyService<IComponentHostCollection>>());
services.AddScoped(_ =>
provider.GetRequiredService<INavigationContextCollection>());
provider.GetRequiredService<INavigationRegionCollection>());
services.AddScoped(_ =>
provider.GetRequiredService<INavigationContextProvider>());
provider.GetRequiredService<INavigationRegionProvider>());
services.AddScoped(_ =>
provider.GetRequiredService<IComponentScopeCollection>());
@@ -36,7 +36,7 @@ public class ComponentInitializer(IEnumerable<IComponent> components,
services.AddRange(typedServices.Services);
services.AddSingleton(new ComponentScope(component.GetType().Name));
services.AddSingleton(new NamedComponent(component.GetType().Name));
});
IComponentHost host = builder.Build();
-3
View File
@@ -1,3 +0,0 @@
namespace Toolkit.Foundation;
public record ComponentScope(string Name);
+5 -5
View File
@@ -33,8 +33,8 @@ public class DefaultHostBuilder :
services.AddScoped<IProxyService<IPublisher>>(provider =>
new ProxyService<IPublisher>(provider.GetRequiredService<IPublisher>()));
services.AddScoped<IProxyService<INavigationContextProvider>>(provider =>
new ProxyService<INavigationContextProvider>(provider.GetRequiredService<INavigationContextProvider>()));
services.AddScoped<IProxyService<INavigationRegionProvider>>(provider =>
new ProxyService<INavigationRegionProvider>(provider.GetRequiredService<INavigationRegionProvider>()));
services.AddScoped<IProxyService<IComponentHostCollection>>(provider =>
new ProxyService<IComponentHostCollection>(provider.GetRequiredService<IComponentHostCollection>()));
@@ -45,12 +45,12 @@ public class DefaultHostBuilder :
services.AddTransient<INavigationProvider, NavigationProvider>();
services.AddScoped<INavigationContextCollection, NavigationContextCollection>();
services.AddTransient<INavigationContextProvider, NavigationContextProvider>();
services.AddScoped<INavigationRegionCollection, NavigationRegionCollection>();
services.AddTransient<INavigationRegionProvider, NavigationRegionProvider>();
services.AddTransient<INavigationScope, NavigationScope>();
services.AddSingleton(new ComponentScope("Root"));
services.AddSingleton(new NamedComponent("Root"));
services.AddScoped<IComponentScopeCollection, ComponentScopeCollection>(provider => new ComponentScopeCollection
{
new ComponentScopeDescriptor("Root", provider.GetRequiredService<IServiceProvider>())
+7
View File
@@ -0,0 +1,7 @@
namespace Toolkit.Foundation;
public interface INavigationRegion
{
void Register(string name,
object target);
}
@@ -1,4 +1,4 @@
namespace Toolkit.Foundation;
public interface INavigationContextCollection :
public interface INavigationRegionCollection :
IDictionary<object, object?>;
@@ -1,6 +1,6 @@
namespace Toolkit.Foundation;
public interface INavigationContextProvider
public interface INavigationRegionProvider
{
object? Get(object key);
+6
View File
@@ -0,0 +1,6 @@
namespace Toolkit.Foundation;
public record NamedComponent(string Name)
{
public override string ToString() => Name;
}
+1 -1
View File
@@ -2,7 +2,7 @@
namespace Toolkit.Foundation;
public class NavigateHandler(ComponentScope scope,
public class NavigateHandler(NamedComponent scope,
IComponentScopeProvider provider) :
INotificationHandler<Navigate>
{
@@ -1,12 +0,0 @@
namespace Toolkit.Foundation;
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class NavigationContextAttribute : Attribute
{
public NavigationContextAttribute(string name)
{
Name = name;
}
public string Name { get; }
}
@@ -1,4 +0,0 @@
namespace Toolkit.Foundation;
public class NavigationContextCollection : Dictionary<object, object?>,
INavigationContextCollection;
@@ -0,0 +1,4 @@
namespace Toolkit.Foundation;
public class NavigationRegionCollection : Dictionary<object, object?>,
INavigationRegionCollection;
@@ -1,7 +1,7 @@
namespace Toolkit.Foundation;
public class NavigationContextProvider(INavigationContextCollection contexts) :
INavigationContextProvider
public class NavigationRegionProvider(INavigationRegionCollection contexts) :
INavigationRegionProvider
{
public object? Get(object key) =>
contexts.TryGetValue(key, out object? target) ? target : default;
+1 -1
View File
@@ -6,7 +6,7 @@ public class NavigationScope(IPublisher publisher,
IServiceProvider provider,
IServiceFactory factory,
INavigationProvider navigationProvider,
INavigationContextProvider navigationContextProvider,
INavigationRegionProvider navigationContextProvider,
IContentTemplateDescriptorProvider contentTemplateDescriptorProvider) :
INavigationScope
{
+1 -2
View File
@@ -9,8 +9,7 @@ public class ConditionAction :
IAction
{
public static readonly DirectProperty<ConditionAction, ActionCollection> ActionsProperty =
AvaloniaProperty.RegisterDirect<ConditionAction, ActionCollection>(nameof(Actions),
x => x.Actions);
AvaloniaProperty.RegisterDirect<ConditionAction, ActionCollection>(nameof(Actions), x => x.Actions);
public static readonly StyledProperty<ICondition> ConditionProperty =
AvaloniaProperty.Register<ConditionAction, ICondition>(nameof(Condition));
+2 -1
View File
@@ -74,7 +74,8 @@ public class NavigateAction :
ParameterBindings.Select(binding => new KeyValuePair<string, object>(binding.Key, binding.Value)).ToArray() :
Enumerable.Empty<KeyValuePair<string, object>>()];
observableViewModel.Publisher.Publish(new Navigate(Route, Context == this ? control : Context, Scope ?? null, control.DataContext, Navigated, parameters));
observableViewModel.Publisher.Publish(new Navigate(Route, Context == this ? control : Context, Scope ?? null,
control.DataContext, Navigated, parameters)).ConfigureAwait(false);
}
}
+1 -1
View File
@@ -35,7 +35,7 @@ public class NavigateBackAction :
if (control.DataContext is IObservableViewModel observableViewModel)
{
observableViewModel.Publisher.Publish(new NavigateBack(Context
?? null, Scope ?? null)).GetAwaiter().GetResult();
?? null, Scope ?? null)).ConfigureAwait(false);
}
}
@@ -0,0 +1,47 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Metadata;
using Avalonia.Xaml.Interactivity;
using Microsoft.Extensions.DependencyInjection;
using Toolkit.Foundation;
namespace Toolkit.UI.Avalonia;
public class NavigateRegionAction :
AvaloniaObject,
IAction
{
public static readonly DirectProperty<NavigateRegionAction, ActionCollection> ActionsProperty =
AvaloniaProperty.RegisterDirect<NavigateRegionAction, ActionCollection>(nameof(Actions), x => x.Actions);
public static readonly StyledProperty<string> NameProperty =
AvaloniaProperty.Register<NavigateRegionAction, string>(nameof(Name));
private ActionCollection? actions;
[Content]
public ActionCollection Actions => actions ??= [];
public string Name
{
get => GetValue(NameProperty);
set => SetValue(NameProperty, value);
}
public object? Execute(object? sender,
object? parameter)
{
if (sender is Control control)
{
if (control.DataContext is IObservableViewModel observableViewModel)
{
if (observableViewModel.Provider.GetRequiredService<INavigationRegion>() is INavigationRegion navigationRegion)
{
navigationRegion.Register(Name, sender);
Interaction.ExecuteActions(sender, Actions, parameter);
}
}
}
return true;
}
}
@@ -2,6 +2,24 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Toolkit.UI.Controls.Avalonia">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<StaticResource x:Key="PersonPictureForegroundThemeBrush" ResourceKey="TextFillColorPrimaryBrush" />
<StaticResource x:Key="PersonPictureEllipseBadgeForegroundThemeBrush" ResourceKey="TextOnAccentFillColorPrimaryBrush" />
<StaticResource x:Key="PersonPictureEllipseBadgeFillThemeBrush" ResourceKey="AccentFillColorDefaultBrush" />
<StaticResource x:Key="PersonPictureEllipseBadgeStrokeThemeBrush" ResourceKey="ControlFillColorTransparentBrush" />
<StaticResource x:Key="PersonPictureEllipseFillThemeBrush" ResourceKey="ControlAltFillColorQuarternaryBrush" />
<StaticResource x:Key="PersonPictureEllipseFillStrokeBrush" ResourceKey="CardStrokeColorDefaultBrush" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<StaticResource x:Key="PersonPictureForegroundThemeBrush" ResourceKey="TextFillColorPrimaryBrush" />
<StaticResource x:Key="PersonPictureEllipseBadgeForegroundThemeBrush" ResourceKey="TextOnAccentFillColorPrimaryBrush" />
<StaticResource x:Key="PersonPictureEllipseBadgeFillThemeBrush" ResourceKey="AccentFillColorDefaultBrush" />
<StaticResource x:Key="PersonPictureEllipseBadgeStrokeThemeBrush" ResourceKey="ControlFillColorTransparentBrush" />
<StaticResource x:Key="PersonPictureEllipseFillThemeBrush" ResourceKey="ControlAltFillColorQuarternaryBrush" />
<StaticResource x:Key="PersonPictureEllipseFillStrokeBrush" ResourceKey="CardStrokeColorDefaultBrush" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<x:Double x:Key="PersonPictureEllipseBadgeStrokeOpacity">1</x:Double>
<x:Double x:Key="PersonPictureEllipseBadgeImageSourceStrokeOpacity">1</x:Double>
<x:Double x:Key="PersonPictureEllipseStrokeThickness">1</x:Double>
@@ -2,39 +2,41 @@
internal class PersonPictureInitialsGenerator
{
public static PersonPictureCharacterType GetCharacterType(string str)
public static PersonPictureCharacterType GetCharacterType(string content)
{
PersonPictureCharacterType result = PersonPictureCharacterType.Other;
for (int i = 0; i < 3; i++)
if (content is { Length: > 0 })
{
if ((i >= str.Length) || (str[i] == '\0') || (str[i] == 0xFEFF))
for (int i = 0; i < 3; i++)
{
break;
}
if ((i >= content.Length) || (content[i] == '\0') || (content[i] == 0xFEFF))
{
break;
}
char character = str[i];
PersonPictureCharacterType evaluationResult = GetCharacterType(character);
char character = content[i];
PersonPictureCharacterType evaluationResult = GetCharacterType(character);
switch (evaluationResult)
{
case PersonPictureCharacterType.Glyph:
result = PersonPictureCharacterType.Glyph;
break;
case PersonPictureCharacterType.Symbolic:
if (result != PersonPictureCharacterType.Glyph)
{
result = PersonPictureCharacterType.Symbolic;
}
break;
case PersonPictureCharacterType.Standard:
if ((result != PersonPictureCharacterType.Glyph) && (result != PersonPictureCharacterType.Symbolic))
{
result = PersonPictureCharacterType.Standard;
}
break;
default:
break;
switch (evaluationResult)
{
case PersonPictureCharacterType.Glyph:
result = PersonPictureCharacterType.Glyph;
break;
case PersonPictureCharacterType.Symbolic:
if (result != PersonPictureCharacterType.Glyph)
{
result = PersonPictureCharacterType.Symbolic;
}
break;
case PersonPictureCharacterType.Standard:
if ((result != PersonPictureCharacterType.Glyph) && (result != PersonPictureCharacterType.Symbolic))
{
result = PersonPictureCharacterType.Standard;
}
break;
default:
break;
}
}
}
return result;
@@ -6,6 +6,7 @@
<MergeResourceInclude Source="../ContentDialog/ContentDialog.axaml" />
<MergeResourceInclude Source="../SettingsExpander/SettingsExpander.axaml" />
<MergeResourceInclude Source="../SettingsExpander/SettingsExpanderItem.axaml" />
<MergeResourceInclude Source="../PersonPicture/PersonPicture.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Styles.Resources>