Browse Source

Merge pull request #169 from PixiEditor/fixes

Fixed 3 issues
Krzysztof Krysiński 4 years ago
parent
commit
e31af045ba

+ 5 - 0
PixiEditor/Helpers/Extensions/ParserHelpers.cs

@@ -20,6 +20,11 @@ namespace PixiEditor.Helpers.Extensions
                     Color.FromArgb(x.Item1, x.Item2, x.Item3, x.Item4)))
             };
 
+            if (document.Layers.Count > 0)
+            {
+                document.SetMainActiveLayer(0);
+            }
+
             return document;
         }
 

+ 2 - 1
PixiEditor/Models/Dialogs/ConfirmationDialog.cs

@@ -9,7 +9,8 @@ namespace PixiEditor.Models.Dialogs
         {
             ConfirmationPopup popup = new ConfirmationPopup
             {
-                Body = message
+                Body = message,
+                Topmost = true
             };
             if ((bool)popup.ShowDialog())
             {

+ 16 - 2
PixiEditor/Models/Dialogs/NoticeDialog.cs

@@ -6,9 +6,23 @@ namespace PixiEditor.Models.Dialogs
     {
         public static void Show(string message)
         {
-            NoticePopup popup = new NoticePopup
+            NoticePopup popup = new ()
             {
-                Body = message
+                Body = message,
+                Title = string.Empty,
+                Topmost = true
+            };
+
+            popup.ShowDialog();
+        }
+
+        public static void Show(string message, string title)
+        {
+            NoticePopup popup = new ()
+            {
+                Body = message,
+                Title = title,
+                Topmost = true
             };
 
             popup.ShowDialog();

+ 0 - 16
PixiEditor/Models/IO/Importer.cs

@@ -71,22 +71,6 @@ namespace PixiEditor.Models.IO
             }
         }
 
-        public static Document ImportOldDocument(string path)
-        {
-            try
-            {
-                using FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read);
-
-                Document doc = PixiParser.DeserializeOld(stream).ToDocument();
-                doc.DocumentFilePath = path;
-                return doc;
-            }
-            catch (SerializationException)
-            {
-                throw new CorruptedFileException();
-            }
-        }
-
         public static bool IsSupportedFile(string path)
         {
             path = path.ToLower();

+ 13 - 1
PixiEditor/Models/UserPreferences/PreferencesSettings.cs

@@ -127,6 +127,9 @@ namespace PixiEditor.Models.UserPreferences
             }
             catch (InvalidCastException)
             {
+                Preferences.Remove(name);
+                Save();
+
                 return fallbackValue;
             }
         }
@@ -151,6 +154,9 @@ namespace PixiEditor.Models.UserPreferences
             }
             catch (InvalidCastException)
             {
+                LocalPreferences.Remove(name);
+                Save();
+
                 return fallbackValue;
             }
         }
@@ -181,7 +187,13 @@ namespace PixiEditor.Models.UserPreferences
             else
             {
                 string json = File.ReadAllText(path);
-                return JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
+                var dictionary = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
+
+                // dictionary is null if the user deletes the content of the preference file.
+                if (dictionary != null)
+                {
+                    return dictionary;
+                }
             }
 
             return new Dictionary<string, object>();

+ 4 - 25
PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs

@@ -195,23 +195,11 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
             catch (CorruptedFileException ex)
             {
-                MessageBox.Show(ex.Message, "Failed to open file.", MessageBoxButton.OK, MessageBoxImage.Error);
+                NoticeDialog.Show(ex.Message, "Failed to open file.");
             }
             catch (OldFileFormatException)
             {
-                MessageBoxResult result = MessageBox.Show("This pixi file uses the old file format and is insecure.\nOnly continue if you trust the source of the file", "Old file format", MessageBoxButton.OKCancel);
-
-                if (result == MessageBoxResult.OK)
-                {
-                    try
-                    {
-                        OpenDocument(path, true);
-                    }
-                    catch (CorruptedFileException ex)
-                    {
-                        MessageBox.Show(ex.Message, "Failed to open file.", MessageBoxButton.OK, MessageBoxImage.Error);
-                    }
-                }
+                NoticeDialog.Show("This .pixi file uses the old format,\n which is no longer supported and can't be opened.", "Old file format");
             }
         }
 
@@ -240,18 +228,9 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
         }
 
-        private void OpenDocument(string path, bool openOld = false)
+        private void OpenDocument(string path)
         {
-            Document document;
-
-            if (openOld)
-            {
-                document = Importer.ImportOldDocument(path);
-            }
-            else
-            {
-                document = Importer.ImportDocument(path);
-            }
+            Document document = Importer.ImportDocument(path);
 
             if (Owner.BitmapManager.Documents.Select(x => x.DocumentFilePath).All(y => y != path))
             {

+ 96 - 27
PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs

@@ -6,6 +6,7 @@ using System.Reflection;
 using System.Threading.Tasks;
 using System.Windows;
 using PixiEditor.Helpers;
+using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Processes;
 using PixiEditor.Models.UserPreferences;
 using PixiEditor.UpdateModule;
@@ -56,47 +57,64 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public async Task<bool> CheckForUpdate()
         {
-            return await Task.Run(async () =>
+            bool updateAvailable = await UpdateChecker.CheckUpdateAvailable();
+            bool updateCompatible = await UpdateChecker.IsUpdateCompatible();
+            bool updateFileDoesNotExists = !File.Exists(
+                Path.Join(UpdateDownloader.DownloadLocation, $"update-{UpdateChecker.LatestReleaseInfo.TagName}.zip"));
+            bool updateExeDoesNotExists = !File.Exists(
+                Path.Join(UpdateDownloader.DownloadLocation, $"update-{UpdateChecker.LatestReleaseInfo.TagName}.exe"));
+            if (updateAvailable && updateFileDoesNotExists && updateExeDoesNotExists)
             {
-                bool updateAvailable = await UpdateChecker.CheckUpdateAvailable();
-                bool updateCompatible = await UpdateChecker.IsUpdateCompatible();
-                bool updateFileDoesNotExists = !File.Exists(
-                    Path.Join(UpdateDownloader.DownloadLocation, $"update-{UpdateChecker.LatestReleaseInfo.TagName}.zip"));
-                bool updateExeDoesNotExists = !File.Exists(
-                    Path.Join(UpdateDownloader.DownloadLocation, $"update-{UpdateChecker.LatestReleaseInfo.TagName}.exe"));
-                if (updateAvailable && updateFileDoesNotExists && updateExeDoesNotExists)
+                VersionText = "Downloading update...";
+                if (updateCompatible)
                 {
-                    VersionText = "Downloading update...";
-                    if (updateCompatible)
-                    {
-                        await UpdateDownloader.DownloadReleaseZip(UpdateChecker.LatestReleaseInfo);
-                    }
-                    else
-                    {
-                        await UpdateDownloader.DownloadInstaller(UpdateChecker.LatestReleaseInfo);
-                    }
-
-                    UpdateReadyToInstall = true;
-                    return true;
+                    await UpdateDownloader.DownloadReleaseZip(UpdateChecker.LatestReleaseInfo);
+                }
+                else
+                {
+                    await UpdateDownloader.DownloadInstaller(UpdateChecker.LatestReleaseInfo);
                 }
 
-                return false;
-            });
+                UpdateReadyToInstall = true;
+                return true;
+            }
+
+            return false;
         }
 
-        private async void Owner_OnStartupEvent(object sender, EventArgs e)
+        private static void AskToInstall()
         {
-            if (IPreferences.Current.GetPreference("CheckUpdatesOnStartup", true))
+            string dir = AppDomain.CurrentDomain.BaseDirectory;
+            UpdateDownloader.CreateTempDirectory();
+            bool updateZipExists = Directory.GetFiles(UpdateDownloader.DownloadLocation, "update-*.zip").Length > 0;
+            string[] updateExeFiles = Directory.GetFiles(UpdateDownloader.DownloadLocation, "update-*.exe");
+            bool updateExeExists = updateExeFiles.Length > 0;
+
+            string updaterPath = Path.Join(dir, "PixiEditor.UpdateInstaller.exe");
+
+            if (updateZipExists || updateExeExists)
             {
-                await CheckForUpdate();
+                ViewModelMain.Current.UpdateSubViewModel.UpdateReadyToInstall = true;
+                var result = ConfirmationDialog.Show("Update is ready to install. Do you want to install it now?");
+                if (result == Models.Enums.ConfirmationType.Yes)
+                {
+                    if (updateZipExists && File.Exists(updaterPath))
+                    {
+                        InstallHeadless(updaterPath);
+                    }
+                    else if (updateExeExists)
+                    {
+                        OpenExeInstaller(updateExeFiles[0]);
+                    }
+                }
             }
         }
 
-        private void RestartApplication(object parameter)
+        private static void InstallHeadless(string updaterPath)
         {
             try
             {
-                ProcessHelper.RunAsAdmin(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "PixiEditor.UpdateInstaller.exe"));
+                ProcessHelper.RunAsAdmin(updaterPath);
                 Application.Current.Shutdown();
             }
             catch (Win32Exception)
@@ -109,6 +127,57 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
         }
 
+        private static void OpenExeInstaller(string updateExeFile)
+        {
+            bool alreadyUpdated = AssemblyHelper.GetCurrentAssemblyVersion() ==
+                    updateExeFile.Split('-')[1].Split(".exe")[0];
+
+            if (!alreadyUpdated)
+            {
+                RestartToUpdate(updateExeFile);
+            }
+            else
+            {
+                File.Delete(updateExeFile);
+            }
+        }
+
+        private static void RestartToUpdate(string updateExeFile)
+        {
+            Process.Start(updateExeFile);
+            Application.Current.Shutdown();
+        }
+
+        private static void RestartApplication(object parameter)
+        {
+            try
+            {
+                ProcessHelper.RunAsAdmin(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "PixiEditor.UpdateInstaller.exe"));
+                Application.Current.Shutdown();
+            }
+            catch (Win32Exception)
+            {
+                NoticeDialog.Show("Couldn't update without administrator rights.", "Insufficient permissions");
+            }
+        }
+
+        private async void Owner_OnStartupEvent(object sender, EventArgs e)
+        {
+            if (IPreferences.Current.GetPreference("CheckUpdatesOnStartup", true))
+            {
+                try
+                {
+                    await CheckForUpdate();
+                }
+                catch (System.Net.Http.HttpRequestException)
+                {
+                    NoticeDialog.Show("Could not check if there's an update available");
+                }
+
+                AskToInstall();
+            }
+        }
+
         private void InitUpdateChecker()
         {
             string version = AssemblyHelper.GetCurrentAssemblyVersion();

+ 16 - 6
PixiEditor/ViewModels/ViewModelMain.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
+using System.Diagnostics;
 using System.Linq;
 using System.Windows;
 using System.Windows.Input;
@@ -59,9 +60,7 @@ namespace PixiEditor.ViewModels
 
         public DiscordViewModel DiscordViewModel { get; set; }
 
-#if DEBUG
         public DebugViewModel DebugSubViewModel { get; set; }
-#endif
 
         public BitmapManager BitmapManager { get; set; }
 
@@ -101,7 +100,6 @@ namespace PixiEditor.ViewModels
             CloseWindowCommand = new RelayCommand(CloseWindow);
 
             FileSubViewModel = new FileViewModel(this);
-            UpdateSubViewModel = new UpdateViewModel(this);
             ToolsSubViewModel = new ToolsViewModel(this);
             IoSubViewModel = new IoViewModel(this);
             LayersSubViewModel = new LayersViewModel(this);
@@ -111,9 +109,9 @@ namespace PixiEditor.ViewModels
             ColorsSubViewModel = new ColorsViewModel(this);
             DocumentSubViewModel = new DocumentViewModel(this);
             DiscordViewModel = new DiscordViewModel(this, "764168193685979138");
-#if DEBUG
-            DebugSubViewModel = new DebugViewModel(this);
-#endif
+
+            AddDebugOnlyViewModels();
+            AddReleaseOnlyViewModels();
 
             ShortcutController = new ShortcutController(
                     new ShortcutGroup(
@@ -192,6 +190,18 @@ namespace PixiEditor.ViewModels
             return BitmapManager.ActiveDocument != null;
         }
 
+        [Conditional("DEBUG")]
+        private void AddDebugOnlyViewModels()
+        {
+            DebugSubViewModel = new DebugViewModel(this);
+        }
+
+        [Conditional("RELEASE")]
+        private void AddReleaseOnlyViewModels()
+        {
+            UpdateSubViewModel = new UpdateViewModel(this);
+        }
+
         private Shortcut CreateToolShortcut<T>(Key key, ModifierKeys modifier = ModifierKeys.None)
             where T : Tool
         {

+ 32 - 7
PixiEditor/Views/Dialogs/HelloTherePopup.xaml

@@ -5,8 +5,9 @@
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:dataHolders="clr-namespace:PixiEditor.Models.DataHolders" xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
         xmlns:sys="clr-namespace:System;assembly=System.Runtime"
+        xmlns:uc="clr-namespace:PixiEditor.Views.UserControls"
         mc:Ignorable="d"
-        Title="Hello there!" Height="651" Width="651"
+        Title="Hello there!" Height="662" Width="632"
         WindowStyle="None" WindowStartupLocation="CenterScreen">
 
     <Window.Resources>
@@ -20,7 +21,7 @@
 
         <Style TargetType="Button" BasedOn="{StaticResource DarkRoundButton}" x:Key="SocialMediaButton">
             <Setter Property="Width" Value="150"/>
-            <Setter Property="Margin" Value="5,0,5,0"/>
+            <Setter Property="Margin" Value="5,8,5,0"/>
             <Setter Property="FontSize" Value="18"/>
             <Setter Property="Height" Value="28"/>
         </Style>
@@ -62,7 +63,7 @@
                         <Image Source="../../Images/PixiEditorLogo.png" Height="40" VerticalAlignment="Center"/>
                         <TextBlock FontSize="40" FontWeight="SemiBold" VerticalAlignment="Center" Margin="10,0,0,0">PixiEditor</TextBlock>
                     </StackPanel>
-                    <TextBlock HorizontalAlignment="Center" FontSize="20" FontWeight="Medium">v0.2</TextBlock>
+                    <TextBlock HorizontalAlignment="Center" FontSize="20" FontWeight="Medium" Text="{Binding VersionText}"/>
                 </StackPanel>
 
                 <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center">
@@ -90,7 +91,7 @@
                                     <StackPanel Margin="8,5,8,0" ToolTip="{Binding FilePath}">
                                         <Button Margin="0,10,0,0" HorizontalAlignment="Center"
                                                 Width="100" Height="100"
-                                                Command="{Binding DataContext.OpenRecentCommand, RelativeSource={RelativeSource AncestorType=WrapPanel}}"
+                                                Command="{Binding DataContext.OpenRecentCommand, RelativeSource={RelativeSource AncestorType=uc:AlignableWrapPanel}}"
                                                 CommandParameter="{Binding FilePath}"
                                                 Style="{StaticResource DarkRoundButton}">
                                             <Image Source="{Binding PreviewBitmap}" Margin="20"/>
@@ -110,13 +111,13 @@
                         </ItemsControl.ItemTemplate>
                         <ItemsControl.ItemsPanel>
                             <ItemsPanelTemplate>
-                                <WrapPanel HorizontalAlignment="Center"/>
+                                <uc:AlignableWrapPanel HorizontalAlignment="Center" HorizontalContentAlignment="Center"/>
                             </ItemsPanelTemplate>
                         </ItemsControl.ItemsPanel>
                     </ItemsControl>
                 </StackPanel>
 
-                <StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,18">
+                <uc:AlignableWrapPanel Grid.Row="3" HorizontalContentAlignment="Center" HorizontalAlignment="Center" Margin="0,5,0,15">
                     <Button Command="{Binding OpenHyperlinkCommand}" CommandParameter="https://discord.gg/tzkQFDkqQS"
                             Content="Discord">
                         <Button.Style>
@@ -154,7 +155,31 @@
                             </Style>
                         </Button.Style>
                     </Button>
-                </StackPanel>
+                    <Button Command="{Binding OpenHyperlinkCommand}" CommandParameter="https://www.youtube.com/channel/UCT5XvyvX1q5PAIaXfWmpsMQ"
+                            Content="YouTube">
+                        <Button.Style>
+                            <Style TargetType="Button" BasedOn="{StaticResource SocialMediaButton}">
+                                <Style.Triggers>
+                                    <Trigger Property="IsMouseOver" Value="True">
+                                        <Setter Property="Background" Value="#FF0000"/>
+                                    </Trigger>
+                                </Style.Triggers>
+                            </Style>
+                        </Button.Style>
+                    </Button>
+                    <Button Command="{Binding OpenHyperlinkCommand}" CommandParameter="https://opencollective.com/pixieditor"
+                            Content="Donate" ToolTip="And subscribe to PixiEditor's OnlyFans please">
+                        <Button.Style>
+                            <Style TargetType="Button" BasedOn="{StaticResource SocialMediaButton}">
+                                <Style.Triggers>
+                                    <Trigger Property="IsMouseOver" Value="True">
+                                        <Setter Property="Background" Value="#DB61A2"/>
+                                    </Trigger>
+                                </Style.Triggers>
+                            </Style>
+                        </Button.Style>
+                    </Button>
+                </uc:AlignableWrapPanel>
             </Grid>
         </ScrollViewer>
     </Grid>

+ 14 - 4
PixiEditor/Views/Dialogs/HelloTherePopup.xaml.cs

@@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
 using System.Diagnostics;
 using System.IO;
 using System.Linq;
+using System.Reflection;
 using System.Windows;
 using System.Windows.Input;
 using Newtonsoft.Json.Linq;
@@ -34,6 +35,8 @@ namespace PixiEditor.Views.Dialogs
 
         public bool RecentlyOpenedEmpty { get => (bool)GetValue(RecentlyOpenedEmptyProperty); set => SetValue(RecentlyOpenedEmptyProperty, value); }
 
+        public static string VersionText { get => $"v{Assembly.GetExecutingAssembly().GetName().Version.Major}.{Assembly.GetExecutingAssembly().GetName().Version.Minor}"; }
+
         public RelayCommand OpenFileCommand { get; set; }
 
         public RelayCommand OpenNewFileCommand { get; set; }
@@ -42,6 +45,8 @@ namespace PixiEditor.Views.Dialogs
 
         public RelayCommand OpenHyperlinkCommand { get => FileViewModel.Owner.MiscSubViewModel.OpenHyperlinkCommand; }
 
+        private bool isClosing;
+
         public HelloTherePopup(FileViewModel fileViewModel)
         {
             DataContext = this;
@@ -54,16 +59,18 @@ namespace PixiEditor.Views.Dialogs
             RecentlyOpenedEmpty = RecentlyOpened.Count == 0;
             RecentlyOpened.CollectionChanged += RecentlyOpened_CollectionChanged;
 
+            Closing += (_, _) => { isClosing = true; };
+
             InitializeComponent();
 
             if (RecentlyOpenedEmpty)
             {
-                Height = 450;
-                Width = 522;
+                Height = 500;
+                Width = 520;
             }
             else if (RecentlyOpened.Count < 7)
             {
-                Height = 656;
+                Height = 676;
                 Width = 545;
             }
         }
@@ -81,7 +88,10 @@ namespace PixiEditor.Views.Dialogs
         [Conditional("RELEASE")]
         private void CloseIfRelease()
         {
-            Close();
+            if (!isClosing)
+            {
+                Close();
+            }
         }
 
         private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)

+ 6 - 2
PixiEditor/Views/Dialogs/NoticePopup.xaml

@@ -22,9 +22,13 @@
         <i:Interaction.Behaviors>
             <behaviours:ClearFocusOnClickBehavior/>
         </i:Interaction.Behaviors>
-        <TextBlock Grid.Row="1" Text="{Binding Body, ElementName=popup}" HorizontalAlignment="Center"
-                   VerticalAlignment="Center" FontSize="18" Foreground="White" />
+        <TextBlock Grid.Row="1" Text="{Binding Body, ElementName=popup}" TextAlignment="Center"
+                   VerticalAlignment="Center" FontSize="18" Foreground="White"
+                       TextWrapping="WrapWithOverflow" TextTrimming="WordEllipsis" />
         <DockPanel Grid.Row="0" Background="{StaticResource MainColor}">
+            <TextBlock Text="{Binding Title, ElementName=popup}" 
+                       FontSize="18" Foreground="White"
+                       VerticalAlignment="Center" Margin="5,0,0,0"/>
             <Button DockPanel.Dock="Right" HorizontalAlignment="Right" Style="{StaticResource CloseButtonStyle}"
                     WindowChrome.IsHitTestVisibleInChrome="True" ToolTip="Close"
                     Command="{Binding DataContext.CancelCommand, ElementName=popup}" />

+ 6 - 0
PixiEditor/Views/Dialogs/NoticePopup.xaml.cs

@@ -22,6 +22,12 @@ namespace PixiEditor.Views.Dialogs
         public static readonly DependencyProperty BodyProperty =
             DependencyProperty.Register(nameof(Body), typeof(string), typeof(NoticePopup));
 
+        public new string Title
+        {
+            get => base.Title;
+            set => base.Title = value;
+        }
+
         public string Body
         {
             get => (string)GetValue(BodyProperty);

+ 1 - 1
PixiEditor/Views/MainWindow.xaml

@@ -450,7 +450,7 @@
         <StackPanel Margin="10,0,0,0" VerticalAlignment="Center" Grid.Row="3"
                        Grid.Column="3" Orientation="Horizontal">
             <Button Style="{StaticResource BaseDarkButton}" 
-                    Visibility="{Binding UpdateSubViewModel.UpdateReadyToInstall, Converter={StaticResource BoolToVisibilityConverter}}" FontSize="14" Height="20" 
+                    Visibility="{Binding UpdateSubViewModel.UpdateReadyToInstall, Converter={StaticResource BoolToVisibilityConverter}, FallbackValue=Hidden}" FontSize="14" Height="20" 
                     Command="{Binding UpdateSubViewModel.RestartApplicationCommand}">Restart</Button>
             <TextBlock VerticalAlignment="Center" Padding="10" HorizontalAlignment="Right"
                        Foreground="White" FontSize="14"  Text="{Binding UpdateSubViewModel.VersionText}" />

+ 0 - 69
PixiEditor/Views/MainWindow.xaml.cs

@@ -85,75 +85,6 @@ namespace PixiEditor
         private void MainWindow_Initialized(object sender, EventArgs e)
         {
             AppDomain.CurrentDomain.UnhandledException += (sender, e) => Helpers.CrashHelper.SaveCrashInfo((Exception)e.ExceptionObject);
-#if RELEASE
-            CheckForDownloadedUpdates();
-#endif
-        }
-
-        private void CheckForDownloadedUpdates()
-        {
-            string dir = AppDomain.CurrentDomain.BaseDirectory;
-            UpdateDownloader.CreateTempDirectory();
-            bool updateZipExists = Directory.GetFiles(UpdateDownloader.DownloadLocation, "update-*.zip").Length > 0;
-            string[] updateExeFiles = Directory.GetFiles(UpdateDownloader.DownloadLocation, "update-*.exe");
-            bool updateExeExists = updateExeFiles.Length > 0;
-
-            string updaterPath = Path.Join(dir, "PixiEditor.UpdateInstaller.exe");
-
-            if (updateZipExists || updateExeExists)
-            {
-                ViewModelMain.Current.UpdateSubViewModel.UpdateReadyToInstall = true;
-                var result = ConfirmationDialog.Show("Update is ready to install. Do you want to install it now?");
-                if (result == Models.Enums.ConfirmationType.Yes)
-                {
-                    if (updateZipExists && File.Exists(updaterPath))
-                    {
-                        InstallHeadless(updaterPath);
-                    }
-                    else if (updateExeExists)
-                    {
-                        OpenExeInstaller(updateExeFiles[0]);
-                    }
-                }
-            }
-        }
-
-        private void InstallHeadless(string updaterPath)
-        {
-            try
-            {
-                ProcessHelper.RunAsAdmin(updaterPath);
-                Close();
-            }
-            catch (Win32Exception)
-            {
-                MessageBox.Show(
-                    "Couldn't update without administrator rights.",
-                    "Insufficient permissions",
-                    MessageBoxButton.OK,
-                    MessageBoxImage.Error);
-            }
-        }
-
-        private void OpenExeInstaller(string updateExeFile)
-        {
-            bool alreadyUpdated = AssemblyHelper.GetCurrentAssemblyVersion() ==
-                    updateExeFile.Split('-')[1].Split(".exe")[0];
-
-            if (!alreadyUpdated)
-            {
-                RestartToUpdate(updateExeFile);
-            }
-            else
-            {
-                File.Delete(updateExeFile);
-            }
-        }
-
-        private void RestartToUpdate(string updateExeFile)
-        {
-            Process.Start(updateExeFile);
-            Close();
         }
     }
 }

+ 122 - 0
PixiEditor/Views/UserControls/AlignableWrapPanel.cs

@@ -0,0 +1,122 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace PixiEditor.Views.UserControls
+{
+    public class AlignableWrapPanel : Panel
+    {
+        public HorizontalAlignment HorizontalContentAlignment
+        {
+            get { return (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty); }
+            set { SetValue(HorizontalContentAlignmentProperty, value); }
+        }
+
+        public static readonly DependencyProperty HorizontalContentAlignmentProperty =
+            DependencyProperty.Register("HorizontalContentAlignment", typeof(HorizontalAlignment), typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(HorizontalAlignment.Left, FrameworkPropertyMetadataOptions.AffectsArrange));
+
+        protected override Size MeasureOverride(Size constraint)
+        {
+            Size curLineSize = default;
+            Size panelSize = default;
+
+            UIElementCollection children = InternalChildren;
+
+            for (int i = 0; i < children.Count; i++)
+            {
+                UIElement child = children[i];
+
+                // Flow passes its own constraint to children
+                child.Measure(constraint);
+                Size sz = child.DesiredSize;
+
+                if (curLineSize.Width + sz.Width > constraint.Width)
+                {
+                    panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
+                    panelSize.Height += curLineSize.Height;
+                    curLineSize = sz;
+
+                    if (sz.Width > constraint.Width)
+                    {
+                        panelSize.Width = Math.Max(sz.Width, panelSize.Width);
+                        panelSize.Height += sz.Height;
+                        curLineSize = default;
+                    }
+                }
+                else
+                {
+                    curLineSize.Width += sz.Width;
+                    curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
+                }
+            }
+
+            panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
+            panelSize.Height += curLineSize.Height;
+
+            return panelSize;
+        }
+
+        protected override Size ArrangeOverride(Size arrangeBounds)
+        {
+            int firstInLine = 0;
+            Size curLineSize = default;
+            double accumulatedHeight = 0;
+            UIElementCollection children = this.InternalChildren;
+
+            for (int i = 0; i < children.Count; i++)
+            {
+                Size sz = children[i].DesiredSize;
+
+                if (curLineSize.Width + sz.Width > arrangeBounds.Width)
+                {
+                    ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, i);
+
+                    accumulatedHeight += curLineSize.Height;
+                    curLineSize = sz;
+
+                    if (sz.Width > arrangeBounds.Width)
+                    {
+                        ArrangeLine(accumulatedHeight, sz, arrangeBounds.Width, i, ++i);
+                        accumulatedHeight += sz.Height;
+                        curLineSize = default;
+                    }
+
+                    firstInLine = i;
+                }
+                else
+                {
+                    curLineSize.Width += sz.Width;
+                    curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
+                }
+            }
+
+            if (firstInLine < children.Count)
+            {
+                ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, children.Count);
+            }
+
+            return arrangeBounds;
+        }
+
+        private void ArrangeLine(double y, Size lineSize, double boundsWidth, int start, int end)
+        {
+            double x = 0;
+            if (this.HorizontalContentAlignment == HorizontalAlignment.Center)
+            {
+                x = (boundsWidth - lineSize.Width) / 2;
+            }
+            else if (this.HorizontalContentAlignment == HorizontalAlignment.Right)
+            {
+                x = boundsWidth - lineSize.Width;
+            }
+
+            UIElementCollection children = InternalChildren;
+            for (int i = start; i < end; i++)
+            {
+                UIElement child = children[i];
+                child.Arrange(new Rect(x, y, child.DesiredSize.Width, lineSize.Height));
+                x += child.DesiredSize.Width;
+            }
+        }
+    }
+}