diff --git a/Toolkit.Avalonia/AvaloniaDispatcher.cs b/Toolkit.Avalonia/AvaloniaDispatcher.cs new file mode 100644 index 0000000..1f190e6 --- /dev/null +++ b/Toolkit.Avalonia/AvaloniaDispatcher.cs @@ -0,0 +1,10 @@ +using Avalonia.Threading; +using IDispatcher = Toolkit.Foundation.IDispatcher; + +namespace Toolkit.Avalonia; + +public class AvaloniaDispatcher : + IDispatcher +{ + public async Task Invoke(Action action) => await Dispatcher.UIThread.InvokeAsync(action); +} \ No newline at end of file diff --git a/Toolkit.Avalonia/ClassicDesktopStyleApplicationHandler.cs b/Toolkit.Avalonia/ClassicDesktopStyleApplicationHandler.cs new file mode 100644 index 0000000..a2bd6d4 --- /dev/null +++ b/Toolkit.Avalonia/ClassicDesktopStyleApplicationHandler.cs @@ -0,0 +1,25 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Toolkit.Foundation; + +namespace Toolkit.Avalonia; + +public class ClassicDesktopStyleApplicationHandler : + INotificationHandler> +{ + public Task Handle(NavigateEventArgs args) + { + if (Application.Current?.ApplicationLifetime is + IClassicDesktopStyleApplicationLifetime lifeTime) + { + if (args.Template is Window window) + { + lifeTime.MainWindow = window; + window.DataContext = args.Content; + } + } + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Toolkit.Avalonia/ClipboardWriter.cs b/Toolkit.Avalonia/ClipboardWriter.cs new file mode 100644 index 0000000..ec5a220 --- /dev/null +++ b/Toolkit.Avalonia/ClipboardWriter.cs @@ -0,0 +1,23 @@ +using Avalonia.Controls; +using Avalonia.Input.Platform; +using Toolkit.Foundation; + +namespace Toolkit.Avalonia; + +public class ClipboardWriter(ITopLevelProvider topLevelProvider) : + IClipboardWriter +{ + public async Task Write(TContent content) + { + if (topLevelProvider.Get() is TopLevel topLevel) + { + if (topLevel.Clipboard is IClipboard clipboard) + { + if (content is string stringContent) + { + await clipboard.SetTextAsync(stringContent); + } + } + } + } +} \ No newline at end of file diff --git a/Toolkit.Avalonia/ContentControlHandler.cs b/Toolkit.Avalonia/ContentControlHandler.cs new file mode 100644 index 0000000..b14046f --- /dev/null +++ b/Toolkit.Avalonia/ContentControlHandler.cs @@ -0,0 +1,60 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Toolkit.Foundation; + +namespace Toolkit.Avalonia; + +public class ContentControlHandler : + INotificationHandler> +{ + public async Task Handle(NavigateEventArgs args) + { + if (args.Region is ContentControl contentControl) + { + if (args.Template is Control control) + { + TaskCompletionSource taskCompletionSource = new(); + async void HandleLoaded(object? sender, RoutedEventArgs args) + { + control.Loaded -= HandleLoaded; + if (control.DataContext is object content) + { + if (content is IActivated activated) + { + await activated.OnActivated(); + } + } + + taskCompletionSource.SetResult(); + } + + async void HandleUnloaded(object? sender, RoutedEventArgs args) + { + control.Unloaded -= HandleLoaded; + if (control.DataContext is object content) + { + if (content is IDeactivated deactivated) + { + await deactivated.OnDeactivated(); + } + + if (content is IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + control.Loaded += HandleLoaded; + control.Unloaded += HandleUnloaded; + + control.DataContext = args.Content; + + contentControl.Content = null; + contentControl.Content = control; + + await taskCompletionSource.Task; + } + } + } +} \ No newline at end of file diff --git a/Toolkit.Avalonia/ContentDialogHandler.cs b/Toolkit.Avalonia/ContentDialogHandler.cs new file mode 100644 index 0000000..20d5b40 --- /dev/null +++ b/Toolkit.Avalonia/ContentDialogHandler.cs @@ -0,0 +1,130 @@ +using FluentAvalonia.Core; +using Toolkit.Foundation; +using Toolkit.UI.Controls.Avalonia; + +namespace Toolkit.Avalonia; + +public class ContentDialogHandler : + INotificationHandler> +{ + public async Task Handle(NavigateEventArgs args) + { + if (args.Template is ContentDialog dialog) + { + dialog.DataContext = args.Content; + + async void HandlePrimaryButtonClick(FluentAvalonia.UI.Controls.ContentDialog sender, + FluentAvalonia.UI.Controls.ContentDialogButtonClickEventArgs args) + { + dialog.PrimaryButtonClick -= HandlePrimaryButtonClick; + if (dialog.DataContext is object content) + { + if (content is IPrimaryConfirmation primaryConfirmation) + { + Deferral deferral = args.GetDeferral(); + if (!await primaryConfirmation.ConfirmPrimary()) + { + args.Cancel = true; + dialog.PrimaryButtonClick += HandlePrimaryButtonClick; + } + + deferral.Complete(); + } + } + } + + async void HandleSecondaryButtonClick(FluentAvalonia.UI.Controls.ContentDialog sender, + FluentAvalonia.UI.Controls.ContentDialogButtonClickEventArgs args) + { + dialog.SecondaryButtonClick -= HandleSecondaryButtonClick; + if (dialog.DataContext is object content) + { + if (content is ISecondaryConfirmation secondaryConfirmation) + { + Deferral deferral = args.GetDeferral(); + if (!await secondaryConfirmation.ConfirmSecondary()) + { + args.Cancel = true; + dialog.SecondaryButtonClick += HandleSecondaryButtonClick; + } + + deferral.Complete(); + } + } + } + + async void HandleClosing(FluentAvalonia.UI.Controls.ContentDialog sender, + FluentAvalonia.UI.Controls.ContentDialogClosingEventArgs args) + { + if (args.Result is FluentAvalonia.UI.Controls.ContentDialogResult.Primary || + args.Result is FluentAvalonia.UI.Controls.ContentDialogResult.Secondary) + { + dialog.Closing -= HandleClosing; + if (dialog.DataContext is object content) + { + bool cancelled = false; + if (content is IConfirmation confirmation) + { + Deferral deferral = args.GetDeferral(); + if (!await confirmation.Confirm()) + { + args.Cancel = true; + cancelled = true; + + dialog.Closing += HandleClosing; + } + + deferral.Complete(); + } + + if (!cancelled) + { + if (content is IDeactivating deactivating) + { + await deactivating.OnDeactivating(); + } + } + } + } + } + + async void HandleOpened(FluentAvalonia.UI.Controls.ContentDialog sender, + EventArgs args) + { + dialog.Opened -= HandleOpened; + if (dialog.DataContext is object content) + { + if (content is IActivated activated) + { + await activated.OnActivated(); + } + } + } + + async void HandleClosed(FluentAvalonia.UI.Controls.ContentDialog sender, + FluentAvalonia.UI.Controls.ContentDialogClosedEventArgs args) + { + dialog.Closed -= HandleClosed; + if (dialog.DataContext is object content) + { + if (content is IDeactivated deactivated) + { + await deactivated.OnDeactivated(); + } + } + } + + dialog.Opened += HandleOpened; + dialog.Closing += HandleClosing; + dialog.Closed += HandleClosed; + + dialog.PrimaryButtonClick += HandlePrimaryButtonClick; + dialog.SecondaryButtonClick += HandleSecondaryButtonClick; + + await dialog.ShowAsync(); + + dialog.PrimaryButtonClick += HandlePrimaryButtonClick; + dialog.SecondaryButtonClick += HandleSecondaryButtonClick; + } + } +} \ No newline at end of file diff --git a/Toolkit.Avalonia/ContentTemplate.cs b/Toolkit.Avalonia/ContentTemplate.cs new file mode 100644 index 0000000..0a39bcc --- /dev/null +++ b/Toolkit.Avalonia/ContentTemplate.cs @@ -0,0 +1,137 @@ +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using Avalonia.Data; +using Avalonia.Interactivity; +using Microsoft.Extensions.DependencyInjection; +using Toolkit.Foundation; +using Toolkit.UI.Controls.Avalonia; + +namespace Toolkit.Avalonia; + +public class ContentTemplate : + IContentTemplate, + IDataTemplate, + IItemContainerTemplateSelector +{ + public Control? Build(object? item) + { + if (item is IObservableViewModel observableViewModel) + { + if (observableViewModel.Provider is IServiceProvider provider) + { + Type itemType = item.GetType(); + if (provider.GetRequiredKeyedService(itemType.Name.Replace("ViewModel", "")) + is IContentTemplateDescriptor descriptor) + { + if (provider.GetRequiredKeyedService(descriptor.TemplateType, descriptor.Key) is Control control) + { + async void HandleLoaded(object? sender, RoutedEventArgs args) + { + control.Loaded -= HandleLoaded; + if (control.DataContext is object content) + { + if (content is IActivated activated) + { + await activated.OnActivated(); + } + } + } + + async void HandleDataContextChanged(object? sender, EventArgs args) + { + if (control.DataContext is object content) + { + if (content is IActivated activated) + { + await activated.OnActivated(); + } + } + } + + async void HandleUnloaded(object? sender, RoutedEventArgs args) + { + control.Unloaded -= HandleUnloaded; + if (control.DataContext is object content) + { + if (content is IDeactivated deactivated) + { + await deactivated.OnDeactivated(); + } + } + } + + control.Loaded += HandleLoaded; + control.Unloaded += HandleUnloaded; + control.DataContextChanged += HandleDataContextChanged; ; + + return control; + } + } + } + } + + return default; + } + + public bool Match(object? data) => true; + + public IDataTemplate? SelectTemplate(object? item, ItemsControl itemsControl) + { + if (item is IObservableViewModel observableViewModel) + { + if (observableViewModel.Provider is IServiceProvider provider) + { + Type itemType = item.GetType(); + if (provider.GetRequiredKeyedService(itemType.Name.Replace("ViewModel", "")) + is IContentTemplateDescriptor descriptor) + { + if (provider.GetRequiredKeyedService(descriptor.TemplateType, descriptor.Key) is Control control) + { + async void HandleLoaded(object? sender, RoutedEventArgs args) + { + control.Loaded -= HandleLoaded; + if (control.DataContext is object content) + { + if (content is IActivated activated) + { + await activated.OnActivated(); + } + } + } + + async void HandleDataContextChanged(object? sender, EventArgs args) + { + if (control.DataContext is object content) + { + if (content is IActivated activated) + { + await activated.OnActivated(); + } + } + } + + async void HandleUnloaded(object? sender, RoutedEventArgs args) + { + control.Unloaded -= HandleUnloaded; + if (control.DataContext is object content) + { + if (content is IDeactivated deactivated) + { + await deactivated.OnDeactivated(); + } + } + } + + control.Loaded += HandleLoaded; + control.Unloaded += HandleUnloaded; + control.DataContextChanged += HandleDataContextChanged; ; + + return new FuncDataTemplate(item.GetType(), (_, _) => control); + } + } + } + } + + return default; + } +} \ No newline at end of file diff --git a/Toolkit.Avalonia/FileProvider.cs b/Toolkit.Avalonia/FileProvider.cs new file mode 100644 index 0000000..a3be506 --- /dev/null +++ b/Toolkit.Avalonia/FileProvider.cs @@ -0,0 +1,31 @@ +using Avalonia.Controls; +using Avalonia.Platform.Storage; +using Toolkit.Foundation; + +namespace Toolkit.Avalonia; + +public class FileProvider(ITopLevelProvider topLevelProvider) : + IFileProvider +{ + public async Task> SelectFiles(FileFilter filter) + { + if (topLevelProvider.Get() is TopLevel topLevel) + { + IReadOnlyList files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions() + { + AllowMultiple = filter.AllowMultiple, + FileTypeFilter = + [ + new(filter.Name) + { + Patterns = filter.Extensions is { Count: > 0 } ? filter.Extensions.Select(x => $"*.{x}").ToList() : ["*.*"] + } + ] + }); + + return files.Select(x => x.Path.LocalPath).ToList(); + } + + return []; + } +} \ No newline at end of file diff --git a/Toolkit.Avalonia/FolderProvider.cs b/Toolkit.Avalonia/FolderProvider.cs new file mode 100644 index 0000000..0f6b234 --- /dev/null +++ b/Toolkit.Avalonia/FolderProvider.cs @@ -0,0 +1,25 @@ +using Avalonia.Controls; +using Avalonia.Platform.Storage; +using Toolkit.Foundation; + +namespace Toolkit.Avalonia; + +public class FolderProvider(ITopLevelProvider topLevelProvider) : + IFolderProvider +{ + public async Task> SelectFolders(FolderFilter filter) + { + if (topLevelProvider.Get() is TopLevel topLevel) + { + IReadOnlyList folders = await topLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions() + { + AllowMultiple = filter.AllowMultiple + }); + + + return folders.Select(x => x.Path.LocalPath).ToList(); + } + + return []; + } +} diff --git a/Toolkit.Avalonia/FrameHandler.cs b/Toolkit.Avalonia/FrameHandler.cs new file mode 100644 index 0000000..147a146 --- /dev/null +++ b/Toolkit.Avalonia/FrameHandler.cs @@ -0,0 +1,222 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using FluentAvalonia.UI.Media.Animation; +using FluentAvalonia.UI.Navigation; +using Toolkit.Foundation; +using Toolkit.UI.Controls.Avalonia; + +namespace Toolkit.Avalonia; + +public class FrameHandler(ITransientNavigationStore navigationStore) : + INotificationHandler>, + INotificationHandler> +{ + public Task Handle(NavigateEventArgs args) + { + if (args.Region is Frame frame) + { + frame.NavigationPageFactory ??= new NavigationPageFactory(); + if (args.Template is Control control) + { + void Navigated(Control sender) + { + async void HandleNavigatedTo(object? _, NavigationEventArgs __) + { + async void HandleNavigatingFrom(object? _, NavigatingCancelEventArgs args) + { + sender.RemoveHandler(Frame.NavigatingFromEvent, HandleNavigatingFrom); + control.Unloaded -= HandleUnloaded; + + async void HandleNavigatedFrom(object? _, NavigationEventArgs args) + { + sender.RemoveHandler(Frame.NavigatedFromEvent, HandleNavigatedFrom); + if (sender.DataContext is object content) + { + if (content is IDeactivated deactivated) + { + await deactivated.OnDeactivated(); + } + + if (content is IDisposable disposable) + { + FrameNavigationOptions? options = navigationStore.Get(frame); + if (options is not FrameNavigationOptions frameOptions || + !frameOptions.IsNavigationStackEnabled) + { + disposable.Dispose(); + } + } + } + } + + sender.AddHandler(Frame.NavigatedFromEvent, HandleNavigatedFrom); + + if (sender.DataContext is object content) + { + if (content is IConfirmation confirmation && + !await confirmation.Confirm()) + { + args.Cancel = true; + } + + if (!args.Cancel) + { + if (content is IDeactivating deactivating) + { + await deactivating.OnDeactivating(); + } + } + } + } + + sender.AddHandler(Frame.NavigatingFromEvent, HandleNavigatingFrom); + if (sender.DataContext is object content) + { + if (content is IActivated activated) + { + await activated.OnActivated(); + } + } + + async void HandleUnloaded(object? _, RoutedEventArgs __) + { + sender.RemoveHandler(Frame.NavigatedToEvent, HandleNavigatedTo); + sender.RemoveHandler(Frame.NavigatingFromEvent, HandleNavigatingFrom); + + control.Unloaded -= HandleUnloaded; + + if (control.DataContext is object content) + { + if (content is IDeactivated deactivated) + { + await deactivated.OnDeactivated(); + } + + if (content is IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + sender.Unloaded += HandleUnloaded; + } + + sender.AddHandler(Frame.NavigatedToEvent, HandleNavigatedTo); + } + + control.DataContext = args.Content; + + FrameNavigationOptions navigationOptions = new(); + List postNavigateActions = []; + + void CleanUp(int start, int end) + { + int startIndex = Math.Max(0, start - 1); + int endIndex = Math.Min(frame.BackStack.Count - 1, end - 1); + + for (int i = endIndex; i >= startIndex; i--) + { + PageStackEntry? entry = frame.BackStack[i]; + if (entry.Context is Control control) + { + if (control.DataContext is IDisposable disposable) + { + disposable.Dispose(); + } + } + + frame.BackStack.RemoveAt(i); + } + } + + if (args.Parameters is not null) + { + if (args.Parameters.TryGetValue("Transition", out object? transition)) + { + switch ($"{transition}") + { + case "Suppress": + navigationOptions.TransitionInfoOverride = new SuppressNavigationTransitionInfo(); + break; + + case "FromLeft": + case "FromRight": + case "FromTop": + case "FromBottom": + navigationOptions.TransitionInfoOverride = new SlideNavigationTransitionInfo + { + Effect = Enum.Parse($"{transition}") + }; + break; + } + } + + if (args.Parameters.TryGetValue("IsBackStackEnabled", out object? isBackStackEnabled)) + { + if (isBackStackEnabled is bool value) + { + navigationOptions.IsNavigationStackEnabled = value; + } + } + + if (args.Parameters.TryGetValue("ClearBackStack", out object? clearBackStack)) + { + if (clearBackStack is bool clearBool) + { + if (clearBool) + { + postNavigateActions.Add(() => CleanUp(1, frame.BackStack.Count)); + } + } + + if (clearBackStack is string clearString) + { + if (clearString.StartsWith('[') && clearString.EndsWith(']') && clearString.Contains('-')) + { + string range = clearString.Trim('[', ']'); + string[] parts = range.Split('-'); + + if (parts.Length == 2 && int.TryParse(parts[0], out int start) && + int.TryParse(parts[1], out int end)) + { + postNavigateActions.Add(() => CleanUp(start, end)); + } + else + { + postNavigateActions.Add(() => CleanUp(1, frame.BackStack.Count)); + } + } + else + { + postNavigateActions.Add(() => CleanUp(1, frame.BackStack.Count)); + } + } + } + } + + Navigated(control); + + navigationStore.Set(frame, navigationOptions); + frame.NavigateFromObject(control, navigationOptions); + + foreach (Action postAction in postNavigateActions) + { + postAction.Invoke(); + } + } + } + + return Task.CompletedTask; + } + + public Task Handle(NavigateBackEventArgs args) + { + if (args.Context is Frame frame) + { + frame.GoBack(); + } + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Toolkit.Avalonia/IImageResizer.cs b/Toolkit.Avalonia/IImageResizer.cs new file mode 100644 index 0000000..5b8e601 --- /dev/null +++ b/Toolkit.Avalonia/IImageResizer.cs @@ -0,0 +1,11 @@ +using Avalonia.Media.Imaging; + +namespace Toolkit.Avalonia; + +public interface IImageResizer +{ + public Bitmap Resize(Stream stream, + double width, + double height, + bool maintainAspectRatio); +} \ No newline at end of file diff --git a/Toolkit.Avalonia/IServiceCollectionExtensions.cs b/Toolkit.Avalonia/IServiceCollectionExtensions.cs new file mode 100644 index 0000000..1f92754 --- /dev/null +++ b/Toolkit.Avalonia/IServiceCollectionExtensions.cs @@ -0,0 +1,83 @@ +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Toolkit.Foundation; +using Toolkit.UI.Controls.Avalonia; + +namespace Toolkit.Avalonia; + +public static class IServiceCollectionExtensions +{ + public static IServiceCollection AddAvalonia(this IServiceCollection services) + { + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + + services.AddHandler(); + services.AddHandler(); + services.AddHandler(); + + services.AddHandler(nameof(IClassicDesktopStyleApplicationLifetime)); + services.AddHandler(nameof(ISingleViewApplicationLifetime)); + services.AddHandler(nameof(ContentControl)); + + services.AddHandler(nameof(Frame)); + services.TryAddSingleton, TransientNavigationStore>(); + + services.AddHandler(nameof(ContentDialog)); + services.AddHandler(nameof(TaskDialog)); + + services.AddScoped(provider => new NavigationRegionCollection + { + { typeof(IClassicDesktopStyleApplicationLifetime), typeof(IClassicDesktopStyleApplicationLifetime) }, + { typeof(ISingleViewApplicationLifetime), typeof(ISingleViewApplicationLifetime) } + }); + + services.AddTransient((Func>)(provider => + new ProxyServiceCollection(services => + { + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + services.AddSingleton(provider.GetRequiredService()); + services.AddTransient(); + + services.AddTransient(); + + services.AddHandler(); + services.AddHandler(); + services.AddHandler(); + + services.AddHandler(nameof(ContentControl)); + + services.AddHandler(nameof(Frame)); + + services.TryAddSingleton(provider.GetRequiredService>()); + + services.AddHandler(nameof(ContentDialog)); + services.AddHandler(nameof(TaskDialog)); + }))); + + return services; + } +} \ No newline at end of file diff --git a/Toolkit.Avalonia/ITopLevelProvider.cs b/Toolkit.Avalonia/ITopLevelProvider.cs new file mode 100644 index 0000000..58a5d04 --- /dev/null +++ b/Toolkit.Avalonia/ITopLevelProvider.cs @@ -0,0 +1,8 @@ +using Avalonia.Controls; + +namespace Toolkit.Avalonia; + +public interface ITopLevelProvider +{ + TopLevel? Get(); +} \ No newline at end of file diff --git a/Toolkit.Avalonia/ITransientNavigationStore.cs b/Toolkit.Avalonia/ITransientNavigationStore.cs new file mode 100644 index 0000000..035db3c --- /dev/null +++ b/Toolkit.Avalonia/ITransientNavigationStore.cs @@ -0,0 +1,15 @@ +namespace Toolkit.Avalonia; + +public interface ITransientNavigationStore + where TControl : class +{ + void Clear(); + + T? Get(TControl control) + where T : class; + + void Remove(TControl control); + + void Set(TControl control, + object parameters); +} \ No newline at end of file diff --git a/Toolkit.Avalonia/ImageReader.cs b/Toolkit.Avalonia/ImageReader.cs new file mode 100644 index 0000000..a0c7a0f --- /dev/null +++ b/Toolkit.Avalonia/ImageReader.cs @@ -0,0 +1,29 @@ +using Avalonia.Media.Imaging; +using Toolkit.Foundation; + +namespace Toolkit.Avalonia; + +public class ImageReader(IImageResizer imageResizer) : + IImageReader +{ + public IImageDescriptor Get(Stream stream, + double width, + double height, + bool maintainAspectRatio) + { + Bitmap resizedImage = imageResizer.Resize(stream, + width, + height, + maintainAspectRatio); + + return new ImageDescriptor(resizedImage, width, height); + } + + public IImageDescriptor Get(Stream stream) + { + Bitmap image = new(stream); + return new ImageDescriptor(image, + image.Size.Width, + image.Size.Height); + } +} diff --git a/Toolkit.Avalonia/ImageResizer.cs b/Toolkit.Avalonia/ImageResizer.cs new file mode 100644 index 0000000..8c79597 --- /dev/null +++ b/Toolkit.Avalonia/ImageResizer.cs @@ -0,0 +1,79 @@ +using Avalonia.Media.Imaging; +using SkiaSharp; + +namespace Toolkit.Avalonia; + +public class ImageResizer : + IImageResizer +{ + public Bitmap Resize(Stream stream, + double width, + double height, + bool maintainAspectRatio) + { + Bitmap bitmap = new(stream); + + if (bitmap.Size.Width != width || bitmap.Size.Height != height) + { + stream.Seek(0, SeekOrigin.Begin); + using SKBitmap sKBitmap = SKBitmap.Decode(stream); + + if (height == 0 && maintainAspectRatio) + { + height = (int)((float)width / sKBitmap.Width * sKBitmap.Height); + } + + if (width == 0 && maintainAspectRatio) + { + width = (int)((float)height / sKBitmap.Height * sKBitmap.Width); + } + + float widthRatio = (float)width / sKBitmap.Width; + float heightRatio = (float)height / sKBitmap.Height; + float scale = maintainAspectRatio ? Math.Max(widthRatio, heightRatio) : Math.Min(widthRatio, heightRatio); + + int newWidth = (int)(sKBitmap.Width * scale); + int newHeight = (int)(sKBitmap.Height * scale); + + using SKBitmap resized = new(newWidth, newHeight); + using SKCanvas canvas = new(resized); + + using SKPaint paint = new() + { + FilterQuality = SKFilterQuality.High, + IsAntialias = true + }; + + canvas.Clear(SKColors.Transparent); + canvas.DrawBitmap(sKBitmap, new SKRect(0, 0, newWidth, newHeight), paint); + + SKBitmap cropped; + if (maintainAspectRatio) + { + int cropX = (int)(newWidth - width) / 2; + int cropY = (int)(newHeight - height) / 2; + + cropped = new SKBitmap((int)width, (int)height); + using SKCanvas croppedCanvas = new(cropped); + SKRect cropRect = new(cropX, cropY, (int)(cropX + width), cropY + (int)height); + croppedCanvas.Clear(SKColors.Transparent); + croppedCanvas.DrawBitmap(resized, cropRect, new SKRect(0, 0, (int)width, (int)height)); + } + else + { + cropped = resized; + } + + using SKImage image = SKImage.FromBitmap(cropped); + using MemoryStream outputStream = new(); + image.Encode(SKEncodedImageFormat.Png, 100).SaveTo(outputStream); + outputStream.Seek(0, SeekOrigin.Begin); + bitmap.Dispose(); + + return new Bitmap(outputStream); + } + + return bitmap; + } + +} \ No newline at end of file diff --git a/Toolkit.Avalonia/ImageWriter.cs b/Toolkit.Avalonia/ImageWriter.cs new file mode 100644 index 0000000..09465f1 --- /dev/null +++ b/Toolkit.Avalonia/ImageWriter.cs @@ -0,0 +1,17 @@ +using Avalonia.Media.Imaging; +using Toolkit.Foundation; + +namespace Toolkit.Avalonia; + +public class ImageWriter : + IImageWriter +{ + public void Write(IImageDescriptor imageDescriptor, + Stream stream) + { + if (imageDescriptor.Image is Bitmap bitmap) + { + bitmap.Save(stream); + } + } +} \ No newline at end of file diff --git a/Toolkit.Avalonia/NavigationPageFactory.cs b/Toolkit.Avalonia/NavigationPageFactory.cs new file mode 100644 index 0000000..ab16eb3 --- /dev/null +++ b/Toolkit.Avalonia/NavigationPageFactory.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; +using FluentAvalonia.UI.Controls; + +namespace Toolkit.Avalonia; + +public class NavigationPageFactory : + INavigationPageFactory +{ + public Control? GetPage(Type srcType) => default; + + public Control GetPageFromObject(object target) => (Control)target; +} \ No newline at end of file diff --git a/Toolkit.Avalonia/NavigationRegion.cs b/Toolkit.Avalonia/NavigationRegion.cs new file mode 100644 index 0000000..cd3c49e --- /dev/null +++ b/Toolkit.Avalonia/NavigationRegion.cs @@ -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; + } + } + } +} \ No newline at end of file diff --git a/Toolkit.Avalonia/SingleViewApplicationHandler.cs b/Toolkit.Avalonia/SingleViewApplicationHandler.cs new file mode 100644 index 0000000..0156dcf --- /dev/null +++ b/Toolkit.Avalonia/SingleViewApplicationHandler.cs @@ -0,0 +1,25 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Toolkit.Foundation; + +namespace Toolkit.Avalonia; + +public class SingleViewApplicationHandler : + INotificationHandler> +{ + public Task Handle(NavigateEventArgs args) + { + if (Application.Current?.ApplicationLifetime is + ISingleViewApplicationLifetime lifeTime) + { + if (args.Template is Control control) + { + lifeTime.MainView = control; + control.DataContext = args.Content; + } + } + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Toolkit.Avalonia/TaskDialogHandler.cs b/Toolkit.Avalonia/TaskDialogHandler.cs new file mode 100644 index 0000000..d528894 --- /dev/null +++ b/Toolkit.Avalonia/TaskDialogHandler.cs @@ -0,0 +1,61 @@ +using Avalonia.Controls; +using FluentAvalonia.Core; +using Toolkit.Foundation; +using Toolkit.UI.Controls.Avalonia; + +namespace Toolkit.Avalonia; + +public class TaskDialogHandler(ITopLevelProvider topLevelProvider) : + INotificationHandler> +{ + public async Task Handle(NavigateEventArgs args) + { + if (args.Template is TaskDialog dialog) + { + if (topLevelProvider.Get() is TopLevel topLevel) + { + dialog.XamlRoot = topLevel; + dialog.DataContext = args.Content; + + async void HandleClosing(FluentAvalonia.UI.Controls.TaskDialog sender, + FluentAvalonia.UI.Controls.TaskDialogClosingEventArgs args) + { + dialog.Closing -= HandleClosing; + if (dialog.DataContext is object content) + { + bool cancelled = false; + + if (args.Result is TaskDialogResult result) + { + if (result is TaskDialogResult.OK && content is + IPrimaryConfirmation primaryConfirmation) + { + Deferral deferral = args.GetDeferral(); + if (!await primaryConfirmation.ConfirmPrimary()) + { + args.Cancel = true; + cancelled = true; + + dialog.Closing += HandleClosing; + } + + deferral.Complete(); + } + } + + if (!cancelled) + { + if (content is IDeactivating deactivating) + { + await deactivating.OnDeactivating(); + } + } + } + } + + dialog.Closing += HandleClosing; + await dialog.ShowAsync(); + } + } + } +} diff --git a/Toolkit.Avalonia/Toolkit.Avalonia.csproj b/Toolkit.Avalonia/Toolkit.Avalonia.csproj new file mode 100644 index 0000000..718702e --- /dev/null +++ b/Toolkit.Avalonia/Toolkit.Avalonia.csproj @@ -0,0 +1,15 @@ + + + net9.0 + enable + enable + + + + + + + + + + \ No newline at end of file diff --git a/Toolkit.Avalonia/TopLevelProvider.cs b/Toolkit.Avalonia/TopLevelProvider.cs new file mode 100644 index 0000000..752a738 --- /dev/null +++ b/Toolkit.Avalonia/TopLevelProvider.cs @@ -0,0 +1,30 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; + +namespace Toolkit.Avalonia; + +public class TopLevelProvider : + ITopLevelProvider +{ + public TopLevel? Get() + { + if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime classicDesktopStyleApplication) + { + if (TopLevel.GetTopLevel(classicDesktopStyleApplication.MainWindow) is TopLevel topLevel) + { + return topLevel; + } + } + + if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime singleViewApplication) + { + if (TopLevel.GetTopLevel(singleViewApplication.MainView) is TopLevel topLevel) + { + return topLevel; + } + } + + return default; + } +} \ No newline at end of file diff --git a/Toolkit.Avalonia/TransientNavigationStore.cs b/Toolkit.Avalonia/TransientNavigationStore.cs new file mode 100644 index 0000000..456f7be --- /dev/null +++ b/Toolkit.Avalonia/TransientNavigationStore.cs @@ -0,0 +1,36 @@ +namespace Toolkit.Avalonia; + +public class TransientNavigationStore : ITransientNavigationStore + where TControl : class +{ + private readonly Dictionary> controlDataMap = []; + + public void Set(TControl control, + object parameters) + { + if (!controlDataMap.TryGetValue(control, out var typeMap)) + { + typeMap = new Dictionary(); + controlDataMap[control] = typeMap; + } + + typeMap[parameters.GetType()] = parameters; + } + + public T? Get(TControl control) + where T : class + { + if (controlDataMap.TryGetValue(control, out var typeMap) && + typeMap.TryGetValue(typeof(T), out var parameters)) + { + typeMap.Remove(typeof(T)); + return parameters as T; + } + + return default; + } + + public void Remove(TControl control) => controlDataMap.Remove(control); + + public void Clear() => controlDataMap.Clear(); +} diff --git a/Toolkit.Avalonia/UserInteraction.cs b/Toolkit.Avalonia/UserInteraction.cs new file mode 100644 index 0000000..1485c33 --- /dev/null +++ b/Toolkit.Avalonia/UserInteraction.cs @@ -0,0 +1,41 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Toolkit.Foundation; + +namespace Toolkit.Avalonia; + +public class UserInteraction(ITopLevelProvider topLevelProvider) : + IUserInteraction +{ + public event EventHandler? UserInteracted; + + private void OnPointerMoved(object? sender, + PointerEventArgs args) => UserInteracted?.Invoke(this, new UserInteractedEventArgs()); + + private void OnKeyDown(object? sender, + KeyEventArgs args) => UserInteracted?.Invoke(this, new UserInteractedEventArgs()); + + private void OnKeyUp(object? sender, + KeyEventArgs args) => UserInteracted?.Invoke(this, new UserInteractedEventArgs()); + + public void Stop() + { + if (topLevelProvider.Get() is TopLevel topLevel) + { + topLevel.RemoveHandler(InputElement.PointerMovedEvent, OnPointerMoved); + topLevel.RemoveHandler(InputElement.KeyDownEvent, OnKeyDown); + topLevel.RemoveHandler(InputElement.KeyUpEvent, OnKeyUp); + } + } + + public void Start() + { + if (topLevelProvider.Get() is TopLevel topLevel) + { + topLevel.AddHandler(InputElement.PointerMovedEvent, OnPointerMoved, RoutingStrategies.Tunnel); + topLevel.AddHandler(InputElement.KeyDownEvent, OnKeyDown, RoutingStrategies.Tunnel); + topLevel.AddHandler(InputElement.KeyUpEvent, OnKeyUp, RoutingStrategies.Tunnel); + } + } +} diff --git a/Toolkit.Foundation/Activated.cs b/Toolkit.Foundation/Activated.cs new file mode 100644 index 0000000..adfb47e --- /dev/null +++ b/Toolkit.Foundation/Activated.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation; + +public record Activated +{ + public static ActivatedEventArgs As(TSender sender) => new(sender); + + public static ActivatedEventArgs As() where TSender : new() => new(new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/ActivatedEventArgs.cs b/Toolkit.Foundation/ActivatedEventArgs.cs new file mode 100644 index 0000000..4f1a0b6 --- /dev/null +++ b/Toolkit.Foundation/ActivatedEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record ActivatedEventArgs(TSender? Sender = default); diff --git a/Toolkit.Foundation/Activation.cs b/Toolkit.Foundation/Activation.cs new file mode 100644 index 0000000..ade3931 --- /dev/null +++ b/Toolkit.Foundation/Activation.cs @@ -0,0 +1,9 @@ +namespace Toolkit.Foundation; + +public record Activation +{ + public static ActivationEventArgs As(TValue value) => new(value); + + public static ActivationEventArgs As() => + new(); +} \ No newline at end of file diff --git a/Toolkit.Foundation/ActivationBuilder.cs b/Toolkit.Foundation/ActivationBuilder.cs new file mode 100644 index 0000000..cefef3e --- /dev/null +++ b/Toolkit.Foundation/ActivationBuilder.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record ActivationBuilder(IActivation Value, object? Key = null); \ No newline at end of file diff --git a/Toolkit.Foundation/ActivationEventArgs.cs b/Toolkit.Foundation/ActivationEventArgs.cs new file mode 100644 index 0000000..a13d81d --- /dev/null +++ b/Toolkit.Foundation/ActivationEventArgs.cs @@ -0,0 +1,7 @@ +namespace Toolkit.Foundation; + +public record ActivationEventArgs(TValue? Value = default) : + IActivation; + +public record ActivationEventArgs() : + IActivation; \ No newline at end of file diff --git a/Toolkit.Foundation/AesDecryptor.cs b/Toolkit.Foundation/AesDecryptor.cs new file mode 100644 index 0000000..8851e2e --- /dev/null +++ b/Toolkit.Foundation/AesDecryptor.cs @@ -0,0 +1,50 @@ +using System.Security.Cryptography; + +namespace Toolkit.Foundation; + +public class AesDecryptor : + IDecryptor +{ + private const int IvSize = 16; + + public bool TryDecrypt(byte[] cipher, + byte[] key, + out byte[]? decryptedData) + { + decryptedData = null; + + if (cipher is null || key is null || cipher.Length < IvSize) + { + return false; + } + + try + { + Span iv = cipher.AsSpan(0, IvSize); + ReadOnlySpan encryptedContent = cipher.AsSpan(IvSize); + + using Aes aes = Aes.Create(); + aes.Key = key; + aes.IV = iv.ToArray(); + + using MemoryStream memoryStream = new(encryptedContent.ToArray()); + using ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV); + using CryptoStream cryptoStream = new(memoryStream, decryptor, CryptoStreamMode.Read); + + using MemoryStream resultStream = new(); + cryptoStream.CopyTo(resultStream); + + decryptedData = resultStream.ToArray(); + + return true; + } + catch (CryptographicException) + { + return false; + } + catch (Exception) + { + return false; + } + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/AesEncryptor.cs b/Toolkit.Foundation/AesEncryptor.cs new file mode 100644 index 0000000..c07c90f --- /dev/null +++ b/Toolkit.Foundation/AesEncryptor.cs @@ -0,0 +1,49 @@ +using System.Security.Cryptography; + +namespace Toolkit.Foundation; + +public class AesEncryptor : + IEncryptor +{ + private const int IvSize = 16; + + public bool TryEncrypt(byte[] data, + byte[] key, + out byte[]? encryptedData) + { + encryptedData = null; + + if (data is null || key is null || key.Length != 32) + { + return false; + } + + try + { + using Aes aes = Aes.Create(); + aes.Key = key; + aes.GenerateIV(); + + using MemoryStream memoryStream = new(); + memoryStream.Write(aes.IV, 0, IvSize); + + using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV)) + using (CryptoStream cryptoStream = new(memoryStream, encryptor, CryptoStreamMode.Write)) + { + cryptoStream.Write(data, 0, data.Length); + cryptoStream.FlushFinalBlock(); + } + + encryptedData = memoryStream.ToArray(); + return true; + } + catch (CryptographicException) + { + return false; + } + catch (Exception) + { + return false; + } + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/AppService.cs b/Toolkit.Foundation/AppService.cs index c318344..5efce30 100644 --- a/Toolkit.Foundation/AppService.cs +++ b/Toolkit.Foundation/AppService.cs @@ -2,18 +2,24 @@ namespace Toolkit.Foundation; -public class AppService(IEnumerable initializers, +public class AppService(IEnumerable initializations, + IEnumerable asyncInitializations, IPublisher publisher) : IHostedService { public async Task StartAsync(CancellationToken cancellationToken) { - foreach (IInitializer initializer in initializers) + foreach (IInitialization initialization in initializations) { - await initializer.Initialize(); + initialization.Initialize(); } - await publisher.Publish(cancellationToken); + foreach (IAsyncInitialization initialization in asyncInitializations) + { + await initialization.Initialize(); + } + + publisher.Publish(); } public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; diff --git a/Toolkit.Foundation/AsyncLock.cs b/Toolkit.Foundation/AsyncLock.cs index 56dc549..a8f1e3b 100644 --- a/Toolkit.Foundation/AsyncLock.cs +++ b/Toolkit.Foundation/AsyncLock.cs @@ -2,22 +2,37 @@ namespace Toolkit.Foundation; -public class AsyncLock(int initial = 1, - int maximum = 1) : +public class ActivityLock(IActivityIndicator activityIndicator) : AsyncLock +{ + public override TaskAwaiter GetAwaiter() + { + activityIndicator.IsActive = true; + return base.GetAwaiter(); + } + + public override void Dispose() + { + activityIndicator.IsActive = false; + base.Dispose(); + } +} + +public class AsyncLock(int initial = 1, + int maximum = 1) : IDisposable { private readonly SemaphoreSlim semaphore = new(initial, maximum); - public void Dispose() + public virtual void Dispose() { semaphore.Release(); } - public TaskAwaiter GetAwaiter() => LockAsync().GetAwaiter(); + public virtual TaskAwaiter GetAwaiter() => LockAsync().GetAwaiter(); private async Task LockAsync() { await semaphore.WaitAsync(); return this; } -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/Cache.cs b/Toolkit.Foundation/Cache.cs index a5c0cfd..1d16ee3 100644 --- a/Toolkit.Foundation/Cache.cs +++ b/Toolkit.Foundation/Cache.cs @@ -1,68 +1,205 @@ -using HyperX; -using System.Collections; -using System.Collections.Concurrent; -using System.Reactive.Disposables; +using System.Collections; namespace Toolkit.Foundation; -public class Cache(IDisposer disposer) : +public class Cache(IComparer comparer) : ICache { - private readonly List cache = []; + private readonly List items = []; - public void Add(TValue value) + public IEnumerable Items => + items; + + public TValue this[int index] => + items[index]; + + public void Add(TValue item) { - if (value is null) - { - return; - } - - disposer.Add(value, Disposable.Create(() => Remove(value))); - cache.Add(value); + int index = FindInsertIndex(item); + items.Insert(index, item); } - public void Clear() => cache.Clear(); + public void Clear() => + items.Clear(); - public IEnumerator GetEnumerator() => cache.GetEnumerator(); + public bool Contains(TValue item) + { + int index = items.IndexOf(item); + if (index >= 0) + { + return true; + } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + return false; + } - public bool Remove(TValue value) => cache.Remove(value); + public IEnumerator GetEnumerator() => + items.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => items.GetEnumerator(); + + public int IndexOf(TValue item) => + items.IndexOf(item); + + public bool Remove(TValue item) + { + int index = items.IndexOf(item); + if (index >= 0) + { + items.RemoveAt(index); + return true; + } + + return false; + } + + public bool TryGetValue(TValue key, out TValue? item) + { + int index = items.IndexOf(key); + if (index >= 0) + { + item = items[index]; + return true; + } + + item = default; + return false; + } + + private int FindInsertIndex(TValue item) + { + int low = 0, high = items.Count - 1; + + while (low <= high) + { + int mid = (low + high) / 2; + int cmp = comparer.Compare(items[mid], item); + + if (cmp < 0) + { + low = mid + 1; + } + else + { + high = mid - 1; + } + } + + while (low < items.Count && comparer.Compare(items[low], item) == 0) + { + low++; + } + + return low; + } } -public class Cache : +public class Cache(IComparer comparer) : ICache where TKey : notnull where TValue : notnull { - private readonly ConcurrentDictionary cache = new(); + private readonly List> items = []; - public void Add(TKey key, - TValue value) + public TValue? this[TKey key] { - cache.TryAdd(key, value); + get + { + int index = items.BinarySearch(new KeyValuePair(key, default), + new KeyValuePairComparer(comparer)); + + if (index >= 0) + { + return items[index].Value; + } + + return default; + } + set + { + int index = items.BinarySearch(new KeyValuePair(key, default), + new KeyValuePairComparer(comparer)); + + if (index >= 0) + { + items[index] = new KeyValuePair(key, value); + } + else + { + items.Insert(~index, new KeyValuePair(key, value)); + } + } } - public void Clear() => cache.Clear(); + public void Add(TKey key, TValue value) + { + int index = items.BinarySearch(new KeyValuePair(key, default), + new KeyValuePairComparer(comparer)); - public bool ContainsKey(TKey key) => cache.ContainsKey(key); + if (index < 0) + { + index = ~index; + } - public IEnumerator> GetEnumerator() => cache.GetEnumerator(); + items.Insert(index, new KeyValuePair(key, value)); + } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public void Clear() => items.Clear(); - public bool Remove(TKey key) => cache.Remove(key, out _); + public bool Contains(TKey key) + { + int index = items.FindIndex(kvp => comparer.Compare(kvp.Key, key) == 0); + if (index >= 0) + { + items.RemoveAt(index); + return true; + } + return false; + } + + public IEnumerator> GetEnumerator() => + items.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + + public int IndexOf(TKey key) => + items.FindIndex(kvp => comparer.Compare(kvp.Key, key) == 0); + + public bool Remove(TKey key) + { + int index = items.FindIndex(kvp => comparer.Compare(kvp.Key, key) == 0); + if (index >= 0) + { + items.RemoveAt(index); + return true; + } + return false; + } public bool TryGetValue(TKey key, out TValue? value) { - if (cache.TryGetValue(key, out value)) + int index = items.BinarySearch(new KeyValuePair(key, default(TValue)), + new KeyValuePairComparer(comparer)); + + if (index >= 0) { + value = items[index].Value; return true; } value = default; return false; } + + private class KeyValuePairComparer(IComparer comparer) : + IComparer> + { + private readonly IComparer comparer = comparer ?? Comparer.Default; + + public int Compare(KeyValuePair x, KeyValuePair y) => + comparer.Compare(x.Key, y.Key); + } } \ No newline at end of file diff --git a/Toolkit.Foundation/Cancel.cs b/Toolkit.Foundation/Cancel.cs new file mode 100644 index 0000000..1bfb291 --- /dev/null +++ b/Toolkit.Foundation/Cancel.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation; + +public record Cancel +{ + public static CancelEventArgs As(TSender sender) => new(sender); + + public static CancelEventArgs As() where TSender : new() => new(new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/CancelEventArgs.cs b/Toolkit.Foundation/CancelEventArgs.cs new file mode 100644 index 0000000..3f706f0 --- /dev/null +++ b/Toolkit.Foundation/CancelEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record CancelEventArgs(TSender Sender); \ No newline at end of file diff --git a/Toolkit.Foundation/Changed.cs b/Toolkit.Foundation/Changed.cs index 82fd0df..c8f538b 100644 --- a/Toolkit.Foundation/Changed.cs +++ b/Toolkit.Foundation/Changed.cs @@ -1,3 +1,8 @@ namespace Toolkit.Foundation; -public record Changed(TValue? Value = default) : INotification; \ No newline at end of file +public record Changed +{ + public static ChangedEventArgs As(TSender sender) => new(sender); + + public static ChangedEventArgs As() where TSender : new() => new(new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/ChangedEventArgs.cs b/Toolkit.Foundation/ChangedEventArgs.cs new file mode 100644 index 0000000..5e6c1c7 --- /dev/null +++ b/Toolkit.Foundation/ChangedEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record ChangedEventArgs(TSender? Sender = default); \ No newline at end of file diff --git a/Toolkit.Foundation/Clipboard.cs b/Toolkit.Foundation/Clipboard.cs new file mode 100644 index 0000000..be865e2 --- /dev/null +++ b/Toolkit.Foundation/Clipboard.cs @@ -0,0 +1,5 @@ +namespace Toolkit.Foundation; + +public record Clipboard; + +public record Clipboard(TValue Value); \ No newline at end of file diff --git a/Toolkit.Foundation/Close.cs b/Toolkit.Foundation/Close.cs new file mode 100644 index 0000000..bfa1f05 --- /dev/null +++ b/Toolkit.Foundation/Close.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation; + +public record Close +{ + public static CloseEventArgs As(TSender sender) => new(sender); + + public static CloseEventArgs As() where TSender : new() => new(new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/CloseEventArgs.cs b/Toolkit.Foundation/CloseEventArgs.cs new file mode 100644 index 0000000..1740d41 --- /dev/null +++ b/Toolkit.Foundation/CloseEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record CloseEventArgs(TSender? Sender = default); diff --git a/Toolkit.Foundation/Closed.cs b/Toolkit.Foundation/Closed.cs new file mode 100644 index 0000000..0ae6bd1 --- /dev/null +++ b/Toolkit.Foundation/Closed.cs @@ -0,0 +1,9 @@ +namespace Toolkit.Foundation; + +public record Closed +{ + public static ClosedEventArgs As(TSender sender) => new(sender); + + public static ClosedEventArgs As() where TSender : new() => new(new TSender()); +} + diff --git a/Toolkit.Foundation/ClosedEventArgs.cs b/Toolkit.Foundation/ClosedEventArgs.cs new file mode 100644 index 0000000..0b0cae6 --- /dev/null +++ b/Toolkit.Foundation/ClosedEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record ClosedEventArgs(TSender? Sender = default); diff --git a/Toolkit.Foundation/CommandValueViewModel.cs b/Toolkit.Foundation/CommandValueViewModel.cs index f2b7717..fd2e5c7 100644 --- a/Toolkit.Foundation/CommandValueViewModel.cs +++ b/Toolkit.Foundation/CommandValueViewModel.cs @@ -2,12 +2,14 @@ namespace Toolkit.Foundation; -public partial class CommandValueViewModel(IServiceProvider serviceProvider, - IServiceFactory serviceFactory, +public partial class CommandValueViewModel(IServiceProvider provider, + IServiceFactory factory, + IMediator mediator, IPublisher publisher, ISubscriber subscriber, IDisposer disposer) : - ValueViewModel(serviceProvider, serviceFactory, publisher, subscriber, disposer) + Observable(provider, factory, mediator, publisher, subscriber, disposer) + where TValue : notnull { public IRelayCommand InvokeCommand => new AsyncRelayCommand(InvokeAsync); diff --git a/Toolkit.Foundation/CommandViewModel.cs b/Toolkit.Foundation/CommandViewModel.cs index 740f078..2b384b5 100644 --- a/Toolkit.Foundation/CommandViewModel.cs +++ b/Toolkit.Foundation/CommandViewModel.cs @@ -2,16 +2,17 @@ namespace Toolkit.Foundation; -public partial class CommandViewModel(IServiceProvider serviceProvider, - IServiceFactory serviceFactory, +public partial class CommandViewModel(IServiceProvider provider, + IServiceFactory factory, + IMediator mediator, IPublisher publisher, ISubscriber subscriber, IDisposer disposer) : - ObservableViewModel(serviceProvider, serviceFactory, publisher, subscriber, disposer) + Observable(provider, factory, mediator, publisher, subscriber, disposer) { - public IRelayCommand InvokeCommand => + public IRelayCommand InvokeCommand => new AsyncRelayCommand(InvokeAsync); - protected virtual Task InvokeAsync() => + protected virtual Task InvokeAsync() => Task.CompletedTask; } \ No newline at end of file diff --git a/Toolkit.Foundation/Component.cs b/Toolkit.Foundation/Component.cs new file mode 100644 index 0000000..d5d3128 --- /dev/null +++ b/Toolkit.Foundation/Component.cs @@ -0,0 +1,60 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Toolkit.Foundation; + +public class Component : + IComponent +{ + private readonly IComponentBuilder builder; + + protected Component(IComponentBuilder builder) => + this.builder = builder; + + public static TComponent Create(IServiceProvider provider, + Action builderDelegate) + where TComponent : class, IComponent + { + IServiceFactory factory = provider.GetRequiredService(); + + IComponentBuilder builder = ComponentBuilder.Create(); + builderDelegate(builder); + + return factory.Create(builder); + } + + public IComponentBuilder Configure(Action? componentDelegate = null) + { + if (componentDelegate is not null) + { + componentDelegate(builder); + } + + return Configuring(builder); + } + + public IComponentBuilder Configure(Action>? configurationDelegate = null, + Action? componentDelegate = null) + where TConfiguration : class, new() + { + if (componentDelegate is not null) + { + componentDelegate(builder); + } + + if (configurationDelegate is not null) + { + ConfigurationBuilder configurationBuilder = new(); + + configurationDelegate(configurationBuilder); + + if (configurationBuilder.Section is { Length: > 0 } section) + { + builder.AddConfiguration(section, configurationBuilder.Configuration ?? new TConfiguration()); + } + } + + return Configuring(builder); + } + + public virtual IComponentBuilder Configuring(IComponentBuilder builder) => builder; +} \ No newline at end of file diff --git a/Toolkit.Foundation/ComponentBuilder.cs b/Toolkit.Foundation/ComponentBuilder.cs index eef2252..78791a3 100644 --- a/Toolkit.Foundation/ComponentBuilder.cs +++ b/Toolkit.Foundation/ComponentBuilder.cs @@ -4,83 +4,81 @@ using Microsoft.Extensions.Hosting; namespace Toolkit.Foundation; -public class ComponentBuilder : +public class ComponentBuilder : IComponentBuilder { private readonly IHostBuilder hostBuilder; - private bool configurationRegistered; + public string ContentRoot { get; set; } = "Local"; + + public string ConfigurationFile { get; set; } = "Settings.json"; private ComponentBuilder() { hostBuilder = new HostBuilder() - .UseContentRoot("Local", true) - .ConfigureAppConfiguration(config => - { - config.AddJsonFile("Settings.json", true, true); - }) .ConfigureServices((context, services) => { services.AddScoped(); services.AddScoped(provider => - new ServiceFactory((type, parameters) => - ActivatorUtilities.CreateInstance(provider, type, parameters!))); + new ServiceFactory((type, parameters) => ActivatorUtilities.CreateInstance(provider, type, + parameters?.Where(x => x is not null).ToArray()!))); + + services.AddSingleton(); services.AddScoped(); - services.AddScoped(); - services.AddTransient(); + services.AddTransient(); + services.AddScoped(); services.AddTransient(); - services.AddTransient(); - services.AddScoped(); - services.AddTransient(); + services.AddTransient(); + services.AddTransient(); - services.AddTransient(); - services.AddScoped(); - services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + services.AddScoped(); + services.AddTransient(); services.AddHandler(); services.AddHandler(); }); } - public static IComponentBuilder Create() => - new ComponentBuilder(); + public static IComponentBuilder Create() => new ComponentBuilder(); public IComponentBuilder AddConfiguration(Action configurationDelegate) - where TConfiguration : ComponentConfiguration, new() + where TConfiguration : + class, new() { - if (configurationRegistered) - { - return this; - } - - configurationRegistered = true; TConfiguration configuration = new(); - if (configurationDelegate is not null) { configurationDelegate(configuration); } - hostBuilder.ConfigureServices(services => - { - services.AddConfiguration(section: configuration.GetType().Name, - configuration: configuration); + AddConfiguration(typeof(TConfiguration).Name, configuration); + return this; + } - services.AddConfiguration(configuration); - }); + public IComponentBuilder AddConfiguration(string section, + TConfiguration? configuration = null) + where TConfiguration : + class, new() + { + hostBuilder.AddConfiguration(section, ConfigurationFile, configuration); return this; } - public IComponentHost Build() + public IComponentBuilder AddConfiguration(string section) + where TConfiguration : + class, new() { - IHost host = hostBuilder.Build(); - return host.Services.GetRequiredService(); + AddConfiguration(section, null); + return this; } public IComponentBuilder AddServices(Action configureDelegate) @@ -88,4 +86,17 @@ public class ComponentBuilder : hostBuilder.ConfigureServices(configureDelegate); return this; } + + public IComponentHost Build() + { + hostBuilder + .UseContentRoot(ContentRoot, true) + .ConfigureAppConfiguration(config => + { + config.AddJsonFile(ConfigurationFile, true, true); + }); + + IHost host = hostBuilder.Build(); + return host.Services.GetRequiredService(); + } } \ No newline at end of file diff --git a/Toolkit.Foundation/ComponentConfiguration.cs b/Toolkit.Foundation/ComponentConfiguration.cs deleted file mode 100644 index 72feb26..0000000 --- a/Toolkit.Foundation/ComponentConfiguration.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Toolkit.Foundation; - -public record ComponentConfiguration -{ - public string? Description { get; set; } - - public string? Name { get; set; } - - [JsonInclude] - internal Guid Id { get; set; } = Guid.NewGuid(); -} \ No newline at end of file diff --git a/Toolkit.Foundation/ComponentConfigurationViewModel.cs b/Toolkit.Foundation/ComponentConfigurationViewModel.cs deleted file mode 100644 index 4eafe1e..0000000 --- a/Toolkit.Foundation/ComponentConfigurationViewModel.cs +++ /dev/null @@ -1,111 +0,0 @@ -using CommunityToolkit.Mvvm.ComponentModel; - -namespace Toolkit.Foundation; - -public partial class ComponentConfigurationViewModel : - ValueViewModel, - IComponentConfigurationViewModel, - INotificationHandler> - where TConfiguration : class -{ - public ComponentConfigurationViewModel(IServiceProvider serviceProvider, - IServiceFactory serviceFactory, - IPublisher publisher, - ISubscriber subscriber, - IDisposer disposer, - THeader header, - TDescription description, - TAction action) : base(serviceProvider, serviceFactory, publisher, subscriber, disposer) - { - - } - - public Task Handle(Changed args, - CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } -} - -public partial class ComponentConfigurationViewModel(IServiceProvider serviceProvider, - IServiceFactory serviceFactory, - IPublisher publisher, - ISubscriber subscriber, - IDisposer disposer, - TAction action, - TConfiguration configuration, - Func valueDelegate, - object header, - object description) : - ValueViewModel(serviceProvider, serviceFactory, publisher, subscriber, disposer), - IComponentConfigurationViewModel, - INotificationHandler> - where TConfiguration : class -{ - [ObservableProperty] - private TAction action = action; - - [ObservableProperty] - private object header = header; - - [ObservableProperty] - private object description = description; - - public override Task Activated() - { - Value = valueDelegate.Invoke(configuration); - return base.Activated(); - } - public Task Handle(Changed args, - CancellationToken cancellationToken = default) - { - if (args.Value is TConfiguration configuration) - { - Value = valueDelegate.Invoke(configuration); - } - - return Task.CompletedTask; - } -} - -public partial class ComponentConfigurationViewModel(IServiceProvider serviceProvider, - IServiceFactory serviceFactory, - IPublisher publisher, - ISubscriber subscriber, - IDisposer disposer, - TAction action, - TDescription description, - TConfiguration configuration, - Func valueDelegate, - object header) : - ValueViewModel(serviceProvider, serviceFactory, publisher, subscriber, disposer), - IComponentConfigurationViewModel, - INotificationHandler> - where TConfiguration : class -{ - [ObservableProperty] - private TAction action = action; - - [ObservableProperty] - private object header = header; - - [ObservableProperty] - private TDescription description = description; - - public override Task Activated() - { - Value = valueDelegate.Invoke(configuration); - return base.Activated(); - } - - public Task Handle(Changed args, - CancellationToken cancellationToken = default) - { - if (args.Value is TConfiguration configuration) - { - Value = valueDelegate.Invoke(configuration); - } - - return Task.CompletedTask; - } -} \ No newline at end of file diff --git a/Toolkit.Foundation/ComponentFactory.cs b/Toolkit.Foundation/ComponentFactory.cs new file mode 100644 index 0000000..77221a2 --- /dev/null +++ b/Toolkit.Foundation/ComponentFactory.cs @@ -0,0 +1,67 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Toolkit.Foundation; + +public class ComponentFactory(IServiceProvider provider, + IProxyServiceCollection proxy, + IComponentScopeCollection scopes) : + IComponentFactory +{ + public IComponentHost? Create(string key, + Action>? configurationDelegate = null, + Action? componentDelegate = null, + Action? servicesDelegate = null) + where TComponent : IComponent + where TConfiguration : class, new() + { + if (provider.GetRequiredService() is TComponent component) + { + IComponentBuilder builder = component.Configure(configurationDelegate, + componentDelegate); + + builder.AddServices(services => + { + services.AddTransient(_ => + provider.GetRequiredService>()); + + services.AddTransient(_ => + provider.GetRequiredService()); + + services.AddTransient(_ => + provider.GetRequiredService>()); + + services.AddTransient(_ => + provider.GetRequiredService>()); + + services.AddScoped(_ => + provider.GetRequiredService()); + + services.AddScoped(_ => + provider.GetRequiredService()); + + services.AddScoped(_ => + provider.GetRequiredService()); + + services.AddTransient(_ => + provider.GetRequiredService()); + + services.AddRange(proxy.Services); + services.AddSingleton(new NamedComponent(key)); + + if (servicesDelegate is not null) + { + servicesDelegate(services); + } + }); + + IComponentHost host = builder.Build(); + + scopes.Add(new ComponentScopeDescriptor(key, + host.Services.GetRequiredService())); + + return host; + } + + return default; + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/ComponentHost.cs b/Toolkit.Foundation/ComponentHost.cs index f660820..9517e58 100644 --- a/Toolkit.Foundation/ComponentHost.cs +++ b/Toolkit.Foundation/ComponentHost.cs @@ -3,26 +3,34 @@ using Microsoft.Extensions.Hosting; namespace Toolkit.Foundation; -public sealed class ComponentHost(IServiceProvider services, - IEnumerable initializers, +public class ComponentHost(IServiceProvider services, + IEnumerable initializations, + IEnumerable asyncInitializations, IEnumerable hostedServices) : IComponentHost { public IServiceProvider Services => services; - public ComponentConfiguration? Configuration => - Services.GetService(); - public void Dispose() { + } + public TConfiguration? GetConfiguration() + where TConfiguration : class + { + return Services.GetService(); } public async Task StartAsync(CancellationToken cancellationToken = default) { - foreach (IInitializer initializer in initializers) + foreach (IInitialization initialization in initializations) { - await initializer.Initialize(); + initialization.Initialize(); + } + + foreach (IAsyncInitialization initialization in asyncInitializations) + { + await initialization.Initialize(); } foreach (IHostedService service in hostedServices) @@ -38,4 +46,4 @@ public sealed class ComponentHost(IServiceProvider services, await service.StopAsync(cancellationToken); } } -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/ComponentHostCollection.cs b/Toolkit.Foundation/ComponentHostCollection.cs index c55e2d7..08f2d7f 100644 --- a/Toolkit.Foundation/ComponentHostCollection.cs +++ b/Toolkit.Foundation/ComponentHostCollection.cs @@ -18,4 +18,4 @@ public class ComponentHostCollection : IEnumerator IEnumerable.GetEnumerator() => hosts.GetEnumerator(); -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/ComponentInitializer.cs b/Toolkit.Foundation/ComponentInitializer.cs index a758541..2ee2b9c 100644 --- a/Toolkit.Foundation/ComponentInitializer.cs +++ b/Toolkit.Foundation/ComponentInitializer.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; namespace Toolkit.Foundation; @@ -7,43 +8,45 @@ public class ComponentInitializer(IEnumerable components, IComponentHostCollection hosts, IComponentScopeCollection scopes, IServiceProvider provider) : - IInitializer + IInitialization { - public async Task Initialize() + public void Initialize() { foreach (IComponent component in components) { - IComponentBuilder builder = component.Create(); + IComponentBuilder builder = component.Configure(); builder.AddServices(services => { - services.AddTransient(_ => + services.AddTransient(_ => provider.GetRequiredService>()); services.AddTransient(_ => provider.GetRequiredService>()); - services.AddScoped(_ => - provider.GetRequiredService()); - - services.AddScoped(_ => - provider.GetRequiredService()); + services.AddScoped(_ => + provider.GetRequiredService()); - services.AddScoped(_ => + services.AddScoped(_ => + provider.GetRequiredService()); + + services.AddScoped(_ => provider.GetRequiredService()); - services.AddTransient(_ => + services.AddTransient(_ => provider.GetRequiredService()); services.AddRange(typedServices.Services); + + services.AddSingleton(new NamedComponent(component.GetType().Name)); }); IComponentHost host = builder.Build(); - scopes.Add(component.GetType().Name, - host.Services.GetRequiredService()); + scopes.Add(new ComponentScopeDescriptor(component.GetType().Name, + provider.GetRequiredService())); hosts.Add(host); - await host.StartAsync(); + host.Start(); } } -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/ComponentScopeCollection.cs b/Toolkit.Foundation/ComponentScopeCollection.cs index d944e5d..e9d40a2 100644 --- a/Toolkit.Foundation/ComponentScopeCollection.cs +++ b/Toolkit.Foundation/ComponentScopeCollection.cs @@ -1,4 +1,4 @@ namespace Toolkit.Foundation; -public class ComponentScopeCollection : Dictionary, - IComponentScopeCollection; +public class ComponentScopeCollection : List, + IComponentScopeCollection; \ No newline at end of file diff --git a/Toolkit.Foundation/ComponentScopeDescriptor.cs b/Toolkit.Foundation/ComponentScopeDescriptor.cs new file mode 100644 index 0000000..23380c7 --- /dev/null +++ b/Toolkit.Foundation/ComponentScopeDescriptor.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record ComponentScopeDescriptor(string Key, IServiceProvider Services); \ No newline at end of file diff --git a/Toolkit.Foundation/ComponentScopeProvider.cs b/Toolkit.Foundation/ComponentScopeProvider.cs index f0b28f4..e29b8d4 100644 --- a/Toolkit.Foundation/ComponentScopeProvider.cs +++ b/Toolkit.Foundation/ComponentScopeProvider.cs @@ -3,10 +3,9 @@ public class ComponentScopeProvider(IComponentScopeCollection scopes) : IComponentScopeProvider { - public IServiceProvider? Get(string name) + public ComponentScopeDescriptor? Get(string key) { - return scopes.TryGetValue(name, - out IServiceProvider? scope) ? scope : default; + return scopes.FirstOrDefault(x => x.Key == key) is ComponentScopeDescriptor + descriptor ? descriptor : default; } -} - +} \ No newline at end of file diff --git a/Toolkit.Foundation/Configuration.cs b/Toolkit.Foundation/Configuration.cs deleted file mode 100644 index f577181..0000000 --- a/Toolkit.Foundation/Configuration.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Toolkit.Foundation; - -public class Configuration(IConfigurationReader reader) : - IConfiguration - where TConfiguration : - class -{ - public TConfiguration Value => reader.Read(); -} diff --git a/Toolkit.Foundation/ConfigurationBuilder.cs b/Toolkit.Foundation/ConfigurationBuilder.cs new file mode 100644 index 0000000..33caad6 --- /dev/null +++ b/Toolkit.Foundation/ConfigurationBuilder.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation; + +public class ConfigurationBuilder : + IConfigurationBuilder + where TConfiguration : class +{ + public string? Section { get; set; } + + public TConfiguration? Configuration { get; set; } +} diff --git a/Toolkit.Foundation/ConfigurationCache.cs b/Toolkit.Foundation/ConfigurationCache.cs new file mode 100644 index 0000000..70f05d7 --- /dev/null +++ b/Toolkit.Foundation/ConfigurationCache.cs @@ -0,0 +1,27 @@ +using System.Collections.Concurrent; + +namespace Toolkit.Foundation; + +public class ConfigurationCache : + IConfigurationCache +{ + private readonly ConcurrentDictionary cache = new(); + + public void Set(string section, + TConfiguration configuration) => cache[section] = configuration; + + public bool Remove(string section) => cache.TryRemove(section, out _); + + public bool TryGet(string section, + out TConfiguration? configuration) + { + if (cache.TryGetValue(section, out object? cachedValue)) + { + configuration = (TConfiguration?)cachedValue; + return true; + } + + configuration = default; + return false; + } +} diff --git a/Toolkit.Foundation/ConfigurationChangedHandler.cs b/Toolkit.Foundation/ConfigurationChangedHandler.cs index de6d8cf..1c343bc 100644 --- a/Toolkit.Foundation/ConfigurationChangedHandler.cs +++ b/Toolkit.Foundation/ConfigurationChangedHandler.cs @@ -1,18 +1,16 @@ namespace Toolkit.Foundation; public class ConfigurationChangedHandler(ConfigurationValue configurationValue) : - INotificationHandler> + INotificationHandler> where TValue : class, new() { - public Task Handle(Changed args, - CancellationToken cancellationToken = default) + public Task Handle(ChangedEventArgs args) { - if (args.Value is TConfiguration configuration) + if (args.Sender is TConfiguration configuration) { if (configurationValue.TryUpdate(configuration, out TValue value)) { - } } diff --git a/Toolkit.Foundation/ConfigurationDescriptor.cs b/Toolkit.Foundation/ConfigurationDescriptor.cs new file mode 100644 index 0000000..e815b66 --- /dev/null +++ b/Toolkit.Foundation/ConfigurationDescriptor.cs @@ -0,0 +1,14 @@ +namespace Toolkit.Foundation; + +public class ConfigurationDescriptor(string section, + IConfigurationReader reader) : + IConfigurationDescriptor + where TConfiguration : + class +{ + public TConfiguration Value => reader.Read(); + + public string Section => section; + + public string Name => section.Split(':').LastOrDefault() ?? section; +} \ No newline at end of file diff --git a/Toolkit.Foundation/ConfigurationFactory.cs b/Toolkit.Foundation/ConfigurationFactory.cs index d41debd..cbf981b 100644 --- a/Toolkit.Foundation/ConfigurationFactory.cs +++ b/Toolkit.Foundation/ConfigurationFactory.cs @@ -1,9 +1,9 @@ namespace Toolkit.Foundation; public class ConfigurationFactory(Func factory) : - IConfigurationFactory + IConfigurationFactory where TConfiguration : class { public object Create() => factory.Invoke(); -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/ConfigurationFile.cs b/Toolkit.Foundation/ConfigurationFile.cs index 50fcd4c..c0c19d4 100644 --- a/Toolkit.Foundation/ConfigurationFile.cs +++ b/Toolkit.Foundation/ConfigurationFile.cs @@ -2,10 +2,10 @@ namespace Toolkit.Foundation; -public class ConfigurationFile(IFileInfo fileInfo) : +public class ConfigurationFile(IFileInfo fileInfo) : IConfigurationFile where TConfiguration : class { public IFileInfo FileInfo => fileInfo; -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/ConfigurationInitializer.cs b/Toolkit.Foundation/ConfigurationInitializer.cs index 29977cb..c9389f8 100644 --- a/Toolkit.Foundation/ConfigurationInitializer.cs +++ b/Toolkit.Foundation/ConfigurationInitializer.cs @@ -1,15 +1,15 @@ namespace Toolkit.Foundation; -public class ConfigurationInitializer(IPublisher publisher, - IConfigurationReader reader, +public class ConfigurationInitializer(IConfigurationReader reader, IConfigurationWriter writer, - IConfigurationFactory factory) : + IConfigurationFactory factory, + IPublisher publisher) : IConfigurationInitializer, - IInitializer + IInitialization where TConfiguration : class { - public async Task Initialize() + public void Initialize() { if (!reader.TryRead(out TConfiguration? configuration)) { @@ -20,6 +20,6 @@ public class ConfigurationInitializer(IPublisher publisher, } } - await publisher.PublishUI(new Changed(configuration)); + publisher.PublishUI(new ActivatedEventArgs(configuration)); } -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/ConfigurationLock.cs b/Toolkit.Foundation/ConfigurationLock.cs new file mode 100644 index 0000000..20929e5 --- /dev/null +++ b/Toolkit.Foundation/ConfigurationLock.cs @@ -0,0 +1,36 @@ +namespace Toolkit.Foundation; + +public static class ConfigurationLock +{ + private static readonly ReaderWriterLockSlim readerWriterLock = new(); + + public static IDisposable EnterRead() + { + readerWriterLock.EnterReadLock(); + return new ConfigurationReaderLockDisposer(readerWriterLock); + } + + public static IDisposable EnterWrite() + { + readerWriterLock.EnterWriteLock(); + return new ConfigurationWriterLockDisposer(readerWriterLock); + } + + private class ConfigurationWriterLockDisposer(ReaderWriterLockSlim lockSlim) : + IDisposable + { + public void Dispose() + { + lockSlim.ExitWriteLock(); + } + } + + private class ConfigurationReaderLockDisposer(ReaderWriterLockSlim lockSlim) : + IDisposable + { + public void Dispose() + { + lockSlim.ExitReadLock(); + } + } +} diff --git a/Toolkit.Foundation/ConfigurationMonitor.cs b/Toolkit.Foundation/ConfigurationMonitor.cs index cfe6228..ccbb85b 100644 --- a/Toolkit.Foundation/ConfigurationMonitor.cs +++ b/Toolkit.Foundation/ConfigurationMonitor.cs @@ -1,7 +1,11 @@ -namespace Toolkit.Foundation; +using Microsoft.Extensions.DependencyInjection; -public class ConfigurationMonitor(IConfigurationFile file, - IConfigurationReader reader, +namespace Toolkit.Foundation; + +public class ConfigurationMonitor(string section, + IConfigurationCache cache, + IConfigurationFile file, + IServiceProvider serviceProvider, IPublisher publisher) : IConfigurationMonitor where TConfiguration : @@ -11,12 +15,14 @@ public class ConfigurationMonitor(IConfigurationFile>(section) is + IConfigurationDescriptor configuration) { - await publisher.PublishUI(new Changed(configuration)); + cache.Remove(section); + publisher.PublishUI(new ChangedEventArgs(configuration.Value)); } } @@ -43,4 +49,4 @@ public class ConfigurationMonitor(IConfigurationFile(IConfigurationSource(IConfigurationFile configurationFile, string section, + IConfigurationCache cache, JsonSerializerOptions? serializerOptions = null) : IConfigurationSource where TConfiguration : class { - private readonly object lockingObject = new(); - - private static readonly Func defaultSerializerOptions = new(() => + private static readonly Func defaultSerializerOptions = () => { return new JsonSerializerOptions { WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, Converters = { new JsonStringEnumConverter(), - new DictionaryStringObjectJsonConverter() + new DictionaryStringObjectJsonConverter(), + new JsonArrayTupleConverter() } }; - }); + }; public void Set(TConfiguration value) => Set((object)value); public void Set(object value) { - lock (lockingObject) + using (ConfigurationLock.EnterWrite()) { IFileInfo fileInfo = configurationFile.FileInfo; + EnsureFileExists(fileInfo.PhysicalPath); - if (!File.Exists(fileInfo.PhysicalPath)) + string? content; + JsonNode? rootNode; + + using (Stream stream = new FileStream(fileInfo.PhysicalPath!, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) + using (StreamReader reader = new(stream)) { - string? fileDirectoryPath = Path.GetDirectoryName(fileInfo.PhysicalPath); - if (!string.IsNullOrEmpty(fileDirectoryPath)) - { - Directory.CreateDirectory(fileDirectoryPath); - } + content = reader.ReadToEnd(); + stream.Seek(0, SeekOrigin.Begin); - File.WriteAllText(fileInfo.PhysicalPath!, "{}"); + rootNode = JsonNode.Parse(content); } - static Stream OpenReadWrite(IFileInfo fileInfo) - { - return fileInfo.PhysicalPath is not null - ? new FileStream(fileInfo.PhysicalPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite) - : fileInfo.CreateReadStream(); - } + JsonNode? valueNode = JsonNode.Parse(JsonSerializer.SerializeToUtf8Bytes(value, serializerOptions ?? defaultSerializerOptions())); - using Stream stream = OpenReadWrite(fileInfo); - using StreamReader? reader = new(stream); + ApplyConfigurationUpdates(ref rootNode, valueNode, section); - string? content = reader.ReadToEnd(); - using JsonDocument jsonDocument = JsonDocument.Parse(content); - - using Stream stream2 = OpenReadWrite(fileInfo); - Utf8JsonWriter writer = new(stream2, new JsonWriterOptions() - { - Indented = true, - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping - }); - - writer.WriteStartObject(); - bool isWritten = false; - - JsonDocument optionsElement = JsonDocument.Parse(JsonSerializer.SerializeToUtf8Bytes(value, - serializerOptions ?? defaultSerializerOptions())); - - foreach (JsonProperty element in jsonDocument.RootElement.EnumerateObject()) - { - if (element.Name != section) - { - element.WriteTo(writer); - continue; - } - writer.WritePropertyName(element.Name); - optionsElement.WriteTo(writer); - isWritten = true; - } - - if (!isWritten) - { - writer.WritePropertyName(section); - optionsElement.WriteTo(writer); - } - - writer.WriteEndObject(); - writer.Flush(); - stream2.SetLength(stream2.Position); + using Stream stream2 = new FileStream(fileInfo.PhysicalPath!, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite); + JsonSerializer.Serialize(stream2, rootNode, serializerOptions ?? defaultSerializerOptions()); + + cache.Set(section, value); } } public bool TryGet(out TConfiguration? value) { - lock (lockingObject) + if (cache.TryGet(section, out value)) + { + return true; + } + + using (ConfigurationLock.EnterRead()) { IFileInfo fileInfo = configurationFile.FileInfo; - if (File.Exists(fileInfo.PhysicalPath)) + if (!File.Exists(fileInfo.PhysicalPath)) { - static Stream OpenRead(IFileInfo fileInfo) + value = default; + return false; + } + + using Stream stream = new FileStream(fileInfo.PhysicalPath!, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); + using StreamReader reader = new(stream); + string content = reader.ReadToEnd(); + JsonNode? rootNode = JsonNode.Parse(content); + + string[] segments = section.Split(':'); + JsonNode? currentNode = rootNode; + + int lastIndex = segments.Length - 1; + for (int i = 0; i < lastIndex; i++) + { + if (currentNode is null) { - return fileInfo.PhysicalPath is not null - ? new FileStream(fileInfo.PhysicalPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite) - : fileInfo.CreateReadStream(); + value = default; + return false; } - using Stream stream = OpenRead(fileInfo); - using StreamReader? reader = new(stream); + currentNode = currentNode[segments[i]]; + } - string? content = reader.ReadToEnd(); - - using JsonDocument jsonDocument = JsonDocument.Parse(content); - if (jsonDocument.RootElement.TryGetProperty(section, out JsonElement sectionValue)) - { - value = JsonSerializer.Deserialize(sectionValue.ToString(), serializerOptions ?? defaultSerializerOptions()); - return true; - } + if (currentNode != null && currentNode[segments[lastIndex]] is JsonNode sectionNode) + { + value = JsonSerializer.Deserialize(sectionNode, serializerOptions ?? defaultSerializerOptions()); + cache.Set(section, value); + return true; } value = default; return false; } } -} \ No newline at end of file + + private void ApplyConfigurationUpdates(ref JsonNode? rootNode, JsonNode? valueNode, string section) + { + string[] segments = section.Split(':'); + JsonNode? currentNode = rootNode; + int lastIndex = segments.Length - 1; + + for (int i = 0; i < lastIndex; i++) + { + if (currentNode is null) + { + return; + } + + string currentKey = segments[i]; + if (currentNode[currentKey] is null) + { + currentNode[currentKey] = new JsonObject(); + } + + currentNode = currentNode[currentKey]; + } + + if (currentNode is not null) + { + string lastKey = segments[lastIndex]; + if (valueNode is JsonArray) + { + currentNode[lastKey] = valueNode; + } + else + { + currentNode[lastKey] = MergeNodes(currentNode[lastKey], valueNode); + } + } + } + + private JsonNode? MergeNodes(JsonNode? existingNode, JsonNode? newNode) + { + if (existingNode is JsonObject existingObject && newNode is JsonObject newObject) + { + foreach (KeyValuePair property in newObject) + { + existingObject[property.Key] = MergeNodes(existingObject[property.Key], CloneNode(property.Value)); + } + return existingObject; + } + + return newNode; + } + + private JsonNode? CloneNode(JsonNode? node) + { + if (node is null) + { + return null; + } + + string serialized = JsonSerializer.Serialize(node, serializerOptions ?? defaultSerializerOptions()); + return JsonNode.Parse(serialized); + } + + + private void EnsureFileExists(string? filePath) + { + if (filePath == null || File.Exists(filePath)) + { + return; + } + + string? directoryPath = Path.GetDirectoryName(filePath); + if (!string.IsNullOrEmpty(directoryPath)) + { + Directory.CreateDirectory(directoryPath); + } + + File.WriteAllText(filePath, "{}"); + } +} diff --git a/Toolkit.Foundation/ConfigurationValue.cs b/Toolkit.Foundation/ConfigurationValue.cs index 4efe3ab..2eff43f 100644 --- a/Toolkit.Foundation/ConfigurationValue.cs +++ b/Toolkit.Foundation/ConfigurationValue.cs @@ -1,12 +1,12 @@ namespace Toolkit.Foundation; public class ConfigurationValue(Func> changed) - where TValue : + where TValue : class, new() { private TValue? currentValue; - public bool TryUpdate(TConfiguration configuration, + public bool TryUpdate(TConfiguration configuration, out TValue value) { TValue newValue = new(); diff --git a/Toolkit.Foundation/ConfigurationValueViewModel.cs b/Toolkit.Foundation/ConfigurationValueViewModel.cs new file mode 100644 index 0000000..d95ef04 --- /dev/null +++ b/Toolkit.Foundation/ConfigurationValueViewModel.cs @@ -0,0 +1,118 @@ + +namespace Toolkit.Foundation; + +public partial class ConfigurationValueViewModel(IServiceProvider provider, + IServiceFactory factory, + IMediator mediator, + IPublisher publisher, + ISubscriber subscriber, + IDisposer disposer, + TConfiguration configuration, + IWritableConfiguration writer, + Func read, + Action write) : + Observable(provider, factory, mediator, publisher, subscriber, disposer), + INotificationHandler> + where TConfiguration : class +{ + public async Task Handle(ChangedEventArgs args) + { + if (args.Sender is TConfiguration configuration) + { + // await Task.Run(() => Value = read(configuration)); + } + } + + public override async Task OnActivated() + { + await Task.Run(() => Value = read(configuration)); + await base.OnActivated(); + } + + protected override async void OnChanged(TValue? value) + { + if (IsActivated) + { + await Task.Run(() => writer.Write(args => write(value, args))); + } + + base.OnChanged(value); + } +} + +public partial class ConfigurationValueViewModel : + ObservableCollection, + INotificationHandler> + where TConfiguration : class + where TItem : notnull, + IDisposable +{ + private readonly TConfiguration configuration; + private readonly Func read; + private readonly Action write; + private readonly IWritableConfiguration writer; + public ConfigurationValueViewModel(IServiceProvider provider, + IServiceFactory factory, + IMediator mediator, + IPublisher publisher, + ISubscriber subscriber, + IDisposer disposer, + TConfiguration configuration, + IWritableConfiguration writer, + Func read, + Action write, + TValue? value = default) : base(provider, factory, mediator, publisher, subscriber, disposer, value) + { + this.configuration = configuration; + this.writer = writer; + this.read = read; + this.write = write; + + Value = value; + } + + public ConfigurationValueViewModel(IServiceProvider provider, + IServiceFactory factory, + IMediator mediator, + IPublisher publisher, + ISubscriber subscriber, + IDisposer disposer, + IEnumerable items, + TConfiguration configuration, + IWritableConfiguration writer, + Func read, + Action write, + TValue? value = default) : base(provider, factory, mediator, publisher, subscriber, disposer, items, value) + { + this.configuration = configuration; + this.writer = writer; + this.read = read; + this.write = write; + + Value = value; + } + + public async Task Handle(ChangedEventArgs args) + { + if (args.Sender is TConfiguration configuration) + { + // await Task.Run(() => Value = read(configuration)); + } + } + + public override async Task OnActivated() + { + await Task.Run(() => Value = read(configuration)); + await base.OnActivated(); + } + + protected override async void OnChanged(TValue? value) + { + if (IsActivated) + { + await Task.Run(() => writer.Write(args => write(value, args))); + } + + base.OnChanged(value); + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/ConfigurationWriter.cs b/Toolkit.Foundation/ConfigurationWriter.cs index 4c9d277..ba6ec78 100644 --- a/Toolkit.Foundation/ConfigurationWriter.cs +++ b/Toolkit.Foundation/ConfigurationWriter.cs @@ -1,19 +1,22 @@ namespace Toolkit.Foundation; -public class ConfigurationWriter(IConfigurationSource source) : +public class ConfigurationWriter(IConfigurationSource source, + IConfigurationFactory factory) : IConfigurationWriter where TConfiguration : class { public void Write(Action updateDelegate) { - if (source.TryGet(out TConfiguration? value)) + if (!source.TryGet(out TConfiguration? value)) { - if (value is not null) - { - updateDelegate?.Invoke(value); - Write(value); - } + value = (TConfiguration)factory.Create(); + } + + if (value is not null) + { + updateDelegate?.Invoke(value); + Write(value); } } diff --git a/Toolkit.Foundation/Confirm.cs b/Toolkit.Foundation/Confirm.cs new file mode 100644 index 0000000..2839cf4 --- /dev/null +++ b/Toolkit.Foundation/Confirm.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation; + +public record Confirm +{ + public static ConfirmEventArgs As(TValue value) => + new(value); + + public static ConfirmEventArgs As() where TValue : new() => + new(new TValue()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/ConfirmEventArgs.cs b/Toolkit.Foundation/ConfirmEventArgs.cs new file mode 100644 index 0000000..16d4dc2 --- /dev/null +++ b/Toolkit.Foundation/ConfirmEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record ConfirmEventArgs(TValue Value); \ No newline at end of file diff --git a/Toolkit.Foundation/ContentFactory.cs b/Toolkit.Foundation/ContentFactory.cs new file mode 100644 index 0000000..1b9d231 --- /dev/null +++ b/Toolkit.Foundation/ContentFactory.cs @@ -0,0 +1,28 @@ +namespace Toolkit.Foundation; + +public class ContentFactory(IServiceProvider provider, + IServiceFactory factory) : + IContentFactory +{ + public object? Create(IContentTemplateDescriptor descriptor, + object[] parameters) + { + object? content = parameters is { Length: > 0 } + ? factory.Create(descriptor.ContentType, args => + { + if (args is IInitialization initialization) + { + initialization.Initialize(); + } + }, parameters) + : provider.GetRequiredKeyedService(descriptor.ContentType, args => + { + if (args is IInitialization initialization) + { + initialization.Initialize(); + } + }, descriptor.Key); + + return content; + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/ContentTemplateDescriptor.cs b/Toolkit.Foundation/ContentTemplateDescriptor.cs index 427d71e..565fd18 100644 --- a/Toolkit.Foundation/ContentTemplateDescriptor.cs +++ b/Toolkit.Foundation/ContentTemplateDescriptor.cs @@ -1,7 +1,7 @@ namespace Toolkit.Foundation; public class ContentTemplateDescriptor(object key, - Type viewModelType, + Type viewModelType, Type viewType, params object[]? parameters) : IContentTemplateDescriptor diff --git a/Toolkit.Foundation/ContentTemplateDescriptorProvider.cs b/Toolkit.Foundation/ContentTemplateDescriptorProvider.cs deleted file mode 100644 index a4f1404..0000000 --- a/Toolkit.Foundation/ContentTemplateDescriptorProvider.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Xml.Linq; - -namespace Toolkit.Foundation; - -public class ContentTemplateDescriptorProvider(IEnumerable contentTemplates) : - IContentTemplateDescriptorProvider -{ - public IContentTemplateDescriptor? Get(object key) - { - if (contentTemplates.FirstOrDefault(x => x.Key.Equals(key) || x.Key.Equals($"{key}".Replace("ViewModel", ""))) - is IContentTemplateDescriptor viewModelTemplate) - { - return viewModelTemplate; - } - - return default; - } -} diff --git a/Toolkit.Foundation/Count.cs b/Toolkit.Foundation/Count.cs new file mode 100644 index 0000000..9563a8d --- /dev/null +++ b/Toolkit.Foundation/Count.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation; + +public record Count +{ + public static CountEventArgs As(TSender sender) => + new(sender); + + public static CountEventArgs As() where TSender : new() => + new(new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/CountEventArgs.cs b/Toolkit.Foundation/CountEventArgs.cs new file mode 100644 index 0000000..b222c5f --- /dev/null +++ b/Toolkit.Foundation/CountEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record CountEventArgs(TSender Sender); \ No newline at end of file diff --git a/Toolkit.Foundation/Create.cs b/Toolkit.Foundation/Create.cs index 9483a29..0973a5d 100644 --- a/Toolkit.Foundation/Create.cs +++ b/Toolkit.Foundation/Create.cs @@ -1,5 +1,10 @@ - -namespace Toolkit.Foundation; +namespace Toolkit.Foundation; -public record Create(TValue Value) : - INotification; \ No newline at end of file +public record Create +{ + public static CreateEventArgs As(TSender sender, params object[] parameters) => + new(sender, parameters); + + public static CreateEventArgs As(params object[] parameters) where TSender : new() => + new(new TSender(), parameters); +} \ No newline at end of file diff --git a/Toolkit.Foundation/CreateEventArgs.cs b/Toolkit.Foundation/CreateEventArgs.cs new file mode 100644 index 0000000..4778dc5 --- /dev/null +++ b/Toolkit.Foundation/CreateEventArgs.cs @@ -0,0 +1,4 @@ +namespace Toolkit.Foundation; + +public record CreateEventArgs(TSender? Sender = default, + params object[] Parameters); \ No newline at end of file diff --git a/Toolkit.Foundation/Created.cs b/Toolkit.Foundation/Created.cs new file mode 100644 index 0000000..2a96ff6 --- /dev/null +++ b/Toolkit.Foundation/Created.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation; + +public record Created +{ + public static CreatedEventArgs As(TSender sender) => + new(sender); + + public static CreatedEventArgs As() where TSender : new() => + new(new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/CreatedEventArgs.cs b/Toolkit.Foundation/CreatedEventArgs.cs new file mode 100644 index 0000000..e7fa76b --- /dev/null +++ b/Toolkit.Foundation/CreatedEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record CreatedEventArgs(TSender? Sender = default); \ No newline at end of file diff --git a/Toolkit.Foundation/Deactivated.cs b/Toolkit.Foundation/Deactivated.cs new file mode 100644 index 0000000..aeb3e2c --- /dev/null +++ b/Toolkit.Foundation/Deactivated.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation; + +public record Deactivated +{ + public static DeactivatedEventArgs As(TSender sender) => new(sender); + + public static DeactivatedEventArgs As() where TSender : new() => new(new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/DeactivatedEventArgs.cs b/Toolkit.Foundation/DeactivatedEventArgs.cs new file mode 100644 index 0000000..2efcbd9 --- /dev/null +++ b/Toolkit.Foundation/DeactivatedEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record DeactivatedEventArgs(TSender? Sender = default); \ No newline at end of file diff --git a/Toolkit.Foundation/DecoratorService.cs b/Toolkit.Foundation/DecoratorService.cs new file mode 100644 index 0000000..c16325b --- /dev/null +++ b/Toolkit.Foundation/DecoratorService.cs @@ -0,0 +1,9 @@ +namespace Toolkit.Foundation; + +public class DecoratorService : + IDecoratorService +{ + public T? Value { get; private set; } + + public void Set(T? value) => Value = value; +} \ No newline at end of file diff --git a/Toolkit.Foundation/DefaultBuilder.cs b/Toolkit.Foundation/DefaultBuilder.cs deleted file mode 100644 index 1a2474b..0000000 --- a/Toolkit.Foundation/DefaultBuilder.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Toolkit.Foundation; - -namespace Toolkit.Foundation; - -public class DefaultBuilder : - HostBuilder -{ - public static IHostBuilder Create() - { - return new HostBuilder() - .UseContentRoot("Local", true) - .ConfigureAppConfiguration(config => - { - config.AddJsonFile("Settings.json", true, true); - }) - .ConfigureServices((context, services) => - { - services.AddScoped(provider => - new ServiceFactory((type, parameters) => ActivatorUtilities.CreateInstance(provider, type, parameters!))); - - services.AddSingleton(); - - services.AddScoped(); - services.AddScoped(); - services.AddTransient(); - services.AddTransient(); - - services.AddScoped(); - - services.AddScoped>(provider => - new ProxyService(provider.GetRequiredService())); - - services.AddScoped>(provider => - new ProxyService(provider.GetRequiredService())); - - services.AddScoped>(provider => - new ProxyService(provider.GetRequiredService())); - - services.AddScoped(); - - services.AddTransient(); - - services.AddTransient(); - - services.AddScoped(); - services.AddTransient(); - - services.AddTransient(); - - services.AddScoped(provider => new ComponentScopeCollection - { - { "Default", provider.GetRequiredService() } - }); - - services.AddTransient(); - - services.AddHandler(); - services.AddHandler(); - - services.AddInitializer(); - services.AddHostedService(); - }); - } -} diff --git a/Toolkit.Foundation/DefaultHostBuilder.cs b/Toolkit.Foundation/DefaultHostBuilder.cs new file mode 100644 index 0000000..c2f431d --- /dev/null +++ b/Toolkit.Foundation/DefaultHostBuilder.cs @@ -0,0 +1,66 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Toolkit.Foundation; + +public class DefaultHostBuilder : + HostBuilder +{ + public static IHostBuilder Create() + { + return new HostBuilder() + .ConfigureServices((context, services) => + { + services.AddScoped(provider => + new ServiceFactory((type, parameters) => ActivatorUtilities.CreateInstance(provider, type, + parameters?.Where(x => x is not null).ToArray()!))); + + services.AddSingleton(); + + services.AddSingleton(); + + services.AddScoped(); + + services.AddTransient(); + services.AddScoped(); + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + + services.AddScoped>(provider => + new ProxyService(provider.GetRequiredService())); + + services.AddScoped>(provider => + new ProxyService(provider.GetRequiredService())); + + services.AddScoped>(provider => + new ProxyService(provider.GetRequiredService())); + + services.AddTransient(); + + services.AddScoped(); + services.AddTransient(); + + services.AddScoped(); + + services.AddSingleton(new NamedComponent("Root")); + services.AddScoped(provider => + [ + new ComponentScopeDescriptor("Root", provider.GetRequiredService()) + ]); + + services.AddTransient(); + services.AddTransient(); + + services.AddHandler(); + services.AddHandler(); + + services.AddInitialization(); + services.AddHostedService(); + }); + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/Delete.cs b/Toolkit.Foundation/Delete.cs new file mode 100644 index 0000000..5937231 --- /dev/null +++ b/Toolkit.Foundation/Delete.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation; + +public record Delete +{ + public static DeleteEventArgs As(TSender sender) => + new(sender); + + public static DeleteEventArgs As() where TSender : new() => + new(new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/DeleteEventArgs.cs b/Toolkit.Foundation/DeleteEventArgs.cs new file mode 100644 index 0000000..00cce35 --- /dev/null +++ b/Toolkit.Foundation/DeleteEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record DeleteEventArgs(TSender? Sender = default); \ No newline at end of file diff --git a/Toolkit.Foundation/DictionaryStringObjectJsonConverter.cs b/Toolkit.Foundation/DictionaryStringObjectJsonConverter.cs index 595fb6e..8ab6df9 100644 --- a/Toolkit.Foundation/DictionaryStringObjectJsonConverter.cs +++ b/Toolkit.Foundation/DictionaryStringObjectJsonConverter.cs @@ -3,15 +3,15 @@ using System.Text.Json.Serialization; namespace Toolkit.Foundation; -public class DictionaryStringObjectJsonConverter : +public class DictionaryStringObjectJsonConverter : JsonConverter> { public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Dictionary) || typeToConvert == typeof(Dictionary); - public override Dictionary Read(ref Utf8JsonReader reader, - Type typeToConvert, + public override Dictionary Read(ref Utf8JsonReader reader, + Type typeToConvert, JsonSerializerOptions options) { Dictionary dictionary = []; @@ -42,7 +42,7 @@ public class DictionaryStringObjectJsonConverter : public override void Write(Utf8JsonWriter writer, Dictionary value, - JsonSerializerOptions options) => + JsonSerializerOptions options) => JsonSerializer.Serialize(writer, (IDictionary)value, options); private object? ExtractValue(ref Utf8JsonReader reader, @@ -56,20 +56,26 @@ public class DictionaryStringObjectJsonConverter : return date; } return reader.GetString(); + case JsonTokenType.False: return false; + case JsonTokenType.True: return true; + case JsonTokenType.Null: return null; + case JsonTokenType.Number: if (reader.TryGetInt64(out var result)) { return result; } return reader.GetDecimal(); + case JsonTokenType.StartObject: return Read(ref reader, null!, options); + case JsonTokenType.StartArray: List list = []; while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) @@ -77,6 +83,7 @@ public class DictionaryStringObjectJsonConverter : list.Add(ExtractValue(ref reader, options)); } return list; + default: return default; } diff --git a/Toolkit.Foundation/DirectoryObserver.cs b/Toolkit.Foundation/DirectoryObserver.cs new file mode 100644 index 0000000..7ea8b87 --- /dev/null +++ b/Toolkit.Foundation/DirectoryObserver.cs @@ -0,0 +1,56 @@ +using System.Collections.Concurrent; +using System.Text.RegularExpressions; + +namespace Toolkit.Foundation; + +public class DirectoryObserver +{ + public static async Task EnumerateFiles(string path, + string[] patterns, + int count, + CancellationToken cancellationToken = default) + { + string[] files = []; + List regexPatterns = patterns.Select(pattern => new Regex(pattern, RegexOptions.IgnoreCase)).ToList(); + + bool IsBatchComplete() + { + files = Directory.EnumerateFiles(path, "*.*", SearchOption.TopDirectoryOnly) + .Where(file => regexPatterns.Any(regex => regex.IsMatch(Path.GetFileName(file)))) + .ToArray(); + + if (files.Length != count) + { + return false; + } + + ConcurrentBag fileAccessResults = []; + + Parallel.ForEach(files, file => + { + try + { + using FileStream fileStream = new(file, FileMode.Open, FileAccess.Read, FileShare.None); + fileAccessResults.Add(true); + } + catch (IOException) + { + fileAccessResults.Add(false); + } + }); + + return fileAccessResults.All(result => result); + } + + await Task.Run(async () => + { + while (!IsBatchComplete()) + { + cancellationToken.ThrowIfCancellationRequested(); + await Task.Delay(5000, cancellationToken); + } + }, cancellationToken); + + return files; + } +} diff --git a/Toolkit.Foundation/Disposer.cs b/Toolkit.Foundation/Disposer.cs index 093120c..c7d03fc 100644 --- a/Toolkit.Foundation/Disposer.cs +++ b/Toolkit.Foundation/Disposer.cs @@ -1,10 +1,10 @@ -using System.Reactive.Disposables; -using System.Collections; +using System.Collections; using System.Collections.Concurrent; +using System.Reactive.Disposables; namespace Toolkit.Foundation; -public class Disposer : +public class Disposer : IDisposer { private readonly ConcurrentDictionary subjects = []; @@ -45,10 +45,10 @@ public class Disposer : } } - public TDisposable Replace(object subject, - IDisposable disposer, + public TDisposable Replace(object subject, + IDisposable disposer, TDisposable replacement) - where TDisposable : + where TDisposable : IDisposable { CompositeDisposable disposables = subjects.GetOrAdd(subject, args => new CompositeDisposable()); @@ -61,7 +61,7 @@ public class Disposer : return replacement; } - public void Remove(object subject, + public void Remove(object subject, IDisposable disposer) { CompositeDisposable disposables = subjects.GetOrAdd(subject, args => new CompositeDisposable()); @@ -78,4 +78,4 @@ public class Disposer : disposables?.Dispose(); } } -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/Enumerate.cs b/Toolkit.Foundation/Enumerate.cs deleted file mode 100644 index 14d385f..0000000 --- a/Toolkit.Foundation/Enumerate.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Toolkit.Foundation; - -public record Enumerate(object? Key = null) : INotification; \ No newline at end of file diff --git a/Toolkit.Foundation/Error.cs b/Toolkit.Foundation/Error.cs new file mode 100644 index 0000000..c12219a --- /dev/null +++ b/Toolkit.Foundation/Error.cs @@ -0,0 +1,14 @@ +namespace Toolkit.Foundation; + +public record Error(string Code, string Message) +{ + public static readonly Error None = new(string.Empty, string.Empty); + + public static readonly Error Null = new("Error.NullValue", "The specified result value is null."); + + public static readonly Error ConditionNotMet = new("Error.ConditionNotMet", "The specified condition was not met."); + + public static readonly Error Duplicated = new("Error.Duplicated", "The specified item already exists."); + + public static readonly Error Failure = new("Error.Failure", "The operation has failed."); +} diff --git a/Toolkit.Foundation/FileDescriptor.cs b/Toolkit.Foundation/FileDescriptor.cs new file mode 100644 index 0000000..3255195 --- /dev/null +++ b/Toolkit.Foundation/FileDescriptor.cs @@ -0,0 +1,4 @@ +namespace Toolkit.Foundation; + +public record FileDescriptor(string Name, string Path, long Size) : + IFileDescriptor; \ No newline at end of file diff --git a/Toolkit.Foundation/FileFilter.cs b/Toolkit.Foundation/FileFilter.cs new file mode 100644 index 0000000..e37c437 --- /dev/null +++ b/Toolkit.Foundation/FileFilter.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record FileFilter(string Name, List Extensions, bool AllowMultiple = false); diff --git a/Toolkit.Foundation/FolderFilter.cs b/Toolkit.Foundation/FolderFilter.cs new file mode 100644 index 0000000..7ff2c9a --- /dev/null +++ b/Toolkit.Foundation/FolderFilter.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record FolderFilter(bool AllowMultiple = false); \ No newline at end of file diff --git a/Toolkit.Foundation/HandlerDelegate.cs b/Toolkit.Foundation/HandlerDelegate.cs index 73ce94b..455c50c 100644 --- a/Toolkit.Foundation/HandlerDelegate.cs +++ b/Toolkit.Foundation/HandlerDelegate.cs @@ -1,5 +1,4 @@ namespace Toolkit.Foundation; -public delegate Task HandlerDelegate(TMessage message, - CancellationToken cancellationToken) - where TMessage : IMessage; \ No newline at end of file +public delegate Task HandlerDelegate(TRequest request, + CancellationToken cancellationToken); \ No newline at end of file diff --git a/Toolkit.Foundation/HandlerProvider.cs b/Toolkit.Foundation/HandlerProvider.cs new file mode 100644 index 0000000..e0ce08f --- /dev/null +++ b/Toolkit.Foundation/HandlerProvider.cs @@ -0,0 +1,25 @@ +namespace Toolkit.Foundation; + +public class HandlerProvider(SubscriptionCollection subscriptions) : + IHandlerProvider +{ + public IEnumerable Get(object key) + { + var d = subscriptions; + if (subscriptions.TryGetValue(key, out List? subscribers)) + { + foreach (WeakReference weakRef in subscribers.ToArray()) + { + object? target = weakRef.Target; + if (target != null) + { + yield return target; + } + else + { + subscribers.Remove(weakRef); + } + } + } + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/HandlerWrapper.cs b/Toolkit.Foundation/HandlerWrapper.cs index f5ba764..ffd1d13 100644 --- a/Toolkit.Foundation/HandlerWrapper.cs +++ b/Toolkit.Foundation/HandlerWrapper.cs @@ -1,25 +1,25 @@ namespace Toolkit.Foundation; -public class HandlerWrapper(IHandler handler, - IEnumerable> pipelineBehaviours) - where TMessage : class, IRequest +public class HandlerWrapper(IHandler handler, + IEnumerable> pipelineBehaviours) + where TRequest : notnull { - private readonly IEnumerable> pipelineBehaviours = + private readonly IEnumerable> pipelineBehaviours = pipelineBehaviours.Reverse(); - public async Task Handle(TMessage message, + public async Task Handle(TRequest request, CancellationToken cancellationToken) { - HandlerDelegate currentHandler = handler.Handle; - foreach (IPipelineBehavior behavior in pipelineBehaviours) + HandlerDelegate currentHandler = handler.Handle; + foreach (IPipelineBehaviour behaviour in pipelineBehaviours) { - HandlerDelegate previousHandler = currentHandler; + HandlerDelegate previousHandler = currentHandler; currentHandler = async (args, token) => { - return await behavior.Handle(args, previousHandler, token); + return await behaviour.Handle(args, previousHandler, token); }; } - return await currentHandler(message, cancellationToken); + return await currentHandler(request, cancellationToken); } } \ No newline at end of file diff --git a/Toolkit.Foundation/IActivated.cs b/Toolkit.Foundation/IActivated.cs index 224ec70..d335a1e 100644 --- a/Toolkit.Foundation/IActivated.cs +++ b/Toolkit.Foundation/IActivated.cs @@ -2,10 +2,5 @@ public interface IActivated { - Task Activated(); -} - -public interface IActivated -{ - Task Activated(TResult result); + Task OnActivated(); } \ No newline at end of file diff --git a/Toolkit.Foundation/IActivation.cs b/Toolkit.Foundation/IActivation.cs new file mode 100644 index 0000000..26c23c8 --- /dev/null +++ b/Toolkit.Foundation/IActivation.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public interface IActivation; \ No newline at end of file diff --git a/Toolkit.Foundation/IActivityIndicator.cs b/Toolkit.Foundation/IActivityIndicator.cs new file mode 100644 index 0000000..aa25262 --- /dev/null +++ b/Toolkit.Foundation/IActivityIndicator.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IActivityIndicator +{ + bool IsActive { get; set; } +} \ No newline at end of file diff --git a/Toolkit.Foundation/IInitializer.cs b/Toolkit.Foundation/IAsyncInitialization.cs similarity index 61% rename from Toolkit.Foundation/IInitializer.cs rename to Toolkit.Foundation/IAsyncInitialization.cs index 00a126a..4d2064d 100644 --- a/Toolkit.Foundation/IInitializer.cs +++ b/Toolkit.Foundation/IAsyncInitialization.cs @@ -1,6 +1,6 @@ namespace Toolkit.Foundation; -public interface IInitializer +public interface IAsyncInitialization { Task Initialize(); } \ No newline at end of file diff --git a/Toolkit.Foundation/ICache.cs b/Toolkit.Foundation/ICache.cs index de9420c..2220c74 100644 --- a/Toolkit.Foundation/ICache.cs +++ b/Toolkit.Foundation/ICache.cs @@ -7,24 +7,31 @@ public interface ICache : void Clear(); + bool Contains(TValue key); + + int IndexOf(TValue value); + bool Remove(TValue value); + + bool TryGetValue(TValue key, out TValue? item); } public interface ICache : IEnumerable> where TKey : notnull - where TValue : + where TValue : notnull { - void Add(TKey key, - TValue value); + void Add(TKey key, TValue value); void Clear(); - bool ContainsKey(TKey key); + bool Contains(TKey key); + + int IndexOf(TKey key); bool Remove(TKey key); bool TryGetValue(TKey key, out TValue? value); -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/IClipboardWriter.cs b/Toolkit.Foundation/IClipboardWriter.cs new file mode 100644 index 0000000..0132e28 --- /dev/null +++ b/Toolkit.Foundation/IClipboardWriter.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IClipboardWriter +{ + Task Write(TContent content); +} \ No newline at end of file diff --git a/Toolkit.Foundation/ICollectionSynchronization.cs b/Toolkit.Foundation/ICollectionSynchronization.cs new file mode 100644 index 0000000..10d67c9 --- /dev/null +++ b/Toolkit.Foundation/ICollectionSynchronization.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public interface ICollectionSynchronization; \ No newline at end of file diff --git a/Toolkit.Foundation/ICollectionSynchronizationExtensions.cs b/Toolkit.Foundation/ICollectionSynchronizationExtensions.cs new file mode 100644 index 0000000..cc3abbd --- /dev/null +++ b/Toolkit.Foundation/ICollectionSynchronizationExtensions.cs @@ -0,0 +1,7 @@ +namespace Toolkit.Foundation; + +public static class ICollectionSynchronizationExtensions +{ + public static int IndexOf(this ICollectionSynchronization synchronization, + TItem item) => synchronization is IList collection ? collection.IndexOf(item) : -1; +} \ No newline at end of file diff --git a/Toolkit.Foundation/IComponent.cs b/Toolkit.Foundation/IComponent.cs index 9165c0f..47b2932 100644 --- a/Toolkit.Foundation/IComponent.cs +++ b/Toolkit.Foundation/IComponent.cs @@ -2,5 +2,9 @@ public interface IComponent { - IComponentBuilder Create(); + IComponentBuilder Configure(Action? configurationDelegate = null); + + IComponentBuilder Configure(Action>? configurationDelegate = null, + Action? componentDelegate = null) + where TConfiguration : class, new(); } \ No newline at end of file diff --git a/Toolkit.Foundation/IComponentBuilder.cs b/Toolkit.Foundation/IComponentBuilder.cs index d65586e..6034fa0 100644 --- a/Toolkit.Foundation/IComponentBuilder.cs +++ b/Toolkit.Foundation/IComponentBuilder.cs @@ -4,10 +4,21 @@ namespace Toolkit.Foundation; public interface IComponentBuilder { - IComponentBuilder AddConfiguration(Action configurationDelegate) - where TConfiguration : ComponentConfiguration, new(); + string ConfigurationFile { get; set; } - IComponentHost Build(); + string ContentRoot { get; set; } + + IComponentBuilder AddConfiguration(Action configurationDelegate) + where TConfiguration : class, new(); + + IComponentBuilder AddConfiguration(string section, + TConfiguration? configuration = null) + where TConfiguration : class, new(); + + IComponentBuilder AddConfiguration(string section) + where TConfiguration : class, new(); IComponentBuilder AddServices(Action configureDelegate); + + IComponentHost Build(); } \ No newline at end of file diff --git a/Toolkit.Foundation/IComponentConfigurationViewModel.cs b/Toolkit.Foundation/IComponentConfigurationViewModel.cs index f7eb4cd..56ff184 100644 --- a/Toolkit.Foundation/IComponentConfigurationViewModel.cs +++ b/Toolkit.Foundation/IComponentConfigurationViewModel.cs @@ -2,5 +2,4 @@ public interface IComponentConfigurationViewModel { - -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/IComponentFactory.cs b/Toolkit.Foundation/IComponentFactory.cs new file mode 100644 index 0000000..1686f95 --- /dev/null +++ b/Toolkit.Foundation/IComponentFactory.cs @@ -0,0 +1,13 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Toolkit.Foundation; + +public interface IComponentFactory +{ + IComponentHost? Create(string key, + Action>? configurationDelegate = null, + Action? componentDelegate = null, + Action? servicesDelegate = null) + where TComponent : IComponent + where TConfiguration : class, new(); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IComponentHost.cs b/Toolkit.Foundation/IComponentHost.cs index 3990386..75586bf 100644 --- a/Toolkit.Foundation/IComponentHost.cs +++ b/Toolkit.Foundation/IComponentHost.cs @@ -2,8 +2,10 @@ namespace Toolkit.Foundation; -public interface IComponentHost : +public interface IComponentHost : IHost { - ComponentConfiguration? Configuration { get; } + TConfiguration? GetConfiguration() + where TConfiguration : + class; } \ No newline at end of file diff --git a/Toolkit.Foundation/IComponentScopeCollection.cs b/Toolkit.Foundation/IComponentScopeCollection.cs index 72eb836..d198369 100644 --- a/Toolkit.Foundation/IComponentScopeCollection.cs +++ b/Toolkit.Foundation/IComponentScopeCollection.cs @@ -1,5 +1,4 @@ namespace Toolkit.Foundation; -public interface IComponentScopeCollection : - IDictionary; - +public interface IComponentScopeCollection : + IList; \ No newline at end of file diff --git a/Toolkit.Foundation/IComponentScopeProvider.cs b/Toolkit.Foundation/IComponentScopeProvider.cs index bf5cfc2..062cca8 100644 --- a/Toolkit.Foundation/IComponentScopeProvider.cs +++ b/Toolkit.Foundation/IComponentScopeProvider.cs @@ -2,6 +2,5 @@ public interface IComponentScopeProvider { - IServiceProvider? Get(string name); -} - + ComponentScopeDescriptor? Get(string key); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IConfiguration.cs b/Toolkit.Foundation/IConfiguration.cs deleted file mode 100644 index cf4ef25..0000000 --- a/Toolkit.Foundation/IConfiguration.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Toolkit.Foundation; - -public interface IConfiguration - where TConfiguration : - class -{ - TConfiguration Value { get; } -} diff --git a/Toolkit.Foundation/IConfigurationBuilder.cs b/Toolkit.Foundation/IConfigurationBuilder.cs new file mode 100644 index 0000000..5f795f7 --- /dev/null +++ b/Toolkit.Foundation/IConfigurationBuilder.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation; + +public interface IConfigurationBuilder +{ + string? Section { get; set; } + + TConfiguration? Configuration { get; set; } +} diff --git a/Toolkit.Foundation/IConfigurationCache.cs b/Toolkit.Foundation/IConfigurationCache.cs new file mode 100644 index 0000000..4dfa090 --- /dev/null +++ b/Toolkit.Foundation/IConfigurationCache.cs @@ -0,0 +1,9 @@ +namespace Toolkit.Foundation +{ + public interface IConfigurationCache + { + bool Remove(string section); + void Set(string section, TConfiguration configuration); + bool TryGet(string section, out TConfiguration? configuration); + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/IConfigurationChanged.cs b/Toolkit.Foundation/IConfigurationChanged.cs index f00fe22..3a436e7 100644 --- a/Toolkit.Foundation/IConfigurationChanged.cs +++ b/Toolkit.Foundation/IConfigurationChanged.cs @@ -1,4 +1,4 @@ namespace Toolkit.Foundation; -public interface IConfigurationChanged : - IInitializer; \ No newline at end of file +public interface IConfigurationChanged : + IInitialization; \ No newline at end of file diff --git a/Toolkit.Foundation/IConfigurationDescriptor.cs b/Toolkit.Foundation/IConfigurationDescriptor.cs new file mode 100644 index 0000000..4ece6b2 --- /dev/null +++ b/Toolkit.Foundation/IConfigurationDescriptor.cs @@ -0,0 +1,12 @@ +namespace Toolkit.Foundation; + +public interface IConfigurationDescriptor + where TConfiguration : + class +{ + string Name { get; } + + string Section { get; } + + TConfiguration Value { get; } +} \ No newline at end of file diff --git a/Toolkit.Foundation/IConfigurationFactory.cs b/Toolkit.Foundation/IConfigurationFactory.cs index 916812a..52702dd 100644 --- a/Toolkit.Foundation/IConfigurationFactory.cs +++ b/Toolkit.Foundation/IConfigurationFactory.cs @@ -1,7 +1,7 @@ namespace Toolkit.Foundation; -public interface IConfigurationFactory - where TConfiguration : +public interface IConfigurationFactory + where TConfiguration : class { object Create(); diff --git a/Toolkit.Foundation/IConfigurationFile.cs b/Toolkit.Foundation/IConfigurationFile.cs index baec244..80fb81a 100644 --- a/Toolkit.Foundation/IConfigurationFile.cs +++ b/Toolkit.Foundation/IConfigurationFile.cs @@ -7,4 +7,4 @@ public interface IConfigurationFile class { IFileInfo FileInfo { get; } -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/IConfigurationInitializer.cs b/Toolkit.Foundation/IConfigurationInitializer.cs index 597609f..4d347d3 100644 --- a/Toolkit.Foundation/IConfigurationInitializer.cs +++ b/Toolkit.Foundation/IConfigurationInitializer.cs @@ -1,8 +1,8 @@ namespace Toolkit.Foundation; -public interface IConfigurationInitializer +public interface IConfigurationInitializer where TConfiguration : class { - Task Initialize(); + void Initialize(); } \ No newline at end of file diff --git a/Toolkit.Foundation/IConfigurationMonitor.cs b/Toolkit.Foundation/IConfigurationMonitor.cs index 3a18e99..c3aafac 100644 --- a/Toolkit.Foundation/IConfigurationMonitor.cs +++ b/Toolkit.Foundation/IConfigurationMonitor.cs @@ -1,9 +1,8 @@ - -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting; namespace Toolkit.Foundation; -public interface IConfigurationMonitor : +public interface IConfigurationMonitor : IHostedService where TConfiguration : class; \ No newline at end of file diff --git a/Toolkit.Foundation/IConfigurationReader.cs b/Toolkit.Foundation/IConfigurationReader.cs index 7e2f5e5..e703305 100644 --- a/Toolkit.Foundation/IConfigurationReader.cs +++ b/Toolkit.Foundation/IConfigurationReader.cs @@ -7,4 +7,4 @@ public interface IConfigurationReader bool TryRead(out TConfiguration? configuration); TConfiguration Read(); -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/IConfigurationSource.cs b/Toolkit.Foundation/IConfigurationSource.cs index 626e754..13d46f1 100644 --- a/Toolkit.Foundation/IConfigurationSource.cs +++ b/Toolkit.Foundation/IConfigurationSource.cs @@ -9,4 +9,4 @@ public interface IConfigurationSource void Set(TConfiguration value); void Set(object value); -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/IConfirmation.cs b/Toolkit.Foundation/IConfirmation.cs new file mode 100644 index 0000000..60a227d --- /dev/null +++ b/Toolkit.Foundation/IConfirmation.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IConfirmation +{ + Task Confirm(); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IContentFactory.cs b/Toolkit.Foundation/IContentFactory.cs new file mode 100644 index 0000000..cb172d2 --- /dev/null +++ b/Toolkit.Foundation/IContentFactory.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation +{ + public interface IContentFactory + { + object? Create(IContentTemplateDescriptor descriptor, + object[] parameters); + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/IContentTemplate.cs b/Toolkit.Foundation/IContentTemplate.cs index 6eb73fb..b3ce145 100644 --- a/Toolkit.Foundation/IContentTemplate.cs +++ b/Toolkit.Foundation/IContentTemplate.cs @@ -2,5 +2,4 @@ public interface IContentTemplate { - } \ No newline at end of file diff --git a/Toolkit.Foundation/IContentTemplateDescriptorProvider.cs b/Toolkit.Foundation/IContentTemplateDescriptorProvider.cs deleted file mode 100644 index 9475494..0000000 --- a/Toolkit.Foundation/IContentTemplateDescriptorProvider.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Toolkit.Foundation; - -public interface IContentTemplateDescriptorProvider -{ - IContentTemplateDescriptor? Get(object key); -} diff --git a/Toolkit.Foundation/IDeactivatable.cs b/Toolkit.Foundation/IDeactivatable.cs deleted file mode 100644 index 4a598ac..0000000 --- a/Toolkit.Foundation/IDeactivatable.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Toolkit.Foundation; - -public interface IDeactivatable -{ - public event EventHandler? DeactivateHandler; - - public Task Deactivate(); -} \ No newline at end of file diff --git a/Toolkit.Foundation/IDeactivated.cs b/Toolkit.Foundation/IDeactivated.cs index f640164..84bbeb4 100644 --- a/Toolkit.Foundation/IDeactivated.cs +++ b/Toolkit.Foundation/IDeactivated.cs @@ -2,5 +2,5 @@ public interface IDeactivated { - Task Deactivated(); + Task OnDeactivated(); } \ No newline at end of file diff --git a/Toolkit.Foundation/IDeactivating.cs b/Toolkit.Foundation/IDeactivating.cs index 032b5fd..4b58970 100644 --- a/Toolkit.Foundation/IDeactivating.cs +++ b/Toolkit.Foundation/IDeactivating.cs @@ -2,10 +2,5 @@ public interface IDeactivating { - Task Deactivating(); -} - -public interface IDeactivating -{ - Task Deactivating(); + Task OnDeactivating(); } \ No newline at end of file diff --git a/Toolkit.Foundation/IDecoratorService.cs b/Toolkit.Foundation/IDecoratorService.cs new file mode 100644 index 0000000..1ed8d19 --- /dev/null +++ b/Toolkit.Foundation/IDecoratorService.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation; + +public interface IDecoratorService +{ + T? Value { get; } + + void Set(T? value); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IDecryptor.cs b/Toolkit.Foundation/IDecryptor.cs new file mode 100644 index 0000000..96031d4 --- /dev/null +++ b/Toolkit.Foundation/IDecryptor.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation; + +public interface IDecryptor +{ + bool TryDecrypt(byte[] cipher, + byte[] key, + out byte[]? decryptedData); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IDispatcher.cs b/Toolkit.Foundation/IDispatcher.cs index d85772d..86790e5 100644 --- a/Toolkit.Foundation/IDispatcher.cs +++ b/Toolkit.Foundation/IDispatcher.cs @@ -2,5 +2,5 @@ public interface IDispatcher { - Task InvokeAsync(Action action); -} + Task Invoke(Action action); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IDisposer.cs b/Toolkit.Foundation/IDisposer.cs index aff06d6..212f697 100644 --- a/Toolkit.Foundation/IDisposer.cs +++ b/Toolkit.Foundation/IDisposer.cs @@ -2,14 +2,14 @@ public interface IDisposer { - void Add(object subject, + void Add(object subject, params object[] objects); TDisposable Replace(object subject, IDisposable disposer, TDisposable replacement) where TDisposable : IDisposable; - void Remove(object subject, + void Remove(object subject, IDisposable disposer); void Dispose(object subject); diff --git a/Toolkit.Foundation/IDisposerRequired.cs b/Toolkit.Foundation/IDisposerRequired.cs new file mode 100644 index 0000000..2842b49 --- /dev/null +++ b/Toolkit.Foundation/IDisposerRequired.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IDisposerRequired +{ + IDisposer Disposer { get; } +} \ No newline at end of file diff --git a/Toolkit.Foundation/IEncryptor.cs b/Toolkit.Foundation/IEncryptor.cs new file mode 100644 index 0000000..755d3c7 --- /dev/null +++ b/Toolkit.Foundation/IEncryptor.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation; + +public interface IEncryptor +{ + bool TryEncrypt(byte[] data, + byte[] key, + out byte[]? encryptedData); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IFactory.cs b/Toolkit.Foundation/IFactory.cs index f6837e1..f17c2ed 100644 --- a/Toolkit.Foundation/IFactory.cs +++ b/Toolkit.Foundation/IFactory.cs @@ -5,8 +5,7 @@ public interface IFactory TService? Create(TParameter value); } - public interface IFactory { TService? Create(); -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/IFileDescriptor.cs b/Toolkit.Foundation/IFileDescriptor.cs new file mode 100644 index 0000000..25ef2b2 --- /dev/null +++ b/Toolkit.Foundation/IFileDescriptor.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation; + +public interface IFileDescriptor +{ + string Name { get; } + + string Path { get; } + + long Size { get; } +} diff --git a/Toolkit.Foundation/IFileProvider.cs b/Toolkit.Foundation/IFileProvider.cs new file mode 100644 index 0000000..bf29d76 --- /dev/null +++ b/Toolkit.Foundation/IFileProvider.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IFileProvider +{ + Task> SelectFiles(FileFilter filter); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IFolderProvider.cs b/Toolkit.Foundation/IFolderProvider.cs new file mode 100644 index 0000000..ce91067 --- /dev/null +++ b/Toolkit.Foundation/IFolderProvider.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IFolderProvider +{ + Task> SelectFolders(FolderFilter filter); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IHandler.cs b/Toolkit.Foundation/IHandler.cs index 9bfb4ee..2263715 100644 --- a/Toolkit.Foundation/IHandler.cs +++ b/Toolkit.Foundation/IHandler.cs @@ -4,14 +4,10 @@ public interface IHandler; public interface IHandler : IHandler - where TRequest : - IRequest { Task Handle(TRequest args, CancellationToken cancellationToken); } public interface IHandler : - IHandler - where TRequest : - IRequest; \ No newline at end of file + IHandler; \ No newline at end of file diff --git a/Toolkit.Foundation/IHandlerProvider.cs b/Toolkit.Foundation/IHandlerProvider.cs new file mode 100644 index 0000000..6ce7f05 --- /dev/null +++ b/Toolkit.Foundation/IHandlerProvider.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IHandlerProvider +{ + IEnumerable Get(object key); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IHostBuilderExtension.cs b/Toolkit.Foundation/IHostBuilderExtension.cs new file mode 100644 index 0000000..8aa74f2 --- /dev/null +++ b/Toolkit.Foundation/IHostBuilderExtension.cs @@ -0,0 +1,210 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.FileProviders.Physical; +using Microsoft.Extensions.Hosting; +using System.Text.Json; + +namespace Toolkit.Foundation; + +public static class IHostBuilderExtension +{ + public static IHostBuilder AddConfiguration(this IHostBuilder builder, + string section) + where TConfiguration : class, new() => + builder.AddConfiguration(section, "Settings.json", null); + + public static IHostBuilder AddConfiguration(this IHostBuilder services) + where TConfiguration : class, new() => + services.AddConfiguration(typeof(TConfiguration).Name, "Settings.json", null); + + public static IHostBuilder AddConfiguration(this IHostBuilder builder, + string section, + TConfiguration configuration) + where TConfiguration : class, new() + { + return builder.AddConfiguration(section, "Settings.json", configuration); + } + + public static IHostBuilder AddConfiguration(this IHostBuilder builder, + string section, + Action configurationDelegate) + where TConfiguration : class, new() + { + TConfiguration configuration = new(); + configurationDelegate.Invoke(configuration); + + return builder.AddConfiguration(section, "Settings.json", configuration); + } + + public static IHostBuilder AddConfiguration(this IHostBuilder builder, + Action configurationDelegate, + string section) + where TConfiguration : class, new() + { + TConfiguration configuration = new(); + configurationDelegate.Invoke(configuration); + + return builder.AddConfiguration(section, "Settings.json", configuration); + } + + public static IHostBuilder AddConfiguration(this IHostBuilder builder, + TConfiguration configuration) + where TConfiguration : class, new() => + builder.AddConfiguration(configuration.GetType().Name, "Settings.json", configuration); + + public static IHostBuilder AddConfiguration(this IHostBuilder builder, + object configuration) + where TConfiguration : class, new() => + builder.AddConfiguration(configuration.GetType().Name, + "Settings.json", (TConfiguration?)configuration); + + public static IHostBuilder AddConfiguration(this IHostBuilder builder, + string section, + string path = "Settings.json", + TConfiguration? defaultConfiguration = null, + Action? serializerDelegate = null) + where TConfiguration : class, new() + { + builder.ConfigureServices((context, services) => + { + HashSet sections = []; + if (section.EndsWith(":*")) + { + section = section[..^1]; + if (context.Configuration is ConfigurationRoot root) + { + foreach (KeyValuePair configuration in root.AsEnumerable()) + { + string[] segments = configuration.Key.Split(':'); + if (segments.Length > 2) + { + string keyPrefix = string.Join(':', segments.Take(2)); + if (keyPrefix.StartsWith(section) && !keyPrefix.EndsWith(":*")) + { + sections.Add(keyPrefix); + } + } + } + } + } + else + { + if (context.Configuration is ConfigurationRoot root) + { + sections.Add(section); + } + } + + foreach (string section in sections) + { + if (context.Properties.TryGetValue(section, out object? value)) + { + if (value is List configurations) + { + if (configurations.Contains(typeof(TConfiguration))) + { + return; + } + else + { + configurations.Add(typeof(TConfiguration)); + } + } + } + else + { + context.Properties.Add(section, new List { typeof(TConfiguration) }); + } + + services.AddKeyedScoped(section); + + services.TryAddKeyedTransient>(section, (provider, key) => + { + IFileInfo? fileInfo = null; + if (provider.GetService() is IHostEnvironment hostEnvironment) + { + Microsoft.Extensions.FileProviders.IFileProvider fileProvider = hostEnvironment.ContentRootFileProvider; + fileInfo = fileProvider.GetFileInfo(path); + } + + fileInfo ??= new PhysicalFileInfo(new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path))); + return new ConfigurationFile(fileInfo); + }); + + services.TryAddKeyedTransient>(section, (provider, key) => + { + JsonSerializerOptions? defaultSerializer = null; + if (serializerDelegate is not null) + { + defaultSerializer = new JsonSerializerOptions(); + serializerDelegate.Invoke(defaultSerializer); + } + + return new ConfigurationSource(provider.GetRequiredKeyedService>(section), + section, + provider.GetRequiredKeyedService(section), + defaultSerializer); + }); + + services.TryAddKeyedTransient>(section, (provider, key) => + new ConfigurationReader(provider.GetRequiredKeyedService>(key), + provider.GetRequiredKeyedService>(key))); + + services.TryAddKeyedTransient>(section, (provider, key) => + new ConfigurationWriter(provider.GetRequiredKeyedService>(key), + provider.GetRequiredKeyedService>(key))); + + services.TryAddKeyedTransient>(section, (provider, key) => + new ConfigurationFactory(() => defaultConfiguration ?? provider.GetRequiredKeyedService(key))); + + services.AddTransient>(provider => + new ConfigurationInitializer(provider.GetRequiredKeyedService>(section), + provider.GetRequiredKeyedService>(section), + provider.GetRequiredKeyedService>(section), + provider.GetRequiredService())); + + services.AddTransient, ConfigurationInitializer>(provider => + provider.GetRequiredService().Create>(section)); + + services.TryAddKeyedTransient>(section, (provider, key) => + new WritableConfiguration(provider.GetRequiredKeyedService>(key))); + + services.TryAddTransient>(provider => + new WritableConfiguration(provider.GetRequiredKeyedService>(section))); + + services.TryAddKeyedTransient>(section, (provider, key) => + new ConfigurationDescriptor(section, provider.GetRequiredKeyedService>(key))); + + services.TryAddTransient(provider => + provider.GetRequiredKeyedService>(section)); + + services.TryAddTransient(provider => + provider.GetRequiredKeyedService>(section).Value); + + services.AddHostedService(provider => + new ConfigurationMonitor(section, + provider.GetRequiredKeyedService(section), + provider.GetRequiredKeyedService>(section), + provider.GetRequiredService(), + provider.GetRequiredService())); + } + }); + + return builder; + } + + public static IHostBuilder UseContentRoot(this IHostBuilder hostBuilder, + string contentRoot, + bool createDirectory) + { + if (createDirectory) + { + Directory.CreateDirectory(contentRoot); + } + + hostBuilder.UseContentRoot(contentRoot); + return hostBuilder; + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/IHostBuilderExtensions.cs b/Toolkit.Foundation/IHostBuilderExtensions.cs deleted file mode 100644 index cb72ce3..0000000 --- a/Toolkit.Foundation/IHostBuilderExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.Extensions.Hosting; - -namespace Toolkit.Foundation; - -public static class IHostBuilderExtensions -{ - public static IHostBuilder UseContentRoot(this IHostBuilder hostBuilder, - string contentRoot, - bool createDirectory) - { - if (createDirectory) - { - Directory.CreateDirectory(contentRoot); - } - - hostBuilder.UseContentRoot(contentRoot); - return hostBuilder; - } -} diff --git a/Toolkit.Foundation/IImageDescriptor.cs b/Toolkit.Foundation/IImageDescriptor.cs new file mode 100644 index 0000000..1dc5ae1 --- /dev/null +++ b/Toolkit.Foundation/IImageDescriptor.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation; + +public interface IImageDescriptor +{ + object Image { get; } + + double Width { get; } + + double Height { get; } +} diff --git a/Toolkit.Foundation/IImageReader.cs b/Toolkit.Foundation/IImageReader.cs new file mode 100644 index 0000000..27bde33 --- /dev/null +++ b/Toolkit.Foundation/IImageReader.cs @@ -0,0 +1,11 @@ +namespace Toolkit.Foundation; + +public interface IImageReader +{ + IImageDescriptor Get(Stream stream, + double width, + double height, + bool maintainAspectRatio = false); + + IImageDescriptor Get(Stream stream); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IImageWriter.cs b/Toolkit.Foundation/IImageWriter.cs new file mode 100644 index 0000000..e69f610 --- /dev/null +++ b/Toolkit.Foundation/IImageWriter.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IImageWriter +{ + void Write(IImageDescriptor imageDescriptor, Stream stream); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IIndexed.cs b/Toolkit.Foundation/IIndexed.cs new file mode 100644 index 0000000..c25bb5c --- /dev/null +++ b/Toolkit.Foundation/IIndexed.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IIndexed +{ + public int Index { get; } +} \ No newline at end of file diff --git a/Toolkit.Foundation/IInitialization.cs b/Toolkit.Foundation/IInitialization.cs new file mode 100644 index 0000000..c317689 --- /dev/null +++ b/Toolkit.Foundation/IInitialization.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IInitialization +{ + void Initialize(); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IKeyDeriver.cs b/Toolkit.Foundation/IKeyDeriver.cs new file mode 100644 index 0000000..8ab44bf --- /dev/null +++ b/Toolkit.Foundation/IKeyDeriver.cs @@ -0,0 +1,9 @@ +namespace Toolkit.Foundation; + +public interface IKeyDeriver +{ + byte[] DeriveKey(byte[] phrased, + byte[] salt, + int keySize = 32, + int iterations = 10000); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IKeyGenerator.cs b/Toolkit.Foundation/IKeyGenerator.cs new file mode 100644 index 0000000..416e420 --- /dev/null +++ b/Toolkit.Foundation/IKeyGenerator.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IKeyGenerator +{ + byte[] Generate(int size); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IKeyed.cs b/Toolkit.Foundation/IKeyed.cs new file mode 100644 index 0000000..dfaae3b --- /dev/null +++ b/Toolkit.Foundation/IKeyed.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IKeyed +{ + public T Id { get; } +} \ No newline at end of file diff --git a/Toolkit.Foundation/IMediator.cs b/Toolkit.Foundation/IMediator.cs index 74fcb71..6a8cdc3 100644 --- a/Toolkit.Foundation/IMediator.cs +++ b/Toolkit.Foundation/IMediator.cs @@ -2,9 +2,28 @@ public interface IMediator { - Task SendAsync(IRequest request, + Task Handle(Type responseType, + object message, + object? key = null, CancellationToken cancellationToken = default); - Task SendAsync(object message, CancellationToken - cancellationToken = default); + Task Handle(TMessage message, + object? key = null, + CancellationToken cancellationToken = default) + where TMessage : notnull; + + IAsyncEnumerable HandleAsyncMany(Type responseType, + object message, + object? key = null, + CancellationToken cancellationToken = default); + + IAsyncEnumerable HandleAsyncMany(TMessage message, + object? key = null, + CancellationToken cancellationToken = default) + where TMessage : notnull; + + Task> HandleMany(TMessage message, + object? key = null, + CancellationToken cancellationToken = default) + where TMessage : notnull; } \ No newline at end of file diff --git a/Toolkit.Foundation/IMediatorRequired.cs b/Toolkit.Foundation/IMediatorRequired.cs new file mode 100644 index 0000000..0ab9d7e --- /dev/null +++ b/Toolkit.Foundation/IMediatorRequired.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IMediatorRequired +{ + IMediator Mediator { get; } +} \ No newline at end of file diff --git a/Toolkit.Foundation/INavigateBackHandler.cs b/Toolkit.Foundation/INavigateBackHandler.cs deleted file mode 100644 index 50805f1..0000000 --- a/Toolkit.Foundation/INavigateBackHandler.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Toolkit.Foundation; - -public interface INavigateBackHandler : - INotificationHandler>, - INavigateHandler; \ No newline at end of file diff --git a/Toolkit.Foundation/INavigateHandler.cs b/Toolkit.Foundation/INavigateHandler.cs deleted file mode 100644 index 199faad..0000000 --- a/Toolkit.Foundation/INavigateHandler.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Toolkit.Foundation; - -public interface INavigateHandler; - -public interface INavigateHandler : - INotificationHandler>, - INavigateHandler; diff --git a/Toolkit.Foundation/INavigation.cs b/Toolkit.Foundation/INavigation.cs index 4298399..b47e408 100644 --- a/Toolkit.Foundation/INavigation.cs +++ b/Toolkit.Foundation/INavigation.cs @@ -2,6 +2,11 @@ public interface INavigation { - Type Type { get; set; } -} + void Navigate(string route, + object? sender = null, + object? region = null, + EventHandler? navigated = null, + IDictionary? parameters = null); + void Back(object? region); +} \ No newline at end of file diff --git a/Toolkit.Foundation/INavigationContextCollection.cs b/Toolkit.Foundation/INavigationContextCollection.cs deleted file mode 100644 index e867d10..0000000 --- a/Toolkit.Foundation/INavigationContextCollection.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Toolkit.Foundation; - -public interface INavigationContextCollection : - IDictionary; diff --git a/Toolkit.Foundation/INavigationProvider.cs b/Toolkit.Foundation/INavigationProvider.cs deleted file mode 100644 index b6255cb..0000000 --- a/Toolkit.Foundation/INavigationProvider.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Toolkit.Foundation; - -public interface INavigationProvider -{ - INavigation? Get(Type type); -} diff --git a/Toolkit.Foundation/INavigationRegion.cs b/Toolkit.Foundation/INavigationRegion.cs new file mode 100644 index 0000000..4887ab5 --- /dev/null +++ b/Toolkit.Foundation/INavigationRegion.cs @@ -0,0 +1,7 @@ +namespace Toolkit.Foundation; + +public interface INavigationRegion +{ + void Register(string name, + object target); +} \ No newline at end of file diff --git a/Toolkit.Foundation/INavigationRegionCollection.cs b/Toolkit.Foundation/INavigationRegionCollection.cs new file mode 100644 index 0000000..8f242bd --- /dev/null +++ b/Toolkit.Foundation/INavigationRegionCollection.cs @@ -0,0 +1,4 @@ +namespace Toolkit.Foundation; + +public interface INavigationRegionCollection : + IDictionary; \ No newline at end of file diff --git a/Toolkit.Foundation/INavigationContextProvider.cs b/Toolkit.Foundation/INavigationRegionProvider.cs similarity index 56% rename from Toolkit.Foundation/INavigationContextProvider.cs rename to Toolkit.Foundation/INavigationRegionProvider.cs index 08ddc03..1553415 100644 --- a/Toolkit.Foundation/INavigationContextProvider.cs +++ b/Toolkit.Foundation/INavigationRegionProvider.cs @@ -1,9 +1,9 @@ namespace Toolkit.Foundation; -public interface INavigationContextProvider +public interface INavigationRegionProvider { object? Get(object key); - bool TryGet(object key, + bool TryGet(object key, out object? value); } \ No newline at end of file diff --git a/Toolkit.Foundation/INavigationScope.cs b/Toolkit.Foundation/INavigationScope.cs deleted file mode 100644 index 6fa029b..0000000 --- a/Toolkit.Foundation/INavigationScope.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Toolkit.Foundation; - -public interface INavigationScope -{ - Task NavigateAsync(string route, object? sender = null, object? context = null, - EventHandler? navigated = null, object[]? parameters = null, CancellationToken cancellationToken = default); - - Task NavigateBackAsync(object? context, CancellationToken cancellationToken = default); -} - diff --git a/Toolkit.Foundation/INavigationViewModel.cs b/Toolkit.Foundation/INavigationViewModel.cs deleted file mode 100644 index d22eb9d..0000000 --- a/Toolkit.Foundation/INavigationViewModel.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Toolkit.Foundation; - -public interface INavigationViewModel : - IObservableViewModel -{ - string Text { get; set; } -} diff --git a/Toolkit.Foundation/INotification.cs b/Toolkit.Foundation/INotification.cs deleted file mode 100644 index 1bc2226..0000000 --- a/Toolkit.Foundation/INotification.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Toolkit.Foundation; - -public interface INotification : - IMessage; \ No newline at end of file diff --git a/Toolkit.Foundation/INotificationHandler.cs b/Toolkit.Foundation/INotificationHandler.cs index abe34bd..6d52db3 100644 --- a/Toolkit.Foundation/INotificationHandler.cs +++ b/Toolkit.Foundation/INotificationHandler.cs @@ -1,10 +1,7 @@ namespace Toolkit.Foundation; -public interface INotificationHandler : +public interface INotificationHandler : IHandler - where TNotification : - INotification { - Task Handle(TNotification args, - CancellationToken cancellationToken = default); + Task Handle(TMessage args); } \ No newline at end of file diff --git a/Toolkit.Foundation/IObservableCollectionViewModel.cs b/Toolkit.Foundation/IObservableCollectionViewModel.cs index 9e12d1e..9e4de44 100644 --- a/Toolkit.Foundation/IObservableCollectionViewModel.cs +++ b/Toolkit.Foundation/IObservableCollectionViewModel.cs @@ -1,4 +1,4 @@ namespace Toolkit.Foundation; -public interface IObservableCollectionViewModel : - IObservableViewModel; +public interface IObservableCollectionViewModel : + IObservableViewModel; \ No newline at end of file diff --git a/Toolkit.Foundation/IObservableViewModel.cs b/Toolkit.Foundation/IObservableViewModel.cs index 7d688f4..333fa16 100644 --- a/Toolkit.Foundation/IObservableViewModel.cs +++ b/Toolkit.Foundation/IObservableViewModel.cs @@ -7,7 +7,7 @@ public interface IObservableViewModel : public IPublisher Publisher { get; } - public IServiceFactory ServiceFactory { get; } + public IServiceFactory Factory { get; } - public IServiceProvider ServiceProvider { get; } + public IServiceProvider Provider { get; } } \ No newline at end of file diff --git a/Toolkit.Foundation/IPasswordHasher.cs b/Toolkit.Foundation/IPasswordHasher.cs new file mode 100644 index 0000000..604cf1a --- /dev/null +++ b/Toolkit.Foundation/IPasswordHasher.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IPasswordHasher +{ + string HashPassword(string password, int iterations = 10000); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IPipelineBehavior.cs b/Toolkit.Foundation/IPipelineBehavior.cs deleted file mode 100644 index 7bd8b32..0000000 --- a/Toolkit.Foundation/IPipelineBehavior.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Toolkit.Foundation; - -public interface IPipelineBehavior - where TMessage : IMessage -{ - Task Handle(TMessage message, - HandlerDelegate next, - CancellationToken cancellationToken = default); -} - -public interface IPipelineBehavior - where TNotification : INotification -{ - Task Handle(TNotification notification, - NotificationHandlerDelegate next, - CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/Toolkit.Foundation/IPipelineBehaviour.cs b/Toolkit.Foundation/IPipelineBehaviour.cs new file mode 100644 index 0000000..9f4999e --- /dev/null +++ b/Toolkit.Foundation/IPipelineBehaviour.cs @@ -0,0 +1,15 @@ +namespace Toolkit.Foundation; + +public interface IPipelineBehaviour +{ + Task Handle(TMessage message, + HandlerDelegate next, + CancellationToken cancellationToken = default); +} + +public interface IPipelineBehaviour +{ + Task Handle(TMessage message, + NotificationHandlerDelegate next, + CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IPrimaryConfirmation.cs b/Toolkit.Foundation/IPrimaryConfirmation.cs index 975ed5c..1e5726a 100644 --- a/Toolkit.Foundation/IPrimaryConfirmation.cs +++ b/Toolkit.Foundation/IPrimaryConfirmation.cs @@ -2,11 +2,5 @@ public interface IPrimaryConfirmation { - Task Confirm(); -} - -public interface IConfirmation -{ - Task Confirm(); -} - + Task ConfirmPrimary(); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IProxyService.cs b/Toolkit.Foundation/IProxyService.cs index bb1747e..f91a38d 100644 --- a/Toolkit.Foundation/IProxyService.cs +++ b/Toolkit.Foundation/IProxyService.cs @@ -2,5 +2,5 @@ public interface IProxyService { - TService Proxy { get; } -} + TService Value { get; } +} \ No newline at end of file diff --git a/Toolkit.Foundation/IPublisher.cs b/Toolkit.Foundation/IPublisher.cs index 6289413..88d1411 100644 --- a/Toolkit.Foundation/IPublisher.cs +++ b/Toolkit.Foundation/IPublisher.cs @@ -2,57 +2,36 @@ public interface IPublisher { - public Task Publish(object key, - CancellationToken cancellationToken = default) - where TNotification : - INotification, - new(); + void Publish(object? key = null) + where TMessage : new(); - public Task Publish(TNotification notification, - CancellationToken cancellationToken = default) - where TNotification : - INotification; + void Publish(TMessage message) + where TMessage : notnull; - public Task Publish(TNotification notification, - object key, - CancellationToken cancellationToken = default) - where TNotification : - INotification; + void Publish(TMessage message, + object? key = null) + where TMessage : notnull; - Task PublishUI(TNotification notification, - object key, - CancellationToken cancellationToken = default) - where TNotification : - INotification; - - Task PublishUI(object key, - CancellationToken cancellationToken = default) - where TNotification : - INotification, - new(); - - Task PublishUI(TNotification notification, - CancellationToken cancellationToken = default) - where TNotification : - INotification; - - Task PublishUI(object notification, - CancellationToken cancellationToken = default); - - Task Publish(object notification, + void Publish(object message, Func, Task> marshal, - object? key = null, - CancellationToken cancellationToken = default); + object? key = null); - Task PublishUIAsync(CancellationToken cancellationToken = default) - where TNotification : - INotification, - new(); + void Publish() + where TMessage : new(); - Task Publish(CancellationToken cancellationToken = default) - where TNotification : - INotification, - new(); + void Publish(object message); - public Task Publish(object notification, CancellationToken cancellationToken = default); -} + void PublishUI(TMessage message, + object? key = null) where TMessage : notnull; + + void PublishUI(object? key = null) + where TMessage : new(); + + void PublishUI(TMessage message) + where TMessage : notnull; + + void PublishUI(object message); + + void PublishUI() + where TMessage : new(); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IPublisherRequired.cs b/Toolkit.Foundation/IPublisherRequired.cs new file mode 100644 index 0000000..20212c1 --- /dev/null +++ b/Toolkit.Foundation/IPublisherRequired.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IPublisherRequired +{ + IPublisher Publisher { get; } +} \ No newline at end of file diff --git a/Toolkit.Foundation/IReadOnlyIndexDictionary.cs b/Toolkit.Foundation/IReadOnlyIndexDictionary.cs new file mode 100644 index 0000000..3b7a0f2 --- /dev/null +++ b/Toolkit.Foundation/IReadOnlyIndexDictionary.cs @@ -0,0 +1,10 @@ + +namespace Toolkit.Foundation +{ + public interface IReadOnlyIndexDictionary : + IReadOnlyDictionary + where TKey : notnull + { + KeyValuePair this[int index] { get; } + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/IRemovable.cs b/Toolkit.Foundation/IRemovable.cs new file mode 100644 index 0000000..0c90d31 --- /dev/null +++ b/Toolkit.Foundation/IRemovable.cs @@ -0,0 +1,4 @@ +namespace Toolkit.Foundation; + +public interface IRemovable : + IDisposable; \ No newline at end of file diff --git a/Toolkit.Foundation/IRequest.cs b/Toolkit.Foundation/IRequest.cs deleted file mode 100644 index 33f891a..0000000 --- a/Toolkit.Foundation/IRequest.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Toolkit.Foundation; - -public interface IRequest : - IMessage; - -public interface IRequest : IRequest; \ No newline at end of file diff --git a/Toolkit.Foundation/ISecondaryConfirmation.cs b/Toolkit.Foundation/ISecondaryConfirmation.cs index f2588ad..dd4d66b 100644 --- a/Toolkit.Foundation/ISecondaryConfirmation.cs +++ b/Toolkit.Foundation/ISecondaryConfirmation.cs @@ -2,5 +2,5 @@ public interface ISecondaryConfirmation { - Task Confirm(); + Task ConfirmSecondary(); } \ No newline at end of file diff --git a/Toolkit.Foundation/ISelectable.cs b/Toolkit.Foundation/ISelectable.cs new file mode 100644 index 0000000..7c484ed --- /dev/null +++ b/Toolkit.Foundation/ISelectable.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface ISelectable +{ + bool IsSelected { get; set; } +} \ No newline at end of file diff --git a/Toolkit.Foundation/IServiceCollectionExtensions.cs b/Toolkit.Foundation/IServiceCollectionExtensions.cs index de3c861..c0c8c56 100644 --- a/Toolkit.Foundation/IServiceCollectionExtensions.cs +++ b/Toolkit.Foundation/IServiceCollectionExtensions.cs @@ -1,18 +1,19 @@ using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.FileProviders.Physical; -using Microsoft.Extensions.Hosting; -using System; -using System.Text.Json; -using Toolkit.Foundation; namespace Toolkit.Foundation; public static class IServiceCollectionExtensions { + public static IServiceCollection AddAsyncInitialization(this IServiceCollection services) + where TInitialization : class, + IAsyncInitialization + { + services.AddTransient(); + return services; + } + public static IServiceCollection AddCache(this IServiceCollection services) - where TKey : notnull + where TKey : notnull where TValue : notnull { services.AddScoped, Cache>(); @@ -30,168 +31,63 @@ public static class IServiceCollectionExtensions } public static IServiceCollection AddComponent(this IServiceCollection services) - where TComponent : class, + where TComponent : class, IComponent { services.AddTransient(); return services; } - public static IServiceCollection AddConfiguration(this IServiceCollection services, - Func> changed) - where TConfiguration : class - where TValue : class, new() + public static IServiceCollection AddHandler(this IServiceCollection services, + string key) + where THandler : IHandler { - services.AddSingleton(new ConfigurationValue(changed)); - services.AddHandler>(); - - return services; - } - - public static IServiceCollection AddConfiguration(this IServiceCollection services) - where TConfiguration : class => - services.AddConfiguration(typeof(TConfiguration).Name, "Settings.json", null); - - public static IServiceCollection AddConfiguration(this IServiceCollection services, - Action configurationDelegate) - where TConfiguration : class, new() - { - TConfiguration configuration = new(); - configurationDelegate.Invoke(configuration); - - return services.AddConfiguration(typeof(TConfiguration).Name, "Settings.json", configuration); - } - - public static IServiceCollection AddConfiguration(this IServiceCollection services, - TConfiguration configuration) - where TConfiguration : class => - services.AddConfiguration(configuration.GetType().Name, "Settings.json", configuration); - - public static IServiceCollection AddConfiguration(this IServiceCollection services, - object configuration) - where TConfiguration : class => - services.AddConfiguration(configuration.GetType().Name, - "Settings.json", (TConfiguration?)configuration); - - public static IServiceCollection AddConfiguration(this IServiceCollection services, - string section, - string path = "Settings.json", - TConfiguration? configuration = null, - Action? serializerDelegate = null) - where TConfiguration : class - { - services.AddSingleton>(provider => - { - JsonSerializerOptions? defaultSerializer = null; - if (serializerDelegate is not null) - { - defaultSerializer = new JsonSerializerOptions(); - serializerDelegate.Invoke(defaultSerializer); - } - - return new ConfigurationSource(provider.GetRequiredService>(), - section, defaultSerializer); - }); - - services.AddSingleton>(provider => - { - IFileInfo? fileInfo = null; - if (provider.GetService() is IHostEnvironment hostEnvironment) - { - IFileProvider fileProvider = hostEnvironment.ContentRootFileProvider; - fileInfo = fileProvider.GetFileInfo(path); - } - - fileInfo ??= new PhysicalFileInfo(new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path))); - return new ConfigurationFile(fileInfo); - }); - - services.AddHostedService>(); - services.AddSingleton, ConfigurationReader>(); - services.AddSingleton, ConfigurationWriter>(); - - services.AddTransient>(provider => new ConfigurationFactory(() => - configuration ?? provider.GetRequiredService())); - - services.AddTransient>(); - services.AddTransient, ConfigurationInitializer>(); - - services.AddTransient, WritableConfiguration>(); - - services.AddTransient, Configuration>(); - services.AddTransient(provider => provider.GetRequiredService>().Value); - - return services; - } - - public static IServiceCollection AddTemplate(this IServiceCollection services, - object? key = null, - params object[]? parameters) - { - Type viewModelType = typeof(TViewModel); - Type viewType = typeof(TView); - - key ??= viewModelType.Name.Replace("ViewModel", ""); - - services.AddTransient(viewModelType, provider => - provider.GetRequiredService().Create(parameters)!); - - services.AddTransient(viewType); - - services.AddKeyedTransient(viewModelType, key, (provider, key) => - provider.GetRequiredService().Create(parameters)!); - - services.AddKeyedTransient(viewType, key); - - services.AddTransient(provider => - new ContentTemplateDescriptor(key, viewModelType, viewType, parameters)); - - return services; + return AddHandler(services, ServiceLifetime.Transient, key); } public static IServiceCollection AddHandler(this IServiceCollection services, - ServiceLifetime lifetime = ServiceLifetime.Transient) + ServiceLifetime lifetime = ServiceLifetime.Transient, + string? key = null) where THandler : IHandler { - if (typeof(THandler).GetInterfaces() is Type[] contracts) + if (typeof(THandler).GetInterfaces() is Type[] handlerTypes) { - foreach (Type contract in contracts) + foreach (Type handlerType in handlerTypes) { - if (contract.Name == typeof(INotificationHandler<>).Name && - contract.GetGenericArguments() is { Length: 1 } notificationHandlerArguments) + if (handlerType.Name == typeof(INotificationHandler<>).Name && + handlerType.GetGenericArguments() is { Length: 1 } notificationHandlerArguments) { Type notificationType = notificationHandlerArguments[0]; + Type wrapperType = typeof(NotificationHandlerWrapper<>).MakeGenericType(notificationType); - Type wrapperType = typeof(NotificationHandlerWrapper<>) - .MakeGenericType(notificationType); + string preferredKey = $"{(key is not null ? $"{key}:" : "")}{notificationType}"; - services.TryAdd(new ServiceDescriptor(typeof(INotificationHandler<>) - .MakeGenericType(notificationType), typeof(THandler), lifetime)); + services.Add(new ServiceDescriptor(typeof(INotificationHandler<>) + .MakeGenericType(notificationType), preferredKey, typeof(THandler), lifetime)); - services.Add(new ServiceDescriptor(wrapperType, provider => - provider.GetService()?.Create(wrapperType, - provider.GetRequiredService(typeof(INotificationHandler<>).MakeGenericType(notificationType)), - provider.GetServices(typeof(IPipelineBehavior<>) - .MakeGenericType(notificationType)))!, lifetime)); + services.Add(new ServiceDescriptor(wrapperType, preferredKey, (provider, registeredKey) => + provider.GetService()?.Create(wrapperType, + provider.GetRequiredKeyedService(typeof(INotificationHandler<>).MakeGenericType(notificationType), registeredKey), + provider.GetServices(typeof(IPipelineBehaviour<>) + .MakeGenericType(notificationType)))!, lifetime)); } - if (contract.Name == typeof(IHandler<,>).Name && - contract.GetGenericArguments() is { Length: 2 } handlerArguments) + if (handlerType.Name == typeof(IHandler<,>).Name && + handlerType.GetGenericArguments() is { Length: 2 } handlerArguments) { Type requestType = handlerArguments[0]; Type responseType = handlerArguments[1]; - Type wrapperType = typeof(HandlerWrapper<,>) - .MakeGenericType(requestType, responseType); + Type wrapperType = typeof(HandlerWrapper<,>).MakeGenericType(requestType, responseType); + string preferredKey = $"{(key is not null ? $"{key}:" : "")}{wrapperType}"; - services.TryAdd(new ServiceDescriptor(typeof(THandler), + services.Add(new ServiceDescriptor(typeof(THandler), preferredKey, typeof(THandler), lifetime)); - services.Add(new ServiceDescriptor(wrapperType, provider => + services.Add(new ServiceDescriptor(wrapperType, preferredKey, (provider, actualKey) => provider.GetService()?.Create(wrapperType, - provider.GetRequiredService(), - provider.GetServices(typeof(IPipelineBehavior<,>) + provider.GetRequiredKeyedService(preferredKey), + provider.GetServices(typeof(IPipelineBehaviour<,>) .MakeGenericType(requestType, responseType)))!, lifetime)); } } @@ -202,35 +98,14 @@ public static class IServiceCollectionExtensions return services; } - public static IServiceCollection AddInitializer(this IServiceCollection services) - where TInitializer : class, - IInitializer + public static IServiceCollection AddInitialization(this IServiceCollection services) + where TInitialization : class, + IInitialization { - services.AddTransient(); + services.AddTransient(); return services; } - public static IServiceCollection AddNavigateHandler(this IServiceCollection services) - where THandler : INavigateHandler, - IHandler - { - IEnumerable contracts = typeof(THandler).GetInterfaces() - .Where(x => x.Name == typeof(INavigateHandler<>).Name || x.Name == typeof(INavigateBackHandler<>).Name); - - foreach (Type contract in contracts) - { - if (contract.GetGenericArguments() is { Length: 1 } arguments) - { - services.AddTransient(provider => new Navigation - { - Type = arguments[0] - }); - } - } - - services.AddHandler(); - return services; - } public static IServiceCollection AddRange(this IServiceCollection services, IServiceCollection fromServices) { @@ -241,4 +116,75 @@ public static class IServiceCollectionExtensions return services; } + + public static IServiceCollection AddTemplate(this IServiceCollection services, + object? key = null, + ServiceLifetime serviceLifetime = ServiceLifetime.Transient, + params object[]? parameters) + { + return AddTemplate(services, key, serviceLifetime, parameters); + } + + public static IServiceCollection AddTemplate(this IServiceCollection services, + object? key = null, + ServiceLifetime serviceLifetime = ServiceLifetime.Transient, + params object[]? parameters) + { + Type viewModelType = typeof(TViewModel); + Type viewModelImplementationType = typeof(TViewModelImplementation); + Type viewType = typeof(TView); + + key ??= viewModelImplementationType.Name.Replace("ViewModel", ""); + + if (parameters is { Length: 0 }) + { + services.Add(new ServiceDescriptor(viewModelType, viewModelImplementationType, serviceLifetime)); + } + else + { + services.Add(new ServiceDescriptor(viewModelType, provider => + provider.GetRequiredService().Create(parameters)!, serviceLifetime)); + } + + services.Add(new ServiceDescriptor(viewType, viewType, serviceLifetime)); + + if (parameters is { Length: 0 }) + { + services.Add(new ServiceDescriptor(viewModelType, key, viewModelImplementationType, serviceLifetime)); + } + else + { + services.Add(new ServiceDescriptor(viewModelType, key, (provider, key) => + provider.GetRequiredService().Create(parameters)!, serviceLifetime)); + } + + services.Add(new ServiceDescriptor(viewType, key, viewType, serviceLifetime)); + + services.AddKeyedTransient(key, (provider, _) => + new ContentTemplateDescriptor(key, viewModelImplementationType, viewType, parameters)); + + return services; + } + + public static IServiceCollection AddValueTemplate(this IServiceCollection services, + Func readDelegate, + Action writeDelegate, + object? key = null, + ServiceLifetime serviceLifetime = ServiceLifetime.Transient, + params object[]? parameters) + { + parameters = [readDelegate, writeDelegate, .. parameters ?? Enumerable.Empty()]; + return AddTemplate(services, key, serviceLifetime, parameters); + } + + public static IServiceCollection AddValueTemplate(this IServiceCollection services, + Func readDelegate, + Action writeDelegate, + object? key = null, + ServiceLifetime serviceLifetime = ServiceLifetime.Transient, + params object[]? parameters) + { + parameters = [readDelegate, writeDelegate, .. parameters ?? Enumerable.Empty()]; + return AddTemplate(services, key, serviceLifetime, parameters); + } } \ No newline at end of file diff --git a/Toolkit.Foundation/IServiceFactory.cs b/Toolkit.Foundation/IServiceFactory.cs index be6aee8..2e0f420 100644 --- a/Toolkit.Foundation/IServiceFactory.cs +++ b/Toolkit.Foundation/IServiceFactory.cs @@ -4,5 +4,11 @@ public interface IServiceFactory { object Create(Type type, params object?[]? parameters); + TService Create(Action serviceDelegate, + params object?[]? parameters); + + object Create(Type type, Action serviceDelegate, + params object?[]? parameters); + TService Create(params object?[]? parameters); -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/IServiceFactoryRequired.cs b/Toolkit.Foundation/IServiceFactoryRequired.cs new file mode 100644 index 0000000..05c2324 --- /dev/null +++ b/Toolkit.Foundation/IServiceFactoryRequired.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IServiceFactoryRequired +{ + IServiceFactory Factory { get; } +} \ No newline at end of file diff --git a/Toolkit.Foundation/IServiceProviderExtensions.cs b/Toolkit.Foundation/IServiceProviderExtensions.cs new file mode 100644 index 0000000..dd15973 --- /dev/null +++ b/Toolkit.Foundation/IServiceProviderExtensions.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Toolkit.Foundation; + +public static class IServiceProviderExtensions +{ + public static object GetRequiredKeyedService(this IServiceProvider provider, + Type serviceType, + Action serviceDelegate, + object? serviceKey) + { + object service = provider.GetRequiredKeyedService(serviceType, serviceKey); + serviceDelegate.Invoke(service); + + return service; + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/IServiceProviderRequired.cs b/Toolkit.Foundation/IServiceProviderRequired.cs new file mode 100644 index 0000000..09933d4 --- /dev/null +++ b/Toolkit.Foundation/IServiceProviderRequired.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IServiceProviderRequired +{ + IServiceProvider Provider { get; } +} \ No newline at end of file diff --git a/Toolkit.Foundation/IServiceScopeFactory.cs b/Toolkit.Foundation/IServiceScopeFactory.cs index dc3bae6..b6fb263 100644 --- a/Toolkit.Foundation/IServiceScopeFactory.cs +++ b/Toolkit.Foundation/IServiceScopeFactory.cs @@ -1,6 +1,8 @@ -namespace Toolkit.Foundation; +using Microsoft.Extensions.DependencyInjection; -public interface IServiceScopeFactory +namespace Toolkit.Foundation; + +public interface IScopeServiceFactory { - TService? Create(params object?[] parameters); -} + (IServiceScope, TService) Create(params object?[] parameters); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IServiceScopeProvider.cs b/Toolkit.Foundation/IServiceScopeProvider.cs index d0aa9ab..46dff06 100644 --- a/Toolkit.Foundation/IServiceScopeProvider.cs +++ b/Toolkit.Foundation/IServiceScopeProvider.cs @@ -5,4 +5,4 @@ namespace Toolkit.Foundation; public interface IServiceScopeProvider { bool TryGet(TService service, out IServiceScope? serviceScope); -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/ISubscriber.cs b/Toolkit.Foundation/ISubscriber.cs index 6948bb2..aada57f 100644 --- a/Toolkit.Foundation/ISubscriber.cs +++ b/Toolkit.Foundation/ISubscriber.cs @@ -2,7 +2,7 @@ public interface ISubscriber { - void Remove(object subscriber); + void Subscribe(object subscriber); - void Add(object subscriber); + void Unsubscribe(object subscriber); } \ No newline at end of file diff --git a/Toolkit.Foundation/ISubscriberRequired.cs b/Toolkit.Foundation/ISubscriberRequired.cs new file mode 100644 index 0000000..7a41b2b --- /dev/null +++ b/Toolkit.Foundation/ISubscriberRequired.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface ISubscriberRequired +{ + ISubscriber Subscription { get; } +} \ No newline at end of file diff --git a/Toolkit.Foundation/ISubscriptionManager.cs b/Toolkit.Foundation/ISubscriptionManager.cs deleted file mode 100644 index 71177e0..0000000 --- a/Toolkit.Foundation/ISubscriptionManager.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Toolkit.Foundation; - -public interface ISubscriptionManager -{ - IEnumerable GetHandlers(Type notificationType, object key); - - void Remove(object subscriber); - - void Add(object subscriber); -} diff --git a/Toolkit.Foundation/IUserInteraction.cs b/Toolkit.Foundation/IUserInteraction.cs new file mode 100644 index 0000000..59432ac --- /dev/null +++ b/Toolkit.Foundation/IUserInteraction.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation; + +public interface IUserInteraction +{ + event EventHandler? UserInteracted; + + void Stop(); + + void Start(); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IValidation.cs b/Toolkit.Foundation/IValidation.cs new file mode 100644 index 0000000..94cb1e8 --- /dev/null +++ b/Toolkit.Foundation/IValidation.cs @@ -0,0 +1,25 @@ +using System.ComponentModel; +using System.Linq.Expressions; + +namespace Toolkit.Foundation; + +public interface IValidation : + INotifyPropertyChanged +{ + IReadOnlyIndexDictionary Errors { get; } + + bool HasErrors { get; } + + void Add(Expression> property, + ValidationRule[] rules, + ValidationTrigger trigger = ValidationTrigger.Deferred); + + void Clear(); + + Task Validate(Expression> property, + ValidationRule[] rules); + + Task Validate(); + + Task Validate(string name); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IValidatorCollection.cs b/Toolkit.Foundation/IValidatorCollection.cs new file mode 100644 index 0000000..f2dfb16 --- /dev/null +++ b/Toolkit.Foundation/IValidatorCollection.cs @@ -0,0 +1,11 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Toolkit.Foundation; + +public interface IValidatorCollection : + IReadOnlyCollection +{ + void Add(string key, Validator binder); + + bool TryGet(string key, [MaybeNull] out Validator? value); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IValueInvoker.cs b/Toolkit.Foundation/IValueInvoker.cs new file mode 100644 index 0000000..67d14b0 --- /dev/null +++ b/Toolkit.Foundation/IValueInvoker.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public interface IValueInvoker +{ + public void Invoke(TValue args); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IWritableConfiguration.cs b/Toolkit.Foundation/IWritableConfiguration.cs index 0c537e1..6b261d1 100644 --- a/Toolkit.Foundation/IWritableConfiguration.cs +++ b/Toolkit.Foundation/IWritableConfiguration.cs @@ -5,4 +5,4 @@ public interface IWritableConfiguration class { void Write(Action updateDelegate); -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/ImageDescriptor.cs b/Toolkit.Foundation/ImageDescriptor.cs new file mode 100644 index 0000000..f60f915 --- /dev/null +++ b/Toolkit.Foundation/ImageDescriptor.cs @@ -0,0 +1,4 @@ +namespace Toolkit.Foundation; + +public record ImageDescriptor(object Image, double Width, double Height) : + IImageDescriptor; \ No newline at end of file diff --git a/Toolkit.Foundation/Insert.cs b/Toolkit.Foundation/Insert.cs index e97b8eb..e6808aa 100644 --- a/Toolkit.Foundation/Insert.cs +++ b/Toolkit.Foundation/Insert.cs @@ -1,3 +1,10 @@ namespace Toolkit.Foundation; -public record Insert(int Index, TValue Value) : INotification; +public record Insert +{ + public static InsertEventArgs As(int index, TSender sender) => + new(index, sender); + + public static InsertEventArgs As(int index) where TSender : new() => + new(index, new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/InsertEventArgs.cs b/Toolkit.Foundation/InsertEventArgs.cs new file mode 100644 index 0000000..fd89cfc --- /dev/null +++ b/Toolkit.Foundation/InsertEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record InsertEventArgs(int Index, TSender Sender); \ No newline at end of file diff --git a/Toolkit.Foundation/KeyAccelerator.cs b/Toolkit.Foundation/KeyAccelerator.cs index 0d3d045..2b7f265 100644 --- a/Toolkit.Foundation/KeyAccelerator.cs +++ b/Toolkit.Foundation/KeyAccelerator.cs @@ -1,5 +1,4 @@ namespace Toolkit.Foundation; public record KeyAccelerator(VirtualKey Key, - VirtualKey[]? Modifiers = null) : - IRequest; + VirtualKey[]? Modifiers = null); \ No newline at end of file diff --git a/Toolkit.Foundation/KeyDeriver.cs b/Toolkit.Foundation/KeyDeriver.cs new file mode 100644 index 0000000..9ce305b --- /dev/null +++ b/Toolkit.Foundation/KeyDeriver.cs @@ -0,0 +1,16 @@ +using System.Security.Cryptography; + +namespace Toolkit.Foundation; + +public class KeyDeriver : + IKeyDeriver +{ + public byte[] DeriveKey(byte[] phrase, + byte[] salt, + int keySize = 32, + int iterations = 100000) + { + using Rfc2898DeriveBytes pbkdf2 = new(phrase, salt, iterations, HashAlgorithmName.SHA256); + return pbkdf2.GetBytes(keySize); + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/KeyGenerator.cs b/Toolkit.Foundation/KeyGenerator.cs new file mode 100644 index 0000000..e6be5ab --- /dev/null +++ b/Toolkit.Foundation/KeyGenerator.cs @@ -0,0 +1,15 @@ +using System.Security.Cryptography; + +namespace Toolkit.Foundation; + +public class KeyGenerator : + IKeyGenerator +{ + public byte[] Generate(int size) + { + byte[] key = new byte[size]; + RandomNumberGenerator.Fill(key); + + return key; + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/Mediator.cs b/Toolkit.Foundation/Mediator.cs index c710ed3..b3731a1 100644 --- a/Toolkit.Foundation/Mediator.cs +++ b/Toolkit.Foundation/Mediator.cs @@ -1,48 +1,165 @@ -using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using System.Reflection; +using System.Runtime.CompilerServices; namespace Toolkit.Foundation; -public class Mediator(IServiceProvider provider) : +public class Mediator(IHandlerProvider handlerProvider, + IServiceProvider provider) : IMediator { - public Task SendAsync(IRequest request, + public async Task Handle(TMessage message, + object? key = null, CancellationToken cancellationToken = default) + where TMessage : notnull { - Type handlerType = typeof(HandlerWrapper<,>).MakeGenericType(request.GetType(), - typeof(TResponse)); + Type messageType = message.GetType(); + Type handlerType = typeof(HandlerWrapper<,>).MakeGenericType(messageType, typeof(TResponse)); + key = $"{(key is not null ? $"{key}:" : "")}{handlerType}"; - if (provider.GetService(handlerType) - is object handler) + List handlers = GetHandlers(message, handlerType, key); + foreach (object? handler in handlers) { - if (handlerType.GetMethod("Handle") is MethodInfo handleMethod) + MethodInfo? handleMethod = handler?.GetType().GetMethod("Handle", [message.GetType(), typeof(CancellationToken)]); + if (handleMethod is not null) { - return (Task)handleMethod.Invoke(handler, new object[] { request, cancellationToken })!; + return await (Task)handleMethod.Invoke(handler, [message, cancellationToken])!; } } - return Task.FromResult(default); + return default; } - public Task SendAsync(object message, + public async Task Handle(Type responseType, + object message, + object? key = null, CancellationToken cancellationToken = default) { - if (message.GetType().GetInterface(typeof(IRequest<>).Name) is Type requestType && - requestType.GetGenericArguments().Length == 1) - { - Type responseType = requestType.GetGenericArguments()[0]; - Type handlerType = typeof(HandlerWrapper<,>).MakeGenericType(message.GetType(), - responseType); + Type messageType = message.GetType(); + Type handlerType = typeof(HandlerWrapper<,>).MakeGenericType(message.GetType(), responseType); + key = $"{(key is not null ? $"{key}:" : "")}{handlerType}"; - if (provider.GetService(handlerType) - is object handler) + List handlers = GetHandlers(message, handlerType, key); + foreach (object? handler in handlers) + { + MethodInfo? handleMethod = handler?.GetType().GetMethod("Handle", + [messageType, typeof(CancellationToken)]); + + if (handleMethod is not null) { - if (handlerType.GetMethod("Handle") is MethodInfo handleMethod) - { - return (Task)handleMethod.Invoke(handler, new object[] { message, cancellationToken })!; - } + dynamic task = handleMethod.Invoke(handler, [message, cancellationToken])!; + await task; + + return task.Result; } } - return Task.FromResult(default); + return default; + } + + public async IAsyncEnumerable HandleAsyncMany(Type responseType, + object message, + object? key = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + Type messageType = message.GetType(); + Type handlerType = typeof(HandlerWrapper<,>).MakeGenericType(message.GetType(), responseType); + key = $"{(key is not null ? $"{key}:" : "")}{handlerType}"; + + List handlers = GetHandlers(message, handlerType, key); + foreach (object? handler in handlers) + { + MethodInfo? handleMethod = handler?.GetType().GetMethod("Handle", + [messageType, typeof(CancellationToken)]); + + if (handleMethod is not null) + { + yield return await (Task)handleMethod.Invoke(handler, [message, cancellationToken])!; + } + } + } + + public async IAsyncEnumerable HandleAsyncMany(TMessage message, + object? key = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + where TMessage : notnull + { + Type messageType = message.GetType(); + Type handlerType = typeof(HandlerWrapper<,>).MakeGenericType(messageType, typeof(TResponse)); + key = $"{(key is not null ? $"{key}:" : "")}{handlerType}"; + + List handlers = GetHandlers(message, handlerType, key); + foreach (object? handler in handlers) + { + MethodInfo? handleMethod = handler?.GetType().GetMethod("Handle", [message.GetType(), typeof(CancellationToken)]); + if (handleMethod is not null) + { + yield return await (Task)handleMethod.Invoke(handler, [message, cancellationToken])!; + } + } + } + + public async Task> HandleMany(Type responseType, + object message, + object? key = null, + CancellationToken cancellationToken = default) + { + List responses = []; + await foreach (object? response in HandleAsyncMany(responseType, message, key, cancellationToken)) + { + responses.Add(response); + } + + return responses; + } + + public async Task> HandleMany(TMessage message, + object? key = null, + CancellationToken cancellationToken = default) + where TMessage : notnull + { + List responses = []; + await foreach (TResponse? response in HandleAsyncMany(message, key, cancellationToken)) + { + responses.Add(response); + } + + return responses; + } + private List GetHandlers(object message, + Type handlerWrapperType, + object? key) + { + Type messageType = message.GetType(); + Dictionary> handlers = []; + + void AddHandlers(IEnumerable newHandlers) + { + foreach (object? handler in newHandlers) + { + if (handler == null) continue; + + Type serviceType = handler.GetType(); + if (!handlers.TryGetValue(serviceType, out List? handlerList)) + { + handlerList = []; + handlers.Add(serviceType, handlerList); + } + + handlerList.Add(handler); + } + } + + IEnumerable keyedServices = key is not null ? provider.GetKeyedServices(handlerWrapperType, key) : + provider.GetServices(handlerWrapperType); + AddHandlers(keyedServices); + + if (key is not null) + { + IEnumerable additionalHandlers = handlerProvider.Get(key); + AddHandlers(additionalHandlers); + } + + return handlers.SelectMany(entry => entry.Value).ToList(); } } \ No newline at end of file diff --git a/Toolkit.Foundation/MethodInfoExtensions.cs b/Toolkit.Foundation/MethodInfoExtensions.cs index 374a4f7..4a64a03 100644 --- a/Toolkit.Foundation/MethodInfoExtensions.cs +++ b/Toolkit.Foundation/MethodInfoExtensions.cs @@ -12,7 +12,7 @@ public static class MethodInfoExtensions } public static async Task InvokeAsync(this MethodInfo methodInfo, - object? obj, + object? obj, params object[] parameters) { await (dynamic?)methodInfo.Invoke(obj, parameters); diff --git a/Toolkit.Foundation/Modified.cs b/Toolkit.Foundation/Modified.cs new file mode 100644 index 0000000..0805e79 --- /dev/null +++ b/Toolkit.Foundation/Modified.cs @@ -0,0 +1,7 @@ +namespace Toolkit.Foundation; + +public record Modified +{ + public static ModifiedEventArgs As(TValue oldValue, TValue newValue) => + new(oldValue, newValue); +} \ No newline at end of file diff --git a/Toolkit.Foundation/ModifiedEventArgs.cs b/Toolkit.Foundation/ModifiedEventArgs.cs new file mode 100644 index 0000000..4789668 --- /dev/null +++ b/Toolkit.Foundation/ModifiedEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record ModifiedEventArgs(TValue OldView, TValue NewValue); \ No newline at end of file diff --git a/Toolkit.Foundation/Move.cs b/Toolkit.Foundation/Move.cs index f3c66d9..482ef13 100644 --- a/Toolkit.Foundation/Move.cs +++ b/Toolkit.Foundation/Move.cs @@ -1,3 +1,8 @@ namespace Toolkit.Foundation; -public record Move(int Index, TValue Value) : INotification; \ No newline at end of file +public record Move +{ + public static MoveEventArgs As(int index, TSender sender) => new(index, sender); + + public static MoveEventArgs As(int index) where TSender : new() => new(index, new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/MoveEventArgs.cs b/Toolkit.Foundation/MoveEventArgs.cs new file mode 100644 index 0000000..c8256d2 --- /dev/null +++ b/Toolkit.Foundation/MoveEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record MoveEventArgs(int Index, TSender? Sender = default); \ No newline at end of file diff --git a/Toolkit.Foundation/MoveTo.cs b/Toolkit.Foundation/MoveTo.cs new file mode 100644 index 0000000..7816e07 --- /dev/null +++ b/Toolkit.Foundation/MoveTo.cs @@ -0,0 +1,7 @@ +namespace Toolkit.Foundation; + +public record MoveTo +{ + public static MoveToEventArgs As(int oldIndex, int newIndex) => + new(oldIndex, newIndex); +} \ No newline at end of file diff --git a/Toolkit.Foundation/MoveToEventArgs.cs b/Toolkit.Foundation/MoveToEventArgs.cs new file mode 100644 index 0000000..fc5c41c --- /dev/null +++ b/Toolkit.Foundation/MoveToEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record MoveToEventArgs(int OldIndex, int NewIndex); \ No newline at end of file diff --git a/Toolkit.Foundation/NamedComponent.cs b/Toolkit.Foundation/NamedComponent.cs new file mode 100644 index 0000000..82c401c --- /dev/null +++ b/Toolkit.Foundation/NamedComponent.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; + +public record NamedComponent(string Key) +{ + public override string ToString() => Key; +} \ No newline at end of file diff --git a/Toolkit.Foundation/Navigate.cs b/Toolkit.Foundation/Navigate.cs index 30dce1c..0b2cec9 100644 --- a/Toolkit.Foundation/Navigate.cs +++ b/Toolkit.Foundation/Navigate.cs @@ -1,16 +1,14 @@ namespace Toolkit.Foundation; -public record Navigate(string Route, - object? Context = null, - string? Scope = null, +public record Navigate(string Route, + object? Context = null, + string? Scope = null, object? Sender = null, EventHandler? Navigated = null, - object[]? Parameters = null) : - INotification; + object[]? Parameters = null); -public record Navigate(object Context, - object Template, +public record Navigate(object Context, + object Template, object Content, object? Sender = null, - object[]? Parameters = null) : - INotification; \ No newline at end of file + object[]? Parameters = null); \ No newline at end of file diff --git a/Toolkit.Foundation/NavigateBack.cs b/Toolkit.Foundation/NavigateBack.cs deleted file mode 100644 index 25be518..0000000 --- a/Toolkit.Foundation/NavigateBack.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Toolkit.Foundation; - -public record NavigateBack(object? Context = null, string? Scope = null) : - INotification; - -public record NavigateBack(object? Context) : - INotification; diff --git a/Toolkit.Foundation/NavigateBackEventArgs.cs b/Toolkit.Foundation/NavigateBackEventArgs.cs new file mode 100644 index 0000000..3636e47 --- /dev/null +++ b/Toolkit.Foundation/NavigateBackEventArgs.cs @@ -0,0 +1,5 @@ +namespace Toolkit.Foundation; + +public record NavigateBackEventArgs(object? Context = null, string? Scope = null); + +public record NavigateBackEventArgs(object? Context); \ No newline at end of file diff --git a/Toolkit.Foundation/NavigateBackHandler.cs b/Toolkit.Foundation/NavigateBackHandler.cs index d66c7e4..69534a4 100644 --- a/Toolkit.Foundation/NavigateBackHandler.cs +++ b/Toolkit.Foundation/NavigateBackHandler.cs @@ -3,20 +3,20 @@ namespace Toolkit.Foundation; public class NavigateBackHandler(IComponentScopeProvider provider) : - INotificationHandler + INotificationHandler { - public async Task Handle(NavigateBack args, - CancellationToken cancellationToken) + public Task Handle(NavigateBackEventArgs args) { - if (provider.Get(args.Scope ?? "Default") - is IServiceProvider scope) + if (provider.Get(args.Scope ?? "Root") + is ComponentScopeDescriptor descriptor) { - if (scope.GetService() is INavigationScope navigationScope) + if (descriptor?.Services?.GetService() is + INavigation navigationScope) { - await navigationScope.NavigateBackAsync(args.Context, cancellationToken); + navigationScope.Back(args.Context); } } + + return Task.CompletedTask; } -} - - +} \ No newline at end of file diff --git a/Toolkit.Foundation/NavigateEventArgs.cs b/Toolkit.Foundation/NavigateEventArgs.cs new file mode 100644 index 0000000..62665f7 --- /dev/null +++ b/Toolkit.Foundation/NavigateEventArgs.cs @@ -0,0 +1,14 @@ +namespace Toolkit.Foundation; + +public record NavigateEventArgs(string Route, + object? Region = null, + string? Scope = null, + object? Sender = null, + EventHandler? Navigated = null, + IDictionary? Parameters = null); + +public record NavigateEventArgs(object Region, + object Template, + object Content, + object? Sender = null, + IDictionary? Parameters = null); \ No newline at end of file diff --git a/Toolkit.Foundation/NavigateHandler.cs b/Toolkit.Foundation/NavigateHandler.cs index a634319..b1eec14 100644 --- a/Toolkit.Foundation/NavigateHandler.cs +++ b/Toolkit.Foundation/NavigateHandler.cs @@ -2,20 +2,39 @@ namespace Toolkit.Foundation; -public class NavigateHandler(IComponentScopeProvider provider) : - INotificationHandler +public class NavigateHandler(NamedComponent scope, + IComponentScopeProvider componentScopeProvider) : + INotificationHandler { - public async Task Handle(Navigate args, - CancellationToken cancellationToken) + public Task Handle(NavigateEventArgs args) { - if (provider.Get(args.Scope ?? "Default") - is IServiceProvider scope) + INavigation? navigation = null; + if (args.Scope is "self" || args.Scope is "new") { - if (scope.GetService() is INavigationScope navigationScope) + if (args.Sender is IServiceProviderRequired requireServiceProvider) { - await navigationScope.NavigateAsync(args.Route, args.Sender, - args.Context, args.Navigated, args.Parameters, cancellationToken); + if (args.Scope is "self") + { + navigation = requireServiceProvider.Provider.GetRequiredService(); + } + + if (args.Scope is "new") + { + IServiceScope serviceScope = requireServiceProvider.Provider.CreateScope(); + navigation = serviceScope.ServiceProvider.GetRequiredService(); + } } } + + if (navigation is null) + { + ComponentScopeDescriptor? descriptor = componentScopeProvider.Get(args.Scope ?? scope.Key); + navigation = descriptor?.Services?.GetRequiredService(); + } + + navigation?.Navigate(args.Route, args.Sender, + args.Region, args.Navigated, args.Parameters); + + return Task.CompletedTask; } -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/NavigatingFrom.cs b/Toolkit.Foundation/NavigatingFrom.cs deleted file mode 100644 index 8d8e611..0000000 --- a/Toolkit.Foundation/NavigatingFrom.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Toolkit.Foundation; - -public record NavigatingFrom(object Content) : - IRequest>; - -public record NavigatingTo(object Content) : - IRequest>; diff --git a/Toolkit.Foundation/Navigation.cs b/Toolkit.Foundation/Navigation.cs index 4a1c6e0..02a3c24 100644 --- a/Toolkit.Foundation/Navigation.cs +++ b/Toolkit.Foundation/Navigation.cs @@ -1,8 +1,104 @@ -namespace Toolkit.Foundation; +using Microsoft.Extensions.DependencyInjection; -public record Navigation : +namespace Toolkit.Foundation; + +public class Navigation(IServiceProvider provider, + INavigationRegionProvider navigationRegionProvider, + IContentFactory contentFactory, + IPublisher publisher) : INavigation { - public required Type Type { get; set; } -} + public async void Navigate(string route, + object? sender = null, + object? region = null, + EventHandler? navigated = null, + IDictionary? parameters = null) + { + if (region is null) + { + return; + } + string[] segments = route.Split('/'); + int segmentCount = segments.Length; + int currentSegmentIndex = 0; + + foreach (object segment in segments) + { + currentSegmentIndex++; + + if (provider.GetKeyedService(segment) + is IContentTemplateDescriptor descriptor) + { + Dictionary? arguments = parameters?.ToDictionary(x => x.Key, x => x.Value, StringComparer.InvariantCultureIgnoreCase) ?? []; + object[]? resolvedArguments = parameters is not null ? [.. descriptor.ContentType + .GetConstructors() + .FirstOrDefault()? + .GetParameters() + .Select(x => x?.Name is not null && arguments + .TryGetValue(x.Name, out object? argument) ? argument : default) + .Where(argument => argument is not null)] : []; + + if (provider.GetRequiredKeyedService(descriptor.TemplateType, descriptor.Key) + is object template) + { + if (region is not null) + { + switch (region) + { + case "self": + region = template; + break; + + default: + if (navigationRegionProvider.TryGet(region, out object? value)) + { + region = value; + } + + break; + } + } + + if (region is not null) + { + object? content = contentFactory.Create(descriptor, resolvedArguments); + if (content is not null) + { + Type navigationType = region is Type type ? type : region.GetType(); + Type navigateEventType = typeof(NavigateEventArgs<>).MakeGenericType(navigationType); + + if (Activator.CreateInstance(navigateEventType, [region, template, content, sender, parameters]) + is object navigateEvent) + { + publisher.Publish(navigateEvent, navigationType.Name); + if (currentSegmentIndex == segmentCount) + { + navigated?.Invoke(this, EventArgs.Empty); + } + } + } + } + } + } + } + } + + public void Back(object? region) + { + if (region is not null) + { + navigationRegionProvider.TryGet(region, out region); + } + + if (region is not null) + { + Type navigationType = region is Type type ? type : region.GetType(); + Type navigateType = typeof(NavigateBackEventArgs<>).MakeGenericType(navigationType); + if (Activator.CreateInstance(navigateType, [region]) is object navigate) + { + publisher.Publish(navigate, navigationType.Name); + } + } + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/NavigationChanged.cs b/Toolkit.Foundation/NavigationChanged.cs deleted file mode 100644 index 6545fd0..0000000 --- a/Toolkit.Foundation/NavigationChanged.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Toolkit.Foundation; - -public record NavigationChanged(TValue? Value) : - INotification; \ No newline at end of file diff --git a/Toolkit.Foundation/NavigationContextAttribute.cs b/Toolkit.Foundation/NavigationContextAttribute.cs deleted file mode 100644 index 651c12d..0000000 --- a/Toolkit.Foundation/NavigationContextAttribute.cs +++ /dev/null @@ -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; } -} diff --git a/Toolkit.Foundation/NavigationContextCollection.cs b/Toolkit.Foundation/NavigationContextCollection.cs deleted file mode 100644 index a36d908..0000000 --- a/Toolkit.Foundation/NavigationContextCollection.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Toolkit.Foundation; - -public class NavigationContextCollection : Dictionary, - INavigationContextCollection; diff --git a/Toolkit.Foundation/NavigationProvider.cs b/Toolkit.Foundation/NavigationProvider.cs deleted file mode 100644 index 2135e07..0000000 --- a/Toolkit.Foundation/NavigationProvider.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Toolkit.Foundation; - -public class NavigationProvider(IEnumerable navigations) : - INavigationProvider -{ - public INavigation? Get(Type type) - { - if (navigations.FirstOrDefault(x => type == x.Type || - type.BaseType == x.Type) is INavigation navigation) - { - return navigation; - } - - return default; - } -} diff --git a/Toolkit.Foundation/NavigationRegionCollection.cs b/Toolkit.Foundation/NavigationRegionCollection.cs new file mode 100644 index 0000000..b1c2f5a --- /dev/null +++ b/Toolkit.Foundation/NavigationRegionCollection.cs @@ -0,0 +1,4 @@ +namespace Toolkit.Foundation; + +public class NavigationRegionCollection : Dictionary, + INavigationRegionCollection; \ No newline at end of file diff --git a/Toolkit.Foundation/NavigationContextProvider.cs b/Toolkit.Foundation/NavigationRegionProvider.cs similarity index 53% rename from Toolkit.Foundation/NavigationContextProvider.cs rename to Toolkit.Foundation/NavigationRegionProvider.cs index 74865bb..eeebceb 100644 --- a/Toolkit.Foundation/NavigationContextProvider.cs +++ b/Toolkit.Foundation/NavigationRegionProvider.cs @@ -1,15 +1,15 @@ namespace Toolkit.Foundation; -public class NavigationContextProvider(INavigationContextCollection contexts) : - INavigationContextProvider +public class NavigationRegionProvider(INavigationRegionCollection collection) : + INavigationRegionProvider { public object? Get(object key) => - contexts.TryGetValue(key, out object? target) ? target : default; + collection.TryGetValue(key, out object? target) ? target : default; - public bool TryGet(object name, + public bool TryGet(object name, out object? value) { - if (contexts.TryGetValue(name, + if (collection.TryGetValue(name, out object? target)) { value = target; @@ -21,4 +21,4 @@ public class NavigationContextProvider(INavigationContextCollection contexts) : return false; } } -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/NavigationScope.cs b/Toolkit.Foundation/NavigationScope.cs deleted file mode 100644 index 374cb41..0000000 --- a/Toolkit.Foundation/NavigationScope.cs +++ /dev/null @@ -1,104 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace Toolkit.Foundation; - -public class NavigationScope(IPublisher publisher, - IServiceProvider serviceProvider, - IServiceFactory serviceFactory, - INavigationProvider navigationProvider, - INavigationContextProvider navigationContextProvider, - IContentTemplateDescriptorProvider contentTemplateDescriptorProvider) : - INavigationScope -{ - public async Task NavigateAsync(string route, object? sender = null, object? context = null, - EventHandler? navigated = null, object[]? parameters = null, CancellationToken cancellationToken = default) - { - string[] segments = route.Split('/'); - int segmentCount = segments.Length; - int currentSegmentIndex = 0; - - foreach (object segment in segments) - { - currentSegmentIndex++; - - if (contentTemplateDescriptorProvider.Get(segment) - is IContentTemplateDescriptor descriptor) - { - Dictionary? arguments = parameters?.OfType>() - .ToDictionary(x => x.Key, x => x.Value, StringComparer.InvariantCultureIgnoreCase) ?? []; - - IEnumerable? mappedParameters = descriptor.ContentType - .GetConstructors() - .FirstOrDefault()? - .GetParameters() - .Select(parameter => parameter?.Name is not null && arguments - .TryGetValue(parameter.Name, out object? argument) ? argument : default) - .Where(argument => argument is not null); - - parameters = [.. parameters?.Where(x => x.GetType() != typeof(KeyValuePair)) ?? - Enumerable.Empty(), - .. mappedParameters ?? Enumerable.Empty()]; - - if (serviceProvider.GetRequiredKeyedService(descriptor.TemplateType, segment) is object view) - { - if ((parameters is { Length: > 0 } - ? serviceFactory.Create(descriptor.ContentType, parameters) - : serviceProvider.GetRequiredKeyedService(descriptor.ContentType, segment)) is object viewModel) - { - if (context is not null) - { - if (navigationContextProvider.TryGet(context, out object? scopedContext)) - { - context = scopedContext; - } - } - else - { - context = view; - } - - if (context is not null) - { - if (navigationProvider.Get(context is Type type ? type : context.GetType()) - is INavigation navigation) - { - Type navigateType = typeof(Navigate<>).MakeGenericType(navigation.Type); - if (Activator.CreateInstance(navigateType, [context, view, viewModel, sender, parameters]) is object navigate) - { - await publisher.Publish(navigate, cancellationToken); - if (currentSegmentIndex == segmentCount) - { - navigated?.Invoke(this, EventArgs.Empty); - } - } - } - } - } - } - } - } - } - - public async Task NavigateBackAsync(object? context, - CancellationToken cancellationToken = default) - { - if (context is not null) - { - navigationContextProvider.TryGet(context, out context); - } - - if (context is not null) - { - if (navigationProvider.Get(context is Type type ? type : context.GetType()) - is INavigation navigation) - { - Type navigateType = typeof(NavigateBack<>).MakeGenericType(navigation.Type); - if (Activator.CreateInstance(navigateType, [context]) is object navigate) - { - await publisher.Publish(navigate, cancellationToken); - } - } - } - } -} - diff --git a/Toolkit.Foundation/NavigationTargetAttribute.cs b/Toolkit.Foundation/NavigationTargetAttribute.cs deleted file mode 100644 index 29bc568..0000000 --- a/Toolkit.Foundation/NavigationTargetAttribute.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace HyperX.UI.Windows; - -[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] -public class NavigationTargetAttribute(string name) : - Attribute -{ - public string Name => name; -} diff --git a/Toolkit.Foundation/NavigationViewModel.cs b/Toolkit.Foundation/NavigationViewModel.cs deleted file mode 100644 index f9052e7..0000000 --- a/Toolkit.Foundation/NavigationViewModel.cs +++ /dev/null @@ -1,31 +0,0 @@ -using CommunityToolkit.Mvvm.ComponentModel; - -namespace Toolkit.Foundation; - -public partial class NavigationViewModel(IServiceProvider serviceProvider, - IServiceFactory serviceFactory, - IPublisher publisher, - ISubscriber subscriber, - IDisposer disposer, - string text) : - ObservableViewModel(serviceProvider, serviceFactory, publisher, subscriber, disposer), - INavigationViewModel -{ - [ObservableProperty] - private string? text = text; -} - -public partial class NavigationViewModel(IServiceProvider serviceProvider, - IServiceFactory serviceFactory, - IPublisher publisher, - ISubscriber subscriber, - IDisposer disposer, - string text) : - ObservableViewModel(serviceProvider, serviceFactory, publisher, subscriber, disposer), - INavigationViewModel - where TNavigationViewModel : - INavigationViewModel -{ - [ObservableProperty] - private string? text = text; -} diff --git a/Toolkit.Foundation/NotificationAttribute.cs b/Toolkit.Foundation/NotificationAttribute.cs index d178ddd..00c7d47 100644 --- a/Toolkit.Foundation/NotificationAttribute.cs +++ b/Toolkit.Foundation/NotificationAttribute.cs @@ -1,7 +1,10 @@ namespace Toolkit.Foundation; -[AttributeUsage(AttributeTargets.Class, Inherited = false)] -public class NotificationAttribute(object key) : Attribute +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] +public class NotificationAttribute(Type type, + object key) : Attribute { + public Type Type => type; + public object Key => key; } \ No newline at end of file diff --git a/Toolkit.Foundation/NotificationHandlerDelegate.cs b/Toolkit.Foundation/NotificationHandlerDelegate.cs index 2237a3a..f0e6031 100644 --- a/Toolkit.Foundation/NotificationHandlerDelegate.cs +++ b/Toolkit.Foundation/NotificationHandlerDelegate.cs @@ -1,5 +1,3 @@ namespace Toolkit.Foundation; -public delegate Task NotificationHandlerDelegate(TNotification notification, - CancellationToken cancellationToken) - where TNotification : INotification; +public delegate Task NotificationHandlerDelegate(TMessage message); \ No newline at end of file diff --git a/Toolkit.Foundation/NotificationHandlerWrapper.cs b/Toolkit.Foundation/NotificationHandlerWrapper.cs index 100165a..e012ab3 100644 --- a/Toolkit.Foundation/NotificationHandlerWrapper.cs +++ b/Toolkit.Foundation/NotificationHandlerWrapper.cs @@ -1,25 +1,23 @@ namespace Toolkit.Foundation; -public class NotificationHandlerWrapper(INotificationHandler handler, - IEnumerable> pipelineBehaviours) - where TNotification : INotification +public class NotificationHandlerWrapper(INotificationHandler handler, + IEnumerable> pipelineBehaviours) { - private readonly IEnumerable> pipelineBehaviours = + private readonly IEnumerable> pipelineBehaviours = pipelineBehaviours.Reverse(); - public async Task Handle(TNotification notification, - CancellationToken cancellationToken) + public async Task Handle(TMessage message) { - NotificationHandlerDelegate currentHandler = handler.Handle; - foreach (IPipelineBehavior behavior in pipelineBehaviours) + NotificationHandlerDelegate currentHandler = handler.Handle; + foreach (IPipelineBehaviour behaviour in pipelineBehaviours) { - NotificationHandlerDelegate previousHandler = currentHandler; - currentHandler = async (args, token) => + NotificationHandlerDelegate previousHandler = currentHandler; + currentHandler = async (args) => { - await behavior.Handle(args, previousHandler, token); + await behaviour.Handle(args, previousHandler); }; } - - await currentHandler(notification, cancellationToken); + + await currentHandler(message); } -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/Notify.cs b/Toolkit.Foundation/Notify.cs new file mode 100644 index 0000000..367692c --- /dev/null +++ b/Toolkit.Foundation/Notify.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation; + +public class Notify +{ + public static NotifyEventArgs As(TSender sender) => new(sender); + + public static NotifyEventArgs As() where TSender : new() => new(new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/NotifyEventArgs.cs b/Toolkit.Foundation/NotifyEventArgs.cs new file mode 100644 index 0000000..6803ca5 --- /dev/null +++ b/Toolkit.Foundation/NotifyEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record NotifyEventArgs(TSender? Sender = default); \ No newline at end of file diff --git a/Toolkit.Foundation/ObjectExtensions.cs b/Toolkit.Foundation/ObjectExtensions.cs index 9cad647..3ffcf4c 100644 --- a/Toolkit.Foundation/ObjectExtensions.cs +++ b/Toolkit.Foundation/ObjectExtensions.cs @@ -9,8 +9,7 @@ public static class ObjectExtensions Type type = obj.GetType(); object? key = selector(); - if (type.GetProperty($"{key}") is PropertyInfo property - && property.GetValue(obj) is { } value) + if (type.GetProperty($"{key}") is PropertyInfo property && property.GetValue(obj) is { } value) { return value; } @@ -18,15 +17,27 @@ public static class ObjectExtensions return null; } - public static TAttribute? GetAttribute(this object obj) + public static TAttribute? GetAttribute(this object obj) where TAttribute : Attribute { Type type = obj.GetType(); - if (type.GetAttribute() is TAttribute attribute) + if (type.GetCustomAttribute(true) is TAttribute attribute) { return attribute; } return null; } + + public static IEnumerable GetAttributes(this object obj) + where TAttribute : Attribute + { + Type type = obj.GetType(); + if (type.GetCustomAttributes(true) is IEnumerable attributes) + { + return attributes; + } + + return Enumerable.Empty(); + } } \ No newline at end of file diff --git a/Toolkit.Foundation/Observable.cs b/Toolkit.Foundation/Observable.cs new file mode 100644 index 0000000..cfd0abe --- /dev/null +++ b/Toolkit.Foundation/Observable.cs @@ -0,0 +1,162 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace Toolkit.Foundation; + +public partial class Observable(IServiceProvider provider, + IServiceFactory factory, + IMediator mediator, + IPublisher publisher, + ISubscriber subscriber, + IDisposer disposer) : + ObservableObject, + IObservableViewModel, + IActivityIndicator, + IInitialization, + IActivated, + IDeactivating, + IDeactivated, + IDisposable, + IServiceProviderRequired, + IServiceFactoryRequired, + IMediatorRequired, + IPublisherRequired, + IDisposerRequired +{ + private readonly Dictionary trackedProperties = []; + + [ObservableProperty] + private bool isActivated; + + [ObservableProperty] + private bool isActive; + + [ObservableProperty] + private bool isInitialized; + + public IDisposer Disposer { get; } = disposer; + + public IServiceFactory Factory { get; } = factory; + + public IMediator Mediator { get; } = mediator; + + public IServiceProvider Provider { get; } = provider; + + public IPublisher Publisher { get; } = publisher; + + public ISubscriber Subscriber { get; } = subscriber; + + public virtual Task OnActivated() + { + IsActivated = true; + return Task.CompletedTask; + } + + public void Commit() + { + foreach (object trackedProperty in trackedProperties.Values) + { + ((dynamic)trackedProperty).Commit(); + } + } + + public virtual Task OnDeactivated() + { + IsActivated = false; + return Task.CompletedTask; + } + + public virtual Task OnDeactivating() => + Task.CompletedTask; + + public virtual void Dispose() + { + GC.SuppressFinalize(this); + Disposer.Dispose(this); + } + + public virtual void OnInitialize() + { + } + + public virtual void Initialize() + { + if (IsInitialized) + { + return; + } + + IsInitialized = true; + Subscriber.Subscribe(this); + OnInitialize(); + } + + public void Revert() + { + foreach (object trackedProperty in trackedProperties.Values) + { + ((dynamic)trackedProperty).Revert(); + } + } + + public void Track(string propertyName, Func getter, Action setter) + { + if (!trackedProperties.ContainsKey(propertyName)) + { + T initialValue = getter(); + trackedProperties[propertyName] = new TrackedProperty(initialValue, setter, getter); + } + } +} + +public partial class Observable : + Observable +{ + [ObservableProperty] + private TValue? value; + + public Observable(IServiceProvider provider, + IServiceFactory factory, + IMediator mediator, + IPublisher publisher, + ISubscriber subscriber, + IDisposer disposer, + TValue? value = default) : base(provider, factory, mediator, publisher, subscriber, disposer) + { + Value = value; + } + + protected virtual void OnChanged(TValue? value) + { + } + + partial void OnValueChanged(TValue? value) => OnChanged(value); +} + +public partial class Observable : + Observable +{ + [ObservableProperty] + private TKey key; + + [ObservableProperty] + private TValue? value; + + public Observable(IServiceProvider provider, + IServiceFactory factory, + IMediator mediator, + IPublisher publisher, + ISubscriber subscriber, + IDisposer disposer, + TKey key, + TValue? value = default) : base(provider, factory, mediator, publisher, subscriber, disposer) + { + Key = key; + Value = value; + } + + protected virtual void OnValueChanged() + { + } + + partial void OnValueChanged(TValue? value) => OnValueChanged(); +} \ No newline at end of file diff --git a/Toolkit.Foundation/ObservableCollection.cs b/Toolkit.Foundation/ObservableCollection.cs new file mode 100644 index 0000000..5453673 --- /dev/null +++ b/Toolkit.Foundation/ObservableCollection.cs @@ -0,0 +1,902 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Microsoft.Extensions.DependencyInjection; +using System.Collections; +using System.Collections.Specialized; +using System.Reactive.Disposables; + +namespace Toolkit.Foundation; + +public partial class ObservableCollection : + ObservableObject, + IObservableCollectionViewModel, + IInitialization, + IActivated, + IDeactivating, + IDeactivated, + IList, + IList, + IReadOnlyList, + INotifyCollectionChanged, + ICollectionSynchronization, + IServiceProviderRequired, + IServiceFactoryRequired, + IMediatorRequired, + IPublisherRequired, + IDisposerRequired, + INotificationHandler>, + INotificationHandler>, + INotificationHandler>, + INotificationHandler>, + INotificationHandler>, + INotificationHandler>, + INotificationHandler>, + INotificationHandler> + where TViewModel : notnull, + IDisposable +{ + private readonly System.Collections.ObjectModel.ObservableCollection collection = []; + + private readonly IDispatcher dispatcher; + + private readonly Queue pendingEvents = []; + + private readonly Dictionary trackedProperties = []; + + [ObservableProperty] + private int count; + + private Func? defaultSelectionFactory; + + [ObservableProperty] + private bool isActivated; + + private bool isClearing; + + [ObservableProperty] + private bool isInitialized; + + [ObservableProperty] + private TViewModel? selectedItem; + + public ObservableCollection(IServiceProvider provider, + IServiceFactory factory, + IMediator mediator, + IPublisher publisher, + ISubscriber subscriber, + IDisposer disposer) + { + Provider = provider; + Factory = factory; + Mediator = mediator; + Publisher = publisher; + Subscriber = subscriber; + Disposer = disposer; + + dispatcher = Provider.GetRequiredService(); + collection.CollectionChanged += OnCollectionChanged; + } + + public ObservableCollection(IServiceProvider provider, + IServiceFactory factory, + IMediator mediator, + IPublisher publisher, + ISubscriber subscriber, + IDisposer disposer, + IEnumerable items) + { + Provider = provider; + Factory = factory; + Mediator = mediator; + Publisher = publisher; + Subscriber = subscriber; + Disposer = disposer; + + dispatcher = Provider.GetRequiredService(); + collection.CollectionChanged += OnCollectionChanged; + + AddRange(items); + } + + public event NotifyCollectionChangedEventHandler? CollectionChanged; + + public IDisposer Disposer { get; private set; } + + public IServiceFactory Factory { get; private set; } + + bool IList.IsFixedSize => false; + + bool ICollection.IsReadOnly => false; + + bool IList.IsReadOnly => false; + + bool ICollection.IsSynchronized => false; + + public IMediator Mediator { get; } + + public IServiceProvider Provider { get; private set; } + + public IPublisher Publisher { get; private set; } + + public ISubscriber Subscriber { get; } + + object ICollection.SyncRoot => this; + + public TViewModel this[int index] + { + get => collection[index]; + set => SetItem(index, value); + } + + object? IList.this[int index] + { + get => index >= 0 && collection.Count > 0 ? collection[index] : null; + set + { + TViewModel? item = default; + + try + { + item = (TViewModel)value!; + } + catch (InvalidCastException) + { + } + + this[index] = item!; + } + } + + public void Activate(Func activateDelegate, + bool reset = false) + { + if (reset) + { + Clear(); + } + + ActivationBuilder builder = activateDelegate.Invoke(); + Publisher.Publish(builder.Value, builder.Key); + } + + public void Activate(bool reset = false) + { + if (reset) + { + Clear(); + } + + ActivationBuilder builder = ActivationBuilder(); + Publisher.PublishUI(builder.Value, builder.Key); + } + + public TViewModel Add(params object?[] parameters) + where T : TViewModel + { + T? item = Factory.Create(args => + { + if (args is IInitialization initialization) + { + initialization.Initialize(); + } + }, parameters); + + Add(item); + return item; + } + + public void Add(TViewModel item) + { + int index = collection.Count; + InsertItem(index, item); + + UpdateSelection(item); + } + + public void Add(object item) + { + int index = collection.Count; + InsertItem(index, (TViewModel)item); + } + + int IList.Add(object? value) + { + TViewModel? item = default; + + try + { + item = (TViewModel)value!; + } + catch (InvalidCastException) + { + } + + Add(item!); + return Count - 1; + } + + public void AddRange(IEnumerable items) + { + foreach (TViewModel? item in items) + { + if (item is IInitialization initialization) + { + initialization.Initialize(); + } + + Add(item); + } + } + + public void Clear(bool disposeItems = false) + { + isClearing = true; + if (disposeItems) + { + foreach (TViewModel item in this.ToList()) + { + Disposer.Dispose(item); + Disposer.Remove(this, item); + } + } + + ClearItems(); + isClearing = false; + } + + public void Clear() + { + isClearing = true; + foreach (TViewModel item in this.ToList()) + { + Disposer.Dispose(item); + Disposer.Remove(this, item); + } + + ClearItems(); + isClearing = false; + } + + public void Commit() + { + foreach (object trackedProperty in trackedProperties.Values) + { + ((dynamic)trackedProperty).Commit(); + } + } + + public bool Contains(TViewModel item) => + collection.Contains(item); + + bool IList.Contains(object? value) => + IsCompatibleObject(value) && Contains((TViewModel)value!); + + public void CopyTo(TViewModel[] array, int index) => + collection.CopyTo(array, index); + + void ICollection.CopyTo(Array array, int index) => + collection.CopyTo((TViewModel[])array, index); + + public virtual void Dispose() + { + GC.SuppressFinalize(this); + Disposer.Dispose(this); + } + + public IEnumerator GetEnumerator() => + collection.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => + ((IEnumerable)collection).GetEnumerator(); + + public Task Handle(RemoveEventArgs args) + { + if (IsActivated) + { + foreach (TViewModel item in this.ToList()) + { + if (args.Sender is not null && args.Sender.Equals(item)) + { + Remove(item); + } + } + } + else + { + pendingEvents.Enqueue(args); + } + + return Task.CompletedTask; + } + + public Task Handle(CreateEventArgs args) + { + if (IsActivated) + { + if (args.Sender is TViewModel item) + { + Add(item); + } + } + else + { + pendingEvents.Enqueue(args); + } + + return Task.CompletedTask; + } + + public Task Handle(InsertEventArgs args) + { + if (IsActivated) + { + if (args.Sender is TViewModel item) + { + Insert(args.Index, item); + } + } + else + { + pendingEvents.Enqueue(args); + } + + return Task.CompletedTask; + } + + public Task Handle(MoveToEventArgs args) + { + if (IsActivated) + { + Move(args.OldIndex, args.NewIndex); + } + else + { + pendingEvents.Enqueue(args); + } + + return Task.CompletedTask; + } + + public Task Handle(MoveEventArgs args) + { + if (IsActivated) + { + if (args.Sender is TViewModel item) + { + Move(args.Index, item); + } + } + else + { + pendingEvents.Enqueue(args); + } + + return Task.CompletedTask; + } + + public Task Handle(ReplaceEventArgs args) + { + if (IsActivated) + { + if (args.Sender is TViewModel item) + { + Replace(args.Index, item); + } + } + else + { + pendingEvents.Enqueue(args); + } + + return Task.CompletedTask; + } + + public Task Handle(RemoveAtEventArgs args) + { + if (IsActivated) + { + int index = args.Index; + if (index >= 0 && index <= Count - 1) + { + RemoveAt(index); + } + } + else + { + pendingEvents.Enqueue(args); + } + + return Task.CompletedTask; + } + + public Task Handle(SelectionEventArgs args) => + Task.CompletedTask; + + public int IndexOf(TViewModel item) => + collection.IndexOf(item); + + int IList.IndexOf(object? value) => + IsCompatibleObject(value) ? + IndexOf((TViewModel)value!) : -1; + + [RelayCommand] + public virtual void Initialize() + { + if (IsInitialized) + { + return; + } + + IsInitialized = true; + Subscriber.Subscribe(this); + + OnInitialize(); + + Activate(); + } + + public TViewModel Insert(int index = 0, + params object?[] parameters) + where T : + TViewModel + { + T? item = Factory.Create(args => + { + if (args is IInitialization initialization) + { + initialization.Initialize(); + } + }, parameters); + + InsertItem(index, item); + UpdateSelection(item); + + return item; + } + + public void Insert(int index, + TViewModel item) + { + InsertItem(index, item); + UpdateSelection(item); + } + + void IList.Insert(int index, + object? value) + { + if (value is TViewModel item) + { + Insert(index, item); + UpdateSelection(item); + } + } + + public bool Move(int oldIndex, int newIndex) + { + if (oldIndex < 0) + { + return false; + } + + TViewModel item = this[oldIndex]; + + bool moveSelection = false; + if (item is ISelectable oldSelection) + { + if (oldSelection.IsSelected) + { + moveSelection = true; + SelectedItem = default; + } + } + + RemoveItem(oldIndex); + InsertItem(newIndex, item); + + if (moveSelection) + { + if (item is ISelectable newSelection) + { + newSelection.IsSelected = true; + dispatcher.Invoke(() => SelectedItem = item); + } + } + + return true; + } + + public bool Move(int index, TViewModel item) + { + int oldIndex = collection.IndexOf(item); + if (oldIndex < 0) + { + return false; + } + + RemoveItem(oldIndex); + Insert(index, item); + + return true; + } + + public virtual Task OnActivated() + { + IsActivated = true; + while (pendingEvents.Count > 0) + { + object current = pendingEvents.Dequeue(); + Handle((dynamic)current); + } + + return Task.CompletedTask; + } + + public virtual Task OnDeactivated() + { + IsActivated = false; + return Task.CompletedTask; + } + + public virtual Task OnDeactivating() => + Task.CompletedTask; + + public virtual void OnInitialize() + { + } + + public bool Remove(TViewModel item) + { + int index = collection.IndexOf(item); + if (index < 0) + { + return false; + } + + Disposer.Dispose(item); + Disposer.Remove(this, item); + + TViewModel? oldSelection = SelectedItem; + RemoveItem(index); + + if (item.Equals(oldSelection)) + { + int newIndex = Math.Min(index, Count - 1); + TViewModel? selectedItem = newIndex >= 0 ? this[newIndex] : default; + dispatcher.Invoke(() => SelectedItem = selectedItem); + } + + return true; + } + + void IList.Remove(object? value) + { + if (IsCompatibleObject(value)) + { + Remove((TViewModel)value!); + } + } + + public void RemoveAt(int index) => + RemoveItem(index); + + public bool Replace(int index, + params object?[] parameters) + where T : + TViewModel + { + if (index <= Count - 1) + { + RemoveItem(index); + } + else + { + index = Count; + } + + T? item = Factory.Create(args => + { + if (args is IInitialization initialization) + { + initialization.Initialize(); + } + }, parameters); + + Insert(index, item); + return true; + } + + public bool Replace(int index, + TViewModel item) + { + if (index <= Count - 1) + { + RemoveItem(index); + } + else + { + index = Count; + } + + Insert(index, item); + return true; + } + + public void Reset(Action> factory, bool disposeItems = true) + { + SelectedItem = default; + + Clear(disposeItems); + factory.Invoke(this); + } + + public void Revert() + { + foreach (object trackedProperty in trackedProperties.Values) + { + ((dynamic)trackedProperty).Revert(); + } + } + + public void SetSource(IList source, Func? defaultSelectionFactory) + { + foreach (TViewModel item in source) + { + Add(item); + } + + if (defaultSelectionFactory is not null) + { + this.defaultSelectionFactory = defaultSelectionFactory; + SelectedItem = defaultSelectionFactory.Invoke(); + } + + if (source is INotifyCollectionChanged observableSource) + { + observableSource.CollectionChanged -= SourceCollectionChanged; + observableSource.CollectionChanged += SourceCollectionChanged; + } + } + + public void Track(string propertyName, Func getter, Action setter) + { + if (!trackedProperties.ContainsKey(propertyName)) + { + T initialValue = getter(); + trackedProperties[propertyName] = new TrackedProperty(initialValue, setter, getter); + } + } + + protected virtual ActivationBuilder ActivationBuilder() => + new(new ActivationEventArgs()); + + protected virtual void ClearItems() => + collection.Clear(); + + protected virtual void InsertItem(int index, + TViewModel item) + { + Disposer.Add(this, item); + Disposer.Add(item, Disposable.Create(() => + { + if (item is IDisposable && !isClearing) + { + if (item is IList collection) + { + collection.Clear(); + } + + Remove(item); + } + })); + + collection.Insert(index > Count ? Count : index, item); + } + + protected virtual void OnSelectedItemChanged() + { + } + + protected virtual void RemoveItem(int index) => + collection.RemoveAt(index); + + protected virtual void SetItem(int index, TViewModel item) => + collection[index] = item; + + private static bool IsCompatibleObject(object? value) => + (value is TViewModel) || (value == null && default(TViewModel) == null); + + private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs args) + { + Count = collection.Count; + CollectionChanged?.Invoke(this, args); + } + + partial void OnIsActivatedChanged(bool value) + { + if (value) + { + while (pendingEvents.Count > 0) + { + object current = pendingEvents.Dequeue(); + Handle((dynamic)current); + } + } + } + + partial void OnSelectedItemChanged(TViewModel? oldValue, TViewModel? newValue) + { + if (oldValue is ISelectable oldSelection) + { + oldSelection.IsSelected = false; + } + + if (newValue is ISelectable newSelection) + { + newSelection.IsSelected = true; + } + + Publisher.Publish(Selection.As(SelectedItem)); + OnSelectedItemChanged(); + } + + private void SourceCollectionChanged(object? sender, + NotifyCollectionChangedEventArgs args) + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + if (args.NewItems is not null) + { + foreach (TViewModel newItem in args.NewItems) + { + Add(newItem); + } + } + break; + + case NotifyCollectionChangedAction.Remove: + if (args.OldItems is not null) + { + foreach (TViewModel oldItem in args.OldItems) + { + if (this.FirstOrDefault(x => x.Equals(oldItem)) is TViewModel removedItem) + { + Remove(removedItem); + } + } + } + break; + + case NotifyCollectionChangedAction.Reset: + + Clear(); + if (sender is IEnumerable collection) + { + foreach (TViewModel item in collection) + { + Add(item); + } + + if (defaultSelectionFactory is not null) + { + SelectedItem = defaultSelectionFactory.Invoke(); + } + } + break; + } + } + private void UpdateSelection(TViewModel item) + { + if (item is ISelectable newSelection) + { + if (newSelection.IsSelected) + { + if (SelectedItem is ISelectable oldSelection) + { + oldSelection.IsSelected = false; + } + + dispatcher.Invoke(() => SelectedItem = item); + } + } + } +} + +public partial class ObservableCollection : + ObservableCollection + where TViewModel : IDisposable +{ + [ObservableProperty] + private TValue? value; + + public ObservableCollection(IServiceProvider provider, + IServiceFactory factory, + IMediator mediator, + IPublisher publisher, + ISubscriber subscriber, + IDisposer disposer, + TValue? value = default) : base(provider, factory, mediator, publisher, subscriber, disposer) + { + Value = value; + } + + public ObservableCollection(IServiceProvider provider, + IServiceFactory factory, + IMediator mediator, + IPublisher publisher, + ISubscriber subscriber, + IDisposer disposer, + IEnumerable items, + TValue? value = default) : base(provider, factory, mediator, publisher, subscriber, disposer, items) + { + Value = value; + } + + protected virtual void OnChanged(TValue? value) + { + + } + + partial void OnValueChanged(TValue? value) => OnChanged(value); +} + +public partial class ObservableCollection : + ObservableCollection + where TViewModel : IDisposable +{ + [ObservableProperty] + private TKey key; + + [ObservableProperty] + private TValue? value; + + public ObservableCollection(IServiceProvider provider, + IServiceFactory factory, + IMediator mediator, + IPublisher publisher, + ISubscriber subscriber, + IDisposer disposer, + TKey key, + TValue? value = default) : base(provider, factory, mediator, publisher, subscriber, disposer) + { + Key = key; + Value = value; + } + + public ObservableCollection(IServiceProvider provider, + IServiceFactory factory, + IMediator mediator, + IPublisher publisher, + ISubscriber subscriber, + IDisposer disposer, + IEnumerable items, + TKey key, + TValue? value = default) : base(provider, factory, mediator, publisher, subscriber, disposer, items) + { + Key = key; + Value = value; + } +} + +public class ObservableCollection : + ObservableCollection +{ + public ObservableCollection(IServiceProvider provider, + IServiceFactory factory, + IMediator mediator, + IPublisher publisher, + ISubscriber subscriber, + IDisposer disposer) : base(provider, factory, mediator, publisher, subscriber, disposer) + { + } + + public ObservableCollection(IServiceProvider provider, + IServiceFactory factory, + IMediator mediator, + IPublisher publisher, + ISubscriber subscriber, + IDisposer disposer, + IEnumerable items) : base(provider, factory, mediator, publisher, subscriber, disposer, items) + { + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/ObservableCollectionViewModel.cs b/Toolkit.Foundation/ObservableCollectionViewModel.cs deleted file mode 100644 index 81c2712..0000000 --- a/Toolkit.Foundation/ObservableCollectionViewModel.cs +++ /dev/null @@ -1,415 +0,0 @@ -using CommunityToolkit.Mvvm.ComponentModel; -using System.Collections; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Reactive.Disposables; - -namespace Toolkit.Foundation; -public partial class ObservableCollectionViewModel : - ObservableObject, - IObservableCollectionViewModel, - IInitializer, - IActivated, - IDeactivating, - IDeactivated, - IDeactivatable, - IList, - IList, - IReadOnlyList, - INotifyCollectionChanged, - INotificationHandler>, - INotificationHandler>, - INotificationHandler>, - INotificationHandler>, - INotificationHandler> - where TViewModel : - notnull -{ - private readonly ObservableCollection collection = []; - - [ObservableProperty] - private bool isInitialized; - - public ObservableCollectionViewModel(IServiceProvider serviceProvider, - IServiceFactory serviceFactory, - IPublisher publisher, - ISubscriber subscriber, - IDisposer disposer) - { - ServiceProvider = serviceProvider; - ServiceFactory = serviceFactory; - Publisher = publisher; - Disposer = disposer; - - subscriber.Add(this); - - collection.CollectionChanged += OnCollectionChanged; - } - - public ObservableCollectionViewModel(IServiceProvider serviceProvider, - IServiceFactory serviceFactory, - IPublisher publisher, - ISubscriber subscriber, - IDisposer disposer, - IEnumerable items) - { - ServiceProvider = serviceProvider; - ServiceFactory = serviceFactory; - Publisher = publisher; - Disposer = disposer; - - subscriber.Add(this); - - collection.CollectionChanged += OnCollectionChanged; - AddRange(items); - } - - public event NotifyCollectionChangedEventHandler? CollectionChanged; - - public event EventHandler? DeactivateHandler; - - public int Count => collection.Count; - - public IDisposer Disposer { get; private set; } - - bool IList.IsFixedSize => false; - - bool ICollection.IsReadOnly => false; - - bool IList.IsReadOnly => false; - - bool ICollection.IsSynchronized => false; - - public IPublisher Publisher { get; private set; } - - public IServiceFactory ServiceFactory { get; private set; } - - public IServiceProvider ServiceProvider { get; private set; } - - object ICollection.SyncRoot => this; - - public TViewModel this[int index] - { - get => collection[index]; - set => SetItem(index, value); - } - - object? IList.this[int index] - { - get => collection[index]; - set - { - TViewModel? item = default; - - try - { - item = (TViewModel)value!; - } - catch (InvalidCastException) - { - - } - - this[index] = item!; - } - } - - public virtual Task Activated() => - Task.CompletedTask; - - public TViewModel Add() - { - TViewModel? item = ServiceFactory.Create(); - - Add(item); - return item; - } - - public TViewModel Add(params object?[] parameters) - where T : TViewModel - { - T? item = ServiceFactory.Create(parameters); - Add(item); - - return item; - } - - public TViewModel Add() - where T : - TViewModel - { - T? item = ServiceFactory.Create(); - Add(item); - - return item; - } - - public void Add(TViewModel item) - { - int index = collection.Count; - InsertItem(index, item); - } - - public void Add(object item) - { - int index = collection.Count; - InsertItem(index, (TViewModel)item); - } - - int IList.Add(object? value) - { - TViewModel? item = default; - - try - { - item = (TViewModel)value!; - } - catch (InvalidCastException) - { - - } - - Add(item!); - return Count - 1; - } - - public void AddRange(IEnumerable items) - { - foreach (TViewModel? item in items) - { - Add(item); - } - } - - public void Clear() - { - foreach (TViewModel item in collection) - { - Disposer.Dispose(item); - } - - ClearItems(); - } - - public bool Contains(TViewModel item) => - collection.Contains(item); - - bool IList.Contains(object? value) => - IsCompatibleObject(value) && Contains((TViewModel)value!); - - public void CopyTo(TViewModel[] array, int index) => - collection.CopyTo(array, index); - - void ICollection.CopyTo(Array array, int index) => - collection.CopyTo((TViewModel[])array, index); - - public Task Deactivate() - { - DeactivateHandler?.Invoke(this, new EventArgs()); - return Task.CompletedTask; - } - - public virtual Task Deactivated() => - Task.CompletedTask; - - public virtual Task Deactivating() => - Task.CompletedTask; - - public virtual void Dispose() - { - GC.SuppressFinalize(this); - Disposer.Dispose(this); - } - - public IEnumerator GetEnumerator() => - collection.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => - ((IEnumerable)collection).GetEnumerator(); - - public Task Handle(Remove args, - CancellationToken cancellationToken) - { - foreach (TViewModel item in this.ToList()) - { - if (args.Value is not null && args.Value.Equals(item)) - { - Remove(item); - } - } - - return Task.CompletedTask; - } - - public Task Handle(Create args, - CancellationToken cancellationToken) - { - if (args.Value is TViewModel item) - { - Add(item); - } - - return Task.CompletedTask; - } - - public Task Handle(Insert args, - CancellationToken cancellationToken) - { - if (args.Value is TViewModel item) - { - Insert(args.Index, item); - } - - return Task.CompletedTask; - } - - public Task Handle(Move args, - CancellationToken cancellationToken) - { - if (args.Value is TViewModel item) - { - Move(args.Index, item); - } - - return Task.CompletedTask; - } - - public Task Handle(Replace args, - CancellationToken cancellationToken) - { - if (args.Value is TViewModel item) - { - Replace(args.Index, item); - } - - return Task.CompletedTask; - } - - public int IndexOf(TViewModel item) => - collection.IndexOf(item); - - int IList.IndexOf(object? value) => - IsCompatibleObject(value) ? - IndexOf((TViewModel)value!) : -1; - - public async Task Initialize() - { - if (isInitialized) - { - return; - } - - isInitialized = true; - - object? key = this.GetAttribute() - is NotificationAttribute attribute - ? this.GetPropertyValue(() => attribute.Key) is { } value ? value : attribute.Key - : null; - - await Publisher.PublishUI(new Enumerate(key)); - } - - public void Insert(int index, TViewModel item) => - InsertItem(index, item); - - void IList.Insert(int index, - object? value) - { - if (value is TViewModel item) - { - Insert(index, item); - } - } - - public bool Move(int index, TViewModel item) - { - int oldIndex = collection.IndexOf(item); - if (oldIndex < 0) - { - return false; - } - - RemoveItem(oldIndex); - Insert(index, item); - - return true; - } - - public bool Remove(TViewModel item) - { - int index = collection.IndexOf(item); - if (index < 0) - { - return false; - } - - Disposer.Dispose(item); - RemoveItem(index); - - return true; - } - - void IList.Remove(object? value) - { - if (IsCompatibleObject(value)) - { - Remove((TViewModel)value!); - } - } - - public void RemoveAt(int index) => - RemoveItem(index); - - public bool Replace(int index, - TViewModel item) - { - if (index <= Count - 1) - { - RemoveItem(index); - } - else - { - index = Count; - } - - Insert(index, item); - return true; - } - - protected virtual void ClearItems() => - collection.Clear(); - - protected virtual void InsertItem(int index, - TViewModel item) - { - Disposer.Add(this, item); - Disposer.Add(item, item, Disposable.Create(() => - { - if (item is IList collection) - { - collection.Clear(); - } - })); - - collection.Insert(index, item); - } - - protected virtual void RemoveItem(int index) => - collection.RemoveAt(index); - - protected virtual void SetItem(int index, TViewModel item) => - collection[index] = item; - - private static bool IsCompatibleObject(object? value) => - (value is TViewModel) || (value == null && default(TViewModel) == null); - - private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs args) => - CollectionChanged?.Invoke(this, args); -} - -public class ObservableCollectionViewModel(IServiceProvider serviceProvider, - IServiceFactory serviceFactory, - IPublisher publisher, - ISubscriber subscriber, - IDisposer disposer) : - ObservableCollectionViewModel(serviceProvider, serviceFactory, publisher, subscriber, disposer); \ No newline at end of file diff --git a/Toolkit.Foundation/ObservableViewModel.cs b/Toolkit.Foundation/ObservableViewModel.cs deleted file mode 100644 index 2f73260..0000000 --- a/Toolkit.Foundation/ObservableViewModel.cs +++ /dev/null @@ -1,72 +0,0 @@ -using CommunityToolkit.Mvvm.ComponentModel; - -namespace Toolkit.Foundation; - -public partial class ObservableViewModel : - ObservableObject, - IObservableViewModel, - IInitializer, - IActivated, - IDeactivating, - IDeactivated, - IDeactivatable -{ - [ObservableProperty] - private bool isInitialized; - - public ObservableViewModel(IServiceProvider serviceProvider, - IServiceFactory serviceFactory, - IPublisher publisher, - ISubscriber subscriber, - IDisposer disposer) - { - ServiceProvider = serviceProvider; - ServiceFactory = serviceFactory; - Publisher = publisher; - Disposer = disposer; - - subscriber.Add(this); - } - - public event EventHandler? DeactivateHandler; - - public IDisposer Disposer { get; } - - public IPublisher Publisher { get; } - - public IServiceFactory ServiceFactory { get; } - - public IServiceProvider ServiceProvider { get; } - - public virtual Task Activated() => - Task.CompletedTask; - - public Task Deactivate() - { - DeactivateHandler?.Invoke(this, new EventArgs()); - return Task.CompletedTask; - } - - public virtual Task Deactivated() => - Task.CompletedTask; - - public virtual Task Deactivating() => - Task.CompletedTask; - - public void Dispose() - { - GC.SuppressFinalize(this); - Disposer.Dispose(this); - } - - public Task Initialize() - { - if (IsInitialized) - { - return Task.CompletedTask; - } - - IsInitialized = true; - return Task.CompletedTask; - } -} diff --git a/Toolkit.Foundation/Open.cs b/Toolkit.Foundation/Open.cs new file mode 100644 index 0000000..16078a6 --- /dev/null +++ b/Toolkit.Foundation/Open.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation; + +public record Open +{ + public static OpenEventArgs As(TSender sender) => new(sender); + + public static OpenEventArgs As() where TSender : new() => new(new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/OpenEventArgs.cs b/Toolkit.Foundation/OpenEventArgs.cs new file mode 100644 index 0000000..74a0bce --- /dev/null +++ b/Toolkit.Foundation/OpenEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record OpenEventArgs(TSender? Sender = default); \ No newline at end of file diff --git a/Toolkit.Foundation/Opened.cs b/Toolkit.Foundation/Opened.cs new file mode 100644 index 0000000..f2bc20e --- /dev/null +++ b/Toolkit.Foundation/Opened.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation; + +public record Opened +{ + public static OpenedEventArgs As(TSender sender) => new(sender); + + public static OpenedEventArgs As() where TSender : new() => new(new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/OpenedEventArgs.cs b/Toolkit.Foundation/OpenedEventArgs.cs new file mode 100644 index 0000000..ccb429b --- /dev/null +++ b/Toolkit.Foundation/OpenedEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record OpenedEventArgs(TSender? Sender = default); \ No newline at end of file diff --git a/Toolkit.Foundation/PasswordHasher.cs b/Toolkit.Foundation/PasswordHasher.cs new file mode 100644 index 0000000..7c1000d --- /dev/null +++ b/Toolkit.Foundation/PasswordHasher.cs @@ -0,0 +1,19 @@ +using System.Security.Cryptography; + +namespace Toolkit.Foundation; + +public class PasswordHasher : + IPasswordHasher +{ + private const int SaltSize = 16; + + public string HashPassword(string password, int iterations = 10000) + { + using Rfc2898DeriveBytes pbkdf2 = new(password, SaltSize, iterations, HashAlgorithmName.SHA256); + + byte[] salt = pbkdf2.Salt; + byte[] hash = pbkdf2.GetBytes(32); + + return $"{Convert.ToBase64String(salt)}:{Convert.ToBase64String(hash)}"; + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/Persist.cs b/Toolkit.Foundation/Persist.cs new file mode 100644 index 0000000..2f8a75f --- /dev/null +++ b/Toolkit.Foundation/Persist.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation; + +public class Persist +{ + public static PersistEventArgs As(TSender sender) => new(sender); + + public static PersistEventArgs As() where TSender : new() => new(new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/PersistEventArgs.cs b/Toolkit.Foundation/PersistEventArgs.cs new file mode 100644 index 0000000..8322b23 --- /dev/null +++ b/Toolkit.Foundation/PersistEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record PersistEventArgs(TSender? Sender = default); \ No newline at end of file diff --git a/Toolkit.Foundation/ProxyService.cs b/Toolkit.Foundation/ProxyService.cs index 03a1eb2..870e226 100644 --- a/Toolkit.Foundation/ProxyService.cs +++ b/Toolkit.Foundation/ProxyService.cs @@ -3,5 +3,5 @@ public class ProxyService(TService proxy) : IProxyService { - public TService Proxy { get; private set; } = proxy; + public TService Value { get; private set; } = proxy; } \ No newline at end of file diff --git a/Toolkit.Foundation/Publisher.cs b/Toolkit.Foundation/Publisher.cs index 12622bc..0020d12 100644 --- a/Toolkit.Foundation/Publisher.cs +++ b/Toolkit.Foundation/Publisher.cs @@ -3,43 +3,42 @@ using System.Reflection; namespace Toolkit.Foundation; -public class Publisher(ISubscriptionManager subscriptionManager, - IServiceProvider provider, - IDispatcher dispatcher) : +public class Publisher(IHandlerProvider handlerProvider, + IServiceFactory serviceFactory, + IServiceProvider serviceProvider, + IDispatcher dispatcher) : IPublisher { - public Task Publish(object key, - CancellationToken cancellationToken = default) - where TNotification : - INotification, - new() => Publish(new TNotification(), async args => await args(), - key, cancellationToken); + public void Publish(object? key = null) + where TMessage : new() => + Publish(serviceFactory.Create() ?? new TMessage(), async args => await args(), key); - public Task Publish(TNotification notification, - CancellationToken cancellationToken = default) - where TNotification : - INotification => Publish(notification, async args => await args(), - null, cancellationToken); + public void Publish(TMessage message) + where TMessage : notnull => + Publish(message, async args => await args(), null); - public Task Publish(TNotification notification, - object key, - CancellationToken cancellationToken = default) - where TNotification : - INotification => Publish(notification, - async args => await args(), key, cancellationToken); + public void Publish(TMessage message, + object? key = null) + where TMessage : notnull => + Publish(message, async args => await args(), key); - public async Task Publish(object notification, + public void Publish(object message, Func, Task> marshal, - object? key = null, - CancellationToken cancellationToken = default) + object? key = null) { - Type notificationType = notification.GetType(); + Type notificationType = message.GetType(); + Type handlerType = typeof(NotificationHandlerWrapper<>) + .MakeGenericType(notificationType); - List handlers = provider.GetServices(typeof(NotificationHandlerWrapper<>) - .MakeGenericType(notificationType)).ToList(); + key = $"{(key is not null ? $"{key}:" : "")}{notificationType}"; - foreach (object? handler in subscriptionManager - .GetHandlers(notificationType, key!)) + List handlers = []; + foreach (object? handler in handlerProvider.Get(key)) + { + handlers.Add(handler); + } + + foreach (object? handler in serviceProvider.GetKeyedServices(handlerType, key)) { handlers.Add(handler); } @@ -48,59 +47,42 @@ public class Publisher(ISubscriptionManager subscriptionManager, { if (handler is not null) { - Type? handlerType = handler.GetType(); - MethodInfo? handleMethod = handlerType.GetMethod("Handle", - [notificationType, typeof(CancellationToken)]); + MethodInfo? handleMethod = handler.GetType().GetMethod("Handle", + [notificationType]); if (handleMethod is not null) { - await marshal(() => (Task)handleMethod.Invoke(handler, new object[] - { notification, cancellationToken })!); + marshal(() => (Task)handleMethod.Invoke(handler, new object[] + { message })!); } } } } - public Task Publish(object notification, - CancellationToken cancellationToken = default) => Publish(notification, - async args => await args(), - null, cancellationToken); + public void Publish(object message) => Publish(message, + async args => await args(), null); - public Task Publish(CancellationToken cancellationToken = default) - where TNotification : - INotification, new() => Publish(new TNotification(), - async args => await args(), - null, cancellationToken); + public void Publish() + where TMessage : new() => + Publish(new TMessage(), async args => await args(), null); - public Task PublishUI(object key, - CancellationToken cancellationToken = default) - where TNotification : - INotification, new() => Publish(new TNotification(), - args => dispatcher.InvokeAsync(async () => await args()), - key, cancellationToken); + public void PublishUI(object? key = null) + where TMessage : new() => + Publish(new TMessage(), args => dispatcher.Invoke(async () => await args()), key); - public Task PublishUI(TNotification notification, - CancellationToken cancellationToken = default) - where TNotification : - INotification => Publish(notification, - args => dispatcher.InvokeAsync(async () => await args()), - null, cancellationToken); + public void PublishUI(TMessage message) + where TMessage : notnull => + Publish(message, args => dispatcher.Invoke(async () => await args()), null); - public Task PublishUI(TNotification notification, - object key, - CancellationToken cancellationToken = default) - where TNotification : - INotification => Publish(notification, - args => dispatcher.InvokeAsync(async () => await args()), - key, cancellationToken); - public Task PublishUIAsync(CancellationToken cancellationToken = default) - where TNotification : - INotification, new() => Publish(new TNotification(), - args => dispatcher.InvokeAsync(async () => await args()), - null, cancellationToken); + public void PublishUI(TMessage message, + object? key = null) + where TMessage : notnull => + Publish(message, args => dispatcher.Invoke(async () => await args()), key); - public Task PublishUI(object notification, - CancellationToken cancellationToken = default) => Publish(notification, args => - dispatcher.InvokeAsync(async () => await args()), - null, cancellationToken); -} + public void PublishUI() + where TMessage : new() => + Publish(new TMessage(), args => dispatcher.Invoke(async () => await args()), null); + + public void PublishUI(object message) => Publish(message, args => + dispatcher.Invoke(async () => await args()), null); +} \ No newline at end of file diff --git a/Toolkit.Foundation/Query.cs b/Toolkit.Foundation/Query.cs new file mode 100644 index 0000000..def084a --- /dev/null +++ b/Toolkit.Foundation/Query.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation; + +public class Query +{ + public static QueryEventArgs As(TValue value) => + new(value); + + public static QueryEventArgs As() where TValue : new() => + new(new TValue()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/QueryEventArgs.cs b/Toolkit.Foundation/QueryEventArgs.cs new file mode 100644 index 0000000..feea16c --- /dev/null +++ b/Toolkit.Foundation/QueryEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record QueryEventArgs(TSender Sender); \ No newline at end of file diff --git a/Toolkit.Foundation/Read.cs b/Toolkit.Foundation/Read.cs new file mode 100644 index 0000000..6449156 --- /dev/null +++ b/Toolkit.Foundation/Read.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation; + +public class Read +{ + public static ReadEventArgs As(TSender sender) => + new(sender); + + public static ReadEventArgs As() where TSender : new() => + new(new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/ReadEventArgs.cs b/Toolkit.Foundation/ReadEventArgs.cs new file mode 100644 index 0000000..4172b67 --- /dev/null +++ b/Toolkit.Foundation/ReadEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record ReadEventArgs(TSender? Sender = default); \ No newline at end of file diff --git a/Toolkit.Foundation/ReadOnlyIndexDictionary.cs b/Toolkit.Foundation/ReadOnlyIndexDictionary.cs new file mode 100644 index 0000000..f88c555 --- /dev/null +++ b/Toolkit.Foundation/ReadOnlyIndexDictionary.cs @@ -0,0 +1,56 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; + +namespace Toolkit.Foundation; + +public class ReadOnlyIndexDictionary : + IReadOnlyDictionary, + IReadOnlyIndexDictionary + where TKey : notnull +{ + private readonly Dictionary dictionary; + private readonly List> indexedItems; + + public ReadOnlyIndexDictionary(IDictionary items) + { + dictionary = new Dictionary(items); + indexedItems = [.. dictionary]; + } + + public TValue this[TKey key] => + dictionary[key]; + + public KeyValuePair this[int index] => + indexedItems[index]; + + public IEnumerable Keys => + dictionary.Keys; + + public IEnumerable Values => + dictionary.Values; + + public int Count => dictionary.Count; + + public bool ContainsKey(TKey key) => + dictionary.ContainsKey(key); + + public IEnumerator> GetEnumerator() => + dictionary.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => dictionary.GetEnumerator(); + + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + { + if (dictionary.TryGetValue(key, out TValue? localValue)) + { + if (localValue is not null) + { + value = localValue; + return true; + } + } + + value = default; + return false; + } +} diff --git a/Toolkit.Foundation/Remove.cs b/Toolkit.Foundation/Remove.cs index 54d190d..7ce4d60 100644 --- a/Toolkit.Foundation/Remove.cs +++ b/Toolkit.Foundation/Remove.cs @@ -1,5 +1,8 @@ - -namespace Toolkit.Foundation; +namespace Toolkit.Foundation; -public record Remove(TValue Value) : - INotification; \ No newline at end of file +public record Remove +{ + public static RemoveEventArgs As(TSender sender) => new(sender); + + public static RemoveEventArgs As() where TSender : new() => new(new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/RemoveAt.cs b/Toolkit.Foundation/RemoveAt.cs new file mode 100644 index 0000000..e675135 --- /dev/null +++ b/Toolkit.Foundation/RemoveAt.cs @@ -0,0 +1,7 @@ +namespace Toolkit.Foundation; + +public record RemoveAt +{ + public static RemoveAtEventArgs As(int index) => + new(index); +} \ No newline at end of file diff --git a/Toolkit.Foundation/RemoveAtEventArgs.cs b/Toolkit.Foundation/RemoveAtEventArgs.cs new file mode 100644 index 0000000..1a45527 --- /dev/null +++ b/Toolkit.Foundation/RemoveAtEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record RemoveAtEventArgs(int Index); \ No newline at end of file diff --git a/Toolkit.Foundation/RemoveEventArgs.cs b/Toolkit.Foundation/RemoveEventArgs.cs new file mode 100644 index 0000000..5ba67ac --- /dev/null +++ b/Toolkit.Foundation/RemoveEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record RemoveEventArgs(TSender? Sender = default); \ No newline at end of file diff --git a/Toolkit.Foundation/Replace.cs b/Toolkit.Foundation/Replace.cs index 6441149..92372e7 100644 --- a/Toolkit.Foundation/Replace.cs +++ b/Toolkit.Foundation/Replace.cs @@ -1,6 +1,8 @@ - -namespace Toolkit.Foundation; +namespace Toolkit.Foundation; -public record Replace(int Index, TValue Value, object? Target = null) : - INotification; +public record Replace +{ + public static ReplaceEventArgs As(int index, TSender sender) => new(index, sender); + public static ReplaceEventArgs As(int index) where TSender : new() => new(index, new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/ReplaceEventArgs.cs b/Toolkit.Foundation/ReplaceEventArgs.cs new file mode 100644 index 0000000..91698f4 --- /dev/null +++ b/Toolkit.Foundation/ReplaceEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record ReplaceEventArgs(int Index, TSender? Sender = default); \ No newline at end of file diff --git a/Toolkit.Foundation/Request.cs b/Toolkit.Foundation/Request.cs index a5d5fb7..f098aa1 100644 --- a/Toolkit.Foundation/Request.cs +++ b/Toolkit.Foundation/Request.cs @@ -1,12 +1,10 @@ namespace Toolkit.Foundation; -public record Request : - INotification -{ - -} - public class Request { - public static Request Create() => new(); + public static RequestEventArgs As(TSender sender) => + new(sender); + + public static RequestEventArgs As() where TSender : new() => + new(new TSender()); } \ No newline at end of file diff --git a/Toolkit.Foundation/RequestEventArgs.cs b/Toolkit.Foundation/RequestEventArgs.cs new file mode 100644 index 0000000..6cb1cf9 --- /dev/null +++ b/Toolkit.Foundation/RequestEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record RequestEventArgs(TSender? Sender = default); \ No newline at end of file diff --git a/Toolkit.Foundation/Response.cs b/Toolkit.Foundation/Response.cs index f1a7248..8197b75 100644 --- a/Toolkit.Foundation/Response.cs +++ b/Toolkit.Foundation/Response.cs @@ -1,4 +1,3 @@ namespace Toolkit.Foundation; -public record Response(TValue Value) : - INotification; \ No newline at end of file +public record Response(TValue Value); \ No newline at end of file diff --git a/Toolkit.Foundation/Result.cs b/Toolkit.Foundation/Result.cs new file mode 100644 index 0000000..8b3dd5f --- /dev/null +++ b/Toolkit.Foundation/Result.cs @@ -0,0 +1,33 @@ +namespace Toolkit.Foundation; + +public record Result : + Result +{ + private readonly TValue? value; + + protected internal Result(TValue? value, bool isSuccess, Error error) + : base(isSuccess, error) => this.value = value; + + public TValue? Value => IsSuccess ? value! : default; + + public static implicit operator Result(TValue? value) => Create(value); +} + + +public record Result(bool IsSuccess, Error Error) +{ + public bool IsFailure => !IsSuccess; + + public static Result Success() => new(true, Error.None); + + public static Result Success(TValue value) => new(value, true, Error.None); + + public static Result Failure(Error error) => new(false, error); + + public static Result Failure(Error error) => new(default, false, error); + + public static Result Create(bool condition) => condition ? Success() : Failure(Error.ConditionNotMet); + + public static Result Create(TValue? value) => value is not null ? Success(value) : Failure(Error.Null); + +} \ No newline at end of file diff --git a/Toolkit.Foundation/ServiceScopeFactory.cs b/Toolkit.Foundation/ScopeServiceFactory.cs similarity index 56% rename from Toolkit.Foundation/ServiceScopeFactory.cs rename to Toolkit.Foundation/ScopeServiceFactory.cs index e8b114e..f4b4bd1 100644 --- a/Toolkit.Foundation/ServiceScopeFactory.cs +++ b/Toolkit.Foundation/ScopeServiceFactory.cs @@ -2,21 +2,21 @@ namespace Toolkit.Foundation; -public class ServiceScopeFactory(IServiceScopeFactory serviceScopeFactory, +public class ScopeServiceFactory(IServiceScopeFactory serviceScopeFactory, ICache cache) : - IServiceScopeFactory + IScopeServiceFactory where TService : notnull { - public TService? Create(params object?[] parameters) + public (IServiceScope, TService) Create(params object?[] parameters) { if (serviceScopeFactory.CreateScope() is IServiceScope serviceScope) { - if (serviceScope.ServiceProvider.GetService() is IServiceFactory serviceFactory) + if (serviceScope.ServiceProvider.GetService() is IServiceFactory factory) { - if (serviceFactory.Create(parameters) is TService service) + if (factory.Create(parameters) is TService service) { cache.Add(service, serviceScope); - return service; + return (serviceScope, service); } } } diff --git a/Toolkit.Foundation/SelectFilesHandler.cs b/Toolkit.Foundation/SelectFilesHandler.cs new file mode 100644 index 0000000..d45cca0 --- /dev/null +++ b/Toolkit.Foundation/SelectFilesHandler.cs @@ -0,0 +1,20 @@ +namespace Toolkit.Foundation; + +public class SelectFilesHandler(IFileProvider fileProvider) : + IHandler, IReadOnlyCollection?> +{ + public async Task?> Handle(SelectionEventArgs args, + CancellationToken cancellationToken) + { + if (args.Sender is FileFilter filter) + { + if (await fileProvider.SelectFiles(filter) + is { Count: > 0 } files) + { + return files; + } + } + + return default; + } +} diff --git a/Toolkit.Foundation/SelectFoldersHandler.cs b/Toolkit.Foundation/SelectFoldersHandler.cs new file mode 100644 index 0000000..34d415f --- /dev/null +++ b/Toolkit.Foundation/SelectFoldersHandler.cs @@ -0,0 +1,21 @@ +namespace Toolkit.Foundation; + + +public class SelectFoldersHandler(IFolderProvider folderProvider) : + IHandler, IReadOnlyCollection?> +{ + public async Task?> Handle(SelectionEventArgs args, + CancellationToken cancellationToken) + { + if (args.Sender is FolderFilter filter) + { + if (await folderProvider.SelectFolders(filter) + is { Count: > 0 } folders) + { + return folders; + } + } + + return default; + } +} diff --git a/Toolkit.Foundation/Selection.cs b/Toolkit.Foundation/Selection.cs new file mode 100644 index 0000000..aeddbd3 --- /dev/null +++ b/Toolkit.Foundation/Selection.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation; + +public record Selection +{ + public static SelectionEventArgs As(TSender? sender) => + new(sender); + + public static SelectionEventArgs As() where TSender : new() => + new(new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/SelectionEventArgs.cs b/Toolkit.Foundation/SelectionEventArgs.cs new file mode 100644 index 0000000..48def9c --- /dev/null +++ b/Toolkit.Foundation/SelectionEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record SelectionEventArgs(TSender? Sender); \ No newline at end of file diff --git a/Toolkit.Foundation/ServiceFactory.cs b/Toolkit.Foundation/ServiceFactory.cs index 39e892e..4c9207c 100644 --- a/Toolkit.Foundation/ServiceFactory.cs +++ b/Toolkit.Foundation/ServiceFactory.cs @@ -6,6 +6,24 @@ public class ServiceFactory(Func factory) : public TService Create(params object?[]? parameters) => (TService)factory(typeof(TService), parameters); - public object Create(Type type, params object?[]? parameters) => + public TService Create(Action serviceDelegate, + params object?[]? parameters) + { + TService service = (TService)factory(typeof(TService), parameters); + serviceDelegate.Invoke(service); + + return service; + } + + public object Create(Type type, params object?[]? parameters) => factory(type, parameters); + + public object Create(Type type, Action serviceDelegate, + params object?[]? parameters) + { + object service = factory(type, parameters); + serviceDelegate.Invoke(service); + + return service; + } } \ No newline at end of file diff --git a/Toolkit.Foundation/ServiceScopeProvider.cs b/Toolkit.Foundation/ServiceScopeProvider.cs index 11ccc9b..639db09 100644 --- a/Toolkit.Foundation/ServiceScopeProvider.cs +++ b/Toolkit.Foundation/ServiceScopeProvider.cs @@ -6,7 +6,7 @@ public class ServiceScopeProvider(ICache cach IServiceScopeProvider where TService : notnull { - public bool TryGet(TService service, + public bool TryGet(TService service, out IServiceScope? serviceScope) { if (cache.TryGetValue(service, out IServiceScope? value)) @@ -18,4 +18,4 @@ public class ServiceScopeProvider(ICache cach serviceScope = null; return false; } -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/SetupRequiredAttribute.cs b/Toolkit.Foundation/SetupRequiredAttribute.cs index 6f2c67d..d2f4cf5 100644 --- a/Toolkit.Foundation/SetupRequiredAttribute.cs +++ b/Toolkit.Foundation/SetupRequiredAttribute.cs @@ -4,4 +4,4 @@ public class NavigationRouteAttribute(string route) : Attribute { public string Route => route; -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/StartProcess.cs b/Toolkit.Foundation/StartProcess.cs index c2cae16..ed075a5 100644 --- a/Toolkit.Foundation/StartProcess.cs +++ b/Toolkit.Foundation/StartProcess.cs @@ -1,3 +1,3 @@ namespace Toolkit.Foundation; -public record StartProcess(string Process) : IRequest; \ No newline at end of file +public record StartProcess(string Process); \ No newline at end of file diff --git a/Toolkit.Foundation/Started.cs b/Toolkit.Foundation/Started.cs deleted file mode 100644 index 9180311..0000000 --- a/Toolkit.Foundation/Started.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Toolkit.Foundation; - -public record Started : - INotification; \ No newline at end of file diff --git a/Toolkit.Foundation/StartedEventArgs.cs b/Toolkit.Foundation/StartedEventArgs.cs new file mode 100644 index 0000000..d04f3a0 --- /dev/null +++ b/Toolkit.Foundation/StartedEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record StartedEventArgs; \ No newline at end of file diff --git a/Toolkit.Foundation/Subscriber.cs b/Toolkit.Foundation/Subscriber.cs index 8d13d8f..88c772f 100644 --- a/Toolkit.Foundation/Subscriber.cs +++ b/Toolkit.Foundation/Subscriber.cs @@ -1,11 +1,165 @@ -namespace Toolkit.Foundation; +using System.Reactive.Disposables; -public class Subscriber(ISubscriptionManager subscriptionManager) : +namespace Toolkit.Foundation; + +public class Subscriber(SubscriptionCollection subscriptions, + IDisposer disposer) : ISubscriber { - public void Remove(object subscriber) => - subscriptionManager.Remove(subscriber); + public void Subscribe(object subscriber) + { + IDictionary> subscribers = GetSubscriptionKeys(subscriber); - public void Add(object subscriber) => - subscriptionManager.Add(subscriber); + foreach (Type handlerType in GetHandlerInterfaces(subscriber.GetType())) + { + if (handlerType.Name == typeof(INotificationHandler<>).Name && + handlerType.GetGenericArguments() is { Length: 1 } notificationHandlerArguments) + { + Type notificationType = notificationHandlerArguments[0]; + AddSubscriptions(subscriber, subscribers, notificationType); + } + + if (handlerType.Name == typeof(IHandler<,>).Name && + handlerType.GetGenericArguments() is { Length: 2 } handlerArguments) + { + Type requestType = handlerArguments[0]; + Type responseType = handlerArguments[1]; + Type wrapperType = typeof(HandlerWrapper<,>).MakeGenericType(requestType, responseType); + + AddSubscriptions(subscriber, subscribers, wrapperType); + } + } + } + + public void Unsubscribe(object subscriber) + { + IDictionary> subscribers = GetSubscriptionKeys(subscriber); + + foreach (Type handlerType in GetHandlerInterfaces(subscriber.GetType())) + { + if (handlerType.Name == typeof(INotificationHandler<>).Name && + handlerType.GetGenericArguments() is { Length: 1 } notificationHandlerArguments) + { + Type notificationType = notificationHandlerArguments[0]; + RemoveSubscriptions(subscriber, subscribers, notificationType); + } + + if (handlerType.Name == typeof(IHandler<,>).Name && + handlerType.GetGenericArguments() is { Length: 2 } handlerArguments) + { + Type requestType = handlerArguments[0]; + Type responseType = handlerArguments[1]; + Type wrapperType = typeof(HandlerWrapper<,>).MakeGenericType(requestType, responseType); + + RemoveSubscriptions(subscriber, subscribers, wrapperType); + } + } + } + + private void AddOrUpdateSubscription(object subscriber, + string preferredKey) + { + subscriptions.AddOrUpdate(preferredKey, _ => new List { new(subscriber) }, (_, collection) => + { + collection.Add(new WeakReference(subscriber)); + return collection; + }); + + disposer.Add(subscriber, Disposable.Create(() => { + + RemoveSubscription(subscriber, preferredKey); + + })); + } + + private void AddSubscriptions(object subscriber, + IDictionary> subscribers, + Type handlerType) + { + if (subscribers.TryGetValue(handlerType, out List? keys)) + { + foreach (object key in keys) + { + string preferredKey = $"{(key is not null ? $"{key}:" : "")}{handlerType}"; + AddOrUpdateSubscription(subscriber, preferredKey); + } + } + else + { + string preferredKey = $"{handlerType}"; + AddOrUpdateSubscription(subscriber, preferredKey); + } + } + + private IEnumerable GetHandlerInterfaces(Type handlerType) => + handlerType.GetInterfaces().Where(interfaceType => + { + Type? definition = interfaceType.IsGenericType ? interfaceType.GetGenericTypeDefinition() : null; + return definition == typeof(INotificationHandler<>) || + definition == typeof(IHandler<>) || + definition == typeof(IHandler<,>); + }); + + private IDictionary> GetSubscriptionKeys(object subscriber) + { + Dictionary> keys = []; + foreach (NotificationAttribute attribute in subscriber.GetAttributes()) + { + if (!keys.TryGetValue(attribute.Type, out List? value)) + { + value = ([]); + keys[attribute.Type] = value; + } + + if (subscriber.GetPropertyValue(() => attribute.Key) is object key) + { + value.Add(key); + } + else + { + value.Add(attribute.Key); + } + } + + return keys; + } + + private void RemoveSubscription(object subscriber, + string key) + { + if (subscriptions.TryGetValue(key, out List? subscribers)) + { + for (int i = subscribers.Count - 1; i >= 0; i--) + { + if (subscribers[i].Target == subscriber) + { + subscribers.RemoveAt(i); + } + } + + if (subscribers.Count == 0) + { + subscriptions.TryRemove(key, out _); + } + } + } + + private void RemoveSubscriptions(object subscriber, + IDictionary> subscribers, + Type handlerType) + { + if (subscribers.TryGetValue(handlerType, out List? keys)) + { + foreach (object key in keys) + { + string subscriptionKey = $"{(key is not null ? $"{key}:" : "")}{handlerType}"; + RemoveSubscription(subscriber, subscriptionKey); + } + } + else + { + string subscriptionKey = $"{handlerType}"; + RemoveSubscription(subscriber, subscriptionKey); + } + } } \ No newline at end of file diff --git a/Toolkit.Foundation/SubscriptionCollection.cs b/Toolkit.Foundation/SubscriptionCollection.cs index 67cbbc4..82f4572 100644 --- a/Toolkit.Foundation/SubscriptionCollection.cs +++ b/Toolkit.Foundation/SubscriptionCollection.cs @@ -3,4 +3,4 @@ namespace Toolkit.Foundation; public class SubscriptionCollection : - ConcurrentDictionary>; + ConcurrentDictionary>; \ No newline at end of file diff --git a/Toolkit.Foundation/SubscriptionManager.cs b/Toolkit.Foundation/SubscriptionManager.cs deleted file mode 100644 index 2d8eae3..0000000 --- a/Toolkit.Foundation/SubscriptionManager.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System.Reflection; - -namespace Toolkit.Foundation; - -public class SubscriptionManager(SubscriptionCollection subscriptions) : - ISubscriptionManager -{ - public IEnumerable GetHandlers(Type notificationType, object key) - { - if (subscriptions.TryGetValue($"{(key is not null ? $"{key}:" : "")}{notificationType}", - out List? subscribers)) - { - foreach (WeakReference weakRef in subscribers.ToArray()) - { - object? target = weakRef.Target; - if (target != null) - { - yield return target; - } - else - { - subscribers.Remove(weakRef); - } - } - } - } - - public void Remove(object subscriber) - { - Type handlerType = subscriber.GetType(); - object? key = GetKeyFromHandler(subscriber); - foreach (Type interfaceType in GetHandlerInterfaces(handlerType)) - { - if (interfaceType.GetGenericArguments().FirstOrDefault() is Type argumentType) - { - if (subscriptions.TryGetValue($"{(key is not null ? $"{key}:" : "")}{argumentType}", out List? subscribers)) - { - for (int i = subscribers.Count - 1; i >= 0; i--) - { - if (!subscribers[i].IsAlive || subscribers[i].Target == subscriber) - { - subscribers.RemoveAt(i); - } - } - } - } - } - } - - public void Add(object subscriber) - { - Type handlerType = subscriber.GetType(); - object? key = GetKeyFromHandler(subscriber); - foreach (Type interfaceType in GetHandlerInterfaces(handlerType)) - { - if (interfaceType.GetGenericArguments().FirstOrDefault() is Type argumentType) - { - subscriptions.AddOrUpdate($"{(key is not null ? $"{key}:" : "")}{argumentType}", _ => new List { new(subscriber) }, (_, collection) => - { - collection.Add(new WeakReference(subscriber)); - return collection; - }); - } - } - } - - private static object? GetKeyFromHandler(object handler) - { - return handler.GetAttribute() - is NotificationAttribute attribute - ? handler.GetPropertyValue(() => attribute.Key) is { } value ? value : attribute.Key - : null; - } - - private static IEnumerable GetHandlerInterfaces(Type handlerType) => - handlerType.GetInterfaces().Where(interfaceType => interfaceType.IsGenericType && - interfaceType.GetGenericTypeDefinition() == typeof(INotificationHandler<>)); -} diff --git a/Toolkit.Foundation/Toolkit.Foundation.csproj b/Toolkit.Foundation/Toolkit.Foundation.csproj index ef89a92..de21c77 100644 --- a/Toolkit.Foundation/Toolkit.Foundation.csproj +++ b/Toolkit.Foundation/Toolkit.Foundation.csproj @@ -1,13 +1,14 @@  - net8.0 + net9.0 enable enable AnyCPU;x64;x86 - - - + + + + diff --git a/Toolkit.Foundation/TrackedProperty.cs b/Toolkit.Foundation/TrackedProperty.cs new file mode 100644 index 0000000..1e22dac --- /dev/null +++ b/Toolkit.Foundation/TrackedProperty.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation; + +public class TrackedProperty(T initial, + Action revert, + Func commit) +{ + public void Commit() => initial = commit(); + + public void Revert() => revert(initial); +} \ No newline at end of file diff --git a/Toolkit.Foundation/TupleExtensions.cs b/Toolkit.Foundation/TupleExtensions.cs new file mode 100644 index 0000000..6f68e90 --- /dev/null +++ b/Toolkit.Foundation/TupleExtensions.cs @@ -0,0 +1,60 @@ +namespace Toolkit.Foundation; + +public static class TupleExtensions +{ + public static (T1, T2) CreateValueTuple(this object[] parameters) => ( + (T1)Convert.ChangeType(parameters[0], typeof(T1)), + (T2)Convert.ChangeType(parameters[1], typeof(T2)) + ); + + public static (T1, T2, T3) CreateValueTuple(this object[] parameters) => ( + (T1)Convert.ChangeType(parameters[0], typeof(T1)), + (T2)Convert.ChangeType(parameters[1], typeof(T2)), + (T3)Convert.ChangeType(parameters[2], typeof(T3)) + ); + + public static (T1, T2, T3, T4) CreateValueTuple(this object[] parameters) => ( + (T1)Convert.ChangeType(parameters[0], typeof(T1)), + (T2)Convert.ChangeType(parameters[1], typeof(T2)), + (T3)Convert.ChangeType(parameters[2], typeof(T3)), + (T4)Convert.ChangeType(parameters[3], typeof(T4)) + ); + + public static (T1, T2, T3, T4, T5) CreateValueTuple(this object[] parameters) => ( + (T1)Convert.ChangeType(parameters[0], typeof(T1)), + (T2)Convert.ChangeType(parameters[1], typeof(T2)), + (T3)Convert.ChangeType(parameters[2], typeof(T3)), + (T4)Convert.ChangeType(parameters[3], typeof(T4)), + (T5)Convert.ChangeType(parameters[4], typeof(T5)) + ); + + public static (T1, T2, T3, T4, T5, T6) CreateValueTuple(this object[] parameters) => + ((T1)Convert.ChangeType(parameters[0], typeof(T1)), + (T2)Convert.ChangeType(parameters[1], typeof(T2)), + (T3)Convert.ChangeType(parameters[2], typeof(T3)), + (T4)Convert.ChangeType(parameters[3], typeof(T4)), + (T5)Convert.ChangeType(parameters[4], typeof(T5)), + (T6)Convert.ChangeType(parameters[5], typeof(T6)) + ); + + public static (T1, T2, T3, T4, T5, T6, T7) CreateValueTuple(this object[] parameters) => + ((T1)Convert.ChangeType(parameters[0], typeof(T1)), + (T2)Convert.ChangeType(parameters[1], typeof(T2)), + (T3)Convert.ChangeType(parameters[2], typeof(T3)), + (T4)Convert.ChangeType(parameters[3], typeof(T4)), + (T5)Convert.ChangeType(parameters[4], typeof(T5)), + (T6)Convert.ChangeType(parameters[5], typeof(T6)), + (T7)Convert.ChangeType(parameters[6], typeof(T7)) + ); + + public static (T1, T2, T3, T4, T5, T6, T7, T8) CreateValueTuple(this object[] parameters) => + ((T1)Convert.ChangeType(parameters[0], typeof(T1)), + (T2)Convert.ChangeType(parameters[1], typeof(T2)), + (T3)Convert.ChangeType(parameters[2], typeof(T3)), + (T4)Convert.ChangeType(parameters[3], typeof(T4)), + (T5)Convert.ChangeType(parameters[4], typeof(T5)), + (T6)Convert.ChangeType(parameters[5], typeof(T6)), + (T7)Convert.ChangeType(parameters[6], typeof(T7)), + (T8)Convert.ChangeType(parameters[7], typeof(T8)) + ); +} \ No newline at end of file diff --git a/Toolkit.Foundation/TypeExtensions.cs b/Toolkit.Foundation/TypeExtensions.cs deleted file mode 100644 index 76ea051..0000000 --- a/Toolkit.Foundation/TypeExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Reflection; - -namespace Toolkit.Foundation; - -public static class TypeExtensions -{ - public static TAttribute? GetAttribute(this Type type) - where TAttribute : Attribute - { - if (type.GetCustomAttribute() is TAttribute attribute) - { - return attribute; - } - - return null; - } -} diff --git a/Toolkit.Foundation/Update.cs b/Toolkit.Foundation/Update.cs new file mode 100644 index 0000000..5703a48 --- /dev/null +++ b/Toolkit.Foundation/Update.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation; + +public record Update +{ + public static UpdateEventArgs As(TValue value) => + new(value); + + public static UpdateEventArgs As() where TValue : new() => + new(new TValue()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/UpdateEventArgs.cs b/Toolkit.Foundation/UpdateEventArgs.cs new file mode 100644 index 0000000..6e3826c --- /dev/null +++ b/Toolkit.Foundation/UpdateEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record UpdateEventArgs(TSender Sender); \ No newline at end of file diff --git a/Toolkit.Foundation/UserInteractedEventArgs.cs b/Toolkit.Foundation/UserInteractedEventArgs.cs new file mode 100644 index 0000000..0291a9e --- /dev/null +++ b/Toolkit.Foundation/UserInteractedEventArgs.cs @@ -0,0 +1,4 @@ +namespace Toolkit.Foundation; + +public class UserInteractedEventArgs : + EventArgs; diff --git a/Toolkit.Foundation/Validate.cs b/Toolkit.Foundation/Validate.cs new file mode 100644 index 0000000..90894b1 --- /dev/null +++ b/Toolkit.Foundation/Validate.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation; + +public record Validate +{ + public static ValidateEventArgs As(TSender sender) => + new(sender); + + public static ValidateEventArgs As() where TSender : new() => + new(new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/ValidateEventArgs.cs b/Toolkit.Foundation/ValidateEventArgs.cs new file mode 100644 index 0000000..409a873 --- /dev/null +++ b/Toolkit.Foundation/ValidateEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record ValidateEventArgs(TSender? Sender = default); \ No newline at end of file diff --git a/Toolkit.Foundation/Validation.cs b/Toolkit.Foundation/Validation.cs new file mode 100644 index 0000000..778c17e --- /dev/null +++ b/Toolkit.Foundation/Validation.cs @@ -0,0 +1,129 @@ +using System.ComponentModel; +using System.Linq.Expressions; + +namespace Toolkit.Foundation; + +public class Validation(IValidatorCollection validators) : + IValidation +{ + private readonly ValidationErrorCollection errors = []; + + public event PropertyChangedEventHandler? PropertyChanged; + + public IReadOnlyIndexDictionary Errors => + new ReadOnlyIndexDictionary(errors); + + public bool HasErrors => + Errors.Count > 0; + + internal IValidatorCollection Validators { get; } = validators; + + public void Add(Expression> property, + ValidationRule[] rules, + ValidationTrigger trigger = ValidationTrigger.Deferred) + { + string? name = GetPropertyName(property); + Validators.Add(name, new Validator(name, rules)); + + if (trigger is ValidationTrigger.Immediate) + { + _ = Validate(name); + } + } + + public void Clear() + { + errors.Clear(); + OnPropertyChanged(nameof(Errors), null, null); + } + + public virtual void OnPropertyChanged(string propertyName, + object? before, object? after) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public async Task Validate(Expression> property, + ValidationRule[] rules) + { + string? name = GetPropertyName(property); + Validator validator = new(name, rules); + + Clear(name); + + (bool isValid, string? message) = await validator.TryValidate(); + + if (!isValid) + { + errors[name] = message ?? ""; + } + + OnPropertyChanged(nameof(Errors), null, null); + OnPropertyChanged(nameof(HasErrors), null, null); + + return !HasErrors; + } + + public async Task Validate(string name) + { + Clear(name); + + if (Validators.TryGet(name, out Validator? validator)) + { + if (validator is not null) + { + (bool isValid, string? message) = await validator.TryValidate(); + if (!isValid) + { + errors[name] = message ?? ""; + } + } + } + + OnPropertyChanged(nameof(Errors), null, null); + OnPropertyChanged(nameof(HasErrors), null, null); + + return !HasErrors; + } + + public async Task Validate() + { + Clear(); + + foreach (Validator? validator in Validators) + { + if (validator.PropertyName is string name) + { + (bool isValid, string? message) = await validator.TryValidate(); + if (!isValid) + { + errors[name] = message ?? ""; + } + } + } + + OnPropertyChanged(nameof(Errors), null, null); + OnPropertyChanged(nameof(HasErrors), null, null); + + return !HasErrors; + } + + private void Clear(string name) + { + if (Errors.ContainsKey(name)) + { + errors.Remove(name); + OnPropertyChanged(nameof(Errors), null, null); + } + } + + private string GetPropertyName(Expression> expression) + { + return expression.Body switch + { + MemberExpression memberExpression => memberExpression.Member.Name, + UnaryExpression unaryExpression when unaryExpression.Operand is MemberExpression operand => operand.Member.Name, + _ => string.Empty + }; + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/ValidationErrorCollection.cs b/Toolkit.Foundation/ValidationErrorCollection.cs new file mode 100644 index 0000000..658574f --- /dev/null +++ b/Toolkit.Foundation/ValidationErrorCollection.cs @@ -0,0 +1,152 @@ +using System.Collections; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; + +namespace Toolkit.Foundation; + +public class ValidationErrorCollection : + IDictionary, + IDictionary, + INotifyCollectionChanged, + INotifyPropertyChanged +{ + private Dictionary items; + + public ValidationErrorCollection() + { + items = []; + } + + public event NotifyCollectionChangedEventHandler? CollectionChanged; + + public event PropertyChangedEventHandler? PropertyChanged; + + public int Count => items.Count; + + bool IDictionary.IsFixedSize => ((IDictionary)items).IsFixedSize; + + public bool IsReadOnly => false; + + bool ICollection.IsSynchronized => ((IDictionary)items).IsSynchronized; + + public ICollection Keys => items.Keys; + + ICollection IDictionary.Keys => ((IDictionary)items).Keys; + + object ICollection.SyncRoot => ((IDictionary)items).SyncRoot; + + public ICollection Values => items.Values; + + ICollection IDictionary.Values => ((IDictionary)items).Values; + + public string this[string key] + { + get => items.TryGetValue(key, out string? value) ? value : ""; + set + { + bool replace = items.TryGetValue(key, out var old); + items[key] = value; + + if (replace) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, + new KeyValuePair(key, value), new KeyValuePair(key, old!))); + } + else + { + NotifyAdd(key, value); + } + } + } + + object? IDictionary.this[object key] + { + get => ((IDictionary)items)[key]; + set => ((IDictionary)items)[key] = value; + } + + public void Add(string key, string value) + { + items.Add(key, value); + NotifyAdd(key, value); + } + + void ICollection>.Add(KeyValuePair item) => + Add(item.Key, item.Value); + + void IDictionary.Add(object key, object? value) => + Add((string)key, (string)value!); + + public void Clear() + { + Dictionary old = items; + items = []; + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item")); + + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, old.ToArray(), -1)); + } + + bool ICollection>.Contains(KeyValuePair item) => + items.Contains(item); + + bool IDictionary.Contains(object key) => + ((IDictionary)items).Contains(key); + + public bool ContainsKey(string key) => items.ContainsKey(key); + + public void CopyTo(KeyValuePair[] array, int arrayIndex) => + ((IDictionary)items).CopyTo(array, arrayIndex); + + void ICollection.CopyTo(Array array, int index) => + ((ICollection)items).CopyTo(array, index); + + public IEnumerator> GetEnumerator() => + items.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => + items.GetEnumerator(); + + IDictionaryEnumerator IDictionary.GetEnumerator() => + ((IDictionary)items).GetEnumerator(); + + public bool Remove(string key) + { + if (items.TryGetValue(key, out var value)) + { + items.Remove(key); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); + + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { new KeyValuePair(key, value) }, -1)); + + return true; + } + else + { + return false; + } + } + + public bool Contains(string key) => + items.ContainsKey(key); + + bool ICollection>.Remove(KeyValuePair item) => + Remove(item.Key); + + void IDictionary.Remove(object key) => Remove((string)key); + + public bool TryGetValue(string key, [MaybeNullWhen(false)] out string value) => items.TryGetValue(key, out value); + + private void NotifyAdd(string key, string value) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); + + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, + new[] { new KeyValuePair(key, value) }, -1)); + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/ValidationRule.cs b/Toolkit.Foundation/ValidationRule.cs new file mode 100644 index 0000000..2c72aac --- /dev/null +++ b/Toolkit.Foundation/ValidationRule.cs @@ -0,0 +1,50 @@ +namespace Toolkit.Foundation; + +public class ValidationRule +{ + private readonly Func? syncValidation; + private readonly Func>? asyncValidation; + + public ValidationRule(Func validation, + string message) + { + syncValidation = validation; + Message = message; + } + + public ValidationRule(Func> validation, + string message) + { + asyncValidation = validation; + Message = message; + } + + public ValidationRule(Func validation) + { + syncValidation = validation; + Message = ""; + } + + public ValidationRule(Func> validation) + { + asyncValidation = validation; + Message = ""; + } + + public async Task Validate() + { + if (syncValidation is not null) + { + return syncValidation(); + } + + if (asyncValidation is not null) + { + return await asyncValidation(); + } + + return false; + } + + public string Message { get; } +} \ No newline at end of file diff --git a/Toolkit.Foundation/ValidationTrigger.cs b/Toolkit.Foundation/ValidationTrigger.cs new file mode 100644 index 0000000..c56a12e --- /dev/null +++ b/Toolkit.Foundation/ValidationTrigger.cs @@ -0,0 +1,7 @@ +namespace Toolkit.Foundation; + +public enum ValidationTrigger +{ + Deferred, + Immediate +} \ No newline at end of file diff --git a/Toolkit.Foundation/Validator.cs b/Toolkit.Foundation/Validator.cs new file mode 100644 index 0000000..ef237a8 --- /dev/null +++ b/Toolkit.Foundation/Validator.cs @@ -0,0 +1,22 @@ +namespace Toolkit.Foundation; + +public class Validator(string propertyName, + ValidationRule[] rules) +{ + private readonly ValidationRule[] rules = rules; + + public string? PropertyName { get; } = propertyName; + + public async Task<(bool isValid, string? message)> TryValidate() + { + foreach (ValidationRule rule in rules) + { + if (!await rule.Validate()) + { + return (false, rule.Message); + } + } + + return (true, null); + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/ValidatorCollection.cs b/Toolkit.Foundation/ValidatorCollection.cs new file mode 100644 index 0000000..434f577 --- /dev/null +++ b/Toolkit.Foundation/ValidatorCollection.cs @@ -0,0 +1,24 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; + +namespace Toolkit.Foundation; + +public class ValidatorCollection : + IValidatorCollection +{ + private readonly Dictionary binders = []; + + public int Count => binders.Count; + + public void Add(string key, Validator binder) => + binders.Add(key, binder); + + public IEnumerator GetEnumerator() => + binders.Select(x => x.Value).GetEnumerator(); + + public bool TryGet(string key, [MaybeNull] out Validator? value) => + binders.TryGetValue(key, out value); + + IEnumerator IEnumerable.GetEnumerator() => + binders.Select(x => x.Value).GetEnumerator(); +} \ No newline at end of file diff --git a/Toolkit.Foundation/ValueViewModel.cs b/Toolkit.Foundation/ValueViewModel.cs deleted file mode 100644 index da03c02..0000000 --- a/Toolkit.Foundation/ValueViewModel.cs +++ /dev/null @@ -1,21 +0,0 @@ -using CommunityToolkit.Mvvm.ComponentModel; - -namespace Toolkit.Foundation; - -public partial class ValueViewModel(IServiceProvider serviceProvider, - IServiceFactory serviceFactory, - IPublisher publisher, - ISubscriber subscriber, - IDisposer disposer) : - ObservableViewModel(serviceProvider, serviceFactory, publisher, subscriber, disposer) -{ - [ObservableProperty] - private TValue? value; - - protected virtual void OnChanged(TValue? value) - { - - } - - partial void OnValueChanged(TValue? value) => OnChanged(value); -} diff --git a/Toolkit.Foundation/VirtualKey.cs b/Toolkit.Foundation/VirtualKey.cs index 67ca644..d02ae56 100644 --- a/Toolkit.Foundation/VirtualKey.cs +++ b/Toolkit.Foundation/VirtualKey.cs @@ -172,4 +172,4 @@ public enum VirtualKey GamepadRightThumbstickDown = 216, GamepadRightThumbstickRight = 217, GamepadRightThumbstickLeft = 218 -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/WritableConfiguration.cs b/Toolkit.Foundation/WritableConfiguration.cs index 4f1f4f1..11f5727 100644 --- a/Toolkit.Foundation/WritableConfiguration.cs +++ b/Toolkit.Foundation/WritableConfiguration.cs @@ -6,4 +6,4 @@ public class WritableConfiguration(IConfigurationWriter updateDelegate) => writer.Write(updateDelegate); -} +} \ No newline at end of file diff --git a/Toolkit.Foundation/Write.cs b/Toolkit.Foundation/Write.cs new file mode 100644 index 0000000..479e25b --- /dev/null +++ b/Toolkit.Foundation/Write.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation; + +public class Write +{ + public static WriteEventArgs As(TSender sender) => + new(sender); + + public static WriteEventArgs As() where TSender : new() => + new(new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/WriteClipboardHandler.cs b/Toolkit.Foundation/WriteClipboardHandler.cs new file mode 100644 index 0000000..ac58dfc --- /dev/null +++ b/Toolkit.Foundation/WriteClipboardHandler.cs @@ -0,0 +1,13 @@ +namespace Toolkit.Foundation; + +public class WriteClipboardHandler(IClipboardWriter clipboardWriter) : + INotificationHandler>> +{ + public async Task Handle(WriteEventArgs> args) + { + if (args.Sender is Clipboard clipboard) + { + await clipboardWriter.Write(clipboard.Value); + } + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/WriteEventArgs.cs b/Toolkit.Foundation/WriteEventArgs.cs new file mode 100644 index 0000000..f6ad727 --- /dev/null +++ b/Toolkit.Foundation/WriteEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record WriteEventArgs(TSender? Sender = default); \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/AttachedBehavior.cs b/Toolkit.UI.Avalonia/AttachedBehaviour.cs similarity index 84% rename from Toolkit.UI.Avalonia/AttachedBehavior.cs rename to Toolkit.UI.Avalonia/AttachedBehaviour.cs index eae5e7c..687986b 100644 --- a/Toolkit.UI.Avalonia/AttachedBehavior.cs +++ b/Toolkit.UI.Avalonia/AttachedBehaviour.cs @@ -2,11 +2,12 @@ namespace Toolkit.UI.Avalonia; -public class AttachedBehavior : Trigger +public class AttachedBehaviour : + Trigger { protected override void OnAttachedToVisualTree() { Interaction.ExecuteActions(AssociatedObject, Actions, null); base.OnAttachedToVisualTree(); } -} +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/AttachedEventTriggerBehaviour.cs b/Toolkit.UI.Avalonia/AttachedEventTriggerBehaviour.cs new file mode 100644 index 0000000..8c6526d --- /dev/null +++ b/Toolkit.UI.Avalonia/AttachedEventTriggerBehaviour.cs @@ -0,0 +1,47 @@ +using Avalonia; +using Avalonia.Interactivity; +using Avalonia.Xaml.Interactivity; + +namespace Toolkit.UI.Avalonia; + +public class AttachedEventTriggerBehaviour : + Trigger +{ + public static readonly StyledProperty RoutedEventProperty = + AvaloniaProperty.Register(nameof(RoutedEvent)); + + public RoutedEvent RoutedEvent + { + get => GetValue(RoutedEventProperty); + set => SetValue(RoutedEventProperty, value); + } + + protected override void OnAttached() + { + if (RoutedEvent is not null) + { + if (AssociatedObject is Interactive interactive) + { + interactive.AddHandler(RoutedEvent, Handle); + } + } + + base.OnAttached(); + } + + protected override void OnDetaching() + { + if (RoutedEvent is not null) + { + if (AssociatedObject is Interactive interactive) + { + interactive.RemoveHandler(RoutedEvent, Handle); + } + } + + base.OnDetaching(); + } + + private void Handle(object sender, RoutedEventArgs args) => + Interaction.ExecuteActions(AssociatedObject, Actions, null); +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/BooleanToPasswordCharConverter.cs b/Toolkit.UI.Avalonia/BooleanToPasswordCharConverter.cs new file mode 100644 index 0000000..4aa88c7 --- /dev/null +++ b/Toolkit.UI.Avalonia/BooleanToPasswordCharConverter.cs @@ -0,0 +1,21 @@ +using Avalonia.Data.Converters; +using Avalonia.Markup.Xaml; +using System.Globalization; + +namespace Toolkit.UI.Avalonia; + +public class BooleanToPasswordCharConverter : + MarkupExtension, + IValueConverter +{ + public override object ProvideValue(IServiceProvider serviceProvider) => + this; + + public char PasswordChar { get; set; } + + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) => + value is bool boolValue ? boolValue ? '\0' : PasswordChar : (object)PasswordChar; + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => + throw new NotImplementedException(); +} diff --git a/Toolkit.UI.Avalonia/ComparisonCondition.cs b/Toolkit.UI.Avalonia/ComparisonCondition.cs index 6d112be..83e271c 100644 --- a/Toolkit.UI.Avalonia/ComparisonCondition.cs +++ b/Toolkit.UI.Avalonia/ComparisonCondition.cs @@ -2,8 +2,9 @@ using Avalonia.Xaml.Interactivity; namespace Toolkit.UI.Avalonia; -public class ComparisonCondition : - AvaloniaObject, + +public class ComparisonCondition : + AvaloniaObject, ICondition { public static readonly StyledProperty LeftOperandProperty = @@ -35,4 +36,4 @@ public class ComparisonCondition : public bool Evaluate() => ComparisonLogic.Evaluate(LeftOperand, Operator, RightOperand); -} +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/ComparisonLogic.cs b/Toolkit.UI.Avalonia/ComparisonLogic.cs index 96415a3..2e1f281 100644 --- a/Toolkit.UI.Avalonia/ComparisonLogic.cs +++ b/Toolkit.UI.Avalonia/ComparisonLogic.cs @@ -6,8 +6,8 @@ namespace Toolkit.UI.Avalonia; internal static class ComparisonLogic { - internal static bool Evaluate(object leftOperand, - ComparisonConditionType operatorType, + internal static bool Evaluate(object leftOperand, + ComparisonConditionType operatorType, object? rightOperand) { bool result = false; @@ -17,14 +17,13 @@ internal static class ComparisonLogic Type leftType = leftOperand.GetType(); if (rightOperand != null) - { + { TypeConverter typeConverter = TypeDescriptor.GetConverter(leftType); rightOperand = typeConverter.ConvertFrom(rightOperand); } } - - if (leftOperand is IComparable leftComparableOperand && + if (leftOperand is IComparable leftComparableOperand && rightOperand is IComparable rightComparableOperand) { return EvaluateComparable(leftComparableOperand, operatorType, rightComparableOperand); @@ -35,6 +34,7 @@ internal static class ComparisonLogic case ComparisonConditionType.Equal: result = Equals(leftOperand, rightOperand); break; + case ComparisonConditionType.NotEqual: result = !Equals(leftOperand, rightOperand); break; @@ -43,7 +43,7 @@ internal static class ComparisonLogic } private static bool EvaluateComparable(IComparable leftOperand, - ComparisonConditionType operatorType, + ComparisonConditionType operatorType, IComparable rightOperand) { object? convertedOperand = null; @@ -54,11 +54,9 @@ internal static class ComparisonLogic } catch (FormatException) { - } catch (InvalidCastException) { - } if (convertedOperand == null) @@ -74,18 +72,23 @@ internal static class ComparisonLogic 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; @@ -93,4 +96,4 @@ internal static class ComparisonLogic return result; } -} +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/ConditionAction.cs b/Toolkit.UI.Avalonia/ConditionAction.cs index 29a25a1..36519ed 100644 --- a/Toolkit.UI.Avalonia/ConditionAction.cs +++ b/Toolkit.UI.Avalonia/ConditionAction.cs @@ -4,13 +4,12 @@ using Avalonia.Xaml.Interactivity; namespace Toolkit.UI.Avalonia; -public class ConditionAction : +public class ConditionAction : AvaloniaObject, IAction { public static readonly DirectProperty ActionsProperty = - AvaloniaProperty.RegisterDirect(nameof(Actions), - x => x.Actions); + AvaloniaProperty.RegisterDirect(nameof(Actions), x => x.Actions); public static readonly StyledProperty ConditionProperty = AvaloniaProperty.Register(nameof(Condition)); diff --git a/Toolkit.UI.Avalonia/ConditionCollection.cs b/Toolkit.UI.Avalonia/ConditionCollection.cs index 36f0c69..d8bee8c 100644 --- a/Toolkit.UI.Avalonia/ConditionCollection.cs +++ b/Toolkit.UI.Avalonia/ConditionCollection.cs @@ -5,4 +5,4 @@ namespace Toolkit.UI.Avalonia; public class ConditionCollection : ObservableCollection { -} +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/ConditionalExpression.cs b/Toolkit.UI.Avalonia/ConditionalExpression.cs index 8b6d28d..14c35ab 100644 --- a/Toolkit.UI.Avalonia/ConditionalExpression.cs +++ b/Toolkit.UI.Avalonia/ConditionalExpression.cs @@ -1,10 +1,9 @@ using Avalonia; using Avalonia.Metadata; -using Toolkit.UI.Avalonia; namespace Toolkit.UI.Avalonia; -public class ConditionalExpression : +public class ConditionalExpression : AvaloniaObject, ICondition { @@ -18,7 +17,7 @@ public class ConditionalExpression : SetValue(ConditionsProperty, []); [Content] - public ConditionCollection Conditions => + public ConditionCollection Conditions => GetValue(ConditionsProperty); public ForwardChaining ForwardChaining @@ -47,4 +46,4 @@ public class ConditionalExpression : return result; } -} +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/EventListenerBehaviour.cs b/Toolkit.UI.Avalonia/EventListenerBehaviour.cs new file mode 100644 index 0000000..b0004c9 --- /dev/null +++ b/Toolkit.UI.Avalonia/EventListenerBehaviour.cs @@ -0,0 +1,133 @@ +using Avalonia; +using Avalonia.Xaml.Interactivity; +using System.Reactive; +using System.Reflection; + +namespace Toolkit.UI.Avalonia; + +public class EventListenerBehaviour : + Trigger +{ + public static readonly StyledProperty EventNameProperty = + AvaloniaProperty.Register(nameof(EventName)); + + public static readonly StyledProperty SourceProperty = + AvaloniaProperty.Register(nameof(Source)); + + private readonly Delegate? eventHandler; + private object? resolvedSource; + + static EventListenerBehaviour() + { + EventNameProperty.Changed.Subscribe(new AnonymousObserver>(EventNamePropertyChanged)); + SourceProperty.Changed.Subscribe(new AnonymousObserver>(SourcePropertyChanged)); + } + + public string EventName + { + get => GetValue(EventNameProperty); + set => SetValue(EventNameProperty, value); + } + + public object Source + { + get => GetValue(SourceProperty); + set => SetValue(SourceProperty, value); + } + + private static void EventNamePropertyChanged(AvaloniaPropertyChangedEventArgs args) + { + if (args.Sender is EventListenerBehaviour behaviour) + { + if (args.OldValue.GetValueOrDefault() is string oldValue) + { + behaviour.UnregisterEvent(oldValue); + } + + if (args.NewValue.GetValueOrDefault() is string newValue) + { + behaviour.RegisterEvent(newValue); + } + } + } + + private static void SourcePropertyChanged(AvaloniaPropertyChangedEventArgs args) + { + if (args.Sender is EventListenerBehaviour behaviour) + { + behaviour.SetResolvedSource(args.GetNewValue()); + } + } + + + private void OnEventRaised(object? sender, EventArgs args) + { + if (IsEnabled) + { + Interaction.ExecuteActions(AssociatedObject, Actions, null); + } + } + + private void RegisterEvent(string eventName) + { + if (eventName is { Length: 0 }) + { + return; + } + + if (resolvedSource is null) + { + return; + } + + Type sourceType = resolvedSource.GetType(); + if (sourceType.GetEvent(EventName) is EventInfo eventInfo) + { + eventInfo.AddEventHandler(resolvedSource, new EventHandler(OnEventRaised)); + } + } + + private void SetResolvedSource(object? newSource) + { + if (resolvedSource == newSource) + { + return; + } + + if (resolvedSource is not null) + { + UnregisterEvent(EventName); + } + + resolvedSource = newSource; + + if (resolvedSource is not null) + { + RegisterEvent(EventName); + } + } + + private void UnregisterEvent(string eventName) + { + if (eventHandler is null) + { + return; + } + + if (eventName is { Length: 0 }) + { + return; + } + + if (resolvedSource is null) + { + return; + } + + Type sourceType = resolvedSource.GetType(); + if (sourceType.GetEvent(EventName) is EventInfo eventInfo) + { + eventInfo.RemoveEventHandler(resolvedSource, new EventHandler(OnEventRaised)); + } + } +} diff --git a/Toolkit.UI.Avalonia/FileSizeNameConverter.cs b/Toolkit.UI.Avalonia/FileSizeNameConverter.cs new file mode 100644 index 0000000..b4743ba --- /dev/null +++ b/Toolkit.UI.Avalonia/FileSizeNameConverter.cs @@ -0,0 +1,37 @@ +using Avalonia.Data.Converters; +using Avalonia.Markup.Xaml; +using System.Globalization; + +namespace Toolkit.UI.Avalonia; + +public class FileSizeNameConverter : + MarkupExtension, + IValueConverter +{ + public override object ProvideValue(IServiceProvider serviceProvider) => + this; + + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is long fileSize) + { + string[] sizeSuffixes = ["bytes", "KB", "MB", "GB", "TB", "PB"]; + + int order = 0; + double adjustedSize = fileSize; + + while (adjustedSize >= 1024 && order < sizeSuffixes.Length - 1) + { + order++; + adjustedSize /= 1024; + } + + return $"{adjustedSize:0.##} {sizeSuffixes[order]}"; + } + + return "0 bytes"; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => + throw new NotImplementedException(); +} diff --git a/Toolkit.UI.Avalonia/ForwardChaining.cs b/Toolkit.UI.Avalonia/ForwardChaining.cs index 7dac9ee..d716d8b 100644 --- a/Toolkit.UI.Avalonia/ForwardChaining.cs +++ b/Toolkit.UI.Avalonia/ForwardChaining.cs @@ -4,4 +4,4 @@ public enum ForwardChaining { And, Or -} +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/ICondition.cs b/Toolkit.UI.Avalonia/ICondition.cs index 5f95e0b..f64733a 100644 --- a/Toolkit.UI.Avalonia/ICondition.cs +++ b/Toolkit.UI.Avalonia/ICondition.cs @@ -3,4 +3,4 @@ public interface ICondition { bool Evaluate(); -} +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/InvokeNavigationViewItemAction.cs b/Toolkit.UI.Avalonia/InvokeNavigationViewItemAction.cs new file mode 100644 index 0000000..87626ee --- /dev/null +++ b/Toolkit.UI.Avalonia/InvokeNavigationViewItemAction.cs @@ -0,0 +1,67 @@ +using Avalonia; +using Avalonia.Threading; +using Avalonia.Xaml.Interactivity; +using System.Collections; +using Toolkit.UI.Controls.Avalonia; + +namespace Toolkit.UI.Avalonia; + +public class InvokeNavigationViewItemAction : + AvaloniaObject, + IAction +{ + public static readonly StyledProperty SelectedIndexProperty = + AvaloniaProperty.Register(nameof(SelectedIndex), 0); + + public static readonly StyledProperty TargetProperty = + AvaloniaProperty.Register(nameof(Target)); + + public object Target + { + get => GetValue(TargetProperty); + set => SetValue(TargetProperty, value); + } + + public int SelectedIndex + { + get => GetValue(SelectedIndexProperty); + set => SetValue(SelectedIndexProperty, value); + } + + public object? Execute(object? sender, object? parameter) + { + if ((Target ?? sender) is NavigationViewItem navigationViewItem) + { + Dispatcher.UIThread.Post(() => + { + if (navigationViewItem.MenuItemsSource is IList collection) + { + if (collection is { Count: > 0 }) + { + navigationViewItem.SetValue(NavigationView.SelectedItemProperty, collection[SelectedIndex]); + } + } + }, DispatcherPriority.ContextIdle); + } + + if ((Target ?? sender) is NavigationView navigationView) + { + Dispatcher.UIThread.Invoke(() => + { + if (navigationView.MenuItemsSource is IList collection) + { + if (collection is { Count: > 0 }) + { + navigationView.SetValue(NavigationView.SelectedItemProperty, collection[SelectedIndex]); + } + else + { + navigationView.SetValue(NavigationView.SelectedItemProperty, null); + } + } + }, DispatcherPriority.ContextIdle); + } + + return true; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/ItemInvokedEventArgs.cs b/Toolkit.UI.Avalonia/ItemInvokedEventArgs.cs new file mode 100644 index 0000000..96f37a4 --- /dev/null +++ b/Toolkit.UI.Avalonia/ItemInvokedEventArgs.cs @@ -0,0 +1,6 @@ +using Avalonia.Interactivity; + +namespace Toolkit.UI.Avalonia; + +public class ItemInvokedEventArgs : + RoutedEventArgs; \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/KeyBindingTriggerBehaviour.cs b/Toolkit.UI.Avalonia/KeyBindingTriggerBehaviour.cs new file mode 100644 index 0000000..ef45513 --- /dev/null +++ b/Toolkit.UI.Avalonia/KeyBindingTriggerBehaviour.cs @@ -0,0 +1,48 @@ +using Avalonia; +using Avalonia.Input; +using Avalonia.Xaml.Interactivity; +using System.Windows.Input; + +namespace Toolkit.UI.Avalonia; + +public class KeyBindingTriggerBehaviour : + Trigger, + ICommand +{ + public static readonly StyledProperty GestureProperty = + AvaloniaProperty.Register(nameof(Gesture)); + + public KeyGesture Gesture + { + get => GetValue(GestureProperty); + set => SetValue(GestureProperty, value); + } + + public event EventHandler? CanExecuteChanged; + + protected override void OnAttached() + { + if (Gesture is not null) + { + KeyBinding keyBinding = new() + { + Gesture = Gesture, + Command = this + }; + + AssociatedObject?.KeyBindings.Add(keyBinding); + } + + base.OnAttached(); + } + + public bool CanExecute(object? parameter) => true; + + public void Execute(object? parameter) + { + if (IsEnabled) + { + Interaction.ExecuteActions(AssociatedObject, Actions, null); + } + } +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/ListBoxExtension.cs b/Toolkit.UI.Avalonia/ListBoxExtension.cs new file mode 100644 index 0000000..b8339ea --- /dev/null +++ b/Toolkit.UI.Avalonia/ListBoxExtension.cs @@ -0,0 +1,83 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.LogicalTree; + +namespace Toolkit.UI.Avalonia; + +public class ListBoxExtension +{ + public static readonly AttachedProperty IsItemInvokedEnabledProperty = + AvaloniaProperty.RegisterAttached("IsItemInvokedEnabled", + typeof(ListBoxExtension), false); + + public static readonly RoutedEvent ItemInvokedEvent = + RoutedEvent.Register("ItemInvoked", + RoutingStrategies.Bubble, typeof(ListBoxExtension)); + + static ListBoxExtension() + { + IsItemInvokedEnabledProperty.Changed.AddClassHandler(OnIsItemClickEnabledPropertyChanged); + } + + private static void OnIsItemClickEnabledPropertyChanged(ListBoxItem sender, + AvaloniaPropertyChangedEventArgs args) + { + bool TrySetupListBox() + { + if (sender.GetLogicalAncestors().OfType().FirstOrDefault() is ListBox listBox) + { + void OnItemInvoked(object? _, SelectionChangedEventArgs args) + { + if (args.AddedItems is { Count: > 0 }) + { + if (sender.DataContext == listBox.SelectedItem) + { + sender.RaiseEvent(new ItemInvokedEventArgs { RoutedEvent = ItemInvokedEvent }); + } + } + } + + if (sender.DataContext == listBox.SelectedItem) + { + sender.RaiseEvent(new ItemInvokedEventArgs { RoutedEvent = ItemInvokedEvent }); + } + + void HandleUnloaded(object? _, RoutedEventArgs __) + { + listBox.SelectionChanged -= OnItemInvoked; + listBox.Unloaded -= HandleUnloaded; + } + + listBox.SelectionChanged += OnItemInvoked; + listBox.Unloaded += HandleUnloaded; + + return true; + } + + return false; + } + + if (!TrySetupListBox()) + { + void HandleLoaded(object? _, RoutedEventArgs __) + { + TrySetupListBox(); + } + + sender.Loaded += HandleLoaded; + } + } + + public static bool GetIsItemInvokedEnabled(ListBoxItem element) => + element.GetValue(IsItemInvokedEnabledProperty); + + public static void SetIsItemInvokedEnabled(ListBoxItem element, bool value) => + element.SetValue(IsItemInvokedEnabledProperty, value); + + public static void AddItemInvokedHandler(ListBoxItem element, EventHandler handler) => + element.AddHandler(ItemInvokedEvent, handler); + + public static void RemoveItemInvokedHandler(ListBoxItem element, EventHandler handler) => + element.RemoveHandler(ItemInvokedEvent, handler); +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/NamedTypeConverter.cs b/Toolkit.UI.Avalonia/NamedTypeConverter.cs new file mode 100644 index 0000000..1064078 --- /dev/null +++ b/Toolkit.UI.Avalonia/NamedTypeConverter.cs @@ -0,0 +1,18 @@ +using Avalonia.Data.Converters; +using Avalonia.Markup.Xaml; +using System.Globalization; + +namespace Toolkit.UI.Avalonia; + +public class NamedTypeConverter : + MarkupExtension, + IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) => + value is not null ? value.GetType().Name : (object?)null; + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => + throw new NotImplementedException(); + + public override object ProvideValue(IServiceProvider serviceProvider) => this; +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/NavigateAction.cs b/Toolkit.UI.Avalonia/NavigateAction.cs index 1ff15ef..cd9f277 100644 --- a/Toolkit.UI.Avalonia/NavigateAction.cs +++ b/Toolkit.UI.Avalonia/NavigateAction.cs @@ -1,7 +1,8 @@ using Avalonia; -using Avalonia.Controls.Primitives; +using Avalonia.Controls; using Avalonia.Metadata; using Avalonia.Xaml.Interactivity; +using System.Collections.Immutable; using Toolkit.Foundation; namespace Toolkit.UI.Avalonia; @@ -10,15 +11,12 @@ public class NavigateAction : AvaloniaObject, IAction { - public static readonly StyledProperty ContextProperty = - AvaloniaProperty.Register(nameof(Context)); + public static readonly DirectProperty ParametersProperty = + AvaloniaProperty.RegisterDirect(nameof(Parameters), + x => x.Parameters); - 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 RegionProperty = + AvaloniaProperty.Register(nameof(Region)); public static readonly StyledProperty RouteProperty = AvaloniaProperty.Register(nameof(Route)); @@ -26,31 +24,26 @@ public class NavigateAction : public static readonly StyledProperty ScopeProperty = AvaloniaProperty.Register(nameof(Scope)); - private ParameterBindingCollection parameterCollection = []; + private ParameterCollection parameterCollection = []; public event EventHandler? Navigated; - public object Context + public object Region { - get => GetValue(ContextProperty); - set => SetValue(ContextProperty, value); + get => GetValue(RegionProperty); + set => SetValue(RegionProperty, value); } [Content] - public ParameterBindingCollection ParameterBindings => + public ParameterCollection Parameters => 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); @@ -60,23 +53,21 @@ public class NavigateAction : public object Execute(object? sender, object? parameter) { - if (sender is TemplatedControl control) + if (sender is Control content) { - Dictionary arguments = + Dictionary arguments = new(StringComparer.InvariantCultureIgnoreCase); - if (control.DataContext is IObservableViewModel observableViewModel) + if (content.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>()]; + ImmutableDictionary? parameters = Parameters is { Count: > 0 } ? Parameters.ToImmutableDictionary(x => x.Key, x => x.Value) : + ImmutableDictionary.Empty; - observableViewModel.Publisher.Publish(new Navigate(Route, Context - ?? null, Scope ?? null, control.DataContext, Navigated, parameters)); + observableViewModel.Publisher.Publish(new NavigateEventArgs(Route, Region == this ? content : Region, Scope ?? null, + content.DataContext, Navigated, parameters)); } } return true; } -} +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/NavigateBackAction.cs b/Toolkit.UI.Avalonia/NavigateBackAction.cs index 93d66db..62e6dbd 100644 --- a/Toolkit.UI.Avalonia/NavigateBackAction.cs +++ b/Toolkit.UI.Avalonia/NavigateBackAction.cs @@ -9,16 +9,16 @@ public class NavigateBackAction : AvaloniaObject, IAction { - public static readonly StyledProperty ContextProperty = - AvaloniaProperty.Register(nameof(Context)); + public static readonly StyledProperty RegionProperty = + AvaloniaProperty.Register(nameof(Region)); public static readonly StyledProperty ScopeProperty = AvaloniaProperty.Register(nameof(Scope)); - public string Context + public string Region { - get => GetValue(ContextProperty); - set => SetValue(ContextProperty, value); + get => GetValue(RegionProperty); + set => SetValue(RegionProperty, value); } public string Scope @@ -34,11 +34,11 @@ public class NavigateBackAction : { if (control.DataContext is IObservableViewModel observableViewModel) { - observableViewModel.Publisher.Publish(new NavigateBack(Context - ?? null, Scope ?? null)).GetAwaiter().GetResult(); + observableViewModel.Publisher.Publish(new NavigateBackEventArgs(Region + ?? null, Scope ?? null)); } } return true; } -} +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/NavigateRegionAction.cs b/Toolkit.UI.Avalonia/NavigateRegionAction.cs new file mode 100644 index 0000000..b497ac0 --- /dev/null +++ b/Toolkit.UI.Avalonia/NavigateRegionAction.cs @@ -0,0 +1,48 @@ +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 ActionsProperty = + AvaloniaProperty.RegisterDirect(nameof(Actions), x => x.Actions); + + public static readonly StyledProperty NameProperty = + AvaloniaProperty.Register(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() is INavigationRegion navigationRegion) + { + navigationRegion.Register(Name, sender); + Interaction.ExecuteActions(sender, Actions, parameter); + } + } + } + + return true; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/NavigationViewExtension.cs b/Toolkit.UI.Avalonia/NavigationViewExtension.cs new file mode 100644 index 0000000..aba1bee --- /dev/null +++ b/Toolkit.UI.Avalonia/NavigationViewExtension.cs @@ -0,0 +1,66 @@ +using Avalonia; +using Avalonia.Interactivity; +using Avalonia.LogicalTree; +using Toolkit.UI.Controls.Avalonia; + +namespace Toolkit.UI.Avalonia; + +public class NavigationViewExtension +{ + public static readonly AttachedProperty IsItemInvokedEnabledProperty = + AvaloniaProperty.RegisterAttached("IsItemInvokedEnabled", + typeof(NavigationViewExtension), false); + + public static readonly RoutedEvent ItemInvokedEvent = + RoutedEvent.Register("ItemInvoked", + RoutingStrategies.Bubble, typeof(NavigationViewExtension)); + + static NavigationViewExtension() + { + IsItemInvokedEnabledProperty.Changed.AddClassHandler(OnIsItemInvokedEnabledPropertyChanged); + } + + private static void OnIsItemInvokedEnabledPropertyChanged(NavigationViewItem sender, + AvaloniaPropertyChangedEventArgs args) + { + bool TrySetupNavigationView() + { + if (sender.GetLogicalAncestors().OfType().FirstOrDefault() is NavigationView navigationView) + { + sender.GetObservable(NavigationViewItem.IsSelectedProperty).Subscribe(args => + { + if (args) + { + sender.RaiseEvent(new ItemInvokedEventArgs { RoutedEvent = ItemInvokedEvent }); + } + }); + + return true; + } + + return false; + } + + if (!TrySetupNavigationView()) + { + void HandleLoaded(object? _, RoutedEventArgs __) + { + TrySetupNavigationView(); + } + + sender.Loaded += HandleLoaded; + } + } + + public static bool GetIsItemInvokedEnabled(NavigationViewItem element) => + element.GetValue(IsItemInvokedEnabledProperty); + + public static void SetIsItemInvokedEnabled(NavigationViewItem element, bool value) => + element.SetValue(IsItemInvokedEnabledProperty, value); + + public static void AddItemInvokedHandler(NavigationViewItem element, EventHandler handler) => + element.AddHandler(ItemInvokedEvent, handler); + + public static void RemoveItemInvokedHandler(NavigationViewItem element, EventHandler handler) => + element.RemoveHandler(ItemInvokedEvent, handler); +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/ParameterBinding.cs b/Toolkit.UI.Avalonia/Parameter.cs similarity index 70% rename from Toolkit.UI.Avalonia/ParameterBinding.cs rename to Toolkit.UI.Avalonia/Parameter.cs index abb7803..e6c4629 100644 --- a/Toolkit.UI.Avalonia/ParameterBinding.cs +++ b/Toolkit.UI.Avalonia/Parameter.cs @@ -2,23 +2,24 @@ namespace Toolkit.UI.Avalonia; -public class ParameterBinding : +public class Parameter : AvaloniaObject { public static readonly StyledProperty KeyProperty = - AvaloniaProperty.Register(nameof(Key)); + AvaloniaProperty.Register(nameof(Key)); public static readonly StyledProperty ValueProperty = - AvaloniaProperty.Register(nameof(Value)); + AvaloniaProperty.Register(nameof(Value)); public string Key { get => GetValue(KeyProperty); set => SetValue(KeyProperty, value); } + public object Value { get => GetValue(ValueProperty); set => SetValue(ValueProperty, value); } -} +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/ParameterBindingCollection.cs b/Toolkit.UI.Avalonia/ParameterBindingCollection.cs deleted file mode 100644 index 734b597..0000000 --- a/Toolkit.UI.Avalonia/ParameterBindingCollection.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Collections.ObjectModel; - -namespace Toolkit.UI.Avalonia; - -public class ParameterBindingCollection : - ObservableCollection; \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/ParameterCollection.cs b/Toolkit.UI.Avalonia/ParameterCollection.cs new file mode 100644 index 0000000..ebc4d99 --- /dev/null +++ b/Toolkit.UI.Avalonia/ParameterCollection.cs @@ -0,0 +1,6 @@ +using System.Collections.ObjectModel; + +namespace Toolkit.UI.Avalonia; + +public class ParameterCollection : + ObservableCollection; \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/Properties/Assembly.cs b/Toolkit.UI.Avalonia/Properties/Assembly.cs new file mode 100644 index 0000000..eaaf8a8 --- /dev/null +++ b/Toolkit.UI.Avalonia/Properties/Assembly.cs @@ -0,0 +1,3 @@ +using Avalonia.Metadata; + +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Toolkit.UI.Avalonia")] \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/TabStripExtension.cs b/Toolkit.UI.Avalonia/TabStripExtension.cs new file mode 100644 index 0000000..e48056e --- /dev/null +++ b/Toolkit.UI.Avalonia/TabStripExtension.cs @@ -0,0 +1,84 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Interactivity; +using Avalonia.LogicalTree; + +namespace Toolkit.UI.Avalonia; + +public class TabStripExtension +{ + public static readonly AttachedProperty IsItemInvokedEnabledProperty = + AvaloniaProperty.RegisterAttached("IsItemInvokedEnabled", + typeof(TabStripExtension), false); + + public static readonly RoutedEvent ItemInvokedEvent = + RoutedEvent.Register("ItemInvoked", + RoutingStrategies.Bubble, typeof(TabStripExtension)); + + static TabStripExtension() + { + IsItemInvokedEnabledProperty.Changed.AddClassHandler(OnIsItemClickEnabledPropertyChanged); + } + + private static void OnIsItemClickEnabledPropertyChanged(TabStripItem sender, + AvaloniaPropertyChangedEventArgs args) + { + bool TrySetupTabStrip() + { + if (sender.GetLogicalAncestors().OfType().FirstOrDefault() is TabStrip tabStrip) + { + void OnItemInvoked(object? _, SelectionChangedEventArgs args) + { + if (args.AddedItems is { Count: > 0 }) + { + if (sender.DataContext == tabStrip.SelectedItem) + { + sender.RaiseEvent(new ItemInvokedEventArgs { RoutedEvent = ItemInvokedEvent }); + } + } + } + + if (sender.DataContext == tabStrip.SelectedItem) + { + sender.RaiseEvent(new ItemInvokedEventArgs { RoutedEvent = ItemInvokedEvent }); + } + + void HandleUnloaded(object? _, RoutedEventArgs __) + { + tabStrip.SelectionChanged -= OnItemInvoked; + tabStrip.Unloaded -= HandleUnloaded; + } + + tabStrip.SelectionChanged += OnItemInvoked; + tabStrip.Unloaded += HandleUnloaded; + + return true; + } + + return false; + } + + if (!TrySetupTabStrip()) + { + void HandleLoaded(object? _, RoutedEventArgs __) + { + TrySetupTabStrip(); + } + + sender.Loaded += HandleLoaded; + } + } + + public static bool GetIsItemInvokedEnabled(TabStripItem element) => + element.GetValue(IsItemInvokedEnabledProperty); + + public static void SetIsItemInvokedEnabled(TabStripItem element, bool value) => + element.SetValue(IsItemInvokedEnabledProperty, value); + + public static void AddItemInvokedHandler(TabStripItem element, EventHandler handler) => + element.AddHandler(ItemInvokedEvent, handler); + + public static void RemoveItemInvokedHandler(TabStripItem element, EventHandler handler) => + element.RemoveHandler(ItemInvokedEvent, handler); +} diff --git a/Toolkit.UI.Avalonia/Toolkit.UI.Avalonia.csproj b/Toolkit.UI.Avalonia/Toolkit.UI.Avalonia.csproj index 97ea892..f5f752c 100644 --- a/Toolkit.UI.Avalonia/Toolkit.UI.Avalonia.csproj +++ b/Toolkit.UI.Avalonia/Toolkit.UI.Avalonia.csproj @@ -1,15 +1,16 @@  - net8.0 + net9.0 enable enable AnyCPU;x64;x86 - - + + + \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/AppWindow/AppWindow.cs b/Toolkit.UI.Controls.Avalonia/AppWindow/AppWindow.cs new file mode 100644 index 0000000..9f5338e --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/AppWindow/AppWindow.cs @@ -0,0 +1,15 @@ +using Avalonia.Controls.Chrome; + +namespace Toolkit.UI.Controls.Avalonia; + +public class AppWindow : FluentAvalonia.UI.Windowing.AppWindow +{ + protected override Type StyleKeyOverride => + typeof(FluentAvalonia.UI.Windowing.AppWindow); + + public AppWindow() + { + TitleBar.ExtendsContentIntoTitleBar = true; + TitleBar.TitleBarHitTestType = FluentAvalonia.UI.Windowing.TitleBarHitTestType.Complex; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/AsyncImage/AsyncImage.cs b/Toolkit.UI.Controls.Avalonia/AsyncImage/AsyncImage.cs index bacad06..a0da572 100644 --- a/Toolkit.UI.Controls.Avalonia/AsyncImage/AsyncImage.cs +++ b/Toolkit.UI.Controls.Avalonia/AsyncImage/AsyncImage.cs @@ -5,4 +5,4 @@ public class AsyncImage : { protected override Type StyleKeyOverride => typeof(global::Avalonia.Labs.Controls.AsyncImage); -} +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/BlurBehind/BlurBehind.cs b/Toolkit.UI.Controls.Avalonia/BlurBehind/BlurBehind.cs index 8284e95..35fc4eb 100644 --- a/Toolkit.UI.Controls.Avalonia/BlurBehind/BlurBehind.cs +++ b/Toolkit.UI.Controls.Avalonia/BlurBehind/BlurBehind.cs @@ -9,29 +9,29 @@ using SkiaSharp; namespace Toolkit.UI.Controls.Avalonia; -public class BlurBehind : +public class BlurBehind : Control { public static readonly StyledProperty MaterialProperty = AvaloniaProperty.Register("Material"); - public static readonly ImmutableExperimentalAcrylicMaterial DefaultAcrylicMaterialDark = + public static readonly ImmutableExperimentalAcrylicMaterial DefaultAcrylicMaterialDark = (ImmutableExperimentalAcrylicMaterial)new ExperimentalAcrylicMaterial() - { - MaterialOpacity = 0.25, - TintColor = Colors.Black, - TintOpacity = 0.7, - PlatformTransparencyCompensationLevel = 0 - }.ToImmutable(); + { + MaterialOpacity = 0.25, + TintColor = Colors.Black, + TintOpacity = 0.7, + PlatformTransparencyCompensationLevel = 0 + }.ToImmutable(); - public static readonly ImmutableExperimentalAcrylicMaterial DefaultAcrylicMaterialLight = + public static readonly ImmutableExperimentalAcrylicMaterial DefaultAcrylicMaterialLight = (ImmutableExperimentalAcrylicMaterial)new ExperimentalAcrylicMaterial() - { - MaterialOpacity = 0.0, - TintColor = Colors.White, - TintOpacity = 0.3, - PlatformTransparencyCompensationLevel = 0 - }.ToImmutable(); + { + MaterialOpacity = 0.0, + TintColor = Colors.White, + TintOpacity = 0.3, + PlatformTransparencyCompensationLevel = 0 + }.ToImmutable(); static BlurBehind() { @@ -49,7 +49,7 @@ public class BlurBehind : ImmutableExperimentalAcrylicMaterial material = Material is not null ? (ImmutableExperimentalAcrylicMaterial)Material.ToImmutable() : Application.Current?.ActualThemeVariant == ThemeVariant.Dark ? DefaultAcrylicMaterialDark : DefaultAcrylicMaterialLight; - + context.Custom(new BlurBehindRenderOperation(material, new Rect(default, Bounds.Size))); } @@ -63,18 +63,17 @@ public class BlurBehind : public void Dispose() { - } - public bool Equals(ICustomDrawOperation? other) => - other is BlurBehindRenderOperation behindRenderOperation && + public bool Equals(ICustomDrawOperation? other) => + other is BlurBehindRenderOperation behindRenderOperation && behindRenderOperation.bounds == bounds && behindRenderOperation.material.Equals(material); public bool HitTest(Point point) => bounds.Contains(point); public void Render(ImmediateDrawingContext context) { - if (context.TryGetFeature() is ISkiaSharpApiLeaseFeature leaseFeature) + if (context.TryGetFeature() is ISkiaSharpApiLeaseFeature leaseFeature) { using ISkiaSharpApiLease? lease = leaseFeature.Lease(); if (lease.SkCanvas is SKCanvas canvas) @@ -92,7 +91,7 @@ public class BlurBehind : SKImageInfo.PlatformColorType, SKAlphaType.Premul)); using (SKImageFilter filter = SKImageFilter.CreateBlur(8, 8, SKShaderTileMode.Clamp)) - using (SKPaint blurPaint = new() { Shader = backdropShader, ImageFilter = filter }) + using (SKPaint blurPaint = new() { Shader = backdropShader, ImageFilter = filter }) blurred.Canvas.DrawRect(5, 5, (float)bounds.Width - 20, (float)bounds.Height - 20, blurPaint); using SKImage blurSnap = blurred.Snapshot(); @@ -105,11 +104,9 @@ public class BlurBehind : canvas.DrawRect(0, 0, (float)bounds.Width, (float)bounds.Height, blurSnapPaint); } - } } } } } -} - +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/CarouselView/CarouselView.cs b/Toolkit.UI.Controls.Avalonia/CarouselView/CarouselView.cs index 23de510..2887ac5 100644 --- a/Toolkit.UI.Controls.Avalonia/CarouselView/CarouselView.cs +++ b/Toolkit.UI.Controls.Avalonia/CarouselView/CarouselView.cs @@ -78,7 +78,7 @@ public class CarouselView : indicatorVisual = ElementComposition.GetElementVisual(indicator); touchAreaVisual = ElementComposition.GetElementVisual(container); if (touchAreaVisual is not null) - { + { compositor = touchAreaVisual.Compositor; } @@ -136,7 +136,7 @@ public class CarouselView : protected override void OnPointerReleased(PointerReleasedEventArgs args) { - if (isPressed && container is not null + if (isPressed && container is not null && items is not null && indicatorVisual is not null) { @@ -167,12 +167,12 @@ public class CarouselView : } private void ArrangeItems(int newIndex, - int oldIndex = -1, + int oldIndex = -1, bool isAnimating = false) { - if (compositor is not null - && container is not null - && items is not null + if (compositor is not null + && container is not null + && items is not null && indicatorVisual is not null) { double containerHeight = Bounds.Height; @@ -237,15 +237,14 @@ public class CarouselView : scopedBatch.Completed += () => { - this.isAnimating = false; + this.isAnimating = false; for (int i = 0; i < columnCount; i++) { itemVisuals[(newIndex + i - 2 + columnCount) % columnCount].Offset = new Vector3((float)(offsets[i] - centreOffset), 0, 0); - } }; - + indicatorVisual.StartAnimation("Offset", indicatorAnimation); scopedBatch.Start(animationDuration); @@ -253,6 +252,7 @@ public class CarouselView : } } } + private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs args) => ArrangeItems(newIndex); diff --git a/Toolkit.UI.Controls.Avalonia/CarouselView/CarouselViewItem.cs b/Toolkit.UI.Controls.Avalonia/CarouselView/CarouselViewItem.cs index ed75a5f..a593889 100644 --- a/Toolkit.UI.Controls.Avalonia/CarouselView/CarouselViewItem.cs +++ b/Toolkit.UI.Controls.Avalonia/CarouselView/CarouselViewItem.cs @@ -9,4 +9,4 @@ public class CarouselViewItem : { PseudoClasses.Set(":selected", selected); } -} +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/ContentBadge/ContentBadge.axaml b/Toolkit.UI.Controls.Avalonia/ContentBadge/ContentBadge.axaml new file mode 100644 index 0000000..26b79d6 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/ContentBadge/ContentBadge.axaml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/Toolkit.UI.Controls.Avalonia/ContentBadge/ContentBadge.cs b/Toolkit.UI.Controls.Avalonia/ContentBadge/ContentBadge.cs new file mode 100644 index 0000000..bb24525 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/ContentBadge/ContentBadge.cs @@ -0,0 +1,161 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Media; +using Path = Avalonia.Controls.Shapes.Path; + +namespace Toolkit.UI.Controls.Avalonia; + +public class ContentBadge : + ContentControl +{ + public static readonly StyledProperty BadgePathProperty = + AvaloniaProperty.Register(nameof(BadgePath)); + + public static readonly StyledProperty BadgePlacementProperty = + AvaloniaProperty.Register(nameof(BadgePlacement), ContentBadgePlacement.BottomRight); + + public static readonly StyledProperty BadgeSizeProperty = + AvaloniaProperty.Register(nameof(BadgeSize), 14); + + public static readonly StyledProperty IsBadgeVisibleProperty = + AvaloniaProperty.Register(nameof(IsBadgeVisible), true); + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == BadgePathProperty || + change.Property == BadgeSizeProperty || + change.Property == IsBadgeVisibleProperty) + { + UpdateBadge(); + } + } + + private ContentControl? badgeContent; + + public string BadgePath + { + get => GetValue(BadgePathProperty); + set => SetValue(BadgePathProperty, value); + } + + public ContentBadgePlacement BadgePlacement + { + get => GetValue(BadgePlacementProperty); + set => SetValue(BadgePlacementProperty, value); + } + + public double BadgeSize + { + get => GetValue(BadgeSizeProperty); + set => SetValue(BadgeSizeProperty, value); + } + + public bool IsBadgeVisible + { + get => GetValue(IsBadgeVisibleProperty); + set => SetValue(IsBadgeVisibleProperty, value); + } + + public void UpdateBadge() + { + if (Content is Control content && badgeContent is not null) + { + if (IsBadgeVisible && + BadgePath is { Length: > 0 } && + Geometry.Parse(BadgePath) is Geometry geometry) + { + double backgroundWidth = DesiredSize.Width; + double backgroundHeight = DesiredSize.Height; + + double badgeWidth = geometry.Bounds.Width; + double badgeHeight = geometry.Bounds.Height; + double scale = BadgeSize / Math.Max(badgeWidth, badgeHeight); + + double scaleX = scale; + double scaleY = scale; + + double adjustedStrokeWidth = Math.Min(scaleX, scaleY) * 8; + + Geometry knockoutGeometry = geometry.GetWidenedGeometry(new Pen(new SolidColorBrush(Colors.Transparent), adjustedStrokeWidth)); + + TransformGroup transformGroup = new(); + transformGroup.Children.Add(new ScaleTransform(scaleX, scaleY)); + + double scaledWidth = knockoutGeometry.Bounds.Width * scaleX; + double scaledHeight = knockoutGeometry.Bounds.Height * scaleY; + + double offsetX = 0; + double offsetY = 0; + + switch (BadgePlacement) + { + case ContentBadgePlacement.TopLeft: + offsetX = 0; + offsetY = 0; + break; + + case ContentBadgePlacement.TopRight: + offsetX = backgroundWidth - scaledWidth; + offsetY = 0; + break; + + case ContentBadgePlacement.BottomLeft: + offsetX = 0; + offsetY = backgroundHeight - scaledHeight; + break; + + case ContentBadgePlacement.BottomRight: + offsetX = backgroundWidth - scaledWidth; + offsetY = backgroundHeight - scaledHeight; + break; + } + + transformGroup.Children.Add(new TranslateTransform(offsetX, offsetY)); + knockoutGeometry.Transform = transformGroup; + + CombinedGeometry combinedGeometry = new() + { + GeometryCombineMode = GeometryCombineMode.Exclude, + Geometry1 = new RectangleGeometry { Rect = new Rect(0, 0, backgroundWidth, backgroundHeight) }, + Geometry2 = knockoutGeometry + }; + + content.Clip = combinedGeometry; + + Geometry overlayGeometry = geometry.Clone(); + + TransformGroup overlayTransformGroup = new(); + overlayTransformGroup.Children.Add(new ScaleTransform(scaleX, scaleY)); + + overlayTransformGroup.Children.Add(new TranslateTransform(offsetX, offsetY)); + overlayGeometry.Transform = overlayTransformGroup; + + badgeContent.Content = new Path + { + Data = overlayGeometry, + Fill = Foreground + }; + } + else + { + badgeContent.Content = null; + content.Clip = null; + } + } + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs args) + { + base.OnApplyTemplate(args); + badgeContent = args.NameScope.Get("BadgeContent"); + } + + protected override void OnSizeChanged(SizeChangedEventArgs args) + { + base.OnSizeChanged(args); + UpdateBadge(); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/ContentBadge/ContentBadgePlacement.cs b/Toolkit.UI.Controls.Avalonia/ContentBadge/ContentBadgePlacement.cs new file mode 100644 index 0000000..439390f --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/ContentBadge/ContentBadgePlacement.cs @@ -0,0 +1,9 @@ +namespace Toolkit.UI.Controls.Avalonia; + +public enum ContentBadgePlacement +{ + TopLeft, + TopRight, + BottomLeft, + BottomRight +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/ContentCard/ContentCard.axaml b/Toolkit.UI.Controls.Avalonia/ContentCard/ContentCard.axaml new file mode 100644 index 0000000..4692718 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/ContentCard/ContentCard.axaml @@ -0,0 +1,33 @@ + + 16 + + + + + + + + + + + + + + + diff --git a/Toolkit.UI.Controls.Avalonia/ContentCard/ContentCard.cs b/Toolkit.UI.Controls.Avalonia/ContentCard/ContentCard.cs new file mode 100644 index 0000000..2e9543e --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/ContentCard/ContentCard.cs @@ -0,0 +1,6 @@ +using Avalonia.Controls; + +namespace Toolkit.UI.Controls.Avalonia; + +public class ContentCard : + ContentControl; \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/ContentColorPicker/ContentColorPicker.axaml b/Toolkit.UI.Controls.Avalonia/ContentColorPicker/ContentColorPicker.axaml new file mode 100644 index 0000000..034b7a1 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/ContentColorPicker/ContentColorPicker.axaml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Toolkit.UI.Controls.Avalonia/ContentColorPicker/ContentColorPicker.cs b/Toolkit.UI.Controls.Avalonia/ContentColorPicker/ContentColorPicker.cs new file mode 100644 index 0000000..f653baa --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/ContentColorPicker/ContentColorPicker.cs @@ -0,0 +1,163 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.PanAndZoom; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.Media; +using Avalonia.Media.Imaging; + +namespace Toolkit.UI.Controls.Avalonia; + +public class ContentColorPicker : + ContentControl +{ + public static readonly StyledProperty PeekOffsetProperty = + AvaloniaProperty.Register(nameof(PeekOffset), 20); + + public static readonly StyledProperty PeekPixelsProperty = + AvaloniaProperty.Register(nameof(PeekPixels), 20); + + private readonly Image image = new(); + + private Canvas? canvas; + private (double X, double Y) lastPointerPosition; + private Border? peekBorder; + private ZoomBorder? zoomBorder; + + public double PeekOffset + { + get => GetValue(PeekOffsetProperty); + set => SetValue(PeekOffsetProperty, value); + } + + public int PeekPixels + { + get => GetValue(PeekPixelsProperty); + set => SetValue(PeekPixelsProperty, value); + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs args) + { + base.OnApplyTemplate(args); + + PointerMoved -= OnPointerMoved; + PointerExited -= OnPointerExited; + PointerEntered -= OnPointerEntered; + + PointerMoved += OnPointerMoved; + PointerExited += OnPointerExited; + PointerEntered += OnPointerEntered; + + canvas = args.NameScope.Find("Canvas"); + + zoomBorder = args.NameScope.Find("ZoomBorder"); + if (zoomBorder is not null) + { + zoomBorder.ZoomChanged += OnZoomChanged; + } + + peekBorder = args.NameScope.Find("PeekBorder"); + if (peekBorder is not null) + { + peekBorder.Child = image; + } + } + + private void OnPointerEntered(object? sender, + PointerEventArgs args) + { + if (peekBorder is not null) + { + peekBorder.IsVisible = true; + } + } + + private void OnPointerExited(object? sender, + PointerEventArgs args) + { + if (peekBorder is not null) + { + peekBorder.IsVisible = false; + } + } + + private void OnPointerMoved(object? sender, + PointerEventArgs args) + { + double relativeX = args.GetPosition(canvas).X; + double relativeY = args.GetPosition(canvas).Y; + + lastPointerPosition = (relativeX, relativeY); + + UpdatePeekPosition(relativeX, relativeY); + } + + private void OnZoomChanged(object sender, + ZoomChangedEventArgs args) => UpdatePeekPreview(lastPointerPosition.X, lastPointerPosition.Y); + + private Bitmap RenderToBitmap(Visual visual, + double centreX, + double centreY) + { + int width = PeekPixels; + int height = PeekPixels; + + double x = Math.Max(centreX - width / 2, 0); + double y = Math.Max(centreY - height / 2, 0); + + x = Math.Min(x, visual.Bounds.Width - width); + y = Math.Min(y, visual.Bounds.Height - height); + + PixelSize pixelSize = new(width, height); + RenderTargetBitmap renderTarget = new(pixelSize); + + using (DrawingContext drawingContext = renderTarget.CreateDrawingContext()) + { + drawingContext.PushClip(new Rect(0, 0, width, height)); + drawingContext.FillRectangle(new VisualBrush(visual), new Rect(-x, -y, + visual.Bounds.Width, visual.Bounds.Height)); + } + + return renderTarget; + } + + private void UpdatePeekPosition(double relativeX, + double relativeY) + { + if (canvas is null || peekBorder is null) + { + return; + } + + double peekOffset = PeekOffset; + + double newX = relativeX + peekOffset; + double newY = relativeY + peekOffset; + + newX = Math.Clamp(newX, -peekBorder.Bounds.Width, canvas.Bounds.Width); + newY = Math.Clamp(newY, -peekBorder.Bounds.Height, canvas.Bounds.Height); + + Canvas.SetLeft(peekBorder, newX); + Canvas.SetTop(peekBorder, newY); + + bool isPointerInside = relativeX >= -peekOffset && relativeX <= canvas.Bounds.Width + peekOffset && + relativeY >= -peekOffset && relativeY <= canvas.Bounds.Height + peekOffset; + + peekBorder.IsVisible = isPointerInside; + + if (isPointerInside) + { + UpdatePeekPreview(relativeX, relativeY); + } + } + + private void UpdatePeekPreview(double relativeX, + double relativeY) + { + if (zoomBorder is not null) + { + Bitmap bitmap = RenderToBitmap(zoomBorder, relativeX, relativeY); + image.Source = bitmap; + } + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/ContentCropper/ContentCropper.axaml b/Toolkit.UI.Controls.Avalonia/ContentCropper/ContentCropper.axaml new file mode 100644 index 0000000..25644d0 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/ContentCropper/ContentCropper.axaml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + 18 + 18 + + 3,3,0,0 + 0,3,3,0 + 0,0,3,3 + 3,0,0,3 + 3,0,0,0 + 0,3,0,0 + 0,0,3,0 + 0,0,0,3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Toolkit.UI.Controls.Avalonia/ContentCropper/ContentCropper.cs b/Toolkit.UI.Controls.Avalonia/ContentCropper/ContentCropper.cs new file mode 100644 index 0000000..6696440 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/ContentCropper/ContentCropper.cs @@ -0,0 +1,584 @@ +using Avalonia.Controls.Primitives; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia; +using Avalonia.Media; +using Path = Avalonia.Controls.Shapes.Path; +using Avalonia.Media.Imaging; + +namespace Toolkit.UI.Controls.Avalonia; + +public class ContentCropper : ContentControl +{ + public static readonly DirectProperty CroppedBitmapProperty = + AvaloniaProperty.RegisterDirect(nameof(CroppedBitmap), + o => o.CroppedBitmap); + + public static readonly StyledProperty CropRectangleProperty = + AvaloniaProperty.Register(nameof(CropRectangle)); + + public static readonly StyledProperty IsRatioScaleProperty = + AvaloniaProperty.Register(nameof(IsRatioScale)); + + public static readonly StyledProperty RectScaleProperty = + AvaloniaProperty.Register(nameof(RectScale), 0.5); + + public static readonly StyledProperty ScaleSizeProperty = + AvaloniaProperty.Register(nameof(ScaleSize), new Size(2, 1)); + + private Border? border; + private Thumb? bottomButton; + private Thumb? bottomLeftButton; + private Thumb? bottomRightButton; + private Canvas? canvas; + private double cropHeightRatio; + private double cropLeftRatio; + private Bitmap? croppedBitmap; + private double cropTopRatio; + private double cropWidthRatio; + private bool isDragging; + private Thumb? leftButton; + private double offsetX; + private double offsetY; + private Path? overlayPath; + private Thumb? rightButton; + private Thumb? topButton; + private Thumb? topLeftButton; + private Thumb? topRightButton; + + static ContentCropper() + { + AffectsRender(RectScaleProperty, ContentProperty); + } + + public Bitmap? CroppedBitmap + { + get => croppedBitmap; + private set => SetAndRaise(CroppedBitmapProperty, ref croppedBitmap, value); + } + public Rect CropRectangle + { + get => GetValue(CropRectangleProperty); + private set => SetValue(CropRectangleProperty, value); + } + + public bool IsRatioScale + { + get => GetValue(IsRatioScaleProperty); + set => SetValue(IsRatioScaleProperty, value); + } + + public double RectScale + { + get => GetValue(RectScaleProperty); + set => SetValue(RectScaleProperty, value); + } + + public Size ScaleSize + { + get => GetValue(ScaleSizeProperty); + set => SetValue(ScaleSizeProperty, value); + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs args) + { + base.OnApplyTemplate(args); + + canvas = args.NameScope.Find("Canvas"); + overlayPath = args.NameScope.Find("OverlayPath"); + border = args.NameScope.Find("Border"); + + topLeftButton = args.NameScope.Find("TopLeftButton"); + if (topLeftButton is not null) + { + topLeftButton.DragDelta += OnThumbDragDelta; + } + + topRightButton = args.NameScope.Find("TopRightButton"); + if (topRightButton is not null) + { + topRightButton.DragDelta += OnThumbDragDelta; + } + + bottomLeftButton = args.NameScope.Find("BottomLeftButton"); + if (bottomLeftButton is not null) + { + bottomLeftButton.DragDelta += OnThumbDragDelta; + } + + bottomRightButton = args.NameScope.Find("BottomRightButton"); + if (bottomRightButton is not null) + { + bottomRightButton.DragDelta += OnThumbDragDelta; + } + + leftButton = args.NameScope.Find("LeftButton"); + if (leftButton is not null) + { + leftButton.DragDelta += OnThumbDragDelta; + } + + rightButton = args.NameScope.Find("RightButton"); + if (rightButton is not null) + { + rightButton.DragDelta += OnThumbDragDelta; + } + + topButton = args.NameScope.Find("TopButton"); + if (topButton is not null) + { + topButton.DragDelta += OnThumbDragDelta; + } + + bottomButton = args.NameScope.Find("BottomButton"); + if (bottomButton is not null) + { + bottomButton.DragDelta += OnThumbDragDelta; + } + + } + + protected override void OnLoaded(RoutedEventArgs args) + { + base.OnLoaded(args); + InitializeCropRect(); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == IsRatioScaleProperty || + change.Property == RectScaleProperty || + change.Property == ContentProperty) + { + InitializeCropRect(); + } + } + + protected override void OnSizeChanged(SizeChangedEventArgs args) + { + base.OnSizeChanged(args); + + if (canvas is null || border is null) + { + return; + } + + double newContentWidth = Bounds.Width; + double newContentHeight = Bounds.Height; + + canvas.Width = newContentWidth; + canvas.Height = newContentHeight; + + if (border.Width > 0 && border.Height > 0) + { + double newCropLeft = cropLeftRatio * newContentWidth; + double newCropTop = cropTopRatio * newContentHeight; + double newCropWidth = cropWidthRatio * newContentWidth; + double newCropHeight = cropHeightRatio * newContentHeight; + + border.Width = newCropWidth; + border.Height = newCropHeight; + + Canvas.SetLeft(border, newCropLeft); + Canvas.SetTop(border, newCropTop); + } + else + { + InitializeCropRect(); + } + + UpdateCropRectangle(); + + PositionThumbs(); + RenderOverLays(); + } + + private void InitializeCropRect() + { + if (canvas is null || Content is not Control content) + { + return; + } + + double maxWidth = Bounds.Width; + double maxHeight = Bounds.Height; + + double contentWidth = content.Bounds.Width > 0 ? content.Bounds.Width : maxWidth * 0.5; + double contentHeight = content.Bounds.Height > 0 ? content.Bounds.Height : maxHeight * 0.5; + + double scaleFactor = Math.Min(maxWidth / contentWidth, maxHeight / contentHeight); + double width = contentWidth * scaleFactor; + double height = contentHeight * scaleFactor; + + canvas.Width = width; + canvas.Height = height; + + UpdateCropArea(width, height); + UpdateCropRatios(); + UpdateCropRectangle(); + + PositionThumbs(); + RenderOverLays(); + } + + private void OnBorderPointerMoved(object? sender, + PointerEventArgs args) + { + if (!isDragging || canvas is null || border is null) + { + return; + } + + Point position = args.GetPosition(this); + double newX = Math.Clamp(position.X - offsetX, 0, canvas.Bounds.Width - border.Bounds.Width); + double newY = Math.Clamp(position.Y - offsetY, 0, canvas.Bounds.Height - border.Bounds.Height); + + Canvas.SetLeft(border, newX); + Canvas.SetTop(border, newY); + + UpdateCropRectangle(); + + PositionThumbs(); + RenderOverLays(); + } + + private void OnBorderPointerPressed(object? sender, + PointerPressedEventArgs args) + { + if (!isDragging && border is not null) + { + isDragging = true; + Point position = args.GetPosition(this); + + offsetX = position.X - Canvas.GetLeft(border); + offsetY = position.Y - Canvas.GetTop(border); + } + } + + private void OnBorderPointerReleased(object? sender, + PointerReleasedEventArgs args) + { + isDragging = false; + UpdateCropRatios(); + } + + private void OnThumbDragDelta(object? sender, VectorEventArgs args) + { + if (canvas is null || border is null || sender is not Thumb thumb) + { + return; + } + + double minimumWidth = 20; + double minimumHeight = 20; + + double deltaX = args.Vector.X; + double deltaY = args.Vector.Y; + + double leftPosition = Canvas.GetLeft(border); + double topPosition = Canvas.GetTop(border); + double newWidth = border.Width; + double newHeight = border.Height; + + bool canResizeWidth = true; + bool canResizeHeight = true; + + switch (thumb.Name) + { + case "TopLeftButton": + if (border.Width - deltaX < minimumWidth) + { + canResizeWidth = false; + } + + if (border.Height - deltaY < minimumHeight) + { + canResizeHeight = false; + } + + if (canResizeWidth) + { + newWidth = border.Width - deltaX; + leftPosition += deltaX; + } + + if (canResizeHeight) + { + newHeight = border.Height - deltaY; + topPosition += deltaY; + } + break; + + case "TopRightButton": + if (border.Height - deltaY < minimumHeight) + { + canResizeHeight = false; + } + + newWidth = border.Width + deltaX; + if (canResizeHeight) + { + newHeight = border.Height - deltaY; + topPosition += deltaY; + } + break; + + case "BottomLeftButton": + if (border.Width - deltaX < minimumWidth) + { + canResizeWidth = false; + } + + newWidth = border.Width - deltaX; + if (canResizeWidth) + { + leftPosition += deltaX; + } + + newHeight = border.Height + deltaY; + break; + + case "BottomRightButton": + newWidth = border.Width + deltaX; + newHeight = border.Height + deltaY; + break; + + case "TopButton": + if (border.Height - deltaY < minimumHeight) + { + canResizeHeight = false; + } + + if (canResizeHeight) + { + newHeight = border.Height - deltaY; + topPosition += deltaY; + } + break; + + case "BottomButton": + newHeight = border.Height + deltaY; + break; + + case "LeftButton": + if (border.Width - deltaX < minimumWidth) + { + canResizeWidth = false; + } + + if (canResizeWidth) + { + newWidth = border.Width - deltaX; + leftPosition += deltaX; + } + break; + + case "RightButton": + newWidth = border.Width + deltaX; + break; + } + + if (leftPosition < 0 || leftPosition + newWidth > canvas.Width || + topPosition < 0 || topPosition + newHeight > canvas.Height || + newWidth < minimumWidth || newHeight < minimumHeight) + { + return; + } + + border.Width = newWidth; + border.Height = newHeight; + + Canvas.SetLeft(border, leftPosition); + Canvas.SetTop(border, topPosition); + + UpdateCropRatios(); + UpdateCropRectangle(); + + PositionThumbs(); + RenderOverLays(); + } + + private void PositionThumbs() + { + if (border == null || canvas == null) return; + + double borderLeft = Canvas.GetLeft(border); + double borderTop = Canvas.GetTop(border); + double borderWidth = border.Width; + double borderHeight = border.Height; + + if (topLeftButton is not null) + { + Canvas.SetLeft(topLeftButton, borderLeft); + Canvas.SetTop(topLeftButton, borderTop); + } + + if (topRightButton is not null) + { + Canvas.SetLeft(topRightButton, borderLeft + borderWidth - topRightButton.Width); + Canvas.SetTop(topRightButton, borderTop); + } + + if (bottomLeftButton is not null) + { + Canvas.SetLeft(bottomLeftButton, borderLeft); + Canvas.SetTop(bottomLeftButton, borderTop + borderHeight - bottomLeftButton.Height); + } + + if (bottomRightButton is not null) + { + Canvas.SetLeft(bottomRightButton, borderLeft + borderWidth - bottomRightButton.Width); + Canvas.SetTop(bottomRightButton, borderTop + borderHeight - bottomRightButton.Height); + } + + if (leftButton is not null) + { + Canvas.SetLeft(leftButton, borderLeft); + Canvas.SetTop(leftButton, borderTop + borderHeight / 2 - leftButton.Height / 2); + } + + if (rightButton is not null) + { + Canvas.SetLeft(rightButton, borderLeft + borderWidth - rightButton.Width); + Canvas.SetTop(rightButton, borderTop + borderHeight / 2 - rightButton.Height / 2); + } + + if (topButton is not null) + { + Canvas.SetLeft(topButton, borderLeft + borderWidth / 2 - topButton.Width / 2); + Canvas.SetTop(topButton, borderTop); + } + + if (bottomButton is not null) + { + Canvas.SetLeft(bottomButton, borderLeft + borderWidth / 2 - bottomButton.Width / 2); + Canvas.SetTop(bottomButton, borderTop + borderHeight - bottomButton.Height); + } + } + + private void RenderOverLays() + { + if (canvas == null || border == null || overlayPath == null) + { + return; + } + + double borderTop = Canvas.GetTop(border); + double borderLeft = Canvas.GetLeft(border); + double borderWidth = border.Width; + double borderHeight = border.Height; + + RectangleGeometry outerRect = new(new Rect(0, 0, + canvas.Width, + canvas.Height)); + + RectangleGeometry innerRect = new(new Rect(borderLeft, + borderTop, + borderWidth, + borderHeight)); + + CombinedGeometry punchThroughGeometry = new(GeometryCombineMode.Exclude, + outerRect, + innerRect); + + overlayPath.Data = punchThroughGeometry; + } + + private void UpdateCropArea(double width, double height) + { + if (canvas == null || border == null) + { + return; + } + + if (IsRatioScale && ScaleSize.Width > 0 && ScaleSize.Height > 0) + { + if (ScaleSize.Width > ScaleSize.Height) + { + border.Width = width * RectScale; + border.Height = border.Width / ScaleSize.Width; + } + else + { + border.Height = height * RectScale; + border.Width = border.Height * ScaleSize.Height; + } + } + else + { + border.Width = width * RectScale; + border.Height = height * RectScale; + } + + double centreX = (canvas.Width - border.Width) / 2; + double centreY = (canvas.Height - border.Height) / 2; + + Canvas.SetLeft(border, centreX); + Canvas.SetTop(border, centreY); + + PositionThumbs(); + + border.PointerPressed -= OnBorderPointerPressed; + border.PointerPressed += OnBorderPointerPressed; + border.PointerMoved -= OnBorderPointerMoved; + border.PointerMoved += OnBorderPointerMoved; + border.PointerReleased -= OnBorderPointerReleased; + border.PointerReleased += OnBorderPointerReleased; + } + + private void UpdateCroppedBitmap(Visual visual, + double centreX, + double centreY) + { + int width = (int)border.Width; + int height = (int)border.Height; + + double x = Math.Max(centreX - width / 2, 0); + double y = Math.Max(centreY - height / 2, 0); + + x = Math.Min(x, visual.Bounds.Width - width); + y = Math.Min(y, visual.Bounds.Height - height); + + PixelSize pixelSize = new(width, height); + RenderTargetBitmap renderTarget = new(pixelSize); + + using (DrawingContext drawingContext = renderTarget.CreateDrawingContext()) + { + drawingContext.PushClip(new Rect(0, 0, width, height)); + drawingContext.FillRectangle(new VisualBrush(visual), new Rect(-x, -y, + visual.Bounds.Width, visual.Bounds.Height)); + } + + CroppedBitmap = renderTarget; + } + + private void UpdateCropRatios() + { + if (canvas == null || border == null) + { + return; + } + + cropLeftRatio = Canvas.GetLeft(border) / canvas.Width; + cropTopRatio = Canvas.GetTop(border) / canvas.Height; + cropWidthRatio = border.Width / canvas.Width; + cropHeightRatio = border.Height / canvas.Height; + } + + private void UpdateCropRectangle() + { + if (canvas is null || border is null) + { + return; + } + + double left = Canvas.GetLeft(border); + double top = Canvas.GetTop(border); + + CropRectangle = new Rect(left, top, border.Width, border.Height); + UpdateCroppedBitmap(canvas, border.Width, border.Height); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/ContentDialog/ContentDialog.axaml b/Toolkit.UI.Controls.Avalonia/ContentDialog/ContentDialog.axaml deleted file mode 100644 index b7cc4ce..0000000 --- a/Toolkit.UI.Controls.Avalonia/ContentDialog/ContentDialog.axaml +++ /dev/null @@ -1,357 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + diff --git a/Toolkit.UI.Controls.Avalonia/Overflow/Overflow.cs b/Toolkit.UI.Controls.Avalonia/Overflow/Overflow.cs new file mode 100644 index 0000000..30da7ca --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/Overflow/Overflow.cs @@ -0,0 +1,319 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Metadata; +using Avalonia.Threading; +using FluentAvalonia.Core; +using System.Collections; +using System.Collections.ObjectModel; +using System.Collections.Specialized; + +namespace Toolkit.UI.Controls.Avalonia; + +public class Overflow : + TemplatedControl +{ + public static readonly StyledProperty ItemContainerTemplateSelectorProperty = + AvaloniaProperty.Register(nameof(ItemContainerTemplateSelector)); + + public static readonly StyledProperty> ItemsPanelProperty = + AvaloniaProperty.Register>(nameof(ItemsPanel), new FuncTemplate(() => new StackPanel())); + + public static readonly StyledProperty ItemsSourceProperty = + AvaloniaProperty.Register(nameof(ItemsSource)); + + public static readonly StyledProperty ItemTemplateProperty = + AvaloniaProperty.Register(nameof(ItemTemplate)); + + public static readonly StyledProperty SelectedItemProperty = + AvaloniaProperty.Register(nameof(SelectedItem)); + + private static readonly StyledProperty TemplateSettingsProperty = + AvaloniaProperty.Register(nameof(TemplateSettings)); + + private readonly ObservableCollection primaryCollection = []; + + private readonly ObservableCollection secondaryCollection = []; + + private OverflowList? primaryListBox; + + private OverflowList? secondaryListBox; + + public Overflow() + { + SetValue(TemplateSettingsProperty, new OverflowTemplateSettings()); + + TemplateSettings.GetPropertyChangedObservable(OverflowTemplateSettings.PrimarySelectionProperty) + .AddClassHandler(OnPrimarySelectionPropertyChanged); + + TemplateSettings.GetPropertyChangedObservable(OverflowTemplateSettings.SecondarySelectionProperty) + .AddClassHandler(OnSecondarySelectionPropertyChanged); + } + + public IItemContainerTemplateSelector? ItemContainerTemplateSelector + { + get => GetValue(ItemContainerTemplateSelectorProperty); + set => SetValue(ItemContainerTemplateSelectorProperty, value); + } + + public ITemplate ItemsPanel + { + get => GetValue(ItemsPanelProperty); + set => SetValue(ItemsPanelProperty, value); + } + + public IEnumerable? ItemsSource + { + get => GetValue(ItemsSourceProperty); + set => SetValue(ItemsSourceProperty, value); + } + + [InheritDataTypeFromItems(nameof(ItemsSource))] + public IDataTemplate? ItemTemplate + { + get => GetValue(ItemTemplateProperty); + set => SetValue(ItemTemplateProperty, value); + } + + public object? SelectedItem + { + get => GetValue(SelectedItemProperty); + set => SetValue(SelectedItemProperty, value); + } + + public OverflowTemplateSettings TemplateSettings + { + get => GetValue(TemplateSettingsProperty); + set => SetValue(TemplateSettingsProperty, value); + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs args) + { + base.OnApplyTemplate(args); + + primaryListBox = args.NameScope.Get("PrimaryListBox"); + primaryListBox?.SetValue(ItemsControl.ItemsSourceProperty, primaryCollection); + + secondaryListBox = args.NameScope.Get("SecondaryListBox"); + secondaryListBox?.SetValue(ItemsControl.ItemsSourceProperty, secondaryCollection); + + InitializeCollections(); + UpdateOverflow(); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs args) + { + base.OnPropertyChanged(args); + + if (args.Property == SelectedItemProperty) + { + UpdateSelectedItem(); + } + + if (args.Property == ItemsSourceProperty) + { + if (args.OldValue is IEnumerable oldCollection && oldCollection is INotifyCollectionChanged oldNotifyCollectionChanged) + { + oldNotifyCollectionChanged.CollectionChanged -= OnSourceCollectionChanged; + } + + if (args.NewValue is IEnumerable newCollection && newCollection is INotifyCollectionChanged notifyCollectionChanged) + { + notifyCollectionChanged.CollectionChanged += OnSourceCollectionChanged; + } + + InitializeCollections(); + UpdateOverflow(); + } + } + + private void InitializeCollections() + { + primaryCollection.Clear(); + secondaryCollection.Clear(); + + if (ItemsSource is not null) + { + foreach (object? item in ItemsSource) + { + primaryCollection.Add(item); + } + } + } + + private void OnPrimarySelectionPropertyChanged(OverflowTemplateSettings sender, + AvaloniaPropertyChangedEventArgs args) + { + object? selection = args.GetNewValue(); + SetValue(SelectedItemProperty, selection); + } + + private void OnSecondarySelectionPropertyChanged(OverflowTemplateSettings sender, + AvaloniaPropertyChangedEventArgs args) + { + object? selection = args.GetNewValue(); + SetValue(SelectedItemProperty, selection); + } + + private void OnSourceCollectionChanged(object? sender, + NotifyCollectionChangedEventArgs args) + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + if (args.NewItems is not null) + { + int insertIndex = args.NewStartingIndex > primaryCollection.Count ? primaryCollection.Count : args.NewStartingIndex; + foreach (object? newItem in args.NewItems) + { + primaryCollection.Insert(insertIndex++, newItem); + } + } + break; + + case NotifyCollectionChangedAction.Remove: + if (args.OldItems is not null) + { + foreach (object? oldItem in args.OldItems) + { + primaryCollection.Remove(oldItem); + secondaryCollection.Remove(oldItem); + } + } + break; + + case NotifyCollectionChangedAction.Replace: + if (args.OldItems is not null && args.NewItems is not null && args.OldItems.Count == args.NewItems.Count) + { + for (int i = 0; i < args.OldItems.Count; i++) + { + if (args.OldItems[i] is object oldItem && + args.NewItems[i] is object newItem) + { + int index = primaryCollection.IndexOf(oldItem); + if (index != -1) + { + primaryCollection[index] = newItem; + } + + index = secondaryCollection.IndexOf(oldItem); + if (index != -1) + { + secondaryCollection[index] = newItem; + } + } + } + } + break; + + case NotifyCollectionChangedAction.Move: + if (args.OldItems != null && args.NewItems != null && args.OldItems.Count == args.NewItems.Count) + { + for (int i = 0; i < args.OldItems.Count; i++) + { + if (args.OldItems[i] is object item) + { + int oldIndex = primaryCollection.IndexOf(item); + if (oldIndex != -1) + { + primaryCollection.RemoveAt(oldIndex); + + int newIndex = args.NewStartingIndex + i; + primaryCollection.Insert(newIndex, item); + } + + oldIndex = secondaryCollection.IndexOf(item); + if (oldIndex != -1) + { + secondaryCollection.RemoveAt(oldIndex); + + int newIndex = args.NewStartingIndex + i; + secondaryCollection.Insert(newIndex, item); + } + } + } + } + break; + + case NotifyCollectionChangedAction.Reset: + InitializeCollections(); + break; + } + + UpdateOverflow(); + } + + private void UpdateOverflow() + { + Dispatcher.UIThread.InvokeAsync(() => + { + if (ItemsSource is null) + { + return; + } + + double controlWidth = 240; + double accumulatedWidth = 0; + double itemSpacing = 6; + + List itemsToMoveToSecondary = []; + + for (int i = 0; i < primaryCollection.Count; i++) + { + object? item = primaryCollection[i]; + if (item is not null && primaryListBox?.ContainerFromItem(item) is ListBoxItem itemContainer) + { + itemContainer.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + double itemWidth = itemContainer.DesiredSize.Width; + + if (accumulatedWidth + itemWidth + (itemsToMoveToSecondary.Count * itemSpacing) > controlWidth) + { + itemsToMoveToSecondary.Add(item); + } + else + { + accumulatedWidth += itemWidth + itemSpacing; + } + } + } + + foreach (object item in itemsToMoveToSecondary) + { + primaryCollection.Remove(item); + + int insertIndexInSecondary = secondaryCollection.Count; + if (ItemsSource.Contains(item)) + { + int indexInItemsSource = ItemsSource.IndexOf(item); + insertIndexInSecondary = Math.Min(indexInItemsSource, secondaryCollection.Count); + } + + secondaryCollection.Insert(insertIndexInSecondary, item); + } + + PseudoClasses.Set(":overflow", secondaryCollection is { Count: > 0 }); + }); + } + + private void UpdateSelectedItem() + { + if (SelectedItem is not null) + { + if (primaryCollection.Contains(SelectedItem)) + { + TemplateSettings.SetValue(OverflowTemplateSettings.PrimarySelectionProperty, SelectedItem); + } + + if (secondaryCollection.Contains(SelectedItem)) + { + TemplateSettings.SetValue(OverflowTemplateSettings.SecondarySelectionProperty, SelectedItem); + } + } + else + { + TemplateSettings.SetValue(OverflowTemplateSettings.PrimarySelectionProperty, null); + TemplateSettings.SetValue(OverflowTemplateSettings.SecondarySelectionProperty, null); + } + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/Overflow/OverflowItem.cs b/Toolkit.UI.Controls.Avalonia/Overflow/OverflowItem.cs new file mode 100644 index 0000000..567d3de --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/Overflow/OverflowItem.cs @@ -0,0 +1,54 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; + +namespace Toolkit.UI.Controls.Avalonia; + +public class OverflowItem : + ListBoxItem +{ + public static readonly StyledProperty BadgeBrushProperty = + AvaloniaProperty.Register(nameof(BadgeBrush)); + + public static readonly StyledProperty BadgePathProperty = + AvaloniaProperty.Register(nameof(BadgePath)); + + public static readonly StyledProperty BadgePlacementProperty = + AvaloniaProperty.Register(nameof(BadgePlacement), ContentBadgePlacement.BottomRight); + + public static readonly StyledProperty BadgeSizeProperty = + AvaloniaProperty.Register(nameof(BadgeSize), 14); + + public static readonly StyledProperty IsBadgeVisibleProperty = + AvaloniaProperty.Register(nameof(IsBadgeVisible), true); + + public IBrush BadgeBrush + { + get => GetValue(BadgeBrushProperty); + set => SetValue(BadgeBrushProperty, value); + } + + public string BadgePath + { + get => GetValue(BadgePathProperty); + set => SetValue(BadgePathProperty, value); + } + + public ContentBadgePlacement BadgePlacement + { + get => GetValue(BadgePlacementProperty); + set => SetValue(BadgePlacementProperty, value); + } + + public double BadgeSize + { + get => GetValue(BadgeSizeProperty); + set => SetValue(BadgeSizeProperty, value); + } + + public bool IsBadgeVisible + { + get => GetValue(IsBadgeVisibleProperty); + set => SetValue(IsBadgeVisibleProperty, value); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/Overflow/OverflowList.cs b/Toolkit.UI.Controls.Avalonia/Overflow/OverflowList.cs new file mode 100644 index 0000000..11fd0e2 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/Overflow/OverflowList.cs @@ -0,0 +1,39 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Templates; + +namespace Toolkit.UI.Controls.Avalonia; + +public class OverflowList : + ListBox +{ + public static readonly StyledProperty ItemContainerTemplateSelectorProperty = + AvaloniaProperty.Register(nameof(ItemContainerTemplateSelector)); + + public IItemContainerTemplateSelector? ItemContainerTemplateSelector + { + get => GetValue(ItemContainerTemplateSelectorProperty); + set => SetValue(ItemContainerTemplateSelectorProperty, value); + } + + protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) + { + if (ItemContainerTemplateSelector?.SelectTemplate(item, this) is IDataTemplate itemContainerTemplate) + { + if (itemContainerTemplate.Build(item) is OverflowItem container) + { + return container; + } + } + + return new OverflowItem(); + } + + protected override bool NeedsContainerOverride(object? item, + int index, + out object? recycleKey) + { + recycleKey = null; + return item is not OverflowItem; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/Overflow/OverflowTemplateSettings.cs b/Toolkit.UI.Controls.Avalonia/Overflow/OverflowTemplateSettings.cs new file mode 100644 index 0000000..aade7bb --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/Overflow/OverflowTemplateSettings.cs @@ -0,0 +1,48 @@ +using Avalonia; + +namespace Toolkit.UI.Controls.Avalonia; + +public class OverflowTemplateSettings : + AvaloniaObject +{ + public static readonly StyledProperty PrimarySelectionProperty = + AvaloniaProperty.Register(nameof(PrimarySelection)); + + public static readonly StyledProperty SecondarySelectionProperty = + AvaloniaProperty.Register(nameof(SecondarySelection)); + + private bool isSelectionChanging; + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs args) + { + base.OnPropertyChanged(args); + + if (!isSelectionChanging) + { + isSelectionChanging = true; + + if (args.Property == PrimarySelectionProperty) + { + SecondarySelection = null; + } + else if (args.Property == SecondarySelectionProperty) + { + PrimarySelection = null; + } + + isSelectionChanging = false; + } + } + + public object? PrimarySelection + { + get => GetValue(PrimarySelectionProperty); + set => SetValue(PrimarySelectionProperty, value); + } + + public object? SecondarySelection + { + get => GetValue(SecondarySelectionProperty); + set => SetValue(SecondarySelectionProperty, value); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/PersonPicture/PersonPicture.axaml b/Toolkit.UI.Controls.Avalonia/PersonPicture/PersonPicture.axaml new file mode 100644 index 0000000..a529535 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/PersonPicture/PersonPicture.axaml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + 1 + 1 + 1 + 2 + 0,-4,-4,0 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Toolkit.UI.Controls.Avalonia/PersonPicture/PersonPicture.cs b/Toolkit.UI.Controls.Avalonia/PersonPicture/PersonPicture.cs new file mode 100644 index 0000000..795139a --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/PersonPicture/PersonPicture.cs @@ -0,0 +1,386 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.Media.Imaging; + +namespace Toolkit.UI.Controls.Avalonia; + +public class PersonPicture : TemplatedControl +{ + public static readonly StyledProperty BadgeGlyphProperty = + AvaloniaProperty.Register(nameof(BadgeGlyph)); + + public static readonly StyledProperty BadgeImageSourceProperty = + AvaloniaProperty.Register(nameof(BadgeImageSource)); + + public static readonly StyledProperty BadgeNumberProperty = + AvaloniaProperty.Register(nameof(BadgeNumber)); + + public static readonly StyledProperty BadgeTextProperty = + AvaloniaProperty.Register(nameof(BadgeText)); + + public static readonly StyledProperty DisplayNameProperty = + AvaloniaProperty.Register(nameof(DisplayName)); + + public static readonly StyledProperty InitialsProperty = + AvaloniaProperty.Register(nameof(Initials)); + + public static readonly StyledProperty IsGroupProperty = + AvaloniaProperty.Register(nameof(IsGroup)); + + public static readonly StyledProperty ProfilePictureProperty = + AvaloniaProperty.Register(nameof(ProfilePicture)); + + private static readonly StyledProperty TemplateSettingsProperty = + AvaloniaProperty.Register(nameof(TemplateSettings)); + + private readonly ImageBrush? badgeImageBrush; + private FontIcon? badgeGlyphIcon; + private TextBlock? badgeNumberTextBlock; + private Ellipse? badgingBackgroundEllipse; + private Ellipse? badgingEllipse; + private PersonPictureColourGenerator colourGenerator = new(); + private string? displayNameInitials; + private TextBlock? initialsTextBlock; + + public PersonPicture() + { + SetValue(TemplateSettingsProperty, new PersonPictureTemplateSettings()); + SizeChanged += OnSizeChanged; + } + + public string BadgeGlyph + { + get => GetValue(BadgeGlyphProperty); + set => SetValue(BadgeGlyphProperty, value); + } + + public IImage BadgeImageSource + { + get => GetValue(BadgeImageSourceProperty); + set => SetValue(BadgeImageSourceProperty, value); + } + + public int BadgeNumber + { + get => GetValue(BadgeNumberProperty); + set => SetValue(BadgeNumberProperty, value); + } + + public string BadgeText + { + get => GetValue(BadgeTextProperty); + set => SetValue(BadgeTextProperty, value); + } + + public string DisplayName + { + get => GetValue(DisplayNameProperty); + set => SetValue(DisplayNameProperty, value); + } + + public string Initials + { + get => GetValue(InitialsProperty); + set => SetValue(InitialsProperty, value); + } + + public bool IsGroup + { + get => GetValue(IsGroupProperty); + set => SetValue(IsGroupProperty, value); + } + + public IImage ProfilePicture + { + get => GetValue(ProfilePictureProperty); + set => SetValue(ProfilePictureProperty, value); + } + + public PersonPictureTemplateSettings TemplateSettings + { + get => GetValue(TemplateSettingsProperty); + set => SetValue(TemplateSettingsProperty, value); + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs args) + { + base.OnApplyTemplate(args); + + initialsTextBlock = args.NameScope.Get("InitialsTextBlock"); + + badgeNumberTextBlock = args.NameScope.Get("BadgeNumberTextBlock"); + badgeGlyphIcon = args.NameScope.Get("BadgeGlyphIcon"); + badgingEllipse = args.NameScope.Get("BadgingEllipse"); + badgingBackgroundEllipse = args.NameScope.Get("BadgingBackgroundEllipse"); + + UpdateBadge(); + UpdateIfReady(); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == BadgeGlyphProperty) + { + UpdateBadge(); + } + + if (change.Property == BadgeImageSourceProperty) + { + UpdateBadge(); + } + + if (change.Property == BadgeNumberProperty) + { + UpdateBadge(); + } + + if (change.Property == DisplayNameProperty) + { + UpdateDisplayName(); + } + + if (change.Property == InitialsProperty) + { + UpdateIfReady(); + } + + if (change.Property == IsGroupProperty) + { + UpdateIfReady(); + } + + if (change.Property == ProfilePictureProperty) + { + UpdateIfReady(); + } + } + + private IImage? GetImageSource() + { + if (ProfilePicture != null) + { + return ProfilePicture; + } + + return null; + } + + private string GetInitials() + { + if (!string.IsNullOrEmpty(Initials)) + { + return Initials; + } + else if (!string.IsNullOrEmpty(displayNameInitials)) + { + return displayNameInitials; + } + + return ""; + } + + private void OnSizeChanged(object? sender, SizeChangedEventArgs args) + { + { + bool widthChanged = args.NewSize.Width != args.PreviousSize.Width; + bool heightChanged = args.NewSize.Height != args.PreviousSize.Height; + double newSize; + + if (widthChanged && heightChanged) + { + newSize = args.NewSize.Width < args.NewSize.Height ? args.NewSize.Width : args.NewSize.Height; + } + else if (widthChanged) + { + newSize = args.NewSize.Width; + } + else if (heightChanged) + { + newSize = args.NewSize.Height; + } + else + { + return; + } + + Height = newSize; + Width = newSize; + } + + double fontSize = Math.Max(1.0, Width * .42); + + if (initialsTextBlock is not null) + { + initialsTextBlock.FontSize = fontSize; + } + + if (badgingEllipse is not null && badgingBackgroundEllipse is not null && badgeNumberTextBlock is not null && badgeGlyphIcon is not null) + { + double newSize = args.NewSize.Width < args.NewSize.Height ? args.NewSize.Width : args.NewSize.Height; + badgingEllipse.Height = newSize * 0.5; + badgingEllipse.Width = newSize * 0.5; + badgingBackgroundEllipse.Height = newSize * 0.5; + badgingBackgroundEllipse.Width = newSize * 0.5; + badgeNumberTextBlock.FontSize = Math.Max(1.0, badgingEllipse.Height * 0.6); + badgeGlyphIcon.FontSize = Math.Max(1.0, badgingEllipse.Height * 0.6); + } + } + + private void UpdateBadge() + { + if (BadgeImageSource != null) + { + UpdateBadgeImageSource(); + } + else if (BadgeNumber != 0) + { + UpdateBadgeNumber(); + } + else if (!string.IsNullOrEmpty(BadgeGlyph)) + { + UpdateBadgeGlyph(); + } + else + { + PseudoClasses.Set(":NoBadge", true); + if (badgeNumberTextBlock != null) + { + badgeNumberTextBlock.Text = ""; + } + + if (badgeGlyphIcon != null) + { + badgeGlyphIcon.Glyph = ""; + } + } + } + + private void UpdateBadgeGlyph() + { + if (badgingEllipse == null || badgeGlyphIcon == null) + { + return; + } + + if (string.IsNullOrEmpty(BadgeGlyph)) + { + PseudoClasses.Set(":NoBadge", true); + badgeGlyphIcon.Glyph = ""; + return; + } + + PseudoClasses.Set(":BadgeWithoutImageSource", true); + badgeGlyphIcon.Glyph = BadgeGlyph; + } + + private void UpdateBadgeImageSource() + { + if (badgingEllipse == null || badgeImageBrush == null) + { + return; + } + + badgeImageBrush.Source = (Bitmap?)BadgeImageSource; + + if (BadgeImageSource != null) + { + PseudoClasses.Set(":BadgeWithImageSource", true); + } + else + { + PseudoClasses.Set(":NoBadge", true); + } + } + + private void UpdateBadgeNumber() + { + if (badgingEllipse == null || badgeNumberTextBlock == null) + { + return; + } + + if (BadgeNumber <= 0) + { + PseudoClasses.Set(":NoBadge", true); + badgeNumberTextBlock.Text = ""; + + return; + } + + PseudoClasses.Set(":BadgeWithoutImageSource", true); + if (BadgeNumber <= 99) + { + badgeNumberTextBlock.Text = BadgeNumber.ToString(); + } + else + { + badgeNumberTextBlock.Text = "99+"; + } + } + + private void UpdateDisplayName() + { + displayNameInitials = PersonPictureInitialsGenerator.InitialsFromDisplayName(DisplayName); + UpdateIfReady(); + } + + private void UpdateIfReady() + { + string initials = GetInitials(); + IImage? imageSource = GetImageSource(); + + PersonPictureTemplateSettings templateSettings = TemplateSettings; + templateSettings.ActualInitials = initials; + + if (DisplayName is { Length: > 0 }) + { + Color rgb = colourGenerator.GenerateColour(DisplayName); + SetValue(BackgroundProperty, new SolidColorBrush(Color.FromArgb(rgb.A, rgb.R, rgb.G, rgb.B))); + } + + if (imageSource is not null) + { + ImageBrush? imageBrush = templateSettings.ActualImageBrush; + if (imageBrush == null) + { + imageBrush = new ImageBrush + { + Stretch = Stretch.UniformToFill + }; + + templateSettings.ActualImageBrush = imageBrush; + } + + imageBrush.Source = (Bitmap?)imageSource; + } + else + { + templateSettings.ActualImageBrush = null; + } + + if (IsGroup) + { + PseudoClasses.Set(":Group", true); + } + else + { + if (imageSource != null) + { + PseudoClasses.Set(":Photo", true); + } + else if (!string.IsNullOrEmpty(initials)) + { + PseudoClasses.Set(":Initials", true); + } + else + { + PseudoClasses.Set(":NoPhotoOrInitials", true); + } + } + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/PersonPicture/PersonPictureCharacterType.cs b/Toolkit.UI.Controls.Avalonia/PersonPicture/PersonPictureCharacterType.cs new file mode 100644 index 0000000..dca7c0f --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/PersonPicture/PersonPictureCharacterType.cs @@ -0,0 +1,9 @@ +namespace Toolkit.UI.Controls.Avalonia; + +internal enum PersonPictureCharacterType +{ + Other = 0, + Standard = 1, + Symbolic = 2, + Glyph = 3 +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/PersonPicture/PersonPictureColourGenerator.cs b/Toolkit.UI.Controls.Avalonia/PersonPicture/PersonPictureColourGenerator.cs new file mode 100644 index 0000000..84edfb6 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/PersonPicture/PersonPictureColourGenerator.cs @@ -0,0 +1,86 @@ +using Avalonia.Media; +using System.Security.Cryptography; +using System.Text; + +namespace Toolkit.UI.Controls.Avalonia; + +public class PersonPictureColourGenerator +{ + private readonly string[] colours = + [ + "#FFB900", + "#FF8C00", + "#F7630C", + "#CA5010", + "#DA3B01", + "#EF6950", + "#D13438", + "#FF4343", + "#E74856", + "#E81123", + "#EA005E", + "#C30052", + "#E3008C", + "#BF0077", + "#C239B3", + "#9A0089", + "#0078D7", + "#0063B1", + "#8E8CD8", + "#6B69D6", + "#8764B8", + "#744DA9", + "#B146C2", + "#881798", + "#0099BC", + "#2D7D9A", + "#00B7C3", + "#038387", + "#00B294", + "#018574", + "#00CC6A", + "#10893E", + "#7A7574", + "#5D5A58", + "#68768A", + "#515C6B", + "#567C73", + "#486860", + "#498205", + "#107C10", + "#767676", + "#4C4A48", + "#69797E", + "#4A5459", + "#647C64", + "#525E54", + "#847545", + "#7E735F" + ]; + + public Color GenerateColour(string input) + { + byte[] hashBytes = GetHash(input); + int colourIndex = BitConverter.ToInt32(hashBytes, 0) % colours.Length; + colourIndex = Math.Abs(colourIndex); + + return HexToColour(colours[colourIndex]); + } + + private byte[] GetHash(string input) + { + return SHA256.HashData(Encoding.UTF8.GetBytes(input)); + } + + private Color HexToColour(string hex) + { + hex = hex.Replace("#", string.Empty); + + byte a = 255; + byte r = byte.Parse(hex.Substring(0, 2), System.Globalization.NumberStyles.HexNumber); + byte g = byte.Parse(hex.Substring(2, 2), System.Globalization.NumberStyles.HexNumber); + byte b = byte.Parse(hex.Substring(4, 2), System.Globalization.NumberStyles.HexNumber); + + return Color.FromArgb(a, r, g, b); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/PersonPicture/PersonPictureInitialsGenerator.cs b/Toolkit.UI.Controls.Avalonia/PersonPicture/PersonPictureInitialsGenerator.cs new file mode 100644 index 0000000..af9f850 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/PersonPicture/PersonPictureInitialsGenerator.cs @@ -0,0 +1,419 @@ +namespace Toolkit.UI.Controls.Avalonia; + +internal class PersonPictureInitialsGenerator +{ + public static PersonPictureCharacterType GetCharacterType(string content) + { + PersonPictureCharacterType result = PersonPictureCharacterType.Other; + if (content is { Length: > 0 }) + { + for (int i = 0; i < 3; i++) + { + if ((i >= content.Length) || (content[i] == '\0') || (content[i] == 0xFEFF)) + { + break; + } + + 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; + } + } + } + return result; + } + + public static PersonPictureCharacterType GetCharacterType(char character) + { + // IPA Extensions + if ((character >= 0x0250) && (character <= 0x02AF)) + { + return PersonPictureCharacterType.Glyph; + } + // Arabic + if ((character >= 0x0600) && (character <= 0x06FF)) + { + return PersonPictureCharacterType.Glyph; + } + // Arabic Supplement + if ((character >= 0x0750) && (character <= 0x077F)) + { + return PersonPictureCharacterType.Glyph; + } + // Arabic Extended-A + if ((character >= 0x08A0) && (character <= 0x08FF)) + { + return PersonPictureCharacterType.Glyph; + } + // Arabic Presentation Forms-A + if ((character >= 0xFB50) && (character <= 0xFDFF)) + { + return PersonPictureCharacterType.Glyph; + } + // Arabic Presentation Forms-B + if ((character >= 0xFE70) && (character <= 0xFEFF)) + { + return PersonPictureCharacterType.Glyph; + } + // Devanagari + if ((character >= 0x0900) && (character <= 0x097F)) + { + return PersonPictureCharacterType.Glyph; + } + // Devanagari Extended + if ((character >= 0xA8E0) && (character <= 0xA8FF)) + { + return PersonPictureCharacterType.Glyph; + } + // Bengali + if ((character >= 0x0980) && (character <= 0x09FF)) + { + return PersonPictureCharacterType.Glyph; + } + // Gurmukhi + if ((character >= 0x0A00) && (character <= 0x0A7F)) + { + return PersonPictureCharacterType.Glyph; + } + // Gujarati + if ((character >= 0x0A80) && (character <= 0x0AFF)) + { + return PersonPictureCharacterType.Glyph; + } + // Oriya + if ((character >= 0x0B00) && (character <= 0x0B7F)) + { + return PersonPictureCharacterType.Glyph; + } + // Tamil + if ((character >= 0x0B80) && (character <= 0x0BFF)) + { + return PersonPictureCharacterType.Glyph; + } + // Telugu + if ((character >= 0x0C00) && (character <= 0x0C7F)) + { + return PersonPictureCharacterType.Glyph; + } + // Kannada + if ((character >= 0x0C80) && (character <= 0x0CFF)) + { + return PersonPictureCharacterType.Glyph; + } + // Malayalam + if ((character >= 0x0D00) && (character <= 0x0D7F)) + { + return PersonPictureCharacterType.Glyph; + } + // Sinhala + if ((character >= 0x0D80) && (character <= 0x0DFF)) + { + return PersonPictureCharacterType.Glyph; + } + // Thai + if ((character >= 0x0E00) && (character <= 0x0E7F)) + { + return PersonPictureCharacterType.Glyph; + } + // Lao + if ((character >= 0x0E80) && (character <= 0x0EFF)) + { + return PersonPictureCharacterType.Glyph; + } + // SYMBOLIC + // + // CJK Unified Ideographs + if ((character >= 0x4E00) && (character <= 0x9FFF)) + { + return PersonPictureCharacterType.Symbolic; + } + // CJK Unified Ideographs Extension + if ((character >= 0x3400) && (character <= 0x4DBF)) + { + return PersonPictureCharacterType.Symbolic; + } + // CJK Unified Ideographs Extension B + if ((character >= 0x20000) && (character <= 0x2A6DF)) + { + return PersonPictureCharacterType.Symbolic; + } + // CJK Unified Ideographs Extension C + if ((character >= 0x2A700) && (character <= 0x2B73F)) + { + return PersonPictureCharacterType.Symbolic; + } + // CJK Unified Ideographs Extension D + if ((character >= 0x2B740) && (character <= 0x2B81F)) + { + return PersonPictureCharacterType.Symbolic; + } + // CJK Radicals Supplement + if ((character >= 0x2E80) && (character <= 0x2EFF)) + { + return PersonPictureCharacterType.Symbolic; + } + // CJK Symbols and Punctuation + if ((character >= 0x3000) && (character <= 0x303F)) + { + return PersonPictureCharacterType.Symbolic; + } + // CJK Strokes + if ((character >= 0x31C0) && (character <= 0x31EF)) + { + return PersonPictureCharacterType.Symbolic; + } + // Enclosed CJK Letters and Months + if ((character >= 0x3200) && (character <= 0x32FF)) + { + return PersonPictureCharacterType.Symbolic; + } + // CJK Compatibility + if ((character >= 0x3300) && (character <= 0x33FF)) + { + return PersonPictureCharacterType.Symbolic; + } + // CJK Compatibility Ideographs + if ((character >= 0xF900) && (character <= 0xFAFF)) + { + return PersonPictureCharacterType.Symbolic; + } + // CJK Compatibility Forms + if ((character >= 0xFE30) && (character <= 0xFE4F)) + { + return PersonPictureCharacterType.Symbolic; + } + // CJK Compatibility Ideographs Supplement + if ((character >= 0x2F800) && (character <= 0x2FA1F)) + { + return PersonPictureCharacterType.Symbolic; + } + // Greek and Coptic + if ((character >= 0x0370) && (character <= 0x03FF)) + { + return PersonPictureCharacterType.Symbolic; + } + // Hebrew + if ((character >= 0x0590) && (character <= 0x05FF)) + { + return PersonPictureCharacterType.Symbolic; + } + // Armenian + if ((character >= 0x0530) && (character <= 0x058F)) + { + return PersonPictureCharacterType.Symbolic; + } + // LATIN + // + // Basic Latin + if ((character > 0x0000) && (character <= 0x007F)) + { + return PersonPictureCharacterType.Standard; + } + // Latin-1 Supplement + if ((character >= 0x0080) && (character <= 0x00FF)) + { + return PersonPictureCharacterType.Standard; + } + // Latin Extended-A + if ((character >= 0x0100) && (character <= 0x017F)) + { + return PersonPictureCharacterType.Standard; + } + // Latin Extended-B + if ((character >= 0x0180) && (character <= 0x024F)) + { + return PersonPictureCharacterType.Standard; + } + // Latin Extended-C + if ((character >= 0x2C60) && (character <= 0x2C7F)) + { + return PersonPictureCharacterType.Standard; + } + // Latin Extended-D + if ((character >= 0xA720) && (character <= 0xA7FF)) + { + return PersonPictureCharacterType.Standard; + } + // Latin Extended-E + if ((character >= 0xAB30) && (character <= 0xAB6F)) + { + return PersonPictureCharacterType.Standard; + } + // Latin Extended Additional + if ((character >= 0x1E00) && (character <= 0x1EFF)) + { + return PersonPictureCharacterType.Standard; + } + // Cyrillic + if ((character >= 0x0400) && (character <= 0x04FF)) + { + return PersonPictureCharacterType.Standard; + } + // Cyrillic Supplement + if ((character >= 0x0500) && (character <= 0x052F)) + { + return PersonPictureCharacterType.Standard; + } + // Combining Diacritical Marks + if ((character >= 0x0300) && (character <= 0x036F)) + { + return PersonPictureCharacterType.Standard; + } + return PersonPictureCharacterType.Other; + } + + public static string InitialsFromDisplayName(string contactDisplayName) + { + PersonPictureCharacterType type = GetCharacterType(contactDisplayName); + if (type == PersonPictureCharacterType.Standard) + { + string displayName = contactDisplayName; + StripTrailingBrackets(ref displayName); + string[] words = Split(displayName, ' '); + + if (words.Length == 1) + { + string firstWord = words.First(); + string result = GetFirstFullCharacter(firstWord); + + return result.ToUpper(); + } + else if (words.Length > 1) + { + string firstWord = words.First(); + string lastWord = words.Last(); + string result = GetFirstFullCharacter(firstWord); + result += GetFirstFullCharacter(lastWord); + + return result.ToUpper(); + } + else + { + return string.Empty; + } + } + else + { + return string.Empty; + } + } + + private static string GetFirstFullCharacter(string str) + { + int start = 0; + while (start < str.Length) + { + char character = str[start]; + // Omit ! " # $ % & ' ( ) * + , - . / + if ((character >= 0x0021) && (character <= 0x002F)) + { + start++; + continue; + } + // Omit : ; < = > ? @ + if ((character >= 0x003A) && (character <= 0x0040)) + { + start++; + continue; + } + // Omit { | } ~ + if ((character >= 0x007B) && (character <= 0x007E)) + { + start++; + continue; + } + break; + } + + if (start >= str.Length) + { + start = 0; + } + + int index = start + 1; + while (index < str.Length) + { + char character = str[index]; + + if ((character < 0x0300) || (character > 0x036F)) + { + break; + } + + index++; + } + + int strLength = index - start; + return SafeSubstring(str, start, strLength); + } + + private static string SafeSubstring(string value, int startIndex, int length) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (startIndex > value.Length) + { + return string.Empty; + } + + if (length > value.Length - startIndex) + { + length = value.Length - startIndex; + } + + return value.Substring(startIndex, length); + } + + private static string[] Split(string source, char delim, int maxIterations = 25) + { + return source.Split(new[] { delim }, maxIterations); + } + + private static void StripTrailingBrackets(ref string source) + { + string[] delimiters = { "{}", "()", "[]" }; + if (source.Length == 0) + { + return; + } + foreach (var delimiter in delimiters) + { + if (source[source.Length - 1] != delimiter[1]) + { + continue; + } + var start = source.LastIndexOf(delimiter[0]); + if (start == -1) + { + continue; + } + source = source.Remove(start); + return; + } + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/PersonPicture/PersonPictureTemplateSettings.cs b/Toolkit.UI.Controls.Avalonia/PersonPicture/PersonPictureTemplateSettings.cs new file mode 100644 index 0000000..62c5ee0 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/PersonPicture/PersonPictureTemplateSettings.cs @@ -0,0 +1,25 @@ +using Avalonia; +using Avalonia.Media; + +namespace Toolkit.UI.Controls.Avalonia; + +public class PersonPictureTemplateSettings : AvaloniaObject +{ + private static readonly StyledProperty ActualImageBrushProperty = + AvaloniaProperty.Register(nameof(ActualImageBrush)); + + private static readonly StyledProperty ActualInitialsProperty = + AvaloniaProperty.Register(nameof(ActualInitials)); + + public ImageBrush? ActualImageBrush + { + get => GetValue(ActualImageBrushProperty); + set => SetValue(ActualImageBrushProperty, value); + } + + public string ActualInitials + { + get => GetValue(ActualInitialsProperty); + set => SetValue(ActualInitialsProperty, value); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/Primitives/IItemContainerTemplateSelector.cs b/Toolkit.UI.Controls.Avalonia/Primitives/IItemContainerTemplateSelector.cs new file mode 100644 index 0000000..f8a0c89 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/Primitives/IItemContainerTemplateSelector.cs @@ -0,0 +1,9 @@ +using Avalonia.Controls; +using Avalonia.Controls.Templates; + +namespace Toolkit.UI.Controls.Avalonia; + +public interface IItemContainerTemplateSelector +{ + IDataTemplate? SelectTemplate(object? item, ItemsControl itemsControl); +} diff --git a/Toolkit.UI.Controls.Avalonia/ProgressRing/ProgressRing.cs b/Toolkit.UI.Controls.Avalonia/ProgressRing/ProgressRing.cs new file mode 100644 index 0000000..015425f --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/ProgressRing/ProgressRing.cs @@ -0,0 +1,8 @@ +namespace Toolkit.UI.Controls.Avalonia; + +public class ProgressRing : + FluentAvalonia.UI.Controls.ProgressRing +{ + protected override Type StyleKeyOverride => + typeof(FluentAvalonia.UI.Controls.ProgressRing); +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/BitList.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/BitList.cs index ec8cd02..d725ab4 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/BitList.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/BitList.cs @@ -1,127 +1,124 @@ -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; namespace Gma.QrCodeNet.Encoding; internal sealed class BitList : IEnumerable { - internal BitList() - { - Count = 0; - List = new List(32); - } + internal BitList() + { + Count = 0; + List = new List(32); + } - internal BitList(IEnumerable byteArray) - { - Count = byteArray.Count(); - List = byteArray.ToList(); - } + internal BitList(IEnumerable byteArray) + { + Count = byteArray.Count(); + List = byteArray.ToList(); + } - internal List List { get; } + internal List List { get; } - internal int Count { get; private set; } + internal int Count { get; private set; } - internal bool this[int index] - { - get - { - if (index < 0 || index >= Count) - { - throw new ArgumentOutOfRangeException(nameof(index), "Index out of range"); - } + internal bool this[int index] + { + get + { + if (index < 0 || index >= Count) + { + throw new ArgumentOutOfRangeException(nameof(index), "Index out of range"); + } - int value_Renamed = List[index >> 3] & 0xff; - return ((value_Renamed >> (7 - (index & 0x7))) & 1) == 1; - } - } + int value_Renamed = List[index >> 3] & 0xff; + return ((value_Renamed >> (7 - (index & 0x7))) & 1) == 1; + } + } - public IEnumerator GetEnumerator() - { - int numBytes = Count >> 3; - int remainder = Count & 0x7; - byte value; - for (int index = 0; index < numBytes; index++) - { - value = List[index]; - for (int shiftNum = 7; shiftNum >= 0; shiftNum--) - { - yield return ((value >> shiftNum) & 1) == 1; - } - } - if (remainder > 0) - { - value = List[numBytes]; - for (int index = 0; index < remainder; index++) - { - yield return ((value >> (7 - index)) & 1) == 1; - } - } - } + public IEnumerator GetEnumerator() + { + int numBytes = Count >> 3; + int remainder = Count & 0x7; + byte value; + for (int index = 0; index < numBytes; index++) + { + value = List[index]; + for (int shiftNum = 7; shiftNum >= 0; shiftNum--) + { + yield return ((value >> shiftNum) & 1) == 1; + } + } + if (remainder > 0) + { + value = List[numBytes]; + for (int index = 0; index < remainder; index++) + { + yield return ((value >> (7 - index)) & 1) == 1; + } + } + } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } - private int ToBit(bool item) - { - return item ? 1 : 0; - } + private int ToBit(bool item) + { + return item ? 1 : 0; + } - internal void Add(bool item) - { - int numBitsinLastByte = Count & 0x7; + internal void Add(bool item) + { + int numBitsinLastByte = Count & 0x7; - // Add one more byte to List when we have no bits in the last byte. - if (numBitsinLastByte == 0) - { - List.Add(0); - } + // Add one more byte to List when we have no bits in the last byte. + if (numBitsinLastByte == 0) + { + List.Add(0); + } - List[Count >> 3] |= (byte)(ToBit(item) << (7 - numBitsinLastByte)); - Count++; - } + List[Count >> 3] |= (byte)(ToBit(item) << (7 - numBitsinLastByte)); + Count++; + } - internal void Add(IEnumerable items) - { - foreach (bool item in items) - { - Add(item); - } - } + internal void Add(IEnumerable items) + { + foreach (bool item in items) + { + Add(item); + } + } - internal void Add(int value, int bitCount) - { - if (bitCount is < 0 or > 32) - { - throw new ArgumentOutOfRangeException(nameof(bitCount), $"{nameof(bitCount)} must be greater than or equal to 0"); - } + internal void Add(int value, int bitCount) + { + if (bitCount is < 0 or > 32) + { + throw new ArgumentOutOfRangeException(nameof(bitCount), $"{nameof(bitCount)} must be greater than or equal to 0"); + } - int numBitsLeft = bitCount; + int numBitsLeft = bitCount; - while (numBitsLeft > 0) - { - if ((Count & 0x7) == 0 && numBitsLeft >= 8) - { - // Add one more byte to List. - byte newByte = (byte)((value >> (numBitsLeft - 8)) & 0xFF); - AppendByte(newByte); - numBitsLeft -= 8; - } - else - { - bool bit = ((value >> (numBitsLeft - 1)) & 1) == 1; - Add(bit); - numBitsLeft--; - } - } - } + while (numBitsLeft > 0) + { + if ((Count & 0x7) == 0 && numBitsLeft >= 8) + { + // Add one more byte to List. + byte newByte = (byte)((value >> (numBitsLeft - 8)) & 0xFF); + AppendByte(newByte); + numBitsLeft -= 8; + } + else + { + bool bit = ((value >> (numBitsLeft - 1)) & 1) == 1; + Add(bit); + numBitsLeft--; + } + } + } - private void AppendByte(byte item) - { - List.Add(item); - Count += 8; - } -} + private void AppendByte(byte item) + { + List.Add(item); + Count += 8; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/BitMatrix.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/BitMatrix.cs index ae0fb52..ed44f68 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/BitMatrix.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/BitMatrix.cs @@ -2,23 +2,23 @@ namespace Gma.QrCodeNet.Encoding; public abstract class BitMatrix { - public abstract int Width { get; } - public abstract int Height { get; } - public abstract bool[,] InternalArray { get; } + public abstract int Width { get; } + public abstract int Height { get; } + public abstract bool[,] InternalArray { get; } - public abstract bool this[int i, int j] { get; set; } + public abstract bool this[int i, int j] { get; set; } - internal void CopyTo(TriStateMatrix target, MatrixRectangle sourceArea, MatrixPoint targetPoint, MatrixStatus mstatus) - { - for (int j = 0; j < sourceArea.Size.Height; j++) - { - for (int i = 0; i < sourceArea.Size.Width; i++) - { - bool value = this[sourceArea.Location.X + i, sourceArea.Location.Y + j]; - target[targetPoint.X + i, targetPoint.Y + j, mstatus] = value; - } - } - } + internal void CopyTo(TriStateMatrix target, MatrixRectangle sourceArea, MatrixPoint targetPoint, MatrixStatus mstatus) + { + for (int j = 0; j < sourceArea.Size.Height; j++) + { + for (int i = 0; i < sourceArea.Size.Width; i++) + { + bool value = this[sourceArea.Location.X + i, sourceArea.Location.Y + j]; + target[targetPoint.X + i, targetPoint.Y + j, mstatus] = value; + } + } + } - internal void CopyTo(TriStateMatrix target, MatrixPoint targetPoint, MatrixStatus mstatus) => CopyTo(target, new MatrixRectangle(new MatrixPoint(0, 0), new MatrixSize(Width, Height)), targetPoint, mstatus); -} + internal void CopyTo(TriStateMatrix target, MatrixPoint targetPoint, MatrixStatus mstatus) => CopyTo(target, new MatrixRectangle(new MatrixPoint(0, 0), new MatrixSize(Width, Height)), targetPoint, mstatus); +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/BitMatrixBase.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/BitMatrixBase.cs index aaa4ba5..6a577e2 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/BitMatrixBase.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/BitMatrixBase.cs @@ -2,30 +2,30 @@ namespace Gma.QrCodeNet.Encoding; public abstract class BitMatrixBase : BitMatrix { - protected BitMatrixBase(int width, bool[,] internalArray) - { - Width = width; - InternalArray = internalArray; - } + protected BitMatrixBase(int width, bool[,] internalArray) + { + Width = width; + InternalArray = internalArray; + } - protected BitMatrixBase(bool[,] internalArray) - { - InternalArray = internalArray; - int width = internalArray.GetLength(0); - Width = width; - } + protected BitMatrixBase(bool[,] internalArray) + { + InternalArray = internalArray; + int width = internalArray.GetLength(0); + Width = width; + } - public override bool[,] InternalArray { get; } + public override bool[,] InternalArray { get; } - public override int Width { get; } + public override int Width { get; } - public static bool CanCreate(bool[,] internalArray) - { - if (internalArray is null) - { - return false; - } + public static bool CanCreate(bool[,] internalArray) + { + if (internalArray is null) + { + return false; + } - return internalArray.GetLength(0) == internalArray.GetLength(1); - } -} + return internalArray.GetLength(0) == internalArray.GetLength(1); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/CharCountIndicatorTable.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/CharCountIndicatorTable.cs index 9a6c752..c66d6b2 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/CharCountIndicatorTable.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/CharCountIndicatorTable.cs @@ -1,48 +1,46 @@ -using System; - namespace Gma.QrCodeNet.Encoding.DataEncodation; public static class CharCountIndicatorTable { - /// ISO/IEC 18004:2000 Table 3 Page 18 - public static int[] GetCharCountIndicatorSet() - { - return new int[] { 8, 16, 16 }; - } + /// ISO/IEC 18004:2000 Table 3 Page 18 + public static int[] GetCharCountIndicatorSet() + { + return new int[] { 8, 16, 16 }; + } - public static int GetBitCountInCharCountIndicator(int version) - { - int[] charCountIndicatorSet = GetCharCountIndicatorSet(); - int versionGroup = GetVersionGroup(version); + public static int GetBitCountInCharCountIndicator(int version) + { + int[] charCountIndicatorSet = GetCharCountIndicatorSet(); + int versionGroup = GetVersionGroup(version); - return charCountIndicatorSet[versionGroup]; - } + return charCountIndicatorSet[versionGroup]; + } - /// - /// Used to define length of the Character Count Indicator - /// - /// Returns the 0 based index of the row from Chapter 8.4 Data encodation, Table 3 — Number of bits in Character Count Indicator. - private static int GetVersionGroup(int version) - { - if (version > 40) - { - throw new InvalidOperationException($"Unexpected version: {version}."); - } - else if (version >= 27) - { - return 2; - } - else if (version >= 10) - { - return 1; - } - else if (version > 0) - { - return 0; - } - else - { - throw new InvalidOperationException($"Unexpected version: {version}."); - } - } -} + /// + /// Used to define length of the Character Count Indicator + /// + /// Returns the 0 based index of the row from Chapter 8.4 Data encodation, Table 3 — Number of bits in Character Count Indicator. + private static int GetVersionGroup(int version) + { + if (version > 40) + { + throw new InvalidOperationException($"Unexpected version: {version}."); + } + else if (version >= 27) + { + return 2; + } + else if (version >= 10) + { + return 1; + } + else if (version > 0) + { + return 0; + } + else + { + throw new InvalidOperationException($"Unexpected version: {version}."); + } + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/DataEncode.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/DataEncode.cs index 2a3c222..f36ec90 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/DataEncode.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/DataEncode.cs @@ -1,4 +1,3 @@ -using System; using Gma.QrCodeNet.Encoding.DataEncodation.InputRecognition; using Gma.QrCodeNet.Encoding.Terminate; using Gma.QrCodeNet.Encoding.Versions; @@ -10,53 +9,53 @@ namespace Gma.QrCodeNet.Encoding.DataEncodation; /// Which uses sub functions under several different namespaces internal static class DataEncode { - internal static EncodationStruct Encode(string content, ErrorCorrectionLevel ecLevel) - { - RecognitionStruct recognitionResult = InputRecognise.Recognise(content); - EncoderBase encoderBase = CreateEncoder(recognitionResult.EncodingName); + internal static EncodationStruct Encode(string content, ErrorCorrectionLevel ecLevel) + { + RecognitionStruct recognitionResult = InputRecognise.Recognise(content); + EncoderBase encoderBase = CreateEncoder(recognitionResult.EncodingName); - BitList encodeContent = encoderBase.GetDataBits(content); + BitList encodeContent = encoderBase.GetDataBits(content); - int encodeContentLength = encodeContent.Count; + int encodeContentLength = encodeContent.Count; - VersionControlStruct vcStruct = - VersionControl.InitialSetup(encodeContentLength, ecLevel, recognitionResult.EncodingName); + VersionControlStruct vcStruct = + VersionControl.InitialSetup(encodeContentLength, ecLevel, recognitionResult.EncodingName); - BitList dataCodewords = new(); + BitList dataCodewords = new(); - // Eci header - if (vcStruct.IsContainECI && vcStruct.ECIHeader is { }) - { - dataCodewords.Add(vcStruct.ECIHeader); - } + // Eci header + if (vcStruct.IsContainECI && vcStruct.ECIHeader is { }) + { + dataCodewords.Add(vcStruct.ECIHeader); + } - // Header - dataCodewords.Add(encoderBase.GetModeIndicator()); - int numLetter = encodeContentLength >> 3; - dataCodewords.Add(encoderBase.GetCharCountIndicator(numLetter, vcStruct.VersionDetail.Version)); + // Header + dataCodewords.Add(encoderBase.GetModeIndicator()); + int numLetter = encodeContentLength >> 3; + dataCodewords.Add(encoderBase.GetCharCountIndicator(numLetter, vcStruct.VersionDetail.Version)); - // Data - dataCodewords.Add(encodeContent); + // Data + dataCodewords.Add(encodeContent); - // Terminator Padding - dataCodewords.TerminateBites(dataCodewords.Count, vcStruct.VersionDetail.NumDataBytes); + // Terminator Padding + dataCodewords.TerminateBites(dataCodewords.Count, vcStruct.VersionDetail.NumDataBytes); - int dataCodewordsCount = dataCodewords.Count; - if ((dataCodewordsCount & 0x7) != 0) - { - throw new ArgumentException($"{nameof(dataCodewords)} is not byte sized."); - } - else if (dataCodewordsCount >> 3 != vcStruct.VersionDetail.NumDataBytes) - { - throw new ArgumentException($"{nameof(dataCodewords)} num of bytes not equal to {nameof(vcStruct.VersionDetail.NumDataBytes)} for current version"); - } + int dataCodewordsCount = dataCodewords.Count; + if ((dataCodewordsCount & 0x7) != 0) + { + throw new ArgumentException($"{nameof(dataCodewords)} is not byte sized."); + } + else if (dataCodewordsCount >> 3 != vcStruct.VersionDetail.NumDataBytes) + { + throw new ArgumentException($"{nameof(dataCodewords)} num of bytes not equal to {nameof(vcStruct.VersionDetail.NumDataBytes)} for current version"); + } - var encStruct = new EncodationStruct(vcStruct, dataCodewords); - return encStruct; - } + var encStruct = new EncodationStruct(vcStruct, dataCodewords); + return encStruct; + } - private static EncoderBase CreateEncoder(string encodingName) - { - return new EightBitByteEncoder(encodingName); - } -} + private static EncoderBase CreateEncoder(string encodingName) + { + return new EightBitByteEncoder(encodingName); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/ECISet.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/ECISet.cs index 9afdc5d..1709b4c 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/ECISet.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/ECISet.cs @@ -1,255 +1,252 @@ -using System; -using System.Collections.Generic; - namespace Gma.QrCodeNet.Encoding.DataEncodation; public sealed class ECISet { - /// - /// ISO/IEC 18004:2006 Chapter 6.4.2 Mode indicator = 0111 Page 23 - /// - private const int ECIMode = 7; + /// + /// ISO/IEC 18004:2006 Chapter 6.4.2 Mode indicator = 0111 Page 23 + /// + private const int ECIMode = 7; - private const int ECIIndicatorNumBits = 4; + private const int ECIIndicatorNumBits = 4; - private Dictionary? _nameToValue; - private Dictionary? _valueToName; + private Dictionary? _nameToValue; + private Dictionary? _valueToName; - /// - /// Initialize ECI Set. - /// - /// AppendOption is enum under ECISet - /// Use NameToValue during Encode. ValueToName during Decode - internal ECISet(AppendOption option) - { - Initialize(option); - } + /// + /// Initialize ECI Set. + /// + /// AppendOption is enum under ECISet + /// Use NameToValue during Encode. ValueToName during Decode + internal ECISet(AppendOption option) + { + Initialize(option); + } - public enum AppendOption - { - NameToValue, - ValueToName, - Both - } + public enum AppendOption + { + NameToValue, + ValueToName, + Both + } - /// - /// Length indicator for number of ECI codewords - /// - /// ISO/IEC 18004:2006 Chapter 6.4.2 Page 24. - /// 1 codeword length = 0. Any additional codeword add 1 to front. Eg: 3 = 110 - /// Bits required for each one is: - /// one = 1, two = 2, three = 3 - private enum ECICodewordsLength - { - One = 0, - Two = 2, - Three = 6 - } + /// + /// Length indicator for number of ECI codewords + /// + /// ISO/IEC 18004:2006 Chapter 6.4.2 Page 24. + /// 1 codeword length = 0. Any additional codeword add 1 to front. Eg: 3 = 110 + /// Bits required for each one is: + /// one = 1, two = 2, three = 3 + private enum ECICodewordsLength + { + One = 0, + Two = 2, + Three = 6 + } - /// ISO/IEC 18004:2006E ECI Designator Page 24 - /// Range: 0 ~ 999999 - /// Number of Codewords(Byte) for ECI Assignment Value - private static int NumOfCodewords(int eCIValue) - { - if (eCIValue is >= 0 and <= 127) - { - return 1; - } - else if (eCIValue is > 127 and <= 16383) - { - return 2; - } - else if (eCIValue is > 16383 and <= 999999) - { - return 3; - } - else - { - throw new ArgumentOutOfRangeException($"{nameof(eCIValue)} should be in range: 0 to 999999."); - } - } + /// ISO/IEC 18004:2006E ECI Designator Page 24 + /// Range: 0 ~ 999999 + /// Number of Codewords(Byte) for ECI Assignment Value + private static int NumOfCodewords(int eCIValue) + { + if (eCIValue is >= 0 and <= 127) + { + return 1; + } + else if (eCIValue is > 127 and <= 16383) + { + return 2; + } + else if (eCIValue is > 16383 and <= 999999) + { + return 3; + } + else + { + throw new ArgumentOutOfRangeException($"{nameof(eCIValue)} should be in range: 0 to 999999."); + } + } - /// ISO/IEC 18004:2006E ECI Designator Page 24 - /// Range: 0 ~ 999999 - /// Number of bits for ECI Assignment Value - private static int NumOfAssignmentBits(int eCIValue) => NumOfCodewords(eCIValue) * 8; + /// ISO/IEC 18004:2006E ECI Designator Page 24 + /// Range: 0 ~ 999999 + /// Number of bits for ECI Assignment Value + private static int NumOfAssignmentBits(int eCIValue) => NumOfCodewords(eCIValue) * 8; - private void AppendECI(string name, int value, AppendOption option) - { - switch (option) - { - case AppendOption.NameToValue: - _nameToValue?.Add(name, value); - break; + private void AppendECI(string name, int value, AppendOption option) + { + switch (option) + { + case AppendOption.NameToValue: + _nameToValue?.Add(name, value); + break; - case AppendOption.ValueToName: - _valueToName?.Add(value, name); - break; + case AppendOption.ValueToName: + _valueToName?.Add(value, name); + break; - case AppendOption.Both: - _nameToValue?.Add(name, value); - _valueToName?.Add(value, name); - break; + case AppendOption.Both: + _nameToValue?.Add(name, value); + _valueToName?.Add(value, name); + break; - default: - throw new InvalidOperationException($"There is no such {nameof(AppendOption)}."); - } - } + default: + throw new InvalidOperationException($"There is no such {nameof(AppendOption)}."); + } + } - private void Initialize(AppendOption option) - { - switch (option) - { - case AppendOption.NameToValue: - _nameToValue = new Dictionary(); - break; + private void Initialize(AppendOption option) + { + switch (option) + { + case AppendOption.NameToValue: + _nameToValue = new Dictionary(); + break; - case AppendOption.ValueToName: - _valueToName = new Dictionary(); - break; + case AppendOption.ValueToName: + _valueToName = new Dictionary(); + break; - case AppendOption.Both: - _nameToValue = new Dictionary(); - _valueToName = new Dictionary(); - break; + case AppendOption.Both: + _nameToValue = new Dictionary(); + _valueToName = new Dictionary(); + break; - default: - throw new InvalidOperationException($"There is no such {nameof(AppendOption)}."); - } + default: + throw new InvalidOperationException($"There is no such {nameof(AppendOption)}."); + } - // ECI table. Source 01 URL: http://strokescribe.com/en/ECI.html - // ECI table. Source 02 URL: http://lab.must.or.kr/Extended-Channel-Interpretations-ECI-Encoding.ashx - // ToDo. Fill up remaining missing table. - AppendECI("iso-8859-1", 1, option); - AppendECI("IBM437", 2, option); + // ECI table. Source 01 URL: http://strokescribe.com/en/ECI.html + // ECI table. Source 02 URL: http://lab.must.or.kr/Extended-Channel-Interpretations-ECI-Encoding.ashx + // ToDo. Fill up remaining missing table. + AppendECI("iso-8859-1", 1, option); + AppendECI("IBM437", 2, option); - // AppendECI("iso-8859-1", 3, option); //ECI value 1 is default encoding. - AppendECI("iso-8859-2", 4, option); - AppendECI("iso-8859-3", 5, option); - AppendECI("iso-8859-4", 6, option); - AppendECI("iso-8859-5", 7, option); - AppendECI("iso-8859-6", 8, option); - AppendECI("iso-8859-7", 9, option); - AppendECI("iso-8859-8", 10, option); - AppendECI("iso-8859-9", 11, option); - AppendECI("windows-874", 13, option); - AppendECI("iso-8859-13", 15, option); - AppendECI("iso-8859-15", 17, option); - AppendECI("shift_jis", 20, option); - AppendECI("utf-8", 26, option); - } + // AppendECI("iso-8859-1", 3, option); //ECI value 1 is default encoding. + AppendECI("iso-8859-2", 4, option); + AppendECI("iso-8859-3", 5, option); + AppendECI("iso-8859-4", 6, option); + AppendECI("iso-8859-5", 7, option); + AppendECI("iso-8859-6", 8, option); + AppendECI("iso-8859-7", 9, option); + AppendECI("iso-8859-8", 10, option); + AppendECI("iso-8859-9", 11, option); + AppendECI("windows-874", 13, option); + AppendECI("iso-8859-13", 15, option); + AppendECI("iso-8859-15", 17, option); + AppendECI("shift_jis", 20, option); + AppendECI("utf-8", 26, option); + } - /// ISO/IEC 18004:2006E ECI Designator Page 24 - /// Range: 0 ~ 999999 - /// Number of bits for ECI Header - internal static int NumOfECIHeaderBits(int eCIValue) => NumOfAssignmentBits(eCIValue) + 4; + /// ISO/IEC 18004:2006E ECI Designator Page 24 + /// Range: 0 ~ 999999 + /// Number of bits for ECI Header + internal static int NumOfECIHeaderBits(int eCIValue) => NumOfAssignmentBits(eCIValue) + 4; - internal int GetECIValueByName(string encodingName) - { - if (_nameToValue is null) - { - Initialize(AppendOption.NameToValue); - } + internal int GetECIValueByName(string encodingName) + { + if (_nameToValue is null) + { + Initialize(AppendOption.NameToValue); + } - if (_nameToValue!.TryGetValue(encodingName, out int eCIValue)) - { - return eCIValue; - } - else - { - throw new ArgumentOutOfRangeException($"ECI does not contain encoding: {encodingName}."); - } - } + if (_nameToValue!.TryGetValue(encodingName, out int eCIValue)) + { + return eCIValue; + } + else + { + throw new ArgumentOutOfRangeException($"ECI does not contain encoding: {encodingName}."); + } + } - internal string GetECINameByValue(int eCIValue) - { - if (_valueToName is null) - { - Initialize(AppendOption.ValueToName); - } + internal string GetECINameByValue(int eCIValue) + { + if (_valueToName is null) + { + Initialize(AppendOption.ValueToName); + } - if (_valueToName!.TryGetValue(eCIValue, out var eCIName)) - { - return eCIName; - } - else - { - throw new ArgumentOutOfRangeException($"ECI does not contain value: {eCIValue}."); - } - } + if (_valueToName!.TryGetValue(eCIValue, out var eCIName)) + { + return eCIName; + } + else + { + throw new ArgumentOutOfRangeException($"ECI does not contain value: {eCIValue}."); + } + } - /// ECI table in Dictionary collection - public Dictionary? GetECITable() - { - if (_nameToValue is null) - { - Initialize(AppendOption.NameToValue); - } + /// ECI table in Dictionary collection + public Dictionary? GetECITable() + { + if (_nameToValue is null) + { + Initialize(AppendOption.NameToValue); + } - return _nameToValue; - } + return _nameToValue; + } - public bool ContainsECIName(string encodingName) - { - if (_nameToValue is null) - { - Initialize(AppendOption.NameToValue); - } + public bool ContainsECIName(string encodingName) + { + if (_nameToValue is null) + { + Initialize(AppendOption.NameToValue); + } - return _nameToValue!.ContainsKey(encodingName); - } + return _nameToValue!.ContainsKey(encodingName); + } - public bool ContainsECIValue(int eciValue) - { - if (_valueToName is null) - { - Initialize(AppendOption.ValueToName); - } + public bool ContainsECIValue(int eciValue) + { + if (_valueToName is null) + { + Initialize(AppendOption.ValueToName); + } - return _valueToName!.ContainsKey(eciValue); - } + return _valueToName!.ContainsKey(eciValue); + } - /// ISO/IEC 18004:2006 Chapter 6.4.2 Page 24. - internal BitList GetECIHeader(string encodingName) - { - int eciValue = GetECIValueByName(encodingName); + /// ISO/IEC 18004:2006 Chapter 6.4.2 Page 24. + internal BitList GetECIHeader(string encodingName) + { + int eciValue = GetECIValueByName(encodingName); - BitList dataBits = new() - { - { ECIMode, ECIIndicatorNumBits } - }; + BitList dataBits = new() + { + { ECIMode, ECIIndicatorNumBits } + }; - int eciAssignmentByte = NumOfCodewords(eciValue); + int eciAssignmentByte = NumOfCodewords(eciValue); - // Number of bits = Num codewords indicator + codeword value = Number of codewords * 8 - // Chapter 6.4.2.1 ECI Designator ISOIEC 18004:2006 Page 24 - int eciAssignmentBits; - switch (eciAssignmentByte) - { - case 1: - // Indicator = 0. Page 24. Chapter 6.4.2.1 - dataBits.Add((int)ECICodewordsLength.One, 1); - eciAssignmentBits = (eciAssignmentByte * 8) - 1; - break; + // Number of bits = Num codewords indicator + codeword value = Number of codewords * 8 + // Chapter 6.4.2.1 ECI Designator ISOIEC 18004:2006 Page 24 + int eciAssignmentBits; + switch (eciAssignmentByte) + { + case 1: + // Indicator = 0. Page 24. Chapter 6.4.2.1 + dataBits.Add((int)ECICodewordsLength.One, 1); + eciAssignmentBits = (eciAssignmentByte * 8) - 1; + break; - case 2: - // Indicator = 10. Page 24. Chapter 6.4.2.1 - dataBits.Add((int)ECICodewordsLength.Two, 2); - eciAssignmentBits = (eciAssignmentByte * 8) - 2; - break; + case 2: + // Indicator = 10. Page 24. Chapter 6.4.2.1 + dataBits.Add((int)ECICodewordsLength.Two, 2); + eciAssignmentBits = (eciAssignmentByte * 8) - 2; + break; - case 3: - // Indicator = 110. Page 24. Chapter 6.4.2.1 - dataBits.Add((int)ECICodewordsLength.Three, 3); - eciAssignmentBits = (eciAssignmentByte * 8) - 3; - break; + case 3: + // Indicator = 110. Page 24. Chapter 6.4.2.1 + dataBits.Add((int)ECICodewordsLength.Three, 3); + eciAssignmentBits = (eciAssignmentByte * 8) - 3; + break; - default: - throw new InvalidOperationException("Assignment Codewords should be either 1, 2 or 3."); - } + default: + throw new InvalidOperationException("Assignment Codewords should be either 1, 2 or 3."); + } - dataBits.Add(eciValue, eciAssignmentBits); + dataBits.Add(eciValue, eciAssignmentBits); - return dataBits; - } -} + return dataBits; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/EightBitByteEncoder.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/EightBitByteEncoder.cs index e8ffe7b..866f94b 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/EightBitByteEncoder.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/EightBitByteEncoder.cs @@ -1,5 +1,3 @@ -using System; - namespace Gma.QrCodeNet.Encoding.DataEncodation; /// @@ -14,67 +12,67 @@ namespace Gma.QrCodeNet.Encoding.DataEncodation; /// ISO/IEC 18004:2000 Chapter 8.4.4 Page 22 internal class EightBitByteEncoder : EncoderBase { - private const string DefaultEncoding = QRCodeConstantVariable.DefaultEncoding; + private const string DefaultEncoding = QRCodeConstantVariable.DefaultEncoding; - /// - /// Bitcount, Chapter 8.4.4, P.24 - /// - private const int EightBitByteBitcount = 8; + /// + /// Bitcount, Chapter 8.4.4, P.24 + /// + private const int EightBitByteBitcount = 8; - /// - /// EightBitByte encoder's encoding will change according to different region - /// - /// Default encoding is "iso-8859-1" - internal EightBitByteEncoder(string encoding) : base() - { - Encoding = encoding ?? DefaultEncoding; - } + /// + /// EightBitByte encoder's encoding will change according to different region + /// + /// Default encoding is "iso-8859-1" + internal EightBitByteEncoder(string encoding) : base() + { + Encoding = encoding ?? DefaultEncoding; + } - internal EightBitByteEncoder() : base() - { - Encoding = DefaultEncoding; - } + internal EightBitByteEncoder() : base() + { + Encoding = DefaultEncoding; + } - internal string Encoding { get; private set; } + internal string Encoding { get; private set; } - protected byte[] EncodeContent(string content, string encoding) => System.Text.Encoding.GetEncoding(encoding).GetBytes(content); + protected byte[] EncodeContent(string content, string encoding) => System.Text.Encoding.GetEncoding(encoding).GetBytes(content); - internal override BitList GetDataBits(string content) - { - var eciSet = new ECISet(ECISet.AppendOption.NameToValue); - if (!eciSet.ContainsECIName(Encoding)) - { - throw new ArgumentOutOfRangeException( - nameof(Encoding), - $"Current ECI table does not support this encoding. Please check {nameof(ECISet)} class for more info."); - } + internal override BitList GetDataBits(string content) + { + var eciSet = new ECISet(ECISet.AppendOption.NameToValue); + if (!eciSet.ContainsECIName(Encoding)) + { + throw new ArgumentOutOfRangeException( + nameof(Encoding), + $"Current ECI table does not support this encoding. Please check {nameof(ECISet)} class for more info."); + } - byte[] contentBytes = EncodeContent(content, Encoding); + byte[] contentBytes = EncodeContent(content, Encoding); - return GetDataBitsByByteArray(contentBytes, Encoding); - } + return GetDataBitsByByteArray(contentBytes, Encoding); + } - internal BitList GetDataBitsByByteArray(byte[] encodeContent, string encodingName) - { - var dataBits = new BitList(); + internal BitList GetDataBitsByByteArray(byte[] encodeContent, string encodingName) + { + var dataBits = new BitList(); - // Current plan for UTF8 support is put Byte order Mark in front of content byte. - // Also include ECI header before encoding header. Which will be add with encoding header. - if (encodingName == "utf-8") - { - byte[] utf8BOM = QRCodeConstantVariable.UTF8ByteOrderMark; - for (int index = 0; index < utf8BOM.Length; index++) - { - dataBits.Add(utf8BOM[index], EightBitByteBitcount); - } - } + // Current plan for UTF8 support is put Byte order Mark in front of content byte. + // Also include ECI header before encoding header. Which will be add with encoding header. + if (encodingName == "utf-8") + { + byte[] utf8BOM = QRCodeConstantVariable.UTF8ByteOrderMark; + for (int index = 0; index < utf8BOM.Length; index++) + { + dataBits.Add(utf8BOM[index], EightBitByteBitcount); + } + } - for (int index = 0; index < encodeContent.Length; index++) - { - dataBits.Add(encodeContent[index], EightBitByteBitcount); - } - return dataBits; - } + for (int index = 0; index < encodeContent.Length; index++) + { + dataBits.Add(encodeContent[index], EightBitByteBitcount); + } + return dataBits; + } - protected override int GetBitCountInCharCountIndicator(int version) => CharCountIndicatorTable.GetBitCountInCharCountIndicator(version); -} + protected override int GetBitCountInCharCountIndicator(int version) => CharCountIndicatorTable.GetBitCountInCharCountIndicator(version); +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/EncodationStruct.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/EncodationStruct.cs index f9a854f..ec31c7a 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/EncodationStruct.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/EncodationStruct.cs @@ -4,12 +4,12 @@ namespace Gma.QrCodeNet.Encoding.DataEncodation; internal struct EncodationStruct { - internal EncodationStruct(VersionControlStruct vcStruct, BitList dataCodewords) - { - VersionDetail = vcStruct.VersionDetail; - DataCodewords = dataCodewords; - } + internal EncodationStruct(VersionControlStruct vcStruct, BitList dataCodewords) + { + VersionDetail = vcStruct.VersionDetail; + DataCodewords = dataCodewords; + } - internal VersionDetail VersionDetail { get; set; } - internal BitList DataCodewords { get; set; } -} + internal VersionDetail VersionDetail { get; set; } + internal BitList DataCodewords { get; set; } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/EncoderBase.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/EncoderBase.cs index b04dc73..454fab6 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/EncoderBase.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/EncoderBase.cs @@ -2,45 +2,45 @@ namespace Gma.QrCodeNet.Encoding.DataEncodation; public abstract class EncoderBase { - internal EncoderBase() - { - } + internal EncoderBase() + { + } - protected virtual int GetDataLength(string content) => content.Length; + protected virtual int GetDataLength(string content) => content.Length; - /// - /// Returns the bit representation of input data. - /// - internal abstract BitList GetDataBits(string content); + /// + /// Returns the bit representation of input data. + /// + internal abstract BitList GetDataBits(string content); - /// - /// Returns bit representation of Modevalue. - /// - /// See Chapter 8.4 Data encodation, Table 2 — Mode indicators - internal BitList GetModeIndicator() - { - BitList modeIndicatorBits = new() - { - { 0001 << 2, 4 } - }; - return modeIndicatorBits; - } + /// + /// Returns bit representation of Modevalue. + /// + /// See Chapter 8.4 Data encodation, Table 2 — Mode indicators + internal BitList GetModeIndicator() + { + BitList modeIndicatorBits = new() + { + { 0001 << 2, 4 } + }; + return modeIndicatorBits; + } - internal BitList GetCharCountIndicator(int characterCount, int version) - { - BitList characterCountBits = new(); - int bitCount = GetBitCountInCharCountIndicator(version); - characterCountBits.Add(characterCount, bitCount); - return characterCountBits; - } + internal BitList GetCharCountIndicator(int characterCount, int version) + { + BitList characterCountBits = new(); + int bitCount = GetBitCountInCharCountIndicator(version); + characterCountBits.Add(characterCount, bitCount); + return characterCountBits; + } - /// - /// Defines the length of the Character Count Indicator, - /// which varies according to the mode and the symbol version in use - /// - /// Number of bits in Character Count Indicator. - /// - /// See Chapter 8.4 Data encodation, Table 3 — Number of bits in Character Count Indicator. - /// - protected abstract int GetBitCountInCharCountIndicator(int version); -} + /// + /// Defines the length of the Character Count Indicator, + /// which varies according to the mode and the symbol version in use + /// + /// Number of bits in Character Count Indicator. + /// + /// See Chapter 8.4 Data encodation, Table 3 — Number of bits in Character Count Indicator. + /// + protected abstract int GetBitCountInCharCountIndicator(int version); +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/InputRecognition/InputRecognise.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/InputRecognition/InputRecognise.cs index b08a97e..b751a85 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/InputRecognition/InputRecognise.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/InputRecognition/InputRecognise.cs @@ -1,57 +1,54 @@ -using System; -using System.Collections.Generic; - namespace Gma.QrCodeNet.Encoding.DataEncodation.InputRecognition; public static class InputRecognise { - public static RecognitionStruct Recognise(string content) - { - string encodingName = EightBitByteRecognision(content, 0, content.Length); - return new RecognitionStruct(encodingName); - } + public static RecognitionStruct Recognise(string content) + { + string encodingName = EightBitByteRecognision(content, 0, content.Length); + return new RecognitionStruct(encodingName); + } - private static string EightBitByteRecognision(string content, int startPos, int contentLength) - { - if(string.IsNullOrEmpty(content)) + private static string EightBitByteRecognision(string content, int startPos, int contentLength) + { + if (string.IsNullOrEmpty(content)) throw new ArgumentNullException(nameof(content)); - var eciSets = new ECISet(ECISet.AppendOption.NameToValue); + var eciSets = new ECISet(ECISet.AppendOption.NameToValue); - Dictionary? eciSet = eciSets.GetECITable(); + Dictionary? eciSet = eciSets.GetECITable(); - if(eciSet == null) + if (eciSet == null) return string.Empty; - // we will not check for utf8 encoding. - eciSet.Remove(QRCodeConstantVariable.UTF8Encoding); - eciSet.Remove(QRCodeConstantVariable.DefaultEncoding); + // we will not check for utf8 encoding. + eciSet.Remove(QRCodeConstantVariable.UTF8Encoding); + eciSet.Remove(QRCodeConstantVariable.DefaultEncoding); - int scanPos = startPos; + int scanPos = startPos; - // default encoding as priority - scanPos = ModeEncodeCheck.TryEncodeEightBitByte(content, QRCodeConstantVariable.DefaultEncoding, scanPos, contentLength); - if (scanPos == -1) - { - return QRCodeConstantVariable.DefaultEncoding; - } + // default encoding as priority + scanPos = ModeEncodeCheck.TryEncodeEightBitByte(content, QRCodeConstantVariable.DefaultEncoding, scanPos, contentLength); + if (scanPos == -1) + { + return QRCodeConstantVariable.DefaultEncoding; + } - foreach (KeyValuePair kvp in eciSet) - { - scanPos = ModeEncodeCheck.TryEncodeEightBitByte(content, kvp.Key, scanPos, contentLength); - if (scanPos == -1) - { - return kvp.Key; - } - } + foreach (KeyValuePair kvp in eciSet) + { + scanPos = ModeEncodeCheck.TryEncodeEightBitByte(content, kvp.Key, scanPos, contentLength); + if (scanPos == -1) + { + return kvp.Key; + } + } - if (scanPos == -1) - { - throw new ArgumentException("foreach Loop check give wrong result."); - } - else - { - return QRCodeConstantVariable.UTF8Encoding; - } - } -} + if (scanPos == -1) + { + throw new ArgumentException("foreach Loop check give wrong result."); + } + else + { + return QRCodeConstantVariable.UTF8Encoding; + } + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/InputRecognition/ModeEncodeCheck.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/InputRecognition/ModeEncodeCheck.cs index ac9d215..f480a49 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/InputRecognition/ModeEncodeCheck.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/InputRecognition/ModeEncodeCheck.cs @@ -1,72 +1,70 @@ -using System; - namespace Gma.QrCodeNet.Encoding.DataEncodation.InputRecognition; public static class ModeEncodeCheck { - /// - /// Encoding.GetEncoding.GetBytes will transform char to 0x3F if that char not belong to current encoding table. - /// 0x3F is '?' - /// - private const int QuestionMarkChar = 0x3F; + /// + /// Encoding.GetEncoding.GetBytes will transform char to 0x3F if that char not belong to current encoding table. + /// 0x3F is '?' + /// + private const int QuestionMarkChar = 0x3F; - /// - /// Use given encoding to check input string from starting position. If encoding table is suitable solution. - /// it will return -1. Else it will return failed encoding position. - /// - /// Input string - /// Encoding name. Check ECI table - /// Returns -1 if from starting position to end encoding success. Else returns fail position - internal static int TryEncodeEightBitByte(string content, string encodingName, int startingPosition, int contentLength) - { - if (string.IsNullOrEmpty(content)) - { - throw new IndexOutOfRangeException("Input cannot be null or empty."); - } + /// + /// Use given encoding to check input string from starting position. If encoding table is suitable solution. + /// it will return -1. Else it will return failed encoding position. + /// + /// Input string + /// Encoding name. Check ECI table + /// Returns -1 if from starting position to end encoding success. Else returns fail position + internal static int TryEncodeEightBitByte(string content, string encodingName, int startingPosition, int contentLength) + { + if (string.IsNullOrEmpty(content)) + { + throw new IndexOutOfRangeException("Input cannot be null or empty."); + } - System.Text.Encoding encoding; - try - { - encoding = System.Text.Encoding.GetEncoding(encodingName); - } - catch (ArgumentException) - { - return startingPosition; - } + System.Text.Encoding encoding; + try + { + encoding = System.Text.Encoding.GetEncoding(encodingName); + } + catch (ArgumentException) + { + return startingPosition; + } - char[] currentChar = new char[1]; - byte[] bytes; + char[] currentChar = new char[1]; + byte[] bytes; - for (int index = startingPosition; index < contentLength; index++) - { - currentChar[0] = content[index]; - bytes = encoding.GetBytes(currentChar); - int length = bytes.Length; - if (currentChar[0] != '?' && length == 1 && bytes[0] == QuestionMarkChar) - { - return index; - } - else if (length > 1) - { - return index; - } - } + for (int index = startingPosition; index < contentLength; index++) + { + currentChar[0] = content[index]; + bytes = encoding.GetBytes(currentChar); + int length = bytes.Length; + if (currentChar[0] != '?' && length == 1 && bytes[0] == QuestionMarkChar) + { + return index; + } + else if (length > 1) + { + return index; + } + } - for (int index = 0; index < startingPosition; index++) - { - currentChar[0] = content[index]; - bytes = encoding.GetBytes(currentChar); - int length = bytes.Length; - if (currentChar[0] != '?' && length == 1 && bytes[0] == QuestionMarkChar) - { - return index; - } - else if (length > 1) - { - return index; - } - } + for (int index = 0; index < startingPosition; index++) + { + currentChar[0] = content[index]; + bytes = encoding.GetBytes(currentChar); + int length = bytes.Length; + if (currentChar[0] != '?' && length == 1 && bytes[0] == QuestionMarkChar) + { + return index; + } + else if (length > 1) + { + return index; + } + } - return -1; - } -} + return -1; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/InputRecognition/RecognitionStruct.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/InputRecognition/RecognitionStruct.cs index 256b3cf..986c923 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/InputRecognition/RecognitionStruct.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/InputRecognition/RecognitionStruct.cs @@ -2,11 +2,11 @@ namespace Gma.QrCodeNet.Encoding.DataEncodation.InputRecognition; public struct RecognitionStruct { - public RecognitionStruct(string encodingName) - : this() - { - EncodingName = encodingName; - } + public RecognitionStruct(string encodingName) + : this() + { + EncodingName = encodingName; + } - public string EncodingName { get; private set; } -} + public string EncodingName { get; private set; } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/BCHCalculator.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/BCHCalculator.cs index a9fec96..a4434f7 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/BCHCalculator.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/BCHCalculator.cs @@ -2,59 +2,59 @@ namespace Gma.QrCodeNet.Encoding.EncodingRegion; internal static class BCHCalculator { - /// - /// Calculate int length by search for Most significant bit - /// - /// Input Number - /// Most significant bit - internal static int PosMSB(int num) => num == 0 ? 0 : BinarySearchPos(num, 0, 32) + 1; + /// + /// Calculate int length by search for Most significant bit + /// + /// Input Number + /// Most significant bit + internal static int PosMSB(int num) => num == 0 ? 0 : BinarySearchPos(num, 0, 32) + 1; - /// - /// Search for right side bit of Most significant bit - /// - /// Input number - /// Lower boundary. At start should be 0 - /// Higher boundary. At start should be 32 - /// Most significant bit - 1 - private static int BinarySearchPos(int num, int lowBoundary, int highBoundary) - { - int mid = (lowBoundary + highBoundary) / 2; - int shiftResult = num >> mid; - if (shiftResult == 1) - { - return mid; - } - else if (shiftResult < 1) - { - return BinarySearchPos(num, lowBoundary, mid); - } - else - { - return BinarySearchPos(num, mid, highBoundary); - } - } + /// + /// Search for right side bit of Most significant bit + /// + /// Input number + /// Lower boundary. At start should be 0 + /// Higher boundary. At start should be 32 + /// Most significant bit - 1 + private static int BinarySearchPos(int num, int lowBoundary, int highBoundary) + { + int mid = (lowBoundary + highBoundary) / 2; + int shiftResult = num >> mid; + if (shiftResult == 1) + { + return mid; + } + else if (shiftResult < 1) + { + return BinarySearchPos(num, lowBoundary, mid); + } + else + { + return BinarySearchPos(num, mid, highBoundary); + } + } - /// - /// With input number and polynomial number. Method will calculate BCH value and return - /// - /// Input number - /// Polynomial number - /// BCH value - internal static int CalculateBCH(int num, int poly) - { - int polyMSB = PosMSB(poly); + /// + /// With input number and polynomial number. Method will calculate BCH value and return + /// + /// Input number + /// Polynomial number + /// BCH value + internal static int CalculateBCH(int num, int poly) + { + int polyMSB = PosMSB(poly); - // num's length will be old length + new length - 1. - // Once divide poly number. BCH number will be one length short than Poly number's length. - num <<= (polyMSB - 1); - int numMSB = PosMSB(num); - while (PosMSB(num) >= polyMSB) - { - // left shift Poly number to same level as num. Then xor. - // Remove most significant bits of num. - num ^= poly << (numMSB - polyMSB); - numMSB = PosMSB(num); - } - return num; - } -} + // num's length will be old length + new length - 1. + // Once divide poly number. BCH number will be one length short than Poly number's length. + num <<= (polyMSB - 1); + int numMSB = PosMSB(num); + while (PosMSB(num) >= polyMSB) + { + // left shift Poly number to same level as num. Then xor. + // Remove most significant bits of num. + num ^= poly << (numMSB - polyMSB); + numMSB = PosMSB(num); + } + return num; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/Codeword.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/Codeword.cs index d6b1c0a..5afeb9e 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/Codeword.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/Codeword.cs @@ -1,72 +1,70 @@ -using System; - namespace Gma.QrCodeNet.Encoding.EncodingRegion; /// ISO/IEC 18004:2000 Chapter 8.7.3 Page 46 internal static class Codeword { - internal static void TryEmbedCodewords(this TriStateMatrix tsMatrix, BitList codewords) - { - int sWidth = tsMatrix.Width; - int codewordsSize = codewords.Count; + internal static void TryEmbedCodewords(this TriStateMatrix tsMatrix, BitList codewords) + { + int sWidth = tsMatrix.Width; + int codewordsSize = codewords.Count; - int bitIndex = 0; - int directionUp = -1; + int bitIndex = 0; + int directionUp = -1; - int x = sWidth - 1; - int y = sWidth - 1; + int x = sWidth - 1; + int y = sWidth - 1; - while (x > 0) - { - // Skip vertical timing pattern - if (x == 6) - { - x -= 1; - } + while (x > 0) + { + // Skip vertical timing pattern + if (x == 6) + { + x -= 1; + } - while (y >= 0 && y < sWidth) - { - for (int xOffset = 0; xOffset < 2; xOffset++) - { - int xPos = x - xOffset; - if (tsMatrix.MStatus(xPos, y) == MatrixStatus.None) - { - bool bit; - if (bitIndex < codewordsSize) - { - bit = codewords[bitIndex]; - bitIndex++; - } - else - { - bit = false; - } + while (y >= 0 && y < sWidth) + { + for (int xOffset = 0; xOffset < 2; xOffset++) + { + int xPos = x - xOffset; + if (tsMatrix.MStatus(xPos, y) == MatrixStatus.None) + { + bool bit; + if (bitIndex < codewordsSize) + { + bit = codewords[bitIndex]; + bitIndex++; + } + else + { + bit = false; + } - tsMatrix[xPos, y, MatrixStatus.Data] = bit; - } - } + tsMatrix[xPos, y, MatrixStatus.Data] = bit; + } + } - y = NextY(y, directionUp); - } + y = NextY(y, directionUp); + } - directionUp = ChangeDirection(directionUp); - y = NextY(y, directionUp); - x -= 2; - } + directionUp = ChangeDirection(directionUp); + y = NextY(y, directionUp); + x -= 2; + } - if (bitIndex != codewordsSize) - { - throw new Exception($"Not all bits from {nameof(codewords)} consumed by matrix: {bitIndex} / {codewordsSize}."); - } - } + if (bitIndex != codewordsSize) + { + throw new Exception($"Not all bits from {nameof(codewords)} consumed by matrix: {bitIndex} / {codewordsSize}."); + } + } - internal static int NextY(int y, int directionUp) - { - return y + directionUp; - } + internal static int NextY(int y, int directionUp) + { + return y + directionUp; + } - internal static int ChangeDirection(int directionUp) - { - return -directionUp; - } -} + internal static int ChangeDirection(int directionUp) + { + return -directionUp; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/FormatInformation.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/FormatInformation.cs index eaaf733..3edc1c5 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/FormatInformation.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/FormatInformation.cs @@ -1,4 +1,3 @@ -using System; using Gma.QrCodeNet.Encoding.Masking; namespace Gma.QrCodeNet.Encoding.EncodingRegion; @@ -10,105 +9,105 @@ namespace Gma.QrCodeNet.Encoding.EncodingRegion; /// ISO/IEC 18004:2000 Chapter 8.9 Page 53 internal static class FormatInformation { - /// - /// From Appendix C in JISX0510:2004 (p.65). - /// - private const int FormatInfoPoly = 0x537; + /// + /// From Appendix C in JISX0510:2004 (p.65). + /// + private const int FormatInfoPoly = 0x537; - /// - /// From Appendix C in JISX0510:2004 (p.65). - /// - private const int FormatInfoMaskPattern = 0x5412; + /// + /// From Appendix C in JISX0510:2004 (p.65). + /// + private const int FormatInfoMaskPattern = 0x5412; - /// - /// Embed format information to tristatematrix. - /// Process combination of create info bits, BCH error correction bits calculation, embed towards matrix. - /// - /// ISO/IEC 18004:2000 Chapter 8.9 Page 53 - internal static void EmbedFormatInformation(this TriStateMatrix triMatrix, ErrorCorrectionLevel errorLevel, Pattern pattern) - { - BitList formatInfo = GetFormatInfoBits(errorLevel, pattern); - int width = triMatrix.Width; - for (int index = 0; index < 15; index++) - { - MatrixPoint point = PointForInfo1(index); - bool bit = formatInfo[index]; - triMatrix[point.X, point.Y, MatrixStatus.NoMask] = bit; + /// + /// Embed format information to tristatematrix. + /// Process combination of create info bits, BCH error correction bits calculation, embed towards matrix. + /// + /// ISO/IEC 18004:2000 Chapter 8.9 Page 53 + internal static void EmbedFormatInformation(this TriStateMatrix triMatrix, ErrorCorrectionLevel errorLevel, Pattern pattern) + { + BitList formatInfo = GetFormatInfoBits(errorLevel, pattern); + int width = triMatrix.Width; + for (int index = 0; index < 15; index++) + { + MatrixPoint point = PointForInfo1(index); + bool bit = formatInfo[index]; + triMatrix[point.X, point.Y, MatrixStatus.NoMask] = bit; - if (index < 7) - { - triMatrix[8, width - 1 - index, MatrixStatus.NoMask] = bit; - } - else - { - triMatrix[width - 8 + (index - 7), 8, MatrixStatus.NoMask] = bit; - } - } - } + if (index < 7) + { + triMatrix[8, width - 1 - index, MatrixStatus.NoMask] = bit; + } + else + { + triMatrix[width - 8 + (index - 7), 8, MatrixStatus.NoMask] = bit; + } + } + } - private static MatrixPoint PointForInfo1(int bitsIndex) - { - if (bitsIndex <= 7) - { - return bitsIndex >= 6 - ? new MatrixPoint(bitsIndex + 1, 8) - : new MatrixPoint(bitsIndex, 8); - } - else - { - return bitsIndex == 8 - ? new MatrixPoint(8, 8 - (bitsIndex - 7)) - : new MatrixPoint(8, 8 - (bitsIndex - 7) - 1); - } - } + private static MatrixPoint PointForInfo1(int bitsIndex) + { + if (bitsIndex <= 7) + { + return bitsIndex >= 6 + ? new MatrixPoint(bitsIndex + 1, 8) + : new MatrixPoint(bitsIndex, 8); + } + else + { + return bitsIndex == 8 + ? new MatrixPoint(8, 8 - (bitsIndex - 7)) + : new MatrixPoint(8, 8 - (bitsIndex - 7) - 1); + } + } - private static BitList GetFormatInfoBits(ErrorCorrectionLevel errorLevel, Pattern pattern) - { - int formatInfo = (int)pattern.MaskPatternType; + private static BitList GetFormatInfoBits(ErrorCorrectionLevel errorLevel, Pattern pattern) + { + int formatInfo = (int)pattern.MaskPatternType; - // Pattern bits length = 3 - formatInfo |= GetErrorCorrectionIndicatorBits(errorLevel) << 3; + // Pattern bits length = 3 + formatInfo |= GetErrorCorrectionIndicatorBits(errorLevel) << 3; - int bchCode = BCHCalculator.CalculateBCH(formatInfo, FormatInfoPoly); + int bchCode = BCHCalculator.CalculateBCH(formatInfo, FormatInfoPoly); - // bchCode length = 10 - formatInfo = (formatInfo << 10) | bchCode; + // bchCode length = 10 + formatInfo = (formatInfo << 10) | bchCode; - // xor maskPattern - formatInfo ^= FormatInfoMaskPattern; + // xor maskPattern + formatInfo ^= FormatInfoMaskPattern; - BitList resultBits = new() - { - { formatInfo, 15 } - }; + BitList resultBits = new() + { + { formatInfo, 15 } + }; - if (resultBits.Count != 15) - { - throw new Exception("FormatInfoBits length is not 15"); - } - else - { - return resultBits; - } - } + if (resultBits.Count != 15) + { + throw new Exception("FormatInfoBits length is not 15"); + } + else + { + return resultBits; + } + } - /// - /// According Table 25 — Error correction level indicators - /// Using these bits as enum values would destroy their order which currently corresponds to error correction strength. - /// - internal static int GetErrorCorrectionIndicatorBits(ErrorCorrectionLevel errorLevel) - { - // L 01 - // M 00 - // Q 11 - // H 10 - return errorLevel switch - { - ErrorCorrectionLevel.H => 0x02, - ErrorCorrectionLevel.L => 0x01, - ErrorCorrectionLevel.M => 0x00, - ErrorCorrectionLevel.Q => 0x03, - _ => throw new ArgumentException($"Unsupported error correction level [{errorLevel}]", nameof(errorLevel)) - }; - } -} + /// + /// According Table 25 — Error correction level indicators + /// Using these bits as enum values would destroy their order which currently corresponds to error correction strength. + /// + internal static int GetErrorCorrectionIndicatorBits(ErrorCorrectionLevel errorLevel) + { + // L 01 + // M 00 + // Q 11 + // H 10 + return errorLevel switch + { + ErrorCorrectionLevel.H => 0x02, + ErrorCorrectionLevel.L => 0x01, + ErrorCorrectionLevel.M => 0x00, + ErrorCorrectionLevel.Q => 0x03, + _ => throw new ArgumentException($"Unsupported error correction level [{errorLevel}]", nameof(errorLevel)) + }; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/VersionInformation.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/VersionInformation.cs index 11b9718..acf3577 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/VersionInformation.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/VersionInformation.cs @@ -1,5 +1,3 @@ -using System; - namespace Gma.QrCodeNet.Encoding.EncodingRegion; /// @@ -8,63 +6,63 @@ namespace Gma.QrCodeNet.Encoding.EncodingRegion; /// ISO/IEC 18004:2000 Chapter 8.10 Page 54 internal static class VersionInformation { - private const int VIRectangleHeight = 3; - private const int VIRectangleWidth = 6; + private const int VIRectangleHeight = 3; + private const int VIRectangleWidth = 6; - private const int LengthDataBits = 6; - private const int LengthECBits = 12; - private const int VersionBCHPoly = 0x1f25; + private const int LengthDataBits = 6; + private const int LengthECBits = 12; + private const int VersionBCHPoly = 0x1f25; - /// - /// Embed version information to Matrix - /// Only for version greater than or equal to 7 - /// - internal static void EmbedVersionInformation(this TriStateMatrix tsMatrix, int version) - { - if (version < 7) - { - return; - } + /// + /// Embed version information to Matrix + /// Only for version greater than or equal to 7 + /// + internal static void EmbedVersionInformation(this TriStateMatrix tsMatrix, int version) + { + if (version < 7) + { + return; + } - BitList versionInfo = VersionInfoBitList(version); + BitList versionInfo = VersionInfoBitList(version); - int matrixWidth = tsMatrix.Width; + int matrixWidth = tsMatrix.Width; - // 1 cell between version info and position stencil - int shiftLength = QRCodeConstantVariable.PositionStencilWidth + VIRectangleHeight + 1; + // 1 cell between version info and position stencil + int shiftLength = QRCodeConstantVariable.PositionStencilWidth + VIRectangleHeight + 1; - // Reverse order input - int viIndex = LengthDataBits + LengthECBits - 1; + // Reverse order input + int viIndex = LengthDataBits + LengthECBits - 1; - for (int viWidth = 0; viWidth < VIRectangleWidth; viWidth++) - { - for (int viHeight = 0; viHeight < VIRectangleHeight; viHeight++) - { - bool bit = versionInfo[viIndex]; - viIndex--; + for (int viWidth = 0; viWidth < VIRectangleWidth; viWidth++) + { + for (int viHeight = 0; viHeight < VIRectangleHeight; viHeight++) + { + bool bit = versionInfo[viIndex]; + viIndex--; - // Bottom left - tsMatrix[viWidth, (matrixWidth - shiftLength + viHeight), MatrixStatus.NoMask] = bit; + // Bottom left + tsMatrix[viWidth, (matrixWidth - shiftLength + viHeight), MatrixStatus.NoMask] = bit; - // Top right - tsMatrix[(matrixWidth - shiftLength + viHeight), viWidth, MatrixStatus.NoMask] = bit; - } - } - } + // Top right + tsMatrix[(matrixWidth - shiftLength + viHeight), viWidth, MatrixStatus.NoMask] = bit; + } + } + } - private static BitList VersionInfoBitList(int version) - { - BitList result = new() - { - { version, LengthDataBits }, - { BCHCalculator.CalculateBCH(version, VersionBCHPoly), LengthECBits } - }; + private static BitList VersionInfoBitList(int version) + { + BitList result = new() + { + { version, LengthDataBits }, + { BCHCalculator.CalculateBCH(version, VersionBCHPoly), LengthECBits } + }; - if (result.Count != (LengthECBits + LengthDataBits)) - { - throw new Exception("Version Info creation error. Result is not 18 bits"); - } + if (result.Count != (LengthECBits + LengthDataBits)) + { + throw new Exception("Version Info creation error. Result is not 18 bits"); + } - return result; - } -} + return result; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ErrorCorrection/ECGenerator.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ErrorCorrection/ECGenerator.cs index b9a3dff..b1c6b10 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ErrorCorrection/ECGenerator.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ErrorCorrection/ECGenerator.cs @@ -1,84 +1,82 @@ using Gma.QrCodeNet.Encoding.ReedSolomon; -using System; -using System.Collections.Generic; namespace Gma.QrCodeNet.Encoding.ErrorCorrection; internal static class ECGenerator { - internal static BitList FillECCodewords(BitList dataCodewords, VersionDetail vd) - { - List dataCodewordsByte = dataCodewords.List; + internal static BitList FillECCodewords(BitList dataCodewords, VersionDetail vd) + { + List dataCodewordsByte = dataCodewords.List; - int ecBlockGroup1 = vd.ECBlockGroup1; - int numDataBytesGroup1 = vd.NumDataBytesGroup1; - int numDataBytesGroup2 = vd.NumDataBytesGroup2; + int ecBlockGroup1 = vd.ECBlockGroup1; + int numDataBytesGroup1 = vd.NumDataBytesGroup1; + int numDataBytesGroup2 = vd.NumDataBytesGroup2; - int ecBytesPerBlock = vd.NumECBytesPerBlock; + int ecBytesPerBlock = vd.NumECBytesPerBlock; - int dataBytesOffset = 0; - byte[][] dByteJArray = new byte[vd.NumECBlocks][]; - byte[][] ecByteJArray = new byte[vd.NumECBlocks][]; + int dataBytesOffset = 0; + byte[][] dByteJArray = new byte[vd.NumECBlocks][]; + byte[][] ecByteJArray = new byte[vd.NumECBlocks][]; - GaloisField256 gf256 = GaloisField256.QRCodeGaloisField; - GeneratorPolynomial generator = new(gf256); + GaloisField256 gf256 = GaloisField256.QRCodeGaloisField; + GeneratorPolynomial generator = new(gf256); - for (int blockId = 0; blockId < vd.NumECBlocks; blockId++) - { - if (blockId < ecBlockGroup1) - { - dByteJArray[blockId] = new byte[numDataBytesGroup1]; - for (int index = 0; index < numDataBytesGroup1; index++) - { - dByteJArray[blockId][index] = dataCodewordsByte[dataBytesOffset + index]; - } - dataBytesOffset += numDataBytesGroup1; - } - else - { - dByteJArray[blockId] = new byte[numDataBytesGroup2]; - for (int index = 0; index < numDataBytesGroup2; index++) - { - dByteJArray[blockId][index] = dataCodewordsByte[dataBytesOffset + index]; - } - dataBytesOffset += numDataBytesGroup2; - } + for (int blockId = 0; blockId < vd.NumECBlocks; blockId++) + { + if (blockId < ecBlockGroup1) + { + dByteJArray[blockId] = new byte[numDataBytesGroup1]; + for (int index = 0; index < numDataBytesGroup1; index++) + { + dByteJArray[blockId][index] = dataCodewordsByte[dataBytesOffset + index]; + } + dataBytesOffset += numDataBytesGroup1; + } + else + { + dByteJArray[blockId] = new byte[numDataBytesGroup2]; + for (int index = 0; index < numDataBytesGroup2; index++) + { + dByteJArray[blockId][index] = dataCodewordsByte[dataBytesOffset + index]; + } + dataBytesOffset += numDataBytesGroup2; + } - ecByteJArray[blockId] = ReedSolomonEncoder.Encode(dByteJArray[blockId], ecBytesPerBlock, generator); - } - if (vd.NumDataBytes != dataBytesOffset) - { - throw new ArgumentException("Data bytes do not match offset"); - } + ecByteJArray[blockId] = ReedSolomonEncoder.Encode(dByteJArray[blockId], ecBytesPerBlock, generator); + } + if (vd.NumDataBytes != dataBytesOffset) + { + throw new ArgumentException("Data bytes do not match offset"); + } - BitList codewords = new(); + BitList codewords = new(); - int maxDataLength = ecBlockGroup1 == vd.NumECBlocks ? numDataBytesGroup1 : numDataBytesGroup2; + int maxDataLength = ecBlockGroup1 == vd.NumECBlocks ? numDataBytesGroup1 : numDataBytesGroup2; - for (int dataId = 0; dataId < maxDataLength; dataId++) - { - for (int blockId = 0; blockId < vd.NumECBlocks; blockId++) - { - if (!(dataId == numDataBytesGroup1 && blockId < ecBlockGroup1)) - { - codewords.Add(dByteJArray[blockId][dataId], 8); - } - } - } + for (int dataId = 0; dataId < maxDataLength; dataId++) + { + for (int blockId = 0; blockId < vd.NumECBlocks; blockId++) + { + if (!(dataId == numDataBytesGroup1 && blockId < ecBlockGroup1)) + { + codewords.Add(dByteJArray[blockId][dataId], 8); + } + } + } - for (int ecId = 0; ecId < ecBytesPerBlock; ecId++) - { - for (int blockId = 0; blockId < vd.NumECBlocks; blockId++) - { - codewords.Add(ecByteJArray[blockId][ecId], 8); - } - } + for (int ecId = 0; ecId < ecBytesPerBlock; ecId++) + { + for (int blockId = 0; blockId < vd.NumECBlocks; blockId++) + { + codewords.Add(ecByteJArray[blockId][ecId], 8); + } + } - if (vd.NumTotalBytes != codewords.Count >> 3) - { - throw new ArgumentException($"Total bytes: {vd.NumTotalBytes}. Actual bits: {codewords.Count}"); - } + if (vd.NumTotalBytes != codewords.Count >> 3) + { + throw new ArgumentException($"Total bytes: {vd.NumTotalBytes}. Actual bits: {codewords.Count}"); + } - return codewords; - } -} + return codewords; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ErrorCorrectionLevel.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ErrorCorrectionLevel.cs index 8fb6fb2..37e12cc 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ErrorCorrectionLevel.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ErrorCorrectionLevel.cs @@ -2,8 +2,8 @@ namespace Gma.QrCodeNet.Encoding; public enum ErrorCorrectionLevel { - L, - M, - Q, - H -} + L, + M, + Q, + H +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/InputOutOfBoundaryException.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/InputOutOfBoundaryException.cs index ffd2890..cfc7982 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/InputOutOfBoundaryException.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/InputOutOfBoundaryException.cs @@ -1,5 +1,3 @@ -using System; - namespace Gma.QrCodeNet.Encoding; /// @@ -7,11 +5,11 @@ namespace Gma.QrCodeNet.Encoding; /// public class InputOutOfBoundaryException : Exception { - public InputOutOfBoundaryException() : base() - { - } + public InputOutOfBoundaryException() : base() + { + } - public InputOutOfBoundaryException(string message) : base(message) - { - } -} + public InputOutOfBoundaryException(string message) : base(message) + { + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/MaskPatternType.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/MaskPatternType.cs index f95f6ab..b98fef9 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/MaskPatternType.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/MaskPatternType.cs @@ -2,12 +2,12 @@ namespace Gma.QrCodeNet.Encoding.Masking; public enum MaskPatternType { - Type0 = 0, - Type1 = 1, - Type2 = 2, - Type3 = 3, - Type4 = 4, - Type5 = 5, - Type6 = 6, - Type7 = 7 -} + Type0 = 0, + Type1 = 1, + Type2 = 2, + Type3 = 3, + Type4 = 4, + Type5 = 5, + Type6 = 6, + Type7 = 7 +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/MatrixExtensions.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/MatrixExtensions.cs index fa8c1eb..350a37a 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/MatrixExtensions.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/MatrixExtensions.cs @@ -1,44 +1,43 @@ -using System; using Gma.QrCodeNet.Encoding.EncodingRegion; namespace Gma.QrCodeNet.Encoding.Masking; public static class MatrixExtensions { - public static TriStateMatrix Xor(this TriStateMatrix first, Pattern second, ErrorCorrectionLevel errorLevel) - { - TriStateMatrix result = XorMatrix(first, second); - result.EmbedFormatInformation(errorLevel, second); - return result; - } + public static TriStateMatrix Xor(this TriStateMatrix first, Pattern second, ErrorCorrectionLevel errorLevel) + { + TriStateMatrix result = XorMatrix(first, second); + result.EmbedFormatInformation(errorLevel, second); + return result; + } - private static TriStateMatrix XorMatrix(TriStateMatrix first, BitMatrix second) - { - int width = first.Width; - TriStateMatrix maskedMatrix = new(width); - for (int x = 0; x < width; x++) - { - for (int y = 0; y < width; y++) - { - MatrixStatus states = first.MStatus(x, y); - switch (states) - { - case MatrixStatus.NoMask: - maskedMatrix[x, y, MatrixStatus.NoMask] = first[x, y]; - break; + private static TriStateMatrix XorMatrix(TriStateMatrix first, BitMatrix second) + { + int width = first.Width; + TriStateMatrix maskedMatrix = new(width); + for (int x = 0; x < width; x++) + { + for (int y = 0; y < width; y++) + { + MatrixStatus states = first.MStatus(x, y); + switch (states) + { + case MatrixStatus.NoMask: + maskedMatrix[x, y, MatrixStatus.NoMask] = first[x, y]; + break; - case MatrixStatus.Data: - maskedMatrix[x, y, MatrixStatus.Data] = first[x, y] ^ second[x, y]; - break; + case MatrixStatus.Data: + maskedMatrix[x, y, MatrixStatus.Data] = first[x, y] ^ second[x, y]; + break; - default: - throw new ArgumentException($"{nameof(TriStateMatrix)} has None value cell.", nameof(first)); - } - } - } + default: + throw new ArgumentException($"{nameof(TriStateMatrix)} has None value cell.", nameof(first)); + } + } + } - return maskedMatrix; - } + return maskedMatrix; + } - public static TriStateMatrix Apply(this TriStateMatrix matrix, Pattern pattern, ErrorCorrectionLevel errorLevel) => matrix.Xor(pattern, errorLevel); -} + public static TriStateMatrix Apply(this TriStateMatrix matrix, Pattern pattern, ErrorCorrectionLevel errorLevel) => matrix.Xor(pattern, errorLevel); +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern.cs index ee5a3e3..6303438 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern.cs @@ -1,13 +1,11 @@ -using System; - namespace Gma.QrCodeNet.Encoding.Masking; public abstract class Pattern : BitMatrix { - public override int Width => throw new NotSupportedException(); - public override int Height => throw new NotSupportedException(); + public override int Width => throw new NotSupportedException(); + public override int Height => throw new NotSupportedException(); - public override bool[,] InternalArray => throw new NotImplementedException(); + public override bool[,] InternalArray => throw new NotImplementedException(); - public abstract MaskPatternType MaskPatternType { get; } -} + public abstract MaskPatternType MaskPatternType { get; } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern0.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern0.cs index 7134912..1e5d594 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern0.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern0.cs @@ -1,14 +1,12 @@ -using System; - namespace Gma.QrCodeNet.Encoding.Masking; internal class Pattern0 : Pattern { - public override MaskPatternType MaskPatternType => MaskPatternType.Type0; + public override MaskPatternType MaskPatternType => MaskPatternType.Type0; - public override bool this[int i, int j] - { - get => (j + i) % 2 == 0; - set => throw new NotSupportedException(); - } -} + public override bool this[int i, int j] + { + get => (j + i) % 2 == 0; + set => throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern1.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern1.cs index 63903ac..3f94b65 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern1.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern1.cs @@ -1,14 +1,12 @@ -using System; - namespace Gma.QrCodeNet.Encoding.Masking; internal class Pattern1 : Pattern { - public override MaskPatternType MaskPatternType => MaskPatternType.Type1; + public override MaskPatternType MaskPatternType => MaskPatternType.Type1; - public override bool this[int i, int j] - { - get => j % 2 == 0; - set => throw new NotSupportedException(); - } -} + public override bool this[int i, int j] + { + get => j % 2 == 0; + set => throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern2.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern2.cs index 0bf6e3b..a170fb5 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern2.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern2.cs @@ -1,14 +1,12 @@ -using System; - namespace Gma.QrCodeNet.Encoding.Masking; internal class Pattern2 : Pattern { - public override MaskPatternType MaskPatternType => MaskPatternType.Type2; + public override MaskPatternType MaskPatternType => MaskPatternType.Type2; - public override bool this[int i, int j] - { - get => i % 3 == 0; - set => throw new NotSupportedException(); - } -} + public override bool this[int i, int j] + { + get => i % 3 == 0; + set => throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern3.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern3.cs index ce9d7f3..7a9cf83 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern3.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern3.cs @@ -1,14 +1,12 @@ -using System; - namespace Gma.QrCodeNet.Encoding.Masking; internal class Pattern3 : Pattern { - public override MaskPatternType MaskPatternType => MaskPatternType.Type3; + public override MaskPatternType MaskPatternType => MaskPatternType.Type3; - public override bool this[int i, int j] - { - get => (j + i) % 3 == 0; - set => throw new NotSupportedException(); - } -} + public override bool this[int i, int j] + { + get => (j + i) % 3 == 0; + set => throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern4.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern4.cs index 6da839c..1ce0521 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern4.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern4.cs @@ -1,14 +1,12 @@ -using System; - namespace Gma.QrCodeNet.Encoding.Masking; internal class Pattern4 : Pattern { - public override MaskPatternType MaskPatternType => MaskPatternType.Type4; + public override MaskPatternType MaskPatternType => MaskPatternType.Type4; - public override bool this[int i, int j] - { - get => ((j / 2) + (i / 3)) % 2 == 0; - set => throw new NotSupportedException(); - } -} + public override bool this[int i, int j] + { + get => ((j / 2) + (i / 3)) % 2 == 0; + set => throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern5.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern5.cs index 632aba3..ab62f07 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern5.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern5.cs @@ -1,14 +1,12 @@ -using System; - namespace Gma.QrCodeNet.Encoding.Masking; internal class Pattern5 : Pattern { - public override MaskPatternType MaskPatternType => MaskPatternType.Type5; + public override MaskPatternType MaskPatternType => MaskPatternType.Type5; - public override bool this[int i, int j] - { - get => (((i * j) % 2) + ((i * j) % 3)) == 0; - set => throw new NotSupportedException(); - } -} + public override bool this[int i, int j] + { + get => (((i * j) % 2) + ((i * j) % 3)) == 0; + set => throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern6.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern6.cs index 7ad6eeb..69cde6f 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern6.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern6.cs @@ -1,13 +1,12 @@ -using System; namespace Gma.QrCodeNet.Encoding.Masking; internal class Pattern6 : Pattern { - public override MaskPatternType MaskPatternType => MaskPatternType.Type6; + public override MaskPatternType MaskPatternType => MaskPatternType.Type6; - public override bool this[int i, int j] - { - get => ((((i * j) % 2) + ((i * j) % 3)) % 2) == 0; - set => throw new NotSupportedException(); - } -} + public override bool this[int i, int j] + { + get => ((((i * j) % 2) + ((i * j) % 3)) % 2) == 0; + set => throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern7.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern7.cs index d70b05c..4e1f436 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern7.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern7.cs @@ -1,14 +1,12 @@ -using System; - namespace Gma.QrCodeNet.Encoding.Masking; internal class Pattern7 : Pattern { - public override MaskPatternType MaskPatternType => MaskPatternType.Type7; + public override MaskPatternType MaskPatternType => MaskPatternType.Type7; - public override bool this[int i, int j] - { - get => (((i * j) % 3) + (((i + j) % 2) % 2)) == 0; - set => throw new NotSupportedException(); - } -} + public override bool this[int i, int j] + { + get => (((i * j) % 3) + (((i + j) % 2) % 2)) == 0; + set => throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/PatternFactory.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/PatternFactory.cs index 986c541..4a5de40 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/PatternFactory.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/PatternFactory.cs @@ -1,31 +1,28 @@ -using System; -using System.Collections.Generic; - namespace Gma.QrCodeNet.Encoding.Masking; internal class PatternFactory { - internal Pattern CreateByType(MaskPatternType maskPatternType) - { - return maskPatternType switch - { - MaskPatternType.Type0 => new Pattern0(), - MaskPatternType.Type1 => new Pattern1(), - MaskPatternType.Type2 => new Pattern2(), - MaskPatternType.Type3 => new Pattern3(), - MaskPatternType.Type4 => new Pattern4(), - MaskPatternType.Type5 => new Pattern5(), - MaskPatternType.Type6 => new Pattern6(), - MaskPatternType.Type7 => new Pattern7(), - _ => throw new NotSupportedException("This should never happen.") - }; - } + internal Pattern CreateByType(MaskPatternType maskPatternType) + { + return maskPatternType switch + { + MaskPatternType.Type0 => new Pattern0(), + MaskPatternType.Type1 => new Pattern1(), + MaskPatternType.Type2 => new Pattern2(), + MaskPatternType.Type3 => new Pattern3(), + MaskPatternType.Type4 => new Pattern4(), + MaskPatternType.Type5 => new Pattern5(), + MaskPatternType.Type6 => new Pattern6(), + MaskPatternType.Type7 => new Pattern7(), + _ => throw new NotSupportedException("This should never happen.") + }; + } - internal IEnumerable AllPatterns() - { - foreach (MaskPatternType patternType in Enum.GetValues(typeof(MaskPatternType))) - { - yield return CreateByType(patternType); - } - } -} + internal IEnumerable AllPatterns() + { + foreach (MaskPatternType patternType in Enum.GetValues(typeof(MaskPatternType))) + { + yield return CreateByType(patternType); + } + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/MatrixScoreCalculator.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/MatrixScoreCalculator.cs index 1105279..2b5fd5a 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/MatrixScoreCalculator.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/MatrixScoreCalculator.cs @@ -1,36 +1,34 @@ -using System.Linq; - namespace Gma.QrCodeNet.Encoding.Masking.Scoring; internal static class MatrixScoreCalculator { - internal static BitMatrix GetLowestPenaltyMatrix(this TriStateMatrix matrix, ErrorCorrectionLevel errorLevel) - { - PatternFactory patternFactory = new(); - int score = int.MaxValue; - int tempScore; - TriStateMatrix result = new(matrix.Width); - TriStateMatrix triMatrix; - foreach (Pattern pattern in patternFactory.AllPatterns()) - { - triMatrix = matrix.Apply(pattern, errorLevel); - tempScore = triMatrix.PenaltyScore(); - if (tempScore < score) - { - score = tempScore; - result = triMatrix; - } - } + internal static BitMatrix GetLowestPenaltyMatrix(this TriStateMatrix matrix, ErrorCorrectionLevel errorLevel) + { + PatternFactory patternFactory = new(); + int score = int.MaxValue; + int tempScore; + TriStateMatrix result = new(matrix.Width); + TriStateMatrix triMatrix; + foreach (Pattern pattern in patternFactory.AllPatterns()) + { + triMatrix = matrix.Apply(pattern, errorLevel); + tempScore = triMatrix.PenaltyScore(); + if (tempScore < score) + { + score = tempScore; + result = triMatrix; + } + } - return result; - } + return result; + } - internal static int PenaltyScore(this BitMatrix matrix) - { - PenaltyFactory penaltyFactory = new(); - return - penaltyFactory - .AllRules() - .Sum(penalty => penalty.PenaltyCalculate(matrix)); - } -} + internal static int PenaltyScore(this BitMatrix matrix) + { + PenaltyFactory penaltyFactory = new(); + return + penaltyFactory + .AllRules() + .Sum(penalty => penalty.PenaltyCalculate(matrix)); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty.cs index 761e5a4..39b000f 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty.cs @@ -2,5 +2,5 @@ namespace Gma.QrCodeNet.Encoding.Masking.Scoring; public abstract class Penalty { - internal abstract int PenaltyCalculate(BitMatrix matrix); -} + internal abstract int PenaltyCalculate(BitMatrix matrix); +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty1.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty1.cs index ff80b32..c8be43e 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty1.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty1.cs @@ -5,81 +5,81 @@ namespace Gma.QrCodeNet.Encoding.Masking.Scoring; /// internal class Penalty1 : Penalty { - /// - /// Calculate penalty value for first rule. - /// - internal override int PenaltyCalculate(BitMatrix matrix) - { - int penaltyValue = PenaltyCalculation(matrix, true) + PenaltyCalculation(matrix, false); - return penaltyValue; - } + /// + /// Calculate penalty value for first rule. + /// + internal override int PenaltyCalculate(BitMatrix matrix) + { + int penaltyValue = PenaltyCalculation(matrix, true) + PenaltyCalculation(matrix, false); + return penaltyValue; + } - private int PenaltyCalculation(BitMatrix matrix, bool isHorizontal) - { - int penalty = 0; - int width = matrix.Width; + private int PenaltyCalculation(BitMatrix matrix, bool isHorizontal) + { + int penalty = 0; + int width = matrix.Width; - int i = 0; - int j = 0; + int i = 0; + int j = 0; - while (i < width) - { - while (j < width - 4) - { - bool preBit = isHorizontal - ? matrix[j + 4, i] - : matrix[i, j + 4]; - int numSameBitCell = 1; + while (i < width) + { + while (j < width - 4) + { + bool preBit = isHorizontal + ? matrix[j + 4, i] + : matrix[i, j + 4]; + int numSameBitCell = 1; - for (int x = 1; x <= 4; x++) - { - bool bit = isHorizontal - ? matrix[j + 4 - x, i] - : matrix[i, j + 4 - x]; - if (bit == preBit) - { - numSameBitCell++; - } - else - { - break; - } - } + for (int x = 1; x <= 4; x++) + { + bool bit = isHorizontal + ? matrix[j + 4 - x, i] + : matrix[i, j + 4 - x]; + if (bit == preBit) + { + numSameBitCell++; + } + else + { + break; + } + } - if (numSameBitCell == 1) - { - j += 4; - } - else - { - int x = 5; - while ((j + x) < width) - { - bool bit = isHorizontal - ? matrix[j + x, i] - : matrix[i, j + x]; - if (bit == preBit) - { - numSameBitCell++; - } - else - { - break; - } - x++; - } - if (numSameBitCell >= 5) - { - penalty += (3 + (numSameBitCell - 5)); - } + if (numSameBitCell == 1) + { + j += 4; + } + else + { + int x = 5; + while ((j + x) < width) + { + bool bit = isHorizontal + ? matrix[j + x, i] + : matrix[i, j + x]; + if (bit == preBit) + { + numSameBitCell++; + } + else + { + break; + } + x++; + } + if (numSameBitCell >= 5) + { + penalty += (3 + (numSameBitCell - 5)); + } - j += x; - } - } - j = 0; - i++; - } + j += x; + } + } + j = 0; + i++; + } - return penalty; - } -} + return penalty; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty2.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty2.cs index f99e6d8..283da24 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty2.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty2.cs @@ -5,47 +5,47 @@ namespace Gma.QrCodeNet.Encoding.Masking.Scoring; /// internal class Penalty2 : Penalty { - internal override int PenaltyCalculate(BitMatrix matrix) - { - int width = matrix.Width; - int x = 0; - int y = 0; - int penalty = 0; + internal override int PenaltyCalculate(BitMatrix matrix) + { + int width = matrix.Width; + int x = 0; + int y = 0; + int penalty = 0; - while (y < (width - 1)) - { - while (x < (width - 1)) - { - bool topR = matrix[x + 1, y]; + while (y < (width - 1)) + { + while (x < (width - 1)) + { + bool topR = matrix[x + 1, y]; - if (topR == matrix[x + 1, y + 1]) // Bottom Right - { - if (topR == matrix[x, y + 1]) // Bottom Left - { - if (topR == matrix[x, y]) // Top Left - { - penalty += 3; - x += 1; - } - else - { - x += 1; - } - } - else - { - x += 1; - } - } - else - { - x += 2; - } - } + if (topR == matrix[x + 1, y + 1]) // Bottom Right + { + if (topR == matrix[x, y + 1]) // Bottom Left + { + if (topR == matrix[x, y]) // Top Left + { + penalty += 3; + x += 1; + } + else + { + x += 1; + } + } + else + { + x += 1; + } + } + else + { + x += 2; + } + } - x = 0; - y++; - } - return penalty; - } -} + x = 0; + y++; + } + return penalty; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty3.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty3.cs index 4b0ce1b..7bc285f 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty3.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty3.cs @@ -5,137 +5,137 @@ namespace Gma.QrCodeNet.Encoding.Masking.Scoring; /// internal class Penalty3 : Penalty { - /// - /// Calculate penalty value for Third rule. - /// - internal override int PenaltyCalculate(BitMatrix matrix) => PenaltyCalculation(matrix, true) + PenaltyCalculation(matrix, false); + /// + /// Calculate penalty value for Third rule. + /// + internal override int PenaltyCalculate(BitMatrix matrix) => PenaltyCalculation(matrix, true) + PenaltyCalculation(matrix, false); - private int PenaltyCalculation(BitMatrix matrix, bool isHorizontal) - { - int i = 0; - int j = 1; - int penalty = 0; - int width = matrix.Width; - bool bit; - while (i < width) - { - while (j < width - 5) - { - bit = isHorizontal - ? matrix[j + 4, i] - : matrix[i, j + 4]; - if (!bit) - { - bit = isHorizontal - ? matrix[j, i] - : matrix[i, j]; - if (!bit) - { - penalty += PatternCheck(matrix, i, j, isHorizontal); - j += 4; - } - else - { - j += 4; - } - } - else - { - for (int num = 4; num > 0; num--) - { - bit = isHorizontal - ? matrix[j + num, i] - : matrix[i, j + num]; - if (!bit) - { - j += num; - break; - } - if (num == 1) - { - j += 5; - } - } - } - } - j = 0; - i++; - } - return penalty; - } + private int PenaltyCalculation(BitMatrix matrix, bool isHorizontal) + { + int i = 0; + int j = 1; + int penalty = 0; + int width = matrix.Width; + bool bit; + while (i < width) + { + while (j < width - 5) + { + bit = isHorizontal + ? matrix[j + 4, i] + : matrix[i, j + 4]; + if (!bit) + { + bit = isHorizontal + ? matrix[j, i] + : matrix[i, j]; + if (!bit) + { + penalty += PatternCheck(matrix, i, j, isHorizontal); + j += 4; + } + else + { + j += 4; + } + } + else + { + for (int num = 4; num > 0; num--) + { + bit = isHorizontal + ? matrix[j + num, i] + : matrix[i, j + num]; + if (!bit) + { + j += num; + break; + } + if (num == 1) + { + j += 5; + } + } + } + } + j = 0; + i++; + } + return penalty; + } - private int PatternCheck(BitMatrix matrix, int i, int j, bool isHorizontal) - { - bool bit; - for (int num = 3; num >= 1; num--) - { - bit = isHorizontal - ? matrix[j + num, i] - : matrix[i, j + num]; - if (!bit) - { - return 0; - } - } + private int PatternCheck(BitMatrix matrix, int i, int j, bool isHorizontal) + { + bool bit; + for (int num = 3; num >= 1; num--) + { + bit = isHorizontal + ? matrix[j + num, i] + : matrix[i, j + num]; + if (!bit) + { + return 0; + } + } - // Check for left side and right side x ( xoxxxox ). - if ((j - 1) < 0 || (j + 1) >= matrix.Width) - { - return 0; - } + // Check for left side and right side x ( xoxxxox ). + if ((j - 1) < 0 || (j + 1) >= matrix.Width) + { + return 0; + } - bit = isHorizontal - ? matrix[j + 5, i] - : matrix[i, j + 5]; - if (!bit) - { - return 0; - } + bit = isHorizontal + ? matrix[j + 5, i] + : matrix[i, j + 5]; + if (!bit) + { + return 0; + } - bit = isHorizontal - ? matrix[j - 1, i] - : matrix[i, j - 1]; - if (!bit) - { - return 0; - } + bit = isHorizontal + ? matrix[j - 1, i] + : matrix[i, j - 1]; + if (!bit) + { + return 0; + } - if ((j - 5) >= 0) - { - for (int num = -2; num >= -5; num--) - { - bit = isHorizontal - ? matrix[j + num, i] - : matrix[i, j + num]; - if (bit) - { - break; - } + if ((j - 5) >= 0) + { + for (int num = -2; num >= -5; num--) + { + bit = isHorizontal + ? matrix[j + num, i] + : matrix[i, j + num]; + if (bit) + { + break; + } - if (num == -5) - { - return 40; - } - } - } + if (num == -5) + { + return 40; + } + } + } - if ((j + 9) < matrix.Width) - { - for (int num = 6; num <= 9; num++) - { - bit = isHorizontal - ? matrix[j + num, i] - : matrix[i, j + num]; - if (bit) - { - return 0; - } - } - return 40; - } - else - { - return 0; - } - } -} + if ((j + 9) < matrix.Width) + { + for (int num = 6; num <= 9; num++) + { + bit = isHorizontal + ? matrix[j + num, i] + : matrix[i, j + num]; + if (bit) + { + return 0; + } + } + return 40; + } + else + { + return 0; + } + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty4.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty4.cs index 88382f5..0eeab71 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty4.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty4.cs @@ -1,5 +1,3 @@ -using System; - namespace Gma.QrCodeNet.Encoding.Masking.Scoring; /// @@ -7,30 +5,30 @@ namespace Gma.QrCodeNet.Encoding.Masking.Scoring; /// internal class Penalty4 : Penalty { - /// - /// Calculate penalty value for Fourth rule. - /// Perform O(n) search for available x modules - /// - internal override int PenaltyCalculate(BitMatrix matrix) - { - int width = matrix.Width; - int darkBitCount = 0; + /// + /// Calculate penalty value for Fourth rule. + /// Perform O(n) search for available x modules + /// + internal override int PenaltyCalculate(BitMatrix matrix) + { + int width = matrix.Width; + int darkBitCount = 0; - for (int j = 0; j < width; j++) - { - for (int i = 0; i < width; i++) - { - if (matrix[i, j]) - { - darkBitCount++; - } - } - } + for (int j = 0; j < width; j++) + { + for (int i = 0; i < width; i++) + { + if (matrix[i, j]) + { + darkBitCount++; + } + } + } - int matrixCount = width * width; + int matrixCount = width * width; - double ratio = (double)darkBitCount / matrixCount; + double ratio = (double)darkBitCount / matrixCount; - return Math.Abs((int)((ratio * 100) - 50)) / 5 * 10; - } -} + return Math.Abs((int)((ratio * 100) - 50)) / 5 * 10; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/PenaltyFactory.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/PenaltyFactory.cs index 36d1797..691933a 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/PenaltyFactory.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/PenaltyFactory.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; - namespace Gma.QrCodeNet.Encoding.Masking.Scoring; /// @@ -8,23 +5,23 @@ namespace Gma.QrCodeNet.Encoding.Masking.Scoring; /// internal class PenaltyFactory { - internal Penalty CreateByRule(PenaltyRules penaltyRule) - { - return penaltyRule switch - { - PenaltyRules.Rule01 => new Penalty1(), - PenaltyRules.Rule02 => new Penalty2(), - PenaltyRules.Rule03 => new Penalty3(), - PenaltyRules.Rule04 => new Penalty4(), - _ => throw new ArgumentException($"Unsupport penalty rule: {penaltyRule}", nameof(penaltyRule)) - }; - } + internal Penalty CreateByRule(PenaltyRules penaltyRule) + { + return penaltyRule switch + { + PenaltyRules.Rule01 => new Penalty1(), + PenaltyRules.Rule02 => new Penalty2(), + PenaltyRules.Rule03 => new Penalty3(), + PenaltyRules.Rule04 => new Penalty4(), + _ => throw new ArgumentException($"Unsupport penalty rule: {penaltyRule}", nameof(penaltyRule)) + }; + } - internal IEnumerable AllRules() - { - foreach (PenaltyRules penaltyRule in Enum.GetValues(typeof(PenaltyRules))) - { - yield return CreateByRule(penaltyRule); - } - } -} + internal IEnumerable AllRules() + { + foreach (PenaltyRules penaltyRule in Enum.GetValues(typeof(PenaltyRules))) + { + yield return CreateByRule(penaltyRule); + } + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/PenaltyRules.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/PenaltyRules.cs index 3cac9ed..c58ae1e 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/PenaltyRules.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/PenaltyRules.cs @@ -2,8 +2,8 @@ namespace Gma.QrCodeNet.Encoding.Masking.Scoring; public enum PenaltyRules { - Rule01 = 1, - Rule02 = 2, - Rule03 = 3, - Rule04 = 4 -} + Rule01 = 1, + Rule02 = 2, + Rule03 = 3, + Rule04 = 4 +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixPoint.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixPoint.cs index 66f73de..9f5cac9 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixPoint.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixPoint.cs @@ -2,19 +2,19 @@ namespace Gma.QrCodeNet.Encoding; public struct MatrixPoint { - internal MatrixPoint(int x, int y) - : this() - { - X = x; - Y = y; - } + internal MatrixPoint(int x, int y) + : this() + { + X = x; + Y = y; + } - public int X { get; private set; } - public int Y { get; private set; } + public int X { get; private set; } + public int Y { get; private set; } - public MatrixPoint Offset(MatrixPoint offset) => new(offset.X + X, offset.Y + Y); + public MatrixPoint Offset(MatrixPoint offset) => new(offset.X + X, offset.Y + Y); - internal MatrixPoint Offset(int offsetX, int offsetY) => Offset(new MatrixPoint(offsetX, offsetY)); + internal MatrixPoint Offset(int offsetX, int offsetY) => Offset(new MatrixPoint(offsetX, offsetY)); - public override string ToString() => $"Point({X};{Y})"; -} + public override string ToString() => $"Point({X};{Y})"; +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixRectangle.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixRectangle.cs index 2a9b202..b3e62cf 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixRectangle.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixRectangle.cs @@ -1,32 +1,31 @@ using System.Collections; -using System.Collections.Generic; namespace Gma.QrCodeNet.Encoding; internal struct MatrixRectangle : IEnumerable { - internal MatrixRectangle(MatrixPoint location, MatrixSize size) : - this() - { - Location = location; - Size = size; - } + internal MatrixRectangle(MatrixPoint location, MatrixSize size) : + this() + { + Location = location; + Size = size; + } - public MatrixPoint Location { get; private set; } - public MatrixSize Size { get; private set; } + public MatrixPoint Location { get; private set; } + public MatrixSize Size { get; private set; } - public IEnumerator GetEnumerator() - { - for (int j = Location.Y; j < Location.Y + Size.Height; j++) - { - for (int i = Location.X; i < Location.X + Size.Width; i++) - { - yield return new MatrixPoint(i, j); - } - } - } + public IEnumerator GetEnumerator() + { + for (int j = Location.Y; j < Location.Y + Size.Height; j++) + { + for (int i = Location.X; i < Location.X + Size.Width; i++) + { + yield return new MatrixPoint(i, j); + } + } + } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public override string ToString() => $"Rectangle({Location.X};{Location.Y}):({Size.Width} x {Size.Height})"; -} + public override string ToString() => $"Rectangle({Location.X};{Location.Y}):({Size.Width} x {Size.Height})"; +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixSize.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixSize.cs index cc6b2b3..a5b2705 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixSize.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixSize.cs @@ -2,18 +2,18 @@ namespace Gma.QrCodeNet.Encoding; public struct MatrixSize { - internal MatrixSize(int width, int height) - : this() - { - Width = width; - Height = height; - } + internal MatrixSize(int width, int height) + : this() + { + Width = width; + Height = height; + } - public int Width { get; private set; } - public int Height { get; private set; } + public int Width { get; private set; } + public int Height { get; private set; } - public override string ToString() - { - return $"Size({Width};{Height})"; - } -} + public override string ToString() + { + return $"Size({Width};{Height})"; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixStatus.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixStatus.cs index 4cd3647..d996b40 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixStatus.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixStatus.cs @@ -2,7 +2,7 @@ namespace Gma.QrCodeNet.Encoding; public enum MatrixStatus { - None, - NoMask, - Data -} + None, + NoMask, + Data +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/PositioninngPatternBuilder.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/PositioninngPatternBuilder.cs index 3d695cb..d33bb0b 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/PositioninngPatternBuilder.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/PositioninngPatternBuilder.cs @@ -4,11 +4,11 @@ namespace Gma.QrCodeNet.Encoding.Positioning; internal static class PositioningPatternBuilder { - internal static void EmbedBasicPatterns(int version, TriStateMatrix matrix) - { - new PositionDetectionPattern(version).ApplyTo(matrix); - new DarkDotAtLeftBottom(version).ApplyTo(matrix); - new AlignmentPattern(version).ApplyTo(matrix); - new TimingPattern(version).ApplyTo(matrix); - } -} + internal static void EmbedBasicPatterns(int version, TriStateMatrix matrix) + { + new PositionDetectionPattern(version).ApplyTo(matrix); + new DarkDotAtLeftBottom(version).ApplyTo(matrix); + new AlignmentPattern(version).ApplyTo(matrix); + new TimingPattern(version).ApplyTo(matrix); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/AlignmentPattern.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/AlignmentPattern.cs index 17c8b7e..ed186b1 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/AlignmentPattern.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/AlignmentPattern.cs @@ -1,105 +1,101 @@ -using System; -using System.Collections.Generic; -using System.Linq; - namespace Gma.QrCodeNet.Encoding.Positioning.Stencils; internal class AlignmentPattern : PatternStencilBase { - public AlignmentPattern(int version) - : base(version) - { - } + public AlignmentPattern(int version) + : base(version) + { + } - private static bool[,] AlignmentPatternArray { get; } = - new[,] - { - { X, X, X, X, X }, - { X, O, O, O, X }, - { X, O, X, O, X }, - { X, O, O, O, X }, - { X, X, X, X, X } - }; + private static bool[,] AlignmentPatternArray { get; } = + new[,] + { + { X, X, X, X, X }, + { X, O, O, O, X }, + { X, O, X, O, X }, + { X, O, O, O, X }, + { X, X, X, X, X } + }; - public override bool[,] Stencil => AlignmentPatternArray; + public override bool[,] Stencil => AlignmentPatternArray; - // Table E.1 — Row/column coordinates of center module of Alignment Patterns - private static byte[][] AlignmentPatternCoordinatesByVersion { get; } = - new[] - { - Array.Empty(), - Array.Empty(), - new byte[] { 6, 18 }, - new byte[] { 6, 22 }, - new byte[] { 6, 26 }, - new byte[] { 6, 30 }, - new byte[] { 6, 34 }, - new byte[] { 6, 22, 38 }, - new byte[] { 6, 24, 42 }, - new byte[] { 6, 26, 46 }, - new byte[] { 6, 28, 50 }, - new byte[] { 6, 30, 54 }, - new byte[] { 6, 32, 58 }, - new byte[] { 6, 34, 62 }, - new byte[] { 6, 26, 46, 66 }, - new byte[] { 6, 26, 48, 70 }, - new byte[] { 6, 26, 50, 74 }, - new byte[] { 6, 30, 54, 78 }, - new byte[] { 6, 30, 56, 82 }, - new byte[] { 6, 30, 58, 86 }, - new byte[] { 6, 34, 62, 90 }, - new byte[] { 6, 28, 50, 72, 94 }, - new byte[] { 6, 26, 50, 74, 98 }, - new byte[] { 6, 30, 54, 78, 102 }, - new byte[] { 6, 28, 54, 80, 106 }, - new byte[] { 6, 32, 58, 84, 110 }, - new byte[] { 6, 30, 58, 86, 114 }, - new byte[] { 6, 34, 62, 90, 118 }, - new byte[] { 6, 26, 50, 74, 98, 122 }, - new byte[] { 6, 30, 54, 78, 102, 126 }, - new byte[] { 6, 26, 52, 78, 104, 130 }, - new byte[] { 6, 30, 56, 82, 108, 134 }, - new byte[] { 6, 34, 60, 86, 112, 138 }, - new byte[] { 6, 30, 58, 86, 114, 142 }, - new byte[] { 6, 34, 62, 90, 118, 146 }, - new byte[] { 6, 30, 54, 78, 102, 126, 150 }, - new byte[] { 6, 24, 50, 76, 102, 128, 154 }, - new byte[] { 6, 28, 54, 80, 106, 132, 158 }, - new byte[] { 6, 32, 58, 84, 110, 136, 162 }, - new byte[] { 6, 26, 54, 82, 110, 138, 166 }, - new byte[] { 6, 30, 58, 86, 114, 142, 170 } - }; + // Table E.1 — Row/column coordinates of center module of Alignment Patterns + private static byte[][] AlignmentPatternCoordinatesByVersion { get; } = + new[] + { + Array.Empty(), + Array.Empty(), + new byte[] { 6, 18 }, + new byte[] { 6, 22 }, + new byte[] { 6, 26 }, + new byte[] { 6, 30 }, + new byte[] { 6, 34 }, + new byte[] { 6, 22, 38 }, + new byte[] { 6, 24, 42 }, + new byte[] { 6, 26, 46 }, + new byte[] { 6, 28, 50 }, + new byte[] { 6, 30, 54 }, + new byte[] { 6, 32, 58 }, + new byte[] { 6, 34, 62 }, + new byte[] { 6, 26, 46, 66 }, + new byte[] { 6, 26, 48, 70 }, + new byte[] { 6, 26, 50, 74 }, + new byte[] { 6, 30, 54, 78 }, + new byte[] { 6, 30, 56, 82 }, + new byte[] { 6, 30, 58, 86 }, + new byte[] { 6, 34, 62, 90 }, + new byte[] { 6, 28, 50, 72, 94 }, + new byte[] { 6, 26, 50, 74, 98 }, + new byte[] { 6, 30, 54, 78, 102 }, + new byte[] { 6, 28, 54, 80, 106 }, + new byte[] { 6, 32, 58, 84, 110 }, + new byte[] { 6, 30, 58, 86, 114 }, + new byte[] { 6, 34, 62, 90, 118 }, + new byte[] { 6, 26, 50, 74, 98, 122 }, + new byte[] { 6, 30, 54, 78, 102, 126 }, + new byte[] { 6, 26, 52, 78, 104, 130 }, + new byte[] { 6, 30, 56, 82, 108, 134 }, + new byte[] { 6, 34, 60, 86, 112, 138 }, + new byte[] { 6, 30, 58, 86, 114, 142 }, + new byte[] { 6, 34, 62, 90, 118, 146 }, + new byte[] { 6, 30, 54, 78, 102, 126, 150 }, + new byte[] { 6, 24, 50, 76, 102, 128, 154 }, + new byte[] { 6, 28, 54, 80, 106, 132, 158 }, + new byte[] { 6, 32, 58, 84, 110, 136, 162 }, + new byte[] { 6, 26, 54, 82, 110, 138, 166 }, + new byte[] { 6, 30, 58, 86, 114, 142, 170 } + }; - public override void ApplyTo(TriStateMatrix matrix) - { - foreach (MatrixPoint coordinatePair in GetNonColidingCoordinatePairs(matrix)) - { - CopyTo(matrix, coordinatePair, MatrixStatus.NoMask); - } - } + public override void ApplyTo(TriStateMatrix matrix) + { + foreach (MatrixPoint coordinatePair in GetNonColidingCoordinatePairs(matrix)) + { + CopyTo(matrix, coordinatePair, MatrixStatus.NoMask); + } + } - public IEnumerable GetNonColidingCoordinatePairs(TriStateMatrix matrix) - { - return - GetAllCoordinatePairs() - .Where(point => matrix.MStatus(point.Offset(2, 2)) == MatrixStatus.None); - } + public IEnumerable GetNonColidingCoordinatePairs(TriStateMatrix matrix) + { + return + GetAllCoordinatePairs() + .Where(point => matrix.MStatus(point.Offset(2, 2)) == MatrixStatus.None); + } - private IEnumerable GetAllCoordinatePairs() - { - IEnumerable coordinates = GetPatternCoordinatesByVersion(Version); - foreach (byte centerX in coordinates) - { - foreach (byte centerY in coordinates) - { - MatrixPoint location = new(centerX - 2, centerY - 2); - yield return location; - } - } - } + private IEnumerable GetAllCoordinatePairs() + { + IEnumerable coordinates = GetPatternCoordinatesByVersion(Version); + foreach (byte centerX in coordinates) + { + foreach (byte centerY in coordinates) + { + MatrixPoint location = new(centerX - 2, centerY - 2); + yield return location; + } + } + } - private static IEnumerable GetPatternCoordinatesByVersion(int version) - { - return AlignmentPatternCoordinatesByVersion[version]; - } -} + private static IEnumerable GetPatternCoordinatesByVersion(int version) + { + return AlignmentPatternCoordinatesByVersion[version]; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/DarkDotAtLeftBottom.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/DarkDotAtLeftBottom.cs index 829e3bc..f4747fd 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/DarkDotAtLeftBottom.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/DarkDotAtLeftBottom.cs @@ -1,17 +1,15 @@ -using System; - namespace Gma.QrCodeNet.Encoding.Positioning.Stencils; internal class DarkDotAtLeftBottom : PatternStencilBase { - public DarkDotAtLeftBottom(int version) : base(version) - { - } + public DarkDotAtLeftBottom(int version) : base(version) + { + } - public override bool[,] Stencil => throw new NotImplementedException(); + public override bool[,] Stencil => throw new NotImplementedException(); - public override void ApplyTo(TriStateMatrix matrix) - { - matrix[8, matrix.Width - 8, MatrixStatus.NoMask] = true; - } -} + public override void ApplyTo(TriStateMatrix matrix) + { + matrix[8, matrix.Width - 8, MatrixStatus.NoMask] = true; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/PatternStencilBase.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/PatternStencilBase.cs index 874042e..a69c6c9 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/PatternStencilBase.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/PatternStencilBase.cs @@ -1,32 +1,30 @@ -using System; - namespace Gma.QrCodeNet.Encoding.Positioning.Stencils; internal abstract class PatternStencilBase : BitMatrix { - protected const bool O = false; - protected const bool X = true; + protected const bool O = false; + protected const bool X = true; - internal PatternStencilBase(int version) - { - Version = version; - } + internal PatternStencilBase(int version) + { + Version = version; + } - public int Version { get; private set; } + public int Version { get; private set; } - public abstract bool[,] Stencil { get; } + public abstract bool[,] Stencil { get; } - public override int Width => Stencil.GetLength(0); + public override int Width => Stencil.GetLength(0); - public override int Height => Stencil.GetLength(1); + public override int Height => Stencil.GetLength(1); - public override bool[,] InternalArray => throw new NotImplementedException(); + public override bool[,] InternalArray => throw new NotImplementedException(); - public override bool this[int i, int j] - { - get => Stencil[i, j]; - set => throw new NotSupportedException(); - } + public override bool this[int i, int j] + { + get => Stencil[i, j]; + set => throw new NotSupportedException(); + } - public abstract void ApplyTo(TriStateMatrix matrix); -} + public abstract void ApplyTo(TriStateMatrix matrix); +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/PositionDetectionPattern.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/PositionDetectionPattern.cs index 0ebb501..75fc45b 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/PositionDetectionPattern.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/PositionDetectionPattern.cs @@ -2,40 +2,40 @@ namespace Gma.QrCodeNet.Encoding.Positioning.Stencils; internal class PositionDetectionPattern : PatternStencilBase { - public PositionDetectionPattern(int version) - : base(version) - { - } + public PositionDetectionPattern(int version) + : base(version) + { + } - private static bool[,] PositionDetection { get; } = - new[,] - { - { O, O, O, O, O, O, O, O, O }, - { O, X, X, X, X, X, X, X, O }, - { O, X, O, O, O, O, O, X, O }, - { O, X, O, X, X, X, O, X, O }, - { O, X, O, X, X, X, O, X, O }, - { O, X, O, X, X, X, O, X, O }, - { O, X, O, O, O, O, O, X, O }, - { O, X, X, X, X, X, X, X, O }, - { O, O, O, O, O, O, O, O, O } - }; + private static bool[,] PositionDetection { get; } = + new[,] + { + { O, O, O, O, O, O, O, O, O }, + { O, X, X, X, X, X, X, X, O }, + { O, X, O, O, O, O, O, X, O }, + { O, X, O, X, X, X, O, X, O }, + { O, X, O, X, X, X, O, X, O }, + { O, X, O, X, X, X, O, X, O }, + { O, X, O, O, O, O, O, X, O }, + { O, X, X, X, X, X, X, X, O }, + { O, O, O, O, O, O, O, O, O } + }; - public override bool[,] Stencil => PositionDetection; + public override bool[,] Stencil => PositionDetection; - public override void ApplyTo(TriStateMatrix matrix) - { - MatrixSize size = GetSizeOfSquareWithSeparators(); + public override void ApplyTo(TriStateMatrix matrix) + { + MatrixSize size = GetSizeOfSquareWithSeparators(); - MatrixPoint leftTopCorner = new(0, 0); - CopyTo(matrix, new MatrixRectangle(new MatrixPoint(1, 1), size), leftTopCorner, MatrixStatus.NoMask); + MatrixPoint leftTopCorner = new(0, 0); + CopyTo(matrix, new MatrixRectangle(new MatrixPoint(1, 1), size), leftTopCorner, MatrixStatus.NoMask); - MatrixPoint rightTopCorner = new(matrix.Width - Width + 1, 0); - CopyTo(matrix, new MatrixRectangle(new MatrixPoint(0, 1), size), rightTopCorner, MatrixStatus.NoMask); + MatrixPoint rightTopCorner = new(matrix.Width - Width + 1, 0); + CopyTo(matrix, new MatrixRectangle(new MatrixPoint(0, 1), size), rightTopCorner, MatrixStatus.NoMask); - MatrixPoint leftBottomCorner = new(0, matrix.Width - Width + 1); - CopyTo(matrix, new MatrixRectangle(new MatrixPoint(1, 0), size), leftBottomCorner, MatrixStatus.NoMask); - } + MatrixPoint leftBottomCorner = new(0, matrix.Width - Width + 1); + CopyTo(matrix, new MatrixRectangle(new MatrixPoint(1, 0), size), leftBottomCorner, MatrixStatus.NoMask); + } - private MatrixSize GetSizeOfSquareWithSeparators() => new(Width - 1, Height - 1); -} + private MatrixSize GetSizeOfSquareWithSeparators() => new(Width - 1, Height - 1); +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/TimingPattern.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/TimingPattern.cs index 1cb2dd0..1847fe6 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/TimingPattern.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/TimingPattern.cs @@ -1,35 +1,33 @@ -using System; - namespace Gma.QrCodeNet.Encoding.Positioning.Stencils; internal class TimingPattern : PatternStencilBase { - public TimingPattern(int version) - : base(version) - { - } + public TimingPattern(int version) + : base(version) + { + } - public override bool[,] Stencil => throw new NotImplementedException(); + public override bool[,] Stencil => throw new NotImplementedException(); - public override void ApplyTo(TriStateMatrix matrix) - { - // -8 is for skipping position detection patterns (size 7), and two horizontal/vertical - // separation patterns (size 1). Thus, 8 = 7 + 1. - for (int i = 8; i < matrix.Width - 8; ++i) - { - bool value = (sbyte)((i + 1) % 2) == 1; + public override void ApplyTo(TriStateMatrix matrix) + { + // -8 is for skipping position detection patterns (size 7), and two horizontal/vertical + // separation patterns (size 1). Thus, 8 = 7 + 1. + for (int i = 8; i < matrix.Width - 8; ++i) + { + bool value = (sbyte)((i + 1) % 2) == 1; - // Horizontal line. - if (matrix.MStatus(6, i) == MatrixStatus.None) - { - matrix[6, i, MatrixStatus.NoMask] = value; - } + // Horizontal line. + if (matrix.MStatus(6, i) == MatrixStatus.None) + { + matrix[6, i, MatrixStatus.NoMask] = value; + } - // Vertical line. - if (matrix.MStatus(i, 6) == MatrixStatus.None) - { - matrix[i, 6, MatrixStatus.NoMask] = value; - } - } - } -} + // Vertical line. + if (matrix.MStatus(i, 6) == MatrixStatus.None) + { + matrix[i, 6, MatrixStatus.NoMask] = value; + } + } + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QRCodeConstantVariable.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QRCodeConstantVariable.cs index ca1518b..d2932cd 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QRCodeConstantVariable.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QRCodeConstantVariable.cs @@ -5,48 +5,48 @@ namespace Gma.QrCodeNet.Encoding; /// public static class QRCodeConstantVariable { - public const int MinVersion = 1; - public const int MaxVersion = 40; + public const int MinVersion = 1; + public const int MaxVersion = 40; - public const string DefaultEncoding = "iso-8859-1"; - public const string UTF8Encoding = "utf-8"; + public const string DefaultEncoding = "iso-8859-1"; + public const string UTF8Encoding = "utf-8"; - /// - /// ISO/IEC 18004:2006(E) Page 45 Chapter Generating the error correction codewords - /// Primative Polynomial = Bin 100011101 = Dec 285 - /// - public const int QRCodePrimitive = 285; + /// + /// ISO/IEC 18004:2006(E) Page 45 Chapter Generating the error correction codewords + /// Primative Polynomial = Bin 100011101 = Dec 285 + /// + public const int QRCodePrimitive = 285; - internal const int TerminatorNPaddingBit = 0; + internal const int TerminatorNPaddingBit = 0; - internal const int TerminatorLength = 4; + internal const int TerminatorLength = 4; - /// - /// 0xEC - /// - internal const int PadeCodewordsOdd = 0xec; + /// + /// 0xEC + /// + internal const int PadeCodewordsOdd = 0xec; - /// - /// 0x11 - /// - internal const int PadeCodewordsEven = 0x11; + /// + /// 0x11 + /// + internal const int PadeCodewordsEven = 0x11; - internal const int PositionStencilWidth = 7; + internal const int PositionStencilWidth = 7; - internal static bool[] PadeOdd = new bool[] - { - true, true, true, false, - true, true, false, false - }; + internal static bool[] PadeOdd = new bool[] + { + true, true, true, false, + true, true, false, false + }; - internal static bool[] PadeEven = new bool[] - { - false, false, false, true, - false, false, false, true - }; + internal static bool[] PadeEven = new bool[] + { + false, false, false, true, + false, false, false, true + }; - /// - /// URL:http://en.wikipedia.org/wiki/Byte-order_mark - /// - public static byte[] UTF8ByteOrderMark => new byte[] { 0xEF, 0xBB, 0xBF }; -} + /// + /// URL:http://en.wikipedia.org/wiki/Byte-order_mark + /// + public static byte[] UTF8ByteOrderMark => new byte[] { 0xEF, 0xBB, 0xBF }; +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QRCodeEncode.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QRCodeEncode.cs index 9ad782c..b353867 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QRCodeEncode.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QRCodeEncode.cs @@ -9,24 +9,24 @@ namespace Gma.QrCodeNet.Encoding; internal static class QRCodeEncode { - internal static BitMatrix Encode(string content, ErrorCorrectionLevel errorLevel) - { - EncodationStruct encodeStruct = DataEncode.Encode(content, errorLevel); + internal static BitMatrix Encode(string content, ErrorCorrectionLevel errorLevel) + { + EncodationStruct encodeStruct = DataEncode.Encode(content, errorLevel); - return ProcessEncodationResult(encodeStruct, errorLevel); - } + return ProcessEncodationResult(encodeStruct, errorLevel); + } - private static BitMatrix ProcessEncodationResult(EncodationStruct encodeStruct, ErrorCorrectionLevel errorLevel) - { - BitList codewords = ECGenerator.FillECCodewords(encodeStruct.DataCodewords, encodeStruct.VersionDetail); + private static BitMatrix ProcessEncodationResult(EncodationStruct encodeStruct, ErrorCorrectionLevel errorLevel) + { + BitList codewords = ECGenerator.FillECCodewords(encodeStruct.DataCodewords, encodeStruct.VersionDetail); - TriStateMatrix triMatrix = new(encodeStruct.VersionDetail.MatrixWidth); - PositioningPatternBuilder.EmbedBasicPatterns(encodeStruct.VersionDetail.Version, triMatrix); + TriStateMatrix triMatrix = new(encodeStruct.VersionDetail.MatrixWidth); + PositioningPatternBuilder.EmbedBasicPatterns(encodeStruct.VersionDetail.Version, triMatrix); - triMatrix.EmbedVersionInformation(encodeStruct.VersionDetail.Version); - triMatrix.EmbedFormatInformation(errorLevel, new Pattern0()); - triMatrix.TryEmbedCodewords(codewords); + triMatrix.EmbedVersionInformation(encodeStruct.VersionDetail.Version); + triMatrix.EmbedFormatInformation(errorLevel, new Pattern0()); + triMatrix.TryEmbedCodewords(codewords); - return triMatrix.GetLowestPenaltyMatrix(errorLevel); - } -} + return triMatrix.GetLowestPenaltyMatrix(errorLevel); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QrCode.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QrCode.cs index de932c6..bcf4e26 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QrCode.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QrCode.cs @@ -8,21 +8,21 @@ namespace Gma.QrCodeNet.Encoding; /// public class QrCode { - internal QrCode(BitMatrix matrix) - { - Matrix = matrix; - IsContainMatrix = true; - } + internal QrCode(BitMatrix matrix) + { + Matrix = matrix; + IsContainMatrix = true; + } - public bool IsContainMatrix - { - get; - private set; - } + public bool IsContainMatrix + { + get; + private set; + } - public BitMatrix Matrix - { - get; - private set; - } -} + public BitMatrix Matrix + { + get; + private set; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QrEncoder.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QrEncoder.cs index c40d951..f78c155 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QrEncoder.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QrEncoder.cs @@ -2,38 +2,38 @@ namespace Gma.QrCodeNet.Encoding; public class QrEncoder { - /// - /// Default QrEncoder will set ErrorCorrectionLevel as M - /// - public QrEncoder() - : this(ErrorCorrectionLevel.M) - { - } + /// + /// Default QrEncoder will set ErrorCorrectionLevel as M + /// + public QrEncoder() + : this(ErrorCorrectionLevel.M) + { + } - /// - /// QrEncoder with parameter ErrorCorrectionLevel. - /// - public QrEncoder(ErrorCorrectionLevel errorCorrectionLevel) - { - ErrorCorrectionLevel = errorCorrectionLevel; - } + /// + /// QrEncoder with parameter ErrorCorrectionLevel. + /// + public QrEncoder(ErrorCorrectionLevel errorCorrectionLevel) + { + ErrorCorrectionLevel = errorCorrectionLevel; + } - public ErrorCorrectionLevel ErrorCorrectionLevel { get; set; } + public ErrorCorrectionLevel ErrorCorrectionLevel { get; set; } - /// - /// Encode string content to QrCode matrix - /// - /// - /// This exception for string content is null, empty or too large - public QrCode Encode(string content) - { - if (string.IsNullOrEmpty(content)) - { - throw new InputOutOfBoundaryException("Input cannot be null or empty."); - } - else - { - return new QrCode(QRCodeEncode.Encode(content, ErrorCorrectionLevel)); - } - } -} + /// + /// Encode string content to QrCode matrix + /// + /// + /// This exception for string content is null, empty or too large + public QrCode Encode(string content) + { + if (string.IsNullOrEmpty(content)) + { + throw new InputOutOfBoundaryException("Input cannot be null or empty."); + } + else + { + return new QrCode(QRCodeEncode.Encode(content, ErrorCorrectionLevel)); + } + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/GaloisField256.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/GaloisField256.cs index 7ab528f..52324a9 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/GaloisField256.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/GaloisField256.cs @@ -1,5 +1,3 @@ -using System; - namespace Gma.QrCodeNet.Encoding.ReedSolomon; /// @@ -7,116 +5,116 @@ namespace Gma.QrCodeNet.Encoding.ReedSolomon; /// internal sealed class GaloisField256 { - internal GaloisField256(int primitive) - { - AntiLogTable = new int[256]; - LogTable = new int[256]; + internal GaloisField256(int primitive) + { + AntiLogTable = new int[256]; + LogTable = new int[256]; - Primitive = primitive; + Primitive = primitive; - int gfx = 1; + int gfx = 1; - // Power cycle is from 0 to 254. 2^255 = 1 = 2^0 - // Value cycle is from 1 to 255. Thus there should not have Log(0). - for (int powers = 0; powers < 256; powers++) - { - AntiLogTable[powers] = gfx; - if (powers != 255) - { - LogTable[gfx] = powers; - } + // Power cycle is from 0 to 254. 2^255 = 1 = 2^0 + // Value cycle is from 1 to 255. Thus there should not have Log(0). + for (int powers = 0; powers < 256; powers++) + { + AntiLogTable[powers] = gfx; + if (powers != 255) + { + LogTable[gfx] = powers; + } - gfx <<= 1; // gfx = gfx * 2 where alpha is 2. + gfx <<= 1; // gfx = gfx * 2 where alpha is 2. - if (gfx > 255) - { - gfx ^= primitive; - } - } - } + if (gfx > 255) + { + gfx ^= primitive; + } + } + } - private int[] AntiLogTable { get; } - private int[] LogTable { get; } + private int[] AntiLogTable { get; } + private int[] LogTable { get; } - internal int Primitive { get; } + internal int Primitive { get; } - internal static GaloisField256 QRCodeGaloisField => new(QRCodeConstantVariable.QRCodePrimitive); + internal static GaloisField256 QRCodeGaloisField => new(QRCodeConstantVariable.QRCodePrimitive); - /// - /// Powers of a in GF table. Where a = 2 - /// - internal int Exponent(int powersOfa) => AntiLogTable[powersOfa]; + /// + /// Powers of a in GF table. Where a = 2 + /// + internal int Exponent(int powersOfa) => AntiLogTable[powersOfa]; - /// - /// Log (power of a) in GF table. Where a = 2 - /// - internal int Log(int gfValue) - { - if (gfValue == 0) - { - throw new ArgumentException("GaloisField value will not be equal to 0, Log method."); - } + /// + /// Log (power of a) in GF table. Where a = 2 + /// + internal int Log(int gfValue) + { + if (gfValue == 0) + { + throw new ArgumentException("GaloisField value will not be equal to 0, Log method."); + } - return LogTable[gfValue]; - } + return LogTable[gfValue]; + } - internal int Inverse(int gfValue) - { - if (gfValue == 0) - { - throw new ArgumentException("GaloisField value will not be equal to 0, Inverse method."); - } + internal int Inverse(int gfValue) + { + if (gfValue == 0) + { + throw new ArgumentException("GaloisField value will not be equal to 0, Inverse method."); + } - return Exponent(255 - Log(gfValue)); - } + return Exponent(255 - Log(gfValue)); + } - internal int Addition(int gfValueA, int gfValueB) => gfValueA ^ gfValueB; + internal int Addition(int gfValueA, int gfValueB) => gfValueA ^ gfValueB; - internal int Subtraction(int gfValueA, int gfValueB) => Addition(gfValueA, gfValueB); // Subtraction is same as addition. + internal int Subtraction(int gfValueA, int gfValueB) => Addition(gfValueA, gfValueB); // Subtraction is same as addition. - /// - /// Product of two values. - /// In other words. a multiply b - /// - internal int Product(int gfValueA, int gfValueB) - { - if (gfValueA == 0 || gfValueB == 0) - { - return 0; - } - if (gfValueA == 1) - { - return gfValueB; - } - if (gfValueB == 1) - { - return gfValueA; - } + /// + /// Product of two values. + /// In other words. a multiply b + /// + internal int Product(int gfValueA, int gfValueB) + { + if (gfValueA == 0 || gfValueB == 0) + { + return 0; + } + if (gfValueA == 1) + { + return gfValueB; + } + if (gfValueB == 1) + { + return gfValueA; + } - return Exponent((Log(gfValueA) + Log(gfValueB)) % 255); - } + return Exponent((Log(gfValueA) + Log(gfValueB)) % 255); + } - /// - /// Quotient of two values. - /// In other words. a divided b - /// - internal int Quotient(int gfValueA, int gfValueB) - { - if (gfValueA == 0) - { - return 0; - } + /// + /// Quotient of two values. + /// In other words. a divided b + /// + internal int Quotient(int gfValueA, int gfValueB) + { + if (gfValueA == 0) + { + return 0; + } - if (gfValueB == 0) - { - throw new ArgumentException($"{nameof(gfValueB)} cannot be zero."); - } + if (gfValueB == 0) + { + throw new ArgumentException($"{nameof(gfValueB)} cannot be zero."); + } - if (gfValueB == 1) - { - return gfValueA; - } + if (gfValueB == 1) + { + return gfValueA; + } - return Exponent(Math.Abs(Log(gfValueA) - Log(gfValueB)) % 255); - } -} + return Exponent(Math.Abs(Log(gfValueA) - Log(gfValueB)) % 255); + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/GeneratorPolynomial.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/GeneratorPolynomial.cs index 94619a8..61036e9 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/GeneratorPolynomial.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/GeneratorPolynomial.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; - namespace Gma.QrCodeNet.Encoding.ReedSolomon; /// @@ -7,56 +5,56 @@ namespace Gma.QrCodeNet.Encoding.ReedSolomon; /// internal sealed class GeneratorPolynomial { - /// - /// After create GeneratorPolynomial. Keep it as long as possible. - /// Unless QRCode encode is done or no more QRCode need to generate. - /// - internal GeneratorPolynomial(GaloisField256 gfield) - { - Gfield = gfield; - CacheGenerator = new List(10) - { - new Polynomial(Gfield, new int[] { 1 }) - }; - } + /// + /// After create GeneratorPolynomial. Keep it as long as possible. + /// Unless QRCode encode is done or no more QRCode need to generate. + /// + internal GeneratorPolynomial(GaloisField256 gfield) + { + Gfield = gfield; + CacheGenerator = new List(10) + { + new Polynomial(Gfield, new int[] { 1 }) + }; + } - private GaloisField256 Gfield { get; } + private GaloisField256 Gfield { get; } - private List CacheGenerator { get; } + private List CacheGenerator { get; } - /// - /// Get generator by degree. (Largest degree for that generator) - /// - /// Generator - internal Polynomial GetGenerator(int degree) - { - if (degree >= CacheGenerator.Count) - { - BuildGenerator(degree); - } + /// + /// Get generator by degree. (Largest degree for that generator) + /// + /// Generator + internal Polynomial GetGenerator(int degree) + { + if (degree >= CacheGenerator.Count) + { + BuildGenerator(degree); + } - return CacheGenerator[degree]; - } + return CacheGenerator[degree]; + } - /// - /// Build Generator if we cannot find specific degree of generator from cache - /// - private void BuildGenerator(int degree) - { - lock (CacheGenerator) - { - int currentCacheLength = CacheGenerator.Count; - if (degree >= currentCacheLength) - { - Polynomial lastGenerator = CacheGenerator[currentCacheLength - 1]; + /// + /// Build Generator if we cannot find specific degree of generator from cache + /// + private void BuildGenerator(int degree) + { + lock (CacheGenerator) + { + int currentCacheLength = CacheGenerator.Count; + if (degree >= currentCacheLength) + { + Polynomial lastGenerator = CacheGenerator[currentCacheLength - 1]; - for (int d = currentCacheLength; d <= degree; d++) - { - Polynomial nextGenerator = lastGenerator.Multiply(new Polynomial(Gfield, new int[] { 1, Gfield.Exponent(d - 1) })); - CacheGenerator.Add(nextGenerator); - lastGenerator = nextGenerator; - } - } - } - } -} + for (int d = currentCacheLength; d <= degree; d++) + { + Polynomial nextGenerator = lastGenerator.Multiply(new Polynomial(Gfield, new int[] { 1, Gfield.Exponent(d - 1) })); + CacheGenerator.Add(nextGenerator); + lastGenerator = nextGenerator; + } + } + } + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/PolyDivideStruct.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/PolyDivideStruct.cs index 2619030..7b20bbd 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/PolyDivideStruct.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/PolyDivideStruct.cs @@ -2,14 +2,14 @@ namespace Gma.QrCodeNet.Encoding.ReedSolomon; internal struct PolyDivideStruct { - internal PolyDivideStruct(Polynomial quotient, Polynomial remainder) - : this() - { - Quotient = quotient; - Remainder = remainder; - } + internal PolyDivideStruct(Polynomial quotient, Polynomial remainder) + : this() + { + Quotient = quotient; + Remainder = remainder; + } - internal Polynomial Quotient { get; private set; } + internal Polynomial Quotient { get; private set; } - internal Polynomial Remainder { get; private set; } -} + internal Polynomial Remainder { get; private set; } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/Polynomial.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/Polynomial.cs index c0daaa5..ad70fad 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/Polynomial.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/Polynomial.cs @@ -1,242 +1,240 @@ -using System; - namespace Gma.QrCodeNet.Encoding.ReedSolomon; internal sealed class Polynomial { - internal Polynomial(GaloisField256 gfield, int[] coefficients) - { - int coefficientsLength = coefficients.Length; + internal Polynomial(GaloisField256 gfield, int[] coefficients) + { + int coefficientsLength = coefficients.Length; - if (coefficientsLength == 0 || coefficients is null) - { - throw new ArithmeticException($"Cannot create empty {nameof(Polynomial)}."); - } + if (coefficientsLength == 0 || coefficients is null) + { + throw new ArithmeticException($"Cannot create empty {nameof(Polynomial)}."); + } - GField = gfield; + GField = gfield; - Primitive = gfield.Primitive; + Primitive = gfield.Primitive; - if (coefficientsLength > 1 && coefficients[0] == 0) - { - int firstNonZeroIndex = 1; - while (firstNonZeroIndex < coefficientsLength && coefficients[firstNonZeroIndex] == 0) - { - firstNonZeroIndex++; - } + if (coefficientsLength > 1 && coefficients[0] == 0) + { + int firstNonZeroIndex = 1; + while (firstNonZeroIndex < coefficientsLength && coefficients[firstNonZeroIndex] == 0) + { + firstNonZeroIndex++; + } - if (firstNonZeroIndex == coefficientsLength) - { - Coefficients = new int[] { 0 }; - } - else - { - int newLength = coefficientsLength - firstNonZeroIndex; - Coefficients = new int[newLength]; - Array.Copy(coefficients, firstNonZeroIndex, Coefficients, 0, newLength); - } - } - else - { - Coefficients = new int[coefficientsLength]; - Array.Copy(coefficients, Coefficients, coefficientsLength); - } - } + if (firstNonZeroIndex == coefficientsLength) + { + Coefficients = new int[] { 0 }; + } + else + { + int newLength = coefficientsLength - firstNonZeroIndex; + Coefficients = new int[newLength]; + Array.Copy(coefficients, firstNonZeroIndex, Coefficients, 0, newLength); + } + } + else + { + Coefficients = new int[coefficientsLength]; + Array.Copy(coefficients, Coefficients, coefficientsLength); + } + } - internal int[] Coefficients { get; } + internal int[] Coefficients { get; } - internal GaloisField256 GField { get; } + internal GaloisField256 GField { get; } - internal int Degree => Coefficients.Length - 1; + internal int Degree => Coefficients.Length - 1; - internal int Primitive { get; } + internal int Primitive { get; } - internal bool IsMonomialZero => Coefficients[0] == 0; + internal bool IsMonomialZero => Coefficients[0] == 0; - /// - /// Coefficient position. where (coefficient)x^degree - /// - internal int GetCoefficient(int degree) - { - // Eg: x^2 + x + 1. degree 1, reverse position = degree + 1 = 2. - // Pos = 3 - 2 = 1 - return Coefficients[^(degree + 1)]; - } + /// + /// Coefficient position. where (coefficient)x^degree + /// + internal int GetCoefficient(int degree) + { + // Eg: x^2 + x + 1. degree 1, reverse position = degree + 1 = 2. + // Pos = 3 - 2 = 1 + return Coefficients[^(degree + 1)]; + } - /// - /// Add another Polynomial to current one - /// - /// The polynomial need to add or subtract to current one - /// Result polynomial after add or subtract - internal Polynomial AddOrSubtract(Polynomial other) - { - if (Primitive != other.Primitive) - { - throw new ArgumentException($"{nameof(Polynomial)} cannot perform {nameof(AddOrSubtract)} as they do not have the same {nameof(Primitive)}" + - $" for {nameof(GaloisField256)}."); - } - if (IsMonomialZero) - { - return other; - } - else if (other.IsMonomialZero) - { - return this; - } + /// + /// Add another Polynomial to current one + /// + /// The polynomial need to add or subtract to current one + /// Result polynomial after add or subtract + internal Polynomial AddOrSubtract(Polynomial other) + { + if (Primitive != other.Primitive) + { + throw new ArgumentException($"{nameof(Polynomial)} cannot perform {nameof(AddOrSubtract)} as they do not have the same {nameof(Primitive)}" + + $" for {nameof(GaloisField256)}."); + } + if (IsMonomialZero) + { + return other; + } + else if (other.IsMonomialZero) + { + return this; + } - int otherLength = other.Coefficients.Length; - int thisLength = Coefficients.Length; + int otherLength = other.Coefficients.Length; + int thisLength = Coefficients.Length; - if (otherLength > thisLength) - { - return CoefficientXor(Coefficients, other.Coefficients); - } - else - { - return CoefficientXor(other.Coefficients, Coefficients); - } - } + if (otherLength > thisLength) + { + return CoefficientXor(Coefficients, other.Coefficients); + } + else + { + return CoefficientXor(other.Coefficients, Coefficients); + } + } - internal Polynomial CoefficientXor(int[] smallerCoefficients, int[] largerCoefficients) - { - if (smallerCoefficients.Length > largerCoefficients.Length) - { - throw new ArgumentException($"Cannot perform {nameof(CoefficientXor)} method as smaller {nameof(Coefficients)} length is greater than the larger one."); - } + internal Polynomial CoefficientXor(int[] smallerCoefficients, int[] largerCoefficients) + { + if (smallerCoefficients.Length > largerCoefficients.Length) + { + throw new ArgumentException($"Cannot perform {nameof(CoefficientXor)} method as smaller {nameof(Coefficients)} length is greater than the larger one."); + } - int targetLength = largerCoefficients.Length; - int[] xorCoefficient = new int[targetLength]; - int lengthDiff = largerCoefficients.Length - smallerCoefficients.Length; + int targetLength = largerCoefficients.Length; + int[] xorCoefficient = new int[targetLength]; + int lengthDiff = largerCoefficients.Length - smallerCoefficients.Length; - Array.Copy(largerCoefficients, 0, xorCoefficient, 0, lengthDiff); + Array.Copy(largerCoefficients, 0, xorCoefficient, 0, lengthDiff); - for (int index = lengthDiff; index < targetLength; index++) - { - xorCoefficient[index] = GField.Addition(largerCoefficients[index], smallerCoefficients[index - lengthDiff]); - } + for (int index = lengthDiff; index < targetLength; index++) + { + xorCoefficient[index] = GField.Addition(largerCoefficients[index], smallerCoefficients[index - lengthDiff]); + } - return new Polynomial(GField, xorCoefficient); - } + return new Polynomial(GField, xorCoefficient); + } - /// - /// Multiply current Polynomial to another one. - /// - /// Result polynomial after multiply - internal Polynomial Multiply(Polynomial other) - { - if (Primitive != other.Primitive) - { - throw new ArgumentException($"{nameof(Polynomial)} cannot perform {nameof(Multiply)} as they do not have the same {nameof(Primitive)}" + - $" for {nameof(GaloisField256)}."); - } - if (IsMonomialZero || other.IsMonomialZero) - { - return new Polynomial(GField, new int[] { 0 }); - } + /// + /// Multiply current Polynomial to another one. + /// + /// Result polynomial after multiply + internal Polynomial Multiply(Polynomial other) + { + if (Primitive != other.Primitive) + { + throw new ArgumentException($"{nameof(Polynomial)} cannot perform {nameof(Multiply)} as they do not have the same {nameof(Primitive)}" + + $" for {nameof(GaloisField256)}."); + } + if (IsMonomialZero || other.IsMonomialZero) + { + return new Polynomial(GField, new int[] { 0 }); + } - int[] aCoefficients = Coefficients; - int aLength = aCoefficients.Length; - int[] bCoefficient = other.Coefficients; - int bLength = bCoefficient.Length; - int[] rCoefficients = new int[aLength + bLength - 1]; + int[] aCoefficients = Coefficients; + int aLength = aCoefficients.Length; + int[] bCoefficient = other.Coefficients; + int bLength = bCoefficient.Length; + int[] rCoefficients = new int[aLength + bLength - 1]; - for (int aIndex = 0; aIndex < aLength; aIndex++) - { - int aCoeff = aCoefficients[aIndex]; - for (int bIndex = 0; bIndex < bLength; bIndex++) - { - rCoefficients[aIndex + bIndex] = - GField.Addition(rCoefficients[aIndex + bIndex], GField.Product(aCoeff, bCoefficient[bIndex])); - } - } - return new Polynomial(GField, rCoefficients); - } + for (int aIndex = 0; aIndex < aLength; aIndex++) + { + int aCoeff = aCoefficients[aIndex]; + for (int bIndex = 0; bIndex < bLength; bIndex++) + { + rCoefficients[aIndex + bIndex] = + GField.Addition(rCoefficients[aIndex + bIndex], GField.Product(aCoeff, bCoefficient[bIndex])); + } + } + return new Polynomial(GField, rCoefficients); + } - /// - /// Multiplay scalar to current polynomial - /// - /// Result of polynomial after multiply scalar - internal Polynomial MultiplyScalar(int scalar) - { - if (scalar == 0) - { - return new Polynomial(GField, new int[] { 0 }); - } - else if (scalar == 1) - { - return this; - } + /// + /// Multiplay scalar to current polynomial + /// + /// Result of polynomial after multiply scalar + internal Polynomial MultiplyScalar(int scalar) + { + if (scalar == 0) + { + return new Polynomial(GField, new int[] { 0 }); + } + else if (scalar == 1) + { + return this; + } - int length = Coefficients.Length; - int[] rCoefficient = new int[length]; + int length = Coefficients.Length; + int[] rCoefficient = new int[length]; - for (int index = 0; index < length; index++) - { - rCoefficient[index] = GField.Product(Coefficients[index], scalar); - } + for (int index = 0; index < length; index++) + { + rCoefficient[index] = GField.Product(Coefficients[index], scalar); + } - return new Polynomial(GField, rCoefficient); - } + return new Polynomial(GField, rCoefficient); + } - /// - /// Divide current polynomial by "other" - /// - /// Result polynomial after divide - internal PolyDivideStruct Divide(Polynomial other) - { - if (Primitive != other.Primitive) - { - throw new ArgumentException($"{nameof(Polynomial)} cannot perform {nameof(Divide)} as they do not have the same {nameof(Primitive)}" + - $" for {nameof(GaloisField256)}."); - } - if (other.IsMonomialZero) - { - throw new ArgumentException($"Cannot divide by {nameof(Polynomial)} Zero."); - } + /// + /// Divide current polynomial by "other" + /// + /// Result polynomial after divide + internal PolyDivideStruct Divide(Polynomial other) + { + if (Primitive != other.Primitive) + { + throw new ArgumentException($"{nameof(Polynomial)} cannot perform {nameof(Divide)} as they do not have the same {nameof(Primitive)}" + + $" for {nameof(GaloisField256)}."); + } + if (other.IsMonomialZero) + { + throw new ArgumentException($"Cannot divide by {nameof(Polynomial)} Zero."); + } - // This divide by other = a divide by b - int aLength = Coefficients.Length; + // This divide by other = a divide by b + int aLength = Coefficients.Length; - // We will make change to aCoefficient. It will return as remainder - int[] aCoefficients = new int[aLength]; - Array.Copy(Coefficients, 0, aCoefficients, 0, aLength); + // We will make change to aCoefficient. It will return as remainder + int[] aCoefficients = new int[aLength]; + Array.Copy(Coefficients, 0, aCoefficients, 0, aLength); - int bLength = other.Coefficients.Length; + int bLength = other.Coefficients.Length; - if (aLength < bLength) - { - return new PolyDivideStruct(new Polynomial(GField, new int[] { 0 }), this); - } - else - { - // Quotient coefficients - // qLastIndex = alength - blength qlength = qLastIndex + 1 - int[] qCoefficients = new int[(aLength - bLength) + 1]; + if (aLength < bLength) + { + return new PolyDivideStruct(new Polynomial(GField, new int[] { 0 }), this); + } + else + { + // Quotient coefficients + // qLastIndex = alength - blength qlength = qLastIndex + 1 + int[] qCoefficients = new int[(aLength - bLength) + 1]; - // Denominator - int otherLeadingTerm = other.GetCoefficient(other.Degree); - int inverseOtherLeadingTerm = GField.Inverse(otherLeadingTerm); + // Denominator + int otherLeadingTerm = other.GetCoefficient(other.Degree); + int inverseOtherLeadingTerm = GField.Inverse(otherLeadingTerm); - for (int aIndex = 0; aIndex <= aLength - bLength; aIndex++) - { - if (aCoefficients[aIndex] != 0) - { - int aScalar = GField.Product(inverseOtherLeadingTerm, aCoefficients[aIndex]); - Polynomial term = other.MultiplyScalar(aScalar); - qCoefficients[aIndex] = aScalar; + for (int aIndex = 0; aIndex <= aLength - bLength; aIndex++) + { + if (aCoefficients[aIndex] != 0) + { + int aScalar = GField.Product(inverseOtherLeadingTerm, aCoefficients[aIndex]); + Polynomial term = other.MultiplyScalar(aScalar); + qCoefficients[aIndex] = aScalar; - int[] bCoefficient = term.Coefficients; - if (bCoefficient[0] != 0) - { - for (int bIndex = 0; bIndex < bLength; bIndex++) - { - aCoefficients[aIndex + bIndex] = GField.Subtraction(aCoefficients[aIndex + bIndex], bCoefficient[bIndex]); - } - } - } - } + int[] bCoefficient = term.Coefficients; + if (bCoefficient[0] != 0) + { + for (int bIndex = 0; bIndex < bLength; bIndex++) + { + aCoefficients[aIndex + bIndex] = GField.Subtraction(aCoefficients[aIndex + bIndex], bCoefficient[bIndex]); + } + } + } + } - return new PolyDivideStruct(new Polynomial(GField, qCoefficients), new Polynomial(GField, aCoefficients)); - } - } -} + return new PolyDivideStruct(new Polynomial(GField, qCoefficients), new Polynomial(GField, aCoefficients)); + } + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/ReedSolomonEncoder.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/ReedSolomonEncoder.cs index 6bc47de..3aa8136 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/ReedSolomonEncoder.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/ReedSolomonEncoder.cs @@ -1,91 +1,89 @@ -using System; - namespace Gma.QrCodeNet.Encoding.ReedSolomon; internal sealed class ReedSolomonEncoder { - /// - /// Encode an array of data codeword with GaloisField 256. - /// - /// Array of data codewords for a single block. - /// Number of error correction codewords for data codewords - /// Cached or newly create GeneratorPolynomial - /// Return error correction codewords array - internal static byte[] Encode(byte[] dataBytes, int numECBytes, GeneratorPolynomial generatorPoly) - { - int dataLength = dataBytes.Length; + /// + /// Encode an array of data codeword with GaloisField 256. + /// + /// Array of data codewords for a single block. + /// Number of error correction codewords for data codewords + /// Cached or newly create GeneratorPolynomial + /// Return error correction codewords array + internal static byte[] Encode(byte[] dataBytes, int numECBytes, GeneratorPolynomial generatorPoly) + { + int dataLength = dataBytes.Length; if (generatorPoly == null) throw new ArgumentNullException(nameof(generatorPoly)); if (dataLength == 0) - { - throw new ArgumentException("There is no data bytes to encode."); - } + { + throw new ArgumentException("There is no data bytes to encode."); + } - if (numECBytes <= 0) - { - throw new ArgumentException("No Error Correction bytes."); - } + if (numECBytes <= 0) + { + throw new ArgumentException("No Error Correction bytes."); + } - int[] toEncode = ConvertToIntArray(dataBytes, dataLength, numECBytes); + int[] toEncode = ConvertToIntArray(dataBytes, dataLength, numECBytes); - Polynomial generator = generatorPoly.GetGenerator(numECBytes); + Polynomial generator = generatorPoly.GetGenerator(numECBytes); - Polynomial dataPoly = new(generator.GField, toEncode); + Polynomial dataPoly = new(generator.GField, toEncode); - PolyDivideStruct divideResult = dataPoly.Divide(generator); + PolyDivideStruct divideResult = dataPoly.Divide(generator); - int[] remainderCoeffs = divideResult.Remainder.Coefficients; + int[] remainderCoeffs = divideResult.Remainder.Coefficients; - return ConvertTosByteArray(remainderCoeffs, numECBytes); - } + return ConvertTosByteArray(remainderCoeffs, numECBytes); + } - /// - /// Convert data codewords to int array. And add error correction space at end of that array - /// - /// Data codewords array - /// Data codewords length - /// Num of error correction bytes - /// Int array for data codewords array follow by error correction space - private static int[] ConvertToIntArray(byte[] dataBytes, int dataLength, int numECBytes) - { - int[] resultArray = new int[dataLength + numECBytes]; + /// + /// Convert data codewords to int array. And add error correction space at end of that array + /// + /// Data codewords array + /// Data codewords length + /// Num of error correction bytes + /// Int array for data codewords array follow by error correction space + private static int[] ConvertToIntArray(byte[] dataBytes, int dataLength, int numECBytes) + { + int[] resultArray = new int[dataLength + numECBytes]; - for (int index = 0; index < dataLength; index++) - { - resultArray[index] = dataBytes[index] & 0xff; - } + for (int index = 0; index < dataLength; index++) + { + resultArray[index] = dataBytes[index] & 0xff; + } - return resultArray; - } + return resultArray; + } - /// - /// Reassembly error correction codewords. As Polynomial class will eliminate zero monomial at front. - /// - /// Remainder byte array after divide. - /// Error correction codewords length - /// Error correction codewords - private static byte[] ConvertTosByteArray(int[] remainder, int numECBytes) - { - int remainderLength = remainder.Length; - if (remainderLength > numECBytes) - { - throw new ArgumentException($"Num of {nameof(remainder)} bytes cannot be larger than {nameof(numECBytes)}."); - } + /// + /// Reassembly error correction codewords. As Polynomial class will eliminate zero monomial at front. + /// + /// Remainder byte array after divide. + /// Error correction codewords length + /// Error correction codewords + private static byte[] ConvertTosByteArray(int[] remainder, int numECBytes) + { + int remainderLength = remainder.Length; + if (remainderLength > numECBytes) + { + throw new ArgumentException($"Num of {nameof(remainder)} bytes cannot be larger than {nameof(numECBytes)}."); + } - int numZeroCoeffs = numECBytes - remainderLength; + int numZeroCoeffs = numECBytes - remainderLength; - byte[] resultArray = new byte[numECBytes]; - for (int index = 0; index < numZeroCoeffs; index++) - { - resultArray[index] = 0; - } + byte[] resultArray = new byte[numECBytes]; + for (int index = 0; index < numZeroCoeffs; index++) + { + resultArray[index] = 0; + } - for (int rIndex = 0; rIndex < remainderLength; rIndex++) - { - resultArray[numZeroCoeffs + rIndex] = (byte)remainder[rIndex]; - } + for (int rIndex = 0; rIndex < remainderLength; rIndex++) + { + resultArray[numZeroCoeffs + rIndex] = (byte)remainder[rIndex]; + } - return resultArray; - } -} + return resultArray; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/StateMatrix.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/StateMatrix.cs index 617e771..6378620 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/StateMatrix.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/StateMatrix.cs @@ -2,27 +2,27 @@ namespace Gma.QrCodeNet.Encoding; public sealed class StateMatrix { - public StateMatrix(int width) - { - Width = width; - MatrixStatus = new MatrixStatus[width, width]; - } + public StateMatrix(int width) + { + Width = width; + MatrixStatus = new MatrixStatus[width, width]; + } - private MatrixStatus[,] MatrixStatus { get; } + private MatrixStatus[,] MatrixStatus { get; } - public MatrixStatus this[int x, int y] - { - get => MatrixStatus[x, y]; - set => MatrixStatus[x, y] = value; - } + public MatrixStatus this[int x, int y] + { + get => MatrixStatus[x, y]; + set => MatrixStatus[x, y] = value; + } - internal MatrixStatus this[MatrixPoint point] - { - get => this[point.X, point.Y]; - set => this[point.X, point.Y] = value; - } + internal MatrixStatus this[MatrixPoint point] + { + get => this[point.X, point.Y]; + set => this[point.X, point.Y] = value; + } - public int Width { get; } + public int Width { get; } - public int Height => Width; -} + public int Height => Width; +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Terminate/Terminator.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Terminate/Terminator.cs index 2e9b9aa..586c8e8 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Terminate/Terminator.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Terminate/Terminator.cs @@ -1,73 +1,71 @@ -using System; - namespace Gma.QrCodeNet.Encoding.Terminate; internal static class Terminator { - private const int NumBitsForByte = 8; + private const int NumBitsForByte = 8; - /// - /// This method will create BitList that contains - /// terminator, padding and pad codewords for given datacodewords. - /// Use it to full fill the data codewords capacity. Thus avoid massive empty bits. - /// - /// ISO/IEC 18004:2006 P. 32 33. - /// Terminator / Bit stream to codeword conversion - /// Method will add terminator bits (Terminator, padding and padcodewords) at end of baseList - /// Num of bits for datacodewords without terminator - /// Total number of datacodewords for specific version. - /// Receive it under Version/VersionTable - internal static void TerminateBites(this BitList baseList, int dataCount, int numTotalDataCodewords) - { - int numTotalDataBits = numTotalDataCodewords << 3; - int numDataBits = dataCount; + /// + /// This method will create BitList that contains + /// terminator, padding and pad codewords for given datacodewords. + /// Use it to full fill the data codewords capacity. Thus avoid massive empty bits. + /// + /// ISO/IEC 18004:2006 P. 32 33. + /// Terminator / Bit stream to codeword conversion + /// Method will add terminator bits (Terminator, padding and padcodewords) at end of baseList + /// Num of bits for datacodewords without terminator + /// Total number of datacodewords for specific version. + /// Receive it under Version/VersionTable + internal static void TerminateBites(this BitList baseList, int dataCount, int numTotalDataCodewords) + { + int numTotalDataBits = numTotalDataCodewords << 3; + int numDataBits = dataCount; - int numFillerBits = numTotalDataBits - numDataBits; - int numBitsNeedForLastByte = numFillerBits & 0x7; - int numFillerBytes = numFillerBits >> 3; + int numFillerBits = numTotalDataBits - numDataBits; + int numBitsNeedForLastByte = numFillerBits & 0x7; + int numFillerBytes = numFillerBits >> 3; - // BitList result = new BitList(); - if (numBitsNeedForLastByte >= QRCodeConstantVariable.TerminatorLength) - { - baseList.TerminatorPadding(numBitsNeedForLastByte); - baseList.PadeCodewords(numFillerBytes); - } - else if (numFillerBytes == 0) - { - baseList.TerminatorPadding(numBitsNeedForLastByte); - } - else if (numFillerBytes > 0) - { - baseList.TerminatorPadding(numBitsNeedForLastByte + NumBitsForByte); - baseList.PadeCodewords(numFillerBytes - 1); - } + // BitList result = new BitList(); + if (numBitsNeedForLastByte >= QRCodeConstantVariable.TerminatorLength) + { + baseList.TerminatorPadding(numBitsNeedForLastByte); + baseList.PadeCodewords(numFillerBytes); + } + else if (numFillerBytes == 0) + { + baseList.TerminatorPadding(numBitsNeedForLastByte); + } + else if (numFillerBytes > 0) + { + baseList.TerminatorPadding(numBitsNeedForLastByte + NumBitsForByte); + baseList.PadeCodewords(numFillerBytes - 1); + } - if (baseList.Count != numTotalDataBits) - { - throw new ArgumentException( - $"Generate terminator and Padding fail. Num of bits need: {numFillerBytes}. Actual length: {baseList.Count - numDataBits}"); - } - } + if (baseList.Count != numTotalDataBits) + { + throw new ArgumentException( + $"Generate terminator and Padding fail. Num of bits need: {numFillerBytes}. Actual length: {baseList.Count - numDataBits}"); + } + } - private static void PadeCodewords(this BitList mainList, int numOfPadeCodewords) - { - if (numOfPadeCodewords < 0) - { - throw new ArgumentException("Num of pade codewords is less than Zero"); - } + private static void PadeCodewords(this BitList mainList, int numOfPadeCodewords) + { + if (numOfPadeCodewords < 0) + { + throw new ArgumentException("Num of pade codewords is less than Zero"); + } - for (int numOfP = 1; numOfP <= numOfPadeCodewords; numOfP++) - { - if (numOfP % 2 == 1) - { - mainList.Add(QRCodeConstantVariable.PadeCodewordsOdd, NumBitsForByte); - } - else - { - mainList.Add(QRCodeConstantVariable.PadeCodewordsEven, NumBitsForByte); - } - } - } + for (int numOfP = 1; numOfP <= numOfPadeCodewords; numOfP++) + { + if (numOfP % 2 == 1) + { + mainList.Add(QRCodeConstantVariable.PadeCodewordsOdd, NumBitsForByte); + } + else + { + mainList.Add(QRCodeConstantVariable.PadeCodewordsEven, NumBitsForByte); + } + } + } - private static void TerminatorPadding(this BitList mainList, int numBits) => mainList.Add(QRCodeConstantVariable.TerminatorNPaddingBit, numBits); -} + private static void TerminatorPadding(this BitList mainList, int numBits) => mainList.Add(QRCodeConstantVariable.TerminatorNPaddingBit, numBits); +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/TriStateMatrix.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/TriStateMatrix.cs index 584eb1e..d852c57 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/TriStateMatrix.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/TriStateMatrix.cs @@ -1,48 +1,46 @@ -using System; - namespace Gma.QrCodeNet.Encoding; public class TriStateMatrix : BitMatrixBase { - public TriStateMatrix(int width) : base(width, new bool[width, width]) - { - StateMatrix = new StateMatrix(width); - } + public TriStateMatrix(int width) : base(width, new bool[width, width]) + { + StateMatrix = new StateMatrix(width); + } - internal TriStateMatrix(bool[,] internalArray) : base(internalArray) - { - StateMatrix = new StateMatrix(internalArray.GetLength(0)); - } + internal TriStateMatrix(bool[,] internalArray) : base(internalArray) + { + StateMatrix = new StateMatrix(internalArray.GetLength(0)); + } - private StateMatrix StateMatrix { get; } + private StateMatrix StateMatrix { get; } - public override bool this[int i, int j] - { - get => InternalArray[i, j]; - set - { - if (MStatus(i, j) is MatrixStatus.None or MatrixStatus.NoMask) - { - throw new InvalidOperationException($"The value of cell [{i}, {j}] is not set or is Stencil."); - } - InternalArray[i, j] = value; - } - } + public override bool this[int i, int j] + { + get => InternalArray[i, j]; + set + { + if (MStatus(i, j) is MatrixStatus.None or MatrixStatus.NoMask) + { + throw new InvalidOperationException($"The value of cell [{i}, {j}] is not set or is Stencil."); + } + InternalArray[i, j] = value; + } + } - public bool this[int i, int j, MatrixStatus mstatus] - { - set - { - StateMatrix[i, j] = mstatus; - InternalArray[i, j] = value; - } - } + public bool this[int i, int j, MatrixStatus mstatus] + { + set + { + StateMatrix[i, j] = mstatus; + InternalArray[i, j] = value; + } + } - public override int Height => Width; + public override int Height => Width; - public override int Width => base.Width; + public override int Width => base.Width; - internal MatrixStatus MStatus(int i, int j) => StateMatrix[i, j]; + internal MatrixStatus MStatus(int i, int j) => StateMatrix[i, j]; - internal MatrixStatus MStatus(MatrixPoint point) => MStatus(point.X, point.Y); -} + internal MatrixStatus MStatus(MatrixPoint point) => MStatus(point.X, point.Y); +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/VersionDetail.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/VersionDetail.cs index f7f66e4..efb108a 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/VersionDetail.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/VersionDetail.cs @@ -2,33 +2,33 @@ namespace Gma.QrCodeNet.Encoding; public struct VersionDetail { - internal VersionDetail(int version, int numTotalBytes, int numDataBytes, int numECBlocks) - : this() - { - Version = version; - NumTotalBytes = numTotalBytes; - NumDataBytes = numDataBytes; - NumECBlocks = numECBlocks; - } + internal VersionDetail(int version, int numTotalBytes, int numDataBytes, int numECBlocks) + : this() + { + Version = version; + NumTotalBytes = numTotalBytes; + NumDataBytes = numDataBytes; + NumECBlocks = numECBlocks; + } - internal int Version { get; private set; } - internal int NumTotalBytes { get; private set; } - internal int NumDataBytes { get; private set; } - internal int NumECBlocks { get; private set; } + internal int Version { get; private set; } + internal int NumTotalBytes { get; private set; } + internal int NumDataBytes { get; private set; } + internal int NumECBlocks { get; private set; } - internal int MatrixWidth => Width(Version); + internal int MatrixWidth => Width(Version); - internal int ECBlockGroup1 => NumECBlocks - ECBlockGroup2; + internal int ECBlockGroup1 => NumECBlocks - ECBlockGroup2; - internal int ECBlockGroup2 => NumTotalBytes % NumECBlocks; + internal int ECBlockGroup2 => NumTotalBytes % NumECBlocks; - internal int NumDataBytesGroup1 => NumDataBytes / NumECBlocks; + internal int NumDataBytesGroup1 => NumDataBytes / NumECBlocks; - internal int NumDataBytesGroup2 => NumDataBytesGroup1 + 1; + internal int NumDataBytesGroup2 => NumDataBytesGroup1 + 1; - internal int NumECBytesPerBlock => (NumTotalBytes - NumDataBytes) / NumECBlocks; + internal int NumECBytesPerBlock => (NumTotalBytes - NumDataBytes) / NumECBlocks; - internal static int Width(int version) => 17 + (4 * version); + internal static int Width(int version) => 17 + (4 * version); - public override string ToString() => $"{Version};{NumTotalBytes};{NumDataBytes};{NumECBlocks}"; -} + public override string ToString() => $"{Version};{NumTotalBytes};{NumDataBytes};{NumECBlocks}"; +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/ErrorCorrectionBlock.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/ErrorCorrectionBlock.cs index cb796ef..7d900a1 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/ErrorCorrectionBlock.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/ErrorCorrectionBlock.cs @@ -2,14 +2,14 @@ namespace Gma.QrCodeNet.Encoding.Versions; internal struct ErrorCorrectionBlock { - internal ErrorCorrectionBlock(int numErrorCorrectionBlock, int numDataCodewards) - : this() - { - NumErrorCorrectionBlock = numErrorCorrectionBlock; - NumDataCodewords = numDataCodewards; - } + internal ErrorCorrectionBlock(int numErrorCorrectionBlock, int numDataCodewards) + : this() + { + NumErrorCorrectionBlock = numErrorCorrectionBlock; + NumDataCodewords = numDataCodewards; + } - internal int NumErrorCorrectionBlock { get; private set; } + internal int NumErrorCorrectionBlock { get; private set; } - internal int NumDataCodewords { get; private set; } -} + internal int NumDataCodewords { get; private set; } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/ErrorCorrectionBlocks.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/ErrorCorrectionBlocks.cs index b1fc966..e15f844 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/ErrorCorrectionBlocks.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/ErrorCorrectionBlocks.cs @@ -1,55 +1,53 @@ -using System; - namespace Gma.QrCodeNet.Encoding.Versions; internal struct ErrorCorrectionBlocks { - internal ErrorCorrectionBlocks(int numErrorCorrectionCodewords, ErrorCorrectionBlock ecBlock) - : this() - { - NumErrorCorrectionCodewards = numErrorCorrectionCodewords; - ECBlock = new ErrorCorrectionBlock[] { ecBlock }; + internal ErrorCorrectionBlocks(int numErrorCorrectionCodewords, ErrorCorrectionBlock ecBlock) + : this() + { + NumErrorCorrectionCodewards = numErrorCorrectionCodewords; + ECBlock = new ErrorCorrectionBlock[] { ecBlock }; - Initialize(); - } + Initialize(); + } - internal ErrorCorrectionBlocks(int numErrorCorrectionCodewords, ErrorCorrectionBlock ecBlock1, ErrorCorrectionBlock ecBlock2) - : this() - { - NumErrorCorrectionCodewards = numErrorCorrectionCodewords; - ECBlock = new ErrorCorrectionBlock[] { ecBlock1, ecBlock2 }; + internal ErrorCorrectionBlocks(int numErrorCorrectionCodewords, ErrorCorrectionBlock ecBlock1, ErrorCorrectionBlock ecBlock2) + : this() + { + NumErrorCorrectionCodewards = numErrorCorrectionCodewords; + ECBlock = new ErrorCorrectionBlock[] { ecBlock1, ecBlock2 }; - Initialize(); - } + Initialize(); + } - internal int NumErrorCorrectionCodewards { get; private set; } + internal int NumErrorCorrectionCodewards { get; private set; } - internal int NumBlocks { get; private set; } + internal int NumBlocks { get; private set; } - internal int ErrorCorrectionCodewordsPerBlock { get; private set; } + internal int ErrorCorrectionCodewordsPerBlock { get; private set; } - private ErrorCorrectionBlock[] ECBlock { get; } + private ErrorCorrectionBlock[] ECBlock { get; } - /// - /// Get Error Correction Blocks - /// - internal ErrorCorrectionBlock[] GetECBlocks() => ECBlock; + /// + /// Get Error Correction Blocks + /// + internal ErrorCorrectionBlock[] GetECBlocks() => ECBlock; - /// - /// Initialize for NumBlocks and ErrorCorrectionCodewordsPerBlock - /// - private void Initialize() + /// + /// Initialize for NumBlocks and ErrorCorrectionCodewordsPerBlock + /// + private void Initialize() { if (ECBlock == null) throw new ArgumentNullException(nameof(ECBlock)); NumBlocks = 0; - int blockLength = ECBlock.Length; - for (int i = 0; i < blockLength; i++) - { - NumBlocks += ECBlock[i].NumErrorCorrectionBlock; - } + int blockLength = ECBlock.Length; + for (int i = 0; i < blockLength; i++) + { + NumBlocks += ECBlock[i].NumErrorCorrectionBlock; + } - ErrorCorrectionCodewordsPerBlock = NumErrorCorrectionCodewards / NumBlocks; - } -} + ErrorCorrectionCodewordsPerBlock = NumErrorCorrectionCodewards / NumBlocks; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/QRCodeVersion.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/QRCodeVersion.cs index cfa160e..30ad44b 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/QRCodeVersion.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/QRCodeVersion.cs @@ -1,35 +1,33 @@ -using System; - namespace Gma.QrCodeNet.Encoding.Versions; internal struct QRCodeVersion { - internal QRCodeVersion(int versionNum, int totalCodewords, ErrorCorrectionBlocks ecblocksL, ErrorCorrectionBlocks ecblocksM, ErrorCorrectionBlocks ecblocksQ, ErrorCorrectionBlocks ecblocksH) - : this() - { - VersionNum = versionNum; - TotalCodewords = totalCodewords; - ECBlocks = new ErrorCorrectionBlocks[] { ecblocksL, ecblocksM, ecblocksQ, ecblocksH }; - DimensionForVersion = 17 + (versionNum * 4); - } + internal QRCodeVersion(int versionNum, int totalCodewords, ErrorCorrectionBlocks ecblocksL, ErrorCorrectionBlocks ecblocksM, ErrorCorrectionBlocks ecblocksQ, ErrorCorrectionBlocks ecblocksH) + : this() + { + VersionNum = versionNum; + TotalCodewords = totalCodewords; + ECBlocks = new ErrorCorrectionBlocks[] { ecblocksL, ecblocksM, ecblocksQ, ecblocksH }; + DimensionForVersion = 17 + (versionNum * 4); + } - internal int VersionNum { get; private set; } + internal int VersionNum { get; private set; } - internal int TotalCodewords { get; private set; } + internal int TotalCodewords { get; private set; } - internal int DimensionForVersion { get; private set; } + internal int DimensionForVersion { get; private set; } - private ErrorCorrectionBlocks[] ECBlocks { get; } + private ErrorCorrectionBlocks[] ECBlocks { get; } - internal ErrorCorrectionBlocks GetECBlocksByLevel(ErrorCorrectionLevel eCLevel) - { - return eCLevel switch - { - ErrorCorrectionLevel.L => ECBlocks[0], - ErrorCorrectionLevel.M => ECBlocks[1], - ErrorCorrectionLevel.Q => ECBlocks[2], - ErrorCorrectionLevel.H => ECBlocks[3], - _ => throw new ArgumentOutOfRangeException(nameof(eCLevel)) - }; - } -} + internal ErrorCorrectionBlocks GetECBlocksByLevel(ErrorCorrectionLevel eCLevel) + { + return eCLevel switch + { + ErrorCorrectionLevel.L => ECBlocks[0], + ErrorCorrectionLevel.M => ECBlocks[1], + ErrorCorrectionLevel.Q => ECBlocks[2], + ErrorCorrectionLevel.H => ECBlocks[3], + _ => throw new ArgumentOutOfRangeException(nameof(eCLevel)) + }; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/VersionControl.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/VersionControl.cs index 0fdd767..30d5fea 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/VersionControl.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/VersionControl.cs @@ -1,144 +1,143 @@ -using System; using Gma.QrCodeNet.Encoding.DataEncodation; namespace Gma.QrCodeNet.Encoding.Versions; internal static class VersionControl { - private const int NumBitsModeIndicator = 4; - private const string DefaultEncoding = QRCodeConstantVariable.DefaultEncoding; + private const int NumBitsModeIndicator = 4; + private const string DefaultEncoding = QRCodeConstantVariable.DefaultEncoding; - private static readonly int[] VERSION_GROUP = new int[] { 9, 26, 40 }; + private static readonly int[] VERSION_GROUP = new int[] { 9, 26, 40 }; - /// - /// Determine which version to use - /// - /// Number of bits for encoded content - /// Encoding name for EightBitByte - /// VersionDetail and ECI - internal static VersionControlStruct InitialSetup(int dataBitsLength, ErrorCorrectionLevel level, string encodingName) - { - int totalDataBits = dataBitsLength; + /// + /// Determine which version to use + /// + /// Number of bits for encoded content + /// Encoding name for EightBitByte + /// VersionDetail and ECI + internal static VersionControlStruct InitialSetup(int dataBitsLength, ErrorCorrectionLevel level, string encodingName) + { + int totalDataBits = dataBitsLength; - bool containECI = false; + bool containECI = false; - BitList eciHeader = new(); + BitList eciHeader = new(); - if (encodingName is not DefaultEncoding and not QRCodeConstantVariable.UTF8Encoding) - { - ECISet eciSet = new(ECISet.AppendOption.NameToValue); - int eciValue = eciSet.GetECIValueByName(encodingName); + if (encodingName is not DefaultEncoding and not QRCodeConstantVariable.UTF8Encoding) + { + ECISet eciSet = new(ECISet.AppendOption.NameToValue); + int eciValue = eciSet.GetECIValueByName(encodingName); - totalDataBits += ECISet.NumOfECIHeaderBits(eciValue); - eciHeader = eciSet.GetECIHeader(encodingName); - containECI = true; - } + totalDataBits += ECISet.NumOfECIHeaderBits(eciValue); + eciHeader = eciSet.GetECIHeader(encodingName); + containECI = true; + } - // Determine which version group it belong to - int searchGroup = DynamicSearchIndicator(totalDataBits, level); + // Determine which version group it belong to + int searchGroup = DynamicSearchIndicator(totalDataBits, level); - int[] charCountIndicator = CharCountIndicatorTable.GetCharCountIndicatorSet(); + int[] charCountIndicator = CharCountIndicatorTable.GetCharCountIndicatorSet(); - totalDataBits += (NumBitsModeIndicator + charCountIndicator[searchGroup]); + totalDataBits += (NumBitsModeIndicator + charCountIndicator[searchGroup]); - int lowerSearchBoundary = searchGroup == 0 ? 1 : (VERSION_GROUP[searchGroup - 1] + 1); - int higherSearchBoundary = VERSION_GROUP[searchGroup]; + int lowerSearchBoundary = searchGroup == 0 ? 1 : (VERSION_GROUP[searchGroup - 1] + 1); + int higherSearchBoundary = VERSION_GROUP[searchGroup]; - // Binary search to find proper version - int versionNum = BinarySearch(totalDataBits, level, lowerSearchBoundary, higherSearchBoundary); + // Binary search to find proper version + int versionNum = BinarySearch(totalDataBits, level, lowerSearchBoundary, higherSearchBoundary); - VersionControlStruct vcStruct = FillVCStruct(versionNum, level); + VersionControlStruct vcStruct = FillVCStruct(versionNum, level); - vcStruct.IsContainECI = containECI; + vcStruct.IsContainECI = containECI; - vcStruct.ECIHeader = eciHeader; + vcStruct.ECIHeader = eciHeader; - return vcStruct; - } + return vcStruct; + } - private static VersionControlStruct FillVCStruct(int versionNum, ErrorCorrectionLevel level) - { - if (versionNum is < 1 or > 40) - { - throw new InvalidOperationException($"Unexpected version number: {versionNum}"); - } + private static VersionControlStruct FillVCStruct(int versionNum, ErrorCorrectionLevel level) + { + if (versionNum is < 1 or > 40) + { + throw new InvalidOperationException($"Unexpected version number: {versionNum}"); + } - VersionControlStruct vcStruct = new(); + VersionControlStruct vcStruct = new(); - int version = versionNum; + int version = versionNum; - QRCodeVersion versionData = VersionTable.GetVersionByNum(versionNum); + QRCodeVersion versionData = VersionTable.GetVersionByNum(versionNum); - int numTotalBytes = versionData.TotalCodewords; + int numTotalBytes = versionData.TotalCodewords; - ErrorCorrectionBlocks ecBlocks = versionData.GetECBlocksByLevel(level); - int numDataBytes = numTotalBytes - ecBlocks.NumErrorCorrectionCodewards; - int numECBlocks = ecBlocks.NumBlocks; + ErrorCorrectionBlocks ecBlocks = versionData.GetECBlocksByLevel(level); + int numDataBytes = numTotalBytes - ecBlocks.NumErrorCorrectionCodewards; + int numECBlocks = ecBlocks.NumBlocks; - VersionDetail vcDetail = new(version, numTotalBytes, numDataBytes, numECBlocks); + VersionDetail vcDetail = new(version, numTotalBytes, numDataBytes, numECBlocks); - vcStruct.VersionDetail = vcDetail; - return vcStruct; - } + vcStruct.VersionDetail = vcDetail; + return vcStruct; + } - /// - /// Decide which version group it belong to - /// - /// Number of bits for bitlist where it contain DataBits encode from input content and ECI header - /// Error correction level - /// Version group index for VERSION_GROUP - private static int DynamicSearchIndicator(int numBits, ErrorCorrectionLevel level) - { - int[] charCountIndicator = CharCountIndicatorTable.GetCharCountIndicatorSet(); - int loopLength = VERSION_GROUP.Length; - for (int i = 0; i < loopLength; i++) - { - int totalBits = numBits + NumBitsModeIndicator + charCountIndicator[i]; + /// + /// Decide which version group it belong to + /// + /// Number of bits for bitlist where it contain DataBits encode from input content and ECI header + /// Error correction level + /// Version group index for VERSION_GROUP + private static int DynamicSearchIndicator(int numBits, ErrorCorrectionLevel level) + { + int[] charCountIndicator = CharCountIndicatorTable.GetCharCountIndicatorSet(); + int loopLength = VERSION_GROUP.Length; + for (int i = 0; i < loopLength; i++) + { + int totalBits = numBits + NumBitsModeIndicator + charCountIndicator[i]; - QRCodeVersion version = VersionTable.GetVersionByNum(VERSION_GROUP[i]); - int numECCodewords = version.GetECBlocksByLevel(level).NumErrorCorrectionCodewards; + QRCodeVersion version = VersionTable.GetVersionByNum(VERSION_GROUP[i]); + int numECCodewords = version.GetECBlocksByLevel(level).NumErrorCorrectionCodewards; - int dataCodewords = version.TotalCodewords - numECCodewords; + int dataCodewords = version.TotalCodewords - numECCodewords; - if (totalBits <= dataCodewords * 8) - { - return i; - } - } + if (totalBits <= dataCodewords * 8) + { + return i; + } + } - throw new InputOutOfBoundaryException($"QRCode do not have enough space for {(numBits + NumBitsModeIndicator + charCountIndicator[2])} bits"); - } + throw new InputOutOfBoundaryException($"QRCode do not have enough space for {(numBits + NumBitsModeIndicator + charCountIndicator[2])} bits"); + } - /// - /// Use number of data bits(header + eci header + data bits from EncoderBase) to search for proper version to use - /// between min and max boundary. - /// Boundary define by DynamicSearchIndicator method. - /// - private static int BinarySearch(int numDataBits, ErrorCorrectionLevel level, int lowerVersionNum, int higherVersionNum) - { - int middleVersionNumber; + /// + /// Use number of data bits(header + eci header + data bits from EncoderBase) to search for proper version to use + /// between min and max boundary. + /// Boundary define by DynamicSearchIndicator method. + /// + private static int BinarySearch(int numDataBits, ErrorCorrectionLevel level, int lowerVersionNum, int higherVersionNum) + { + int middleVersionNumber; - while (lowerVersionNum <= higherVersionNum) - { - middleVersionNumber = (lowerVersionNum + higherVersionNum) / 2; - QRCodeVersion version = VersionTable.GetVersionByNum(middleVersionNumber); - int numECCodewords = version.GetECBlocksByLevel(level).NumErrorCorrectionCodewards; - int dataCodewords = version.TotalCodewords - numECCodewords; + while (lowerVersionNum <= higherVersionNum) + { + middleVersionNumber = (lowerVersionNum + higherVersionNum) / 2; + QRCodeVersion version = VersionTable.GetVersionByNum(middleVersionNumber); + int numECCodewords = version.GetECBlocksByLevel(level).NumErrorCorrectionCodewards; + int dataCodewords = version.TotalCodewords - numECCodewords; - if (dataCodewords << 3 == numDataBits) - { - return middleVersionNumber; - } + if (dataCodewords << 3 == numDataBits) + { + return middleVersionNumber; + } - if (dataCodewords << 3 > numDataBits) - { - higherVersionNum = middleVersionNumber - 1; - } - else - { - lowerVersionNum = middleVersionNumber + 1; - } - } - return lowerVersionNum; - } -} + if (dataCodewords << 3 > numDataBits) + { + higherVersionNum = middleVersionNumber - 1; + } + else + { + lowerVersionNum = middleVersionNumber + 1; + } + } + return lowerVersionNum; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/VersionControlStruct.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/VersionControlStruct.cs index 7979f18..682e478 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/VersionControlStruct.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/VersionControlStruct.cs @@ -2,7 +2,7 @@ namespace Gma.QrCodeNet.Encoding.Versions; internal struct VersionControlStruct { - internal VersionDetail VersionDetail { get; set; } - internal bool IsContainECI { get; set; } - internal BitList ECIHeader { get; set; } -} + internal VersionDetail VersionDetail { get; set; } + internal bool IsContainECI { get; set; } + internal BitList ECIHeader { get; set; } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/VersionTable.cs b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/VersionTable.cs index ef30b0b..79f6028 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/VersionTable.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/VersionTable.cs @@ -1,317 +1,315 @@ -using System; - namespace Gma.QrCodeNet.Encoding.Versions; public static class VersionTable { - private static readonly QRCodeVersion[] Version = Initialize(); + private static readonly QRCodeVersion[] Version = Initialize(); - internal static QRCodeVersion GetVersionByNum(int versionNum) - { - if (versionNum is < QRCodeConstantVariable.MinVersion or > QRCodeConstantVariable.MaxVersion) - { - throw new InvalidOperationException($"Unexpected version number: {versionNum}."); - } + internal static QRCodeVersion GetVersionByNum(int versionNum) + { + if (versionNum is < QRCodeConstantVariable.MinVersion or > QRCodeConstantVariable.MaxVersion) + { + throw new InvalidOperationException($"Unexpected version number: {versionNum}."); + } - return Version[versionNum - 1]; - } + return Version[versionNum - 1]; + } - internal static QRCodeVersion GetVersionByWidth(int matrixWidth) - { - if ((matrixWidth - 17) % 4 != 0) - { - throw new ArgumentException("Incorrect matrix width."); - } - else - { - return GetVersionByNum((matrixWidth - 17) / 4); - } - } + internal static QRCodeVersion GetVersionByWidth(int matrixWidth) + { + if ((matrixWidth - 17) % 4 != 0) + { + throw new ArgumentException("Incorrect matrix width."); + } + else + { + return GetVersionByNum((matrixWidth - 17) / 4); + } + } - private static QRCodeVersion[] Initialize() - { - return new QRCodeVersion[] - { - new QRCodeVersion( - 1, - 26, - new ErrorCorrectionBlocks(7, new ErrorCorrectionBlock(1, 19)), - new ErrorCorrectionBlocks(10, new ErrorCorrectionBlock(1, 16)), - new ErrorCorrectionBlocks(13, new ErrorCorrectionBlock(1, 13)), - new ErrorCorrectionBlocks(17, new ErrorCorrectionBlock(1, 9))), - new QRCodeVersion( - 2, - 44, - new ErrorCorrectionBlocks(10, new ErrorCorrectionBlock(1, 34)), - new ErrorCorrectionBlocks(16, new ErrorCorrectionBlock(1, 28)), - new ErrorCorrectionBlocks(22, new ErrorCorrectionBlock(1, 22)), - new ErrorCorrectionBlocks(28, new ErrorCorrectionBlock(1, 16))), - new QRCodeVersion( - 3, - 70, - new ErrorCorrectionBlocks(15, new ErrorCorrectionBlock(1, 55)), - new ErrorCorrectionBlocks(26, new ErrorCorrectionBlock(1, 44)), - new ErrorCorrectionBlocks(36, new ErrorCorrectionBlock(2, 17)), - new ErrorCorrectionBlocks(44, new ErrorCorrectionBlock(2, 13))), - new QRCodeVersion( - 4, - 100, - new ErrorCorrectionBlocks(20, new ErrorCorrectionBlock(1, 80)), - new ErrorCorrectionBlocks(36, new ErrorCorrectionBlock(2, 32)), - new ErrorCorrectionBlocks(52, new ErrorCorrectionBlock(2, 24)), - new ErrorCorrectionBlocks(64, new ErrorCorrectionBlock(4, 9))), - new QRCodeVersion( - 5, - 134, - new ErrorCorrectionBlocks(26, new ErrorCorrectionBlock(1, 108)), - new ErrorCorrectionBlocks(48, new ErrorCorrectionBlock(2, 43)), - new ErrorCorrectionBlocks(72, new ErrorCorrectionBlock(2, 15), new ErrorCorrectionBlock(2, 16)), - new ErrorCorrectionBlocks(88, new ErrorCorrectionBlock(2, 11), new ErrorCorrectionBlock(2, 12))), - new QRCodeVersion( - 6, - 172, - new ErrorCorrectionBlocks(36, new ErrorCorrectionBlock(2, 68)), - new ErrorCorrectionBlocks(64, new ErrorCorrectionBlock(4, 27)), - new ErrorCorrectionBlocks(96, new ErrorCorrectionBlock(4, 19)), - new ErrorCorrectionBlocks(112, new ErrorCorrectionBlock(4, 15))), - new QRCodeVersion( - 7, - 196, - new ErrorCorrectionBlocks(40, new ErrorCorrectionBlock(2, 78)), - new ErrorCorrectionBlocks(72, new ErrorCorrectionBlock(4, 31)), - new ErrorCorrectionBlocks(108, new ErrorCorrectionBlock(2, 14), new ErrorCorrectionBlock(4, 15)), - new ErrorCorrectionBlocks(130, new ErrorCorrectionBlock(4, 13), new ErrorCorrectionBlock(1, 14))), - new QRCodeVersion( - 8, - 242, - new ErrorCorrectionBlocks(48, new ErrorCorrectionBlock(2, 97)), - new ErrorCorrectionBlocks(88, new ErrorCorrectionBlock(2, 38), new ErrorCorrectionBlock(2, 39)), - new ErrorCorrectionBlocks(132, new ErrorCorrectionBlock(4, 18), new ErrorCorrectionBlock(2, 19)), - new ErrorCorrectionBlocks(156, new ErrorCorrectionBlock(4, 14), new ErrorCorrectionBlock(2, 15))), - new QRCodeVersion( - 9, - 292, - new ErrorCorrectionBlocks(60, new ErrorCorrectionBlock(2, 116)), - new ErrorCorrectionBlocks(110, new ErrorCorrectionBlock(3, 36), new ErrorCorrectionBlock(2, 37)), - new ErrorCorrectionBlocks(160, new ErrorCorrectionBlock(4, 16), new ErrorCorrectionBlock(4, 17)), - new ErrorCorrectionBlocks(192, new ErrorCorrectionBlock(4, 12), new ErrorCorrectionBlock(4, 13))), - new QRCodeVersion( - 10, - 346, - new ErrorCorrectionBlocks(72, new ErrorCorrectionBlock(2, 68), new ErrorCorrectionBlock(2, 69)), - new ErrorCorrectionBlocks(130, new ErrorCorrectionBlock(4, 43), new ErrorCorrectionBlock(1, 44)), - new ErrorCorrectionBlocks(192, new ErrorCorrectionBlock(6, 19), new ErrorCorrectionBlock(2, 20)), - new ErrorCorrectionBlocks(224, new ErrorCorrectionBlock(6, 15), new ErrorCorrectionBlock(2, 16))), - new QRCodeVersion( - 11, - 404, - new ErrorCorrectionBlocks(80, new ErrorCorrectionBlock(4, 81)), - new ErrorCorrectionBlocks(150, new ErrorCorrectionBlock(1, 50), new ErrorCorrectionBlock(4, 51)), - new ErrorCorrectionBlocks(224, new ErrorCorrectionBlock(4, 22), new ErrorCorrectionBlock(4, 23)), - new ErrorCorrectionBlocks(264, new ErrorCorrectionBlock(3, 12), new ErrorCorrectionBlock(8, 13))), - new QRCodeVersion( - 12, - 466, - new ErrorCorrectionBlocks(96, new ErrorCorrectionBlock(2, 92), new ErrorCorrectionBlock(2, 93)), - new ErrorCorrectionBlocks(176, new ErrorCorrectionBlock(6, 36), new ErrorCorrectionBlock(2, 37)), - new ErrorCorrectionBlocks(260, new ErrorCorrectionBlock(4, 20), new ErrorCorrectionBlock(6, 21)), - new ErrorCorrectionBlocks(308, new ErrorCorrectionBlock(7, 14), new ErrorCorrectionBlock(4, 15))), - new QRCodeVersion( - 13, - 532, - new ErrorCorrectionBlocks(104, new ErrorCorrectionBlock(4, 107)), - new ErrorCorrectionBlocks(198, new ErrorCorrectionBlock(8, 37), new ErrorCorrectionBlock(1, 38)), - new ErrorCorrectionBlocks(288, new ErrorCorrectionBlock(8, 20), new ErrorCorrectionBlock(4, 21)), - new ErrorCorrectionBlocks(352, new ErrorCorrectionBlock(12, 11), new ErrorCorrectionBlock(4, 12))), - new QRCodeVersion( - 14, - 581, - new ErrorCorrectionBlocks(120, new ErrorCorrectionBlock(3, 115), new ErrorCorrectionBlock(1, 116)), - new ErrorCorrectionBlocks(216, new ErrorCorrectionBlock(4, 40), new ErrorCorrectionBlock(5, 41)), - new ErrorCorrectionBlocks(320, new ErrorCorrectionBlock(11, 16), new ErrorCorrectionBlock(5, 17)), - new ErrorCorrectionBlocks(384, new ErrorCorrectionBlock(11, 12), new ErrorCorrectionBlock(5, 13))), - new QRCodeVersion( - 15, - 655, - new ErrorCorrectionBlocks(132, new ErrorCorrectionBlock(5, 87), new ErrorCorrectionBlock(1, 88)), - new ErrorCorrectionBlocks(240, new ErrorCorrectionBlock(5, 41), new ErrorCorrectionBlock(5, 42)), - new ErrorCorrectionBlocks(360, new ErrorCorrectionBlock(5, 24), new ErrorCorrectionBlock(7, 25)), - new ErrorCorrectionBlocks(432, new ErrorCorrectionBlock(11, 12), new ErrorCorrectionBlock(7, 13))), - new QRCodeVersion( - 16, - 733, - new ErrorCorrectionBlocks(144, new ErrorCorrectionBlock(5, 98), new ErrorCorrectionBlock(1, 99)), - new ErrorCorrectionBlocks(280, new ErrorCorrectionBlock(7, 45), new ErrorCorrectionBlock(3, 46)), - new ErrorCorrectionBlocks(408, new ErrorCorrectionBlock(15, 19), new ErrorCorrectionBlock(2, 20)), - new ErrorCorrectionBlocks(480, new ErrorCorrectionBlock(3, 15), new ErrorCorrectionBlock(13, 16))), - new QRCodeVersion( - 17, - 815, - new ErrorCorrectionBlocks(168, new ErrorCorrectionBlock(1, 107), new ErrorCorrectionBlock(5, 108)), - new ErrorCorrectionBlocks(308, new ErrorCorrectionBlock(10, 46), new ErrorCorrectionBlock(1, 47)), - new ErrorCorrectionBlocks(448, new ErrorCorrectionBlock(1, 22), new ErrorCorrectionBlock(15, 23)), - new ErrorCorrectionBlocks(532, new ErrorCorrectionBlock(2, 14), new ErrorCorrectionBlock(17, 15))), - new QRCodeVersion( - 18, - 901, - new ErrorCorrectionBlocks(180, new ErrorCorrectionBlock(5, 120), new ErrorCorrectionBlock(1, 121)), - new ErrorCorrectionBlocks(338, new ErrorCorrectionBlock(9, 43), new ErrorCorrectionBlock(4, 44)), - new ErrorCorrectionBlocks(504, new ErrorCorrectionBlock(17, 22), new ErrorCorrectionBlock(1, 23)), - new ErrorCorrectionBlocks(588, new ErrorCorrectionBlock(2, 14), new ErrorCorrectionBlock(19, 15))), - new QRCodeVersion( - 19, - 991, - new ErrorCorrectionBlocks(196, new ErrorCorrectionBlock(3, 113), new ErrorCorrectionBlock(4, 114)), - new ErrorCorrectionBlocks(364, new ErrorCorrectionBlock(3, 44), new ErrorCorrectionBlock(11, 45)), - new ErrorCorrectionBlocks(546, new ErrorCorrectionBlock(17, 21), new ErrorCorrectionBlock(4, 22)), - new ErrorCorrectionBlocks(650, new ErrorCorrectionBlock(9, 13), new ErrorCorrectionBlock(16, 14))), - new QRCodeVersion( - 20, - 1085, - new ErrorCorrectionBlocks(224, new ErrorCorrectionBlock(3, 107), new ErrorCorrectionBlock(5, 108)), - new ErrorCorrectionBlocks(416, new ErrorCorrectionBlock(3, 41), new ErrorCorrectionBlock(13, 42)), - new ErrorCorrectionBlocks(600, new ErrorCorrectionBlock(15, 24), new ErrorCorrectionBlock(5, 25)), - new ErrorCorrectionBlocks(700, new ErrorCorrectionBlock(15, 15), new ErrorCorrectionBlock(10, 16))), - new QRCodeVersion( - 21, - 1156, - new ErrorCorrectionBlocks(224, new ErrorCorrectionBlock(4, 116), new ErrorCorrectionBlock(4, 117)), - new ErrorCorrectionBlocks(442, new ErrorCorrectionBlock(17, 42)), - new ErrorCorrectionBlocks(644, new ErrorCorrectionBlock(17, 22), new ErrorCorrectionBlock(6, 23)), - new ErrorCorrectionBlocks(750, new ErrorCorrectionBlock(19, 16), new ErrorCorrectionBlock(6, 17))), - new QRCodeVersion( - 22, - 1258, - new ErrorCorrectionBlocks(252, new ErrorCorrectionBlock(2, 111), new ErrorCorrectionBlock(7, 112)), - new ErrorCorrectionBlocks(476, new ErrorCorrectionBlock(17, 46)), - new ErrorCorrectionBlocks(690, new ErrorCorrectionBlock(7, 24), new ErrorCorrectionBlock(16, 25)), - new ErrorCorrectionBlocks(816, new ErrorCorrectionBlock(34, 13))), - new QRCodeVersion( - 23, - 1364, - new ErrorCorrectionBlocks(270, new ErrorCorrectionBlock(4, 121), new ErrorCorrectionBlock(5, 122)), - new ErrorCorrectionBlocks(504, new ErrorCorrectionBlock(4, 47), new ErrorCorrectionBlock(14, 48)), - new ErrorCorrectionBlocks(750, new ErrorCorrectionBlock(11, 24), new ErrorCorrectionBlock(14, 25)), - new ErrorCorrectionBlocks(900, new ErrorCorrectionBlock(16, 15), new ErrorCorrectionBlock(14, 16))), - new QRCodeVersion( - 24, - 1474, - new ErrorCorrectionBlocks(300, new ErrorCorrectionBlock(6, 117), new ErrorCorrectionBlock(4, 118)), - new ErrorCorrectionBlocks(560, new ErrorCorrectionBlock(6, 45), new ErrorCorrectionBlock(14, 46)), - new ErrorCorrectionBlocks(810, new ErrorCorrectionBlock(11, 24), new ErrorCorrectionBlock(16, 25)), - new ErrorCorrectionBlocks(960, new ErrorCorrectionBlock(30, 16), new ErrorCorrectionBlock(2, 17))), - new QRCodeVersion( - 25, - 1588, - new ErrorCorrectionBlocks(312, new ErrorCorrectionBlock(8, 106), new ErrorCorrectionBlock(4, 107)), - new ErrorCorrectionBlocks(588, new ErrorCorrectionBlock(8, 47), new ErrorCorrectionBlock(13, 48)), - new ErrorCorrectionBlocks(870, new ErrorCorrectionBlock(7, 24), new ErrorCorrectionBlock(22, 25)), - new ErrorCorrectionBlocks(1050, new ErrorCorrectionBlock(22, 15), new ErrorCorrectionBlock(13, 16))), - new QRCodeVersion( - 26, - 1706, - new ErrorCorrectionBlocks(336, new ErrorCorrectionBlock(10, 114), new ErrorCorrectionBlock(2, 115)), - new ErrorCorrectionBlocks(644, new ErrorCorrectionBlock(19, 46), new ErrorCorrectionBlock(4, 47)), - new ErrorCorrectionBlocks(952, new ErrorCorrectionBlock(28, 22), new ErrorCorrectionBlock(6, 23)), - new ErrorCorrectionBlocks(1110, new ErrorCorrectionBlock(33, 16), new ErrorCorrectionBlock(4, 17))), - new QRCodeVersion( - 27, - 1828, - new ErrorCorrectionBlocks(360, new ErrorCorrectionBlock(8, 122), new ErrorCorrectionBlock(4, 123)), - new ErrorCorrectionBlocks(700, new ErrorCorrectionBlock(22, 45), new ErrorCorrectionBlock(3, 46)), - new ErrorCorrectionBlocks(1020, new ErrorCorrectionBlock(8, 23), new ErrorCorrectionBlock(26, 24)), - new ErrorCorrectionBlocks(1200, new ErrorCorrectionBlock(12, 15), new ErrorCorrectionBlock(28, 16))), - new QRCodeVersion( - 28, - 1921, - new ErrorCorrectionBlocks(390, new ErrorCorrectionBlock(3, 117), new ErrorCorrectionBlock(10, 118)), - new ErrorCorrectionBlocks(728, new ErrorCorrectionBlock(3, 45), new ErrorCorrectionBlock(23, 46)), - new ErrorCorrectionBlocks(1050, new ErrorCorrectionBlock(4, 24), new ErrorCorrectionBlock(31, 25)), - new ErrorCorrectionBlocks(1260, new ErrorCorrectionBlock(11, 15), new ErrorCorrectionBlock(31, 16))), - new QRCodeVersion( - 29, - 2051, - new ErrorCorrectionBlocks(420, new ErrorCorrectionBlock(7, 116), new ErrorCorrectionBlock(7, 117)), - new ErrorCorrectionBlocks(784, new ErrorCorrectionBlock(21, 45), new ErrorCorrectionBlock(7, 46)), - new ErrorCorrectionBlocks(1140, new ErrorCorrectionBlock(1, 23), new ErrorCorrectionBlock(37, 24)), - new ErrorCorrectionBlocks(1350, new ErrorCorrectionBlock(19, 15), new ErrorCorrectionBlock(26, 16))), - new QRCodeVersion( - 30, - 2185, - new ErrorCorrectionBlocks(450, new ErrorCorrectionBlock(5, 115), new ErrorCorrectionBlock(10, 116)), - new ErrorCorrectionBlocks(812, new ErrorCorrectionBlock(19, 47), new ErrorCorrectionBlock(10, 48)), - new ErrorCorrectionBlocks(1200, new ErrorCorrectionBlock(15, 24), new ErrorCorrectionBlock(25, 25)), - new ErrorCorrectionBlocks(1440, new ErrorCorrectionBlock(23, 15), new ErrorCorrectionBlock(25, 16))), - new QRCodeVersion( - 31, - 2323, - new ErrorCorrectionBlocks(480, new ErrorCorrectionBlock(13, 115), new ErrorCorrectionBlock(3, 116)), - new ErrorCorrectionBlocks(868, new ErrorCorrectionBlock(2, 46), new ErrorCorrectionBlock(29, 47)), - new ErrorCorrectionBlocks(1290, new ErrorCorrectionBlock(42, 24), new ErrorCorrectionBlock(1, 25)), - new ErrorCorrectionBlocks(1530, new ErrorCorrectionBlock(23, 15), new ErrorCorrectionBlock(28, 16))), - new QRCodeVersion( - 32, - 2465, - new ErrorCorrectionBlocks(510, new ErrorCorrectionBlock(17, 115)), - new ErrorCorrectionBlocks(924, new ErrorCorrectionBlock(10, 46), new ErrorCorrectionBlock(23, 47)), - new ErrorCorrectionBlocks(1350, new ErrorCorrectionBlock(10, 24), new ErrorCorrectionBlock(35, 25)), - new ErrorCorrectionBlocks(1620, new ErrorCorrectionBlock(19, 15), new ErrorCorrectionBlock(35, 16))), - new QRCodeVersion( - 33, - 2611, - new ErrorCorrectionBlocks(540, new ErrorCorrectionBlock(17, 115), new ErrorCorrectionBlock(1, 116)), - new ErrorCorrectionBlocks(980, new ErrorCorrectionBlock(14, 46), new ErrorCorrectionBlock(21, 47)), - new ErrorCorrectionBlocks(1440, new ErrorCorrectionBlock(29, 24), new ErrorCorrectionBlock(19, 25)), - new ErrorCorrectionBlocks(1710, new ErrorCorrectionBlock(11, 15), new ErrorCorrectionBlock(46, 16))), - new QRCodeVersion( - 34, - 2761, - new ErrorCorrectionBlocks(570, new ErrorCorrectionBlock(13, 115), new ErrorCorrectionBlock(6, 116)), - new ErrorCorrectionBlocks(1036, new ErrorCorrectionBlock(14, 46), new ErrorCorrectionBlock(23, 47)), - new ErrorCorrectionBlocks(1530, new ErrorCorrectionBlock(44, 24), new ErrorCorrectionBlock(7, 25)), - new ErrorCorrectionBlocks(1800, new ErrorCorrectionBlock(59, 16), new ErrorCorrectionBlock(1, 17))), - new QRCodeVersion( - 35, - 2876, - new ErrorCorrectionBlocks(570, new ErrorCorrectionBlock(12, 121), new ErrorCorrectionBlock(7, 122)), - new ErrorCorrectionBlocks(1064, new ErrorCorrectionBlock(12, 47), new ErrorCorrectionBlock(26, 48)), - new ErrorCorrectionBlocks(1590, new ErrorCorrectionBlock(39, 24), new ErrorCorrectionBlock(14, 25)), - new ErrorCorrectionBlocks(1890, new ErrorCorrectionBlock(22, 15), new ErrorCorrectionBlock(41, 16))), - new QRCodeVersion( - 36, - 3034, - new ErrorCorrectionBlocks(600, new ErrorCorrectionBlock(6, 121), new ErrorCorrectionBlock(14, 122)), - new ErrorCorrectionBlocks(1120, new ErrorCorrectionBlock(6, 47), new ErrorCorrectionBlock(34, 48)), - new ErrorCorrectionBlocks(1680, new ErrorCorrectionBlock(46, 24), new ErrorCorrectionBlock(10, 25)), - new ErrorCorrectionBlocks(1980, new ErrorCorrectionBlock(2, 15), new ErrorCorrectionBlock(64, 16))), - new QRCodeVersion( - 37, - 3196, - new ErrorCorrectionBlocks(630, new ErrorCorrectionBlock(17, 122), new ErrorCorrectionBlock(4, 123)), - new ErrorCorrectionBlocks(1204, new ErrorCorrectionBlock(29, 46), new ErrorCorrectionBlock(14, 47)), - new ErrorCorrectionBlocks(1770, new ErrorCorrectionBlock(49, 24), new ErrorCorrectionBlock(10, 25)), - new ErrorCorrectionBlocks(2100, new ErrorCorrectionBlock(24, 15), new ErrorCorrectionBlock(46, 16))), - new QRCodeVersion( - 38, - 3362, - new ErrorCorrectionBlocks(660, new ErrorCorrectionBlock(4, 122), new ErrorCorrectionBlock(18, 123)), - new ErrorCorrectionBlocks(1260, new ErrorCorrectionBlock(13, 46), new ErrorCorrectionBlock(32, 47)), - new ErrorCorrectionBlocks(1860, new ErrorCorrectionBlock(48, 24), new ErrorCorrectionBlock(14, 25)), - new ErrorCorrectionBlocks(2220, new ErrorCorrectionBlock(42, 15), new ErrorCorrectionBlock(32, 16))), - new QRCodeVersion( - 39, - 3532, - new ErrorCorrectionBlocks(720, new ErrorCorrectionBlock(20, 117), new ErrorCorrectionBlock(4, 118)), - new ErrorCorrectionBlocks(1316, new ErrorCorrectionBlock(40, 47), new ErrorCorrectionBlock(7, 48)), - new ErrorCorrectionBlocks(1950, new ErrorCorrectionBlock(43, 24), new ErrorCorrectionBlock(22, 25)), - new ErrorCorrectionBlocks(2310, new ErrorCorrectionBlock(10, 15), new ErrorCorrectionBlock(67, 16))), - new QRCodeVersion( - 40, - 3706, - new ErrorCorrectionBlocks(750, new ErrorCorrectionBlock(19, 118), new ErrorCorrectionBlock(6, 119)), - new ErrorCorrectionBlocks(1372, new ErrorCorrectionBlock(18, 47), new ErrorCorrectionBlock(31, 48)), - new ErrorCorrectionBlocks(2040, new ErrorCorrectionBlock(34, 24), new ErrorCorrectionBlock(34, 25)), - new ErrorCorrectionBlocks(2430, new ErrorCorrectionBlock(20, 15), new ErrorCorrectionBlock(61, 16))), - }; - } -} + private static QRCodeVersion[] Initialize() + { + return new QRCodeVersion[] + { + new QRCodeVersion( + 1, + 26, + new ErrorCorrectionBlocks(7, new ErrorCorrectionBlock(1, 19)), + new ErrorCorrectionBlocks(10, new ErrorCorrectionBlock(1, 16)), + new ErrorCorrectionBlocks(13, new ErrorCorrectionBlock(1, 13)), + new ErrorCorrectionBlocks(17, new ErrorCorrectionBlock(1, 9))), + new QRCodeVersion( + 2, + 44, + new ErrorCorrectionBlocks(10, new ErrorCorrectionBlock(1, 34)), + new ErrorCorrectionBlocks(16, new ErrorCorrectionBlock(1, 28)), + new ErrorCorrectionBlocks(22, new ErrorCorrectionBlock(1, 22)), + new ErrorCorrectionBlocks(28, new ErrorCorrectionBlock(1, 16))), + new QRCodeVersion( + 3, + 70, + new ErrorCorrectionBlocks(15, new ErrorCorrectionBlock(1, 55)), + new ErrorCorrectionBlocks(26, new ErrorCorrectionBlock(1, 44)), + new ErrorCorrectionBlocks(36, new ErrorCorrectionBlock(2, 17)), + new ErrorCorrectionBlocks(44, new ErrorCorrectionBlock(2, 13))), + new QRCodeVersion( + 4, + 100, + new ErrorCorrectionBlocks(20, new ErrorCorrectionBlock(1, 80)), + new ErrorCorrectionBlocks(36, new ErrorCorrectionBlock(2, 32)), + new ErrorCorrectionBlocks(52, new ErrorCorrectionBlock(2, 24)), + new ErrorCorrectionBlocks(64, new ErrorCorrectionBlock(4, 9))), + new QRCodeVersion( + 5, + 134, + new ErrorCorrectionBlocks(26, new ErrorCorrectionBlock(1, 108)), + new ErrorCorrectionBlocks(48, new ErrorCorrectionBlock(2, 43)), + new ErrorCorrectionBlocks(72, new ErrorCorrectionBlock(2, 15), new ErrorCorrectionBlock(2, 16)), + new ErrorCorrectionBlocks(88, new ErrorCorrectionBlock(2, 11), new ErrorCorrectionBlock(2, 12))), + new QRCodeVersion( + 6, + 172, + new ErrorCorrectionBlocks(36, new ErrorCorrectionBlock(2, 68)), + new ErrorCorrectionBlocks(64, new ErrorCorrectionBlock(4, 27)), + new ErrorCorrectionBlocks(96, new ErrorCorrectionBlock(4, 19)), + new ErrorCorrectionBlocks(112, new ErrorCorrectionBlock(4, 15))), + new QRCodeVersion( + 7, + 196, + new ErrorCorrectionBlocks(40, new ErrorCorrectionBlock(2, 78)), + new ErrorCorrectionBlocks(72, new ErrorCorrectionBlock(4, 31)), + new ErrorCorrectionBlocks(108, new ErrorCorrectionBlock(2, 14), new ErrorCorrectionBlock(4, 15)), + new ErrorCorrectionBlocks(130, new ErrorCorrectionBlock(4, 13), new ErrorCorrectionBlock(1, 14))), + new QRCodeVersion( + 8, + 242, + new ErrorCorrectionBlocks(48, new ErrorCorrectionBlock(2, 97)), + new ErrorCorrectionBlocks(88, new ErrorCorrectionBlock(2, 38), new ErrorCorrectionBlock(2, 39)), + new ErrorCorrectionBlocks(132, new ErrorCorrectionBlock(4, 18), new ErrorCorrectionBlock(2, 19)), + new ErrorCorrectionBlocks(156, new ErrorCorrectionBlock(4, 14), new ErrorCorrectionBlock(2, 15))), + new QRCodeVersion( + 9, + 292, + new ErrorCorrectionBlocks(60, new ErrorCorrectionBlock(2, 116)), + new ErrorCorrectionBlocks(110, new ErrorCorrectionBlock(3, 36), new ErrorCorrectionBlock(2, 37)), + new ErrorCorrectionBlocks(160, new ErrorCorrectionBlock(4, 16), new ErrorCorrectionBlock(4, 17)), + new ErrorCorrectionBlocks(192, new ErrorCorrectionBlock(4, 12), new ErrorCorrectionBlock(4, 13))), + new QRCodeVersion( + 10, + 346, + new ErrorCorrectionBlocks(72, new ErrorCorrectionBlock(2, 68), new ErrorCorrectionBlock(2, 69)), + new ErrorCorrectionBlocks(130, new ErrorCorrectionBlock(4, 43), new ErrorCorrectionBlock(1, 44)), + new ErrorCorrectionBlocks(192, new ErrorCorrectionBlock(6, 19), new ErrorCorrectionBlock(2, 20)), + new ErrorCorrectionBlocks(224, new ErrorCorrectionBlock(6, 15), new ErrorCorrectionBlock(2, 16))), + new QRCodeVersion( + 11, + 404, + new ErrorCorrectionBlocks(80, new ErrorCorrectionBlock(4, 81)), + new ErrorCorrectionBlocks(150, new ErrorCorrectionBlock(1, 50), new ErrorCorrectionBlock(4, 51)), + new ErrorCorrectionBlocks(224, new ErrorCorrectionBlock(4, 22), new ErrorCorrectionBlock(4, 23)), + new ErrorCorrectionBlocks(264, new ErrorCorrectionBlock(3, 12), new ErrorCorrectionBlock(8, 13))), + new QRCodeVersion( + 12, + 466, + new ErrorCorrectionBlocks(96, new ErrorCorrectionBlock(2, 92), new ErrorCorrectionBlock(2, 93)), + new ErrorCorrectionBlocks(176, new ErrorCorrectionBlock(6, 36), new ErrorCorrectionBlock(2, 37)), + new ErrorCorrectionBlocks(260, new ErrorCorrectionBlock(4, 20), new ErrorCorrectionBlock(6, 21)), + new ErrorCorrectionBlocks(308, new ErrorCorrectionBlock(7, 14), new ErrorCorrectionBlock(4, 15))), + new QRCodeVersion( + 13, + 532, + new ErrorCorrectionBlocks(104, new ErrorCorrectionBlock(4, 107)), + new ErrorCorrectionBlocks(198, new ErrorCorrectionBlock(8, 37), new ErrorCorrectionBlock(1, 38)), + new ErrorCorrectionBlocks(288, new ErrorCorrectionBlock(8, 20), new ErrorCorrectionBlock(4, 21)), + new ErrorCorrectionBlocks(352, new ErrorCorrectionBlock(12, 11), new ErrorCorrectionBlock(4, 12))), + new QRCodeVersion( + 14, + 581, + new ErrorCorrectionBlocks(120, new ErrorCorrectionBlock(3, 115), new ErrorCorrectionBlock(1, 116)), + new ErrorCorrectionBlocks(216, new ErrorCorrectionBlock(4, 40), new ErrorCorrectionBlock(5, 41)), + new ErrorCorrectionBlocks(320, new ErrorCorrectionBlock(11, 16), new ErrorCorrectionBlock(5, 17)), + new ErrorCorrectionBlocks(384, new ErrorCorrectionBlock(11, 12), new ErrorCorrectionBlock(5, 13))), + new QRCodeVersion( + 15, + 655, + new ErrorCorrectionBlocks(132, new ErrorCorrectionBlock(5, 87), new ErrorCorrectionBlock(1, 88)), + new ErrorCorrectionBlocks(240, new ErrorCorrectionBlock(5, 41), new ErrorCorrectionBlock(5, 42)), + new ErrorCorrectionBlocks(360, new ErrorCorrectionBlock(5, 24), new ErrorCorrectionBlock(7, 25)), + new ErrorCorrectionBlocks(432, new ErrorCorrectionBlock(11, 12), new ErrorCorrectionBlock(7, 13))), + new QRCodeVersion( + 16, + 733, + new ErrorCorrectionBlocks(144, new ErrorCorrectionBlock(5, 98), new ErrorCorrectionBlock(1, 99)), + new ErrorCorrectionBlocks(280, new ErrorCorrectionBlock(7, 45), new ErrorCorrectionBlock(3, 46)), + new ErrorCorrectionBlocks(408, new ErrorCorrectionBlock(15, 19), new ErrorCorrectionBlock(2, 20)), + new ErrorCorrectionBlocks(480, new ErrorCorrectionBlock(3, 15), new ErrorCorrectionBlock(13, 16))), + new QRCodeVersion( + 17, + 815, + new ErrorCorrectionBlocks(168, new ErrorCorrectionBlock(1, 107), new ErrorCorrectionBlock(5, 108)), + new ErrorCorrectionBlocks(308, new ErrorCorrectionBlock(10, 46), new ErrorCorrectionBlock(1, 47)), + new ErrorCorrectionBlocks(448, new ErrorCorrectionBlock(1, 22), new ErrorCorrectionBlock(15, 23)), + new ErrorCorrectionBlocks(532, new ErrorCorrectionBlock(2, 14), new ErrorCorrectionBlock(17, 15))), + new QRCodeVersion( + 18, + 901, + new ErrorCorrectionBlocks(180, new ErrorCorrectionBlock(5, 120), new ErrorCorrectionBlock(1, 121)), + new ErrorCorrectionBlocks(338, new ErrorCorrectionBlock(9, 43), new ErrorCorrectionBlock(4, 44)), + new ErrorCorrectionBlocks(504, new ErrorCorrectionBlock(17, 22), new ErrorCorrectionBlock(1, 23)), + new ErrorCorrectionBlocks(588, new ErrorCorrectionBlock(2, 14), new ErrorCorrectionBlock(19, 15))), + new QRCodeVersion( + 19, + 991, + new ErrorCorrectionBlocks(196, new ErrorCorrectionBlock(3, 113), new ErrorCorrectionBlock(4, 114)), + new ErrorCorrectionBlocks(364, new ErrorCorrectionBlock(3, 44), new ErrorCorrectionBlock(11, 45)), + new ErrorCorrectionBlocks(546, new ErrorCorrectionBlock(17, 21), new ErrorCorrectionBlock(4, 22)), + new ErrorCorrectionBlocks(650, new ErrorCorrectionBlock(9, 13), new ErrorCorrectionBlock(16, 14))), + new QRCodeVersion( + 20, + 1085, + new ErrorCorrectionBlocks(224, new ErrorCorrectionBlock(3, 107), new ErrorCorrectionBlock(5, 108)), + new ErrorCorrectionBlocks(416, new ErrorCorrectionBlock(3, 41), new ErrorCorrectionBlock(13, 42)), + new ErrorCorrectionBlocks(600, new ErrorCorrectionBlock(15, 24), new ErrorCorrectionBlock(5, 25)), + new ErrorCorrectionBlocks(700, new ErrorCorrectionBlock(15, 15), new ErrorCorrectionBlock(10, 16))), + new QRCodeVersion( + 21, + 1156, + new ErrorCorrectionBlocks(224, new ErrorCorrectionBlock(4, 116), new ErrorCorrectionBlock(4, 117)), + new ErrorCorrectionBlocks(442, new ErrorCorrectionBlock(17, 42)), + new ErrorCorrectionBlocks(644, new ErrorCorrectionBlock(17, 22), new ErrorCorrectionBlock(6, 23)), + new ErrorCorrectionBlocks(750, new ErrorCorrectionBlock(19, 16), new ErrorCorrectionBlock(6, 17))), + new QRCodeVersion( + 22, + 1258, + new ErrorCorrectionBlocks(252, new ErrorCorrectionBlock(2, 111), new ErrorCorrectionBlock(7, 112)), + new ErrorCorrectionBlocks(476, new ErrorCorrectionBlock(17, 46)), + new ErrorCorrectionBlocks(690, new ErrorCorrectionBlock(7, 24), new ErrorCorrectionBlock(16, 25)), + new ErrorCorrectionBlocks(816, new ErrorCorrectionBlock(34, 13))), + new QRCodeVersion( + 23, + 1364, + new ErrorCorrectionBlocks(270, new ErrorCorrectionBlock(4, 121), new ErrorCorrectionBlock(5, 122)), + new ErrorCorrectionBlocks(504, new ErrorCorrectionBlock(4, 47), new ErrorCorrectionBlock(14, 48)), + new ErrorCorrectionBlocks(750, new ErrorCorrectionBlock(11, 24), new ErrorCorrectionBlock(14, 25)), + new ErrorCorrectionBlocks(900, new ErrorCorrectionBlock(16, 15), new ErrorCorrectionBlock(14, 16))), + new QRCodeVersion( + 24, + 1474, + new ErrorCorrectionBlocks(300, new ErrorCorrectionBlock(6, 117), new ErrorCorrectionBlock(4, 118)), + new ErrorCorrectionBlocks(560, new ErrorCorrectionBlock(6, 45), new ErrorCorrectionBlock(14, 46)), + new ErrorCorrectionBlocks(810, new ErrorCorrectionBlock(11, 24), new ErrorCorrectionBlock(16, 25)), + new ErrorCorrectionBlocks(960, new ErrorCorrectionBlock(30, 16), new ErrorCorrectionBlock(2, 17))), + new QRCodeVersion( + 25, + 1588, + new ErrorCorrectionBlocks(312, new ErrorCorrectionBlock(8, 106), new ErrorCorrectionBlock(4, 107)), + new ErrorCorrectionBlocks(588, new ErrorCorrectionBlock(8, 47), new ErrorCorrectionBlock(13, 48)), + new ErrorCorrectionBlocks(870, new ErrorCorrectionBlock(7, 24), new ErrorCorrectionBlock(22, 25)), + new ErrorCorrectionBlocks(1050, new ErrorCorrectionBlock(22, 15), new ErrorCorrectionBlock(13, 16))), + new QRCodeVersion( + 26, + 1706, + new ErrorCorrectionBlocks(336, new ErrorCorrectionBlock(10, 114), new ErrorCorrectionBlock(2, 115)), + new ErrorCorrectionBlocks(644, new ErrorCorrectionBlock(19, 46), new ErrorCorrectionBlock(4, 47)), + new ErrorCorrectionBlocks(952, new ErrorCorrectionBlock(28, 22), new ErrorCorrectionBlock(6, 23)), + new ErrorCorrectionBlocks(1110, new ErrorCorrectionBlock(33, 16), new ErrorCorrectionBlock(4, 17))), + new QRCodeVersion( + 27, + 1828, + new ErrorCorrectionBlocks(360, new ErrorCorrectionBlock(8, 122), new ErrorCorrectionBlock(4, 123)), + new ErrorCorrectionBlocks(700, new ErrorCorrectionBlock(22, 45), new ErrorCorrectionBlock(3, 46)), + new ErrorCorrectionBlocks(1020, new ErrorCorrectionBlock(8, 23), new ErrorCorrectionBlock(26, 24)), + new ErrorCorrectionBlocks(1200, new ErrorCorrectionBlock(12, 15), new ErrorCorrectionBlock(28, 16))), + new QRCodeVersion( + 28, + 1921, + new ErrorCorrectionBlocks(390, new ErrorCorrectionBlock(3, 117), new ErrorCorrectionBlock(10, 118)), + new ErrorCorrectionBlocks(728, new ErrorCorrectionBlock(3, 45), new ErrorCorrectionBlock(23, 46)), + new ErrorCorrectionBlocks(1050, new ErrorCorrectionBlock(4, 24), new ErrorCorrectionBlock(31, 25)), + new ErrorCorrectionBlocks(1260, new ErrorCorrectionBlock(11, 15), new ErrorCorrectionBlock(31, 16))), + new QRCodeVersion( + 29, + 2051, + new ErrorCorrectionBlocks(420, new ErrorCorrectionBlock(7, 116), new ErrorCorrectionBlock(7, 117)), + new ErrorCorrectionBlocks(784, new ErrorCorrectionBlock(21, 45), new ErrorCorrectionBlock(7, 46)), + new ErrorCorrectionBlocks(1140, new ErrorCorrectionBlock(1, 23), new ErrorCorrectionBlock(37, 24)), + new ErrorCorrectionBlocks(1350, new ErrorCorrectionBlock(19, 15), new ErrorCorrectionBlock(26, 16))), + new QRCodeVersion( + 30, + 2185, + new ErrorCorrectionBlocks(450, new ErrorCorrectionBlock(5, 115), new ErrorCorrectionBlock(10, 116)), + new ErrorCorrectionBlocks(812, new ErrorCorrectionBlock(19, 47), new ErrorCorrectionBlock(10, 48)), + new ErrorCorrectionBlocks(1200, new ErrorCorrectionBlock(15, 24), new ErrorCorrectionBlock(25, 25)), + new ErrorCorrectionBlocks(1440, new ErrorCorrectionBlock(23, 15), new ErrorCorrectionBlock(25, 16))), + new QRCodeVersion( + 31, + 2323, + new ErrorCorrectionBlocks(480, new ErrorCorrectionBlock(13, 115), new ErrorCorrectionBlock(3, 116)), + new ErrorCorrectionBlocks(868, new ErrorCorrectionBlock(2, 46), new ErrorCorrectionBlock(29, 47)), + new ErrorCorrectionBlocks(1290, new ErrorCorrectionBlock(42, 24), new ErrorCorrectionBlock(1, 25)), + new ErrorCorrectionBlocks(1530, new ErrorCorrectionBlock(23, 15), new ErrorCorrectionBlock(28, 16))), + new QRCodeVersion( + 32, + 2465, + new ErrorCorrectionBlocks(510, new ErrorCorrectionBlock(17, 115)), + new ErrorCorrectionBlocks(924, new ErrorCorrectionBlock(10, 46), new ErrorCorrectionBlock(23, 47)), + new ErrorCorrectionBlocks(1350, new ErrorCorrectionBlock(10, 24), new ErrorCorrectionBlock(35, 25)), + new ErrorCorrectionBlocks(1620, new ErrorCorrectionBlock(19, 15), new ErrorCorrectionBlock(35, 16))), + new QRCodeVersion( + 33, + 2611, + new ErrorCorrectionBlocks(540, new ErrorCorrectionBlock(17, 115), new ErrorCorrectionBlock(1, 116)), + new ErrorCorrectionBlocks(980, new ErrorCorrectionBlock(14, 46), new ErrorCorrectionBlock(21, 47)), + new ErrorCorrectionBlocks(1440, new ErrorCorrectionBlock(29, 24), new ErrorCorrectionBlock(19, 25)), + new ErrorCorrectionBlocks(1710, new ErrorCorrectionBlock(11, 15), new ErrorCorrectionBlock(46, 16))), + new QRCodeVersion( + 34, + 2761, + new ErrorCorrectionBlocks(570, new ErrorCorrectionBlock(13, 115), new ErrorCorrectionBlock(6, 116)), + new ErrorCorrectionBlocks(1036, new ErrorCorrectionBlock(14, 46), new ErrorCorrectionBlock(23, 47)), + new ErrorCorrectionBlocks(1530, new ErrorCorrectionBlock(44, 24), new ErrorCorrectionBlock(7, 25)), + new ErrorCorrectionBlocks(1800, new ErrorCorrectionBlock(59, 16), new ErrorCorrectionBlock(1, 17))), + new QRCodeVersion( + 35, + 2876, + new ErrorCorrectionBlocks(570, new ErrorCorrectionBlock(12, 121), new ErrorCorrectionBlock(7, 122)), + new ErrorCorrectionBlocks(1064, new ErrorCorrectionBlock(12, 47), new ErrorCorrectionBlock(26, 48)), + new ErrorCorrectionBlocks(1590, new ErrorCorrectionBlock(39, 24), new ErrorCorrectionBlock(14, 25)), + new ErrorCorrectionBlocks(1890, new ErrorCorrectionBlock(22, 15), new ErrorCorrectionBlock(41, 16))), + new QRCodeVersion( + 36, + 3034, + new ErrorCorrectionBlocks(600, new ErrorCorrectionBlock(6, 121), new ErrorCorrectionBlock(14, 122)), + new ErrorCorrectionBlocks(1120, new ErrorCorrectionBlock(6, 47), new ErrorCorrectionBlock(34, 48)), + new ErrorCorrectionBlocks(1680, new ErrorCorrectionBlock(46, 24), new ErrorCorrectionBlock(10, 25)), + new ErrorCorrectionBlocks(1980, new ErrorCorrectionBlock(2, 15), new ErrorCorrectionBlock(64, 16))), + new QRCodeVersion( + 37, + 3196, + new ErrorCorrectionBlocks(630, new ErrorCorrectionBlock(17, 122), new ErrorCorrectionBlock(4, 123)), + new ErrorCorrectionBlocks(1204, new ErrorCorrectionBlock(29, 46), new ErrorCorrectionBlock(14, 47)), + new ErrorCorrectionBlocks(1770, new ErrorCorrectionBlock(49, 24), new ErrorCorrectionBlock(10, 25)), + new ErrorCorrectionBlocks(2100, new ErrorCorrectionBlock(24, 15), new ErrorCorrectionBlock(46, 16))), + new QRCodeVersion( + 38, + 3362, + new ErrorCorrectionBlocks(660, new ErrorCorrectionBlock(4, 122), new ErrorCorrectionBlock(18, 123)), + new ErrorCorrectionBlocks(1260, new ErrorCorrectionBlock(13, 46), new ErrorCorrectionBlock(32, 47)), + new ErrorCorrectionBlocks(1860, new ErrorCorrectionBlock(48, 24), new ErrorCorrectionBlock(14, 25)), + new ErrorCorrectionBlocks(2220, new ErrorCorrectionBlock(42, 15), new ErrorCorrectionBlock(32, 16))), + new QRCodeVersion( + 39, + 3532, + new ErrorCorrectionBlocks(720, new ErrorCorrectionBlock(20, 117), new ErrorCorrectionBlock(4, 118)), + new ErrorCorrectionBlocks(1316, new ErrorCorrectionBlock(40, 47), new ErrorCorrectionBlock(7, 48)), + new ErrorCorrectionBlocks(1950, new ErrorCorrectionBlock(43, 24), new ErrorCorrectionBlock(22, 25)), + new ErrorCorrectionBlocks(2310, new ErrorCorrectionBlock(10, 15), new ErrorCorrectionBlock(67, 16))), + new QRCodeVersion( + 40, + 3706, + new ErrorCorrectionBlocks(750, new ErrorCorrectionBlock(19, 118), new ErrorCorrectionBlock(6, 119)), + new ErrorCorrectionBlocks(1372, new ErrorCorrectionBlock(18, 47), new ErrorCorrectionBlock(31, 48)), + new ErrorCorrectionBlocks(2040, new ErrorCorrectionBlock(34, 24), new ErrorCorrectionBlock(34, 25)), + new ErrorCorrectionBlocks(2430, new ErrorCorrectionBlock(20, 15), new ErrorCorrectionBlock(61, 16))), + }; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/QrCode/QrCode.cs b/Toolkit.UI.Controls.Avalonia/QrCode/QrCode.cs index 7c14ba5..ff5c6ea 100644 --- a/Toolkit.UI.Controls.Avalonia/QrCode/QrCode.cs +++ b/Toolkit.UI.Controls.Avalonia/QrCode/QrCode.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Threading.Tasks; using Avalonia; using Avalonia.Animation; using Avalonia.Controls; @@ -10,6 +6,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Media; using Avalonia.Threading; using Gma.QrCodeNet.Encoding; +using System.Collections; namespace Toolkit.UI.Controls.Avalonia; @@ -20,41 +17,48 @@ namespace Toolkit.UI.Controls.Avalonia; public class QrCode : Control { #region Properties + /// /// Property for the Background brush (i.e. the area that has no data) /// public static readonly StyledProperty BackgroundProperty = Border.BackgroundProperty.AddOwner(); + /// /// Property for the Foreground brush (i.e. the actual data) /// public static readonly StyledProperty ForegroundProperty = TextElement.ForegroundProperty.AddOwner(); + /// /// Property indicating how rounded the corners will be /// public static readonly StyledProperty CornerRadiusProperty = Border.CornerRadiusProperty.AddOwner(); + /// /// Property indicating the Quiet Zone (distance between the edge of the control and where the data actually starts) - /// + /// /// Note: The Quiet Zone (aka Padding) is defined in the QC Code standard (ISO 18004) as the width of 4 modules on all /// sides, but is implemented separately in this control. Official support may wish to remove this property as adjusting /// it will technically make the generated QRCodes "non-standard". This implementation does not currently concern itself /// with this as the code itself it not meant for public consumption. /// public static readonly StyledProperty PaddingProperty = Decorator.PaddingProperty.AddOwner(); + /// /// Property indicating whether the Quiet Zone of 4 modules should be added to the QR Code as additional padding. Default: True /// /// Note: Disabling the Quiet Zone makes the generated QRCodes "non-standard" according to the ISO 18004 standard. /// The padding created by the Quiet Zone depends on the module size and therefore on the amount of data. This can be - /// disabled and a fixed can be set instead to have more control over the layout. + /// disabled and a fixed can be set instead to have more control over the layout. /// public static readonly StyledProperty IsQuietZoneEnabledProperty = AvaloniaProperty.Register(nameof(IsQuietZoneEnabled), true); + /// /// Property indicating the Error Correction Code of the generated data. Default: Medium /// /// Note: See for the specific definitions of each value. /// public static readonly StyledProperty ErrorCorrectionProperty = AvaloniaProperty.Register(nameof(ErrorCorrection), EccLevel.Medium); + /// /// Property for the data represented in the QRCode /// @@ -66,52 +70,61 @@ public class QrCode : Control get => GetValue(BackgroundProperty) ?? Brushes.White; set => SetValue(BackgroundProperty, value); } + /// public IBrush Foreground { get => GetValue(ForegroundProperty) ?? Brushes.Black; set => SetValue(ForegroundProperty, value); } + /// public CornerRadius CornerRadius { get => GetValue(CornerRadiusProperty); set => SetValue(CornerRadiusProperty, value); } + /// public Thickness Padding { get => GetValue(PaddingProperty); set => SetValue(PaddingProperty, value); } + /// public bool IsQuietZoneEnabled { get => GetValue(IsQuietZoneEnabledProperty); set => SetValue(IsQuietZoneEnabledProperty, value); } + /// public EccLevel ErrorCorrection { get => GetValue(ErrorCorrectionProperty); set => SetValue(ErrorCorrectionProperty, value); } + /// public string? Data { get => GetValue(DataProperty); set => SetValue(DataProperty, value); } - #endregion + + #endregion Properties /// - /// Engine to actually calculate the bit matrix of the QRCode. Currently a Nuget package, but official support may wish to implement and remove such dependency + /// Engine to actually calculate the bit matrix of the QRCode. Currently a Nuget package, but official support may wish to implement and remove such dependency /// private static readonly QrEncoder QrCodeGenerator = new(); + /// /// A cache of currently set bits in the bit matrix. This is used to potentially speed up processing. /// private readonly Hashtable _setBitsTable = new(); + /// /// A cache of the last encoded QRCode. This is used to reuse the last generated data whenever a style property like Width, Height or Padding was changed. /// @@ -119,6 +132,7 @@ public class QrCode : Control // QRCode specs mandate a standard 4-symbol-sized space on each side of the data. We support custom Padding and will ignore this zone when processing private int QuietZoneCount => IsQuietZoneEnabled ? 4 : 0; + private int QuietMargin => QuietZoneCount * 2; /// @@ -170,7 +184,7 @@ public class QrCode : Control { // Error Correction change requires the data to be reprocessed to recalculate the new bit matrix. This is unavoidable. case nameof(ErrorCorrection): - // A change in data obviously indicates the need to update the bit matrix + // A change in data obviously indicates the need to update the bit matrix case nameof(Data): _encodedQrCode = null; break; @@ -213,7 +227,6 @@ public class QrCode : Control _oldQrCodeGeometry = null; InvalidateVisual(); - }); } @@ -694,7 +707,6 @@ public class QrCode : Control // Render background over the foreground as the geometry has "cut outs" that allow the foreground to show through context.DrawGeometry(Background, null, newGeometry); } - } /// @@ -706,14 +718,17 @@ public class QrCode : Control /// The lowest level of error correction where up to ~7% of data can be be recovered if lost and uses the least amount of symbols to represent the data /// Lowest, + /// /// The standard level of error correction where up to ~15% of data can be be recovered if lost and represents a good compromise between a small size and reliability /// Medium, + /// /// A high readability level of error correction where up to ~25% of data can be be recovered if lost but requires a larger footprint to represent the data /// Quality, + /// /// The maximum level of error correction where up to ~30% of data can be be recovered if lost and represents the maximum achievable reliability /// @@ -722,7 +737,7 @@ public class QrCode : Control /// /// Converts from our EccLevel to the one used by whichever algorithm being used. - /// This exists as an abstraction layer for if/when the package or namespace of the actual QR Generator changes so that breaking changes are not introduced + /// This exists as an abstraction layer for if/when the package or namespace of the actual QR Generator changes so that breaking changes are not introduced /// /// The selected ECC Level to convert /// The appropriate ECC Level type used by the generator @@ -748,4 +763,4 @@ public class QrCode : Control BottomRight = 1 << 2, BottomLeft = 1 << 3 } -} +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/ResponsiveGrid/ResponsiveGrid.cs b/Toolkit.UI.Controls.Avalonia/ResponsiveGrid/ResponsiveGrid.cs new file mode 100644 index 0000000..29b6b4c --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/ResponsiveGrid/ResponsiveGrid.cs @@ -0,0 +1,335 @@ +using Avalonia; +using Avalonia.Controls; + +namespace Toolkit.UI.Controls.Avalonia; + +public class ResponsiveGrid : Grid +{ + public static readonly AvaloniaProperty ActualColumnProperty = + AvaloniaProperty.RegisterAttached("ActualColumn", 0); + + public static readonly AvaloniaProperty ActualRowProperty = + AvaloniaProperty.RegisterAttached("ActualRow", 0); + + public static readonly StyledProperty BreakPointsProperty = + AvaloniaProperty.Register(nameof(Thresholds)); + + public static readonly AvaloniaProperty LargeOffsetProperty = + AvaloniaProperty.RegisterAttached("LargeOffset", 0); + + public static readonly AvaloniaProperty LargePullProperty = + AvaloniaProperty.RegisterAttached("LargePull", 0); + + public static readonly AvaloniaProperty LargePushProperty = + AvaloniaProperty.RegisterAttached("LargePush", 0); + + public static readonly AvaloniaProperty LargeProperty = + AvaloniaProperty.RegisterAttached("Large", 0); + + public static readonly StyledProperty MaxDivisionProperty = + AvaloniaProperty.Register(nameof(MaxDivision), 12); + + public static readonly AvaloniaProperty MediumOffsetProperty = + AvaloniaProperty.RegisterAttached("MediumOffset", 0); + + public static readonly AvaloniaProperty MediumPullProperty = + AvaloniaProperty.RegisterAttached("MediumPull", 0); + + public static readonly AvaloniaProperty MediumPushProperty = + AvaloniaProperty.RegisterAttached("MediumPush", 0); + + public static readonly AvaloniaProperty MediumProperty = + AvaloniaProperty.RegisterAttached("Medium", 0); + + public static readonly AvaloniaProperty SmallOffsetProperty = + AvaloniaProperty.RegisterAttached("SmallOffset", 0); + + public static readonly AvaloniaProperty SmallPullProperty = + AvaloniaProperty.RegisterAttached("SmallPull", 0); + + public static readonly AvaloniaProperty SmallPushProperty = + AvaloniaProperty.RegisterAttached("SmallPush", 0); + + public static readonly AvaloniaProperty SmallProperty = + AvaloniaProperty.RegisterAttached("Small", 0); + + public static readonly AvaloniaProperty ExtraSmallOffsetProperty = + AvaloniaProperty.RegisterAttached("ExtraSmallOffset", 0); + + public static readonly AvaloniaProperty ExtraSmallPullProperty = + AvaloniaProperty.RegisterAttached("ExtraSmallPull", 0); + + public static readonly AvaloniaProperty ExtraSmallPushProperty = + AvaloniaProperty.RegisterAttached("ExtraSmallPush", 0); + + public static readonly AvaloniaProperty ExtraSmallProperty = + AvaloniaProperty.RegisterAttached("ExtraSmall", 0); + + static ResponsiveGrid() + { + AffectsMeasure( + MaxDivisionProperty, + BreakPointsProperty, + LargeProperty, + MediumProperty, + SmallProperty, + ExtraSmallProperty, + LargeOffsetProperty, + LargePullProperty, + LargePushProperty, + MediumOffsetProperty, + MediumPullProperty, + MediumPushProperty, + SmallOffsetProperty, + SmallPullProperty, + SmallPushProperty, + ExtraSmallOffsetProperty, + ExtraSmallPullProperty, + ExtraSmallPushProperty + ); + } + + public ResponsiveGrid() + { + MaxDivision = 12; + Thresholds = new SizeThresholds(); + } + + public int MaxDivision + { + get => GetValue(MaxDivisionProperty); + set => SetValue(MaxDivisionProperty, value); + } + + public SizeThresholds Thresholds + { + get => GetValue(BreakPointsProperty); + set => SetValue(BreakPointsProperty, value); + } + + public static int GetActualColumn(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(ActualColumnProperty); + + public static int GetActualRow(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(ActualRowProperty); + + public static int GetLarge(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(LargeProperty); + + public static int GetLargeOffset(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(LargeOffsetProperty); + + public static int GetLargePull(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(LargePullProperty); + + public static int GetLargePush(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(LargePushProperty); + + public static int GetMedium(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(MediumProperty); + + public static int GetMediumOffset(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(MediumOffsetProperty); + + public static int GetMediumPull(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(MediumPullProperty); + + public static int GetMediumPush(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(MediumPushProperty); + + public static int GetSmall(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(SmallProperty); + + public static int GetSmallOffset(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(SmallOffsetProperty); + + public static int GetSmallPull(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(SmallPullProperty); + + public static int GetSmallPush(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(SmallPushProperty); + + public static int GetExtraSmall(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(ExtraSmallProperty); + + public static int GetExtraSmallOffset(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(ExtraSmallOffsetProperty); + + public static int GetExtraSmallPull(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(ExtraSmallPullProperty); + + public static int GetExtraSmallPush(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(ExtraSmallPushProperty); + + public static void SetLarge(AvaloniaObject avaloniaObject, int value) => + avaloniaObject.SetValue(LargeProperty, value); + + public static void SetLargeOffset(AvaloniaObject avaloniaObject, int value) => + avaloniaObject.SetValue(LargeOffsetProperty, value); + + public static void SetLargePull(AvaloniaObject avaloniaObject, int value) => + avaloniaObject.SetValue(LargePullProperty, value); + + public static void SetLargePush(AvaloniaObject avaloniaObject, int value) => + avaloniaObject.SetValue(LargePushProperty, value); + + public static void SetMedium(AvaloniaObject avaloniaObject, int value) => + avaloniaObject.SetValue(MediumProperty, value); + + public static void SetMediumOffset(AvaloniaObject avaloniaObject, int value) => + avaloniaObject.SetValue(MediumOffsetProperty, value); + + public static void SetMediumPull(AvaloniaObject avaloniaObject, int value) => + avaloniaObject.SetValue(MediumPullProperty, value); + + public static void SetMediumPush(AvaloniaObject avaloniaObject, int value) => + avaloniaObject.SetValue(MediumPushProperty, value); + + public static void SetSmall(AvaloniaObject avaloniaObject, int value) => + avaloniaObject.SetValue(SmallProperty, value); + + public static void SetSmallOffset(AvaloniaObject avaloniaObject, int value) => + avaloniaObject.SetValue(SmallOffsetProperty, value); + + public static void SetSmallPull(AvaloniaObject avaloniaObject, int value) => + avaloniaObject.SetValue(SmallPullProperty, value); + + public static void SetSmallPush(AvaloniaObject avaloniaObject, int value) => + avaloniaObject.SetValue(SmallPushProperty, value); + + public static void SetExtraSmall(AvaloniaObject avaloniaObject, int value) => + avaloniaObject.SetValue(ExtraSmallProperty, value); + + public static void SetExtraSmallOffset(AvaloniaObject avaloniaObject, int value) => + avaloniaObject.SetValue(ExtraSmallOffsetProperty, value); + + public static void SetExtraSmallPull(AvaloniaObject avaloniaObject, int value) => + avaloniaObject.SetValue(ExtraSmallPullProperty, value); + + public static void SetExtraSmallPush(AvaloniaObject avaloniaObject, int value) => + avaloniaObject.SetValue(ExtraSmallPushProperty, value); + + protected static void SetActualColumn(AvaloniaObject avaloniaObject, int value) => + avaloniaObject.SetValue(ActualColumnProperty, value); + + protected static void SetActualRow(AvaloniaObject avaloniaObject, int value) => + avaloniaObject.SetValue(ActualRowProperty, value); + + protected override Size ArrangeOverride(Size finalSize) + { + double columnWidth = finalSize.Width / MaxDivision; + + IEnumerable> groupedRows = Children.OfType().GroupBy(GetActualRow); + + double yOffset = 0; + foreach (IGrouping row in groupedRows) + { + double maxRowHeight = row.Max(control => control.DesiredSize.Height); + + foreach (Control element in row) + { + int column = GetActualColumn(element); + int span = GetSpan(element, finalSize.Width); + + Rect rect = new(column * columnWidth, yOffset, span * columnWidth, maxRowHeight); + element.Arrange(rect); + } + + yOffset += maxRowHeight; + } + + return finalSize; + } + + protected int GetOffset(Control control, double width) + { + int GetXS(Control control) => GetExtraSmallOffset(control) is 0 ? 0 : GetExtraSmallOffset(control); + int GetSM(Control control) => GetSmallOffset(control) is 0 ? GetXS(control) : GetSmallOffset(control); + int GetMD(Control control) => GetMediumOffset(control) is 0 ? GetSM(control) : GetMediumOffset(control); + int GetLG(Control control) => GetLargeOffset(control) is 0 ? GetMD(control) : GetLargeOffset(control); + + int span = width < Thresholds.ExtraSmallToSmall ? GetXS(control) : width < Thresholds.SmallToMedium ? + GetSM(control) : width < Thresholds.MediumToLarge ? GetMD(control) : GetLG(control); + return Math.Min(span, MaxDivision); + } + + protected int GetPull(Control control, double width) + { + int GetXS(Control control) => GetExtraSmallPull(control) is 0 ? 0 : GetExtraSmallPull(control); + int GetSM(Control control) => GetSmallPull(control) is 0 ? GetXS(control) : GetSmallPull(control); + int GetMD(Control control) => GetMediumPull(control) is 0 ? GetSM(control) : GetMediumPull(control); + int GetLG(Control control) => GetLargePull(control) is 0 ? GetMD(control) : GetLargePull(control); + + int span = width < Thresholds.ExtraSmallToSmall ? GetXS(control) : width < Thresholds.SmallToMedium ? + GetSM(control) : width < Thresholds.MediumToLarge ? GetMD(control) : GetLG(control); + return Math.Min(span, MaxDivision); + } + + protected int GetPush(Control control, double width) + { + int GetXS(Control control) => GetExtraSmallPush(control) is 0 ? 0 : GetExtraSmallPush(control); + int GetSM(Control control) => GetSmallPush(control) is 0 ? GetXS(control) : GetSmallPush(control); + int GetMD(Control control) => GetMediumPush(control) is 0 ? GetSM(control) : GetMediumPush(control); + int GetLG(Control control) => GetLargePush(control) is 0 ? GetMD(control) : GetLargePush(control); + + int span = width < Thresholds.ExtraSmallToSmall ? GetXS(control) : width < Thresholds.SmallToMedium ? + GetSM(control) : width < Thresholds.MediumToLarge ? GetMD(control) : GetLG(control); + + return Math.Min(span, MaxDivision); + } + + protected int GetSpan(Control control, double width) + { + int GetXS(Control control) => GetExtraSmall(control) is 0 ? MaxDivision : GetExtraSmall(control); + int GetSM(Control control) => GetSmall(control) is 0 ? GetXS(control) : GetSmall(control); + int GetMD(Control control) => GetMedium(control) is 0 ? GetSM(control) : GetMedium(control); + int GetLG(Control control) => GetLarge(control) is 0 ? GetMD(control) : GetLarge(control); + + int span = width < Thresholds.ExtraSmallToSmall ? GetXS(control) : width < Thresholds.SmallToMedium ? + GetSM(control) : width < Thresholds.MediumToLarge ? GetMD(control) : GetLG(control); + + return Math.Min(Math.Max(0, span), MaxDivision); ; + } + + protected override Size MeasureOverride(Size availableSize) + { + int count = 0; + int currentRow = 0; + + double availableWidth = double.IsPositiveInfinity(availableSize.Width) + ? double.PositiveInfinity + : availableSize.Width / MaxDivision; + + foreach (Control control in Children.OfType()) + { + if (control.IsVisible) + { + int span = GetSpan(control, availableSize.Width); + int offset = GetOffset(control, availableSize.Width); + int push = GetPush(control, availableSize.Width); + int pull = GetPull(control, availableSize.Width); + + if (count + span + offset > MaxDivision) + { + currentRow++; + count = 0; + } + + SetActualColumn(control, count + offset + push - pull); + SetActualRow(control, currentRow); + + count += span + offset; + + Size size = new(availableWidth * span, double.PositiveInfinity); + control.Measure(size); + } + } + + IEnumerable> groupedRows = Children.OfType().GroupBy(GetActualRow); + + Size totalSize = new(groupedRows.Max(rows => rows.Sum(control => control.DesiredSize.Width)), + groupedRows.Sum(rows => rows.Max(control => control.DesiredSize.Height))); + + return totalSize; + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/ResponsiveGrid/SizeThresholds.cs b/Toolkit.UI.Controls.Avalonia/ResponsiveGrid/SizeThresholds.cs new file mode 100644 index 0000000..9e87bb8 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/ResponsiveGrid/SizeThresholds.cs @@ -0,0 +1,36 @@ +using Avalonia; +using System.ComponentModel; + +namespace Toolkit.UI.Controls.Avalonia +{ + [TypeConverter(typeof(SizeThresholdsTypeConverter))] + public class SizeThresholds : AvaloniaObject + { + public static readonly StyledProperty MediumToLargeProperty = + AvaloniaProperty.Register("MediumToLarge", 1200.0); + + public static readonly StyledProperty SmallToMediumProperty = + AvaloniaProperty.Register("SmallToMedium", 992.0); + + public static readonly StyledProperty ExtraSmallToSmallProperty = + AvaloniaProperty.Register("ExtraSmallToSmall", 768.0); + + public double MediumToLarge + { + get => (double)GetValue(MediumToLargeProperty); + set => SetValue(MediumToLargeProperty, value); + } + + public double SmallToMedium + { + get => (double)GetValue(SmallToMediumProperty); + set => SetValue(SmallToMediumProperty, value); + } + + public double ExtraSmallToSmall + { + get => (double)GetValue(ExtraSmallToSmallProperty); + set => SetValue(ExtraSmallToSmallProperty, value); + } + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/ResponsiveGrid/SizeThresholdsTypeConverter.cs b/Toolkit.UI.Controls.Avalonia/ResponsiveGrid/SizeThresholdsTypeConverter.cs new file mode 100644 index 0000000..30aefd2 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/ResponsiveGrid/SizeThresholdsTypeConverter.cs @@ -0,0 +1,44 @@ +using System.ComponentModel; +using System.Globalization; + +namespace Toolkit.UI.Controls.Avalonia +{ + public class SizeThresholdsTypeConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type? sourceType) + { + return sourceType == typeof(string); + } + + public override object ConvertFrom(ITypeDescriptorContext? context, + CultureInfo? culture, object value) + { + if (value is not string text) + { + return new SizeThresholds(); + } + + List values = text.Split(',') + .Select(o => o.Trim()) + .Select(o => int.TryParse(o, out var result) ? result : 0) + .ToList(); + + if (values.Count != 3) + { + return new SizeThresholds + { + ExtraSmallToSmall = 768, + SmallToMedium = 992, + MediumToLarge = 1200 + }; + } + + return new SizeThresholds + { + ExtraSmallToSmall = values[0], + SmallToMedium = values[1], + MediumToLarge = values[2] + }; + } + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpander.axaml b/Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpander.axaml index 61a2466..864a58b 100644 --- a/Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpander.axaml +++ b/Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpander.axaml @@ -1,11 +1,123 @@ - - - - + 0,4,0,0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -24,29 +136,197 @@ Name="Expander" IsExpanded="{TemplateBinding IsExpanded, Mode=TwoWay}" + Tag="{TemplateBinding IsExpandable}" Theme="{StaticResource SettingsExpanderExpanderStyle}"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpander.cs b/Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpander.cs index 90c8807..a1a1e08 100644 --- a/Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpander.cs +++ b/Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpander.cs @@ -1,33 +1,56 @@ using Avalonia; +using Avalonia.Controls.Templates; namespace Toolkit.UI.Controls.Avalonia; -public class SettingsExpander : FluentAvalonia.UI.Controls.SettingsExpander +public class SettingsExpander : + FluentAvalonia.UI.Controls.SettingsExpander { - protected override Type StyleKeyOverride => - typeof(SettingsExpander); + public static readonly StyledProperty ActionProperty = + AvaloniaProperty.Register(nameof(Action)); - public new static readonly StyledProperty DescriptionProperty = - AvaloniaProperty.Register(nameof(Description)); + public static readonly StyledProperty ActionTemplateProperty = + AvaloniaProperty.Register(nameof(ActionTemplate)); - public new object Description + public static readonly StyledProperty IconProperty = + AvaloniaProperty.Register(nameof(Icon)); + + public static readonly StyledProperty IconTemplateProperty = + AvaloniaProperty.Register(nameof(IconTemplate)); + + public static readonly StyledProperty IsExpandableProperty = + AvaloniaProperty.Register(nameof(IsExpandable)); + + public object Action { - get => GetValue(DescriptionProperty); - set => SetValue(DescriptionProperty, value); + get => GetValue(ActionProperty); + set => SetValue(ActionProperty, value); + } + + public IDataTemplate ActionTemplate + { + get => GetValue(ActionTemplateProperty); + set => SetValue(ActionTemplateProperty, value); + } + + public object Icon + { + get => GetValue(IconProperty); + set => SetValue(IconProperty, value); + } + + public IDataTemplate IconTemplate + { + get => GetValue(IconTemplateProperty); + set => SetValue(IconTemplateProperty, value); + } + + public bool IsExpandable + { + get => GetValue(IsExpandableProperty); + set => SetValue(IsExpandableProperty, value); } -} -public class SettingsExpanderItem : FluentAvalonia.UI.Controls.SettingsExpanderItem -{ protected override Type StyleKeyOverride => - typeof(SettingsExpanderItem); - - public new static readonly StyledProperty DescriptionProperty = - AvaloniaProperty.Register(nameof(Description)); - - public new object Description - { - get => GetValue(DescriptionProperty); - set => SetValue(DescriptionProperty, value); - } -} + typeof(SettingsExpander); +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpanderItem.axaml b/Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpanderItem.axaml deleted file mode 100644 index dfeacf4..0000000 --- a/Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpanderItem.axaml +++ /dev/null @@ -1,144 +0,0 @@ - - - - - 16 10 - 48 - 42 0 0 0 - 16 0 0 0 - 42 16 0 0 - 24 - 18 - 460 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpanderItem.cs b/Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpanderItem.cs new file mode 100644 index 0000000..e4a1def --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpanderItem.cs @@ -0,0 +1,63 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Templates; + +namespace Toolkit.UI.Controls.Avalonia; + +public class SettingsExpanderItem : + FluentAvalonia.UI.Controls.SettingsExpanderItem +{ + public static readonly StyledProperty ActionProperty = + AvaloniaProperty.Register(nameof(Action)); + + public static readonly StyledProperty ActionTemplateProperty = + AvaloniaProperty.Register(nameof(ActionTemplate)); + + public static readonly StyledProperty IconProperty = + AvaloniaProperty.Register(nameof(Icon)); + + public static readonly StyledProperty IconTemplateProperty = + AvaloniaProperty.Register(nameof(IconTemplate)); + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == IconProperty || change.Property == IconTemplateProperty) + { + UpdateIcon(); + } + } + + private void UpdateIcon() + { + PseudoClasses.Set(":icon", Icon is not null || IconTemplate is not null); + } + + public object Action + { + get => GetValue(ActionProperty); + set => SetValue(ActionProperty, value); + } + + public IDataTemplate ActionTemplate + { + get => GetValue(ActionTemplateProperty); + set => SetValue(ActionTemplateProperty, value); + } + + public object Icon + { + get => GetValue(IconProperty); + set => SetValue(IconProperty, value); + } + + public IDataTemplate IconTemplate + { + get => GetValue(IconTemplateProperty); + set => SetValue(IconTemplateProperty, value); + } + + protected override Type StyleKeyOverride => + typeof(SettingsExpanderItem); +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpanderToggleButton.cs b/Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpanderToggleButton.cs new file mode 100644 index 0000000..d52b192 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpanderToggleButton.cs @@ -0,0 +1,26 @@ +using Avalonia; +using Avalonia.Controls.Primitives; +using Avalonia.Input; + +namespace Toolkit.UI.Controls.Avalonia; + +public class SettingsExpanderToggleButton : + ToggleButton +{ + public static readonly StyledProperty IsToggleableProperty = + AvaloniaProperty.Register(nameof(IsToggleable)); + + public object IsToggleable + { + get => GetValue(IsToggleableProperty); + set => SetValue(IsToggleableProperty, value); + } + + protected override void OnKeyDown(KeyEventArgs args) + { + if (args.Key is not Key.Space) + { + base.OnKeyDown(args); + } + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/SpacedGrid/ISpacingDefinition.cs b/Toolkit.UI.Controls.Avalonia/SpacedGrid/ISpacingDefinition.cs index bfb9320..1ea1e1e 100644 --- a/Toolkit.UI.Controls.Avalonia/SpacedGrid/ISpacingDefinition.cs +++ b/Toolkit.UI.Controls.Avalonia/SpacedGrid/ISpacingDefinition.cs @@ -3,4 +3,4 @@ public interface ISpacingDefinition { double Spacing { get; set; } -} +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/SpacedGrid/SpacedGrid.cs b/Toolkit.UI.Controls.Avalonia/SpacedGrid/SpacedGrid.cs index df4d52a..7536872 100644 --- a/Toolkit.UI.Controls.Avalonia/SpacedGrid/SpacedGrid.cs +++ b/Toolkit.UI.Controls.Avalonia/SpacedGrid/SpacedGrid.cs @@ -59,7 +59,7 @@ public class SpacedGrid : Grid } } - private void OnCollectionChanged(object? sender, + private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs args) { if (args.Action == NotifyCollectionChangedAction.Add || args.Action == NotifyCollectionChangedAction.Replace) diff --git a/Toolkit.UI.Controls.Avalonia/SpacedGrid/SpacingColumnDefinition.cs b/Toolkit.UI.Controls.Avalonia/SpacedGrid/SpacingColumnDefinition.cs index 3b213f6..eff3fdc 100644 --- a/Toolkit.UI.Controls.Avalonia/SpacedGrid/SpacingColumnDefinition.cs +++ b/Toolkit.UI.Controls.Avalonia/SpacedGrid/SpacingColumnDefinition.cs @@ -11,4 +11,4 @@ public class SpacingColumnDefinition(double width) : get => Width.Value; set => Width = new GridLength(value, GridUnitType.Pixel); } -} +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/SpacedGrid/SpacingRowDefinition.cs b/Toolkit.UI.Controls.Avalonia/SpacedGrid/SpacingRowDefinition.cs index 6d6bc43..15909c8 100644 --- a/Toolkit.UI.Controls.Avalonia/SpacedGrid/SpacingRowDefinition.cs +++ b/Toolkit.UI.Controls.Avalonia/SpacedGrid/SpacingRowDefinition.cs @@ -11,4 +11,4 @@ public class SpacingRowDefinition(double height) : get => Height.Value; set => Height = new GridLength(value, GridUnitType.Pixel); } -} +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/TabStrip/TabStrip.axaml b/Toolkit.UI.Controls.Avalonia/TabStrip/TabStrip.axaml new file mode 100644 index 0000000..deaba4d --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/TabStrip/TabStrip.axaml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Toolkit.UI.Controls.Avalonia/TaskDialog/TaskDialog.axaml b/Toolkit.UI.Controls.Avalonia/TaskDialog/TaskDialog.axaml new file mode 100644 index 0000000..f6e97e2 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/TaskDialog/TaskDialog.axaml @@ -0,0 +1,395 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 320 + 648 + 184 + 800 + + More Details + Less Details + + 12 + 60 0 24 12 + 24 0 24 12 + 18 4 + 36 + 24 0 24 0 + 18 + + SemiBold + 20 + Normal + 16 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +