feat: initialize image viewer, add demo.
This commit is contained in:
BIN
demo/Ursa.Demo/Assets/WORLD.png
Normal file
BIN
demo/Ursa.Demo/Assets/WORLD.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
@@ -7,6 +7,7 @@ public static class MenuKeys
|
||||
public const string MenuKeyButtonGroup = "ButtonGroup";
|
||||
public const string MenuKeyDivider = "Divider";
|
||||
public const string MenuKeyDualBadge = "DualBadge";
|
||||
public const string MenuKeyImageViewer = "ImageViewer";
|
||||
public const string MenuKeyIpBox = "IPv4Box";
|
||||
public const string MenuKeyLoading = "Loading";
|
||||
public const string MenuKeyNavigation = "Navigation";
|
||||
|
||||
29
demo/Ursa.Demo/Pages/ImageViewerDemo.axaml
Normal file
29
demo/Ursa.Demo/Pages/ImageViewerDemo.axaml
Normal file
@@ -0,0 +1,29 @@
|
||||
<UserControl
|
||||
x:Class="Ursa.Demo.Pages.ImageViewerDemo"
|
||||
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:u="https://irihi.tech/ursa"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
<StackPanel>
|
||||
<u:ImageViewer
|
||||
Name="imageViewer"
|
||||
Width="600"
|
||||
Height="300"
|
||||
Source="../Assets/WORLD.png">
|
||||
<u:ImageViewer.Overlayer>
|
||||
<Rectangle
|
||||
Width="100"
|
||||
Height="100"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Fill="Red"
|
||||
IsHitTestVisible="False"
|
||||
Opacity="0.2" />
|
||||
</u:ImageViewer.Overlayer>
|
||||
</u:ImageViewer>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
14
demo/Ursa.Demo/Pages/ImageViewerDemo.axaml.cs
Normal file
14
demo/Ursa.Demo/Pages/ImageViewerDemo.axaml.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Ursa.Demo.Pages;
|
||||
|
||||
public partial class ImageViewerDemo : UserControl
|
||||
{
|
||||
public ImageViewerDemo()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -7,23 +7,23 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**"/>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<TrimmerRootDescriptor Include="Roots.xml"/>
|
||||
<TrimmerRootDescriptor Include="Roots.xml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)"/>
|
||||
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" />
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)"/>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0"/>
|
||||
<PackageReference Include="Semi.Avalonia" Version="11.0.0"/>
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0" />
|
||||
<PackageReference Include="Semi.Avalonia" Version="11.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Ursa.Themes.Semi\Ursa.Themes.Semi.csproj"/>
|
||||
<ProjectReference Include="..\..\src\Ursa\Ursa.csproj"/>
|
||||
<ProjectReference Include="..\..\src\Ursa.Themes.Semi\Ursa.Themes.Semi.csproj" />
|
||||
<ProjectReference Include="..\..\src\Ursa\Ursa.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
8
demo/Ursa.Demo/ViewModels/ImageViewerDemoViewModel.cs
Normal file
8
demo/Ursa.Demo/ViewModels/ImageViewerDemoViewModel.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Ursa.Demo.ViewModels;
|
||||
|
||||
public class ImageViewerDemoViewModel: ObservableObject
|
||||
{
|
||||
|
||||
}
|
||||
@@ -29,6 +29,7 @@ public class MainViewViewModel : ViewModelBase
|
||||
MenuKeys.MenuKeyButtonGroup => new ButtonGroupDemoViewModel(),
|
||||
MenuKeys.MenuKeyDivider => new DividerDemoViewModel(),
|
||||
MenuKeys.MenuKeyDualBadge => new DualBadgeDemoViewModel(),
|
||||
MenuKeys.MenuKeyImageViewer => new ImageViewerDemoViewModel(),
|
||||
MenuKeys.MenuKeyIpBox => new IPv4BoxDemoViewModel(),
|
||||
MenuKeys.MenuKeyLoading => new LoadingDemoViewModel(),
|
||||
MenuKeys.MenuKeyNavigation => new NavigationMenuDemoViewModel(),
|
||||
|
||||
@@ -16,6 +16,7 @@ public class MenuViewModel: ViewModelBase
|
||||
new() { MenuHeader = "ButtonGroup", Key = MenuKeys.MenuKeyButtonGroup },
|
||||
new() { MenuHeader = "Divider", Key = MenuKeys.MenuKeyDivider },
|
||||
new() { MenuHeader = "DualBadge", Key = MenuKeys.MenuKeyDualBadge },
|
||||
new() { MenuHeader = "ImageViewer", Key = MenuKeys.MenuKeyImageViewer },
|
||||
new() { MenuHeader = "IPv4Box", Key = MenuKeys.MenuKeyIpBox },
|
||||
new() { MenuHeader = "Loading", Key = MenuKeys.MenuKeyLoading },
|
||||
new() { MenuHeader = "Navigation", Key = MenuKeys.MenuKeyNavigation },
|
||||
|
||||
26
src/Ursa.Themes.Semi/Controls/ImageViewer.axaml
Normal file
26
src/Ursa.Themes.Semi/Controls/ImageViewer.axaml
Normal file
@@ -0,0 +1,26 @@
|
||||
<ResourceDictionary
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="https://irihi.tech/ursa">
|
||||
<!-- Add Resources Here -->
|
||||
<ControlTheme x:Key="{x:Type u:ImageViewer}" TargetType="u:ImageViewer">
|
||||
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate TargetType="{x:Type u:ImageViewer}">
|
||||
<VisualLayerManager Name="{x:Static u:ImageViewer.PART_Layer}">
|
||||
<Border Background="Transparent" ClipToBounds="True">
|
||||
<Image Name="{x:Static u:ImageViewer.PART_Image}" Source="{TemplateBinding Source}">
|
||||
<Image.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform ScaleX="{Binding Scale, RelativeSource={RelativeSource TemplatedParent}}" ScaleY="{Binding Scale, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
<TranslateTransform X="{Binding TranslateX, RelativeSource={RelativeSource TemplatedParent}}" Y="{Binding TranslateY, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
<RotateTransform Angle="0" />
|
||||
</TransformGroup>
|
||||
</Image.RenderTransform>
|
||||
</Image>
|
||||
</Border>
|
||||
</VisualLayerManager>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
</ResourceDictionary>
|
||||
@@ -6,6 +6,7 @@
|
||||
<ResourceInclude Source="ButtonGroup.axaml" />
|
||||
<ResourceInclude Source="Divider.axaml" />
|
||||
<ResourceInclude Source="DualBadge.axaml" />
|
||||
<ResourceInclude Source="ImageViewer.axaml" />
|
||||
<ResourceInclude Source="IPv4Box.axaml" />
|
||||
<ResourceInclude Source="Loading.axaml" />
|
||||
<ResourceInclude Source="Navigation.axaml" />
|
||||
|
||||
153
src/Ursa/Controls/ImageViewer/ImageViewer.cs
Normal file
153
src/Ursa/Controls/ImageViewer/ImageViewer.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
[TemplatePart(PART_Image, typeof(Image))]
|
||||
[TemplatePart(PART_Layer, typeof(VisualLayerManager))]
|
||||
public class ImageViewer: TemplatedControl
|
||||
{
|
||||
public const string PART_Image = "PART_Image";
|
||||
public const string PART_Layer = "PART_Layer";
|
||||
|
||||
private Image _image = null!;
|
||||
private VisualLayerManager? _layer;
|
||||
private Point? _lastClickPoint;
|
||||
private Point? _lastReleasePoint;
|
||||
|
||||
public static readonly StyledProperty<object?> OverlayerProperty = AvaloniaProperty.Register<ImageViewer, object?>(
|
||||
nameof(Overlayer));
|
||||
|
||||
public object? Overlayer
|
||||
{
|
||||
get => GetValue(OverlayerProperty);
|
||||
set => SetValue(OverlayerProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IImage?> SourceProperty = Image.SourceProperty.AddOwner<ImageViewer>();
|
||||
public IImage? Source
|
||||
{
|
||||
get => GetValue(SourceProperty);
|
||||
set => SetValue(SourceProperty, value);
|
||||
}
|
||||
|
||||
private double _scale = 1;
|
||||
|
||||
public static readonly DirectProperty<ImageViewer, double> ScaleProperty = AvaloniaProperty.RegisterDirect<ImageViewer, double>(
|
||||
nameof(Scale), o => o.Scale, unsetValue: 1);
|
||||
|
||||
public double Scale
|
||||
{
|
||||
get => _scale;
|
||||
private set => SetAndRaise(ScaleProperty, ref _scale, value);
|
||||
}
|
||||
|
||||
private double _translateX;
|
||||
|
||||
public static readonly DirectProperty<ImageViewer, double> TranslateXProperty = AvaloniaProperty.RegisterDirect<ImageViewer, double>(
|
||||
nameof(TranslateX), o => o.TranslateX, unsetValue: 0);
|
||||
|
||||
public double TranslateX
|
||||
{
|
||||
get => _translateX;
|
||||
private set => SetAndRaise(TranslateXProperty, ref _translateX, value);
|
||||
}
|
||||
|
||||
private double _translateY;
|
||||
|
||||
public static readonly DirectProperty<ImageViewer, double> TranslateYProperty = AvaloniaProperty.RegisterDirect<ImageViewer, double>(
|
||||
nameof(TranslateY), o => o.TranslateY);
|
||||
|
||||
public double TranslateY
|
||||
{
|
||||
get => _translateY;
|
||||
private set => SetAndRaise(TranslateYProperty, ref _translateY, value);
|
||||
}
|
||||
|
||||
|
||||
static ImageViewer()
|
||||
{
|
||||
OverlayerProperty.Changed.AddClassHandler<ImageViewer>((o, e) => o.OnOverlayerChanged(e));
|
||||
SourceProperty.Changed.AddClassHandler<ImageViewer>((o, e) => o.OnSourceChanged(e));
|
||||
}
|
||||
|
||||
private void OnOverlayerChanged(AvaloniaPropertyChangedEventArgs args)
|
||||
{
|
||||
var control = args.GetNewValue<object?>();
|
||||
if (control is Control c)
|
||||
{
|
||||
AdornerLayer.SetAdorner(this, c);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSourceChanged(AvaloniaPropertyChangedEventArgs args)
|
||||
{
|
||||
IImage image = args.GetNewValue<IImage>();
|
||||
Size size = image.Size;
|
||||
double width = this.Width;
|
||||
double height = this.Height;
|
||||
Scale = Math.Min(width/size.Width, height/size.Height);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
_image = e.NameScope.Get<Image>(PART_Image);
|
||||
_layer = e.NameScope.Get<VisualLayerManager>(PART_Layer);
|
||||
Scale = 1;
|
||||
if (Overlayer is Control c)
|
||||
{
|
||||
AdornerLayer.SetAdorner(this, c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
|
||||
{
|
||||
base.OnPointerWheelChanged(e);
|
||||
if(e.Delta.Y > 0)
|
||||
{
|
||||
Scale *= 1.1;
|
||||
}
|
||||
else
|
||||
{
|
||||
var scale = Scale;
|
||||
scale /= 1.1;
|
||||
if (scale < 0.1) scale = 0.1;
|
||||
Scale = scale;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPointerMoved(PointerEventArgs e)
|
||||
{
|
||||
base.OnPointerMoved(e);
|
||||
if (e.Pointer.Captured == this && _lastClickPoint != null)
|
||||
{
|
||||
Point p = e.GetPosition(this);
|
||||
double deltaX = p.X - _lastClickPoint.Value.X;
|
||||
double deltaY = p.Y - _lastClickPoint.Value.Y;
|
||||
TranslateX = deltaX + (_lastReleasePoint?.X ?? 0);
|
||||
TranslateY = deltaY + (_lastReleasePoint?.Y ?? 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||
{
|
||||
base.OnPointerPressed(e);
|
||||
e.Pointer.Capture(this);
|
||||
_lastClickPoint = e.GetPosition(this);
|
||||
}
|
||||
|
||||
protected override void OnPointerReleased(PointerReleasedEventArgs e)
|
||||
{
|
||||
base.OnPointerReleased(e);
|
||||
e.Pointer.Capture(null);
|
||||
_lastReleasePoint = new Point(TranslateX, TranslateY);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user