From 862e7b2e34f1e5d5a0a832fd223c8d5402e04c30 Mon Sep 17 00:00:00 2001 From: TheXamlGuy Date: Sat, 13 Apr 2024 11:41:33 +0100 Subject: [PATCH] Toolkit.UI.Controls.Avalonia --- .../AsyncImage/AsyncImage.cs | 8 + .../BlurBehind/BlurBehind.cs | 115 + .../CarouselView/CarouselView.axaml | 95 + .../CarouselView/CarouselView.cs | 313 ++ .../CarouselView/CarouselViewItem.cs | 12 + .../ContentDialog/ContentDialog.axaml | 357 +++ .../ContentDialog/ContentDialog.cs | 8 + .../FastNoiseBackgroundRenderer.cs | 166 ++ .../FastNoiseRendererOptions.cs | 23 + .../FastRendererBackground.cs | 44 + Toolkit.UI.Controls.Avalonia/Frame/Frame.cs | 8 + .../Helpers/FastNoiseLite.cs | 2510 +++++++++++++++++ .../Helpers/ScopedBatchHelper.cs | 22 + .../NavigationView/NavigationView.cs | 8 + .../NavigationView/NavigationViewItem.cs | 8 + .../Properties/Assembly.cs | 3 + .../QrCode/Encoding/BitList.cs | 127 + .../QrCode/Encoding/BitMatrix.cs | 24 + .../QrCode/Encoding/BitMatrixBase.cs | 31 + .../DataEncodation/CharCountIndicatorTable.cs | 48 + .../Encoding/DataEncodation/DataEncode.cs | 62 + .../QrCode/Encoding/DataEncodation/ECISet.cs | 255 ++ .../DataEncodation/EightBitByteEncoder.cs | 80 + .../DataEncodation/EncodationStruct.cs | 15 + .../Encoding/DataEncodation/EncoderBase.cs | 46 + .../InputRecognition/InputRecognise.cs | 57 + .../InputRecognition/ModeEncodeCheck.cs | 72 + .../InputRecognition/RecognitionStruct.cs | 12 + .../Encoding/EncodingRegion/BCHCalculator.cs | 60 + .../Encoding/EncodingRegion/Codeword.cs | 72 + .../EncodingRegion/FormatInformation.cs | 114 + .../EncodingRegion/VersionInformation.cs | 70 + .../Encoding/ErrorCorrection/ECGenerator.cs | 84 + .../QrCode/Encoding/ErrorCorrectionLevel.cs | 9 + .../Encoding/InputOutOfBoundaryException.cs | 17 + .../Encoding/Masking/MaskPatternType.cs | 13 + .../Encoding/Masking/MatrixExtensions.cs | 44 + .../QrCode/Encoding/Masking/Pattern.cs | 13 + .../QrCode/Encoding/Masking/Pattern0.cs | 14 + .../QrCode/Encoding/Masking/Pattern1.cs | 14 + .../QrCode/Encoding/Masking/Pattern2.cs | 14 + .../QrCode/Encoding/Masking/Pattern3.cs | 14 + .../QrCode/Encoding/Masking/Pattern4.cs | 14 + .../QrCode/Encoding/Masking/Pattern5.cs | 14 + .../QrCode/Encoding/Masking/Pattern6.cs | 13 + .../QrCode/Encoding/Masking/Pattern7.cs | 14 + .../QrCode/Encoding/Masking/PatternFactory.cs | 31 + .../Masking/Scoring/MatrixScoreCalculator.cs | 36 + .../Encoding/Masking/Scoring/Penalty.cs | 6 + .../Encoding/Masking/Scoring/Penalty1.cs | 85 + .../Encoding/Masking/Scoring/Penalty2.cs | 51 + .../Encoding/Masking/Scoring/Penalty3.cs | 141 + .../Encoding/Masking/Scoring/Penalty4.cs | 36 + .../Masking/Scoring/PenaltyFactory.cs | 30 + .../Encoding/Masking/Scoring/PenaltyRules.cs | 9 + .../QrCode/Encoding/MatrixPoint.cs | 20 + .../QrCode/Encoding/MatrixRectangle.cs | 32 + .../QrCode/Encoding/MatrixSize.cs | 19 + .../QrCode/Encoding/MatrixStatus.cs | 8 + .../Positioning/PositioninngPatternBuilder.cs | 14 + .../Positioning/Stencils/AlignmentPattern.cs | 105 + .../Stencils/DarkDotAtLeftBottom.cs | 17 + .../Stencils/PatternStencilBase.cs | 32 + .../Stencils/PositionDetectionPattern.cs | 41 + .../Positioning/Stencils/TimingPattern.cs | 35 + .../QrCode/Encoding/QRCodeConstantVariable.cs | 52 + .../QrCode/Encoding/QRCodeEncode.cs | 32 + .../QrCode/Encoding/QrCode.cs | 28 + .../QrCode/Encoding/QrEncoder.cs | 39 + .../Encoding/ReedSolomon/GaloisField256.cs | 122 + .../ReedSolomon/GeneratorPolynomial.cs | 62 + .../Encoding/ReedSolomon/PolyDivideStruct.cs | 15 + .../QrCode/Encoding/ReedSolomon/Polynomial.cs | 242 ++ .../ReedSolomon/ReedSolomonEncoder.cs | 91 + .../QrCode/Encoding/StateMatrix.cs | 28 + .../QrCode/Encoding/Terminate/Terminator.cs | 73 + .../QrCode/Encoding/TriStateMatrix.cs | 48 + .../QrCode/Encoding/VersionDetail.cs | 34 + .../Encoding/Versions/ErrorCorrectionBlock.cs | 15 + .../Versions/ErrorCorrectionBlocks.cs | 55 + .../QrCode/Encoding/Versions/QRCodeVersion.cs | 35 + .../Encoding/Versions/VersionControl.cs | 144 + .../Encoding/Versions/VersionControlStruct.cs | 8 + .../QrCode/Encoding/Versions/VersionTable.cs | 317 +++ Toolkit.UI.Controls.Avalonia/QrCode/QrCode.cs | 751 +++++ .../SettingsExpander/SettingsExpander.axaml | 52 + .../SettingsExpander/SettingsExpander.cs | 33 + .../SettingsExpanderItem.axaml | 144 + .../SpacedGrid/ISpacingDefinition.cs | 6 + .../SpacedGrid/SpacedGrid.cs | 166 ++ .../SpacedGrid/SpacingColumnDefinition.cs | 14 + .../SpacedGrid/SpacingRowDefinition.cs | 14 + .../Themes/ControlResources.axaml | 12 + .../Themes/ThemeResources.axaml | 10 + .../Themes/ThemeResources.axaml.cs | 13 + .../Toolkit.UI.Controls.Avalonia.csproj | 13 + Toolkit.sln | 6 + 97 files changed, 8558 insertions(+) create mode 100644 Toolkit.UI.Controls.Avalonia/AsyncImage/AsyncImage.cs create mode 100644 Toolkit.UI.Controls.Avalonia/BlurBehind/BlurBehind.cs create mode 100644 Toolkit.UI.Controls.Avalonia/CarouselView/CarouselView.axaml create mode 100644 Toolkit.UI.Controls.Avalonia/CarouselView/CarouselView.cs create mode 100644 Toolkit.UI.Controls.Avalonia/CarouselView/CarouselViewItem.cs create mode 100644 Toolkit.UI.Controls.Avalonia/ContentDialog/ContentDialog.axaml create mode 100644 Toolkit.UI.Controls.Avalonia/ContentDialog/ContentDialog.cs create mode 100644 Toolkit.UI.Controls.Avalonia/FastNoiseBackground/FastNoiseBackgroundRenderer.cs create mode 100644 Toolkit.UI.Controls.Avalonia/FastNoiseBackground/FastNoiseRendererOptions.cs create mode 100644 Toolkit.UI.Controls.Avalonia/FastNoiseBackground/FastRendererBackground.cs create mode 100644 Toolkit.UI.Controls.Avalonia/Frame/Frame.cs create mode 100644 Toolkit.UI.Controls.Avalonia/Helpers/FastNoiseLite.cs create mode 100644 Toolkit.UI.Controls.Avalonia/Helpers/ScopedBatchHelper.cs create mode 100644 Toolkit.UI.Controls.Avalonia/NavigationView/NavigationView.cs create mode 100644 Toolkit.UI.Controls.Avalonia/NavigationView/NavigationViewItem.cs create mode 100644 Toolkit.UI.Controls.Avalonia/Properties/Assembly.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/BitList.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/BitMatrix.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/BitMatrixBase.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/CharCountIndicatorTable.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/DataEncode.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/ECISet.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/EightBitByteEncoder.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/EncodationStruct.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/EncoderBase.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/InputRecognition/InputRecognise.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/InputRecognition/ModeEncodeCheck.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/DataEncodation/InputRecognition/RecognitionStruct.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/BCHCalculator.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/Codeword.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/FormatInformation.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/EncodingRegion/VersionInformation.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ErrorCorrection/ECGenerator.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ErrorCorrectionLevel.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/InputOutOfBoundaryException.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/MaskPatternType.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/MatrixExtensions.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern0.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern1.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern2.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern3.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern4.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern5.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern6.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Pattern7.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/PatternFactory.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/MatrixScoreCalculator.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty1.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty2.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty3.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/Penalty4.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/PenaltyFactory.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Masking/Scoring/PenaltyRules.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixPoint.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixRectangle.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixSize.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/MatrixStatus.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/PositioninngPatternBuilder.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/AlignmentPattern.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/DarkDotAtLeftBottom.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/PatternStencilBase.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/PositionDetectionPattern.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Positioning/Stencils/TimingPattern.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QRCodeConstantVariable.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QRCodeEncode.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QrCode.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/QrEncoder.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/GaloisField256.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/GeneratorPolynomial.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/PolyDivideStruct.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/Polynomial.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/ReedSolomon/ReedSolomonEncoder.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/StateMatrix.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Terminate/Terminator.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/TriStateMatrix.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/VersionDetail.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/ErrorCorrectionBlock.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/ErrorCorrectionBlocks.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/QRCodeVersion.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/VersionControl.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/VersionControlStruct.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/Encoding/Versions/VersionTable.cs create mode 100644 Toolkit.UI.Controls.Avalonia/QrCode/QrCode.cs create mode 100644 Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpander.axaml create mode 100644 Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpander.cs create mode 100644 Toolkit.UI.Controls.Avalonia/SettingsExpander/SettingsExpanderItem.axaml create mode 100644 Toolkit.UI.Controls.Avalonia/SpacedGrid/ISpacingDefinition.cs create mode 100644 Toolkit.UI.Controls.Avalonia/SpacedGrid/SpacedGrid.cs create mode 100644 Toolkit.UI.Controls.Avalonia/SpacedGrid/SpacingColumnDefinition.cs create mode 100644 Toolkit.UI.Controls.Avalonia/SpacedGrid/SpacingRowDefinition.cs create mode 100644 Toolkit.UI.Controls.Avalonia/Themes/ControlResources.axaml create mode 100644 Toolkit.UI.Controls.Avalonia/Themes/ThemeResources.axaml create mode 100644 Toolkit.UI.Controls.Avalonia/Themes/ThemeResources.axaml.cs create mode 100644 Toolkit.UI.Controls.Avalonia/Toolkit.UI.Controls.Avalonia.csproj diff --git a/Toolkit.UI.Controls.Avalonia/AsyncImage/AsyncImage.cs b/Toolkit.UI.Controls.Avalonia/AsyncImage/AsyncImage.cs new file mode 100644 index 0000000..bacad06 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/AsyncImage/AsyncImage.cs @@ -0,0 +1,8 @@ +namespace Toolkit.UI.Controls.Avalonia; + +public class AsyncImage : + global::Avalonia.Labs.Controls.AsyncImage +{ + protected override Type StyleKeyOverride => + typeof(global::Avalonia.Labs.Controls.AsyncImage); +} diff --git a/Toolkit.UI.Controls.Avalonia/BlurBehind/BlurBehind.cs b/Toolkit.UI.Controls.Avalonia/BlurBehind/BlurBehind.cs new file mode 100644 index 0000000..8284e95 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/BlurBehind/BlurBehind.cs @@ -0,0 +1,115 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.Rendering.SceneGraph; +using Avalonia.Skia; +using Avalonia.Styling; +using SkiaSharp; + +namespace Toolkit.UI.Controls.Avalonia; + +public class BlurBehind : + Control +{ + public static readonly StyledProperty MaterialProperty = + AvaloniaProperty.Register("Material"); + + public static readonly ImmutableExperimentalAcrylicMaterial DefaultAcrylicMaterialDark = + (ImmutableExperimentalAcrylicMaterial)new ExperimentalAcrylicMaterial() + { + MaterialOpacity = 0.25, + TintColor = Colors.Black, + TintOpacity = 0.7, + PlatformTransparencyCompensationLevel = 0 + }.ToImmutable(); + + public static readonly ImmutableExperimentalAcrylicMaterial DefaultAcrylicMaterialLight = + (ImmutableExperimentalAcrylicMaterial)new ExperimentalAcrylicMaterial() + { + MaterialOpacity = 0.0, + TintColor = Colors.White, + TintOpacity = 0.3, + PlatformTransparencyCompensationLevel = 0 + }.ToImmutable(); + + static BlurBehind() + { + AffectsRender(MaterialProperty); + } + + public ExperimentalAcrylicMaterial Material + { + get => GetValue(MaterialProperty); + set => SetValue(MaterialProperty, value); + } + + public override void Render(DrawingContext context) + { + 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))); + } + + private class BlurBehindRenderOperation(ImmutableExperimentalAcrylicMaterial material, + Rect bounds) : ICustomDrawOperation + { + private readonly Rect bounds = bounds; + private readonly ImmutableExperimentalAcrylicMaterial material = material; + + public Rect Bounds => bounds.Inflate(4); + + public void Dispose() + { + + } + + 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) + { + using ISkiaSharpApiLease? lease = leaseFeature.Lease(); + if (lease.SkCanvas is SKCanvas canvas) + { + if (canvas.TotalMatrix.TryInvert(out SKMatrix currentInvertedTransform)) + { + if (lease.SkSurface is SKSurface surface) + { + using SKImage backgroundSnapshot = surface.Snapshot(); + using SKShader backdropShader = SKShader.CreateImage(backgroundSnapshot, SKShaderTileMode.Clamp, + SKShaderTileMode.Clamp, currentInvertedTransform); + + using SKSurface blurred = SKSurface.Create(lease.GrContext, false, + new SKImageInfo((int)Math.Ceiling(bounds.Width), (int)Math.Ceiling(bounds.Height), + SKImageInfo.PlatformColorType, SKAlphaType.Premul)); + + using (SKImageFilter filter = SKImageFilter.CreateBlur(8, 8, SKShaderTileMode.Clamp)) + 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(); + using SKShader blurSnapShader = SKShader.CreateImage(blurSnap); + using SKPaint blurSnapPaint = new() + { + Shader = blurSnapShader, + IsAntialias = true + }; + + canvas.DrawRect(0, 0, (float)bounds.Width, (float)bounds.Height, blurSnapPaint); + } + + } + } + } + } + } +} + diff --git a/Toolkit.UI.Controls.Avalonia/CarouselView/CarouselView.axaml b/Toolkit.UI.Controls.Avalonia/CarouselView/CarouselView.axaml new file mode 100644 index 0000000..80f3f5b --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/CarouselView/CarouselView.axaml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Toolkit.UI.Controls.Avalonia/CarouselView/CarouselView.cs b/Toolkit.UI.Controls.Avalonia/CarouselView/CarouselView.cs new file mode 100644 index 0000000..23de510 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/CarouselView/CarouselView.cs @@ -0,0 +1,313 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Shapes; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Rendering.Composition; +using Avalonia.Rendering.Composition.Animations; +using System.Collections.Specialized; +using System.Numerics; + +namespace Toolkit.UI.Controls.Avalonia; + +public class CarouselView : + ItemsControl +{ + private readonly TimeSpan animationDuration = TimeSpan.FromMilliseconds(500); + private readonly List animations = []; + private readonly int columnCount = 5; + private readonly List itemVisuals = []; + private readonly ScopedBatchHelper scopedBatch = new(); + private readonly double spacing = 12; + private Compositor? compositor; + private Grid? container; + private Vector3D finalOffset; + private float horizontalDelta; + private Rectangle? indicator; + private Vector3DKeyFrameAnimation? indicatorAnimation; + private CompositionVisual? indicatorVisual; + private bool isAnimating; + private bool isPressed; + private List? items; + private Point? lastPosition; + private int newIndex; + private int SelectedIndex; + private Point? startPosition; + private CompositionVisual? touchAreaVisual; + + protected override void OnApplyTemplate(TemplateAppliedEventArgs args) + { + container = args.NameScope.Get("Container"); + if (container is not null) + { + items = container.Children.OfType().ToList(); + foreach (Border item in items) + { + if (item.Child is CarouselViewItem contentControl) + { + contentControl.ContentTemplate = ItemTemplate; + } + } + } + + indicator = args.NameScope.Get("Indicator"); + if (indicator is not null) + { + indicatorVisual = ElementComposition.GetElementVisual(indicator); + } + + ItemsView.CollectionChanged -= OnCollectionChanged; + ItemsView.CollectionChanged += OnCollectionChanged; + + base.OnApplyTemplate(args); + } + + protected override void OnSizeChanged(SizeChangedEventArgs args) + { + base.OnSizeChanged(args); + ArrangeItems(newIndex, isAnimating: false); + } + + protected override void OnLoaded(RoutedEventArgs args) + { + if (container is not null + && items is not null + && indicator is not null) + { + indicatorVisual = ElementComposition.GetElementVisual(indicator); + touchAreaVisual = ElementComposition.GetElementVisual(container); + if (touchAreaVisual is not null) + { + compositor = touchAreaVisual.Compositor; + } + + itemVisuals.Clear(); + foreach (Border item in items) + { + if (ElementComposition.GetElementVisual(item) is CompositionVisual visual) + { + itemVisuals.Add(visual); + } + } + + ArrangeItems(newIndex); + } + + base.OnLoaded(args); + } + + protected override void OnPointerMoved(PointerEventArgs args) + { + if (isPressed && indicatorVisual is not null && startPosition.HasValue) + { + lastPosition = args.GetPosition(container); + horizontalDelta = (float)(lastPosition.Value.X - startPosition.Value.X); + + indicatorVisual.Offset = new Vector3(horizontalDelta, 0.0f, 0.0f); + } + + base.OnPointerMoved(args); + } + + protected override void OnPointerPressed(PointerPressedEventArgs args) + { + if (!isPressed && indicatorVisual is not null) + { + if (!isAnimating) + { + horizontalDelta = 0; + + isPressed = true; + startPosition = args.GetPosition(container); + + indicatorVisual.Offset = new Vector3(horizontalDelta, 0.0f, 0.0f); + PrepareAnimations(); + + for (int i = 0; i < itemVisuals.Count; i++) + { + itemVisuals[i].StartAnimation("Offset", animations[i]); + } + } + } + + base.OnPointerPressed(args); + } + + protected override void OnPointerReleased(PointerReleasedEventArgs args) + { + if (isPressed && container is not null + && items is not null + && indicatorVisual is not null) + { + isPressed = false; + + double itemWidth = items[0].Bounds.Width; + double threshold = itemWidth / 3; + + int oldSelectedIndex = newIndex; + double offset = indicatorVisual.Offset.X; + + if (offset <= -threshold) + { + newIndex = (newIndex + 1) % 5; + SelectedIndex = (SelectedIndex + 1) % ItemsView.Count; + } + + if (offset >= threshold) + { + newIndex = (newIndex + 4) % 5; + SelectedIndex = (SelectedIndex + ItemsView.Count - 1) % ItemsView.Count; + } + + ArrangeItems(newIndex, oldSelectedIndex, true); + } + + base.OnPointerReleased(args); + } + + private void ArrangeItems(int newIndex, + int oldIndex = -1, + bool isAnimating = false) + { + if (compositor is not null + && container is not null + && items is not null + && indicatorVisual is not null) + { + double containerHeight = Bounds.Height; + double containerWidth = Bounds.Width; + container.Height = containerHeight; + + double targetSize = containerHeight; + + foreach (Border item in items) + { + if (item.Child is CarouselViewItem content) + { + content.Width = targetSize; + content.Height = targetSize; + } + + item.Width = targetSize; + item.Height = targetSize; + } + + double centreLeft = (containerWidth - targetSize) / 2; + double leftLeft = -targetSize + centreLeft; + double rightLeft = containerWidth - centreLeft; + + double[] offsets = + [ + leftLeft - targetSize + spacing * 1, + leftLeft + spacing * 2, + centreLeft + spacing * 3, + rightLeft + spacing * 4, + rightLeft + targetSize + spacing * 5 + ]; + + double centreOffset = spacing * (columnCount - 1) / 2 + spacing; + if (!isAnimating) + { + for (int i = 0; i < columnCount; i++) + { + itemVisuals[(newIndex + i - 2 + columnCount) % columnCount].Offset = + new Vector3((float)(offsets[i] - centreOffset), 0, 100); + } + + SetItems(); + } + else + { + int difference = newIndex - oldIndex; + finalOffset = difference switch + { + 0 => new Vector3D(0, 0, 0), + 1 => new Vector3D((float)(-targetSize - spacing), 0, 0), + -1 => new Vector3D((float)(targetSize + spacing), 0, 0), + _ => new Vector3D((float)(targetSize * Math.Sign(difference) + + spacing * Math.Sign(difference)), 0, 0) + }; + + indicatorAnimation = compositor.CreateVector3DKeyFrameAnimation(); + indicatorAnimation.InsertKeyFrame(1.0f, finalOffset); + indicatorAnimation.Duration = animationDuration; + indicatorAnimation.StopBehavior = AnimationStopBehavior.LeaveCurrentValue; + SetItems(); + + scopedBatch.Completed += () => + { + 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); + + this.isAnimating = true; + } + } + } + private void OnCollectionChanged(object? sender, + NotifyCollectionChangedEventArgs args) => ArrangeItems(newIndex); + + private void PrepareAnimations() + { + animations.Clear(); + if (compositor is not null && indicatorVisual is not null && itemVisuals is not null) + { + for (int i = 0; i < itemVisuals.Count; i++) + { + ExpressionAnimation animation = compositor.CreateExpressionAnimation(); + + animation.Expression = $"Source.Offset + Vector3({itemVisuals[i].Offset.X}, 0, 0)"; + animation.SetReferenceParameter("Source", indicatorVisual); + animations.Add(animation); + } + } + } + + private void SetItems() + { + if (items is not null) + { + int itemCount = ItemsView.Count; + if (itemCount == 0) + { + return; + } + + int[] selectedIndexOffsets = new int[columnCount]; + int[] indexOffsets = new int[columnCount]; + + SelectedIndex = SelectedIndex < 0 ? 0 : SelectedIndex; + + for (int i = -2; i <= 2; i++) + { + selectedIndexOffsets[i + 2] = (SelectedIndex + i + itemCount) % itemCount; + indexOffsets[i + 2] = (newIndex + i + columnCount) % columnCount; + } + + for (int i = 0; i < columnCount; i++) + { + int index = selectedIndexOffsets[i]; + if (itemCount == 1) + { + index = 0; + } + + if (items[indexOffsets[i]] is Border border && border.Child is + CarouselViewItem content) + { + content.Content = ItemsView[index]; + content.SetSelected(indexOffsets.Length / 2 == i); + } + } + } + } +} \ No newline at end of file diff --git a/Toolkit.UI.Controls.Avalonia/CarouselView/CarouselViewItem.cs b/Toolkit.UI.Controls.Avalonia/CarouselView/CarouselViewItem.cs new file mode 100644 index 0000000..ed75a5f --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/CarouselView/CarouselViewItem.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace Toolkit.UI.Controls.Avalonia; + +public class CarouselViewItem : + ContentControl +{ + internal void SetSelected(bool selected) + { + PseudoClasses.Set(":selected", selected); + } +} diff --git a/Toolkit.UI.Controls.Avalonia/ContentDialog/ContentDialog.axaml b/Toolkit.UI.Controls.Avalonia/ContentDialog/ContentDialog.axaml new file mode 100644 index 0000000..b7cc4ce --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/ContentDialog/ContentDialog.axaml @@ -0,0 +1,357 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +