Browse Source

New from clipboard button

flabbet 1 year ago
parent
commit
58c3b895d9

+ 2 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -727,5 +727,6 @@
   "TOGGLE_ONION_SKINNING": "Toggle onion skinning",
   "CHANGE_ACTIVE_FRAME_PREVIOUS": "Change active frame to previous",
   "CHANGE_ACTIVE_FRAME_NEXT": "Change active frame to next",
-  "TOGGLE_ANIMATION": "Toggle animation"
+  "TOGGLE_ANIMATION": "Toggle animation",
+  "NEW_FROM_CLIPBOARD": "New from clipboard"
 }

+ 74 - 49
src/PixiEditor/Models/Controllers/ClipboardController.cs

@@ -32,13 +32,14 @@ namespace PixiEditor.Models.Controllers;
 internal static class ClipboardController
 {
     public static IClipboard Clipboard { get; private set; }
+
     private const string PositionFormat = "PixiEditor.Position";
 
     public static void Initialize(IClipboard clipboard)
     {
         Clipboard = clipboard;
     }
-    
+
     public static readonly string TempCopyFilePath = Path.Join(
         Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
         "PixiEditor",
@@ -74,7 +75,7 @@ internal static class ClipboardController
             Directory.CreateDirectory(Path.GetDirectoryName(TempCopyFilePath)!);
             await using FileStream fileStream = new FileStream(TempCopyFilePath, FileMode.Create, FileAccess.Write);
             await pngStream.CopyToAsync(fileStream);
-            data.SetFileDropList(new [] { TempCopyFilePath });
+            data.SetFileDropList(new[] { TempCopyFilePath });
         }
 
         WriteableBitmap finalBitmap = actuallySurface.ToWriteableBitmap();
@@ -92,7 +93,7 @@ internal static class ClipboardController
     /// <summary>
     ///     Pastes image from clipboard into new layer.
     /// </summary>
-    public static bool TryPaste(DocumentViewModel document, IDataObject data, bool pasteAsNew = false)
+    public static bool TryPaste(DocumentViewModel document, IEnumerable<IDataObject> data, bool pasteAsNew = false)
     {
         List<DataImage> images = GetImage(data);
         if (images.Count == 0)
@@ -137,7 +138,6 @@ internal static class ClipboardController
     /// </summary>
     public static async Task<bool> TryPasteFromClipboard(DocumentViewModel document, bool pasteAsNew = false)
     {
-        //TODO: maybe if we have access to more formats, we can check them as well
         var data = await TryGetDataObject();
         if (data == null)
             return false;
@@ -145,21 +145,29 @@ internal static class ClipboardController
         return TryPaste(document, data, pasteAsNew);
     }
 
-    private static async Task<DataObject?> TryGetDataObject()
+    private static async Task<List<DataObject?>> TryGetDataObject()
     {
         string[] formats = await Clipboard.GetFormatsAsync();
         if (formats.Length == 0)
             return null;
 
-        string format = formats[0];
-        var obj = await Clipboard.GetDataAsync(format);
+        List<DataObject?> dataObjects = new();
 
-        if (obj == null)
-            return null;
+        for (int i = 0; i < formats.Length; i++)
+        {
+            string format = formats[i];
+            var obj = await Clipboard.GetDataAsync(format);
 
-        DataObject data = new DataObject();
-        data.Set(format, obj);
-        return data;
+            if (obj == null)
+                continue;
+
+            DataObject data = new DataObject();
+            data.Set(format, obj);
+            
+            dataObjects.Add(data);
+        }
+
+        return dataObjects;
     }
 
     public static async Task<List<DataImage>> GetImagesFromClipboard()
@@ -171,55 +179,58 @@ internal static class ClipboardController
     /// <summary>
     /// Gets images from clipboard, supported PNG, Dib and Bitmap.
     /// </summary>
-    public static List<DataImage> GetImage(IDataObject? data)
+    public static List<DataImage> GetImage(IEnumerable<IDataObject?> data)
     {
         List<DataImage> surfaces = new();
 
         if (data == null)
             return surfaces;
 
-        if (TryExtractSingleImage(data, out var singleImage))
+        foreach (var dataObject in data)
         {
-            surfaces.Add(new DataImage(singleImage, data.GetVecI(PositionFormat)));
-            return surfaces;
-        }
-
-        var paths = data.GetFileDropList().Select(x => x.Path.LocalPath).ToList();
-        if(paths != null && data.TryGetRawTextPath(out string? textPath))
-        {
-            paths.Add(textPath);
-        }
+            if (TryExtractSingleImage(dataObject, out var singleImage))
+            {
+                surfaces.Add(new DataImage(singleImage, dataObject.GetVecI(PositionFormat)));
+                continue;
+            }
 
-        if (paths == null || paths.Count == 0)
-        {
-            return surfaces;
-        }
+            var paths = dataObject.GetFileDropList().Select(x => x.Path.LocalPath).ToList();
+            if (paths != null && dataObject.TryGetRawTextPath(out string? textPath))
+            {
+                paths.Add(textPath);
+            }
 
-        foreach (string? path in paths)
-        {
-            if (path is null || !Importer.IsSupportedFile(path))
-                continue;
-            try
+            if (paths == null || paths.Count == 0)
             {
-                Surface imported;
+                continue;
+            }
 
-                if (Path.GetExtension(path) == ".pixi")
+            foreach (string? path in paths)
+            {
+                if (path is null || !Importer.IsSupportedFile(path))
+                    continue;
+                try
                 {
-                    using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
+                    Surface imported;
+
+                    if (Path.GetExtension(path) == ".pixi")
+                    {
+                        using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
+
+                        imported = Surface.Load(PixiParser.ReadPreview(stream));
+                    }
+                    else
+                    {
+                        imported = Surface.Load(path);
+                    }
 
-                    imported = Surface.Load(PixiParser.ReadPreview(stream));
+                    string filename = Path.GetFullPath(path);
+                    surfaces.Add(new DataImage(filename, imported, dataObject.GetVecI(PositionFormat)));
                 }
-                else
+                catch
                 {
-                    imported = Surface.Load(path);
+                    continue;
                 }
-
-                string filename = Path.GetFullPath(path);
-                surfaces.Add(new DataImage(filename, imported, data.GetVecI(PositionFormat)));
-            }
-            catch
-            {
-                continue;
             }
         }
 
@@ -238,7 +249,7 @@ internal static class ClipboardController
         if (!isImage)
         {
             string path = await TryFindImageInFiles(formats);
-            return path != string.Empty;
+            return Path.Exists(path);
         }
 
         return isImage;
@@ -256,6 +267,20 @@ internal static class ClipboardController
                     return text;
                 }
             }
+            else if (format == DataFormats.Files)
+            {
+                var files = await Clipboard.GetDataAsync(format);
+                if (files is IEnumerable<IStorageItem> storageFiles)
+                {
+                    foreach (var file in storageFiles)
+                    {
+                        if (Importer.IsSupportedFile(file.Path.LocalPath))
+                        {
+                            return file.Path.LocalPath;
+                        }
+                    }
+                }
+            }
         }
 
         return string.Empty;
@@ -301,7 +326,7 @@ internal static class ClipboardController
     private static Bitmap FromPNG(IDataObject data)
     {
         object obj = data.Get("PNG");
-        if(obj is byte[] bytes)
+        if (obj is byte[] bytes)
         {
             using MemoryStream stream = new MemoryStream(bytes);
             return new Bitmap(stream);
@@ -316,7 +341,7 @@ internal static class ClipboardController
     }
 
     private static bool HasData(IDataObject dataObject, params string[] formats) => formats.Any(dataObject.Contains);
-    
+
     private static bool TryExtractSingleImage(IDataObject data, [NotNullWhen(true)] out Surface? result)
     {
         try
@@ -328,7 +353,7 @@ internal static class ClipboardController
             }
             else if (HasData(data, ClipboardDataFormats.Dib, ClipboardDataFormats.Bitmap))
             {
-                var imgs = GetImage(data);
+                var imgs = GetImage(new [] { data });
                 if (imgs == null || imgs.Count == 0)
                 {
                     result = null;

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/ClipboardViewModel.cs

@@ -60,7 +60,7 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
 
-        DataImage imageData = (data == null ? await ClipboardController.GetImagesFromClipboard() : ClipboardController.GetImage(data)).First();
+        DataImage imageData = (data == null ? await ClipboardController.GetImagesFromClipboard() : ClipboardController.GetImage(new [] { data })).First();
         using var surface = imageData.image;
 
         var bitmap = imageData.image.ToWriteableBitmap();

+ 1 - 1
src/PixiEditor/Views/Layers/LayersManager.axaml.cs

@@ -137,7 +137,7 @@ internal partial class LayersManager : UserControl
             e.Handled = true;
         }
 
-        if (ClipboardController.TryPaste(ActiveDocument, (IDataObject)e.Data, true))
+        if (ClipboardController.TryPaste(ActiveDocument, new [] { (IDataObject)e.Data }, true))
         {
             e.Handled = true;
         }

+ 3 - 0
src/PixiEditor/Views/Windows/HelloTherePopup.axaml

@@ -78,6 +78,9 @@
                                 ui:Translator.Key="OPEN_FILE" />
                         <Button Command="{Binding OpenNewFileCommand}" MinWidth="150" Margin="10"
                                 ui:Translator.Key="NEW_FILE" />
+                        <Button Classes="pixi-icon" Content="{DynamicResource icon-paste-as-new-layer}"
+                                Command="{Binding NewFromClipboardCommand}"
+                                ui:Translator.TooltipKey="NEW_FROM_CLIPBOARD"/>
                     </StackPanel>
 
                     <StackPanel Grid.Row="2" HorizontalAlignment="Center" Margin="0,30,0,0">

+ 44 - 9
src/PixiEditor/Views/Windows/HelloTherePopup.axaml.cs

@@ -9,6 +9,7 @@ using CommunityToolkit.Mvvm.Input;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Extensions.CommonApi.UserPreferences.Settings.PixiEditor;
 using PixiEditor.Helpers;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Services.NewsFeed;
 using PixiEditor.Models.Structures;
 using PixiEditor.Models.UserData;
@@ -24,7 +25,7 @@ namespace PixiEditor.Views.Windows;
 internal partial class HelloTherePopup : PixiEditorPopup
 {
     public RecentlyOpenedCollection RecentlyOpened { get => FileViewModel.RecentlyOpened; }
-    
+
     public static readonly StyledProperty<FileViewModel> FileViewModelProperty =
         AvaloniaProperty.Register<HelloTherePopup, FileViewModel>(nameof(FileViewModel));
 
@@ -53,12 +54,13 @@ internal partial class HelloTherePopup : PixiEditorPopup
         get { return (bool)GetValue(NewsPanelCollapsedProperty); }
         set { SetValue(NewsPanelCollapsedProperty, value); }
     }
+
     public bool IsFetchingNews
     {
         get { return (bool)GetValue(IsFetchingNewsProperty); }
         set { SetValue(IsFetchingNewsProperty, value); }
     }
-    
+
     public bool ShowAllBetaExamples
     {
         get => GetValue(ShowAllBetaExamplesProperty);
@@ -76,16 +78,26 @@ internal partial class HelloTherePopup : PixiEditorPopup
     public static string VersionText =>
         $"v{VersionHelpers.GetCurrentAssemblyVersionString()}";
 
-    public FileViewModel FileViewModel { get => (FileViewModel)GetValue(FileViewModelProperty); set => SetValue(FileViewModelProperty, value); }
+    public FileViewModel FileViewModel
+    {
+        get => (FileViewModel)GetValue(FileViewModelProperty);
+        set => SetValue(FileViewModelProperty, value);
+    }
 
-    public bool RecentlyOpenedEmpty { get => (bool)GetValue(RecentlyOpenedEmptyProperty); set => SetValue(RecentlyOpenedEmptyProperty, value); }
+    public bool RecentlyOpenedEmpty
+    {
+        get => (bool)GetValue(RecentlyOpenedEmptyProperty);
+        set => SetValue(RecentlyOpenedEmptyProperty, value);
+    }
 
     public AsyncRelayCommand OpenFileCommand { get; set; }
 
     public AsyncRelayCommand OpenNewFileCommand { get; set; }
 
+    public AsyncRelayCommand NewFromClipboardCommand { get; set; }
+
     public RelayCommand<string> OpenRecentCommand { get; set; }
-    
+
     public RelayCommand<string> OpenInExplorerCommand { get; set; }
 
     public RelayCommand<bool> SetShowAllBetaExamplesCommand { get; set; }
@@ -115,6 +127,7 @@ internal partial class HelloTherePopup : PixiEditorPopup
         OpenRecentCommand = new RelayCommand<string>(OpenRecent);
         SetShowAllBetaExamplesCommand = new RelayCommand<bool>(SetShowAllBetaExamples);
         OpenInExplorerCommand = new RelayCommand<string>(OpenInExplorer, CanOpenInExplorer);
+        NewFromClipboardCommand = new AsyncRelayCommand(NewFromClipboard, CanOpenFromClipboard);
 
         RecentlyOpenedEmpty = RecentlyOpened.Count == 0;
         RecentlyOpened.CollectionChanged += RecentlyOpened_CollectionChanged;
@@ -125,6 +138,8 @@ internal partial class HelloTherePopup : PixiEditorPopup
 
         Closing += (_, _) => { IsClosing = true; };
 
+        Activated += RefreshClipboardImg;
+
         InitializeComponent();
 
         int newsWidth = 300;
@@ -163,7 +178,7 @@ internal partial class HelloTherePopup : PixiEditorPopup
     {
         HelloTherePopup helloTherePopup = (HelloTherePopup)e.Sender;
 
-        if(helloTherePopup._newsDisabled)
+        if (helloTherePopup._newsDisabled)
             return;
 
         if (e.NewValue.Value)
@@ -178,7 +193,8 @@ internal partial class HelloTherePopup : PixiEditorPopup
         PixiEditorSettings.StartupWindow.NewsPanelCollapsed.Value = e.NewValue.Value;
     }
 
-    private void RecentlyOpened_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+    private void RecentlyOpened_CollectionChanged(object sender,
+        System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
     {
         RecentlyOpenedEmpty = FileViewModel.RecentlyOpened.Count == 0;
     }
@@ -197,6 +213,25 @@ internal partial class HelloTherePopup : PixiEditorPopup
         await FileViewModel.CreateFromNewFileDialog();
     }
 
+    private void RefreshClipboardImg(object? sender, EventArgs e)
+    {
+        NewFromClipboardCommand.NotifyCanExecuteChanged();
+    }
+
+
+    private async Task NewFromClipboard()
+    {
+        Activated -= RefreshClipboardImg;
+        Application.Current.ForDesktopMainWindow(mainWindow => mainWindow.Activate());
+        Close();
+        await FileViewModel.OpenFromClipboard();
+    }
+
+    private bool CanOpenFromClipboard()
+    {
+        return ClipboardController.IsImageInClipboard().Result;
+    }
+
     private void OpenRecent(string parameter)
     {
         Application.Current.ForDesktopMainWindow(mainWindow => mainWindow.Activate());
@@ -214,7 +249,7 @@ internal partial class HelloTherePopup : PixiEditorPopup
     private async void HelloTherePopup_OnLoaded(object sender, RoutedEventArgs e)
     {
         // TODO: Fetching news freezes input when no internet is present
-        if(_newsDisabled) return;
+        if (_newsDisabled) return;
 
         try
         {
@@ -236,7 +271,7 @@ internal partial class HelloTherePopup : PixiEditorPopup
                 FailedFetchingNews = true;
             }
         }
-        catch(Exception ex)
+        catch (Exception ex)
         {
             IsFetchingNews = false;
             FailedFetchingNews = true;