diff --git a/Toolkit.UI.Avalonia/AttachedBehavior.cs b/Toolkit.UI.Avalonia/AttachedBehavior.cs new file mode 100644 index 0000000..eae5e7c --- /dev/null +++ b/Toolkit.UI.Avalonia/AttachedBehavior.cs @@ -0,0 +1,12 @@ +using Avalonia.Xaml.Interactivity; + +namespace Toolkit.UI.Avalonia; + +public class AttachedBehavior : Trigger +{ + protected override void OnAttachedToVisualTree() + { + Interaction.ExecuteActions(AssociatedObject, Actions, null); + base.OnAttachedToVisualTree(); + } +} diff --git a/Toolkit.UI.Avalonia/ComparisonCondition.cs b/Toolkit.UI.Avalonia/ComparisonCondition.cs new file mode 100644 index 0000000..6d112be --- /dev/null +++ b/Toolkit.UI.Avalonia/ComparisonCondition.cs @@ -0,0 +1,38 @@ +using Avalonia; +using Avalonia.Xaml.Interactivity; + +namespace Toolkit.UI.Avalonia; +public class ComparisonCondition : + AvaloniaObject, + ICondition +{ + public static readonly StyledProperty LeftOperandProperty = + AvaloniaProperty.Register(nameof(LeftOperand)); + + public static readonly StyledProperty OperatorProperty = + AvaloniaProperty.Register(nameof(Operator)); + + public static readonly StyledProperty RightOperandProperty = + AvaloniaProperty.Register(nameof(RightOperand)); + + public object LeftOperand + { + get => GetValue(LeftOperandProperty); + set => SetValue(LeftOperandProperty, value); + } + + public object RightOperand + { + get => GetValue(RightOperandProperty); + set => SetValue(RightOperandProperty, value); + } + + public ComparisonConditionType Operator + { + get => GetValue(OperatorProperty); + set => SetValue(OperatorProperty, value); + } + + public bool Evaluate() => ComparisonLogic.Evaluate(LeftOperand, + Operator, RightOperand); +} diff --git a/Toolkit.UI.Avalonia/ComparisonLogic.cs b/Toolkit.UI.Avalonia/ComparisonLogic.cs new file mode 100644 index 0000000..96415a3 --- /dev/null +++ b/Toolkit.UI.Avalonia/ComparisonLogic.cs @@ -0,0 +1,96 @@ +using Avalonia.Xaml.Interactivity; +using System.ComponentModel; +using System.Globalization; + +namespace Toolkit.UI.Avalonia; + +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; + } +} diff --git a/Toolkit.UI.Avalonia/ConditionAction.cs b/Toolkit.UI.Avalonia/ConditionAction.cs new file mode 100644 index 0000000..29a25a1 --- /dev/null +++ b/Toolkit.UI.Avalonia/ConditionAction.cs @@ -0,0 +1,39 @@ +using Avalonia; +using Avalonia.Metadata; +using Avalonia.Xaml.Interactivity; + +namespace Toolkit.UI.Avalonia; + +public class ConditionAction : + AvaloniaObject, + IAction +{ + public static readonly DirectProperty ActionsProperty = + AvaloniaProperty.RegisterDirect(nameof(Actions), + x => x.Actions); + + public static readonly StyledProperty ConditionProperty = + AvaloniaProperty.Register(nameof(Condition)); + + private ActionCollection? actions; + + [Content] + public ActionCollection Actions => actions ??= []; + + public ICondition Condition + { + get => GetValue(ConditionProperty); + set => SetValue(ConditionProperty, value); + } + + public object? Execute(object? sender, object? parameter) + { + bool? result = Condition?.Evaluate(); + if (result is true) + { + Interaction.ExecuteActions(sender, Actions, parameter); + } + + return true; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/ConditionCollection.cs b/Toolkit.UI.Avalonia/ConditionCollection.cs new file mode 100644 index 0000000..36f0c69 --- /dev/null +++ b/Toolkit.UI.Avalonia/ConditionCollection.cs @@ -0,0 +1,8 @@ +using System.Collections.ObjectModel; + +namespace Toolkit.UI.Avalonia; + +public class ConditionCollection : + ObservableCollection +{ +} diff --git a/Toolkit.UI.Avalonia/ConditionalExpression.cs b/Toolkit.UI.Avalonia/ConditionalExpression.cs new file mode 100644 index 0000000..8b6d28d --- /dev/null +++ b/Toolkit.UI.Avalonia/ConditionalExpression.cs @@ -0,0 +1,50 @@ +using Avalonia; +using Avalonia.Metadata; +using Toolkit.UI.Avalonia; + +namespace Toolkit.UI.Avalonia; + +public class ConditionalExpression : + AvaloniaObject, + ICondition +{ + public static readonly StyledProperty ConditionsProperty = + AvaloniaProperty.Register(nameof(Conditions)); + + public static readonly StyledProperty ForwardChainingProperty = + AvaloniaProperty.Register(nameof(ForwardChaining)); + + public ConditionalExpression() => + SetValue(ConditionsProperty, []); + + [Content] + public ConditionCollection Conditions => + GetValue(ConditionsProperty); + + public ForwardChaining ForwardChaining + { + get => GetValue(ForwardChainingProperty); + set => SetValue(ForwardChainingProperty, value); + } + + public bool Evaluate() + { + bool result = false; + foreach (ComparisonCondition operation in this.Conditions) + { + result = operation.Evaluate(); + + if (result == false && ForwardChaining == ForwardChaining.And) + { + return result; + } + + if (result == true && ForwardChaining == ForwardChaining.Or) + { + return result; + } + } + + return result; + } +} diff --git a/Toolkit.UI.Avalonia/ForwardChaining.cs b/Toolkit.UI.Avalonia/ForwardChaining.cs new file mode 100644 index 0000000..7dac9ee --- /dev/null +++ b/Toolkit.UI.Avalonia/ForwardChaining.cs @@ -0,0 +1,7 @@ +namespace Toolkit.UI.Avalonia; + +public enum ForwardChaining +{ + And, + Or +} diff --git a/Toolkit.UI.Avalonia/ICondition.cs b/Toolkit.UI.Avalonia/ICondition.cs new file mode 100644 index 0000000..5f95e0b --- /dev/null +++ b/Toolkit.UI.Avalonia/ICondition.cs @@ -0,0 +1,6 @@ +namespace Toolkit.UI.Avalonia; + +public interface ICondition +{ + bool Evaluate(); +} diff --git a/Toolkit.UI.Avalonia/NavigateAction.cs b/Toolkit.UI.Avalonia/NavigateAction.cs new file mode 100644 index 0000000..1ff15ef --- /dev/null +++ b/Toolkit.UI.Avalonia/NavigateAction.cs @@ -0,0 +1,82 @@ +using Avalonia; +using Avalonia.Controls.Primitives; +using Avalonia.Metadata; +using Avalonia.Xaml.Interactivity; +using Toolkit.Foundation; + +namespace Toolkit.UI.Avalonia; + +public class NavigateAction : + AvaloniaObject, + IAction +{ + public static readonly StyledProperty ContextProperty = + AvaloniaProperty.Register(nameof(Context)); + + public static readonly DirectProperty ParameterBindingsProperty = + AvaloniaProperty.RegisterDirect(nameof(ParameterBindings), + x => x.ParameterBindings); + + public static readonly StyledProperty ParametersProperty = + AvaloniaProperty.Register(nameof(Parameters)); + + public static readonly StyledProperty RouteProperty = + AvaloniaProperty.Register(nameof(Route)); + + public static readonly StyledProperty ScopeProperty = + AvaloniaProperty.Register(nameof(Scope)); + + private ParameterBindingCollection parameterCollection = []; + + public event EventHandler? Navigated; + + public object Context + { + get => GetValue(ContextProperty); + set => SetValue(ContextProperty, value); + } + + [Content] + public ParameterBindingCollection ParameterBindings => + parameterCollection ??= []; + + public object[]? Parameters + { + get => GetValue(ParametersProperty); + set => SetValue(ParametersProperty, value); + } + + public string Route + { + get => GetValue(RouteProperty); + set => SetValue(RouteProperty, value); + } + public string Scope + { + get => GetValue(ScopeProperty); + set => SetValue(ScopeProperty, value); + } + + public object Execute(object? sender, + object? parameter) + { + if (sender is TemplatedControl control) + { + Dictionary arguments = + new(StringComparer.InvariantCultureIgnoreCase); + + if (control.DataContext is IObservableViewModel observableViewModel) + { + object[] parameters = [.. Parameters ?? Enumerable.Empty(), .. + ParameterBindings is { Count: > 0 } ? + ParameterBindings.Select(binding => new KeyValuePair(binding.Key, binding.Value)).ToArray() : + Enumerable.Empty>()]; + + observableViewModel.Publisher.Publish(new Navigate(Route, Context + ?? null, Scope ?? null, control.DataContext, Navigated, parameters)); + } + } + + return true; + } +} diff --git a/Toolkit.UI.Avalonia/NavigateBackAction.cs b/Toolkit.UI.Avalonia/NavigateBackAction.cs new file mode 100644 index 0000000..93d66db --- /dev/null +++ b/Toolkit.UI.Avalonia/NavigateBackAction.cs @@ -0,0 +1,44 @@ +using Avalonia; +using Avalonia.Controls.Primitives; +using Avalonia.Xaml.Interactivity; +using Toolkit.Foundation; + +namespace Toolkit.UI.Avalonia; + +public class NavigateBackAction : + AvaloniaObject, + IAction +{ + public static readonly StyledProperty ContextProperty = + AvaloniaProperty.Register(nameof(Context)); + + public static readonly StyledProperty ScopeProperty = + AvaloniaProperty.Register(nameof(Scope)); + + public string Context + { + get => GetValue(ContextProperty); + set => SetValue(ContextProperty, value); + } + + public string Scope + { + get => GetValue(ScopeProperty); + set => SetValue(ScopeProperty, value); + } + + public object Execute(object? sender, + object? parameter) + { + if (sender is TemplatedControl control) + { + if (control.DataContext is IObservableViewModel observableViewModel) + { + observableViewModel.Publisher.Publish(new NavigateBack(Context + ?? null, Scope ?? null)).GetAwaiter().GetResult(); + } + } + + return true; + } +} diff --git a/Toolkit.UI.Avalonia/ParameterBinding.cs b/Toolkit.UI.Avalonia/ParameterBinding.cs new file mode 100644 index 0000000..abb7803 --- /dev/null +++ b/Toolkit.UI.Avalonia/ParameterBinding.cs @@ -0,0 +1,24 @@ +using Avalonia; + +namespace Toolkit.UI.Avalonia; + +public class ParameterBinding : + AvaloniaObject +{ + public static readonly StyledProperty KeyProperty = + AvaloniaProperty.Register(nameof(Key)); + + public static readonly StyledProperty ValueProperty = + AvaloniaProperty.Register(nameof(Value)); + + public string Key + { + get => GetValue(KeyProperty); + set => SetValue(KeyProperty, value); + } + public object Value + { + get => GetValue(ValueProperty); + set => SetValue(ValueProperty, value); + } +} diff --git a/Toolkit.UI.Avalonia/ParameterBindingCollection.cs b/Toolkit.UI.Avalonia/ParameterBindingCollection.cs new file mode 100644 index 0000000..734b597 --- /dev/null +++ b/Toolkit.UI.Avalonia/ParameterBindingCollection.cs @@ -0,0 +1,6 @@ +using System.Collections.ObjectModel; + +namespace Toolkit.UI.Avalonia; + +public class ParameterBindingCollection : + ObservableCollection; \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/Toolkit.UI.Avalonia.csproj b/Toolkit.UI.Avalonia/Toolkit.UI.Avalonia.csproj new file mode 100644 index 0000000..e80fef1 --- /dev/null +++ b/Toolkit.UI.Avalonia/Toolkit.UI.Avalonia.csproj @@ -0,0 +1,14 @@ + + + net8.0 + enable + enable + + + + + + + + + \ No newline at end of file diff --git a/Toolkit.sln b/Toolkit.sln index 6086fe2..b1fd4c4 100644 --- a/Toolkit.sln +++ b/Toolkit.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.4.33110.190 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Toolkit.Foundation", "Toolkit.Foundation\Toolkit.Foundation.csproj", "{66968F8D-689E-49D8-9370-DFF099C56202}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Toolkit.UI.Avalonia", "Toolkit.UI.Avalonia\Toolkit.UI.Avalonia.csproj", "{E091FA94-2F15-403A-98D1-4557C2FF9A02}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {66968F8D-689E-49D8-9370-DFF099C56202}.Debug|Any CPU.Build.0 = Debug|Any CPU {66968F8D-689E-49D8-9370-DFF099C56202}.Release|Any CPU.ActiveCfg = Release|Any CPU {66968F8D-689E-49D8-9370-DFF099C56202}.Release|Any CPU.Build.0 = Release|Any CPU + {E091FA94-2F15-403A-98D1-4557C2FF9A02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E091FA94-2F15-403A-98D1-4557C2FF9A02}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E091FA94-2F15-403A-98D1-4557C2FF9A02}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E091FA94-2F15-403A-98D1-4557C2FF9A02}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE