You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
133 lines
5.1 KiB
133 lines
5.1 KiB
1 year ago
|
using System;
|
||
|
using System.Diagnostics.CodeAnalysis;
|
||
|
using System.Reflection;
|
||
|
using AsyncImageLoader;
|
||
|
using Avalonia;
|
||
|
using Avalonia.Layout;
|
||
|
using Avalonia.Media;
|
||
|
|
||
|
namespace StabilityMatrix.Avalonia.Controls;
|
||
|
|
||
|
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
||
|
public class BetterAdvancedImage : AdvancedImage
|
||
|
{
|
||
|
#region Reflection Shenanigans to access private parent fields
|
||
|
[NotNull]
|
||
|
private static readonly FieldInfo? IsCornerRadiusUsedField = typeof(AdvancedImage).GetField(
|
||
|
"_isCornerRadiusUsed",BindingFlags.Instance | BindingFlags.NonPublic);
|
||
|
|
||
|
[NotNull]
|
||
|
private static readonly FieldInfo? CornerRadiusClipField = typeof(AdvancedImage).GetField(
|
||
|
"_cornerRadiusClip",BindingFlags.Instance | BindingFlags.NonPublic);
|
||
|
|
||
|
private bool IsCornerRadiusUsed
|
||
|
{
|
||
|
get => IsCornerRadiusUsedField.GetValue(this) as bool? ?? false;
|
||
|
set => IsCornerRadiusUsedField.SetValue(this, value);
|
||
|
}
|
||
|
|
||
|
private RoundedRect CornerRadiusClip
|
||
|
{
|
||
|
get => (RoundedRect) CornerRadiusClipField.GetValue(this)!;
|
||
|
set => CornerRadiusClipField.SetValue(this, value);
|
||
|
}
|
||
|
|
||
|
static BetterAdvancedImage()
|
||
|
{
|
||
|
if (IsCornerRadiusUsedField is null)
|
||
|
{
|
||
|
throw new NullReferenceException("IsCornerRadiusUsedField was not resolved");
|
||
|
}
|
||
|
if (CornerRadiusClipField is null)
|
||
|
{
|
||
|
throw new NullReferenceException("CornerRadiusClipField was not resolved");
|
||
|
}
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
protected override Type StyleKeyOverride { get; } = typeof(AdvancedImage);
|
||
|
|
||
|
public BetterAdvancedImage(Uri? baseUri) : base(baseUri)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public BetterAdvancedImage(IServiceProvider serviceProvider) : base(serviceProvider)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||
|
/// <inheritdoc />
|
||
|
public override void Render(DrawingContext context)
|
||
|
{
|
||
|
var source = CurrentImage;
|
||
|
|
||
|
if (source != null && Bounds is { Width: > 0, Height: > 0 })
|
||
|
{
|
||
|
var viewPort = new Rect(Bounds.Size);
|
||
|
var sourceSize = source.Size;
|
||
|
|
||
|
var scale = Stretch.CalculateScaling(Bounds.Size, sourceSize, StretchDirection);
|
||
|
var scaledSize = sourceSize * scale;
|
||
|
|
||
|
// Calculate starting points for dest
|
||
|
var destX = HorizontalContentAlignment switch
|
||
|
{
|
||
|
HorizontalAlignment.Left => 0,
|
||
|
HorizontalAlignment.Center => (int) (viewPort.Width - scaledSize.Width) / 2,
|
||
|
HorizontalAlignment.Right => (int) (viewPort.Width - scaledSize.Width),
|
||
|
// Stretch is default, use center
|
||
|
HorizontalAlignment.Stretch => (int) (viewPort.Width - scaledSize.Width) / 2,
|
||
|
_ => throw new ArgumentException(nameof(HorizontalContentAlignment))
|
||
|
};
|
||
|
var destY = VerticalContentAlignment switch
|
||
|
{
|
||
|
VerticalAlignment.Top => 0,
|
||
|
VerticalAlignment.Center => (int) (viewPort.Height - scaledSize.Height) / 2,
|
||
|
VerticalAlignment.Bottom => (int) (viewPort.Height - scaledSize.Height),
|
||
|
VerticalAlignment.Stretch => 0, // Stretch is default, use top
|
||
|
_ => throw new ArgumentException(nameof(VerticalContentAlignment))
|
||
|
};
|
||
|
|
||
|
var destRect = viewPort
|
||
|
.CenterRect(new Rect(scaledSize))
|
||
|
.WithX(destX)
|
||
|
.WithY(destY)
|
||
|
.Intersect(viewPort);
|
||
|
var destRectUnscaledSize = destRect.Size / scale;
|
||
|
|
||
|
// Calculate starting points for source
|
||
|
var sourceX = HorizontalContentAlignment switch
|
||
|
{
|
||
|
HorizontalAlignment.Left => 0,
|
||
|
HorizontalAlignment.Center => (int) (sourceSize - destRectUnscaledSize).Width / 2,
|
||
|
HorizontalAlignment.Right => (int) (sourceSize - destRectUnscaledSize).Width,
|
||
|
// Stretch is default, use center
|
||
|
HorizontalAlignment.Stretch => (int) (sourceSize - destRectUnscaledSize).Width / 2,
|
||
|
_ => throw new ArgumentException(nameof(HorizontalContentAlignment))
|
||
|
};
|
||
|
var sourceY = VerticalContentAlignment switch
|
||
|
{
|
||
|
VerticalAlignment.Top => 0,
|
||
|
VerticalAlignment.Center => (int) (sourceSize - destRectUnscaledSize).Height / 2,
|
||
|
VerticalAlignment.Bottom => (int) (sourceSize - destRectUnscaledSize).Height,
|
||
|
VerticalAlignment.Stretch => 0, // Stretch is default, use top
|
||
|
_ => throw new ArgumentException(nameof(VerticalContentAlignment))
|
||
|
};
|
||
|
|
||
|
var sourceRect = new Rect(sourceSize)
|
||
|
.CenterRect(new Rect(destRect.Size / scale))
|
||
|
.WithX(sourceX)
|
||
|
.WithY(sourceY);
|
||
|
|
||
|
DrawingContext.PushedState? pushedState =
|
||
|
IsCornerRadiusUsed ? context.PushClip(CornerRadiusClip) : null;
|
||
|
context.DrawImage(source, sourceRect, destRect);
|
||
|
pushedState?.Dispose();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
base.Render(context);
|
||
|
}
|
||
|
}
|
||
|
}
|