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.
140 lines
5.2 KiB
140 lines
5.2 KiB
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")] |
|
[SuppressMessage("ReSharper", "UnusedMember.Local")] |
|
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); |
|
|
|
if (IsCornerRadiusUsed) |
|
{ |
|
using (context.PushClip(CornerRadiusClip)) |
|
{ |
|
context.DrawImage(source, sourceRect, destRect); |
|
} |
|
} |
|
else |
|
{ |
|
context.DrawImage(source, sourceRect, destRect); |
|
} |
|
} |
|
else |
|
{ |
|
base.Render(context); |
|
} |
|
} |
|
}
|
|
|