feature: add two view mode for image diff - side-by-side and blend

This commit is contained in:
leo 2024-06-21 17:48:19 +08:00
parent d3d6889e25
commit 8e88df92b3
No known key found for this signature in database
10 changed files with 490 additions and 231 deletions

View file

@ -0,0 +1,19 @@
using Avalonia.Data.Converters;
namespace SourceGit.Converters
{
public static class DoubleConverters
{
public static readonly FuncValueConverter<double, double> Increase =
new FuncValueConverter<double, double>(v => v + 1.0);
public static readonly FuncValueConverter<double, double> Decrease =
new FuncValueConverter<double, double>(v => v - 1.0);
public static readonly FuncValueConverter<double, string> ToPercentage =
new FuncValueConverter<double, string>(v => (v * 100).ToString("F3") + "%");
public static readonly FuncValueConverter<double, string> OneMinusToPercentage =
new FuncValueConverter<double, string>(v => ((1.0 - v) * 100).ToString("F3") + "%");
}
}

View file

@ -1,13 +0,0 @@
using Avalonia.Data.Converters;
namespace SourceGit.Converters
{
public static class FontSizeModifyConverters
{
public static readonly FuncValueConverter<double, double> Increase =
new FuncValueConverter<double, double>(v => v + 1.0);
public static readonly FuncValueConverter<double, double> Decrease =
new FuncValueConverter<double, double>(v => v - 1.0);
}
}

View file

@ -25,7 +25,7 @@
</DockPanel> </DockPanel>
<SelectableTextBlock Text="{Binding AuthorTimeStr}" <SelectableTextBlock Text="{Binding AuthorTimeStr}"
Margin="2,0,0,0" Margin="2,0,0,0"
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Decrease}}" FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}"
Foreground="{DynamicResource Brush.FG2}"/> Foreground="{DynamicResource Brush.FG2}"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
@ -41,7 +41,7 @@
</DockPanel> </DockPanel>
<SelectableTextBlock Text="{Binding CommitterTimeStr}" <SelectableTextBlock Text="{Binding CommitterTimeStr}"
Margin="2,0,0,0" Margin="2,0,0,0"
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Decrease}}" FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}"
Foreground="{DynamicResource Brush.FG2}"/> Foreground="{DynamicResource Brush.FG2}"/>
</StackPanel> </StackPanel>
</Grid> </Grid>

View file

@ -184,60 +184,7 @@
<!-- Image Diff --> <!-- Image Diff -->
<DataTemplate DataType="m:ImageDiff"> <DataTemplate DataType="m:ImageDiff">
<Grid RowDefinitions="Auto,*" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="8,16,8,0"> <v:ImageDiffView/>
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto" HorizontalAlignment="Center">
<Border Grid.Column="0" Height="16" Background="{DynamicResource Brush.Badge}" CornerRadius="8" VerticalAlignment="Center">
<TextBlock Classes="monospace" Text="{DynamicResource Text.Diff.Binary.Old}" Margin="8,0" FontSize="10"/>
</Border>
<TextBlock Grid.Column="1" Classes="monospace" Text="{Binding OldImageSize}" Margin="8,0,0,0"/>
<TextBlock Grid.Column="2" Classes="monospace" Text="{Binding OldFileSize}" Foreground="{DynamicResource Brush.FG2}" Margin="16,0,0,0" HorizontalAlignment="Right"/>
<TextBlock Grid.Column="3" Classes="monospace" Text="{DynamicResource Text.Bytes}" Foreground="{DynamicResource Brush.FG2}" Margin="2,0,0,0"/>
<Border Grid.Column="4" Height="16" Background="Green" CornerRadius="8" VerticalAlignment="Center" Margin="32,0,0,0">
<TextBlock Classes="monospace" Text="{DynamicResource Text.Diff.Binary.New}" Margin="8,0" FontSize="10"/>
</Border>
<TextBlock Grid.Column="5" Classes="monospace" Text="{Binding NewImageSize}" Margin="8,0,0,0"/>
<TextBlock Grid.Column="6" Classes="monospace" Text="{Binding NewFileSize}" Foreground="{DynamicResource Brush.FG2}" Margin="16,0,0,0" HorizontalAlignment="Right"/>
<TextBlock Grid.Column="7" Classes="monospace" Text="{DynamicResource Text.Bytes}" Foreground="{DynamicResource Brush.FG2}" Margin="2,0,0,0"/>
</Grid>
<Grid Grid.Row="1" RowDefinitions="*,Auto,Auto" Margin="0,16,0,0" HorizontalAlignment="Center">
<Border Grid.Row="0" Background="{DynamicResource Brush.Window}" Effect="drop-shadow(0 0 8 #A0000000)">
<Border BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}" Margin="8">
<v:ImageDiffView Alpha="{Binding #ImageDiffSlider.Value}"
OldImage="{Binding Old}"
NewImage="{Binding New}"
RenderOptions.BitmapInterpolationMode="HighQuality"/>
</Border>
</Border>
<Grid Grid.Row="1" ColumnDefinitions="*,*" Margin="0,8,0,0">
<TextBlock Grid.Column="0" Classes="monospace" Text="{DynamicResource Text.Diff.Binary.Old}" Foreground="{DynamicResource Brush.FG2}" FontSize="10"/>
<TextBlock Grid.Column="1" Classes="monospace" Text="{DynamicResource Text.Diff.Binary.New}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Right" FontSize="10"/>
</Grid>
<Slider Grid.Row="2"
x:Name="ImageDiffSlider"
Minimum="0" Maximum="1"
VerticalAlignment="Top"
TickPlacement="None"
Margin="8,4,8,0"
MinHeight="0"
Foreground="{DynamicResource Brush.Border1}"
Value="0.5">
<Slider.Resources>
<Thickness x:Key="SliderTopHeaderMargin">0,0,0,4</Thickness>
<GridLength x:Key="SliderPreContentMargin">0</GridLength>
<GridLength x:Key="SliderPostContentMargin">0</GridLength>
<CornerRadius x:Key="SliderThumbCornerRadius">8</CornerRadius>
<x:Double x:Key="SliderHorizontalThumbWidth">16</x:Double>
<x:Double x:Key="SliderHorizontalThumbHeight">16</x:Double>
</Slider.Resources>
</Slider>
</Grid>
</Grid>
</DataTemplate> </DataTemplate>
<!-- Text Diff --> <!-- Text Diff -->

View file

@ -1,164 +1,7 @@
using System;
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Styling;
namespace SourceGit.Views namespace SourceGit.Views
{ {
public class ImageDiffView : Control
{
public static readonly StyledProperty<double> AlphaProperty =
AvaloniaProperty.Register<ImageDiffView, double>(nameof(Alpha), 0.5);
public double Alpha
{
get => GetValue(AlphaProperty);
set => SetValue(AlphaProperty, value);
}
public static readonly StyledProperty<Bitmap> OldImageProperty =
AvaloniaProperty.Register<ImageDiffView, Bitmap>(nameof(OldImage), null);
public Bitmap OldImage
{
get => GetValue(OldImageProperty);
set => SetValue(OldImageProperty, value);
}
public static readonly StyledProperty<Bitmap> NewImageProperty =
AvaloniaProperty.Register<ImageDiffView, Bitmap>(nameof(NewImage), null);
public Bitmap NewImage
{
get => GetValue(NewImageProperty);
set => SetValue(NewImageProperty, value);
}
static ImageDiffView()
{
AffectsMeasure<ImageDiffView>(OldImageProperty, NewImageProperty);
AffectsRender<ImageDiffView>(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 partial class DiffView : UserControl
{ {
public DiffView() public DiffView()

View file

@ -55,7 +55,7 @@
<TextBlock Text="{DynamicResource Text.Hotkeys.Global}" <TextBlock Text="{DynamicResource Text.Hotkeys.Global}"
Foreground="{DynamicResource Brush.FG2}" Foreground="{DynamicResource Brush.FG2}"
FontWeight="Bold" FontWeight="Bold"
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Increase}}" FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Increase}}"
Margin="0,0,0,8"/> Margin="0,0,0,8"/>
<Grid RowDefinitions="20,20,20,20,20,20" ColumnDefinitions="150,*"> <Grid RowDefinitions="20,20,20,20,20,20" ColumnDefinitions="150,*">
@ -81,7 +81,7 @@
<TextBlock Text="{DynamicResource Text.Hotkeys.Repo}" <TextBlock Text="{DynamicResource Text.Hotkeys.Repo}"
Foreground="{DynamicResource Brush.FG2}" Foreground="{DynamicResource Brush.FG2}"
FontWeight="Bold" FontWeight="Bold"
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Increase}}" FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Increase}}"
Margin="0,8"/> Margin="0,8"/>
<Grid RowDefinitions="20,20,20,20,20,20,20,20" ColumnDefinitions="150,*"> <Grid RowDefinitions="20,20,20,20,20,20,20,20" ColumnDefinitions="150,*">
@ -113,7 +113,7 @@
<TextBlock Text="{DynamicResource Text.Hotkeys.TextEditor}" <TextBlock Text="{DynamicResource Text.Hotkeys.TextEditor}"
Foreground="{DynamicResource Brush.FG2}" Foreground="{DynamicResource Brush.FG2}"
FontWeight="Bold" FontWeight="Bold"
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Increase}}" FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Increase}}"
Margin="0,8"/> Margin="0,8"/>
<Grid RowDefinitions="20,20,20,20" ColumnDefinitions="150,*"> <Grid RowDefinitions="20,20,20,20" ColumnDefinitions="150,*">

View file

@ -0,0 +1,169 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:m="using:SourceGit.Models"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views"
xmlns:c="using:SourceGit.Converters"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.ImageDiffView"
x:DataType="m:ImageDiff">
<TabControl Margin="0,0,0,8" TabStripPlacement="Bottom">
<TabControl.Styles>
<Style Selector="TabControl /template/ ItemsPresenter#PART_ItemsPresenter > WrapPanel">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</TabControl.Styles>
<TabItem Header="SIDE-BY-SIDE">
<Grid ColumnDefinitions="*,32,*" RowDefinitions="Auto,*" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="8,16">
<StackPanel Grid.Row="0" Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Center">
<Border Height="16" Background="{DynamicResource Brush.Badge}" CornerRadius="8" VerticalAlignment="Center">
<TextBlock Classes="monospace" Text="{DynamicResource Text.Diff.Binary.Old}" Margin="8,0" FontSize="10"/>
</Border>
<TextBlock Grid.Column="1" Classes="monospace" Text="{Binding OldImageSize}" Margin="8,0,0,0"/>
<TextBlock Grid.Column="2" Classes="monospace" Text="{Binding OldFileSize}" Foreground="{DynamicResource Brush.FG2}" Margin="16,0,0,0" HorizontalAlignment="Right"/>
<TextBlock Grid.Column="3" Classes="monospace" Text="{DynamicResource Text.Bytes}" Foreground="{DynamicResource Brush.FG2}" Margin="2,0,0,0"/>
</StackPanel>
<Border Grid.Row="1" Grid.Column="0" Margin="0,12,0,0" Effect="drop-shadow(0 0 8 #A0000000)">
<Border Background="{DynamicResource Brush.Popup}" HorizontalAlignment="Center" Padding="8">
<Border BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}">
<v:ImageContainer>
<Image Source="{Binding Old}" Stretch="Uniform" VerticalAlignment="Center"/>
</v:ImageContainer>
</Border>
</Border>
</Border>
<StackPanel Grid.Row="0" Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Center">
<Border Height="16" Background="Green" CornerRadius="8" VerticalAlignment="Center">
<TextBlock Classes="monospace" Text="{DynamicResource Text.Diff.Binary.New}" Margin="8,0" FontSize="10"/>
</Border>
<TextBlock Grid.Column="1" Classes="monospace" Text="{Binding NewImageSize}" Margin="8,0,0,0"/>
<TextBlock Grid.Column="2" Classes="monospace" Text="{Binding NewFileSize}" Foreground="{DynamicResource Brush.FG2}" Margin="16,0,0,0" HorizontalAlignment="Right"/>
<TextBlock Grid.Column="3" Classes="monospace" Text="{DynamicResource Text.Bytes}" Foreground="{DynamicResource Brush.FG2}" Margin="2,0,0,0"/>
</StackPanel>
<Border Grid.Row="1" Grid.Column="2" Margin="0,12,0,0" Effect="drop-shadow(0 0 8 #A0000000)">
<Border Background="{DynamicResource Brush.Popup}" HorizontalAlignment="Center" Padding="8">
<Border BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}">
<v:ImageContainer>
<Image Source="{Binding New}" Stretch="Uniform"/>
</v:ImageContainer>
</Border>
</Border>
</Border>
</Grid>
</TabItem>
<TabItem Header="SWIPE">
<Grid RowDefinitions="Auto,*" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="8,16">
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto" HorizontalAlignment="Center">
<Border Grid.Column="0" Height="16" Background="{DynamicResource Brush.Badge}" CornerRadius="8" VerticalAlignment="Center">
<TextBlock Classes="monospace" Text="{DynamicResource Text.Diff.Binary.Old}" Margin="8,0" FontSize="10"/>
</Border>
<TextBlock Grid.Column="1" Classes="monospace" Text="{Binding OldImageSize}" Margin="8,0,0,0"/>
<TextBlock Grid.Column="2" Classes="monospace" Text="{Binding OldFileSize}" Foreground="{DynamicResource Brush.FG2}" Margin="16,0,0,0" HorizontalAlignment="Right"/>
<TextBlock Grid.Column="3" Classes="monospace" Text="{DynamicResource Text.Bytes}" Foreground="{DynamicResource Brush.FG2}" Margin="2,0,0,0"/>
<Border Grid.Column="4" Height="16" Background="Green" CornerRadius="8" VerticalAlignment="Center" Margin="32,0,0,0">
<TextBlock Classes="monospace" Text="{DynamicResource Text.Diff.Binary.New}" Margin="8,0" FontSize="10"/>
</Border>
<TextBlock Grid.Column="5" Classes="monospace" Text="{Binding NewImageSize}" Margin="8,0,0,0"/>
<TextBlock Grid.Column="6" Classes="monospace" Text="{Binding NewFileSize}" Foreground="{DynamicResource Brush.FG2}" Margin="16,0,0,0" HorizontalAlignment="Right"/>
<TextBlock Grid.Column="7" Classes="monospace" Text="{DynamicResource Text.Bytes}" Foreground="{DynamicResource Brush.FG2}" Margin="2,0,0,0"/>
</Grid>
<Border Grid.Row="1" Margin="0,12,0,0" Effect="drop-shadow(0 0 8 #A0000000)">
<Border HorizontalAlignment="Center" Background="{DynamicResource Brush.Window}">
<Border BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}" Margin="8">
<v:ImageContainer>
<v:ImagesSwipeControl OldImage="{Binding Old}"
NewImage="{Binding New}"
RenderOptions.BitmapInterpolationMode="HighQuality"/>
</v:ImageContainer>
</Border>
</Border>
</Border>
</Grid>
</TabItem>
<TabItem Header="BLEND">
<Grid RowDefinitions="Auto,*,Auto" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="8,16,8,0">
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto" HorizontalAlignment="Center">
<Border Grid.Column="0" Height="16" Background="{DynamicResource Brush.Badge}" CornerRadius="8" VerticalAlignment="Center">
<TextBlock Classes="monospace" Text="{DynamicResource Text.Diff.Binary.Old}" Margin="8,0" FontSize="10"/>
</Border>
<TextBlock Grid.Column="1" Classes="monospace" Text="{Binding OldImageSize}" Margin="8,0,0,0"/>
<TextBlock Grid.Column="2" Classes="monospace" Text="{Binding OldFileSize}" Foreground="{DynamicResource Brush.FG2}" Margin="16,0,0,0" HorizontalAlignment="Right"/>
<TextBlock Grid.Column="3" Classes="monospace" Text="{DynamicResource Text.Bytes}" Foreground="{DynamicResource Brush.FG2}" Margin="2,0,0,0"/>
<Border Grid.Column="4" Height="16" Background="Green" CornerRadius="8" VerticalAlignment="Center" Margin="32,0,0,0">
<TextBlock Classes="monospace" Text="{DynamicResource Text.Diff.Binary.New}" Margin="8,0" FontSize="10"/>
</Border>
<TextBlock Grid.Column="5" Classes="monospace" Text="{Binding NewImageSize}" Margin="8,0,0,0"/>
<TextBlock Grid.Column="6" Classes="monospace" Text="{Binding NewFileSize}" Foreground="{DynamicResource Brush.FG2}" Margin="16,0,0,0" HorizontalAlignment="Right"/>
<TextBlock Grid.Column="7" Classes="monospace" Text="{DynamicResource Text.Bytes}" Foreground="{DynamicResource Brush.FG2}" Margin="2,0,0,0"/>
</Grid>
<Border Grid.Row="1" Margin="0,12,0,0" Effect="drop-shadow(0 0 8 #A0000000)">
<Border HorizontalAlignment="Center" Background="{DynamicResource Brush.Window}">
<Border BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}" Margin="8">
<v:ImageContainer>
<v:ImageBlendControl Alpha="{Binding #ImageBlendSlider.Value}"
OldImage="{Binding Old}"
NewImage="{Binding New}"
RenderOptions.BitmapInterpolationMode="HighQuality"/>
</v:ImageContainer>
</Border>
</Border>
</Border>
<Grid Grid.Row="2" ColumnDefinitions="100,200,100" Margin="0,12,0,0" HorizontalAlignment="Center">
<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,0,8,0">
<TextBlock Classes="monospace" Text="{DynamicResource Text.Diff.Binary.Old}"/>
<TextBlock Classes="monospace"
Margin="8,0,0,0"
Text="{Binding #ImageBlendSlider.Value, Converter={x:Static c:DoubleConverters.ToPercentage}}"
Foreground="{DynamicResource Brush.FG2}"/>
</StackPanel>
<Slider Grid.Column="1"
x:Name="ImageBlendSlider"
Minimum="0" Maximum="1"
VerticalAlignment="Top"
TickPlacement="None"
Margin="0"
MinHeight="0"
Foreground="{DynamicResource Brush.Border1}"
Value="0.5">
<Slider.Resources>
<Thickness x:Key="SliderTopHeaderMargin">0,0,0,4</Thickness>
<GridLength x:Key="SliderPreContentMargin">0</GridLength>
<GridLength x:Key="SliderPostContentMargin">0</GridLength>
<CornerRadius x:Key="SliderThumbCornerRadius">8</CornerRadius>
<x:Double x:Key="SliderHorizontalThumbWidth">16</x:Double>
<x:Double x:Key="SliderHorizontalThumbHeight">16</x:Double>
</Slider.Resources>
</Slider>
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Top" Margin="8,0,0,0">
<TextBlock Classes="monospace" Text="{DynamicResource Text.Diff.Binary.New}"/>
<TextBlock Classes="monospace"
Margin="8,0,0,0"
Text="{Binding #ImageBlendSlider.Value, Converter={x:Static c:DoubleConverters.OneMinusToPercentage}}"
Foreground="{DynamicResource Brush.FG2}"/>
</StackPanel>
</Grid>
</Grid>
</TabItem>
</TabControl>
</UserControl>

View file

@ -0,0 +1,294 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Styling;
namespace SourceGit.Views
{
public class ImageContainer : ContentControl
{
protected override Type StyleKeyOverride => typeof(ContentControl);
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));
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property.Name == "ActualThemeVariant")
{
_bgBrush = null;
InvalidateVisual();
}
}
private DrawingBrush _bgBrush = null;
}
public class ImagesSwipeControl : Control
{
public static readonly StyledProperty<double> AlphaProperty =
AvaloniaProperty.Register<ImagesSwipeControl, double>(nameof(Alpha), 0.5);
public double Alpha
{
get => GetValue(AlphaProperty);
set => SetValue(AlphaProperty, value);
}
public static readonly StyledProperty<Bitmap> OldImageProperty =
AvaloniaProperty.Register<ImagesSwipeControl, Bitmap>(nameof(OldImage), null);
public Bitmap OldImage
{
get => GetValue(OldImageProperty);
set => SetValue(OldImageProperty, value);
}
public static readonly StyledProperty<Bitmap> NewImageProperty =
AvaloniaProperty.Register<ImagesSwipeControl, Bitmap>(nameof(NewImage), null);
public Bitmap NewImage
{
get => GetValue(NewImageProperty);
set => SetValue(NewImageProperty, value);
}
static ImagesSwipeControl()
{
AffectsMeasure<ImagesSwipeControl>(OldImageProperty, NewImageProperty);
AffectsRender<ImagesSwipeControl>(AlphaProperty);
}
public override void Render(DrawingContext context)
{
var alpha = Alpha;
var w = Bounds.Width;
var h = Bounds.Height;
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(0, 0, 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, 0, w - x, h);
context.DrawImage(right, src, dst);
}
context.DrawLine(new Pen(Brushes.DarkGreen, 2), new Point(x, 0), new Point(x, Bounds.Height));
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);
var p = e.GetPosition(this);
var hitbox = new Rect(Math.Max(Bounds.Width * Alpha - 2, 0), 0, 4, Bounds.Height);
var pointer = e.GetCurrentPoint(this);
if (pointer.Properties.IsLeftButtonPressed && hitbox.Contains(p))
{
_pressedOnSlider = true;
Cursor = new Cursor(StandardCursorType.SizeWestEast);
e.Pointer.Capture(this);
e.Handled = true;
}
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
_pressedOnSlider = false;
}
protected override void OnPointerMoved(PointerEventArgs e)
{
var w = Bounds.Width;
var p = e.GetPosition(this);
if (_pressedOnSlider)
{
SetCurrentValue(AlphaProperty, Math.Clamp(p.X, 0, w) / w);
}
else
{
var hitbox = new Rect(Math.Max(w * Alpha - 2, 0), 0, 4, Bounds.Height);
if (hitbox.Contains(p))
{
if (!_lastInSlider)
{
_lastInSlider = true;
Cursor = new Cursor(StandardCursorType.SizeWestEast);
}
}
else
{
if (_lastInSlider)
{
_lastInSlider = false;
Cursor = null;
}
}
}
}
protected override Size MeasureOverride(Size availableSize)
{
var left = OldImage;
var right = NewImage;
if (left == null)
return right == null ? availableSize : GetDesiredSize(right.Size, availableSize);
if (right == null)
return GetDesiredSize(left.Size, availableSize);
var ls = GetDesiredSize(left.Size, availableSize);
var rs = GetDesiredSize(right.Size, availableSize);
return ls.Width > rs.Width ? ls : rs;
}
private Size GetDesiredSize(Size img, Size available)
{
var w = available.Width;
var h = available.Height;
var sw = available.Width / img.Width;
var sh = available.Height / img.Height;
var scale = Math.Min(sw, sh);
return new Size(scale * img.Width, scale * img.Height);
}
private bool _pressedOnSlider = false;
private bool _lastInSlider = false;
}
public class ImageBlendControl : Control
{
public static readonly StyledProperty<double> AlphaProperty =
AvaloniaProperty.Register<ImageBlendControl, double>(nameof(Alpha), 1.0);
public double Alpha
{
get => GetValue(AlphaProperty);
set => SetValue(AlphaProperty, value);
}
public static readonly StyledProperty<Bitmap> OldImageProperty =
AvaloniaProperty.Register<ImageBlendControl, Bitmap>(nameof(OldImage), null);
public Bitmap OldImage
{
get => GetValue(OldImageProperty);
set => SetValue(OldImageProperty, value);
}
public static readonly StyledProperty<Bitmap> NewImageProperty =
AvaloniaProperty.Register<ImageBlendControl, Bitmap>(nameof(NewImage), null);
public Bitmap NewImage
{
get => GetValue(NewImageProperty);
set => SetValue(NewImageProperty, value);
}
static ImageBlendControl()
{
AffectsMeasure<ImageBlendControl>(OldImageProperty, NewImageProperty);
AffectsRender<ImageBlendControl>(AlphaProperty);
}
public override void Render(DrawingContext context)
{
var rect = new Rect(0, 0, Bounds.Width, Bounds.Height);
var alpha = Alpha;
var left = OldImage;
if (left != null && alpha < 1)
{
var state = context.PushOpacity(1- alpha);
context.DrawImage(left, rect);
state.Dispose();
}
var right = NewImage;
if (right != null && alpha > 0)
{
var state = context.PushOpacity(alpha);
context.DrawImage(right, rect);
state.Dispose();
}
}
protected override Size MeasureOverride(Size availableSize)
{
var left = OldImage;
var right = NewImage;
if (left == null)
return right == null ? availableSize : GetDesiredSize(right.Size, availableSize);
if (right == null)
return GetDesiredSize(left.Size, availableSize);
var ls = GetDesiredSize(left.Size, availableSize);
var rs = GetDesiredSize(right.Size, availableSize);
return ls.Width > rs.Width ? ls : rs;
}
private Size GetDesiredSize(Size img, Size available)
{
var w = available.Width;
var h = available.Height;
var sw = available.Width / img.Width;
var sh = available.Height / img.Height;
var scale = Math.Min(sw, sh);
return new Size(scale * img.Width, scale * img.Height);
}
}
public partial class ImageDiffView : UserControl
{
public ImageDiffView()
{
InitializeComponent();
}
}
}

View file

@ -18,7 +18,7 @@
<TextBlock x:Name="txtDesc" <TextBlock x:Name="txtDesc"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
TextWrapping="Wrap" TextWrapping="Wrap"
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Decrease}}" FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}"
FontStyle="Italic" FontStyle="Italic"
Text="{Binding #me.Description}"/> Text="{Binding #me.Description}"/>

View file

@ -117,7 +117,7 @@
Classes="italic" Classes="italic"
Margin="0,0,8,0" Margin="0,0,8,0"
HorizontalAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" VerticalAlignment="Center"
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Decrease}}" FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}"
Text="{DynamicResource Text.Welcome.DragDropTip}" Text="{DynamicResource Text.Welcome.DragDropTip}"
Foreground="{DynamicResource Brush.FG2}"/> Foreground="{DynamicResource Brush.FG2}"/>
</Grid> </Grid>