From 911ed375b4b48ce80db0f32024789959f895877f Mon Sep 17 00:00:00 2001 From: Dan Clark Date: Sat, 23 Nov 2024 21:41:59 +0000 Subject: [PATCH] wip --- .../AsyncHandlerInitialization.cs | 4 +- .../AsyncHandlerKeyedInitialization.cs | 13 +-- .../ForwardChaining.cs | 2 +- .../ICondition.cs | 2 +- Toolkit.Foundation/IDisposableApplication.cs | 6 ++ Toolkit.Foundation/IMessengerExtensions.cs | 16 +++ Toolkit.Foundation/ObservableCollection.cs | 8 +- Toolkit.UI.Avalonia/ComparisonCondition.cs | 3 +- Toolkit.UI.Avalonia/ConditionAction.cs | 1 + Toolkit.UI.Avalonia/ConditionalExpression.cs | 1 + Toolkit.UI.WinUI/ComparisonCondition.cs | 46 ++++++++ Toolkit.UI.WinUI/ComparisonLogic.cs | 100 ++++++++++++++++++ Toolkit.UI.WinUI/ConditionAction.cs | 42 ++++++++ Toolkit.UI.WinUI/ConditionCollection.cs | 8 ++ Toolkit.UI.WinUI/ConditionalExpression.cs | 56 ++++++++++ Toolkit.WinUI/IWindowRegistry.cs | 3 +- Toolkit.WinUI/WindowRegistry.cs | 18 +++- 17 files changed, 311 insertions(+), 18 deletions(-) rename {Toolkit.UI.Avalonia => Toolkit.Foundation}/ForwardChaining.cs (58%) rename {Toolkit.UI.Avalonia => Toolkit.Foundation}/ICondition.cs (60%) create mode 100644 Toolkit.Foundation/IDisposableApplication.cs create mode 100644 Toolkit.UI.WinUI/ComparisonCondition.cs create mode 100644 Toolkit.UI.WinUI/ComparisonLogic.cs create mode 100644 Toolkit.UI.WinUI/ConditionAction.cs create mode 100644 Toolkit.UI.WinUI/ConditionCollection.cs create mode 100644 Toolkit.UI.WinUI/ConditionalExpression.cs diff --git a/Toolkit.Foundation/AsyncHandlerInitialization.cs b/Toolkit.Foundation/AsyncHandlerInitialization.cs index 0b1f2e6..04ddf2e 100644 --- a/Toolkit.Foundation/AsyncHandlerInitialization.cs +++ b/Toolkit.Foundation/AsyncHandlerInitialization.cs @@ -9,7 +9,7 @@ public class AsyncHandlerInitialization(IServiceP { public void Initialize() { - if (!StrongReferenceMessenger.Default.IsRegistered>(provider)) + if (!StrongReferenceMessenger.Default.IsRegistered>(provider)) { StrongReferenceMessenger.Default.Register>(provider, (provider, args) => @@ -29,7 +29,7 @@ public class AsyncHandlerInitialization(IServiceProvider pro { public void Initialize() { - if (!StrongReferenceMessenger.Default.IsRegistered(provider)) + if (!StrongReferenceMessenger.Default.IsRegistered>(provider)) { StrongReferenceMessenger.Default.Register>(provider, (provider, args) => diff --git a/Toolkit.Foundation/AsyncHandlerKeyedInitialization.cs b/Toolkit.Foundation/AsyncHandlerKeyedInitialization.cs index 0233722..27926c5 100644 --- a/Toolkit.Foundation/AsyncHandlerKeyedInitialization.cs +++ b/Toolkit.Foundation/AsyncHandlerKeyedInitialization.cs @@ -9,14 +9,15 @@ public class AsyncHandlerKeyedInitialization(string key, ISe { public void Initialize() { - if (!StrongReferenceMessenger.Default.IsRegistered(provider, key)) + if (!StrongReferenceMessenger.Default.IsRegistered, string>(provider, key)) { - StrongReferenceMessenger.Default.Register(provider, key, + StrongReferenceMessenger.Default.Register, string>(provider, key, (provider, args) => { foreach (IAsyncHandler handler in provider.GetKeyedServices>(key)) { - handler.Handle(args); + handler.Handle(args.Message, args.CancellationToken); + args.Reply(Unit.Value); } }); } @@ -29,14 +30,14 @@ public class AsyncHandlerKeyedInitialization(stri { public void Initialize() { - if (!StrongReferenceMessenger.Default.IsRegistered, string>(provider, key)) + if (!StrongReferenceMessenger.Default.IsRegistered, string>(provider, key)) { - StrongReferenceMessenger.Default.Register, string>(provider, key, + StrongReferenceMessenger.Default.Register, string>(provider, key, (provider, args) => { foreach (IAsyncHandler handler in provider.GetKeyedServices>(key)) { - handler.Handle(args.Message); + args.Reply(handler.Handle(args.Message, args.CancellationToken)); } }); } diff --git a/Toolkit.UI.Avalonia/ForwardChaining.cs b/Toolkit.Foundation/ForwardChaining.cs similarity index 58% rename from Toolkit.UI.Avalonia/ForwardChaining.cs rename to Toolkit.Foundation/ForwardChaining.cs index d716d8b..3af2d5b 100644 --- a/Toolkit.UI.Avalonia/ForwardChaining.cs +++ b/Toolkit.Foundation/ForwardChaining.cs @@ -1,4 +1,4 @@ -namespace Toolkit.UI.Avalonia; +namespace Toolkit.Foundation; public enum ForwardChaining { diff --git a/Toolkit.UI.Avalonia/ICondition.cs b/Toolkit.Foundation/ICondition.cs similarity index 60% rename from Toolkit.UI.Avalonia/ICondition.cs rename to Toolkit.Foundation/ICondition.cs index f64733a..14d5ab8 100644 --- a/Toolkit.UI.Avalonia/ICondition.cs +++ b/Toolkit.Foundation/ICondition.cs @@ -1,4 +1,4 @@ -namespace Toolkit.UI.Avalonia; +namespace Toolkit.Foundation; public interface ICondition { diff --git a/Toolkit.Foundation/IDisposableApplication.cs b/Toolkit.Foundation/IDisposableApplication.cs new file mode 100644 index 0000000..b3a5b8c --- /dev/null +++ b/Toolkit.Foundation/IDisposableApplication.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IDisposableApplication +{ + void Dispose(); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IMessengerExtensions.cs b/Toolkit.Foundation/IMessengerExtensions.cs index f1d8554..0d83532 100644 --- a/Toolkit.Foundation/IMessengerExtensions.cs +++ b/Toolkit.Foundation/IMessengerExtensions.cs @@ -31,15 +31,31 @@ public static class IMessengerExtensions where TMessage : class, new() => await messenger.Send(new AsyncResponseEventArgs { Message = new TMessage() }); + public static async Task SendAsync(this IMessenger messenger, + string key) where TMessage : class, new() => + await messenger.Send(new AsyncResponseEventArgs { Message = new TMessage() }, key); + public static async Task SendAsync(this IMessenger messenger, TMessage message) where TMessage : class => await messenger.Send(new AsyncResponseEventArgs { Message = message }); + public static async Task SendAsync(this IMessenger messenger, + TMessage message, string key) where TMessage : class => + await messenger.Send(new AsyncResponseEventArgs { Message = message }, key); + public static async Task SendAsync(this IMessenger messenger) where TMessage : class, new() => await messenger.Send(new AsyncResponseEventArgs { Message = new TMessage() }); + public static async Task SendAsync(this IMessenger messenger, + string key) where TMessage : class, new() => + await messenger.Send(new AsyncResponseEventArgs { Message = new TMessage() }, key); + public static async Task SendAsync(this IMessenger messenger, TMessage message) where TMessage : class => await messenger.Send(new AsyncResponseEventArgs { Message = message }); + + public static async Task SendAsync(this IMessenger messenger, + TMessage message, string key) where TMessage : class => + await messenger.Send(new AsyncResponseEventArgs { Message = message }, key); } diff --git a/Toolkit.Foundation/ObservableCollection.cs b/Toolkit.Foundation/ObservableCollection.cs index 8de841d..fb4d382 100644 --- a/Toolkit.Foundation/ObservableCollection.cs +++ b/Toolkit.Foundation/ObservableCollection.cs @@ -485,10 +485,10 @@ public abstract partial class ObservableCollection : protected override sealed void OnDeactivated() { - Messenger.UnregisterAll(this); - Dispose(); - + Messenger.UnregisterAll(this); Deactivated(); + + Dispose(); } protected virtual void Activated() @@ -525,8 +525,6 @@ public abstract partial class ObservableCollection : { newSelection.IsSelected = true; } - - Messenger.Send(Selection.As(SelectedItem)); } private void SourceCollectionChanged(object? sender, diff --git a/Toolkit.UI.Avalonia/ComparisonCondition.cs b/Toolkit.UI.Avalonia/ComparisonCondition.cs index 83e271c..6fe3b5b 100644 --- a/Toolkit.UI.Avalonia/ComparisonCondition.cs +++ b/Toolkit.UI.Avalonia/ComparisonCondition.cs @@ -1,5 +1,6 @@ using Avalonia; using Avalonia.Xaml.Interactivity; +using Toolkit.Foundation; namespace Toolkit.UI.Avalonia; @@ -35,5 +36,5 @@ public class ComparisonCondition : } public bool Evaluate() => ComparisonLogic.Evaluate(LeftOperand, - Operator, RightOperand); + Operator, RightOperand); } \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/ConditionAction.cs b/Toolkit.UI.Avalonia/ConditionAction.cs index 36519ed..bf05bc1 100644 --- a/Toolkit.UI.Avalonia/ConditionAction.cs +++ b/Toolkit.UI.Avalonia/ConditionAction.cs @@ -1,6 +1,7 @@ using Avalonia; using Avalonia.Metadata; using Avalonia.Xaml.Interactivity; +using Toolkit.Foundation; namespace Toolkit.UI.Avalonia; diff --git a/Toolkit.UI.Avalonia/ConditionalExpression.cs b/Toolkit.UI.Avalonia/ConditionalExpression.cs index 14c35ab..fb2df2b 100644 --- a/Toolkit.UI.Avalonia/ConditionalExpression.cs +++ b/Toolkit.UI.Avalonia/ConditionalExpression.cs @@ -1,5 +1,6 @@ using Avalonia; using Avalonia.Metadata; +using Toolkit.Foundation; namespace Toolkit.UI.Avalonia; diff --git a/Toolkit.UI.WinUI/ComparisonCondition.cs b/Toolkit.UI.WinUI/ComparisonCondition.cs new file mode 100644 index 0000000..773146a --- /dev/null +++ b/Toolkit.UI.WinUI/ComparisonCondition.cs @@ -0,0 +1,46 @@ +using Microsoft.UI.Xaml; +using Microsoft.Xaml.Interactions.Core; +using Toolkit.Foundation; + +namespace Toolkit.UI.WinUI; + +public class ComparisonCondition : + DependencyObject, + ICondition +{ + public static readonly DependencyProperty LeftOperandProperty = + DependencyProperty.Register(nameof(LeftOperand), + typeof(object), typeof(ComparisonCondition), + new PropertyMetadata(null)); + + public static readonly DependencyProperty OperatorProperty = + DependencyProperty.Register(nameof(Operator), + typeof(ComparisonConditionType), typeof(ComparisonCondition), + new PropertyMetadata(null)); + + public static readonly DependencyProperty RightOperandProperty = + DependencyProperty.Register(nameof(RightOperand), + typeof(object), typeof(ComparisonCondition), + new PropertyMetadata(null)); + + public object LeftOperand + { + get => GetValue(LeftOperandProperty); + set => SetValue(LeftOperandProperty, value); + } + + public object RightOperand + { + get => GetValue(RightOperandProperty); + set => SetValue(RightOperandProperty, value); + } + + public ComparisonConditionType Operator + { + get => (ComparisonConditionType)GetValue(OperatorProperty); + set => SetValue(OperatorProperty, value); + } + + public bool Evaluate() => + ComparisonLogic.Evaluate(LeftOperand, Operator, RightOperand); +} \ No newline at end of file diff --git a/Toolkit.UI.WinUI/ComparisonLogic.cs b/Toolkit.UI.WinUI/ComparisonLogic.cs new file mode 100644 index 0000000..ceeb98c --- /dev/null +++ b/Toolkit.UI.WinUI/ComparisonLogic.cs @@ -0,0 +1,100 @@ +using Microsoft.Xaml.Interactions.Core; +using System; +using System.ComponentModel; +using System.Globalization; + +namespace Toolkit.UI.WinUI; + +internal static class ComparisonLogic +{ + internal static bool Evaluate(object leftOperand, + ComparisonConditionType operatorType, + object? rightOperand) + { + bool result = false; + + if (leftOperand != null) + { + Type leftType = leftOperand.GetType(); + + if (rightOperand != null) + { + TypeConverter typeConverter = TypeDescriptor.GetConverter(leftType); + rightOperand = typeConverter.ConvertFrom(rightOperand); + } + } + + if (leftOperand is IComparable leftComparableOperand && + rightOperand is IComparable rightComparableOperand) + { + return EvaluateComparable(leftComparableOperand, operatorType, rightComparableOperand); + } + + switch (operatorType) + { + case ComparisonConditionType.Equal: + result = Equals(leftOperand, rightOperand); + break; + + case ComparisonConditionType.NotEqual: + result = !Equals(leftOperand, rightOperand); + break; + } + return result; + } + + private static bool EvaluateComparable(IComparable leftOperand, + ComparisonConditionType operatorType, + IComparable rightOperand) + { + object? convertedOperand = null; + + try + { + convertedOperand = Convert.ChangeType(rightOperand, leftOperand.GetType(), CultureInfo.CurrentCulture); + } + catch (FormatException) + { + } + catch (InvalidCastException) + { + } + + if (convertedOperand == null) + { + return operatorType == ComparisonConditionType.NotEqual; + } + + int comparison = leftOperand.CompareTo((IComparable)convertedOperand); + bool result = false; + + switch (operatorType) + { + case ComparisonConditionType.Equal: + result = comparison == 0; + break; + + case ComparisonConditionType.GreaterThan: + result = comparison > 0; + break; + + case ComparisonConditionType.GreaterThanOrEqual: + result = comparison >= 0; + break; + + case ComparisonConditionType.LessThan: + result = comparison < 0; + break; + + case ComparisonConditionType.LessThanOrEqual: + result = comparison <= 0; + break; + + case ComparisonConditionType.NotEqual: + result = comparison != 0; + break; + } + + return result; + } +} \ No newline at end of file diff --git a/Toolkit.UI.WinUI/ConditionAction.cs b/Toolkit.UI.WinUI/ConditionAction.cs new file mode 100644 index 0000000..4cb590f --- /dev/null +++ b/Toolkit.UI.WinUI/ConditionAction.cs @@ -0,0 +1,42 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Markup; +using Microsoft.Xaml.Interactivity; +using Toolkit.Foundation; + +namespace Toolkit.UI.WinUI; + +[ContentProperty(Name = nameof(Actions))] +public class ConditionAction : + DependencyObject, + IAction +{ + public static readonly DependencyProperty ActionsProperty = + DependencyProperty.Register(nameof(Actions), + typeof(ActionCollection), typeof(ConditionAction), + new PropertyMetadata(null)); + + public static readonly DependencyProperty ConditionProperty = + DependencyProperty.Register(nameof(Condition), + typeof(ICondition), typeof(ConditionAction), + new PropertyMetadata(null)); + + private ActionCollection? actions; + + public ActionCollection Actions => actions ??= []; + + public ICondition? Condition + { + get => (ICondition?)GetValue(ConditionProperty); + set => SetValue(ConditionProperty, value); + } + + public object? Execute(object? sender, object? parameter) + { + if (Condition?.Evaluate() == true) + { + Interaction.ExecuteActions(sender, Actions, parameter); + } + + return true; + } +} \ No newline at end of file diff --git a/Toolkit.UI.WinUI/ConditionCollection.cs b/Toolkit.UI.WinUI/ConditionCollection.cs new file mode 100644 index 0000000..e7da33d --- /dev/null +++ b/Toolkit.UI.WinUI/ConditionCollection.cs @@ -0,0 +1,8 @@ +using System.Collections.ObjectModel; + +namespace Toolkit.UI.WinUI; + +public class ConditionCollection : + ObservableCollection +{ +} \ No newline at end of file diff --git a/Toolkit.UI.WinUI/ConditionalExpression.cs b/Toolkit.UI.WinUI/ConditionalExpression.cs new file mode 100644 index 0000000..609a786 --- /dev/null +++ b/Toolkit.UI.WinUI/ConditionalExpression.cs @@ -0,0 +1,56 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Markup; +using Toolkit.Foundation; + +namespace Toolkit.UI.WinUI; + +[ContentProperty(Name = nameof(Conditions))] +public class ConditionalExpression : + DependencyObject, + ICondition +{ + public static readonly DependencyProperty ConditionsProperty = + DependencyProperty.Register(nameof(Conditions), + typeof(ConditionCollection), typeof(ConditionalExpression), + new PropertyMetadata(new ConditionCollection())); + + public static readonly DependencyProperty ForwardChainingProperty = + DependencyProperty.Register(nameof(ForwardChaining), + typeof(ForwardChaining), typeof(ConditionalExpression), + new PropertyMetadata(ForwardChaining.And)); + + public ConditionalExpression() + { + SetValue(ConditionsProperty, new ConditionCollection()); + } + + public ConditionCollection Conditions => + (ConditionCollection)GetValue(ConditionsProperty); + + public ForwardChaining ForwardChaining + { + get => (ForwardChaining)GetValue(ForwardChainingProperty); + set => SetValue(ForwardChainingProperty, value); + } + + public bool Evaluate() + { + bool result = false; + foreach (var operation in Conditions) + { + result = operation.Evaluate(); + + if (!result && ForwardChaining == ForwardChaining.And) + { + return false; + } + + if (result && ForwardChaining == ForwardChaining.Or) + { + return true; + } + } + + return result; + } +} \ No newline at end of file diff --git a/Toolkit.WinUI/IWindowRegistry.cs b/Toolkit.WinUI/IWindowRegistry.cs index f39afb1..4a07e4d 100644 --- a/Toolkit.WinUI/IWindowRegistry.cs +++ b/Toolkit.WinUI/IWindowRegistry.cs @@ -3,7 +3,8 @@ using System.Diagnostics.CodeAnalysis; namespace Toolkit.WinUI; -public interface IWindowRegistry +public interface IWindowRegistry : + IDisposable { void Add(Window window); diff --git a/Toolkit.WinUI/WindowRegistry.cs b/Toolkit.WinUI/WindowRegistry.cs index c779c03..e22c0a8 100644 --- a/Toolkit.WinUI/WindowRegistry.cs +++ b/Toolkit.WinUI/WindowRegistry.cs @@ -1,9 +1,11 @@ using Microsoft.UI.Xaml; using System.Diagnostics.CodeAnalysis; +using System.Reactive.Disposables; +using Toolkit.Foundation; namespace Toolkit.WinUI; -public class WindowRegistry : +public class WindowRegistry(IDisposer disposer) : IWindowRegistry { private readonly List windows = []; @@ -19,13 +21,27 @@ public class WindowRegistry : } windows.Add(window); + + disposer.Add(this, Disposable.Create(() => + { + windows.Remove(window); + window.Close(); + })); + window.Closed += OnWindowClosed; } } + public void Dispose() + { + disposer.Dispose(this); + GC.SuppressFinalize(this); + } + public bool TryGet([DisallowNull] out TWindow? window) where TWindow : Window { + window = windows.OfType().FirstOrDefault() ?? null; return window is not null; }