Browse Source

Zoombox conversion wip

Krzysztof Krysiński 2 years ago
parent
commit
82b567b4d2
23 changed files with 1296 additions and 118 deletions
  1. 1 1
      src/PixiEditor.Avalonia/Directory.Build.props
  2. 6 6
      src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/Models/Controllers/InputDevice/MouseUpdateController.cs
  3. 1 0
      src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/PixiEditor.AvaloniaUI.csproj
  4. 36 0
      src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/ViewModels/MainVM.cs
  5. 29 0
      src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/ViewModels/MainVmEnum.cs
  6. 15 0
      src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/ViewModels/ToolVM.cs
  7. 421 0
      src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/Views/Main/Viewport.axaml
  8. 458 0
      src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/Views/Main/Viewport.axaml.cs
  9. 103 6
      src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/Views/MainView.axaml
  10. 57 0
      src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/Views/Overlays/TogglableFlyout.axaml
  11. 34 0
      src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/Views/Overlays/TogglableFlyout.axaml.cs
  12. 1 1
      src/PixiEditor.Extensions/PixiEditor.Extensions.csproj
  13. 1 1
      src/PixiEditor.UI.Common/PixiEditor.UI.Common.csproj
  14. 0 10
      src/PixiEditor.Zoombox/AssemblyInfo.cs
  15. 3 2
      src/PixiEditor.Zoombox/Operations/IDragOperation.cs
  16. 3 2
      src/PixiEditor.Zoombox/Operations/ManipulationOperation.cs
  17. 8 4
      src/PixiEditor.Zoombox/Operations/MoveDragOperation.cs
  18. 10 5
      src/PixiEditor.Zoombox/Operations/RotateDragOperation.cs
  19. 9 4
      src/PixiEditor.Zoombox/Operations/ZoomDragOperation.cs
  20. 6 3
      src/PixiEditor.Zoombox/PixiEditor.Zoombox.csproj
  21. 1 0
      src/PixiEditor.Zoombox/ViewportRoutedEventArgs.cs
  22. 6 10
      src/PixiEditor.Zoombox/Zoombox.axaml
  23. 87 63
      src/PixiEditor.Zoombox/Zoombox.axaml.cs

+ 1 - 1
src/PixiEditor.Avalonia/Directory.Build.props

@@ -1,6 +1,6 @@
 <Project>
 <Project>
   <PropertyGroup>
   <PropertyGroup>
     <Nullable>enable</Nullable>
     <Nullable>enable</Nullable>
-    <AvaloniaVersion>11.0.0</AvaloniaVersion>
+    <AvaloniaVersion>11.0.3</AvaloniaVersion>
   </PropertyGroup>
   </PropertyGroup>
 </Project>
 </Project>

+ 6 - 6
src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/Models/Controllers/InputDevice/MouseUpdateController.cs

@@ -11,9 +11,9 @@ public class MouseUpdateController : IDisposable
     
     
     private InputElement element;
     private InputElement element;
     
     
-    private Action mouseMoveHandler;
+    private Action<PointerEventArgs> mouseMoveHandler;
     
     
-    public MouseUpdateController(InputElement uiElement, Action onMouseMove)
+    public MouseUpdateController(InputElement uiElement, Action<PointerEventArgs> onMouseMove)
     {
     {
         mouseMoveHandler = onMouseMove;
         mouseMoveHandler = onMouseMove;
         element = uiElement;
         element = uiElement;
@@ -22,20 +22,20 @@ public class MouseUpdateController : IDisposable
         _timer.AutoReset = true;
         _timer.AutoReset = true;
         _timer.Elapsed += TimerOnElapsed;
         _timer.Elapsed += TimerOnElapsed;
         
         
-        element.AddHandler(InputElement.PointerMovedEvent, OnMouseMove);
+        element.PointerMoved += OnMouseMove;
     }
     }
 
 
     private void TimerOnElapsed(object sender, ElapsedEventArgs e)
     private void TimerOnElapsed(object sender, ElapsedEventArgs e)
     {
     {
         _timer.Stop();
         _timer.Stop();
-        element.AddHandler(InputElement.PointerMovedEvent, OnMouseMove);
+        element.PointerMoved += OnMouseMove;
     }
     }
 
 
     private void OnMouseMove(object sender, PointerEventArgs e)
     private void OnMouseMove(object sender, PointerEventArgs e)
     {
     {
-        element.RemoveHandler(InputElement.PointerMovedEvent, OnMouseMove);
+        element.PointerMoved -= OnMouseMove;
         _timer.Start();
         _timer.Start();
-        mouseMoveHandler();
+        mouseMoveHandler(e);
     }
     }
 
 
     public void Dispose()
     public void Dispose()

+ 1 - 0
src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/PixiEditor.AvaloniaUI.csproj

@@ -53,6 +53,7 @@
       <ProjectReference Include="..\..\PixiEditor.UI.Common\PixiEditor.UI.Common.csproj" />
       <ProjectReference Include="..\..\PixiEditor.UI.Common\PixiEditor.UI.Common.csproj" />
       <ProjectReference Include="..\..\PixiEditor.UpdateModule\PixiEditor.UpdateModule.csproj" />
       <ProjectReference Include="..\..\PixiEditor.UpdateModule\PixiEditor.UpdateModule.csproj" />
       <ProjectReference Include="..\..\PixiEditor.Windows\PixiEditor.Windows.csproj" />
       <ProjectReference Include="..\..\PixiEditor.Windows\PixiEditor.Windows.csproj" />
+      <ProjectReference Include="..\..\PixiEditor.Zoombox\PixiEditor.Zoombox.csproj" />
       <ProjectReference Include="..\..\PixiEditorGen\PixiEditorGen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
       <ProjectReference Include="..\..\PixiEditorGen\PixiEditorGen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
     </ItemGroup>
     </ItemGroup>
   
   

+ 36 - 0
src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/ViewModels/MainVM.cs

@@ -0,0 +1,36 @@
+using System.Collections.Generic;
+using Avalonia.Markup.Xaml;
+using PixiEditor.AvaloniaUI.ViewModels;
+
+namespace PixiEditor.ViewModels;
+internal class MainVM : MarkupExtension
+{
+    private MainVmEnum? vm;
+    private static Dictionary<MainVmEnum, object> subVms = new();
+
+    public override object ProvideValue(IServiceProvider serviceProvider)
+    {
+        return vm != null ? subVms[vm.Value] : ViewModelMain.Current;
+    }
+
+    static MainVM()
+    {
+        var type = typeof(ViewModelMain);
+        var vm = ViewModelMain.Current;
+        
+        foreach (var value in Enum.GetValues<MainVmEnum>())
+        {
+            subVms.Add(value, type.GetProperty(value.ToString().Replace("SVM", "SubViewModel").Replace("VM", "ViewModel"))?.GetValue(vm));
+        }
+    }
+    
+    public MainVM()
+    {
+        vm = null;
+    }
+
+    public MainVM(MainVmEnum vm)
+    {
+        this.vm = vm;
+    }
+}

+ 29 - 0
src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/ViewModels/MainVmEnum.cs

@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditor.ViewModels;
+
+enum MainVmEnum
+{
+    FileSVM,
+    UpdateSVM,
+    ToolsSVM,
+    IoSVM,
+    LayersSVM,
+    ClipboardSVM,
+    UndoSVM,
+    SelectionSVM,
+    ViewportSVM,
+    ColorsSVM,
+    MiscSVM,
+    DiscordVM,
+    DebugSVM,
+    DocumentManagerSVM,
+    StylusSVM,
+    WindowSVM,
+    SearchSVM,
+    RegistrySVM
+}

+ 15 - 0
src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/ViewModels/ToolVM.cs

@@ -0,0 +1,15 @@
+using System.Windows.Markup;
+
+namespace PixiEditor.ViewModels;
+
+internal class ToolVM : MarkupExtension
+{
+    public string TypeName { get; set; }
+
+    public ToolVM(string typeName) => TypeName = typeName;
+
+    public override object ProvideValue(IServiceProvider serviceProvider)
+    {
+        return ViewModelMain.Current?.ToolsSubViewModel.ToolSet?.Where(tool => tool.GetType().Name == TypeName).FirstOrDefault();
+    }
+}

+ 421 - 0
src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/Views/Main/Viewport.axaml

@@ -0,0 +1,421 @@
+<UserControl
+    x:Class="PixiEditor.Views.UserControls.Viewport"
+    x:ClassModifier="internal"
+    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:local="clr-namespace:PixiEditor.Views.UserControls"
+    xmlns:sys="clr-namespace:System;assembly=System.Runtime"
+    xmlns:uc="clr-namespace:PixiEditor.Views.UserControls"
+    xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
+    xmlns:views="clr-namespace:PixiEditor.Views"
+    xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+    xmlns:xaml="clr-namespace:PixiEditor.AvaloniaUI.Models.Commands.XAML"
+    xmlns:zoombox="clr-namespace:PixiEditor.Zoombox;assembly=PixiEditor.Zoombox"
+    mc:Ignorable="d"
+    x:Name="vpUc"
+    d:DesignHeight="450"
+    d:DesignWidth="800">
+    <Grid 
+        x:Name="mainGrid"
+        PointerPressed="Image_MouseDown"
+        PointerReleased="Image_MouseUp">
+        <Interaction.Behaviors>
+            <EventTriggerBehavior EventName="StylusButtonDown">
+                <InvokeCommandAction Command="{Binding StylusButtonDownCommand, ElementName=vpUc}"
+                                        PassEventArgsToCommand="True"/>
+            </EventTriggerBehavior>
+            <EventTriggerBehavior EventName="StylusButtonUp">
+                <InvokeCommandAction Command="{Binding StylusButtonUpCommand, ElementName=vpUc}"
+                                        PassEventArgsToCommand="True"/>
+            </EventTriggerBehavior>
+            <EventTriggerBehavior EventName="StylusSystemGesture">
+                <InvokeCommandAction Command="{Binding StylusGestureCommand, ElementName=vpUc}"
+                                        PassEventArgsToCommand="True"/>
+            </EventTriggerBehavior>
+            <EventTriggerBehavior EventName="StylusOutOfRange">
+                <InvokeCommandAction Command="{Binding StylusOutOfRangeCommand, ElementName=vpUc}"
+                                        PassEventArgsToCommand="True"/>
+            </EventTriggerBehavior>
+        </Interaction.Behaviors>
+        <views:TogglableFlyout Margin="5" IconPath="/Images/Settings.png" ui:Translator.TooltipKey="VIEWPORT_SETTINGS"
+                               Panel.ZIndex="2" HorizontalAlignment="Right" VerticalAlignment="Top">
+            <views:TogglableFlyout.Child>
+                <Border BorderThickness="1" CornerRadius="5" Padding="5" Background="#C8202020" Panel.ZIndex="2">
+        <StackPanel Orientation="Vertical">
+            <StackPanel Orientation="Horizontal">
+            <TextBlock Margin="5 0" TextAlignment="Center"
+                       Text="{Binding Path=Angle, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, 
+             Converter={converters:RadiansToDegreesConverter}, StringFormat={}{0}°}"
+                       Width="35" Foreground="White" VerticalAlignment="Center" FontSize="16"/>
+            <Button Width="32" Height="32" ui:Translator.TooltipKey="RESET_VIEWPORT"
+                    Style="{StaticResource OverlayButton}"
+                    Click="ResetViewportClicked"
+                    Cursor="Hand">
+            <Button.Content>
+                <Image Width="28" Height="28" Source="/Images/Layout.png"/>
+            </Button.Content>
+            </Button>
+        </StackPanel>
+            <Separator/>
+            <StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
+                <ToggleButton Width="32" Height="32" ui:Translator.TooltipKey="TOGGLE_VERTICAL_SYMMETRY"
+                        Style="{StaticResource OverlayToggleButton}"
+                        IsChecked="{Binding Document.VerticalSymmetryAxisEnabledBindable, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=TwoWay}"
+                        Cursor="Hand">
+                    <ToggleButton.Content>
+                        <Image Width="28" Height="28" Source="/Images/SymmetryVertical.png"/>
+                    </ToggleButton.Content>
+                </ToggleButton>
+                <ToggleButton Margin="10 0 0 0" Width="32" Height="32" ui:Translator.TooltipKey="TOGGLE_HORIZONTAL_SYMMETRY"
+                              Style="{StaticResource OverlayToggleButton}"
+                              IsChecked="{Binding Document.HorizontalSymmetryAxisEnabledBindable, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=TwoWay}"
+                              Cursor="Hand">
+                    <ToggleButton.Content>
+                        <Image Width="28" Height="28" Source="/Images/SymmetryVertical.png">
+                            <Image.LayoutTransform>
+                                <RotateTransform Angle="90"/>
+                            </Image.LayoutTransform>
+                        </Image>
+                    </ToggleButton.Content>
+                </ToggleButton>
+            </StackPanel>
+            <Separator/>
+            <StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
+                <ToggleButton Width="32" Height="32" ui:Translator.TooltipKey="FLIP_VIEWPORT_HORIZONTALLY"
+                              Style="{StaticResource OverlayToggleButton}"
+                              IsChecked="{Binding FlipX, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=TwoWay}"
+                              Cursor="Hand">
+                    <ToggleButton.Content>
+                        <Image Width="28" Height="28" Source="/Images/FlipHorizontal.png"/>
+                    </ToggleButton.Content>
+                </ToggleButton>
+                <ToggleButton Margin="10 0 0 0" Width="32" Height="32" ui:Translator.TooltipKey="FLIP_VIEWPORT_VERTICALLY"
+                              Style="{StaticResource OverlayToggleButton}"
+                              IsChecked="{Binding FlipY, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=TwoWay}"
+                              Cursor="Hand">
+                    <ToggleButton.Content>
+                        <Image Width="28" Height="28" Source="/Images/FlipHorizontal.png">
+                            <Image.LayoutTransform>
+                                <RotateTransform Angle="90"/>
+                            </Image.LayoutTransform>
+                        </Image>
+                    </ToggleButton.Content>
+                </ToggleButton>
+            </StackPanel>
+        </StackPanel>
+        </Border>
+            </views:TogglableFlyout.Child>
+        </views:TogglableFlyout>
+        <zoombox:Zoombox
+            Tag="{Binding ElementName=vpUc}"
+            x:Name="zoombox"
+            UseTouchGestures="{Binding UseTouchGestures, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=OneWay}"
+            Scale="{Binding ZoomboxScale, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=OneWayToSource}"
+            Center="{Binding Center, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=OneWayToSource}"
+            Angle="{Binding Angle, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=OneWayToSource}"
+            RealDimensions="{Binding RealDimensions, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=OneWayToSource}"
+            Dimensions="{Binding Dimensions, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=OneWayToSource}"
+            ZoomMode="{Binding ZoomMode, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=TwoWay}"
+            ZoomOutOnClick="{Binding ZoomOutOnClick, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=TwoWay}"
+            FlipX="{Binding FlipX, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=TwoWay}"
+            FlipY="{Binding FlipY, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=TwoWay}">
+            <Border
+                d:Width="64"
+                d:Height="64"
+                HorizontalAlignment="Center"
+                VerticalAlignment="Center"
+                DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}}"
+                RenderOptions.BitmapScalingMode="NearestNeighbor">
+                <Border.Background>
+                    <ImageBrush ImageSource="/Images/CheckerTile.png" TileMode="Tile" ViewportUnits="Absolute">
+                        <ImageBrush.Viewport>
+                            <Binding Path="Scale" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type zoombox:Zoombox}}" Converter="{converters:ZoomToViewportConverter}">
+                                <Binding.ConverterParameter>
+                                    <sys:Double>16</sys:Double>
+                                </Binding.ConverterParameter>
+                            </Binding>
+                        </ImageBrush.Viewport>
+                    </ImageBrush>
+                </Border.Background>
+                <Grid>
+                    <Canvas
+                        ZIndex="{Binding Document.ReferenceLayerViewModel.ShowHighest, Converter={converters:BoolToIntConverter}}"
+                        IsHitTestVisible="{Binding Document.ReferenceLayerViewModel.IsTransforming}">
+                        <Image
+                            Focusable="False"
+                            Width="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap.Width}"
+                            Height="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap.Height}"
+                            Source="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, Mode=OneWay}"
+                            Visibility="{Binding Document.ReferenceLayerViewModel.IsVisibleBindable, Converter={converters:BoolToHiddenVisibilityConverter}}"
+                            SizeChanged="OnReferenceImageSizeChanged"
+                            RenderOptions.BitmapScalingMode="{Binding ReferenceLayerScale, Converter={converters:ScaleToBitmapScalingModeConverter}}"
+                            FlowDirection="LeftToRight">
+                            <Image.RenderTransform>
+                                <TransformGroup>
+                                    <MatrixTransform
+                                        Matrix="{Binding Document.ReferenceLayerViewModel.ReferenceTransformMatrix}" />
+                                </TransformGroup>
+                            </Image.RenderTransform>
+                            <Image.Style>
+                                <Style>
+                                    <Style.Triggers>
+                                        <DataTrigger Binding="{Binding Document.ReferenceLayerViewModel.ShowHighest, Mode=OneWay}" Value="True">
+                                            <DataTrigger.EnterActions>
+                                                <BeginStoryboard>
+                                                    <Storyboard>
+                                                        <DoubleAnimation 
+                                                            Storyboard.TargetProperty="(Button.Opacity)" 
+                                                            From="1" To="{x:Static subviews:ReferenceLayerViewModel.TopMostOpacity}" Duration="0:0:0.1" /> 
+                                                    </Storyboard>
+                                                </BeginStoryboard>
+                                            </DataTrigger.EnterActions>
+                                            <DataTrigger.ExitActions>
+                                                <BeginStoryboard>
+                                                    <Storyboard>
+                                                        <DoubleAnimation 
+                                                            Storyboard.TargetProperty="(Button.Opacity)" 
+                                                            From="{x:Static subviews:ReferenceLayerViewModel.TopMostOpacity}" To="1" Duration="0:0:0.1" /> 
+                                                    </Storyboard>
+                                                </BeginStoryboard>
+                                            </DataTrigger.ExitActions>
+                                        </DataTrigger>
+                                    </Style.Triggers>
+                                </Style>
+                            </Image.Style>
+                        </Image>
+                        <Canvas.Style>
+                            <Style>
+                                <Style.Triggers>
+                                    <DataTrigger Binding="{Binding Source={vm:ToolVM ColorPickerToolViewModel}, Path=PickFromReferenceLayer, Mode=OneWay}" Value="False">
+                                        <DataTrigger.EnterActions>
+                                            <BeginStoryboard>
+                                                <Storyboard>
+                                                    <DoubleAnimation 
+                                                        Storyboard.TargetProperty="(Button.Opacity)" 
+                                                        From="1" To="0" Duration="0:0:0.1" /> 
+                                                </Storyboard>
+                                            </BeginStoryboard>
+                                        </DataTrigger.EnterActions>
+                                        <DataTrigger.ExitActions>
+                                            <BeginStoryboard>
+                                                <Storyboard>
+                                                    <DoubleAnimation 
+                                                        Storyboard.TargetProperty="(Button.Opacity)" 
+                                                        From="0" To="1" Duration="0:0:0.1" /> 
+                                                </Storyboard>
+                                            </BeginStoryboard>
+                                        </DataTrigger.ExitActions>
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </Canvas.Style>
+                    </Canvas>
+                    <Image
+                        Focusable="False"
+                        Width="{Binding Document.Width}"
+                        Height="{Binding Document.Height}"
+                        Source="{Binding TargetBitmap}"
+                        RenderOptions.BitmapScalingMode="{Binding Zoombox.Scale, Converter={converters:ScaleToBitmapScalingModeConverter}}"
+                        FlowDirection="LeftToRight">
+                        <Image.Style>
+                            <Style>
+                                <Style.Triggers>
+                                    <DataTrigger Binding="{Binding Source={vm:ToolVM ColorPickerToolViewModel}, Path=PickOnlyFromReferenceLayer, Mode=OneWay}" Value="True">
+                                        <DataTrigger.EnterActions>
+                                            <BeginStoryboard>
+                                                <Storyboard>
+                                                    <DoubleAnimation 
+                                                        Storyboard.TargetProperty="(Button.Opacity)" 
+                                                        From="1" To="0" Duration="0:0:0.1" /> 
+                                                </Storyboard>
+                                            </BeginStoryboard>
+                                        </DataTrigger.EnterActions>
+                                        <DataTrigger.ExitActions>
+                                            <BeginStoryboard>
+                                                <Storyboard>
+                                                    <DoubleAnimation 
+                                                        Storyboard.TargetProperty="(Button.Opacity)" 
+                                                        From="0" To="1" Duration="0:0:0.1" /> 
+                                                </Storyboard>
+                                            </BeginStoryboard>
+                                        </DataTrigger.ExitActions>
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </Image.Style>
+                    </Image>
+                    <Grid ZIndex="5">
+                        <symOverlay:SymmetryOverlay
+                            Focusable="False"
+                            IsHitTestVisible="{Binding ZoomMode, Converter={converters:ZoomModeToHitTestVisibleConverter}}"
+                            ZoomboxScale="{Binding Zoombox.Scale}"
+                            HorizontalAxisVisible="{Binding Document.HorizontalSymmetryAxisEnabledBindable}"
+                            VerticalAxisVisible="{Binding Document.VerticalSymmetryAxisEnabledBindable}"
+                            HorizontalAxisY="{Binding Document.HorizontalSymmetryAxisYBindable, Mode=OneWay}"
+                            VerticalAxisX="{Binding Document.VerticalSymmetryAxisXBindable, Mode=OneWay}"
+                            DragCommand="{cmds:Command PixiEditor.Document.DragSymmetry, UseProvided=True}"
+                            DragEndCommand="{cmds:Command PixiEditor.Document.EndDragSymmetry, UseProvided=True}"
+                            DragStartCommand="{cmds:Command PixiEditor.Document.StartDragSymmetry, UseProvided=True}"
+                            FlowDirection="LeftToRight" />
+                        <overlays:SelectionOverlay
+                            Focusable="False"
+                            ShowFill="{Binding ToolsSubViewModel.ActiveTool, Source={vm:MainVM}, Converter={converters:IsSelectionToolConverter}}"
+                            Path="{Binding Document.SelectionPathBindable}"
+                            ZoomboxScale="{Binding Zoombox.Scale}"
+                            FlowDirection="LeftToRight" />
+                        <brushOverlay:BrushShapeOverlay
+                            Focusable="False"
+                            IsHitTestVisible="False"
+                            Visibility="{Binding Document.TransformViewModel.TransformActive, Converter={converters:InverseBoolToVisibilityConverter}}"
+                            ZoomboxScale="{Binding Zoombox.Scale}"
+                            MouseEventSource="{Binding Zoombox.Tag.BackgroundGrid, Mode=OneTime}"
+                            MouseReference="{Binding Zoombox.Tag.MainImage, Mode=OneTime}"
+                            BrushSize="{Binding ToolsSubViewModel.ActiveBasicToolbar.ToolSize, Source={vm:MainVM}}"
+                            BrushShape="{Binding ToolsSubViewModel.ActiveTool.BrushShape, Source={vm:MainVM}, FallbackValue={x:Static brushOverlay:BrushShape.Hidden}}"
+                            FlowDirection="LeftToRight"/>
+                        <transformOverlay:TransformOverlay
+                            Focusable="False"
+                            Cursor="Arrow"
+                            IsHitTestVisible="{Binding ZoomMode, Converter={converters:ZoomModeToHitTestVisibleConverter}}"
+                            HorizontalAlignment="Stretch"
+                            VerticalAlignment="Stretch"
+                            Visibility="{Binding Document.TransformViewModel.TransformActive, Converter={converters:BoolToVisibilityConverter}}"
+                            ActionCompleted="{Binding Document.TransformViewModel.ActionCompletedCommand}"
+                            Corners="{Binding Document.TransformViewModel.Corners, Mode=TwoWay}"
+                            RequestedCorners="{Binding Document.TransformViewModel.RequestedCorners, Mode=TwoWay}"
+                            CornerFreedom="{Binding Document.TransformViewModel.CornerFreedom}"
+                            SideFreedom="{Binding Document.TransformViewModel.SideFreedom}"
+                            LockRotation="{Binding Document.TransformViewModel.LockRotation}"
+                            CoverWholeScreen="{Binding Document.TransformViewModel.CoverWholeScreen}"
+                            SnapToAngles="{Binding Document.TransformViewModel.SnapToAngles}"
+                            InternalState="{Binding Document.TransformViewModel.InternalState, Mode=TwoWay}"
+                            ZoomboxScale="{Binding Zoombox.Scale}"
+                            ZoomboxAngle="{Binding Zoombox.Angle}" />
+                        <lineOverlay:LineToolOverlay
+                            Focusable="False"
+                            Visibility="{Binding Document.LineToolOverlayViewModel.IsEnabled, Converter={converters:BoolToVisibilityConverter}}"
+                            ActionCompleted="{Binding Document.LineToolOverlayViewModel.ActionCompletedCommand}"
+                            LineStart="{Binding Document.LineToolOverlayViewModel.LineStart, Mode=TwoWay}"
+                            LineEnd="{Binding Document.LineToolOverlayViewModel.LineEnd, Mode=TwoWay}"
+                            ZoomboxScale="{Binding Zoombox.Scale}"
+                            FlowDirection="LeftToRight"/>
+                    </Grid>
+                    <Grid IsHitTestVisible="False" 
+                        ShowGridLines="True" Width="{Binding Document.Width}" Height="{Binding Document.Height}" Panel.ZIndex="10" 
+                        Visibility="{Binding GridLinesVisible, Converter={converters:BoolToVisibilityConverter}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}}">
+                        <Grid.Resources>
+                            <converters:ThresholdVisibilityConverter Threshold="10" x:Key="ThresholdVisibilityConverter"/>
+                        </Grid.Resources>
+                        <Rectangle Focusable="False" Visibility="{Binding Zoombox.Scale, Converter={StaticResource ThresholdVisibilityConverter}}">
+                            <Rectangle.Fill>
+                                <VisualBrush Viewport="{Binding Document.Width, Converter={converters:IntToViewportRectConverter}, ConverterParameter=vertical}" ViewboxUnits="Absolute" TileMode="Tile" >
+                                    <VisualBrush.Visual>
+                                        <Line X1="0" Y1="0" X2="0" Y2="1" Stroke="Black" 
+                                              StrokeThickness="{Binding Zoombox.Scale, Converter={converters:ReciprocalConverter}}"/>
+                                    </VisualBrush.Visual>
+                                </VisualBrush>
+                            </Rectangle.Fill>
+                        </Rectangle>
+                        <Rectangle Focusable="False" Visibility="{Binding Zoombox.Scale, Converter={StaticResource ThresholdVisibilityConverter}}">
+                            <Rectangle.Fill>
+                                <VisualBrush Viewport="{Binding Document.Height, Converter={converters:IntToViewportRectConverter}}" ViewboxUnits="Absolute" TileMode="Tile" >
+                                    <VisualBrush.Visual>
+                                        <Line X1="0" Y1="0" X2="1" Y2="0" Stroke="Black" StrokeThickness="{Binding Zoombox.Scale, Converter={converters:ReciprocalConverter}}"/>
+                                    </VisualBrush.Visual>
+                                </VisualBrush>
+                            </Rectangle.Fill>
+                        </Rectangle>
+                        <Rectangle Focusable="False" Visibility="{Binding Zoombox.Scale, Converter={StaticResource ThresholdVisibilityConverter}}">
+                            <Rectangle.Fill>
+                                <VisualBrush Viewport="{Binding Document.Width, Converter={converters:IntToViewportRectConverter}, ConverterParameter=vertical}" ViewboxUnits="Absolute" TileMode="Tile" >
+                                    <VisualBrush.Visual>
+                                        <Line X1="0" Y1="0" X2="0" Y2="1" Stroke="White">
+                                            <Line.StrokeThickness>
+                                                <Binding Converter="{converters:ReciprocalConverter}">
+                                                    <Binding.Path>Zoombox.Scale</Binding.Path>
+                                                    <Binding.ConverterParameter>
+                                                        <sys:Double>
+                                                            1.1
+                                                        </sys:Double>
+                                                    </Binding.ConverterParameter>
+                                                </Binding>
+                                            </Line.StrokeThickness>
+                                        </Line>
+                                    </VisualBrush.Visual>
+                                </VisualBrush>
+                            </Rectangle.Fill>
+                        </Rectangle>
+                        <Rectangle Focusable="False" Visibility="{Binding Zoombox.Scale, Converter={StaticResource ThresholdVisibilityConverter}}">
+                            <Rectangle.Fill>
+                                <VisualBrush Viewport="{Binding Document.Height, Converter={converters:IntToViewportRectConverter}}" ViewboxUnits="Absolute" TileMode="Tile" >
+                                    <VisualBrush.Visual>
+                                        <Line X1="0" Y1="0" X2="1" Y2="0" Stroke="White">
+                                            <Line.StrokeThickness>
+                                                <Binding Converter="{converters:ReciprocalConverter}">
+                                                    <Binding.Path>Zoombox.Scale</Binding.Path>
+                                                    <Binding.ConverterParameter>
+                                                        <sys:Double>
+                                                            1.1
+                                                        </sys:Double>
+                                                    </Binding.ConverterParameter>
+                                                </Binding>
+                                            </Line.StrokeThickness>
+                                        </Line>
+                                    </VisualBrush.Visual>
+                                </VisualBrush>
+                            </Rectangle.Fill>
+                        </Rectangle>
+                    </Grid>
+                    <Rectangle Stroke="{StaticResource AccentColor}" Opacity=".8" Panel.ZIndex="2" Visibility="{Binding Document.ReferenceLayerViewModel.IsVisibleBindable, Converter={converters:BoolToHiddenVisibilityConverter}}">
+                        <Rectangle.StrokeThickness>
+                            <Binding Converter="{converters:ReciprocalConverter}">
+                                <Binding.Path>Zoombox.Scale</Binding.Path>
+                                <Binding.ConverterParameter>
+                                    <sys:Double>
+                                        3
+                                    </sys:Double>
+                                </Binding.ConverterParameter>
+                            </Binding>
+                        </Rectangle.StrokeThickness>
+                        <Rectangle.Margin>
+                            <Binding Converter="{converters:ReciprocalConverter}">
+                                <Binding.Path>Zoombox.Scale</Binding.Path>
+                                <Binding.ConverterParameter>
+                                    <sys:Double>
+                                        -3
+                                    </sys:Double>
+                                </Binding.ConverterParameter>
+                            </Binding>
+                        </Rectangle.Margin>
+                    </Rectangle>
+                </Grid>
+            </Border>
+        </zoombox:Zoombox>
+        <Button 
+            Panel.ZIndex="99999"
+            DockPanel.Dock="Bottom"
+            Margin="5"
+            Padding="8,5,5,5"
+            VerticalAlignment="Bottom" 
+            HorizontalAlignment="Center"
+            Style="{StaticResource GrayRoundButton}"
+            Command="{xaml:Command PixiEditor.Tools.ApplyTransform}">
+            <Button.IsVisible>
+                <MultiBinding Converter="{converters:BoolOrToVisibilityConverter}">
+                    <MultiBinding.Bindings>
+                        <Binding ElementName="vpUc" Path="Document.TransformViewModel.ShowTransformControls"/>
+                        <Binding ElementName="vpUc" Path="Document.LineToolOverlayViewModel.IsEnabled"/>
+                    </MultiBinding.Bindings>
+                </MultiBinding>
+            </Button.IsVisible>
+            <StackPanel Orientation="Horizontal">
+                <TextBlock ui:Translator.Key="APPLY_TRANSFORM" VerticalAlignment="Center" Margin="0,0,5,0" />
+                <Border Padding="10,3" CornerRadius="5" Background="{StaticResource AccentColor}" Visibility="{cmds:ShortcutBinding PixiEditor.Tools.ApplyTransform, Converter={converters:NotNullToVisibilityConverter}}">
+                    <TextBlock Text="{xaml:ShortcutBinding PixiEditor.Tools.ApplyTransform}" />
+                </Border>
+            </StackPanel>
+        </Button>
+    </Grid>
+</UserControl>

+ 458 - 0
src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/Views/Main/Viewport.axaml.cs

@@ -0,0 +1,458 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Windows.Input;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Data;
+using Avalonia.Data.Core;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Media.Imaging;
+using ChunkyImageLib.DataHolders;
+using Hardware.Info;
+using PixiEditor.AvaloniaUI.Helpers.UI;
+using PixiEditor.AvaloniaUI.Models.Controllers.InputDevice;
+using PixiEditor.AvaloniaUI.Models.Position;
+using PixiEditor.AvaloniaUI.ViewModels.Document;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.Zoombox;
+
+namespace PixiEditor.Views.UserControls;
+
+#nullable enable
+internal partial class Viewport : UserControl, INotifyPropertyChanged
+{
+    public event PropertyChangedEventHandler? PropertyChanged;
+
+    public static readonly StyledProperty<bool> FlipXProperty =
+        AvaloniaProperty.Register<Viewport, bool>(nameof(FlipX), false);
+
+    public static readonly StyledProperty<bool> FlipYProperty =
+        AvaloniaProperty.Register<Viewport, bool>(nameof(FlipY), false);
+
+    public static readonly StyledProperty<ZoomboxMode> ZoomModeProperty =
+        AvaloniaProperty.Register<Viewport, ZoomboxMode>(nameof(ZoomMode), ZoomboxMode.Normal);
+
+    public static readonly StyledProperty<DocumentViewModel> DocumentProperty =
+        AvaloniaProperty.Register<Viewport, DocumentViewModel>(nameof(Document), null);
+
+    public static readonly StyledProperty<ICommand> MouseDownCommandProperty =
+        AvaloniaProperty.Register<Viewport, ICommand>(nameof(MouseDownCommand), null);
+
+    public static readonly StyledProperty<ICommand> MouseMoveCommandProperty =
+        AvaloniaProperty.Register<Viewport, ICommand>(nameof(MouseMoveCommand), null);
+
+    public static readonly StyledProperty<ICommand> MouseUpCommandProperty =
+        AvaloniaProperty.Register<Viewport, ICommand>(nameof(MouseUpCommand), null);
+
+    private static readonly StyledProperty<Dictionary<ChunkResolution, WriteableBitmap>> BitmapsProperty =
+        AvaloniaProperty.Register<Viewport, Dictionary<ChunkResolution, WriteableBitmap>>(nameof(Bitmaps), null);
+
+    public static readonly StyledProperty<bool> DelayedProperty =
+        AvaloniaProperty.Register<Viewport, bool>(nameof(Delayed), false);
+
+    public static readonly StyledProperty<bool> GridLinesVisibleProperty =
+        AvaloniaProperty.Register<Viewport, bool>(nameof(GridLinesVisible), false);
+
+    public static readonly StyledProperty<bool> ZoomOutOnClickProperty =
+        AvaloniaProperty.Register<Viewport, bool>(nameof(ZoomOutOnClick), false);
+
+    public static readonly StyledProperty<ExecutionTrigger<double>> ZoomViewportTriggerProperty =
+        AvaloniaProperty.Register<Viewport, ExecutionTrigger<double>>(nameof(ZoomViewportTrigger), null);
+
+    public static readonly StyledProperty<ExecutionTrigger<VecI>> CenterViewportTriggerProperty =
+        AvaloniaProperty.Register<Viewport, ExecutionTrigger<VecI>>(nameof(CenterViewportTrigger), null);
+
+    public static readonly StyledProperty<bool> UseTouchGesturesProperty =
+        AvaloniaProperty.Register<Viewport, bool>(nameof(UseTouchGestures), false);
+
+    public static readonly StyledProperty<ICommand> StylusButtonDownCommandProperty =
+        AvaloniaProperty.Register<Viewport, ICommand>(nameof(StylusButtonDownCommand), null);
+
+    public static readonly StyledProperty<ICommand> StylusButtonUpCommandProperty =
+        AvaloniaProperty.Register<Viewport, ICommand>(nameof(StylusButtonUpCommand), null);
+
+    public static readonly StyledProperty<ICommand> StylusGestureCommandProperty =
+        AvaloniaProperty.Register<Viewport, ICommand>(nameof(StylusGestureCommand), null);
+
+    public static readonly StyledProperty<ICommand> StylusOutOfRangeCommandProperty =
+        AvaloniaProperty.Register<Viewport, ICommand>(nameof(StylusOutOfRangeCommand), null);
+
+    public static readonly StyledProperty<ICommand> MiddleMouseClickedCommandProperty =
+        AvaloniaProperty.Register<Viewport, ICommand>(nameof(MiddleMouseClickedCommand), null);
+
+    public ICommand? MiddleMouseClickedCommand
+    {
+        get => (ICommand?)GetValue(MiddleMouseClickedCommandProperty);
+        set => SetValue(MiddleMouseClickedCommandProperty, value);
+    }
+
+    public ICommand? StylusOutOfRangeCommand
+    {
+        get => (ICommand?)GetValue(StylusOutOfRangeCommandProperty);
+        set => SetValue(StylusOutOfRangeCommandProperty, value);
+    }
+
+    public ICommand? StylusGestureCommand
+    {
+        get => (ICommand?)GetValue(StylusGestureCommandProperty);
+        set => SetValue(StylusGestureCommandProperty, value);
+    }
+
+    public ICommand? StylusButtonUpCommand
+    {
+        get => (ICommand?)GetValue(StylusButtonUpCommandProperty);
+        set => SetValue(StylusButtonUpCommandProperty, value);
+    }
+
+    public ICommand? StylusButtonDownCommand
+    {
+        get => (ICommand?)GetValue(StylusButtonDownCommandProperty);
+        set => SetValue(StylusButtonDownCommandProperty, value);
+    }
+
+    public bool UseTouchGestures
+    {
+        get => (bool)GetValue(UseTouchGesturesProperty);
+        set => SetValue(UseTouchGesturesProperty, value);
+    }
+
+    public ExecutionTrigger<VecI>? CenterViewportTrigger
+    {
+        get => (ExecutionTrigger<VecI>)GetValue(CenterViewportTriggerProperty);
+        set => SetValue(CenterViewportTriggerProperty, value);
+    }
+
+    public ExecutionTrigger<double>? ZoomViewportTrigger
+    {
+        get => (ExecutionTrigger<double>)GetValue(ZoomViewportTriggerProperty);
+        set => SetValue(ZoomViewportTriggerProperty, value);
+    }
+
+    public bool ZoomOutOnClick
+    {
+        get => (bool)GetValue(ZoomOutOnClickProperty);
+        set => SetValue(ZoomOutOnClickProperty, value);
+    }
+
+    public bool GridLinesVisible
+    {
+        get => (bool)GetValue(GridLinesVisibleProperty);
+        set => SetValue(GridLinesVisibleProperty, value);
+    }
+
+    public bool Delayed
+    {
+        get => (bool)GetValue(DelayedProperty);
+        set => SetValue(DelayedProperty, value);
+    }
+
+    public Dictionary<ChunkResolution, WriteableBitmap>? Bitmaps
+    {
+        get => (Dictionary<ChunkResolution, WriteableBitmap>?)GetValue(BitmapsProperty);
+        set => SetValue(BitmapsProperty, value);
+    }
+
+    public ICommand? MouseDownCommand
+    {
+        get => (ICommand?)GetValue(MouseDownCommandProperty);
+        set => SetValue(MouseDownCommandProperty, value);
+    }
+
+    public ICommand? MouseMoveCommand
+    {
+        get => (ICommand?)GetValue(MouseMoveCommandProperty);
+        set => SetValue(MouseMoveCommandProperty, value);
+    }
+
+    public ICommand? MouseUpCommand
+    {
+        get => (ICommand?)GetValue(MouseUpCommandProperty);
+        set => SetValue(MouseUpCommandProperty, value);
+    }
+
+
+    public DocumentViewModel? Document
+    {
+        get => (DocumentViewModel)GetValue(DocumentProperty);
+        set => SetValue(DocumentProperty, value);
+    }
+
+    public ZoomboxMode ZoomMode
+    {
+        get => (ZoomboxMode)GetValue(ZoomModeProperty);
+        set => SetValue(ZoomModeProperty, value);
+    }
+
+    public double ZoomboxScale
+    {
+        get => zoombox.Scale;
+        // ReSharper disable once ValueParameterNotUsed
+        set
+        {
+            PropertyChanged?.Invoke(this, new(nameof(ReferenceLayerScale)));
+        }
+    }
+
+    public bool FlipX
+    {
+        get => (bool)GetValue(FlipXProperty);
+        set => SetValue(FlipXProperty, value);
+    }
+
+    public bool FlipY
+    {
+        get => (bool)GetValue(FlipYProperty);
+        set => SetValue(FlipYProperty, value);
+    }
+
+    private double angle = 0;
+
+    public double Angle
+    {
+        get => angle;
+        set
+        {
+            angle = value;
+            PropertyChanged?.Invoke(this, new(nameof(Angle)));
+            Document?.Operations.AddOrUpdateViewport(GetLocation());
+        }
+    }
+
+    private VecD center = new(32, 32);
+
+    public VecD Center
+    {
+        get => center;
+        set
+        {
+            center = value;
+            PropertyChanged?.Invoke(this, new(nameof(Center)));
+            Document?.Operations.AddOrUpdateViewport(GetLocation());
+        }
+    }
+
+    private VecD realDimensions = new(double.MaxValue, double.MaxValue);
+
+    public VecD RealDimensions
+    {
+        get => realDimensions;
+        set
+        {
+            ChunkResolution oldRes = CalculateResolution();
+            realDimensions = value;
+            ChunkResolution newRes = CalculateResolution();
+
+            PropertyChanged?.Invoke(this, new(nameof(RealDimensions)));
+            Document?.Operations.AddOrUpdateViewport(GetLocation());
+
+            if (oldRes != newRes)
+                PropertyChanged?.Invoke(this, new(nameof(TargetBitmap)));
+        }
+    }
+
+    private VecD dimensions = new(64, 64);
+
+    public VecD Dimensions
+    {
+        get => dimensions;
+        set
+        {
+            ChunkResolution oldRes = CalculateResolution();
+            dimensions = value;
+            ChunkResolution newRes = CalculateResolution();
+
+            PropertyChanged?.Invoke(this, new(nameof(Dimensions)));
+            Document?.Operations.AddOrUpdateViewport(GetLocation());
+
+            if (oldRes != newRes)
+                PropertyChanged?.Invoke(this, new(nameof(TargetBitmap)));
+        }
+    }
+
+    public WriteableBitmap? TargetBitmap
+    {
+        get
+        {
+            return Document?.LazyBitmaps.TryGetValue(CalculateResolution(), out WriteableBitmap? value) == true ? value : null;
+        }
+    }
+
+    public double ReferenceLayerScale =>
+        ZoomboxScale * ((Document?.ReferenceLayerViewModel.ReferenceBitmap != null && Document?.ReferenceLayerViewModel.ReferenceShapeBindable != null)
+            ? (Document.ReferenceLayerViewModel.ReferenceShapeBindable.RectSize.X / (double)Document.ReferenceLayerViewModel.ReferenceBitmap.PixelWidth)
+            : 1);
+
+    public PixiEditor.Zoombox.Zoombox Zoombox => zoombox;
+
+    public Guid GuidValue { get; } = Guid.NewGuid();
+
+    private MouseUpdateController mouseUpdateController;
+
+    static Viewport()
+    {
+        DocumentProperty.Changed.Subscribe(OnDocumentChange);
+        BitmapsProperty.Changed.Subscribe(OnBitmapsChange);
+        ZoomViewportTriggerProperty.Changed.Subscribe(ZoomViewportTriggerChanged);
+        CenterViewportTriggerProperty.Changed.Subscribe(CenterViewportTriggerChanged);
+    }
+
+    public Viewport()
+    {
+        InitializeComponent();
+
+        Binding binding = new Binding { Source = this, Path = new PropertyPath($"{nameof(Document)}.{nameof(Document.LazyBitmaps)}") };
+        Bind(BitmapsProperty, binding);
+
+        MainImage!.Loaded += OnImageLoaded;
+        Loaded += OnLoad;
+        Unloaded += OnUnload;
+        
+        mouseUpdateController = new MouseUpdateController(this, Image_MouseMove);
+    }
+
+    public Image? MainImage => (Image?)((Grid?)((Border?)zoombox.AdditionalContent)?.Child)?.Children[1];
+    public Grid BackgroundGrid => mainGrid;
+
+    private void ForceRefreshFinalImage()
+    {
+        MainImage?.InvalidateVisual();
+    }
+
+    private void OnUnload(object sender, RoutedEventArgs e)
+    {
+        Document?.Operations.RemoveViewport(GuidValue);
+    }
+
+    private void OnLoad(object sender, RoutedEventArgs e)
+    {
+        Document?.Operations.AddOrUpdateViewport(GetLocation());
+    }
+
+    private static void OnDocumentChange(AvaloniaPropertyChangedEventArgs<DocumentViewModel> e)
+    {
+        DocumentViewModel? oldDoc = e.OldValue.Value;
+        DocumentViewModel? newDoc = e.NewValue.Value;
+        Viewport? viewport = (Viewport)e.Sender;
+        oldDoc?.Operations.RemoveViewport(viewport.GuidValue);
+        newDoc?.Operations.AddOrUpdateViewport(viewport.GetLocation());
+    }
+
+    private static void OnBitmapsChange(AvaloniaPropertyChangedEventArgs<Dictionary<ChunkResolution, WriteableBitmap>?> e)
+    {
+        Viewport viewportObj = (Viewport)e.Sender;
+        ((Viewport)viewportObj).PropertyChanged?.Invoke(viewportObj, new(nameof(TargetBitmap)));
+    }
+
+    private ChunkResolution CalculateResolution()
+    {
+        VecD densityVec = Dimensions.Divide(RealDimensions);
+        double density = Math.Min(densityVec.X, densityVec.Y);
+        if (density > 8.01)
+            return ChunkResolution.Eighth;
+        else if (density > 4.01)
+            return ChunkResolution.Quarter;
+        else if (density > 2.01)
+            return ChunkResolution.Half;
+        return ChunkResolution.Full;
+    }
+
+    private ViewportInfo GetLocation()
+    {
+        return new(Angle, Center, RealDimensions, Dimensions, CalculateResolution(), GuidValue, Delayed, ForceRefreshFinalImage);
+    }
+
+    private void OnReferenceImageSizeChanged(object? sender, SizeChangedEventArgs e)
+    {
+        PropertyChanged?.Invoke(this, new(nameof(ReferenceLayerScale)));
+    }
+
+    private void Image_MouseDown(object sender, PointerEventArgs e)
+    {
+        bool isMiddle = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed;
+        HandleMiddleMouse(isMiddle);
+
+        if (MouseDownCommand is null)
+            return;
+
+        MouseButton mouseButton = e.GetCurrentPoint(this).Properties.PointerUpdateKind switch
+        {
+            PointerUpdateKind.LeftButtonPressed => MouseButton.Left,
+            PointerUpdateKind.RightButtonPressed => MouseButton.Right,
+            _ => MouseButton.Middle
+        };
+
+        Point pos = e.GetPosition(MainImage);
+        VecD conv = new VecD(pos.X, pos.Y);
+        MouseOnCanvasEventArgs? parameter = new MouseOnCanvasEventArgs(mouseButton, conv);
+
+        if (MouseDownCommand.CanExecute(parameter))
+            MouseDownCommand.Execute(parameter);
+    }
+
+    private void Image_MouseMove(PointerEventArgs pointerEventArgs)
+    {
+        if (MouseMoveCommand is null)
+            return;
+        Point pos = e.GetPosition(MainImage);
+        VecD conv = new VecD(pos.X, pos.Y);
+
+        if (MouseMoveCommand.CanExecute(conv))
+            MouseMoveCommand.Execute(conv);
+    }
+
+    private void Image_MouseUp(object? sender, PointerReleasedEventArgs e)
+    {
+        if (MouseUpCommand is null)
+            return;
+        if (MouseUpCommand.CanExecute(e.InitialPressMouseButton))
+            MouseUpCommand.Execute(e.InitialPressMouseButton);
+    }
+
+    private void CenterZoomboxContent(object? sender, VecI args)
+    {
+        zoombox.CenterContent(args);
+    }
+
+    private void ZoomZoomboxContent(object? sender, double delta)
+    {
+        zoombox.ZoomIntoCenter(delta);
+    }
+
+    private void OnImageLoaded(object sender, EventArgs e)
+    {
+        zoombox.CenterContent();
+    }
+    
+    private void ResetViewportClicked(object sender, RoutedEventArgs e)
+    {
+        zoombox.Angle = 0;
+        zoombox.CenterContent();
+    }
+
+    private static void CenterViewportTriggerChanged(AvaloniaPropertyChangedEventArgs<ExecutionTrigger<VecI>> e)
+    {
+        Viewport? viewport = (Viewport)e.Sender;
+        if (e.OldValue.Value != null)
+            e.OldValue.Value.Triggered -= viewport.CenterZoomboxContent;
+        if (e.NewValue.Value != null)
+            e.NewValue.Value.Triggered += viewport.CenterZoomboxContent;
+    }
+
+    private static void ZoomViewportTriggerChanged(AvaloniaPropertyChangedEventArgs<ExecutionTrigger<double>> e)
+    {
+        Viewport? viewport = (Viewport)e.Sender;
+        if (e.OldValue.Value != null)
+            e.OldValue.Value.Triggered -= viewport.ZoomZoomboxContent;
+        if (e.NewValue.Value != null)
+            e.NewValue.Value.Triggered += viewport.ZoomZoomboxContent;
+    }
+
+    private void HandleMiddleMouse(bool isMiddle)
+    {
+        if (MiddleMouseClickedCommand is null)
+            return;
+        if (isMiddle && MiddleMouseClickedCommand.CanExecute(null))
+            MiddleMouseClickedCommand.Execute(null);
+    }
+}

+ 103 - 6
src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/Views/MainView.axaml

@@ -4,6 +4,10 @@
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
              xmlns:viewModels1="clr-namespace:PixiEditor.AvaloniaUI.ViewModels"
              xmlns:viewModels1="clr-namespace:PixiEditor.AvaloniaUI.ViewModels"
              xmlns:main1="clr-namespace:PixiEditor.AvaloniaUI.Views.Main"
              xmlns:main1="clr-namespace:PixiEditor.AvaloniaUI.Views.Main"
+             xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+             xmlns:xaml="clr-namespace:PixiEditor.AvaloniaUI.Models.Commands.XAML"
+             xmlns:userControls="clr-namespace:PixiEditor.Views.UserControls"
+             xmlns:viewModels="clr-namespace:PixiEditor.ViewModels"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              x:Class="PixiEditor.AvaloniaUI.Views.MainView"
              x:Class="PixiEditor.AvaloniaUI.Views.MainView"
              x:DataType="viewModels1:ViewModelMain" Background="{DynamicResource ThemeBackgroundBrush}">
              x:DataType="viewModels1:ViewModelMain" Background="{DynamicResource ThemeBackgroundBrush}">
@@ -52,12 +56,105 @@
               <DocumentDock x:Name="DocumentsPane" Id="DocumentsPane" CanCreateDocument="True" ActiveDockable="Document1">
               <DocumentDock x:Name="DocumentsPane" Id="DocumentsPane" CanCreateDocument="True" ActiveDockable="Document1">
                 <DocumentDock.DocumentTemplate>
                 <DocumentDock.DocumentTemplate>
                   <DocumentTemplate>
                   <DocumentTemplate>
-                    <StackPanel x:DataType="Document">
-                      <TextBlock Text="Title"/>
-                      <TextBox Text="{Binding Title}"/>
-                      <TextBlock Text="Context"/>
-                      <TextBox Text="{Binding Context}"/>
-                    </StackPanel>
+                      <!--Tool cursor not present here, since Avalonia doesn't have Cursor property??-->
+                      <userControls:Viewport
+                                             CenterViewportTrigger="{Binding CenterViewportTrigger}"
+                                            ZoomViewportTrigger="{Binding ZoomViewportTrigger}"
+                                            MouseDownCommand="{Binding ElementName=mainWindow, Path=DataContext.IoSubViewModel.MouseDownCommand}"
+                                            MouseMoveCommand="{Binding ElementName=mainWindow, Path=DataContext.IoSubViewModel.MouseMoveCommand}"
+                                            MouseUpCommand="{Binding ElementName=mainWindow, Path=DataContext.IoSubViewModel.MouseUpCommand}"
+                                            MiddleMouseClickedCommand="{Binding IoSubViewModel.PreviewMouseMiddleButtonCommand, Source={viewModels:MainVM}}"
+                                            Cursor="{Binding ToolsSubViewModel.ToolCursor, Source={viewModels:MainVM}}"
+                                            GridLinesVisible="{Binding ViewportSubViewModel.GridLinesEnabled, Source={viewModels:MainVM}}"
+                                            ZoomMode="{Binding ToolsSubViewModel.ActiveTool, Source={viewModels:MainVM}, Converter={converters:ActiveToolToZoomModeConverter}}"
+                                            ZoomOutOnClick="{Binding ToolsSubViewModel.ZoomTool.ZoomOutOnClick, Source={viewModels:MainVM}}"
+                                            UseTouchGestures="{Binding StylusSubViewModel.UseTouchGestures, Source={viewModels:MainVM}}"
+                                            StylusButtonDownCommand="{xaml:Command PixiEditor.Stylus.StylusDown, UseProvided=True}"
+                                            StylusButtonUpCommand="{xaml:Command PixiEditor.Stylus.StylusUp, UseProvided=True}"
+                                            StylusGestureCommand="{xaml:Command PixiEditor.Stylus.StylusSystemGesture, UseProvided=True}"
+                                            StylusOutOfRangeCommand="{xaml:Command PixiEditor.Stylus.StylusOutOfRange, UseProvided=True}"
+                                            FlipX="{Binding FlipX, Mode=TwoWay}"
+                                            FlipY="{Binding FlipY, Mode=TwoWay}"
+
+                                            ContextMenuOpening="Viewport_OnContextMenuOpening"
+                                            Stylus.IsTapFeedbackEnabled="False"
+                                            Stylus.IsTouchFeedbackEnabled="False"
+                                            Document="{Binding Document}">
+                                            <userControls:Viewport.ContextMenu>
+                                                <ContextMenu DataContext="{Binding PlacementTarget.Document, RelativeSource={RelativeSource Self}}">
+                                                    <ContextMenu.Template>
+                                                        <ControlTemplate>
+                                                            <Border Background="{StaticResource AccentColor}" BorderBrush="Black" BorderThickness="1" CornerRadius="5">
+                                                                <Grid Height="235">
+                                                                    <Grid.ColumnDefinitions>
+                                                                        <ColumnDefinition Width="{Binding Palette, Converter={converters:PaletteItemsToWidthConverter}}"/>
+                                                                        <ColumnDefinition />
+                                                                    </Grid.ColumnDefinitions>
+                                                                    <Border Grid.Column="1" BorderThickness="0 0 1 0" BorderBrush="Black">
+                                                                        <StackPanel Orientation="Vertical" Grid.Column="0">
+                                                                            <MenuItem
+																		ui:Translator.Key="SELECT_ALL"
+																		xaml:ContextMenu.Command="PixiEditor.Selection.SelectAll" />
+                                                                            <MenuItem
+                                                                                ui:Translator.Key="DESELECT"
+                                                                                xaml:ContextMenu.Command="PixiEditor.Selection.Clear" />
+                                                                            <Separator />
+                                                                            <MenuItem
+                                                                                ui:Translator.Key="CUT"
+                                                                                xaml:ContextMenu.Command="PixiEditor.Clipboard.Cut" />
+                                                                            <MenuItem
+                                                                                ui:Translator.Key="COPY"
+                                                                                xaml:ContextMenu.Command="PixiEditor.Clipboard.Copy" />
+                                                                            <MenuItem
+                                                                                ui:Translator.Key="PASTE"
+                                                                                xaml:ContextMenu.Command="PixiEditor.Clipboard.Paste" />
+                                                                            <Separator />
+                                                                            <MenuItem ui:Translator.Key="FLIP_LAYERS_HORIZONTALLY" xaml:Menu.Command="PixiEditor.Document.FlipLayersHorizontal"/>
+                                                                            <MenuItem ui:Translator.Key="FLIP_LAYERS_VERTICALLY" xaml:Menu.Command="PixiEditor.Document.FlipLayersVertical"/>
+                                                                            <Separator />
+                                                                            <MenuItem ui:Translator.Key="ROT_LAYERS_90_D" xaml:Menu.Command="PixiEditor.Document.Rotate90DegLayers"/>
+                                                                            <MenuItem ui:Translator.Key="ROT_LAYERS_180_D" xaml:Menu.Command="PixiEditor.Document.Rotate180DegLayers"/>
+                                                                            <MenuItem ui:Translator.Key="ROT_LAYERS_-90_D" xaml:Menu.Command="PixiEditor.Document.Rotate270DegLayers"/>
+                                                                        </StackPanel>
+                                                                    </Border>
+                                                                    <ScrollViewer Margin="5" Grid.Column="0" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
+                                                                        <ItemsControl ItemsSource="{Binding Palette}">
+                                                                            <ItemsControl.ItemsPanel>
+                                                                                <ItemsPanelTemplate>
+                                                                                    <WrapPanel Orientation="Horizontal"
+                                  HorizontalAlignment="Left" VerticalAlignment="Top"/>
+                                                                                </ItemsPanelTemplate>
+                                                                            </ItemsControl.ItemsPanel>
+                                                                            <ItemsControl.ItemTemplate>
+                                                                                <DataTemplate>
+                                                                                    <palettes:PaletteColorControl Cursor="Hand" CornerRadius="0"
+                                                                                        ui:Translator.TooltipKey="CLICK_SELECT_PRIMARY"
+                                                                                        Width="22" Height="22" Color="{Binding}">
+                                                                                        <b:Interaction.Triggers>
+                                                                                            <b:EventTrigger EventName="MouseLeftButtonUp">
+                                                                                                <b:InvokeCommandAction
+                                                                                                    Command="{xaml:Command PixiEditor.Colors.SelectColor, UseProvided=True}"
+                                                                                                    CommandParameter="{Binding}" />
+                                                                                            </b:EventTrigger>
+                                                                                            <b:EventTrigger EventName="MouseLeftButtonUp">
+                                                                                                <b:InvokeCommandAction
+                                                                                                    Command="{xaml:Command PixiEditor.CloseContextMenu, UseProvided=True}"
+                                                                                                    CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,
+                                                                                                     AncestorType={x:Type ContextMenu}}}" />
+                                                                                            </b:EventTrigger>
+                                                                                        </b:Interaction.Triggers>
+                                                                                    </palettes:PaletteColorControl>
+                                                                                </DataTemplate>
+                                                                            </ItemsControl.ItemTemplate>
+                                                                        </ItemsControl>
+                                                                    </ScrollViewer>
+                                                                </Grid>
+                                                            </Border>
+                                                        </ControlTemplate>
+                                                    </ContextMenu.Template>
+                                                </ContextMenu>
+                                            </userControls:Viewport.ContextMenu>
+                                        </userControls:Viewport>
                   </DocumentTemplate>
                   </DocumentTemplate>
                 </DocumentDock.DocumentTemplate>
                 </DocumentDock.DocumentTemplate>
                 <Document x:Name="Document1" Id="Document1" Title="SomeDrawing.pixi" x:DataType="Document">
                 <Document x:Name="Document1" Id="Document1" Title="SomeDrawing.pixi" x:DataType="Document">

+ 57 - 0
src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/Views/Overlays/TogglableFlyout.axaml

@@ -0,0 +1,57 @@
+<UserControl x:Class="PixiEditor.Views.TogglableFlyout"
+             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"
+             mc:Ignorable="d"
+             d:DesignHeight="380" d:DesignWidth="200" Name="togglableFlyout">
+    <Border Background="Transparent">
+        <StackPanel Orientation="Vertical">
+            <Border HorizontalAlignment="Right" Background="#C8202020" CornerRadius="5" Padding="5" x:Name="btnBorder">
+                <ToggleButton Padding="0" Margin="0" ToolTip.Tip="{Binding ElementName=togglableFlyout, Path=ToolTip}"
+                              x:Name="toggleButton" BorderThickness="0" Width="24" Height="24" Background="Transparent">
+                    <ToggleButton.Template>
+                        <ControlTemplate TargetType="{x:Type ToggleButton}">
+                            <Grid>
+                                <Image Focusable="False" Width="24" Cursor="Hand" x:Name="btnBg" 
+                                       Source="{Binding ElementName=togglableFlyout, Path=IconPath}">
+                                    <Image.RenderTransform>
+                                        <RotateTransform Angle="0" CenterX="12" CenterY="12"/>
+                                    </Image.RenderTransform>
+                                </Image>
+                                <ContentPresenter/>
+                            </Grid>
+                            <!--<ControlTemplate.Triggers>
+                                <Trigger Property="IsChecked" Value="True">
+                                    <Trigger.EnterActions>
+                                        <BeginStoryboard x:Name="Rotate90Animation">
+                                            <Storyboard>
+                                                <DoubleAnimation From="0" To="180"
+                                                                 Storyboard.TargetName="btnBg"
+                                                                 Storyboard.TargetProperty="(ToggleButton.RenderTransform).(RotateTransform.Angle)"
+                                                                 Duration="0:0:0.15"/>
+                                            </Storyboard>
+                                        </BeginStoryboard>
+                                    </Trigger.EnterActions>
+                                    <Trigger.ExitActions>
+                                        <BeginStoryboard x:Name="RotateReverse90Animation">
+                                            <Storyboard>
+                                                <DoubleAnimation From="180" To="0"
+                                                                 Storyboard.TargetName="btnBg"
+                                                                 Storyboard.TargetProperty="(ToggleButton.RenderTransform).(RotateTransform.Angle)"
+                                                                 Duration="0:0:0.15"/>
+                                            </Storyboard>
+                                        </BeginStoryboard>
+                                    </Trigger.ExitActions>
+                                </Trigger>
+                            </ControlTemplate.Triggers>-->
+                        </ControlTemplate>
+                    </ToggleButton.Template>
+                </ToggleButton>
+            </Border>
+            <ContentControl x:Name="popup" DataContext="{Binding ElementName=togglableFlyout}"
+                              IsVisible="{Binding Path=IsChecked, ElementName=toggleButton}"
+                   Content="{Binding ElementName=togglableFlyout, Path=Child}" />
+        </StackPanel>
+    </Border>
+</UserControl>

+ 34 - 0
src/PixiEditor.Avalonia/PixiEditor.AvaloniaUI/Views/Overlays/TogglableFlyout.axaml.cs

@@ -0,0 +1,34 @@
+using System.ComponentModel;
+using Avalonia;
+using Avalonia.Controls;
+
+namespace PixiEditor.Views;
+
+public partial class TogglableFlyout : UserControl
+{
+    public static readonly StyledProperty<AvaloniaObject> ChildProperty =
+        AvaloniaProperty.Register<TogglableFlyout, AvaloniaObject>(nameof(Child));
+
+    [Bindable(true)]
+    [Category("Content")]
+    public AvaloniaObject Child
+    {
+        get { return GetValue(ChildProperty); }
+        set { SetValue(ChildProperty, value); }
+    }
+
+    public static readonly StyledProperty<string> IconPathProperty =
+        AvaloniaProperty.Register<TogglableFlyout, string>(nameof(IconPath));
+
+    public string IconPath
+    {
+        get { return (string)GetValue(IconPathProperty); }
+        set { SetValue(IconPathProperty, value); }
+    }
+    
+    public TogglableFlyout()
+    {
+        InitializeComponent();
+    }
+}
+

+ 1 - 1
src/PixiEditor.Extensions/PixiEditor.Extensions.csproj

@@ -10,7 +10,7 @@
       <Authors>PixiEditor Organization</Authors>
       <Authors>PixiEditor Organization</Authors>
       <Copyright>PixiEditor Organization</Copyright>
       <Copyright>PixiEditor Organization</Copyright>
       <Description>Package for creating custom extensions for pixel art editor PixiEditor</Description>
       <Description>Package for creating custom extensions for pixel art editor PixiEditor</Description>
-      <AvaloniaVersion>11.0.0</AvaloniaVersion>
+      <AvaloniaVersion>11.0.3</AvaloniaVersion>
     </PropertyGroup>
     </PropertyGroup>
 
 
     <ItemGroup>
     <ItemGroup>

+ 1 - 1
src/PixiEditor.UI.Common/PixiEditor.UI.Common.csproj

@@ -4,7 +4,7 @@
         <TargetFramework>net7.0</TargetFramework>
         <TargetFramework>net7.0</TargetFramework>
         <ImplicitUsings>enable</ImplicitUsings>
         <ImplicitUsings>enable</ImplicitUsings>
         <Nullable>enable</Nullable>
         <Nullable>enable</Nullable>
-        <AvaloniaVersion>11.0.0</AvaloniaVersion>
+        <AvaloniaVersion>11.0.3</AvaloniaVersion>
     </PropertyGroup>
     </PropertyGroup>
 
 
     <ItemGroup>
     <ItemGroup>

+ 0 - 10
src/PixiEditor.Zoombox/AssemblyInfo.cs

@@ -1,10 +0,0 @@
-using System.Windows;
-
-[assembly: ThemeInfo(
-    ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
-                                     //(used if a resource is not found in the page,
-                                     // or application resource dictionaries)
-    ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
-                                              //(used if a resource is not found in the page,
-                                              // app, or any theme specific resource dictionaries)
-)]

+ 3 - 2
src/PixiEditor.Zoombox/Operations/IDragOperation.cs

@@ -1,12 +1,13 @@
 using System.Windows.Input;
 using System.Windows.Input;
+using Avalonia.Input;
 
 
 namespace PixiEditor.Zoombox.Operations;
 namespace PixiEditor.Zoombox.Operations;
 
 
 internal interface IDragOperation
 internal interface IDragOperation
 {
 {
-    void Start(MouseButtonEventArgs e);
+    void Start(PointerEventArgs e);
 
 
-    void Update(MouseEventArgs e);
+    void Update(PointerEventArgs e);
 
 
     void Terminate();
     void Terminate();
 }
 }

+ 3 - 2
src/PixiEditor.Zoombox/Operations/ManipulationOperation.cs

@@ -27,7 +27,8 @@ internal class ManipulationOperation
         rotationProcess = new LockingRotationProcess(owner.Angle);
         rotationProcess = new LockingRotationProcess(owner.Angle);
     }
     }
 
 
-    public void Update(ManipulationDeltaEventArgs args)
+    //TODO: Implement this
+    /*public void Update(ManipulationDeltaEventArgs args)
     {
     {
         args.Handled = true;
         args.Handled = true;
         double thresholdFactor = 1;
         double thresholdFactor = 1;
@@ -44,7 +45,7 @@ internal class ManipulationOperation
         if (owner.FlipX ^ owner.FlipY)
         if (owner.FlipX ^ owner.FlipY)
             deltaAngle = -deltaAngle;
             deltaAngle = -deltaAngle;
         Manipulate(args.DeltaManipulation.Scale.X, screenTranslation, screenOrigin, deltaAngle, thresholdFactor);
         Manipulate(args.DeltaManipulation.Scale.X, screenTranslation, screenOrigin, deltaAngle, thresholdFactor);
-    }
+    }*/
 
 
     private void Manipulate(double deltaScale, VecD screenTranslation, VecD screenOrigin, double rotation, double thresholdFactor)
     private void Manipulate(double deltaScale, VecD screenTranslation, VecD screenOrigin, double rotation, double thresholdFactor)
     {
     {

+ 8 - 4
src/PixiEditor.Zoombox/Operations/MoveDragOperation.cs

@@ -1,4 +1,5 @@
 using System.Windows.Input;
 using System.Windows.Input;
+using Avalonia.Input;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Numerics;
 
 
@@ -8,19 +9,21 @@ internal class MoveDragOperation : IDragOperation
 {
 {
     private Zoombox parent;
     private Zoombox parent;
     private VecD prevMousePos;
     private VecD prevMousePos;
+    private IPointer? capturedPointer = null!;
 
 
     public MoveDragOperation(Zoombox zoomBox)
     public MoveDragOperation(Zoombox zoomBox)
     {
     {
         parent = zoomBox;
         parent = zoomBox;
     }
     }
 
 
-    public void Start(MouseButtonEventArgs e)
+    public void Start(PointerEventArgs e)
     {
     {
         prevMousePos = Zoombox.ToVecD(e.GetPosition(parent.mainCanvas));
         prevMousePos = Zoombox.ToVecD(e.GetPosition(parent.mainCanvas));
-        parent.mainGrid.CaptureMouse();
+        e.Pointer.Capture(parent.mainGrid);
+        capturedPointer = e.Pointer;
     }
     }
 
 
-    public void Update(MouseEventArgs e)
+    public void Update(PointerEventArgs e)
     {
     {
         var curMousePos = Zoombox.ToVecD(e.GetPosition(parent.mainCanvas));
         var curMousePos = Zoombox.ToVecD(e.GetPosition(parent.mainCanvas));
         parent.Center += parent.ToZoomboxSpace(prevMousePos) - parent.ToZoomboxSpace(curMousePos);
         parent.Center += parent.ToZoomboxSpace(prevMousePos) - parent.ToZoomboxSpace(curMousePos);
@@ -29,6 +32,7 @@ internal class MoveDragOperation : IDragOperation
 
 
     public void Terminate()
     public void Terminate()
     {
     {
-        parent.mainGrid.ReleaseMouseCapture();
+        capturedPointer?.Capture(null);
+        capturedPointer = null!;
     }
     }
 }
 }

+ 10 - 5
src/PixiEditor.Zoombox/Operations/RotateDragOperation.cs

@@ -1,5 +1,7 @@
 using System.Windows;
 using System.Windows;
 using System.Windows.Input;
 using System.Windows.Input;
+using Avalonia;
+using Avalonia.Input;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Numerics;
 
 
@@ -11,31 +13,33 @@ internal class RotateDragOperation : IDragOperation
     private double initialZoomboxAngle;
     private double initialZoomboxAngle;
     private double initialClickAngle;
     private double initialClickAngle;
     private LockingRotationProcess? rotationProcess;
     private LockingRotationProcess? rotationProcess;
+    private IPointer? capturedPointer = null!;
 
 
     public RotateDragOperation(Zoombox zoomBox)
     public RotateDragOperation(Zoombox zoomBox)
     {
     {
         owner = zoomBox;
         owner = zoomBox;
     }
     }
 
 
-    public void Start(MouseButtonEventArgs e)
+    public void Start(PointerEventArgs e)
     {
     {
         Point pointCur = e.GetPosition(owner.mainCanvas);
         Point pointCur = e.GetPosition(owner.mainCanvas);
         initialClickAngle = GetAngle(new(pointCur.X, pointCur.Y));
         initialClickAngle = GetAngle(new(pointCur.X, pointCur.Y));
         initialZoomboxAngle = owner.Angle;
         initialZoomboxAngle = owner.Angle;
         rotationProcess = new LockingRotationProcess(initialZoomboxAngle);
         rotationProcess = new LockingRotationProcess(initialZoomboxAngle);
-        owner.mainGrid.CaptureMouse();
+        e.Pointer.Capture(owner.mainGrid);
+        capturedPointer = e.Pointer;
     }
     }
 
 
     private double GetAngle(VecD point)
     private double GetAngle(VecD point)
     {
     {
-        VecD center = new(owner.mainCanvas.ActualWidth / 2, owner.mainCanvas.ActualHeight / 2);
+        VecD center = new(owner.mainCanvas.Width / 2, owner.mainCanvas.Height / 2);
         double angle = (point - center).Angle;
         double angle = (point - center).Angle;
         if (double.IsNaN(angle) || double.IsInfinity(angle))
         if (double.IsNaN(angle) || double.IsInfinity(angle))
             return 0;
             return 0;
         return angle;
         return angle;
     }
     }
 
 
-    public void Update(MouseEventArgs e)
+    public void Update(PointerEventArgs e)
     {
     {
         Point pointCur = e.GetPosition(owner.mainCanvas);
         Point pointCur = e.GetPosition(owner.mainCanvas);
         double clickAngle = GetAngle(new(pointCur.X, pointCur.Y));
         double clickAngle = GetAngle(new(pointCur.X, pointCur.Y));
@@ -49,6 +53,7 @@ internal class RotateDragOperation : IDragOperation
 
 
     public void Terminate()
     public void Terminate()
     {
     {
-        owner.mainGrid.ReleaseMouseCapture();
+        capturedPointer?.Capture(null);
+        capturedPointer = null!;
     }
     }
 }
 }

+ 9 - 4
src/PixiEditor.Zoombox/Operations/ZoomDragOperation.cs

@@ -1,6 +1,8 @@
 using System;
 using System;
 using System.Windows;
 using System.Windows;
 using System.Windows.Input;
 using System.Windows.Input;
+using Avalonia;
+using Avalonia.Input;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Numerics;
 
 
@@ -13,21 +15,23 @@ internal class ZoomDragOperation : IDragOperation
     private VecD scaleOrigin;
     private VecD scaleOrigin;
     private VecD screenScaleOrigin;
     private VecD screenScaleOrigin;
     private double originalScale;
     private double originalScale;
+    private IPointer? capturedPointer = null!;
 
 
     public ZoomDragOperation(Zoombox zoomBox)
     public ZoomDragOperation(Zoombox zoomBox)
     {
     {
         parent = zoomBox;
         parent = zoomBox;
     }
     }
 
 
-    public void Start(MouseButtonEventArgs e)
+    public void Start(PointerEventArgs e)
     {
     {
         screenScaleOrigin = Zoombox.ToVecD(e.GetPosition(parent.mainCanvas));
         screenScaleOrigin = Zoombox.ToVecD(e.GetPosition(parent.mainCanvas));
         scaleOrigin = parent.ToZoomboxSpace(screenScaleOrigin);
         scaleOrigin = parent.ToZoomboxSpace(screenScaleOrigin);
         originalScale = parent.Scale;
         originalScale = parent.Scale;
-        parent.mainGrid.CaptureMouse();
+        capturedPointer = e.Pointer;
+        e.Pointer.Capture(parent.mainGrid);
     }
     }
 
 
-    public void Update(MouseEventArgs e)
+    public void Update(PointerEventArgs e)
     {
     {
         Point curScreenPos = e.GetPosition(parent.mainCanvas);
         Point curScreenPos = e.GetPosition(parent.mainCanvas);
         double deltaX = curScreenPos.X - screenScaleOrigin.X;
         double deltaX = curScreenPos.X - screenScaleOrigin.X;
@@ -41,6 +45,7 @@ internal class ZoomDragOperation : IDragOperation
 
 
     public void Terminate()
     public void Terminate()
     {
     {
-        parent.mainGrid.ReleaseMouseCapture();
+        capturedPointer?.Capture(null);
+        capturedPointer = null!;
     }
     }
 }
 }

+ 6 - 3
src/PixiEditor.Zoombox/PixiEditor.Zoombox.csproj

@@ -1,9 +1,8 @@
 <Project Sdk="Microsoft.NET.Sdk">
 <Project Sdk="Microsoft.NET.Sdk">
 
 
   <PropertyGroup>
   <PropertyGroup>
-    <TargetFramework>net7.0-windows</TargetFramework>
+    <TargetFramework>net7.0</TargetFramework>
     <Nullable>enable</Nullable>
     <Nullable>enable</Nullable>
-    <UseWPF>true</UseWPF>
     <WarningsAsErrors>Nullable</WarningsAsErrors>
     <WarningsAsErrors>Nullable</WarningsAsErrors>
     <Configurations>Debug;Release;Steam;DevRelease</Configurations>
     <Configurations>Debug;Release;Steam;DevRelease</Configurations>
     <Platforms>AnyCPU</Platforms>
     <Platforms>AnyCPU</Platforms>
@@ -52,10 +51,14 @@
   </ItemGroup>
   </ItemGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Update="StyleCop.Analyzers" Version="1.2.0-beta.435">
+    <PackageReference Update="StyleCop.Analyzers" Version="1.2.0-beta.507">
       <PrivateAssets>all</PrivateAssets>
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
     </PackageReference>
+
+    <PackageReference Include="Avalonia" Version="11.0.3" />
+
+    <PackageReference Include="System.Reactive" Version="6.0.0" />
   </ItemGroup>
   </ItemGroup>
 
 
 </Project>
 </Project>

+ 1 - 0
src/PixiEditor.Zoombox/ViewportRoutedEventArgs.cs

@@ -1,5 +1,6 @@
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using System.Windows;
 using System.Windows;
+using Avalonia.Interactivity;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Numerics;
 
 
 namespace PixiEditor.Zoombox;
 namespace PixiEditor.Zoombox;

+ 6 - 10
src/PixiEditor.Zoombox/Zoombox.xaml → src/PixiEditor.Zoombox/Zoombox.axaml

@@ -1,24 +1,20 @@
 <ContentControl
 <ContentControl
     x:Class="PixiEditor.Zoombox.Zoombox"
     x:Class="PixiEditor.Zoombox.Zoombox"
-    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns="https://github.com/avaloniaui"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     xmlns:local="clr-namespace:PixiEditor.Zoombox"
     xmlns:local="clr-namespace:PixiEditor.Zoombox"
     mc:Ignorable="d"
     mc:Ignorable="d"
     x:Name="uc"
     x:Name="uc"
     d:DesignHeight="450"
     d:DesignHeight="450"
     d:DesignWidth="800">
     d:DesignWidth="800">
     <Canvas
     <Canvas
-        MouseDown="OnMouseDown"
-        MouseUp="OnMouseUp"
-        MouseMove="OnMouseMove"
-        MouseWheel="OnScroll"
+        PointerPressed="OnMouseDown"
+        PointerReleased="OnMouseUp"
+        PointerMoved="OnMouseMove"
+        PointerWheelChanged="OnScroll"
         ClipToBounds="True"
         ClipToBounds="True"
-        IsManipulationEnabled="{Binding UseTouchGestures, ElementName=uc}"
-        ManipulationDelta="OnManipulationDelta"
-        ManipulationStarted="OnManipulationStarted"
-        ManipulationCompleted="OnManipulationCompleted"
         x:Name="mainCanvas"
         x:Name="mainCanvas"
         Background="Transparent"
         Background="Transparent"
         SizeChanged="OnMainCanvasSizeChanged">
         SizeChanged="OnMainCanvasSizeChanged">

+ 87 - 63
src/PixiEditor.Zoombox/Zoombox.xaml.cs → src/PixiEditor.Zoombox/Zoombox.axaml.cs

@@ -1,59 +1,56 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.ComponentModel;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Input;
-using System.Windows.Markup;
-using ChunkyImageLib.DataHolders;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Metadata;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Zoombox.Operations;
 using PixiEditor.Zoombox.Operations;
 
 
 namespace PixiEditor.Zoombox;
 namespace PixiEditor.Zoombox;
 
 
-[ContentProperty(nameof(AdditionalContent))]
 public partial class Zoombox : ContentControl, INotifyPropertyChanged
 public partial class Zoombox : ContentControl, INotifyPropertyChanged
 {
 {
-    public static readonly DependencyProperty AdditionalContentProperty =
-        DependencyProperty.Register(nameof(AdditionalContent), typeof(object), typeof(Zoombox),
-            new PropertyMetadata(null));
+ public static readonly StyledProperty<object> AdditionalContentProperty =
+            AvaloniaProperty.Register<Zoombox, object>(nameof(AdditionalContent));
 
 
-    public static readonly DependencyProperty ZoomModeProperty =
-        DependencyProperty.Register(nameof(ZoomMode), typeof(ZoomboxMode), typeof(Zoombox),
-            new PropertyMetadata(ZoomboxMode.Normal, ZoomModeChanged));
+        public static readonly StyledProperty<ZoomboxMode> ZoomModeProperty =
+            AvaloniaProperty.Register<Zoombox, ZoomboxMode>(nameof(ZoomMode), defaultValue: ZoomboxMode.Normal);
 
 
-    public static readonly DependencyProperty ZoomOutOnClickProperty =
-        DependencyProperty.Register(nameof(ZoomOutOnClick), typeof(bool), typeof(Zoombox),
-            new PropertyMetadata(false));
+        public static readonly StyledProperty<bool> ZoomOutOnClickProperty =
+            AvaloniaProperty.Register<Zoombox, bool>(nameof(ZoomOutOnClick), defaultValue: false);
 
 
-    public static readonly DependencyProperty UseTouchGesturesProperty =
-        DependencyProperty.Register(nameof(UseTouchGestures), typeof(bool), typeof(Zoombox));
+        public static readonly StyledProperty<bool> UseTouchGesturesProperty =
+            AvaloniaProperty.Register<Zoombox, bool>(nameof(UseTouchGestures));
 
 
-    public static readonly DependencyProperty ScaleProperty =
-        DependencyProperty.Register(nameof(Scale), typeof(double), typeof(Zoombox), new(1.0, OnPropertyChange));
+        public static readonly StyledProperty<double> ScaleProperty =
+            AvaloniaProperty.Register<Zoombox, double>(nameof(Scale), defaultValue: 1.0);
 
 
-    public static readonly DependencyProperty CenterProperty =
-        DependencyProperty.Register(nameof(Center), typeof(VecD), typeof(Zoombox), new(new VecD(0, 0), OnPropertyChange));
+        public static readonly StyledProperty<VecD> CenterProperty =
+            AvaloniaProperty.Register<Zoombox, VecD>(nameof(Center), defaultValue: new VecD(0, 0));
 
 
-    public static readonly DependencyProperty DimensionsProperty =
-        DependencyProperty.Register(nameof(Dimensions), typeof(VecD), typeof(Zoombox));
+        public static readonly StyledProperty<VecD> DimensionsProperty =
+            AvaloniaProperty.Register<Zoombox, VecD>(nameof(Dimensions));
 
 
-    public static readonly DependencyProperty RealDimensionsProperty =
-        DependencyProperty.Register(nameof(RealDimensions), typeof(VecD), typeof(Zoombox));
+        public static readonly StyledProperty<VecD> RealDimensionsProperty =
+            AvaloniaProperty.Register<Zoombox, VecD>(nameof(RealDimensions));
 
 
-    public static readonly DependencyProperty AngleProperty =
-        DependencyProperty.Register(nameof(Angle), typeof(double), typeof(Zoombox), new(0.0, OnPropertyChange));
+        public static readonly StyledProperty<double> AngleProperty =
+            AvaloniaProperty.Register<Zoombox, double>(nameof(Angle), defaultValue: 0.0);
 
 
-    public static readonly DependencyProperty FlipXProperty =
-        DependencyProperty.Register(nameof(FlipX), typeof(bool), typeof(Zoombox), new(false, OnPropertyChange));
+        public static readonly StyledProperty<bool> FlipXProperty =
+            AvaloniaProperty.Register<Zoombox, bool>(nameof(FlipX), defaultValue: false);
 
 
-    public static readonly DependencyProperty FlipYProperty =
-        DependencyProperty.Register(nameof(FlipY), typeof(bool), typeof(Zoombox), new(false, OnPropertyChange));
+        public static readonly StyledProperty<bool> FlipYProperty =
+            AvaloniaProperty.Register<Zoombox, bool>(nameof(FlipY), defaultValue: false);
 
 
-    public static readonly RoutedEvent ViewportMovedEvent = EventManager.RegisterRoutedEvent(
-        nameof(ViewportMoved), RoutingStrategy.Bubble, typeof(EventHandler<ViewportRoutedEventArgs>), typeof(Zoombox));
+    public static readonly RoutedEvent<ViewportRoutedEventArgs> ViewportMovedEvent = RoutedEvent.Register<Zoombox, ViewportRoutedEventArgs>(
+        nameof(ViewportMoved), RoutingStrategies.Bubble);
 
 
-    public object? AdditionalContent
+    [Content]
+    public object AdditionalContent
     {
     {
         get => GetValue(AdditionalContentProperty);
         get => GetValue(AdditionalContentProperty);
         set => SetValue(AdditionalContentProperty, value);
         set => SetValue(AdditionalContentProperty, value);
@@ -139,8 +136,8 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
         get
         get
         {
         {
             double fraction = Math.Max(
             double fraction = Math.Max(
-                mainCanvas.ActualWidth / mainGrid.ActualWidth,
-                mainCanvas.ActualHeight / mainGrid.ActualHeight);
+                mainCanvas.Width / mainGrid.Width,
+                mainCanvas.Height / mainGrid.Height);
             return Math.Min(fraction / 8, 0.1);
             return Math.Min(fraction / 8, 0.1);
         }
         }
     }
     }
@@ -155,13 +152,13 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
             delta.X = -delta.X;
             delta.X = -delta.X;
         if (FlipY)
         if (FlipY)
             delta.Y = -delta.Y;
             delta.Y = -delta.Y;
-        delta += new VecD(mainCanvas.ActualWidth / 2, mainCanvas.ActualHeight / 2);
+        delta += new VecD(mainCanvas.Width / 2, mainCanvas.Height / 2);
         return delta;
         return delta;
     }
     }
 
 
     internal VecD ToZoomboxSpace(VecD mousePos)
     internal VecD ToZoomboxSpace(VecD mousePos)
     {
     {
-        VecD delta = mousePos - new VecD(mainCanvas.ActualWidth / 2, mainCanvas.ActualHeight / 2);
+        VecD delta = mousePos - new VecD(mainCanvas.Width / 2, mainCanvas.Height / 2);
         if (FlipX)
         if (FlipX)
             delta.X = -delta.X;
             delta.X = -delta.X;
         if (FlipY)
         if (FlipY)
@@ -171,14 +168,14 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
     }
     }
 
 
     private IDragOperation? activeDragOperation = null;
     private IDragOperation? activeDragOperation = null;
-    private MouseButtonEventArgs? activeMouseDownEventArgs = null;
+    private PointerEventArgs? activeMouseDownEventArgs = null;
     private VecD activeMouseDownPos;
     private VecD activeMouseDownPos;
 
 
     public event PropertyChangedEventHandler? PropertyChanged;
     public event PropertyChangedEventHandler? PropertyChanged;
 
 
-    private static void ZoomModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+    private static void ZoomModeChanged(AvaloniaPropertyChangedEventArgs<ZoomboxMode> e)
     {
     {
-        Zoombox sender = (Zoombox)d;
+        Zoombox sender = (Zoombox)e.Sender;
         sender.activeDragOperation?.Terminate();
         sender.activeDragOperation?.Terminate();
         sender.activeDragOperation = null;
         sender.activeDragOperation = null;
         sender.activeMouseDownEventArgs = null;
         sender.activeMouseDownEventArgs = null;
@@ -186,11 +183,22 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
 
 
     private double[]? zoomValues;
     private double[]? zoomValues;
 
 
+    static Zoombox()
+    {
+        // Add here all with notifyingSetter
+        ZoomModeProperty.Changed.Subscribe(ZoomModeChanged);
+        ScaleProperty.Changed.Subscribe(OnPropertyChange);
+        AngleProperty.Changed.Subscribe(OnPropertyChange);
+        FlipXProperty.Changed.Subscribe(OnPropertyChange);
+        FlipYProperty.Changed.Subscribe(OnPropertyChange);
+        CenterProperty.Changed.Subscribe(OnPropertyChange);
+    }
+
     public Zoombox()
     public Zoombox()
     {
     {
         CalculateZoomValues();
         CalculateZoomValues();
         InitializeComponent();
         InitializeComponent();
-        Loaded += (_, _) => OnPropertyChange(this, new DependencyPropertyChangedEventArgs());
+        Loaded += (_, _) => OnPropertyChange(this);
     }
     }
 
 
     private void CalculateZoomValues()
     private void CalculateZoomValues()
@@ -262,7 +270,7 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
 
 
     private void RaiseViewportEvent()
     private void RaiseViewportEvent()
     {
     {
-        VecD realDim = new VecD(mainCanvas.ActualWidth, mainCanvas.ActualHeight);
+        VecD realDim = new VecD(mainCanvas.Width, mainCanvas.Height);
         RealDimensions = realDim;
         RealDimensions = realDim;
         RaiseEvent(new ViewportRoutedEventArgs(
         RaiseEvent(new ViewportRoutedEventArgs(
             ViewportMovedEvent,
             ViewportMovedEvent,
@@ -272,14 +280,14 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
             Angle));
             Angle));
     }
     }
 
 
-    public void CenterContent() => CenterContent(new(mainGrid.ActualWidth, mainGrid.ActualHeight));
+    public void CenterContent() => CenterContent(new(mainGrid.Width, mainGrid.Height));
 
 
     public void CenterContent(VecD newSize)
     public void CenterContent(VecD newSize)
     {
     {
         const double marginFactor = 1.1;
         const double marginFactor = 1.1;
         double scaleFactor = Math.Max(
         double scaleFactor = Math.Max(
-            newSize.X * marginFactor / mainCanvas.ActualWidth,
-            newSize.Y * marginFactor / mainCanvas.ActualHeight);
+            newSize.X * marginFactor / mainCanvas.Width,
+            newSize.Y * marginFactor / mainCanvas.Height);
 
 
         Angle = 0;
         Angle = 0;
         FlipX = false;
         FlipX = false;
@@ -290,7 +298,7 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
 
 
     public void ZoomIntoCenter(double delta)
     public void ZoomIntoCenter(double delta)
     {
     {
-        ZoomInto(new VecD(mainCanvas.ActualWidth / 2, mainCanvas.ActualHeight / 2), delta);
+        ZoomInto(new VecD(mainCanvas.Width / 2, mainCanvas.Height / 2), delta);
     }
     }
 
 
     public void ZoomInto(VecD mousePos, double delta)
     public void ZoomInto(VecD mousePos, double delta)
@@ -330,16 +338,25 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
         return index;
         return index;
     }
     }
 
 
-    private void OnMouseDown(object sender, MouseButtonEventArgs e)
+    private void OnMouseDown(object? sender, PointerPressedEventArgs e)
     {
     {
-        if (e.ChangedButton == MouseButton.Right)
+        // TODO: idk if this is correct
+        MouseButton but = e.GetCurrentPoint(this).Properties.PointerUpdateKind switch
+        {
+            PointerUpdateKind.LeftButtonPressed => MouseButton.Left,
+            PointerUpdateKind.RightButtonPressed => MouseButton.Right,
+            PointerUpdateKind.MiddleButtonPressed => MouseButton.Middle,
+            _ => MouseButton.None,
+        };
+
+        if (but == MouseButton.Right)
             return;
             return;
         activeMouseDownEventArgs = e;
         activeMouseDownEventArgs = e;
         activeMouseDownPos = ToVecD(e.GetPosition(mainCanvas));
         activeMouseDownPos = ToVecD(e.GetPosition(mainCanvas));
-        Keyboard.Focus(this);
+        Focus(NavigationMethod.Unspecified);
     }
     }
 
 
-    private void InitiateDrag(MouseButtonEventArgs e)
+    private void InitiateDrag(PointerEventArgs e)
     {
     {
         if (ZoomMode == ZoomboxMode.Normal)
         if (ZoomMode == ZoomboxMode.Normal)
             return;
             return;
@@ -358,9 +375,9 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
         activeDragOperation.Start(e);
         activeDragOperation.Start(e);
     }
     }
 
 
-    private void OnMouseUp(object sender, MouseButtonEventArgs e)
+    private void OnMouseUp(object? sender, PointerReleasedEventArgs e)
     {
     {
-        if (e.ChangedButton == MouseButton.Right)
+        if (e.InitialPressMouseButton == MouseButton.Right)
             return;
             return;
         if (activeDragOperation is not null)
         if (activeDragOperation is not null)
         {
         {
@@ -369,13 +386,13 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
         }
         }
         else
         else
         {
         {
-            if (ZoomMode == ZoomboxMode.Zoom && e.ChangedButton == MouseButton.Left)
+            if (ZoomMode == ZoomboxMode.Zoom && e.InitialPressMouseButton == MouseButton.Left)
                 ZoomInto(ToVecD(e.GetPosition(mainCanvas)), ZoomOutOnClick ? -1 : 1);
                 ZoomInto(ToVecD(e.GetPosition(mainCanvas)), ZoomOutOnClick ? -1 : 1);
         }
         }
         activeMouseDownEventArgs = null;
         activeMouseDownEventArgs = null;
     }
     }
 
 
-    private void OnMouseMove(object sender, MouseEventArgs e)
+    private void OnMouseMove(object? sender, PointerEventArgs e)
     {
     {
         if (activeDragOperation is null && activeMouseDownEventArgs is not null)
         if (activeDragOperation is null && activeMouseDownEventArgs is not null)
         {
         {
@@ -387,18 +404,19 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
         activeDragOperation?.Update(e);
         activeDragOperation?.Update(e);
     }
     }
 
 
-    private void OnScroll(object sender, MouseWheelEventArgs e)
+    private void OnScroll(object sender, PointerWheelEventArgs e)
     {
     {
-        double abs = Math.Abs(e.Delta / 100.0);
+        double abs = Math.Abs(e.Delta.Y / 100.0);
         for (int i = 0; i < abs; i++)
         for (int i = 0; i < abs; i++)
         {
         {
-            ZoomInto(ToVecD(e.GetPosition(mainCanvas)), e.Delta / 100.0);
+            ZoomInto(ToVecD(e.GetPosition(mainCanvas)), e.Delta.Y / 100.0);
         }
         }
     }
     }
 
 
 
 
     private ManipulationOperation? activeManipulationOperation;
     private ManipulationOperation? activeManipulationOperation;
 
 
+    /* TODO: Avalonia uses Pointer events for both mouse and touch, so we can't use this, would be cool to Implement UseTouchGestures
     private void OnManipulationStarted(object? sender, ManipulationStartedEventArgs e)
     private void OnManipulationStarted(object? sender, ManipulationStartedEventArgs e)
     {
     {
         if (!UseTouchGestures || activeManipulationOperation is not null)
         if (!UseTouchGestures || activeManipulationOperation is not null)
@@ -420,15 +438,21 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
             return;
             return;
         activeManipulationOperation = null;
         activeManipulationOperation = null;
     }
     }
+    */
 
 
     internal static VecD ToVecD(Point point) => new VecD(point.X, point.Y);
     internal static VecD ToVecD(Point point) => new VecD(point.X, point.Y);
 
 
-    private static void OnPropertyChange(DependencyObject obj, DependencyPropertyChangedEventArgs args)
+    private static void OnPropertyChange(AvaloniaPropertyChangedEventArgs e)
     {
     {
-        Zoombox? zoombox = (Zoombox)obj;
+        Zoombox? zoombox = (Zoombox)e.Sender;
+
+       OnPropertyChange(zoombox);
+    }
 
 
+    private static void OnPropertyChange(Zoombox zoombox)
+    {
         VecD topLeft = zoombox.ToZoomboxSpace(VecD.Zero).Rotate(zoombox.Angle);
         VecD topLeft = zoombox.ToZoomboxSpace(VecD.Zero).Rotate(zoombox.Angle);
-        VecD bottomRight = zoombox.ToZoomboxSpace(new(zoombox.mainCanvas.ActualWidth, zoombox.mainCanvas.ActualHeight)).Rotate(zoombox.Angle);
+        VecD bottomRight = zoombox.ToZoomboxSpace(new(zoombox.mainCanvas.Width, zoombox.mainCanvas.Height)).Rotate(zoombox.Angle);
 
 
         zoombox.Dimensions = (bottomRight - topLeft).Abs();
         zoombox.Dimensions = (bottomRight - topLeft).Abs();
         zoombox.PropertyChanged?.Invoke(zoombox, new(nameof(zoombox.ScaleTransformXY)));
         zoombox.PropertyChanged?.Invoke(zoombox, new(nameof(zoombox.ScaleTransformXY)));
@@ -442,13 +466,13 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
 
 
     private void OnMainCanvasSizeChanged(object sender, SizeChangedEventArgs e)
     private void OnMainCanvasSizeChanged(object sender, SizeChangedEventArgs e)
     {
     {
-        OnPropertyChange(this, new DependencyPropertyChangedEventArgs());
+        OnPropertyChange(this);
         RaiseViewportEvent();
         RaiseViewportEvent();
     }
     }
 
 
     private void OnGridSizeChanged(object sender, SizeChangedEventArgs args)
     private void OnGridSizeChanged(object sender, SizeChangedEventArgs args)
     {
     {
-        OnPropertyChange(this, new DependencyPropertyChangedEventArgs());
+        OnPropertyChange(this);
         RaiseViewportEvent();
         RaiseViewportEvent();
     }
     }
 }
 }