Merged
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -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);
|
||||
}
|
||||
}
|
||||
+39
-41
@@ -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;
|
||||
}
|
||||
}
|
||||
+52
-54
@@ -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);
|
||||
}
|
||||
+39
-42
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
-62
@@ -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;
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
-30
@@ -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
|
||||
}
|
||||
+8
-8
@@ -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);
|
||||
}
|
||||
}
|
||||
+90
-94
@@ -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];
|
||||
}
|
||||
}
|
||||
+9
-11
@@ -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
Reference in New Issue
Block a user