Kaynağa Gözat

Merge pull request #326 from PixiEditor/crash-fixes

Fixes
Krzysztof Krysiński 3 yıl önce
ebeveyn
işleme
f50f2feb0c

+ 1 - 0
PixiEditor/App.xaml

@@ -27,6 +27,7 @@
                 <ResourceDictionary Source="Styles/AvalonDock/Themes/Generic.xaml" />
                 <ResourceDictionary Source="Styles/AvalonDock/PixiEditorDockTheme.xaml" />
                 <ResourceDictionary Source="Styles/TreeViewStyle.xaml" />
+                <ResourceDictionary Source="Styles/RadioButtonStyle.xaml" />
             </ResourceDictionary.MergedDictionaries>
         </ResourceDictionary>
     </Application.Resources>

+ 1 - 1
PixiEditor/Helpers/Converters/ToolSizeToIntConverter.cs

@@ -31,4 +31,4 @@ namespace PixiEditor.Helpers.Converters
             return int.Parse(match.Groups[0].ValueSpan);
         }
     }
-}
+}

+ 10 - 7
PixiEditor/Helpers/SizeCalculator.cs

@@ -1,17 +1,20 @@
-namespace PixiEditor.Helpers
+using System;
+
+namespace PixiEditor.Helpers
 {
     public static class SizeCalculator
     {
-        public static System.Drawing.Size CalcAbsoluteFromPercentage(int percentage, System.Drawing.Size currentSize)
+        public static System.Drawing.Size CalcAbsoluteFromPercentage(float percentage, System.Drawing.Size currentSize)
         {
-            var percFactor = ((float)percentage) / 100f;
-            var newWidth = currentSize.Width * percFactor;
-            var newHeight = currentSize.Height * percFactor;
-            return new System.Drawing.Size((int)newWidth, (int)newHeight);
+            float percFactor = percentage / 100f;
+            float newWidth = currentSize.Width * percFactor;
+            float newHeight = currentSize.Height * percFactor;
+            return new System.Drawing.Size((int)MathF.Round(newWidth), (int)MathF.Round(newHeight));
         }
+
         public static int CalcPercentageFromAbsolute(int initAbsoluteSize, int currentAbsoluteSize)
         {
-            return (int)((float)currentAbsoluteSize*100) / initAbsoluteSize;
+            return (int)((float)currentAbsoluteSize * 100) / initAbsoluteSize;
         }
     }
 }

+ 1 - 2
PixiEditor/Models/Constants.cs

@@ -6,8 +6,7 @@
         public const int MaxPreviewWidth = 2048;
         public const int MaxPreviewHeight = 2048;
 
-        public const int MaxCanvasWidth = 9999;
-        public const int MaxCanvasHeight = 9999;
+        public const int MaxCanvasSize = 9999;
 
         public const string NativeExtensionNoDot = "pixi";
         public const string NativeExtension = "." + NativeExtensionNoDot;

+ 26 - 6
PixiEditor/Models/DataHolders/Document/Document.Operations.cs

@@ -31,16 +31,18 @@ namespace PixiEditor.Models.DataHolders
             int offsetX = GetOffsetXForAnchor(Width, width, anchor);
             int offsetY = GetOffsetYForAnchor(Height, height, anchor);
 
-            Thickness[] oldOffsets = Layers.Select(x => x.Offset).ToArray();
             Thickness[] newOffsets = Layers.Select(x => new Thickness(offsetX + x.OffsetX, offsetY + x.OffsetY, 0, 0))
                 .ToArray();
 
             object[] processArgs = { newOffsets, width, height };
-            object[] reverseProcessArgs = { oldOffsets, Width, Height };
+            object[] reverseProcessArgs = { Width, Height };
+
+            StorageBasedChange change = new(this, Layers);
 
             ResizeCanvas(newOffsets, width, height);
-            UndoManager.AddUndoChange(new Change(
-                ResizeCanvasProcess,
+
+            UndoManager.AddUndoChange(change.ToChange(
+                RestoreDocumentLayersProcess,
                 reverseProcessArgs,
                 ResizeCanvasProcess,
                 processArgs,
@@ -137,7 +139,7 @@ namespace PixiEditor.Models.DataHolders
                         int diff = documentCenter.X - newOffsetX;
                         newOffsetX = layer.OffsetX + (diff * 2);
                     }
-                    else if(flip == FlipType.Vertical)
+                    else if (flip == FlipType.Vertical)
                     {
                         newOffsetY += layerCenter.Y;
                         int diff = documentCenter.Y - newOffsetY;
@@ -205,8 +207,13 @@ namespace PixiEditor.Models.DataHolders
 
         private void RestoreDocumentLayersProcess(Layer[] layers, UndoLayer[] data, object[] args)
         {
+            int oldWidth = Width;
+            int oldHeight = Height;
             Width = (int)args[0];
             Height = (int)args[1];
+            DocumentSizeChanged?.Invoke(
+                this,
+                new DocumentSizeChangedEventArgs(oldWidth, oldHeight, Width, Height));
             Layers.Clear();
             Layers.AddRange(layers);
         }
@@ -219,9 +226,22 @@ namespace PixiEditor.Models.DataHolders
         /// <param name="newHeight">New canvas height.</param>
         private void ResizeCanvas(Thickness[] offset, int newWidth, int newHeight)
         {
+            Int32Rect newCanvasRect = new(0, 0, newWidth, newHeight);
             for (int i = 0; i < Layers.Count; i++)
             {
-                Layers[i].Offset = offset[i];
+                Layer layer = Layers[i];
+                Thickness newOffset = offset[i];
+                Int32Rect newRect = new((int)newOffset.Left, (int)newOffset.Top, layer.Width, layer.Height);
+                Int32Rect newLayerRect = newRect.Intersect(newCanvasRect);
+                Surface newBitmap = new(newLayerRect.Width, newLayerRect.Height);
+                var oldBitmap = layer.LayerBitmap;
+                using var snapshot = oldBitmap.SkiaSurface.Snapshot();
+                newBitmap.SkiaSurface.Canvas.DrawImage(snapshot, newRect.X - newLayerRect.X, newRect.Y - newLayerRect.Y, Surface.ReplacingPaint);
+
+                layer.LayerBitmap = newBitmap;
+                oldBitmap.Dispose();
+
+                Layers[i].Offset = new Thickness(newLayerRect.X, newLayerRect.Y, 0, 0);
                 Layers[i].MaxWidth = newWidth;
                 Layers[i].MaxHeight = newHeight;
             }

+ 2 - 2
PixiEditor/Models/DataHolders/Surface.cs

@@ -118,8 +118,8 @@ namespace PixiEditor.Models.DataHolders
         public unsafe SKColor GetSRGBPixel(int x, int y)
         {
             Half* ptr = (Half*)(surfaceBuffer + (x + y * Width) * 8);
-            SKColor color = (SKColor)new SKColorF((float)ptr[0], (float)ptr[1], (float)ptr[2], (float)ptr[3]);
-            return SKPMColor.UnPreMultiply(new SKPMColor((uint)color));
+            float a = (float)ptr[3];
+            return (SKColor)new SKColorF((float)ptr[0] / a, (float)ptr[1] / a, (float)ptr[2] / a, (float)ptr[3]);
         }
 
         public void SetSRGBPixel(int x, int y, SKColor color)

+ 5 - 1
PixiEditor/Models/Dialogs/ResizeDocumentDialog.cs

@@ -66,6 +66,10 @@ namespace PixiEditor.Models.Dialogs
             {
                 Width = popup.NewAbsoluteWidth;
                 Height = popup.NewAbsoluteHeight;
+                if (popup is ResizeCanvasPopup resizeCanvas)
+                {
+                    ResizeAnchor = resizeCanvas.SelectedAnchorPoint;
+                }
             }
 
             return (bool)popup.DialogResult;
@@ -81,4 +85,4 @@ namespace PixiEditor.Models.Dialogs
             return ShowDialog<ResizeCanvasPopup>();
         }
     }
-}
+}

+ 1 - 1
PixiEditor/Models/Undo/StorageBasedChange.cs

@@ -93,7 +93,7 @@ namespace PixiEditor.Models.Undo
                         storedLayer.SerializedRect.Height);
 
                     using var image = layer.LayerBitmap.SkiaSurface.Snapshot();
-                    Surface targetSizeSurface = new Surface(finalRect.Width, finalRect.Height);
+                    using Surface targetSizeSurface = new Surface(finalRect.Width, finalRect.Height);
 
                     targetSizeSurface.SkiaSurface.Canvas.DrawImage(image, finalRect, SKRect.Create(0, 0, finalRect.Width, finalRect.Height), Surface.ReplacingPaint);
 

+ 1 - 1
PixiEditor/PixiEditor.csproj

@@ -196,7 +196,7 @@
 			<NoWarn>NU1701</NoWarn>
 		</PackageReference>
 		<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
-		<PackageReference Include="PixiEditor.ColorPicker" Version="3.1.0" />
+		<PackageReference Include="PixiEditor.ColorPicker" Version="3.2.0" />
 		<PackageReference Include="PixiEditor.Parser" Version="2.0.0" />
 		<PackageReference Include="PixiEditor.Parser.Skia" Version="2.0.0.1" />
 		<PackageReference Include="SkiaSharp" Version="2.80.3" />

+ 2 - 2
PixiEditor/Styles/DarkCheckboxStyle.xaml

@@ -13,7 +13,7 @@
                         <BulletDecorator.Bullet>
                             <Border x:Name="Border" Width="18" Height="18" CornerRadius="2" Background="#FF1B1B1B"
                                     BorderThickness="1">
-                                <Path Width="9" Height="9" x:Name="CheckMark" SnapsToDevicePixels="False" Stroke="#FF0077C9" StrokeThickness="1.5" Data="M 0 4 L 3 8 8 0" />
+                                <Path Width="9" Height="9" x:Name="CheckMark" SnapsToDevicePixels="False" Stroke="{StaticResource UIElementBlue}" StrokeThickness="1.5" Data="M 0 4 L 3 8 8 0" />
                             </Border>
                         </BulletDecorator.Bullet>
                         <ContentPresenter Margin="4,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Left" RecognizesAccessKey="True"/>
@@ -21,7 +21,7 @@
                     <ControlTemplate.Triggers>
                         <Trigger Property="IsMouseOver" Value="False">
                             <Setter TargetName="Border" Property="Background" Value="#252525" />
-                            <Setter TargetName="Border" Property="BorderBrush" Value="#3F3F46"/>
+                            <Setter TargetName="Border" Property="BorderBrush" Value="{StaticResource UIElementBorder}"/>
                         </Trigger>
                         <Trigger Property="IsChecked" Value="false">
                             <Setter TargetName="CheckMark" Property="Visibility" Value="Collapsed"/>

+ 32 - 0
PixiEditor/Styles/RadioButtonStyle.xaml

@@ -0,0 +1,32 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+    <Style TargetType="{x:Type RadioButton}" >
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="{x:Type RadioButton}">
+                    <BulletDecorator Background="Transparent" Cursor="Hand">
+                        <BulletDecorator.Bullet>
+                            <Grid Height="16" Width="16">
+                                <!--Define size of the Bullet-->
+                                <!--The two borders-->
+                                <Border Name="RadioOuter" Background="#202020" BorderBrush="{StaticResource UIElementBorder}" BorderThickness="1" CornerRadius="10" />
+                                <Border CornerRadius="10" Margin="4" Name="RadioMark" Background="{StaticResource UIElementBlue}" Visibility="Hidden" />
+                            </Grid>
+                        </BulletDecorator.Bullet>
+                        <!--Text element-->
+                        <TextBlock Margin="3,1,0,0" Foreground="White" FontSize="12">
+                        <ContentPresenter />
+                        </TextBlock>
+                    </BulletDecorator>
+                    <!--If item is checked, trigger the visibility of the mark-->
+                    <ControlTemplate.Triggers>
+                        <Trigger Property="IsChecked" Value="true">
+                            <!--If item is checked, trigger the visibility of the mark and change the color of the selected bullet into a darker gray for better highlighting-->
+                            <Setter TargetName="RadioMark" Property="Visibility" Value="Visible"/>
+                        </Trigger>
+                    </ControlTemplate.Triggers>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+</ResourceDictionary>

+ 2 - 0
PixiEditor/Styles/ThemeColors.xaml

@@ -8,4 +8,6 @@
     <SolidColorBrush x:Key="BrighterAccentColor" Color="#3F3F46" />
     <SolidColorBrush x:Key="AlmostLightModeAccentColor" Color="#4F4F4F" />
     <SolidColorBrush x:Key="SelectionColor" Color="#999" />
+    <SolidColorBrush x:Key="UIElementBlue" Color="#FF0077C9"/>
+    <SolidColorBrush x:Key="UIElementBorder" Color="#3F3F46" />
 </ResourceDictionary>

+ 4 - 0
PixiEditor/Styles/ThemeStyle.xaml

@@ -9,6 +9,10 @@
         <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
     </Style>
 
+    <Style TargetType="{x:Type Border}">
+        <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
+    </Style>
+
     <Style TargetType="Button" x:Key="BaseDarkButton">
         <Setter Property="Background" Value="#404040" />
         <Setter Property="Foreground" Value="White" />

+ 3 - 3
PixiEditor/Views/Dialogs/ResizeCanvasPopup.xaml

@@ -11,7 +11,7 @@
         mc:Ignorable="d" 
         x:Name="window"
         Title="ResizeCanvasPopup" ShowInTaskbar="False" WindowStartupLocation="CenterScreen"
-        Height="420" Width="320" MinHeight="380" MinWidth="300" 
+        Height="420" Width="320" MinHeight="420" MinWidth="320" 
         WindowStyle="None"
         >
 
@@ -37,8 +37,8 @@
                 Style="{StaticResource DarkRoundButton}" Content="Resize" Click="Button_Click" IsDefault="True" />
 
         <StackPanel HorizontalAlignment="Center" Margin="0,30,0,0" Background="{StaticResource MainColor}"
-                    VerticalAlignment="Top" Grid.Row="1" Width="250" Height="300">
-            <local:SizePicker Margin="0,18,0,0" 
+                    VerticalAlignment="Top" Grid.Row="1" Width="250" Height="290">
+            <local:SizePicker Margin="0,8,0,0" 
                               Width="240"
                               Height="170"
                               x:Name="sizePicker"

+ 2 - 2
PixiEditor/Views/Dialogs/ResizeDocumentPopup.xaml

@@ -9,7 +9,7 @@
         xmlns:dial="clr-namespace:PixiEditor.Views.Dialogs"
         mc:Ignorable="d" x:Name="window"
         Title="ResizeDocumentPopup" ShowInTaskbar="False" WindowStartupLocation="CenterScreen"
-        Height="300" Width="320" MaxHeight="300" MaxWidth="300"
+        Height="305" Width="310" MinHeight="305" MinWidth="310"
         xmlns:base="clr-namespace:PixiEditor.Views"
         WindowStyle="None">
 
@@ -45,4 +45,4 @@
             />
 
     </DockPanel>
-    </base:ResizeablePopup>
+</base:ResizeablePopup>

+ 2 - 2
PixiEditor/Views/UserControls/SizeInput.xaml

@@ -42,7 +42,7 @@
                      x:Name="textBox"
                      Text="{Binding Size, ElementName=uc, Converter={converters:ToolSizeToIntConverter}}"
                      d:Text="22"
-                     MaxLength = "4"
+                     MaxLength = "6"
                      >
                 <i:Interaction.Behaviors>
                     <behaviors:GlobalShortcutFocusBehavior/>
@@ -54,7 +54,7 @@
             </TextBox>
             <Grid Grid.Column="1" Background="{Binding BorderBrush, ElementName=border}"
                   d:Background="{StaticResource BrighterAccentColor}"/>
-                      <TextBlock Text="{Binding Unit, ElementName=uc, Converter={converters:EnumToStringConverter}}" TextAlignment="Right"
+            <TextBlock Text="{Binding Unit, ElementName=uc, Converter={converters:EnumToStringConverter}}" TextAlignment="Right"
                        Grid.Column="2" Margin="5,0" VerticalAlignment="Center" d:Text="px"
                        
                        />

+ 62 - 64
PixiEditor/Views/UserControls/SizePicker.xaml

@@ -8,7 +8,7 @@
              xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
              xmlns:enums="clr-namespace:PixiEditor.Models.Enums"
              mc:Ignorable="d"
-             d:DesignHeight="180" d:DesignWidth="240" Name="uc">
+             d:DesignHeight="200" d:DesignWidth="240" Name="uc">
     <i:Interaction.Triggers>
         <i:EventTrigger EventName="Loaded">
             <i:InvokeCommandAction Command="{Binding ElementName=uc, Path=LoadedCommand}"/>
@@ -22,107 +22,105 @@
             <Setter Property="FontSize" Value="12"/>
             <Setter Property="Margin" Value="10,0,0,0"/>
             <Setter Property="Width" Value="80"/>
-            <Setter Property="Height" Value="30"/>
+            <Setter Property="Height" Value="25"/>
         </Style>
     </UserControl.Resources>
     <Border Background="{StaticResource MainColor}" VerticalAlignment="Stretch">
-        <DockPanel>
-            <Grid Height="50" HorizontalAlignment="Center" DockPanel.Dock="Top"
-                  Margin="0,20,0,0"
-                  Visibility="{Binding SizeUnitSelectionVisibility, ElementName=uc}"> 
+        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
+            <Grid Height="60" HorizontalAlignment="Center" DockPanel.Dock="Top"
+                  Visibility="{Binding SizeUnitSelectionVisibility, ElementName=uc}">
                 <Grid.ColumnDefinitions>
                     <ColumnDefinition Width="100"/>
                     <ColumnDefinition/>
                 </Grid.ColumnDefinitions>
                 <Grid.RowDefinitions>
-                    <RowDefinition />
-                    <RowDefinition Height="20"/>
+                    <RowDefinition Height="30"/>
+                    <RowDefinition Height="30"/>
                 </Grid.RowDefinitions>
 
-                <RadioButton    Grid.Row="0" Grid.Column="0"  
-                                x:Name="PercentageRb" 
-                                  Foreground="White" 
-                                  FontSize="12"
-                                  GroupName="Unit"
-                                  Checked="PercentageRb_Checked"
-                                  VerticalContentAlignment="Center"
-                                  IsChecked="{Binding Path=SelectedUnit,  
+                <RadioButton Grid.Row="0" Grid.Column="0"
+                             x:Name="PercentageRb" 
+                             Foreground="White" 
+                             FontSize="12"
+                             GroupName="Unit"
+                             Checked="PercentageRb_Checked"   
+                             VerticalAlignment="Center"
+                             IsChecked="{Binding Path=SelectedUnit,  
                                               ElementName=uc, 
                                               Converter={converters:EnumBooleanConverter}, 
                                               ConverterParameter=Percentage
-                                              }"
-                                  >Percentage:</RadioButton>
-                <local:SizeInput Grid.Column="1" Grid.Row="0"
-                             x:Name="PercentageSizePicker"
-                             IsEnabled="{Binding EditingEnabled, ElementName=uc}"
-                             Size="{Binding Path=ChosenPercentageSize, ElementName=uc, Mode=TwoWay}"
-                             Unit="Percentage"
-                                 Margin="-10,0,0,0"
-                             Width="80"
-                             >
-
+                                              }">Percentage:</RadioButton>
+                <local:SizeInput Grid.Column="1" Grid.Row="0" 
+                                     VerticalAlignment="Center"
+                                     x:Name="PercentageSizePicker"
+                                     IsEnabled="{Binding EditingEnabled, ElementName=uc}"
+                                     Size="{Binding Path=ChosenPercentageSize, ElementName=uc, Mode=TwoWay}"
+                                     Unit="Percentage"
+                                     Margin="-10,0,0,0"
+                                     MaxSize="999900"
+                                     Width="80">
                     <i:Interaction.Triggers>
                         <i:EventTrigger EventName="LostFocus">
-                            <i:InvokeCommandAction Command="{Binding ElementName=uc, Path=WidthLostFocusCommand}"/>
+                            <i:InvokeCommandAction Command="{Binding ElementName=uc, Path=PercentageLostFocusCommand}"/>
                         </i:EventTrigger>
                     </i:Interaction.Triggers>
                 </local:SizeInput>
 
                 <RadioButton Grid.Row="1" Grid.Column="0"  
-                              x:Name="AbsoluteRb" 
-                              Foreground="White" 
-                              FontSize="12"
-                              GroupName="Unit"
-                              Checked="AbsoluteRb_Checked"
-                              VerticalContentAlignment="Center"
-                              IsChecked="{Binding Path=SelectedUnit,  
+                             x:Name="AbsoluteRb" 
+                             Foreground="White" 
+                             FontSize="12"
+                             GroupName="Unit"
+                             Checked="AbsoluteRb_Checked"
+                             VerticalAlignment="Center"
+                             IsChecked="{Binding Path=SelectedUnit,  
                                               ElementName=uc, 
                                               Converter={converters:EnumBooleanConverter}, 
                                               ConverterParameter=Pixel}"
                               >Absolute:</RadioButton>
-               
+
             </Grid>
 
             <Grid Height="90" HorizontalAlignment="Center" DockPanel.Dock="Top">
-            <Grid.ColumnDefinitions>
-                <ColumnDefinition Width="40"/>
-                <ColumnDefinition/>
-            </Grid.ColumnDefinitions>
-            <Grid.RowDefinitions>
-                <RowDefinition />
-                <RowDefinition />
-                <RowDefinition />
-            </Grid.RowDefinitions>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="40"/>
+                    <ColumnDefinition/>
+                </Grid.ColumnDefinitions>
+                <Grid.RowDefinitions>
+                    <RowDefinition />
+                    <RowDefinition />
+                    <RowDefinition />
+                </Grid.RowDefinitions>
 
-            <TextBlock Grid.Column="0" Grid.Row="0" Foreground="Snow" Text="Width:" VerticalAlignment="Center" HorizontalAlignment="Left" />
-            <local:SizeInput Grid.Column="1" Grid.Row="0"
+                <TextBlock Grid.Column="0" Grid.Row="0" Foreground="Snow" Text="Width:" VerticalAlignment="Center" HorizontalAlignment="Left" />
+                <local:SizeInput Grid.Column="1" Grid.Row="0"
                              x:Name="WidthPicker"
                              Width="80"
                              IsEnabled="{Binding EditingEnabled, ElementName=uc}"
                              Size="{Binding Path=ChosenWidth, ElementName=uc, Mode=TwoWay}"
                              Margin="50,0,0,0"
                              >
-                <i:Interaction.Triggers>
-                    <i:EventTrigger EventName="LostFocus">
-                        <i:InvokeCommandAction Command="{Binding ElementName=uc, Path=WidthLostFocusCommand}"/>
-                    </i:EventTrigger>
-                </i:Interaction.Triggers>
-            </local:SizeInput>
+                    <i:Interaction.Triggers>
+                        <i:EventTrigger EventName="LostFocus">
+                            <i:InvokeCommandAction Command="{Binding ElementName=uc, Path=WidthLostFocusCommand}"/>
+                        </i:EventTrigger>
+                    </i:Interaction.Triggers>
+                </local:SizeInput>
 
-            <TextBlock Grid.Column="0" Grid.Row="1" Foreground="Snow" Text="Height:" VerticalAlignment="Center" HorizontalAlignment="Left"/>
-            <local:SizeInput Grid.Column="1" Grid.Row="1"
+                <TextBlock Grid.Column="0" Grid.Row="1" Foreground="Snow" Text="Height:" VerticalAlignment="Center" HorizontalAlignment="Left"/>
+                <local:SizeInput Grid.Column="1" Grid.Row="1"
                              x:Name="HeightPicker" 
                              IsEnabled="{Binding EditingEnabled, ElementName=uc}"
                              Margin="50,0,0,0"
                              Size="{Binding ChosenHeight, ElementName=uc, Mode=TwoWay}">
-                <i:Interaction.Triggers>
-                    <i:EventTrigger EventName="LostFocus">
-                        <i:InvokeCommandAction Command="{Binding ElementName=uc, Path=HeightLostFocusCommand}"/>
-                    </i:EventTrigger>
-                </i:Interaction.Triggers>
-            </local:SizeInput>
+                    <i:Interaction.Triggers>
+                        <i:EventTrigger EventName="LostFocus">
+                            <i:InvokeCommandAction Command="{Binding ElementName=uc, Path=HeightLostFocusCommand}"/>
+                        </i:EventTrigger>
+                    </i:Interaction.Triggers>
+                </local:SizeInput>
 
-            <CheckBox 
+                <CheckBox 
                   Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="2"
                   Name="aspectRatio" 
                   IsChecked="{Binding ElementName=uc, Path=PreserveAspectRatio}"
@@ -130,7 +128,7 @@
                   Foreground="White" 
                   HorizontalAlignment="Left" 
                   VerticalAlignment="Center" />
-        </Grid>
-        </DockPanel>
+            </Grid>
+        </StackPanel>
     </Border>
 </UserControl>

+ 50 - 36
PixiEditor/Views/UserControls/SizePicker.xaml.cs

@@ -1,7 +1,6 @@
 using PixiEditor.Helpers;
 using PixiEditor.Models;
 using PixiEditor.Models.Enums;
-using PixiEditor.ViewModels;
 using System;
 using System.Windows;
 using System.Windows.Controls;
@@ -22,11 +21,8 @@ namespace PixiEditor.Views
         public static readonly DependencyProperty ChosenHeightProperty =
             DependencyProperty.Register(nameof(ChosenHeight), typeof(int), typeof(SizePicker), new PropertyMetadata(1));
 
-        public static readonly DependencyProperty NextControlProperty =
-            DependencyProperty.Register(nameof(NextControl), typeof(FrameworkElement), typeof(SizePicker));
-
         public static readonly DependencyProperty ChosenPercentageSizeProperty =
-            DependencyProperty.Register(nameof(ChosenPercentageSize), typeof(int), typeof(SizePicker), new PropertyMetadata(1, InputSizeChanged));
+            DependencyProperty.Register(nameof(ChosenPercentageSize), typeof(float), typeof(SizePicker), new PropertyMetadata(100f));
 
         public static readonly DependencyProperty SelectedUnitProperty =
             DependencyProperty.Register(nameof(SelectedUnit), typeof(SizeUnit), typeof(SizePicker), new PropertyMetadata(SizeUnit.Pixel));
@@ -35,24 +31,6 @@ namespace PixiEditor.Views
             DependencyProperty.Register(nameof(SizeUnitSelectionVisibility), typeof(Visibility), typeof(SizePicker), new PropertyMetadata(Visibility.Collapsed));
 
         System.Drawing.Size? initSize = null;
-               
-        private static void InputSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
-        {
-            var sizePicker = d as SizePicker;
-            if (!sizePicker.initSize.HasValue)
-                return;
-
-            var newValue = (int)e.NewValue;
-            var newSize = SizeCalculator.CalcAbsoluteFromPercentage(newValue, sizePicker.initSize.Value);
-            if (newSize.Width > Constants.MaxCanvasWidth || newSize.Height > Constants.MaxCanvasHeight)
-            {
-                newSize = new System.Drawing.Size(Constants.MaxCanvasWidth, Constants.MaxCanvasHeight);
-                d.SetValue(ChosenPercentageSizeProperty, SizeCalculator.CalcPercentageFromAbsolute(sizePicker.initSize.Value.Width, newSize.Width));
-            }
-            
-            d.SetValue(ChosenWidthProperty, newSize.Width);
-            d.SetValue(ChosenHeightProperty, newSize.Height);
-        }
 
         public bool EditingEnabled
         {
@@ -72,13 +50,13 @@ namespace PixiEditor.Views
             set => SetValue(ChosenHeightProperty, value);
         }
 
-        public int ChosenPercentageSize
+        public float ChosenPercentageSize
         {
-            get => (int)GetValue(ChosenPercentageSizeProperty);
+            get => (float)GetValue(ChosenPercentageSizeProperty);
             set => SetValue(ChosenPercentageSizeProperty, value);
         }
 
-        public SizeUnit SelectedUnit 
+        public SizeUnit SelectedUnit
         {
             get => (SizeUnit)GetValue(SelectedUnitProperty);
             set => SetValue(SelectedUnitProperty, value);
@@ -90,12 +68,6 @@ namespace PixiEditor.Views
             set => SetValue(SizeUnitSelectionVisibilityProperty, value);
         }
 
-        public FrameworkElement NextControl
-        {
-            get => (FrameworkElement)GetValue(NextControlProperty);
-            set => SetValue(NextControlProperty, value);
-        }
-
         public bool PreserveAspectRatio
         {
             get => (bool)GetValue(PreserveAspectRatioProperty);
@@ -105,12 +77,14 @@ namespace PixiEditor.Views
         public RelayCommand LoadedCommand { get; private set; }
         public RelayCommand WidthLostFocusCommand { get; private set; }
         public RelayCommand HeightLostFocusCommand { get; private set; }
-                        
+        public RelayCommand PercentageLostFocusCommand { get; private set; }
+
         public SizePicker()
         {
             LoadedCommand = new(AfterLoaded);
             WidthLostFocusCommand = new(WidthLostFocus);
             HeightLostFocusCommand = new(HeightLostFocus);
+            PercentageLostFocusCommand = new(PercentageLostFocus);
             InitializeComponent();
         }
 
@@ -128,6 +102,46 @@ namespace PixiEditor.Views
         private void WidthLostFocus(object param) => OnSizeUpdate(true);
         private void HeightLostFocus(object param) => OnSizeUpdate(false);
 
+        private void PercentageLostFocus(object param)
+        {
+            if (!initSize.HasValue)
+                return;
+
+            float targetPercentage = GetTargetPercentage(initSize.Value, ChosenPercentageSize);
+            var newSize = SizeCalculator.CalcAbsoluteFromPercentage(targetPercentage, initSize.Value);
+
+            //this shouldn't ever be necessary but just in case
+            newSize.Width = Math.Clamp(newSize.Width, 1, Constants.MaxCanvasSize);
+            newSize.Height = Math.Clamp(newSize.Height, 1, Constants.MaxCanvasSize);
+
+            ChosenPercentageSize = targetPercentage;
+            ChosenWidth = newSize.Width;
+            ChosenHeight = newSize.Height;
+        }
+
+        private static float GetTargetPercentage(System.Drawing.Size initSize, float desiredPercentage)
+        {
+            var potentialSize = SizeCalculator.CalcAbsoluteFromPercentage(desiredPercentage, initSize);
+            // all good
+            if (potentialSize.Width > 0 && potentialSize.Height > 0 && potentialSize.Width <= Constants.MaxCanvasSize && potentialSize.Height <= Constants.MaxCanvasSize)
+                return desiredPercentage;
+
+            // canvas too small
+            if (potentialSize.Width <= 0 || potentialSize.Height <= 0)
+            {
+                if (potentialSize.Width < potentialSize.Height)
+                    return 100f / initSize.Width;
+                else
+                    return 100f / initSize.Height;
+            }
+
+            // canvas too big
+            if (potentialSize.Width > potentialSize.Height)
+                return Constants.MaxCanvasSize * 100f / initSize.Width;
+            else
+                return Constants.MaxCanvasSize * 100f / initSize.Height;
+        }
+
         private void OnSizeUpdate(bool widthUpdated)
         {
             if (!initSize.HasValue || !PreserveAspectRatio)
@@ -142,7 +156,7 @@ namespace PixiEditor.Views
                 ChosenWidth = Math.Clamp(ChosenHeight * initSize.Value.Width / initSize.Value.Height, 1, WidthPicker.MaxSize);
             }
         }
-                
+
         private void PercentageRb_Checked(object sender, RoutedEventArgs e)
         {
             EnableSizeEditors();
@@ -155,11 +169,11 @@ namespace PixiEditor.Views
 
         private void EnableSizeEditors()
         {
-            if(PercentageSizePicker != null)
+            if (PercentageSizePicker != null)
                 PercentageSizePicker.IsEnabled = EditingEnabled && PercentageRb.IsChecked.Value;
             if (WidthPicker != null)
                 WidthPicker.IsEnabled = EditingEnabled && !PercentageRb.IsChecked.Value;
-            if(HeightPicker != null)
+            if (HeightPicker != null)
                 HeightPicker.IsEnabled = EditingEnabled && !PercentageRb.IsChecked.Value;
         }
     }

+ 1 - 1
PixiEditor/Views/UserControls/ToolSettingColorPicker.xaml

@@ -9,7 +9,7 @@
              d:Background="{StaticResource AccentColor}">
     <Grid>
         <StackPanel Orientation="Horizontal">
-            <colorpicker:PortableColorPicker x:Name="ColorPicker" SelectedColor="{Binding SelectedColor, ElementName=uc,Mode=TwoWay}"/>
+            <colorpicker:PortableColorPicker Width="40" Height="20" x:Name="ColorPicker" SelectedColor="{Binding SelectedColor, ElementName=uc,Mode=TwoWay}"/>
             <Button Command="{Binding CopyMainColorCommand, ElementName=uc}" Style="{StaticResource DarkRoundButton}" FontSize="12" Width="100" Margin="5,0,0,0">Copy Main Color</Button>
         </StackPanel>
     </Grid>