diff --git a/demo/Ursa.Demo/Assets/WORLD.png b/demo/Ursa.Demo/Assets/WORLD.png
new file mode 100644
index 0000000..7c5b71f
Binary files /dev/null and b/demo/Ursa.Demo/Assets/WORLD.png differ
diff --git a/demo/Ursa.Demo/Models/MenuKeys.cs b/demo/Ursa.Demo/Models/MenuKeys.cs
index 1d8954e..9db0558 100644
--- a/demo/Ursa.Demo/Models/MenuKeys.cs
+++ b/demo/Ursa.Demo/Models/MenuKeys.cs
@@ -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";
diff --git a/demo/Ursa.Demo/Pages/ImageViewerDemo.axaml b/demo/Ursa.Demo/Pages/ImageViewerDemo.axaml
new file mode 100644
index 0000000..67d03a8
--- /dev/null
+++ b/demo/Ursa.Demo/Pages/ImageViewerDemo.axaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
diff --git a/demo/Ursa.Demo/Pages/ImageViewerDemo.axaml.cs b/demo/Ursa.Demo/Pages/ImageViewerDemo.axaml.cs
new file mode 100644
index 0000000..7aa908c
--- /dev/null
+++ b/demo/Ursa.Demo/Pages/ImageViewerDemo.axaml.cs
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/demo/Ursa.Demo/Ursa.Demo.csproj b/demo/Ursa.Demo/Ursa.Demo.csproj
index 4f0188d..d11b6e7 100644
--- a/demo/Ursa.Demo/Ursa.Demo.csproj
+++ b/demo/Ursa.Demo/Ursa.Demo.csproj
@@ -7,23 +7,23 @@
-
+
-
+
-
+
-
-
-
+
+
+
-
-
+
+
diff --git a/demo/Ursa.Demo/ViewModels/ImageViewerDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/ImageViewerDemoViewModel.cs
new file mode 100644
index 0000000..9faefee
--- /dev/null
+++ b/demo/Ursa.Demo/ViewModels/ImageViewerDemoViewModel.cs
@@ -0,0 +1,8 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace Ursa.Demo.ViewModels;
+
+public class ImageViewerDemoViewModel: ObservableObject
+{
+
+}
\ No newline at end of file
diff --git a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
index 54109ae..56b418f 100644
--- a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
+++ b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
@@ -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(),
diff --git a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
index 011375b..de33560 100644
--- a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
+++ b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
@@ -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 },
diff --git a/src/Ursa.Themes.Semi/Controls/ImageViewer.axaml b/src/Ursa.Themes.Semi/Controls/ImageViewer.axaml
new file mode 100644
index 0000000..3a8b234
--- /dev/null
+++ b/src/Ursa.Themes.Semi/Controls/ImageViewer.axaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml
index dee9b3c..7103216 100644
--- a/src/Ursa.Themes.Semi/Controls/_index.axaml
+++ b/src/Ursa.Themes.Semi/Controls/_index.axaml
@@ -6,6 +6,7 @@
+
diff --git a/src/Ursa/Controls/ImageViewer/ImageViewer.cs b/src/Ursa/Controls/ImageViewer/ImageViewer.cs
new file mode 100644
index 0000000..ca77270
--- /dev/null
+++ b/src/Ursa/Controls/ImageViewer/ImageViewer.cs
@@ -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