This commit is contained in:
Dan Clark
2024-11-02 19:57:33 +00:00
471 changed files with 13991 additions and 5471 deletions
@@ -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;
}
}
@@ -5,4 +5,4 @@ public class AsyncImage :
{
protected override Type StyleKeyOverride =>
typeof(global::Avalonia.Labs.Controls.AsyncImage);
}
}
@@ -9,29 +9,29 @@ using SkiaSharp;
namespace Toolkit.UI.Controls.Avalonia;
public class BlurBehind :
public class BlurBehind :
Control
{
public static readonly StyledProperty<ExperimentalAcrylicMaterial> MaterialProperty =
AvaloniaProperty.Register<BlurBehind, ExperimentalAcrylicMaterial>("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<ISkiaSharpApiLeaseFeature>() is ISkiaSharpApiLeaseFeature leaseFeature)
if (context.TryGetFeature<ISkiaSharpApiLeaseFeature>() 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);
}
}
}
}
}
}
}
}
@@ -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);
@@ -9,4 +9,4 @@ public class CarouselViewItem :
{
PseudoClasses.Set(":selected", selected);
}
}
}
@@ -0,0 +1,15 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Toolkit.UI.Controls.Avalonia">
<ControlTheme x:Key="{x:Type controls:ContentBadge}" TargetType="controls:ContentBadge">
<Setter Property="Template">
<ControlTemplate>
<Grid>
<ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" />
<ContentControl x:Name="BadgeContent" />
</Grid>
</ControlTemplate>
</Setter>
</ControlTheme>
</ResourceDictionary>
@@ -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<string> BadgePathProperty =
AvaloniaProperty.Register<ContentBadge, string>(nameof(BadgePath));
public static readonly StyledProperty<ContentBadgePlacement> BadgePlacementProperty =
AvaloniaProperty.Register<ContentBadge, ContentBadgePlacement>(nameof(BadgePlacement), ContentBadgePlacement.BottomRight);
public static readonly StyledProperty<double> BadgeSizeProperty =
AvaloniaProperty.Register<ContentBadge, double>(nameof(BadgeSize), 14);
public static readonly StyledProperty<bool> IsBadgeVisibleProperty =
AvaloniaProperty.Register<ContentBadge, bool>(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<ContentControl>("BadgeContent");
}
protected override void OnSizeChanged(SizeChangedEventArgs args)
{
base.OnSizeChanged(args);
UpdateBadge();
}
}
@@ -0,0 +1,9 @@
namespace Toolkit.UI.Controls.Avalonia;
public enum ContentBadgePlacement
{
TopLeft,
TopRight,
BottomLeft,
BottomRight
}
@@ -0,0 +1,33 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Toolkit.UI.Controls.Avalonia">
<Thickness x:Key="SettingsExpanderPadding">16</Thickness>
<ControlTheme x:Key="{x:Type controls:ContentCard}" TargetType="controls:ContentCard">
<Setter Property="Background" Value="{DynamicResource ExpanderHeaderBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource ExpanderHeaderBorderBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderHeaderBorderThickness}" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Padding" Value="{DynamicResource SettingsExpanderPadding}" />
<Setter Property="Template">
<ControlTemplate>
<Border
Name="Root"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BackgroundSizing="{TemplateBinding BackgroundSizing}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ContentPresenter
x:Name="Content"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
FontFamily="{StaticResource ContentControlThemeFontFamily}"
FontSize="{StaticResource ControlContentThemeFontSize}"
Foreground="{TemplateBinding Foreground}" />
</Border>
</ControlTemplate>
</Setter>
</ControlTheme>
</ResourceDictionary>
@@ -0,0 +1,6 @@
using Avalonia.Controls;
namespace Toolkit.UI.Controls.Avalonia;
public class ContentCard :
ContentControl;
@@ -0,0 +1,49 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Toolkit.UI.Controls.Avalonia">
<ControlTheme x:Key="{x:Type ContentColorPicker}" TargetType="ContentColorPicker">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="ClipToBounds" Value="False" />
<Setter Property="Template">
<ControlTemplate>
<Border
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BackgroundSizing="{TemplateBinding BackgroundSizing}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid>
<ZoomBorder
x:Name="ZoomBorder"
ClipToBounds="True"
PanButton="Left">
<ContentPresenter
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
</ZoomBorder>
<Canvas
x:Name="Canvas"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
ClipToBounds="False">
<Border
x:Name="PeekBorder"
Width="100"
Height="100"
Background="Pink"
ClipToBounds="False"
IsVisible="False" />
</Canvas>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</ControlTheme>
</ResourceDictionary>
@@ -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<double> PeekOffsetProperty =
AvaloniaProperty.Register<ContentColorPicker, double>(nameof(PeekOffset), 20);
public static readonly StyledProperty<int> PeekPixelsProperty =
AvaloniaProperty.Register<ContentColorPicker, int>(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>("Canvas");
zoomBorder = args.NameScope.Find<ZoomBorder>("ZoomBorder");
if (zoomBorder is not null)
{
zoomBorder.ZoomChanged += OnZoomChanged;
}
peekBorder = args.NameScope.Find<Border>("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;
}
}
}
@@ -0,0 +1,131 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Toolkit.UI.Controls.Avalonia">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<StaticResource x:Key="ContentCropperOverlayBrush" ResourceKey="SmokeFillColorDefaultBrush" />
<StaticResource x:Key="ContentCropperInnerBorderBrush" ResourceKey="FocusStrokeColorOuter" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<StaticResource x:Key="ContentCropperOuterThumbBackground" ResourceKey="ControlSolidFillColorDefaultBrush" />
<StaticResource x:Key="ContentCropperOverlayBrush" ResourceKey="SmokeFillColorDefaultBrush" />
<StaticResource x:Key="ContentCropperInnerBorderBrush" ResourceKey="FocusStrokeColorOuter" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<x:Double x:Key="ContentCropperThumbWidth">18</x:Double>
<x:Double x:Key="ContentCropperThumbHeight">18</x:Double>
<Thickness x:Key="ContentCropperTopLeftThumbBorderThickness">3,3,0,0</Thickness>
<Thickness x:Key="ContentCropperTopRightThumbBorderThickness">0,3,3,0</Thickness>
<Thickness x:Key="ContentCropperBottomRightThumbBorderThickness">0,0,3,3</Thickness>
<Thickness x:Key="ContentCropperBottomLeftThumbBorderThickness">3,0,0,3</Thickness>
<Thickness x:Key="ContentCropperLeftThumbBorderThickness">3,0,0,0</Thickness>
<Thickness x:Key="ContentCropperTopThumbBorderThickness">0,3,0,0</Thickness>
<Thickness x:Key="ContentCropperRightThumbBorderThickness">0,0,3,0</Thickness>
<Thickness x:Key="ContentCropperBottomThumbBorderThickness">0,0,0,3</Thickness>
<ControlTheme x:Key="ContentCropperThumbStyle" TargetType="Thumb">
<Setter Property="BorderBrush" Value="{DynamicResource ContentCropperInnerBorderBrush}" />
<Setter Property="ClipToBounds" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border
Background="Transparent"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</ControlTheme>
<ControlTheme x:Key="{x:Type ContentCropper}" TargetType="ContentCropper">
<Setter Property="Template">
<ControlTemplate>
<Grid>
<ZoomBorder
x:Name="ZoomBorder"
ClipToBounds="True"
PanButton="Left">
<ContentPresenter
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
</ZoomBorder>
<Canvas x:Name="Canvas">
<Path x:Name="OverlayPath" Fill="{DynamicResource ContentCropperOverlayBrush}" />
<Border
x:Name="Border"
Background="Transparent"
BorderBrush="{DynamicResource ContentCropperInnerBorderBrush}"
BorderThickness="1"
Cursor="SizeAll" />
<Thumb
x:Name="TopLeftButton"
Width="{StaticResource ContentCropperThumbWidth}"
Height="{StaticResource ContentCropperThumbHeight}"
BorderThickness="{StaticResource ContentCropperTopLeftThumbBorderThickness}"
Cursor="TopLeftCorner"
Theme="{StaticResource ContentCropperThumbStyle}" />
<Thumb
x:Name="TopRightButton"
Width="{StaticResource ContentCropperThumbWidth}"
Height="{StaticResource ContentCropperThumbHeight}"
BorderThickness="{StaticResource ContentCropperTopRightThumbBorderThickness}"
Cursor="TopRightCorner"
Theme="{StaticResource ContentCropperThumbStyle}" />
<Thumb
x:Name="BottomLeftButton"
Width="{StaticResource ContentCropperThumbWidth}"
Height="{StaticResource ContentCropperThumbHeight}"
BorderThickness="{StaticResource ContentCropperBottomLeftThumbBorderThickness}"
Cursor="BottomLeftCorner"
Theme="{StaticResource ContentCropperThumbStyle}" />
<Thumb
x:Name="BottomRightButton"
Width="{StaticResource ContentCropperThumbWidth}"
Height="{StaticResource ContentCropperThumbHeight}"
BorderThickness="{StaticResource ContentCropperBottomRightThumbBorderThickness}"
Cursor="BottomRightCorner"
Theme="{StaticResource ContentCropperThumbStyle}" />
<Thumb
x:Name="LeftButton"
Width="{StaticResource ContentCropperThumbWidth}"
Height="{StaticResource ContentCropperThumbHeight}"
BorderThickness="{StaticResource ContentCropperLeftThumbBorderThickness}"
Cursor="LeftSide"
Theme="{StaticResource ContentCropperThumbStyle}" />
<Thumb
x:Name="RightButton"
Width="{StaticResource ContentCropperThumbWidth}"
Height="{StaticResource ContentCropperThumbHeight}"
BorderThickness="{StaticResource ContentCropperRightThumbBorderThickness}"
Cursor="RightSide"
Theme="{StaticResource ContentCropperThumbStyle}" />
<Thumb
x:Name="TopButton"
Width="{StaticResource ContentCropperThumbWidth}"
Height="{StaticResource ContentCropperThumbHeight}"
BorderThickness="{StaticResource ContentCropperTopThumbBorderThickness}"
Cursor="TopSide"
Theme="{StaticResource ContentCropperThumbStyle}" />
<Thumb
x:Name="BottomButton"
Width="{StaticResource ContentCropperThumbWidth}"
Height="{StaticResource ContentCropperThumbHeight}"
BorderThickness="{StaticResource ContentCropperBottomThumbBorderThickness}"
Cursor="BottomSide"
Theme="{StaticResource ContentCropperThumbStyle}" />
</Canvas>
</Grid>
</ControlTemplate>
</Setter>
</ControlTheme>
</ResourceDictionary>
@@ -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<ContentCropper, Bitmap?> CroppedBitmapProperty =
AvaloniaProperty.RegisterDirect<ContentCropper, Bitmap?>(nameof(CroppedBitmap),
o => o.CroppedBitmap);
public static readonly StyledProperty<Rect> CropRectangleProperty =
AvaloniaProperty.Register<ContentCropper, Rect>(nameof(CropRectangle));
public static readonly StyledProperty<bool> IsRatioScaleProperty =
AvaloniaProperty.Register<ContentCropper, bool>(nameof(IsRatioScale));
public static readonly StyledProperty<double> RectScaleProperty =
AvaloniaProperty.Register<ContentCropper, double>(nameof(RectScale), 0.5);
public static readonly StyledProperty<Size> ScaleSizeProperty =
AvaloniaProperty.Register<ContentCropper, Size>(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<ContentCropper>(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>("Canvas");
overlayPath = args.NameScope.Find<Path>("OverlayPath");
border = args.NameScope.Find<Border>("Border");
topLeftButton = args.NameScope.Find<Thumb>("TopLeftButton");
if (topLeftButton is not null)
{
topLeftButton.DragDelta += OnThumbDragDelta;
}
topRightButton = args.NameScope.Find<Thumb>("TopRightButton");
if (topRightButton is not null)
{
topRightButton.DragDelta += OnThumbDragDelta;
}
bottomLeftButton = args.NameScope.Find<Thumb>("BottomLeftButton");
if (bottomLeftButton is not null)
{
bottomLeftButton.DragDelta += OnThumbDragDelta;
}
bottomRightButton = args.NameScope.Find<Thumb>("BottomRightButton");
if (bottomRightButton is not null)
{
bottomRightButton.DragDelta += OnThumbDragDelta;
}
leftButton = args.NameScope.Find<Thumb>("LeftButton");
if (leftButton is not null)
{
leftButton.DragDelta += OnThumbDragDelta;
}
rightButton = args.NameScope.Find<Thumb>("RightButton");
if (rightButton is not null)
{
rightButton.DragDelta += OnThumbDragDelta;
}
topButton = args.NameScope.Find<Thumb>("TopButton");
if (topButton is not null)
{
topButton.DragDelta += OnThumbDragDelta;
}
bottomButton = args.NameScope.Find<Thumb>("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);
}
}
@@ -1,357 +0,0 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Toolkit.UI.Controls.Avalonia"
xmlns:ui="using:FluentAvalonia.UI.Controls">
<Design.PreviewWith>
<Border Padding="20" />
</Design.PreviewWith>
<ControlTheme x:Key="{x:Type ui:ContentDialog}" TargetType="ui:ContentDialog">
<Setter Property="Foreground" Value="{DynamicResource ContentDialogForeground}" />
<Setter Property="Background" Value="{DynamicResource ContentDialogBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource ContentDialogBorderBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ContentDialogBorderWidth}" />
<Setter Property="CornerRadius" Value="{DynamicResource OverlayCornerRadius}" />
<Setter Property="Template">
<ControlTemplate>
<Border Name="Container">
<Panel Name="LayoutRoot" Background="{DynamicResource ContentDialogSmokeFill}">
<controls:BlurBehind />
<ui:FABorder
Name="BackgroundElement"
MinWidth="{DynamicResource ContentDialogMinWidth}"
MinHeight="{DynamicResource ContentDialogMinHeight}"
MaxWidth="{DynamicResource ContentDialogMaxWidth}"
MaxHeight="{DynamicResource ContentDialogMaxHeight}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{StaticResource ContentDialogBorderWidth}"
BoxShadow="0 8 32 0 #66000000"
CornerRadius="{TemplateBinding CornerRadius}">
<!-- Even in WinUI, shadow is always black regardless of light/dark mode -->
<!--
if this border isn't here, dialog space displays outside of corner radius at top
if we put ClipToBounds=True on BackgroundElement above, it clips the shadow
-->
<Border ClipToBounds="True" CornerRadius="{TemplateBinding CornerRadius}">
<Grid
Name="DialogSpace"
ClipToBounds="True"
RowDefinitions="*,Auto">
<ScrollViewer
Name="ContentScrollViewer"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border
Padding="{DynamicResource ContentDialogPadding}"
Background="{DynamicResource ContentDialogTopOverlay}"
BorderBrush="{DynamicResource ContentDialogSeparatorBorderBrush}"
BorderThickness="{StaticResource ContentDialogSeparatorThickness}">
<Grid RowDefinitions="Auto,*">
<Grid.Styles>
<!-- Make sure text wrapping is on -->
<Style Selector="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</Grid.Styles>
<ContentControl
Name="Title"
Margin="{StaticResource ContentDialogTitleMargin}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="{TemplateBinding Title}"
ContentTemplate="{TemplateBinding TitleTemplate}"
FontFamily="Default"
FontSize="20"
FontWeight="SemiBold"
Foreground="{TemplateBinding Foreground}">
<ContentControl.Template>
<ControlTemplate>
<ContentPresenter
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
<ContentPresenter
Name="Content"
Grid.Row="1"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
FontFamily="{StaticResource ContentControlThemeFontFamily}"
FontSize="{StaticResource ControlContentThemeFontSize}"
Foreground="{TemplateBinding Foreground}" />
</Grid>
</Border>
</ScrollViewer>
<Border
Grid.Row="1"
Padding="{StaticResource ContentDialogPadding}"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Background="{TemplateBinding Background}">
<Grid Name="CommandSpace">
<!--
B/C we can't target Row/Column defs in Styles like WinUI
this still uses the old Col defs, but it works the same
way in the end...
-->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button
Name="PrimaryButton"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Content="{TemplateBinding PrimaryButtonText}"
IsEnabled="{TemplateBinding IsPrimaryButtonEnabled}"
IsVisible="False" />
<Button
Name="SecondaryButton"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Content="{TemplateBinding SecondaryButtonText}"
IsEnabled="{TemplateBinding IsSecondaryButtonEnabled}"
IsVisible="False" />
<Button
Name="CloseButton"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Content="{TemplateBinding CloseButtonText}"
IsVisible="False" />
</Grid>
</Border>
</Grid>
</Border>
</ui:FABorder>
</Panel>
</Border>
</ControlTemplate>
</Setter>
<!-- Handle hidden dialog -->
<Style Selector="^:hidden /template/ Panel#LayoutRoot">
<Style.Animations>
<Animation FillMode="Forward" Duration="00:00:00.167">
<KeyFrame Cue="0%">
<Setter Property="Opacity" Value="1.0" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Opacity" Value="0.0" />
<Setter Property="IsVisible" Value="False" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="^:hidden /template/ Border#Container">
<Style.Animations>
<Animation FillMode="Forward" Duration="00:00:00.167">
<KeyFrame Cue="0%">
<Setter Property="ScaleTransform.ScaleX" Value="1.0" />
<Setter Property="ScaleTransform.ScaleY" Value="1.0" />
</KeyFrame>
<KeyFrame Cue="100%" KeySpline="0,0 0,1">
<Setter Property="ScaleTransform.ScaleX" Value="1.05" />
<Setter Property="ScaleTransform.ScaleY" Value="1.05" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<!-- Handle open dialog -->
<Style Selector="^:open /template/ Panel#LayoutRoot">
<Setter Property="IsVisible" Value="True" />
<Style.Animations>
<!--
Animation applies with priority of LocalValue
To overrule the IsVisible=False in :hidden, set
IsVisible=True in BOTH KeyFrames here
-->
<Animation FillMode="Forward" Duration="00:00:00.250">
<KeyFrame Cue="0%">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Opacity" Value="0.0" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Opacity" Value="1.0" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="^:open /template/ Border#Container">
<Style.Animations>
<Animation FillMode="Forward" Duration="00:00:00.250">
<KeyFrame Cue="0%">
<Setter Property="ScaleTransform.ScaleX" Value="1.05" />
<Setter Property="ScaleTransform.ScaleY" Value="1.05" />
</KeyFrame>
<KeyFrame Cue="100%" KeySpline="0,0 0,1">
<Setter Property="ScaleTransform.ScaleX" Value="1.00" />
<Setter Property="ScaleTransform.ScaleY" Value="1.00" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<!-- Handle showing smoke layer -->
<Style Selector="^:nosmokelayer /template/ Panel#LayoutRoot">
<Setter Property="Background" Value="{x:Null}" />
</Style>
<!-- Handle FullDialogSizing -->
<Style Selector="^:fullsize /template/ ui|FABorder#BackgroundElement">
<Setter Property="VerticalAlignment" Value="Stretch" />
</Style>
<!-- Primary Button Only -->
<Style Selector="^:primary /template/ Button#PrimaryButton">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Grid.Column" Value="2" />
<Setter Property="Grid.ColumnSpan" Value="2" />
</Style>
<Style Selector="^:primary /template/ Button#SecondaryButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^:primary /template/ Button#CloseButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<!-- Secondary Button Only -->
<Style Selector="^:secondary /template/ Button#PrimaryButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^:secondary /template/ Button#SecondaryButton">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Grid.Column" Value="2" />
<Setter Property="Grid.ColumnSpan" Value="2" />
</Style>
<Style Selector="^:secondary /template/ Button#CloseButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<!-- Close Button Only -->
<Style Selector="^:close /template/ Button#PrimaryButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^:close /template/ Button#SecondaryButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^:close /template/ Button#CloseButton">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Grid.Column" Value="2" />
<Setter Property="Grid.ColumnSpan" Value="2" />
</Style>
<!-- Margins are defined by ContentDialogButtonSpacing (8) -->
<!-- Primary and Secondary -->
<Style Selector="^:primary:secondary /template/ Button#PrimaryButton">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Grid.Column" Value="0" />
<Setter Property="Grid.ColumnSpan" Value="2" />
<Setter Property="Margin" Value="0 0 4 0" />
</Style>
<Style Selector="^:primary:secondary /template/ Button#SecondaryButton">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Grid.Column" Value="2" />
<Setter Property="Grid.ColumnSpan" Value="2" />
<Setter Property="Margin" Value="4 0 0 0" />
</Style>
<Style Selector="^:primary:secondary /template/ Button#CloseButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<!-- Primary and Close Buttons -->
<Style Selector="^:primary:close /template/ Button#PrimaryButton">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Grid.Column" Value="0" />
<Setter Property="Grid.ColumnSpan" Value="2" />
<Setter Property="Margin" Value="0 0 4 0" />
</Style>
<Style Selector="^:primary:close /template/ Button#SecondaryButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^:primary:close /template/ Button#CloseButton">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Grid.Column" Value="2" />
<Setter Property="Grid.ColumnSpan" Value="2" />
<Setter Property="Margin" Value="4 0 0 0" />
</Style>
<!-- Primary and Secondary Buttons -->
<Style Selector="^:primary:secondary /template/ Button#PrimaryButton">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Grid.Column" Value="0" />
<Setter Property="Grid.ColumnSpan" Value="2" />
<Setter Property="Margin" Value="0 0 4 0" />
</Style>
<Style Selector="^:primary:secondary /template/ Button#CloseButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^:primary:secondary /template/ Button#SecondaryButton">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Grid.Column" Value="2" />
<Setter Property="Grid.ColumnSpan" Value="2" />
<Setter Property="Margin" Value="4 0 0 0" />
</Style>
<!-- Secondary and Close Buttons -->
<Style Selector="^:secondary:close /template/ Button#Secondary">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Grid.Column" Value="0" />
<Setter Property="Grid.ColumnSpan" Value="2" />
<Setter Property="Margin" Value="0 0 4 0" />
</Style>
<Style Selector="^:secondary:close /template/ Button#PrimaryButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^:secondary:close /template/ Button#CloseButton">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Grid.Column" Value="2" />
<Setter Property="Grid.ColumnSpan" Value="2" />
<Setter Property="Margin" Value="4 0 0 0" />
</Style>
<!-- All Buttons -->
<Style Selector="^:primary:secondary:close /template/ Button#PrimaryButton">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Grid.Column" Value="0" />
<Setter Property="Grid.ColumnSpan" Value="1" />
<Setter Property="Margin" Value="0 0 4 0" />
</Style>
<Style Selector="^:primary:secondary:close /template/ Button#SecondaryButton">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Grid.Column" Value="1" />
<Setter Property="Grid.ColumnSpan" Value="2" />
<Setter Property="Margin" Value="4 0 4 0" />
</Style>
<Style Selector="^:primary:secondary:close /template/ Button#CloseButton">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Grid.Column" Value="3" />
<Setter Property="Grid.ColumnSpan" Value="1" />
<Setter Property="Margin" Value="4 0 0 0" />
</Style>
</ControlTheme>
</ResourceDictionary>
@@ -1,9 +1,9 @@
using Avalonia.Media.Imaging;
using Avalonia.Styling;
using Avalonia;
using SukiUI.Utilities.Background;
using Avalonia.Platform;
using Avalonia;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Styling;
using SukiUI.Utilities.Background;
namespace Toolkit.UI.Controls.Avalonia;
@@ -28,7 +28,7 @@ public class FastNoiseBackgroundRenderer
public FastNoiseBackgroundRenderer(FastNoiseRendererOptions? options = null)
{
FastNoiseRendererOptions opt = options ??
FastNoiseRendererOptions opt = options ??
new FastNoiseRendererOptions(FastNoiseLite.NoiseType.OpenSimplex2);
NoiseGen.SetNoiseType(opt.Type);
@@ -130,7 +130,7 @@ public class FastNoiseBackgroundRenderer
return ARGB(A(back), resultR, resultG, resultB);
}
private static byte G(uint col) =>
private static byte G(uint col) =>
(byte)(col >> 8);
private static uint GetBackgroundColour(Color input)
@@ -159,7 +159,7 @@ public class FastNoiseBackgroundRenderer
private static byte R(uint col) => (byte)(col >> 16);
private static uint ToUInt32(Color colour) =>
private static uint ToUInt32(Color colour) =>
(uint)(colour.A << 24 | colour.R << 16 | colour.G << 8 | colour.B);
private static uint WithAlpha(uint col, byte a) => col & 0x00FFFFFF | (uint)(a << 24);
@@ -20,4 +20,4 @@ public readonly struct FastNoiseRendererOptions(
public float XSeed { get; } = xSeed * seedScale;
public float YSeed { get; } = ySeed * seedScale;
}
}
@@ -7,13 +7,13 @@ using Avalonia.Styling;
namespace Toolkit.UI.Controls.Avalonia;
public class FastRendererBackground :
public class FastRendererBackground :
Image, IDisposable
{
private const int ImageWidth = 100;
private const int ImageHeight = 100;
private readonly WriteableBitmap bitmap = new(new PixelSize(ImageWidth, ImageHeight),
private readonly WriteableBitmap bitmap = new(new PixelSize(ImageWidth, ImageHeight),
new Vector(96, 96), PixelFormat.Bgra8888);
private readonly FastNoiseBackgroundRenderer renderer = new();
@@ -29,7 +29,7 @@ public class FastRendererBackground :
base.EndInit();
if (Application.Current?.ActualThemeVariant is ThemeVariant theme)
{
renderer.UpdateValues((Color)Application.Current.FindResource("SystemAccentColorLight3"),
renderer.UpdateValues((Color)Application.Current.FindResource("SystemAccentColorLight3"),
(Color)Application.Current.FindResource("SystemAccentColorDark3"), theme);
}
@@ -41,4 +41,4 @@ public class FastRendererBackground :
GC.SuppressFinalize(this);
bitmap.Dispose();
}
}
}
+3 -3
View File
@@ -1,8 +1,8 @@
namespace Toolkit.UI.Controls.Avalonia;
public class Frame :
public class Frame :
FluentAvalonia.UI.Controls.Frame
{
protected override Type StyleKeyOverride =>
protected override Type StyleKeyOverride =>
typeof(FluentAvalonia.UI.Controls.Frame);
}
}
@@ -1,6 +1,7 @@
using Avalonia.Threading;
namespace Toolkit.UI.Controls.Avalonia;
public class ScopedBatchHelper
{
private DispatcherTimer? timer;
@@ -0,0 +1,3 @@
namespace Toolkit.UI.Controls.Avalonia;
public class BitmapIcon : FluentAvalonia.UI.Controls.BitmapIcon;
@@ -0,0 +1,3 @@
namespace Toolkit.UI.Controls.Avalonia;
public class BitmapIconSource : FluentAvalonia.UI.Controls.BitmapIconSource;
@@ -0,0 +1,72 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
namespace Toolkit.UI.Controls.Avalonia;
public class ContentIcon : FluentAvalonia.UI.Controls.FAIconElement
{
public static readonly StyledProperty<object?> ContentProperty =
AvaloniaProperty.Register<ContentIcon, object?>("Content");
public static readonly StyledProperty<IDataTemplate?> ContentTemplateProperty =
AvaloniaProperty.Register<ContentIcon, IDataTemplate?>("ContentTemplate");
private ContentControl? content;
[Content]
public object? Content
{
get => GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
public IDataTemplate? IconTemplate
{
get => GetValue(ContentTemplateProperty);
set => SetValue(ContentTemplateProperty, value);
}
protected override Size MeasureOverride(Size availableSize)
{
if (content == null)
{
CreateContent();
}
return base.MeasureOverride(availableSize);
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs args)
{
if (VisualChildren.Count > 0)
{
((ILogical)VisualChildren[0]).NotifyAttachedToLogicalTree(args);
}
base.OnAttachedToLogicalTree(args);
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs args)
{
if (VisualChildren.Count > 0)
{
((ILogical)VisualChildren[0]).NotifyDetachedFromLogicalTree(args);
}
base.OnDetachedFromLogicalTree(args);
}
private void CreateContent()
{
content = new ContentControl();
content.Bind(ContentControl.ContentProperty, this.GetBindingObservable(ContentProperty));
content.Bind(ContentControl.ContentTemplateProperty, this.GetBindingObservable(ContentTemplateProperty));
LogicalChildren.Add(content);
VisualChildren.Add(content);
}
}
@@ -0,0 +1,3 @@
namespace Toolkit.UI.Controls.Avalonia;
public class FAIconElement : FluentAvalonia.UI.Controls.FAIconElement;
@@ -0,0 +1,3 @@
namespace Toolkit.UI.Controls.Avalonia;
public class FAPathIcon : FluentAvalonia.UI.Controls.FAPathIcon;
@@ -0,0 +1,3 @@
namespace Toolkit.UI.Controls.Avalonia;
public class FontIcon : FluentAvalonia.UI.Controls.FontIcon;
@@ -0,0 +1,3 @@
namespace Toolkit.UI.Controls.Avalonia;
public class FontIconSource : FluentAvalonia.UI.Controls.FontIconSource;
@@ -0,0 +1,3 @@
namespace Toolkit.UI.Controls.Avalonia;
public class ImageIcon : FluentAvalonia.UI.Controls.ImageIcon;
@@ -0,0 +1,3 @@
namespace Toolkit.UI.Controls.Avalonia;
public class ImageIconSource : FluentAvalonia.UI.Controls.ImageIconSource;
@@ -0,0 +1,3 @@
namespace Toolkit.UI.Controls.Avalonia;
public class PathIconSource : FluentAvalonia.UI.Controls.PathIconSource;
@@ -0,0 +1,3 @@
namespace Toolkit.UI.Controls.Avalonia;
public class SymbolIcon : FluentAvalonia.UI.Controls.SymbolIcon;
@@ -0,0 +1,3 @@
namespace Toolkit.UI.Controls.Avalonia;
public class SymbolIconSource : FluentAvalonia.UI.Controls.SymbolIconSource;
@@ -0,0 +1,8 @@
namespace Toolkit.UI.Controls.Avalonia;
public class InfoBadge :
FluentAvalonia.UI.Controls.InfoBadge
{
protected override Type StyleKeyOverride =>
typeof(FluentAvalonia.UI.Controls.InfoBadge);
}
@@ -0,0 +1,8 @@
namespace Toolkit.UI.Controls.Avalonia;
public class InfoBar :
FluentAvalonia.UI.Controls.InfoBar
{
protected override Type StyleKeyOverride =>
typeof(FluentAvalonia.UI.Controls.InfoBar);
}
@@ -0,0 +1,41 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
namespace Toolkit.UI.Controls.Avalonia;
public class ListView :
ListBox
{
public static readonly StyledProperty<IItemContainerTemplateSelector?> ItemContainerTemplateSelectorProperty =
AvaloniaProperty.Register<ListView, IItemContainerTemplateSelector?>(nameof(ItemContainerTemplateSelector));
public IItemContainerTemplateSelector? ItemContainerTemplateSelector
{
get => GetValue(ItemContainerTemplateSelectorProperty);
set => SetValue(ItemContainerTemplateSelectorProperty, value);
}
protected override Type StyleKeyOverride =>
typeof(ListBox);
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
{
if (ItemContainerTemplateSelector?.SelectTemplate(item, this) is IDataTemplate itemContainerTemplate)
{
if (itemContainerTemplate.Build(item) is ListViewItem container)
{
return container;
}
}
return new ListViewItem();
}
protected override bool NeedsContainerOverride(object? item,
int index,
out object? recycleKey)
{
recycleKey = null;
return item is not ListViewItem;
}
}
@@ -0,0 +1,10 @@
using Avalonia.Controls;
namespace Toolkit.UI.Controls.Avalonia;
public class ListViewItem :
ListBoxItem
{
protected override Type StyleKeyOverride =>
typeof(ListBoxItem);
}
@@ -1,8 +1,39 @@
namespace Toolkit.UI.Controls.Avalonia;
using FluentAvalonia.UI.Controls;
using System.Reflection;
namespace Toolkit.UI.Controls.Avalonia;
public class NavigationView :
FluentAvalonia.UI.Controls.NavigationView
{
protected override Type StyleKeyOverride =>
public NavigationView()
{
ItemInvoked += OnItemInvoked;
}
protected override Type StyleKeyOverride =>
typeof(FluentAvalonia.UI.Controls.NavigationView);
}
private void ApplyNavigationFix()
{
if (typeof(FluentAvalonia.UI.Controls.NavigationView)
.GetField("_shouldRaiseItemInvokedAfterSelection",
BindingFlags.NonPublic | BindingFlags.Instance)
is FieldInfo shouldRaiseItemInvokedAfterSelectionFieldInfo)
{
if (shouldRaiseItemInvokedAfterSelectionFieldInfo.GetValue(this) is bool value)
{
if (value)
{
shouldRaiseItemInvokedAfterSelectionFieldInfo.SetValue(this, false);
}
}
};
}
private void OnItemInvoked(object? sender,
NavigationViewItemInvokedEventArgs args)
{
ApplyNavigationFix();
}
}
@@ -5,4 +5,4 @@ public class NavigationViewItem :
{
protected override Type StyleKeyOverride =>
typeof(FluentAvalonia.UI.Controls.NavigationViewItem);
}
}
@@ -0,0 +1,8 @@
namespace Toolkit.UI.Controls.Avalonia;
public class NavigationViewItemSeparator :
FluentAvalonia.UI.Controls.NavigationViewItemSeparator
{
protected override Type StyleKeyOverride =>
typeof(FluentAvalonia.UI.Controls.NavigationViewItemSeparator);
}
@@ -0,0 +1,8 @@
namespace Toolkit.UI.Controls.Avalonia;
public class NumberBox :
FluentAvalonia.UI.Controls.NumberBox
{
protected override Type StyleKeyOverride =>
typeof(FluentAvalonia.UI.Controls.NumberBox);
}
@@ -0,0 +1,223 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Toolkit.UI.Controls.Avalonia">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<StaticResource x:Key="OverflowItemBackgroundSelected" ResourceKey="SubtleFillColorTransparentBrush" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<StaticResource x:Key="OverflowItemBackgroundSelected" ResourceKey="SubtleFillColorTransparentBrush" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<x:Double x:Key="OverflowItemSpacing">6</x:Double>
<x:Double x:Key="OverflowItemSize">40</x:Double>
<CornerRadius x:Key="OverflowItemCornerRadius">40</CornerRadius>
<ControlTheme x:Key="{x:Type OverflowList}" TargetType="OverflowList">
<Setter Property="Foreground" Value="{DynamicResource ListBoxForeground}" />
<Setter Property="Background" Value="{DynamicResource ListBoxBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource ListBoxBorder}" />
<Setter Property="BorderThickness" Value="{DynamicResource ListBoxBorderThemeThickness}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.IsScrollChainingEnabled" Value="True" />
<Setter Property="ScrollViewer.IsScrollInertiaEnabled" Value="True" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Template">
<ControlTemplate>
<Border
Name="border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer
Name="PART_ScrollViewer"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
Background="{TemplateBinding Background}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
HorizontalSnapPointsType="{TemplateBinding (ScrollViewer.HorizontalSnapPointsType)}"
IsDeferredScrollingEnabled="{TemplateBinding (ScrollViewer.IsDeferredScrollingEnabled)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
IsScrollInertiaEnabled="{TemplateBinding (ScrollViewer.IsScrollInertiaEnabled)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
VerticalSnapPointsType="{TemplateBinding (ScrollViewer.VerticalSnapPointsType)}">
<ItemsPresenter
Name="PART_ItemsPresenter"
Margin="{TemplateBinding Padding}"
ItemsPanel="{TemplateBinding ItemsPanel}" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter>
</ControlTheme>
<ControlTheme x:Key="{x:Type controls:OverflowItem}" TargetType="controls:OverflowItem">
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Background" Value="{DynamicResource ListViewItemBackground}" />
<Setter Property="Foreground" Value="{DynamicResource ListViewItemForeground}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Width" Value="{StaticResource OverflowItemSize}" />
<Setter Property="Height" Value="{StaticResource OverflowItemSize}" />
<Setter Property="MinHeight" Value="{StaticResource OverflowItemSize}" />
<Setter Property="MinWidth" Value="{StaticResource OverflowItemSize}" />
<Setter Property="CornerRadius" Value="{StaticResource OverflowItemCornerRadius}" />
<Setter Property="Template">
<ControlTemplate>
<Panel>
<Border
x:Name="PART_ContentBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<controls:ContentBadge
BadgePath="{TemplateBinding BadgePath}"
BadgePlacement="{TemplateBinding BadgePlacement}"
BadgeSize="{TemplateBinding BadgeSize}"
Foreground="{TemplateBinding BadgeBrush}"
IsBadgeVisible="{TemplateBinding IsBadgeVisible}">
<Grid>
<ContentPresenter
Name="PART_ContentPresenter"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}" />
<Border
Name="SelectionIndicator"
BorderBrush="{DynamicResource AccentFillColorDefaultBrush}"
BorderThickness="3"
CornerRadius="{TemplateBinding CornerRadius}"
IsVisible="False"
UseLayoutRounding="False" />
</Grid>
</controls:ContentBadge>
</Border>
</Panel>
</ControlTemplate>
</Setter>
<Style Selector="^:disabled">
<Setter Property="Opacity" Value="0.5" />
</Style>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ Border#PART_ContentBorder">
<Setter Property="Background" Value="{DynamicResource ListViewItemBackgroundPointerOver}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ListViewItemForegroundPointerOver}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ Border#PART_ContentBorder">
<Setter Property="Background" Value="{DynamicResource ListViewItemBackgroundPressed}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ListViewItemForegroundPressed}" />
</Style>
</Style>
<Style Selector="^:selected">
<Style Selector="^ /template/ Border#PART_ContentBorder">
<Setter Property="Background" Value="{DynamicResource OverflowItemBackgroundSelected}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ListViewItemForegroundSelected}" />
</Style>
<Style Selector="^ /template/ Border#SelectionIndicator">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="^:not(:focus) /template/ Border#PART_ContentBorder">
<Setter Property="Background" Value="{DynamicResource OverflowItemBackgroundSelected}" />
</Style>
<Style Selector="^:not(:focus) /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ListViewItemForegroundSelected}" />
</Style>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ Border#PART_ContentBorder">
<Setter Property="Background" Value="{DynamicResource OverflowItemBackgroundSelectedPointerOver}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ListViewItemForegroundSelectedPointerOver}" />
</Style>
<Style Selector="^ /template/ Border#SelectionIndicator">
<Setter Property="BorderBrush" Value="{DynamicResource ListViewItemSelectionIndicatorPointerOverBrush}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ Border#PART_ContentBorder">
<Setter Property="Background" Value="{DynamicResource OverflowItemBackgroundSelectedPressed}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ListViewItemForegroundSelectedPressed}" />
</Style>
</Style>
<Style Selector="^:disabled /template/ Border#SelectionIndicator">
<Setter Property="BorderBrush" Value="{DynamicResource ListViewItemSelectionIndicatorDisabledBrush}" />
</Style>
</Style>
</ControlTheme>
<ControlTheme x:Key="{x:Type controls:Overflow}" TargetType="controls:Overflow">
<Setter Property="Template">
<ControlTemplate>
<StackPanel Margin="{TemplateBinding Margin}">
<controls:OverflowList
x:Name="PrimaryListBox"
Grid.Column="0"
ItemContainerTemplateSelector="{TemplateBinding ItemContainerTemplateSelector}"
ItemTemplate="{TemplateBinding ItemTemplate}"
SelectedItem="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.PrimarySelection, Mode=TwoWay}">
<controls:OverflowList.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="{StaticResource OverflowItemSpacing}" />
</ItemsPanelTemplate>
</controls:OverflowList.ItemsPanel>
</controls:OverflowList>
<Grid
x:Name="Spacer"
Grid.Column="1"
Width="{StaticResource OverflowItemSpacing}"
Height="{StaticResource OverflowItemSpacing}"
IsVisible="False" />
<Button
x:Name="OverflowButton"
Grid.Column="2"
MinWidth="{StaticResource OverflowItemSize}"
MinHeight="{StaticResource OverflowItemSize}"
Padding="0"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
CornerRadius="{StaticResource OverflowItemCornerRadius}"
Focusable="False"
IsVisible="False">
<Viewbox Width="14" Height="14">
<PathIcon
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="F1 M 6.24 11.799999 C 6.239999 12.146667 6.119999 12.440001 5.88 12.679999 C 5.64 12.92 5.346666 13.039999 5 13.039999 C 4.653333 13.039999 4.36 12.92 4.12 12.679999 C 3.88 12.440001 3.76 12.146667 3.76 11.799999 C 3.76 11.453333 3.88 11.16 4.12 10.919999 C 4.36 10.679999 4.653333 10.559999 5 10.559999 C 5.346666 10.559999 5.64 10.679999 5.88 10.919999 C 6.119999 11.16 6.239999 11.453333 6.24 11.799999 Z M 11.24 11.799999 C 11.24 12.146667 11.12 12.440001 10.88 12.679999 C 10.639999 12.92 10.346666 13.039999 10 13.039999 C 9.653333 13.039999 9.359999 12.92 9.12 12.679999 C 8.879999 12.440001 8.76 12.146667 8.76 11.799999 C 8.76 11.453333 8.879999 11.16 9.12 10.919999 C 9.359999 10.679999 9.653333 10.559999 10 10.559999 C 10.346666 10.559999 10.639999 10.679999 10.88 10.919999 C 11.12 11.16 11.24 11.453333 11.24 11.799999 Z M 15 13.039999 C 15.346665 13.039999 15.639999 12.92 15.88 12.679999 C 16.119999 12.440001 16.24 12.146667 16.24 11.799999 C 16.24 11.453333 16.119999 11.16 15.88 10.919999 C 15.639999 10.679999 15.346665 10.559999 15 10.559999 C 14.653333 10.559999 14.36 10.679999 14.12 10.919999 C 13.879999 11.16 13.759998 11.453333 13.759999 11.799999 C 13.759998 12.146667 13.879999 12.440001 14.12 12.679999 C 14.36 12.92 14.653333 13.039999 15 13.039999 Z " />
</Viewbox>
<Button.Flyout>
<Flyout>
<controls:OverflowList
x:Name="SecondaryListBox"
ItemContainerTemplateSelector="{TemplateBinding ItemContainerTemplateSelector}"
ItemTemplate="{TemplateBinding ItemTemplate}"
SelectedItem="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.SecondarySelection, Mode=TwoWay}">
<controls:OverflowList.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="{StaticResource OverflowItemSpacing}" />
</ItemsPanelTemplate>
</controls:OverflowList.ItemsPanel>
</controls:OverflowList>
</Flyout>
</Button.Flyout>
</Button>
</StackPanel>
</ControlTemplate>
</Setter>
<Style Selector="^:overflow /template/ Button#OverflowButton">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="^:overflow /template/ Grid#Spacer">
<Setter Property="IsVisible" Value="True" />
</Style>
</ControlTheme>
</ResourceDictionary>
@@ -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<IItemContainerTemplateSelector?> ItemContainerTemplateSelectorProperty =
AvaloniaProperty.Register<Overflow, IItemContainerTemplateSelector?>(nameof(ItemContainerTemplateSelector));
public static readonly StyledProperty<ITemplate<Panel?>> ItemsPanelProperty =
AvaloniaProperty.Register<Overflow, ITemplate<Panel?>>(nameof(ItemsPanel), new FuncTemplate<Panel?>(() => new StackPanel()));
public static readonly StyledProperty<IEnumerable?> ItemsSourceProperty =
AvaloniaProperty.Register<Overflow, IEnumerable?>(nameof(ItemsSource));
public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
AvaloniaProperty.Register<Overflow, IDataTemplate?>(nameof(ItemTemplate));
public static readonly StyledProperty<object?> SelectedItemProperty =
AvaloniaProperty.Register<Overflow, object?>(nameof(SelectedItem));
private static readonly StyledProperty<OverflowTemplateSettings> TemplateSettingsProperty =
AvaloniaProperty.Register<Overflow, OverflowTemplateSettings>(nameof(TemplateSettings));
private readonly ObservableCollection<object> primaryCollection = [];
private readonly ObservableCollection<object> secondaryCollection = [];
private OverflowList? primaryListBox;
private OverflowList? secondaryListBox;
public Overflow()
{
SetValue(TemplateSettingsProperty, new OverflowTemplateSettings());
TemplateSettings.GetPropertyChangedObservable(OverflowTemplateSettings.PrimarySelectionProperty)
.AddClassHandler<OverflowTemplateSettings>(OnPrimarySelectionPropertyChanged);
TemplateSettings.GetPropertyChangedObservable(OverflowTemplateSettings.SecondarySelectionProperty)
.AddClassHandler<OverflowTemplateSettings>(OnSecondarySelectionPropertyChanged);
}
public IItemContainerTemplateSelector? ItemContainerTemplateSelector
{
get => GetValue(ItemContainerTemplateSelectorProperty);
set => SetValue(ItemContainerTemplateSelectorProperty, value);
}
public ITemplate<Panel?> 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<OverflowList>("PrimaryListBox");
primaryListBox?.SetValue(ItemsControl.ItemsSourceProperty, primaryCollection);
secondaryListBox = args.NameScope.Get<OverflowList>("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<object>();
SetValue(SelectedItemProperty, selection);
}
private void OnSecondarySelectionPropertyChanged(OverflowTemplateSettings sender,
AvaloniaPropertyChangedEventArgs args)
{
object? selection = args.GetNewValue<object>();
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<object> 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);
}
}
}
@@ -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<IBrush> BadgeBrushProperty =
AvaloniaProperty.Register<OverflowItem, IBrush>(nameof(BadgeBrush));
public static readonly StyledProperty<string> BadgePathProperty =
AvaloniaProperty.Register<OverflowItem, string>(nameof(BadgePath));
public static readonly StyledProperty<ContentBadgePlacement> BadgePlacementProperty =
AvaloniaProperty.Register<OverflowItem, ContentBadgePlacement>(nameof(BadgePlacement), ContentBadgePlacement.BottomRight);
public static readonly StyledProperty<double> BadgeSizeProperty =
AvaloniaProperty.Register<OverflowItem, double>(nameof(BadgeSize), 14);
public static readonly StyledProperty<bool> IsBadgeVisibleProperty =
AvaloniaProperty.Register<OverflowItem, bool>(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);
}
}
@@ -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<IItemContainerTemplateSelector?> ItemContainerTemplateSelectorProperty =
AvaloniaProperty.Register<OverflowList, IItemContainerTemplateSelector?>(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;
}
}
@@ -0,0 +1,48 @@
using Avalonia;
namespace Toolkit.UI.Controls.Avalonia;
public class OverflowTemplateSettings :
AvaloniaObject
{
public static readonly StyledProperty<object?> PrimarySelectionProperty =
AvaloniaProperty.Register<OverflowTemplateSettings, object?>(nameof(PrimarySelection));
public static readonly StyledProperty<object?> SecondarySelectionProperty =
AvaloniaProperty.Register<OverflowTemplateSettings, object?>(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);
}
}
@@ -0,0 +1,95 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Toolkit.UI.Controls.Avalonia">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<StaticResource x:Key="PersonPictureForegroundThemeBrush" ResourceKey="TextOnAccentFillColorSelectedText" />
<StaticResource x:Key="PersonPictureEllipseBadgeForegroundThemeBrush" ResourceKey="TextOnAccentFillColorPrimaryBrush" />
<StaticResource x:Key="PersonPictureEllipseBadgeFillThemeBrush" ResourceKey="AccentFillColorDefaultBrush" />
<StaticResource x:Key="PersonPictureEllipseBadgeStrokeThemeBrush" ResourceKey="ControlFillColorTransparentBrush" />
<StaticResource x:Key="PersonPictureEllipseFillThemeBrush" ResourceKey="ControlFillColorDefaultBrush" />
<StaticResource x:Key="PersonPictureEllipseFillStrokeBrush" ResourceKey="CardStrokeColorDefaultBrush" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<StaticResource x:Key="PersonPictureForegroundThemeBrush" ResourceKey="TextOnAccentFillColorSelectedText" />
<StaticResource x:Key="PersonPictureEllipseBadgeForegroundThemeBrush" ResourceKey="TextOnAccentFillColorPrimaryBrush" />
<StaticResource x:Key="PersonPictureEllipseBadgeFillThemeBrush" ResourceKey="AccentFillColorDefaultBrush" />
<StaticResource x:Key="PersonPictureEllipseBadgeStrokeThemeBrush" ResourceKey="ControlFillColorTransparentBrush" />
<StaticResource x:Key="PersonPictureEllipseFillThemeBrush" ResourceKey="ControlFillColorDefaultBrush" />
<StaticResource x:Key="PersonPictureEllipseFillStrokeBrush" ResourceKey="CardStrokeColorDefaultBrush" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<x:Double x:Key="PersonPictureEllipseBadgeStrokeOpacity">1</x:Double>
<x:Double x:Key="PersonPictureEllipseBadgeImageSourceStrokeOpacity">1</x:Double>
<x:Double x:Key="PersonPictureEllipseStrokeThickness">1</x:Double>
<x:Double x:Key="PersonPictureEllipseBadgeStrokeThickness">2</x:Double>
<Thickness x:Key="PersonPictureBadgeGridMargin">0,-4,-4,0</Thickness>
<ControlTheme x:Key="{x:Type controls:PersonPicture}" TargetType="controls:PersonPicture">
<Setter Property="Foreground" Value="{DynamicResource PersonPictureForegroundThemeBrush}" />
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="Background" Value="{DynamicResource PersonPictureEllipseFillThemeBrush}" />
<Setter Property="FontWeight" Value="600" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Width" Value="100" />
<Setter Property="Height" Value="100" />
<Setter Property="Template">
<ControlTemplate>
<Grid x:Name="RootGrid">
<Ellipse
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Fill="{TemplateBinding Background}"
Stroke="{DynamicResource PersonPictureEllipseFillStrokeBrush}"
StrokeThickness="{DynamicResource PersonPictureEllipseStrokeThickness}" />
<TextBlock
x:Name="InitialsTextBlock"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="{TemplateBinding FontFamily}"
FontSize="40"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ActualInitials}" />
<Ellipse
x:Name="PersonPictureEllipse"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Fill="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ActualImageBrush}"
FlowDirection="LeftToRight" />
<Grid
x:Name="BadgeGrid"
Margin="{DynamicResource PersonPictureBadgeGridMargin}"
HorizontalAlignment="Right"
VerticalAlignment="Top"
IsVisible="False">
<Ellipse
x:Name="BadgingBackgroundEllipse"
Fill="{DynamicResource PersonPictureEllipseBadgeFillThemeBrush}"
Opacity="{DynamicResource PersonPictureEllipseBadgeStrokeOpacity}"
Stroke="{DynamicResource PersonPictureEllipseBadgeStrokeThemeBrush}"
StrokeThickness="{DynamicResource PersonPictureEllipseBadgeStrokeThickness}" />
<Ellipse
x:Name="BadgingEllipse"
FlowDirection="LeftToRight"
Opacity="0" />
<TextBlock
x:Name="BadgeNumberTextBlock"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="{TemplateBinding FontFamily}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{DynamicResource PersonPictureEllipseBadgeForegroundThemeBrush}" />
<controls:FontIcon
x:Name="BadgeGlyphIcon"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="{DynamicResource SymbolThemeFontFamily}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{DynamicResource PersonPictureEllipseBadgeForegroundThemeBrush}" />
</Grid>
</Grid>
</ControlTemplate>
</Setter>
</ControlTheme>
</ResourceDictionary>
@@ -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<string> BadgeGlyphProperty =
AvaloniaProperty.Register<PersonPicture, string>(nameof(BadgeGlyph));
public static readonly StyledProperty<IImage> BadgeImageSourceProperty =
AvaloniaProperty.Register<PersonPicture, IImage>(nameof(BadgeImageSource));
public static readonly StyledProperty<int> BadgeNumberProperty =
AvaloniaProperty.Register<PersonPicture, int>(nameof(BadgeNumber));
public static readonly StyledProperty<string> BadgeTextProperty =
AvaloniaProperty.Register<PersonPicture, string>(nameof(BadgeText));
public static readonly StyledProperty<string> DisplayNameProperty =
AvaloniaProperty.Register<PersonPicture, string>(nameof(DisplayName));
public static readonly StyledProperty<string> InitialsProperty =
AvaloniaProperty.Register<PersonPicture, string>(nameof(Initials));
public static readonly StyledProperty<bool> IsGroupProperty =
AvaloniaProperty.Register<PersonPicture, bool>(nameof(IsGroup));
public static readonly StyledProperty<IImage> ProfilePictureProperty =
AvaloniaProperty.Register<PersonPicture, IImage>(nameof(ProfilePicture));
private static readonly StyledProperty<PersonPictureTemplateSettings> TemplateSettingsProperty =
AvaloniaProperty.Register<PersonPicture, PersonPictureTemplateSettings>(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<TextBlock>("InitialsTextBlock");
badgeNumberTextBlock = args.NameScope.Get<TextBlock>("BadgeNumberTextBlock");
badgeGlyphIcon = args.NameScope.Get<FontIcon>("BadgeGlyphIcon");
badgingEllipse = args.NameScope.Get<Ellipse>("BadgingEllipse");
badgingBackgroundEllipse = args.NameScope.Get<Ellipse>("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);
}
}
}
}
@@ -0,0 +1,9 @@
namespace Toolkit.UI.Controls.Avalonia;
internal enum PersonPictureCharacterType
{
Other = 0,
Standard = 1,
Symbolic = 2,
Glyph = 3
}
@@ -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);
}
}
@@ -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;
}
}
}
@@ -0,0 +1,25 @@
using Avalonia;
using Avalonia.Media;
namespace Toolkit.UI.Controls.Avalonia;
public class PersonPictureTemplateSettings : AvaloniaObject
{
private static readonly StyledProperty<ImageBrush?> ActualImageBrushProperty =
AvaloniaProperty.Register<PersonPictureTemplateSettings, ImageBrush?>(nameof(ActualImageBrush));
private static readonly StyledProperty<string> ActualInitialsProperty =
AvaloniaProperty.Register<PersonPictureTemplateSettings, string>(nameof(ActualInitials));
public ImageBrush? ActualImageBrush
{
get => GetValue(ActualImageBrushProperty);
set => SetValue(ActualImageBrushProperty, value);
}
public string ActualInitials
{
get => GetValue(ActualInitialsProperty);
set => SetValue(ActualInitialsProperty, value);
}
}
@@ -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);
}
@@ -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);
}
@@ -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<bool>
{
internal BitList()
{
Count = 0;
List = new List<byte>(32);
}
internal BitList()
{
Count = 0;
List = new List<byte>(32);
}
internal BitList(IEnumerable<byte> byteArray)
{
Count = byteArray.Count();
List = byteArray.ToList();
}
internal BitList(IEnumerable<byte> byteArray)
{
Count = byteArray.Count();
List = byteArray.ToList();
}
internal List<byte> List { get; }
internal List<byte> 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<bool> 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<bool> 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<bool> items)
{
foreach (bool item in items)
{
Add(item);
}
}
internal void Add(IEnumerable<bool> 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;
}
}
@@ -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);
}
@@ -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);
}
}
@@ -1,48 +1,46 @@
using System;
namespace Gma.QrCodeNet.Encoding.DataEncodation;
public static class CharCountIndicatorTable
{
/// <remarks>ISO/IEC 18004:2000 Table 3 Page 18</remarks>
public static int[] GetCharCountIndicatorSet()
{
return new int[] { 8, 16, 16 };
}
/// <remarks>ISO/IEC 18004:2000 Table 3 Page 18</remarks>
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];
}
/// <summary>
/// Used to define length of the Character Count Indicator <see cref="GetBitCountInCharCountIndicator"/>
/// </summary>
/// <returns>Returns the 0 based index of the row from Chapter 8.4 Data encodation, Table 3 — Number of bits in Character Count Indicator. </returns>
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}.");
}
}
}
/// <summary>
/// Used to define length of the Character Count Indicator <see cref="GetBitCountInCharCountIndicator"/>
/// </summary>
/// <returns>Returns the 0 based index of the row from Chapter 8.4 Data encodation, Table 3 — Number of bits in Character Count Indicator. </returns>
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}.");
}
}
}
@@ -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</remarks>
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);
}
}
@@ -1,255 +1,252 @@
using System;
using System.Collections.Generic;
namespace Gma.QrCodeNet.Encoding.DataEncodation;
public sealed class ECISet
{
/// <summary>
/// ISO/IEC 18004:2006 Chapter 6.4.2 Mode indicator = 0111 Page 23
/// </summary>
private const int ECIMode = 7;
/// <summary>
/// ISO/IEC 18004:2006 Chapter 6.4.2 Mode indicator = 0111 Page 23
/// </summary>
private const int ECIMode = 7;
private const int ECIIndicatorNumBits = 4;
private const int ECIIndicatorNumBits = 4;
private Dictionary<string, int>? _nameToValue;
private Dictionary<int, string>? _valueToName;
private Dictionary<string, int>? _nameToValue;
private Dictionary<int, string>? _valueToName;
/// <summary>
/// Initialize ECI Set.
/// </summary>
/// <param name="option">AppendOption is enum under ECISet
/// Use NameToValue during Encode. ValueToName during Decode</param>
internal ECISet(AppendOption option)
{
Initialize(option);
}
/// <summary>
/// Initialize ECI Set.
/// </summary>
/// <param name="option">AppendOption is enum under ECISet
/// Use NameToValue during Encode. ValueToName during Decode</param>
internal ECISet(AppendOption option)
{
Initialize(option);
}
public enum AppendOption
{
NameToValue,
ValueToName,
Both
}
public enum AppendOption
{
NameToValue,
ValueToName,
Both
}
/// <summary>
/// Length indicator for number of ECI codewords
/// </summary>
/// <remarks>ISO/IEC 18004:2006 Chapter 6.4.2 Page 24.
/// 1 codeword length = 0. Any additional codeword add 1 to front. Eg: 3 = 110</remarks>
/// <description>Bits required for each one is:
/// one = 1, two = 2, three = 3</description>
private enum ECICodewordsLength
{
One = 0,
Two = 2,
Three = 6
}
/// <summary>
/// Length indicator for number of ECI codewords
/// </summary>
/// <remarks>ISO/IEC 18004:2006 Chapter 6.4.2 Page 24.
/// 1 codeword length = 0. Any additional codeword add 1 to front. Eg: 3 = 110</remarks>
/// <description>Bits required for each one is:
/// one = 1, two = 2, three = 3</description>
private enum ECICodewordsLength
{
One = 0,
Two = 2,
Three = 6
}
/// <remarks>ISO/IEC 18004:2006E ECI Designator Page 24</remarks>
/// <param name="eCIValue">Range: 0 ~ 999999</param>
/// <returns>Number of Codewords(Byte) for ECI Assignment Value</returns>
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.");
}
}
/// <remarks>ISO/IEC 18004:2006E ECI Designator Page 24</remarks>
/// <param name="eCIValue">Range: 0 ~ 999999</param>
/// <returns>Number of Codewords(Byte) for ECI Assignment Value</returns>
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.");
}
}
/// <remarks>ISO/IEC 18004:2006E ECI Designator Page 24</remarks>
/// <param name="eCIValue">Range: 0 ~ 999999</param>
/// <returns>Number of bits for ECI Assignment Value</returns>
private static int NumOfAssignmentBits(int eCIValue) => NumOfCodewords(eCIValue) * 8;
/// <remarks>ISO/IEC 18004:2006E ECI Designator Page 24</remarks>
/// <param name="eCIValue">Range: 0 ~ 999999</param>
/// <returns>Number of bits for ECI Assignment Value</returns>
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<string, int>();
break;
private void Initialize(AppendOption option)
{
switch (option)
{
case AppendOption.NameToValue:
_nameToValue = new Dictionary<string, int>();
break;
case AppendOption.ValueToName:
_valueToName = new Dictionary<int, string>();
break;
case AppendOption.ValueToName:
_valueToName = new Dictionary<int, string>();
break;
case AppendOption.Both:
_nameToValue = new Dictionary<string, int>();
_valueToName = new Dictionary<int, string>();
break;
case AppendOption.Both:
_nameToValue = new Dictionary<string, int>();
_valueToName = new Dictionary<int, string>();
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);
}
/// <remarks>ISO/IEC 18004:2006E ECI Designator Page 24</remarks>
/// <param name="eCIValue">Range: 0 ~ 999999</param>
/// <returns>Number of bits for ECI Header</returns>
internal static int NumOfECIHeaderBits(int eCIValue) => NumOfAssignmentBits(eCIValue) + 4;
/// <remarks>ISO/IEC 18004:2006E ECI Designator Page 24</remarks>
/// <param name="eCIValue">Range: 0 ~ 999999</param>
/// <returns>Number of bits for ECI Header</returns>
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}.");
}
}
/// <returns>ECI table in Dictionary collection</returns>
public Dictionary<string, int>? GetECITable()
{
if (_nameToValue is null)
{
Initialize(AppendOption.NameToValue);
}
/// <returns>ECI table in Dictionary collection</returns>
public Dictionary<string, int>? 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);
}
/// <remarks>ISO/IEC 18004:2006 Chapter 6.4.2 Page 24.</remarks>
internal BitList GetECIHeader(string encodingName)
{
int eciValue = GetECIValueByName(encodingName);
/// <remarks>ISO/IEC 18004:2006 Chapter 6.4.2 Page 24.</remarks>
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;
}
}
@@ -1,5 +1,3 @@
using System;
namespace Gma.QrCodeNet.Encoding.DataEncodation;
/// <summary>
@@ -14,67 +12,67 @@ namespace Gma.QrCodeNet.Encoding.DataEncodation;
/// <remarks>ISO/IEC 18004:2000 Chapter 8.4.4 Page 22</remarks>
internal class EightBitByteEncoder : EncoderBase
{
private const string DefaultEncoding = QRCodeConstantVariable.DefaultEncoding;
private const string DefaultEncoding = QRCodeConstantVariable.DefaultEncoding;
/// <summary>
/// Bitcount, Chapter 8.4.4, P.24
/// </summary>
private const int EightBitByteBitcount = 8;
/// <summary>
/// Bitcount, Chapter 8.4.4, P.24
/// </summary>
private const int EightBitByteBitcount = 8;
/// <summary>
/// EightBitByte encoder's encoding will change according to different region
/// </summary>
/// <param name="encoding">Default encoding is "iso-8859-1"</param>
internal EightBitByteEncoder(string encoding) : base()
{
Encoding = encoding ?? DefaultEncoding;
}
/// <summary>
/// EightBitByte encoder's encoding will change according to different region
/// </summary>
/// <param name="encoding">Default encoding is "iso-8859-1"</param>
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);
}
@@ -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; }
}
@@ -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;
/// <summary>
/// Returns the bit representation of input data.
/// </summary>
internal abstract BitList GetDataBits(string content);
/// <summary>
/// Returns the bit representation of input data.
/// </summary>
internal abstract BitList GetDataBits(string content);
/// <summary>
/// Returns bit representation of Modevalue.
/// </summary>
/// <remarks>See Chapter 8.4 Data encodation, Table 2 — Mode indicators</remarks>
internal BitList GetModeIndicator()
{
BitList modeIndicatorBits = new()
{
{ 0001 << 2, 4 }
};
return modeIndicatorBits;
}
/// <summary>
/// Returns bit representation of Modevalue.
/// </summary>
/// <remarks>See Chapter 8.4 Data encodation, Table 2 — Mode indicators</remarks>
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;
}
/// <summary>
/// Defines the length of the Character Count Indicator,
/// which varies according to the mode and the symbol version in use
/// </summary>
/// <returns>Number of bits in Character Count Indicator.</returns>
/// <remarks>
/// See Chapter 8.4 Data encodation, Table 3 — Number of bits in Character Count Indicator.
/// </remarks>
protected abstract int GetBitCountInCharCountIndicator(int version);
}
/// <summary>
/// Defines the length of the Character Count Indicator,
/// which varies according to the mode and the symbol version in use
/// </summary>
/// <returns>Number of bits in Character Count Indicator.</returns>
/// <remarks>
/// See Chapter 8.4 Data encodation, Table 3 — Number of bits in Character Count Indicator.
/// </remarks>
protected abstract int GetBitCountInCharCountIndicator(int version);
}
@@ -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<string, int>? eciSet = eciSets.GetECITable();
Dictionary<string, int>? 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<string, int> kvp in eciSet)
{
scanPos = ModeEncodeCheck.TryEncodeEightBitByte(content, kvp.Key, scanPos, contentLength);
if (scanPos == -1)
{
return kvp.Key;
}
}
foreach (KeyValuePair<string, int> 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;
}
}
}
@@ -1,72 +1,70 @@
using System;
namespace Gma.QrCodeNet.Encoding.DataEncodation.InputRecognition;
public static class ModeEncodeCheck
{
/// <summary>
/// Encoding.GetEncoding.GetBytes will transform char to 0x3F if that char not belong to current encoding table.
/// 0x3F is '?'
/// </summary>
private const int QuestionMarkChar = 0x3F;
/// <summary>
/// Encoding.GetEncoding.GetBytes will transform char to 0x3F if that char not belong to current encoding table.
/// 0x3F is '?'
/// </summary>
private const int QuestionMarkChar = 0x3F;
/// <summary>
/// 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.
/// </summary>
/// <param name="content">Input string</param>
/// <param name="encodingName">Encoding name. Check ECI table</param>
/// <returns>Returns -1 if from starting position to end encoding success. Else returns fail position</returns>
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.");
}
/// <summary>
/// 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.
/// </summary>
/// <param name="content">Input string</param>
/// <param name="encodingName">Encoding name. Check ECI table</param>
/// <returns>Returns -1 if from starting position to end encoding success. Else returns fail position</returns>
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;
}
}
@@ -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; }
}
@@ -2,59 +2,59 @@ namespace Gma.QrCodeNet.Encoding.EncodingRegion;
internal static class BCHCalculator
{
/// <summary>
/// Calculate int length by search for Most significant bit
/// </summary>
/// <param name="num">Input Number</param>
/// <returns>Most significant bit</returns>
internal static int PosMSB(int num) => num == 0 ? 0 : BinarySearchPos(num, 0, 32) + 1;
/// <summary>
/// Calculate int length by search for Most significant bit
/// </summary>
/// <param name="num">Input Number</param>
/// <returns>Most significant bit</returns>
internal static int PosMSB(int num) => num == 0 ? 0 : BinarySearchPos(num, 0, 32) + 1;
/// <summary>
/// Search for right side bit of Most significant bit
/// </summary>
/// <param name="num">Input number</param>
/// <param name="lowBoundary">Lower boundary. At start should be 0</param>
/// <param name="highBoundary">Higher boundary. At start should be 32</param>
/// <returns>Most significant bit - 1</returns>
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);
}
}
/// <summary>
/// Search for right side bit of Most significant bit
/// </summary>
/// <param name="num">Input number</param>
/// <param name="lowBoundary">Lower boundary. At start should be 0</param>
/// <param name="highBoundary">Higher boundary. At start should be 32</param>
/// <returns>Most significant bit - 1</returns>
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);
}
}
/// <summary>
/// With input number and polynomial number. Method will calculate BCH value and return
/// </summary>
/// <param name="num">Input number</param>
/// <param name="poly">Polynomial number</param>
/// <returns>BCH value</returns>
internal static int CalculateBCH(int num, int poly)
{
int polyMSB = PosMSB(poly);
/// <summary>
/// With input number and polynomial number. Method will calculate BCH value and return
/// </summary>
/// <param name="num">Input number</param>
/// <param name="poly">Polynomial number</param>
/// <returns>BCH value</returns>
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;
}
}
@@ -1,72 +1,70 @@
using System;
namespace Gma.QrCodeNet.Encoding.EncodingRegion;
/// <remarks>ISO/IEC 18004:2000 Chapter 8.7.3 Page 46</remarks>
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;
}
}
@@ -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;
/// <remarks>ISO/IEC 18004:2000 Chapter 8.9 Page 53</remarks>
internal static class FormatInformation
{
/// <summary>
/// From Appendix C in JISX0510:2004 (p.65).
/// </summary>
private const int FormatInfoPoly = 0x537;
/// <summary>
/// From Appendix C in JISX0510:2004 (p.65).
/// </summary>
private const int FormatInfoPoly = 0x537;
/// <summary>
/// From Appendix C in JISX0510:2004 (p.65).
/// </summary>
private const int FormatInfoMaskPattern = 0x5412;
/// <summary>
/// From Appendix C in JISX0510:2004 (p.65).
/// </summary>
private const int FormatInfoMaskPattern = 0x5412;
/// <summary>
/// Embed format information to tristatematrix.
/// Process combination of create info bits, BCH error correction bits calculation, embed towards matrix.
/// </summary>
/// <remarks>ISO/IEC 18004:2000 Chapter 8.9 Page 53</remarks>
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;
/// <summary>
/// Embed format information to tristatematrix.
/// Process combination of create info bits, BCH error correction bits calculation, embed towards matrix.
/// </summary>
/// <remarks>ISO/IEC 18004:2000 Chapter 8.9 Page 53</remarks>
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;
}
}
/// <summary>
/// According Table 25 — Error correction level indicators
/// Using these bits as enum values would destroy their order which currently corresponds to error correction strength.
/// </summary>
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))
};
}
}
/// <summary>
/// According Table 25 — Error correction level indicators
/// Using these bits as enum values would destroy their order which currently corresponds to error correction strength.
/// </summary>
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))
};
}
}
@@ -1,5 +1,3 @@
using System;
namespace Gma.QrCodeNet.Encoding.EncodingRegion;
/// <summary>
@@ -8,63 +6,63 @@ namespace Gma.QrCodeNet.Encoding.EncodingRegion;
/// <remarks>ISO/IEC 18004:2000 Chapter 8.10 Page 54</remarks>
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;
/// <summary>
/// Embed version information to Matrix
/// Only for version greater than or equal to 7
/// </summary>
internal static void EmbedVersionInformation(this TriStateMatrix tsMatrix, int version)
{
if (version < 7)
{
return;
}
/// <summary>
/// Embed version information to Matrix
/// Only for version greater than or equal to 7
/// </summary>
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;
}
}
@@ -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<byte> dataCodewordsByte = dataCodewords.List;
internal static BitList FillECCodewords(BitList dataCodewords, VersionDetail vd)
{
List<byte> 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;
}
}
@@ -2,8 +2,8 @@ namespace Gma.QrCodeNet.Encoding;
public enum ErrorCorrectionLevel
{
L,
M,
Q,
H
}
L,
M,
Q,
H
}
@@ -1,5 +1,3 @@
using System;
namespace Gma.QrCodeNet.Encoding;
/// <summary>
@@ -7,11 +5,11 @@ namespace Gma.QrCodeNet.Encoding;
/// </summary>
public class InputOutOfBoundaryException : Exception
{
public InputOutOfBoundaryException() : base()
{
}
public InputOutOfBoundaryException() : base()
{
}
public InputOutOfBoundaryException(string message) : base(message)
{
}
}
public InputOutOfBoundaryException(string message) : base(message)
{
}
}
@@ -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
}
@@ -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);
}
@@ -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; }
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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<Pattern> AllPatterns()
{
foreach (MaskPatternType patternType in Enum.GetValues(typeof(MaskPatternType)))
{
yield return CreateByType(patternType);
}
}
}
internal IEnumerable<Pattern> AllPatterns()
{
foreach (MaskPatternType patternType in Enum.GetValues(typeof(MaskPatternType)))
{
yield return CreateByType(patternType);
}
}
}
@@ -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));
}
}
@@ -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);
}
@@ -5,81 +5,81 @@ namespace Gma.QrCodeNet.Encoding.Masking.Scoring;
/// </summary>
internal class Penalty1 : Penalty
{
/// <summary>
/// Calculate penalty value for first rule.
/// </summary>
internal override int PenaltyCalculate(BitMatrix matrix)
{
int penaltyValue = PenaltyCalculation(matrix, true) + PenaltyCalculation(matrix, false);
return penaltyValue;
}
/// <summary>
/// Calculate penalty value for first rule.
/// </summary>
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;
}
}
@@ -5,47 +5,47 @@ namespace Gma.QrCodeNet.Encoding.Masking.Scoring;
/// </summary>
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;
}
}
@@ -5,137 +5,137 @@ namespace Gma.QrCodeNet.Encoding.Masking.Scoring;
/// </summary>
internal class Penalty3 : Penalty
{
/// <summary>
/// Calculate penalty value for Third rule.
/// </summary>
internal override int PenaltyCalculate(BitMatrix matrix) => PenaltyCalculation(matrix, true) + PenaltyCalculation(matrix, false);
/// <summary>
/// Calculate penalty value for Third rule.
/// </summary>
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;
}
}
}
@@ -1,5 +1,3 @@
using System;
namespace Gma.QrCodeNet.Encoding.Masking.Scoring;
/// <summary>
@@ -7,30 +5,30 @@ namespace Gma.QrCodeNet.Encoding.Masking.Scoring;
/// </summary>
internal class Penalty4 : Penalty
{
/// <summary>
/// Calculate penalty value for Fourth rule.
/// Perform O(n) search for available x modules
/// </summary>
internal override int PenaltyCalculate(BitMatrix matrix)
{
int width = matrix.Width;
int darkBitCount = 0;
/// <summary>
/// Calculate penalty value for Fourth rule.
/// Perform O(n) search for available x modules
/// </summary>
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;
}
}
@@ -1,6 +1,3 @@
using System;
using System.Collections.Generic;
namespace Gma.QrCodeNet.Encoding.Masking.Scoring;
/// <summary>
@@ -8,23 +5,23 @@ namespace Gma.QrCodeNet.Encoding.Masking.Scoring;
/// </summary>
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<Penalty> AllRules()
{
foreach (PenaltyRules penaltyRule in Enum.GetValues(typeof(PenaltyRules)))
{
yield return CreateByRule(penaltyRule);
}
}
}
internal IEnumerable<Penalty> AllRules()
{
foreach (PenaltyRules penaltyRule in Enum.GetValues(typeof(PenaltyRules)))
{
yield return CreateByRule(penaltyRule);
}
}
}
@@ -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
}
@@ -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})";
}
@@ -1,32 +1,31 @@
using System.Collections;
using System.Collections.Generic;
namespace Gma.QrCodeNet.Encoding;
internal struct MatrixRectangle : IEnumerable<MatrixPoint>
{
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<MatrixPoint> 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<MatrixPoint> 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})";
}
@@ -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})";
}
}
@@ -2,7 +2,7 @@ namespace Gma.QrCodeNet.Encoding;
public enum MatrixStatus
{
None,
NoMask,
Data
}
None,
NoMask,
Data
}
@@ -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);
}
}
@@ -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<byte>(),
Array.Empty<byte>(),
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<byte>(),
Array.Empty<byte>(),
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<MatrixPoint> GetNonColidingCoordinatePairs(TriStateMatrix matrix)
{
return
GetAllCoordinatePairs()
.Where(point => matrix.MStatus(point.Offset(2, 2)) == MatrixStatus.None);
}
public IEnumerable<MatrixPoint> GetNonColidingCoordinatePairs(TriStateMatrix matrix)
{
return
GetAllCoordinatePairs()
.Where(point => matrix.MStatus(point.Offset(2, 2)) == MatrixStatus.None);
}
private IEnumerable<MatrixPoint> GetAllCoordinatePairs()
{
IEnumerable<byte> 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<MatrixPoint> GetAllCoordinatePairs()
{
IEnumerable<byte> 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<byte> GetPatternCoordinatesByVersion(int version)
{
return AlignmentPatternCoordinatesByVersion[version];
}
}
private static IEnumerable<byte> GetPatternCoordinatesByVersion(int version)
{
return AlignmentPatternCoordinatesByVersion[version];
}
}
@@ -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;
}
}

Some files were not shown because too many files have changed in this diff Show More