using System; using Avalonia; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Media.Imaging; using Avalonia.Styling; namespace SourceGit.Views { public class ImageDiffView : Control { public static readonly StyledProperty AlphaProperty = AvaloniaProperty.Register(nameof(Alpha), 0.5); public double Alpha { get => GetValue(AlphaProperty); set => SetValue(AlphaProperty, value); } public static readonly StyledProperty OldImageProperty = AvaloniaProperty.Register(nameof(OldImage), null); public Bitmap OldImage { get => GetValue(OldImageProperty); set => SetValue(OldImageProperty, value); } public static readonly StyledProperty NewImageProperty = AvaloniaProperty.Register(nameof(NewImage), null); public Bitmap NewImage { get => GetValue(NewImageProperty); set => SetValue(NewImageProperty, value); } static ImageDiffView() { AffectsMeasure(OldImageProperty, NewImageProperty); AffectsRender(AlphaProperty); } public override void Render(DrawingContext context) { if (_bgBrush == null) { var maskBrush = new SolidColorBrush(ActualThemeVariant == ThemeVariant.Dark ? 0xFF404040 : 0xFFBBBBBB); var bg = new DrawingGroup() { Children = { new GeometryDrawing() { Brush = maskBrush, Geometry = new RectangleGeometry(new Rect(0, 0, 12, 12)) }, new GeometryDrawing() { Brush = maskBrush, Geometry = new RectangleGeometry(new Rect(12, 12, 12, 12)) }, } }; _bgBrush = new DrawingBrush(bg) { AlignmentX = AlignmentX.Left, AlignmentY = AlignmentY.Top, DestinationRect = new RelativeRect(new Size(24, 24), RelativeUnit.Absolute), Stretch = Stretch.None, TileMode = TileMode.Tile, }; } context.FillRectangle(_bgBrush, new Rect(Bounds.Size)); var alpha = Alpha; var w = Bounds.Width - 16; var h = Bounds.Height - 16; var x = w * alpha; var left = OldImage; if (left != null && alpha > 0) { var src = new Rect(0, 0, left.Size.Width * alpha, left.Size.Height); var dst = new Rect(8, 8, x, h); context.DrawImage(left, src, dst); } var right = NewImage; if (right != null && alpha < 1) { var src = new Rect(right.Size.Width * alpha, 0, right.Size.Width * (1 - alpha), right.Size.Height); var dst = new Rect(x + 8, 8, w - x, h); context.DrawImage(right, src, dst); } context.DrawLine(new Pen(Brushes.DarkGreen, 2), new Point(x + 8, 0), new Point(x + 8, Bounds.Height)); } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); if (change.Property.Name == "ActualThemeVariant") { _bgBrush = null; InvalidateVisual(); } } protected override Size MeasureOverride(Size availableSize) { var left = OldImage; var right = NewImage; if (left != null) { var lSize = GetDesiredSize(left.Size, availableSize); if (right != null) { var rSize = GetDesiredSize(right.Size, availableSize); if (rSize.Width > lSize.Width) return rSize; return lSize; } else { return lSize; } } else if (right != null) { return GetDesiredSize(right.Size, availableSize); } else { return availableSize; } } private Size GetDesiredSize(Size img, Size available) { var w = available.Width - 16; var h = available.Height - 16; if (img.Width <= w) { if (img.Height <= h) { return new Size(img.Width + 16, img.Height + 16); } else { return new Size(h * img.Width / img.Height + 16, available.Height); } } else { var s = Math.Max(img.Width / w, img.Height / h); return new Size(img.Width / s + 16, img.Height / s + 16); } } private DrawingBrush _bgBrush = null; } public partial class DiffView : UserControl { public DiffView() { InitializeComponent(); } } }