소스 검색

Merge pull request #1084 from PixiEditor/linux-clipboard

Improved clipboard
Krzysztof Krysiński 3 주 전
부모
커밋
39501e31fe

+ 1 - 1
src/ColorPicker

@@ -1 +1 @@
-Subproject commit bf83dee60fda714c94024212948a1ff5ed40d438
+Subproject commit 943e9abbb60b73c4965b947e987dc2696e0b08f8

+ 1 - 1
src/Directory.Build.props

@@ -1,7 +1,7 @@
 <Project>
     <PropertyGroup>
         <CodeAnalysisRuleSet>../Custom.ruleset</CodeAnalysisRuleSet>
-		    <AvaloniaVersion>11.3.3</AvaloniaVersion>
+		    <AvaloniaVersion>11.3.0</AvaloniaVersion>
     </PropertyGroup>
     <ItemGroup>
         <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit bd34e239887d69235d18f0f95abdf80f94534b5d
+Subproject commit 1be85ac9f4bc6b584e6a3a5a3d0287201c6a5f03

+ 1 - 1
src/PixiDocks

@@ -1 +1 @@
-Subproject commit cb36e5ed4b61b99d52461e353d009ef52cb7d97b
+Subproject commit 6e745d0309ad7a00a53f62f2aa362be77903a5fd

+ 6 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/FuncInputProperty.cs

@@ -65,10 +65,15 @@ public class FuncInputProperty<T> : InputProperty<Func<FuncContext, T>>, IFuncIn
                 else if (sourceObj is Expression expression)
                 {
                     ShaderExpressionVariable shaderExpressionVariable = (ShaderExpressionVariable)toReturn;
-                    shaderExpressionVariable.OverrideExpression = Adjust(expression, toReturn, out var adjustNested);
+                    var adjusted = Adjust(expression, toReturn, out var adjustNested);
                     if (adjustNested)
                     {
                         AdjustNested(((IMultiValueVariable)toReturn), expression);
+                        shaderExpressionVariable.OverrideExpression = ((IMultiValueVariable)toReturn).GetWholeNestedExpression();
+                    }
+                    else
+                    {
+                        shaderExpressionVariable.OverrideExpression = adjusted;
                     }
                 }
 
@@ -91,7 +96,6 @@ public class FuncInputProperty<T> : InputProperty<Func<FuncContext, T>>, IFuncIn
         if (toReturn is IMultiValueVariable)
         {
             adjustNestedVariables = true;
-            return expression;
         }
 
         return expression;

+ 2 - 2
src/PixiEditor/Helpers/Constants/ClipboardDataFormats.cs

@@ -2,12 +2,12 @@
 
 public static class ClipboardDataFormats
 {
-    public const string Png = "PNG";
+    public static readonly string[] PngFormats = [ "PNG", "image/png", "public.png" ];
     public const string LayerIdList = "PixiEditor.LayerIdList";
     public const string PositionFormat = "PixiEditor.Position";
-    public const string ImageSlashPng = "image/png";
     public const string DocumentFormat = "PixiEditor.Document";
     public const string NodeIdList = "PixiEditor.NodeIdList";
     public const string CelIdList = "PixiEditor.CelIdList";
     public const string PixiVectorData = "PixiEditor.VectorData";
+    public const string UriList = "text/uri-list";
 }

+ 13 - 5
src/PixiEditor/Helpers/Extensions/DataObjectExtensions.cs

@@ -37,14 +37,22 @@ public static class DataObjectExtensions
             return false;
         }
 
-        string text = data.GetText();
-
-        if (Directory.Exists(text) || File.Exists(text))
+        try
+        {
+            var text = data.GetText();
+            if (Directory.Exists(text) || File.Exists(text))
+            {
+                path = text;
+                return true;
+            }
+        }
+        catch(InvalidCastException ex) // bug on x11
         {
-            path = text;
-            return true;
+            path = null;
+            return false;
         }
 
+
         path = null;
         return false;
     }

+ 12 - 2
src/PixiEditor/Models/Commands/XAML/NativeMenu.cs

@@ -65,9 +65,19 @@ internal class NativeMenu : global::Avalonia.Controls.Menu
         {
             if (!ShortcutController.ShortcutExecutionBlocked)
             {
-                if (iCommand.CanExecute(parameter))
+                if (command?.Shortcut != null && command.Shortcut.Gesture != null && command.Shortcut.Gesture.Key != Key.None || command.Shortcut.Gesture.KeyModifiers != KeyModifiers.None)
                 {
-                    iCommand.Execute(parameter);
+                     ViewModelMain.Current.ShortcutController.KeyPressed(
+                         false,
+                         command.Shortcut.Key,
+                         command.Shortcut.Modifiers);
+                }
+                else
+                {
+                    if (iCommand.CanExecute(parameter))
+                    {
+                        iCommand.Execute(parameter);
+                    }
                 }
             }
             else

+ 227 - 81
src/PixiEditor/Models/Controllers/ClipboardController.cs

@@ -86,7 +86,8 @@ internal static class ClipboardController
         }
         else if (document.TransformViewModel.TransformActive || lastTransform != null)
         {
-            RectD transform = document.TransformViewModel.TransformActive ? document.TransformViewModel.Corners.AABBBounds
+            RectD transform = document.TransformViewModel.TransformActive
+                ? document.TransformViewModel.Corners.AABBBounds
                 : lastTransform.Value;
             var surface =
                 document.TryExtractAreaFromSelected(
@@ -149,7 +150,7 @@ internal static class ClipboardController
             copyArea = document.TransformViewModel.Corners.AABBBounds;
         }
 
-        if(copyArea.IsZeroOrNegativeArea || copyArea.HasNaNOrInfinity)
+        if (copyArea.IsZeroOrNegativeArea || copyArea.HasNaNOrInfinity)
         {
             NoticeDialog.Show("SELECTED_AREA_EMPTY", "NOTHING_TO_COPY");
             return;
@@ -185,8 +186,13 @@ internal static class ClipboardController
             await pngData.AsStream().CopyToAsync(pngStream);
 
             var pngArray = pngStream.ToArray();
-            data.Set(ClipboardDataFormats.Png, pngArray);
-            data.Set(ClipboardDataFormats.ImageSlashPng, pngArray);
+            foreach (string format in ClipboardDataFormats.PngFormats)
+            {
+                if (!data.Contains(format))
+                {
+                    data.Set(format, pngArray);
+                }
+            }
 
             pngStream.Position = 0;
             try
@@ -221,17 +227,19 @@ internal static class ClipboardController
     /// <summary>
     ///     Pastes image from clipboard into new layer.
     /// </summary>
-    public static bool TryPaste(DocumentViewModel document, DocumentManagerViewModel manager, IEnumerable<IDataObject>
-        data, bool pasteAsNew = false)
+    public static async Task<bool> TryPaste(DocumentViewModel document, DocumentManagerViewModel manager,
+        IImportObject[]
+            data, bool pasteAsNew = false)
     {
-        Guid sourceDocument = GetSourceDocument(data, document.Id);
-        Guid[] layerIds = GetLayerIds(data);
+        Guid sourceDocument = await GetSourceDocument(data, document.Id);
+        Guid[] layerIds = await GetLayerIds(data);
 
         bool hasPos = data.Any(x => x.Contains(ClipboardDataFormats.PositionFormat));
 
         IDocument? targetDoc = manager.Documents.FirstOrDefault(x => x.Id == sourceDocument);
 
-        if (targetDoc != null && pasteAsNew && layerIds is { Length: > 0 } && (!hasPos || AllMatchesPos(layerIds, data, targetDoc)))
+        if (targetDoc != null && pasteAsNew && layerIds is { Length: > 0 } &&
+            (!hasPos || await AllMatchesPos(layerIds, data, targetDoc)))
         {
             foreach (var layerId in layerIds)
             {
@@ -250,7 +258,7 @@ internal static class ClipboardController
             return true;
         }
 
-        List<DataImage> images = GetImage(data);
+        List<DataImage> images = await GetImage(data);
         if (images.Count == 0)
             return false;
 
@@ -291,16 +299,14 @@ internal static class ClipboardController
         return true;
     }
 
-    private static bool AllMatchesPos(Guid[] layerIds, IEnumerable<IDataObject> data, IDocument doc)
+    private static async Task<bool> AllMatchesPos(Guid[] layerIds, IImportObject[] dataFormats, IDocument doc)
     {
-        var dataObjects = data as IDataObject[] ?? data.ToArray();
-
-        var dataObjectWithPos = dataObjects.FirstOrDefault(x => x.Contains(ClipboardDataFormats.PositionFormat));
+        var dataObjectWithPos = dataFormats.FirstOrDefault(x => x.Contains(ClipboardDataFormats.PositionFormat));
         VecD pos = VecD.Zero;
 
         if (dataObjectWithPos != null)
         {
-            pos = dataObjectWithPos.GetVecD(ClipboardDataFormats.PositionFormat);
+            pos = await GetVecD(ClipboardDataFormats.PositionFormat, dataFormats);
         }
 
         RectD? tightBounds = null;
@@ -325,13 +331,13 @@ internal static class ClipboardController
         return tightBounds.HasValue && tightBounds.Value.Pos.AlmostEquals(pos);
     }
 
-    private static Guid[] GetLayerIds(IEnumerable<IDataObject> data)
+    private static async Task<Guid[]> GetLayerIds(IImportObject[] formats)
     {
-        foreach (var dataObject in data)
+        foreach (var dataObject in formats)
         {
             if (dataObject.Contains(ClipboardDataFormats.LayerIdList))
             {
-                byte[] layerIds = (byte[])dataObject.Get(ClipboardDataFormats.LayerIdList);
+                byte[] layerIds = await Clipboard.GetDataAsync(ClipboardDataFormats.LayerIdList) as byte[];
                 string layerIdsString = System.Text.Encoding.UTF8.GetString(layerIds);
                 return layerIdsString.Split(';').Select(Guid.Parse).ToArray();
             }
@@ -340,13 +346,14 @@ internal static class ClipboardController
         return [];
     }
 
-    private static Guid GetSourceDocument(IEnumerable<IDataObject> data, Guid fallback)
+    private static async Task<Guid> GetSourceDocument(IImportObject[] formats, Guid fallback)
     {
-        foreach (var dataObject in data)
+        foreach (var dataObject in formats)
         {
             if (dataObject.Contains(ClipboardDataFormats.DocumentFormat))
             {
-                byte[] guidBytes = (byte[])dataObject.Get(ClipboardDataFormats.DocumentFormat);
+                var data = await Clipboard.GetDataAsync(ClipboardDataFormats.DocumentFormat);
+                byte[] guidBytes = (byte[])data;
                 string guidString = System.Text.Encoding.UTF8.GetString(guidBytes);
                 return Guid.Parse(guidString);
             }
@@ -361,73 +368,68 @@ internal static class ClipboardController
     public static async Task<bool> TryPasteFromClipboard(DocumentViewModel document, DocumentManagerViewModel manager,
         bool pasteAsNew = false)
     {
-        var data = await TryGetDataObject();
+        var data = await TryGetImportObjects();
         if (data == null)
             return false;
 
-        return TryPaste(document, manager, data, pasteAsNew);
+        return await TryPaste(document, manager, data, pasteAsNew);
     }
 
-    private static async Task<List<DataObject?>> TryGetDataObject()
+    private static async Task<ClipboardPromiseObject[]> TryGetImportObjects()
     {
         string[] formats = await Clipboard.GetFormatsAsync();
         if (formats.Length == 0)
             return null;
 
-        List<DataObject?> dataObjects = new();
+        List<ClipboardPromiseObject?> dataObjects = new();
 
         for (int i = 0; i < formats.Length; i++)
         {
             string format = formats[i];
-            var obj = await Clipboard.GetDataAsync(format);
-
-            if (obj == null)
-                continue;
-
-            DataObject data = new DataObject();
-            data.Set(format, obj);
-
-            dataObjects.Add(data);
+            dataObjects.Add(new ClipboardPromiseObject(format, Clipboard));
         }
 
-        return dataObjects;
+        return dataObjects.ToArray();
     }
 
     public static async Task<List<DataImage>> GetImagesFromClipboard()
     {
-        var dataObj = await TryGetDataObject();
-        return GetImage(dataObj);
+        var dataObj = await TryGetImportObjects();
+        return await GetImage(dataObj);
     }
 
     /// <summary>
     /// Gets images from clipboard, supported PNG and Bitmap.
     /// </summary>
-    public static List<DataImage> GetImage(IEnumerable<IDataObject?> data)
+    public static async Task<List<DataImage>> GetImage(IImportObject[] importableObjects)
     {
         List<DataImage> surfaces = new();
 
-        if (data == null)
+        if (importableObjects == null)
             return surfaces;
 
         VecD pos = VecD.Zero;
 
         string? importingType = null;
+        bool pngImported = false;
 
-        foreach (var dataObject in data)
+        foreach (var dataObject in importableObjects)
         {
-            if (importingType is null or "bytes" && TryExtractSingleImage(dataObject, out var singleImage))
+            var img = await TryExtractSingleImage(dataObject);
+            if (importingType is null or "bytes" && img != null && !pngImported)
             {
-                surfaces.Add(new DataImage(singleImage,
+                surfaces.Add(new DataImage(img,
                     dataObject.Contains(ClipboardDataFormats.PositionFormat)
-                        ? (VecI)dataObject.GetVecD(ClipboardDataFormats.PositionFormat)
+                        ? (VecI)await GetVecD(ClipboardDataFormats.PositionFormat, importableObjects)
                         : (VecI)pos));
                 importingType = "bytes";
+                pngImported = true;
                 continue;
             }
 
             if (dataObject.Contains(ClipboardDataFormats.PositionFormat))
             {
-                pos = dataObject.GetVecD(ClipboardDataFormats.PositionFormat);
+                pos = await GetVecD(ClipboardDataFormats.PositionFormat, importableObjects);
                 for (var i = 0; i < surfaces.Count; i++)
                 {
                     var surface = surfaces[i];
@@ -435,10 +437,11 @@ internal static class ClipboardController
                 }
             }
 
-            var paths = dataObject.GetFileDropList().Select(x => x.Path.LocalPath).ToList();
-            if (paths != null && dataObject.TryGetRawTextPath(out string? textPath))
+            var paths = (await GetFileDropList(dataObject))?.Select(x => x.Path.LocalPath).ToList();
+            string[]? rawPaths = await TryGetRawTextPaths(dataObject);
+            if (paths != null && rawPaths != null)
             {
-                paths.Add(textPath);
+                paths.AddRange(rawPaths);
             }
 
             if (paths == null || paths.Count == 0 || (importingType != null && importingType != "files"))
@@ -467,7 +470,7 @@ internal static class ClipboardController
 
                     string filename = Path.GetFullPath(path);
                     surfaces.Add(new DataImage(filename, imported,
-                        (VecI)dataObject.GetVecD(ClipboardDataFormats.PositionFormat)));
+                        (VecI)await GetVecD(ClipboardDataFormats.PositionFormat, importableObjects)));
                     importingType = "files";
                 }
                 catch
@@ -480,6 +483,104 @@ internal static class ClipboardController
         return surfaces;
     }
 
+    private static async Task<string[]?> TryGetRawTextPaths(IImportObject importObj)
+    {
+        if (!importObj.Contains(DataFormats.Text) && !importObj.Contains(ClipboardDataFormats.UriList))
+        {
+            return null;
+        }
+
+        string text = null;
+        try
+        {
+            text = await importObj.GetDataAsync(DataFormats.Text) as string;
+        }
+        catch(InvalidCastException ex) // bug on x11
+        {
+        }
+
+        string[] paths = [text];
+        if (text == null)
+        {
+            if (await importObj.GetDataAsync(ClipboardDataFormats.UriList) is byte[] bytes)
+            {
+                paths = Encoding.UTF8.GetString(bytes).Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries);
+            }
+        }
+
+        if (paths.Length == 0)
+        {
+            return null;
+        }
+
+        List<string> validPaths = new();
+
+        foreach (string path in paths)
+        {
+            if (string.IsNullOrWhiteSpace(path))
+                continue;
+
+            if (Directory.Exists(path) || File.Exists(path))
+            {
+                validPaths.Add(path);
+            }
+            else
+            {
+                try
+                {
+                    Uri uri = new Uri(path);
+                    if (uri.IsAbsoluteUri && (Directory.Exists(uri.LocalPath) || File.Exists(uri.LocalPath)))
+                    {
+                        validPaths.Add(uri.LocalPath);
+                    }
+                }
+                catch (UriFormatException)
+                {
+                    // Ignore invalid URIs
+                }
+            }
+        }
+
+        return validPaths.Count > 0 ? validPaths.ToArray() : null;
+    }
+
+    private static async Task<IEnumerable<IStorageItem>> GetFileDropList(IImportObject obj)
+    {
+        if (!obj.Contains(DataFormats.Files))
+            return [];
+
+        var data = await obj.GetDataAsync(DataFormats.Files);
+        if (data == null)
+            return [];
+
+        if (data is IEnumerable<IStorageItem> storageItems)
+            return storageItems;
+
+        if (data is Task<object> task)
+        {
+            data = await task;
+            if (data is IEnumerable<IStorageItem> storageItemsFromTask)
+                return storageItemsFromTask;
+        }
+
+        return [];
+    }
+
+
+    private static async Task<VecD> GetVecD(string format, IImportObject[] availableFormats)
+    {
+        var firstFormat = availableFormats.FirstOrDefault(x => x.Contains(format));
+        if (firstFormat == null)
+            return new VecD(-1, -1);
+
+        byte[] bytes = (byte[])await firstFormat.GetDataAsync(format);
+
+        if (bytes is { Length: < 16 })
+            return new VecD(-1, -1);
+
+        return VecD.FromBytes(bytes);
+    }
+
     public static bool IsImage(IDataObject? dataObject)
     {
         if (dataObject == null)
@@ -501,7 +602,7 @@ internal static class ClipboardController
             return false;
         }
 
-        return HasData(dataObject, ClipboardDataFormats.Png, ClipboardDataFormats.ImageSlashPng);
+        return HasData(dataObject, ClipboardDataFormats.PngFormats);
     }
 
     public static async Task<bool> IsImageInClipboard()
@@ -515,7 +616,20 @@ internal static class ClipboardController
         if (!isImage)
         {
             string path = await TryFindImageInFiles(formats);
-            return Path.Exists(path);
+            try
+            {
+                Uri uri = new Uri(path);
+                return Path.Exists(uri.LocalPath);
+            }
+            catch (UriFormatException)
+            {
+                return false;
+            }
+            catch (Exception ex)
+            {
+                CrashHelper.SendExceptionInfo(ex);
+                return false;
+            }
         }
 
         return isImage;
@@ -525,15 +639,7 @@ internal static class ClipboardController
     {
         foreach (string format in formats)
         {
-            if (format == DataFormats.Text)
-            {
-                string text = await ClipboardController.GetTextFromClipboard();
-                if (Importer.IsSupportedFile(text))
-                {
-                    return text;
-                }
-            }
-            else if (format == DataFormats.Files)
+            if (format == DataFormats.Files || format == ClipboardDataFormats.UriList)
             {
                 var files = await ClipboardController.Clipboard.GetDataAsync(format);
                 if (files is IEnumerable<IStorageItem> storageFiles)
@@ -553,6 +659,28 @@ internal static class ClipboardController
                         }
                     }
                 }
+
+                if (files is byte[] bytes)
+                {
+                    string utf8String = Encoding.UTF8.GetString(bytes);
+                    string[] paths = utf8String.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries);
+                    foreach (string path in paths)
+                    {
+                        if (Importer.IsSupportedFile(path))
+                        {
+                            return path;
+                        }
+                    }
+                }
+
+                if (format == DataFormats.Text)
+                {
+                    string text = await ClipboardController.GetTextFromClipboard();
+                    if (Importer.IsSupportedFile(text))
+                    {
+                        return text;
+                    }
+                }
             }
         }
 
@@ -563,7 +691,7 @@ internal static class ClipboardController
     {
         foreach (var format in formats)
         {
-            if (format == ClipboardDataFormats.Png)
+            if (ClipboardDataFormats.PngFormats.Contains(format, StringComparer.OrdinalIgnoreCase))
             {
                 return true;
             }
@@ -577,38 +705,55 @@ internal static class ClipboardController
         return false;
     }
 
-    private static Surface FromPNG(IDataObject data)
+    private static async Task<Surface?> FromPNG(IImportObject importObj)
     {
-        object obj = data.Get("PNG");
-        if (obj is byte[] bytes)
+        object? pngData = null;
+        foreach (string format in ClipboardDataFormats.PngFormats)
+        {
+            if (importObj.Contains(format))
+            {
+                object? data = await importObj.GetDataAsync(format);
+                if (data == null)
+                    continue;
+
+                pngData = data;
+                break;
+            }
+        }
+
+        if (pngData is byte[] bytes)
         {
             return Surface.Load(bytes);
         }
 
-        if (obj is MemoryStream memoryStream)
+        if (pngData is MemoryStream memoryStream)
         {
             bytes = memoryStream.ToArray();
             return Surface.Load(bytes);
         }
 
-        throw new InvalidDataException("PNG data is not in a supported format.");
+        return null;
     }
 
     private static bool HasData(IDataObject dataObject, params string[] formats) => formats.Any(dataObject.Contains);
 
-    private static bool TryExtractSingleImage(IDataObject data, [NotNullWhen(true)] out Surface? result)
+    private static async Task<Surface?> TryExtractSingleImage(IImportObject importedObj)
     {
         try
         {
             Surface source;
-            if (data.Contains(ClipboardDataFormats.Png) || data.Contains(ClipboardDataFormats.ImageSlashPng))
+            bool dataContainsPng = ClipboardDataFormats.PngFormats.Any(importedObj.Contains);
+            if (dataContainsPng)
             {
-                source = FromPNG(data);
+                source = await FromPNG(importedObj);
+                if (source == null)
+                {
+                    return null;
+                }
             }
             else
             {
-                result = null;
-                return false;
+                return null;
             }
 
             /*if (source.Format.Value.IsSkiaSupported())
@@ -624,13 +769,11 @@ internal static class ClipboardController
                 result = SurfaceHelpers.FromBitmap(newFormat);
             }*/
 
-            result = source;
-            return true;
+            return source;
         }
         catch { }
 
-        result = null;
-        return false;
+        return null;
     }
 
     public static async Task CopyNodes(Guid[] nodeIds, Guid docId)
@@ -650,17 +793,20 @@ internal static class ClipboardController
 
     public static async Task<Guid[]> GetIds(string format)
     {
-        var data = await TryGetDataObject();
-        return GetIds(data, format);
+        var data = await TryGetImportObjects();
+        return await GetIds(data, format);
     }
 
-    private static Guid[] GetIds(IEnumerable<IDataObject?> data, string format)
+
+    private static async Task<Guid[]> GetIds(IEnumerable<IImportObject?> data, string format)
     {
         foreach (var dataObject in data)
         {
             if (dataObject.Contains(format))
             {
-                byte[] nodeIds = (byte[])dataObject.Get(format);
+                byte[] nodeIds = await dataObject.GetDataAsync(format) as byte[];
+                if (nodeIds == null || nodeIds.Length == 0)
+                    return [];
                 string nodeIdsString = System.Text.Encoding.UTF8.GetString(nodeIds);
                 return nodeIdsString.Split(';').Select(Guid.Parse).ToArray();
             }
@@ -702,7 +848,7 @@ internal static class ClipboardController
         data.Set(ClipboardDataFormats.DocumentFormat, Encoding.UTF8.GetBytes(docId.ToString()));
 
         byte[] idsBytes = Encoding.UTF8.GetBytes(string.Join(";", ids.Select(x => x.ToString())));
-
+        
         data.Set(format, idsBytes);
 
         await Clipboard.SetDataObjectAsync(data);
@@ -710,7 +856,7 @@ internal static class ClipboardController
 
     public static async Task<Guid> GetDocumentId()
     {
-        var data = await TryGetDataObject();
+        var data = await TryGetImportObjects();
         if (data == null)
             return Guid.Empty;
 
@@ -718,7 +864,7 @@ internal static class ClipboardController
         {
             if (dataObject.Contains(ClipboardDataFormats.DocumentFormat))
             {
-                byte[] guidBytes = (byte[])dataObject.Get(ClipboardDataFormats.DocumentFormat);
+                byte[] guidBytes = (byte[])await dataObject.GetDataAsync(ClipboardDataFormats.DocumentFormat);
                 string guidString = System.Text.Encoding.UTF8.GetString(guidBytes);
                 return Guid.Parse(guidString);
             }

+ 62 - 0
src/PixiEditor/Models/Controllers/IImportObject.cs

@@ -0,0 +1,62 @@
+using Avalonia.Input;
+using Avalonia.Input.Platform;
+
+namespace PixiEditor.Models.Controllers;
+
+public interface IImportObject
+{
+    public bool Contains(string format);
+    public Task<object?> GetDataAsync(string format);
+}
+
+public class ImportedObject : IImportObject
+{
+    private readonly IDataObject dataObject;
+
+    public ImportedObject(IDataObject dataObject)
+    {
+        this.dataObject = dataObject;
+    }
+
+    public bool Contains(string format)
+    {
+        return dataObject.Contains(format);
+    }
+
+    public async Task<object?> GetDataAsync(string format)
+    {
+        return Task.FromResult(dataObject.Get(format));
+    }
+}
+
+public class ClipboardPromiseObject : IImportObject
+{
+    public string Format { get; }
+    public IClipboard Clipboard { get; }
+
+    public ClipboardPromiseObject(string format, IClipboard clipboard)
+    {
+        Format = format;
+        Clipboard = clipboard;
+    }
+
+    public bool Contains(string format)
+    {
+        return Format == format;
+    }
+
+    public async Task<object?> GetDataAsync(string format)
+    {
+        if (Format != format)
+        {
+            return null;
+        }
+
+        return await Clipboard.GetDataAsync(format);
+    }
+
+    public override string ToString()
+    {
+        return $"ClipboardPromiseObject: {Format}";
+    }
+}

+ 5 - 1
src/PixiEditor/ViewModels/Document/CelViewModel.cs

@@ -91,7 +91,11 @@ internal abstract class CelViewModel : ObservableObject, ICelHandler
     public bool IsSelected
     {
         get => isSelected;
-        set => SetProperty(ref isSelected, value);
+        set
+        {
+            if (StartFrameBindable == 0) return;
+            SetProperty(ref isSelected, value);
+        }
     }
 
     protected CelViewModel(int startFrame, int duration, Guid layerGuid, Guid id,

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

@@ -189,7 +189,7 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
     {
         var activeDocument = Owner.DocumentManagerSubViewModel.ActiveDocument;
 
-        if (activeDocument is null)
+        if (activeDocument is null || info == default)
             return;
 
         if (!info.end)

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

@@ -134,7 +134,7 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
             DataImage imageData =
                 (data == null
                     ? await ClipboardController.GetImagesFromClipboard()
-                    : ClipboardController.GetImage(new[] { data })).First();
+                    : ClipboardController.GetImage(new[] { new ImportedObject(data) }).Result).First();
             using var surface = imageData.Image;
 
             var bitmap = imageData.Image.ToWriteableBitmap();
@@ -398,7 +398,7 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
         var selectedNodes = doc.NodeGraph.AllNodes.Where(x => x.IsNodeSelected).Select(x => x.Id).ToArray();
         if (selectedNodes.Length == 0)
             return;
-
+        
         await ClipboardController.CopyNodes(selectedNodes, doc.Id);
 
         areNodesInClipboard = true;

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

@@ -235,7 +235,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
             {
                 if (File.Exists(dataImage.Name))
                 {
-                    OpenRegularImage(dataImage.Image, null);
+                    OpenRegularImage(dataImage.Image, dataImage.Name);
                     continue;
                 }
 

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

@@ -181,7 +181,8 @@ internal partial class LayersManager : UserControl
             e.Handled = true;
         }
 
-        if (ClipboardController.TryPaste(ActiveDocument, ManagerViewModel, new[] { (IDataObject)e.Data }, true))
+        // Imported object doesn't do any async operations so .Result is safe to use here.
+        if (ClipboardController.TryPaste(ActiveDocument, ManagerViewModel, new[] { new ImportedObject((IDataObject)e.Data) }, true).Result)
         {
             e.Handled = true;
         }

+ 6 - 2
src/PixiEditor/Views/MainView.axaml.cs

@@ -75,9 +75,13 @@ public partial class MainView : UserControl
             return;
         }
 
-        if (fileDropList is { Length: > 0 } && Importer.IsSupportedFile(fileDropList[0].Path.LocalPath))
+        if (fileDropList is { Length: > 0 })
         {
-            Context.FileSubViewModel.OpenFromPath(fileDropList[0].Path.LocalPath);
+            foreach (var item in fileDropList)
+            {
+                Importer.IsSupportedFile(item.Path.LocalPath);
+                Context.FileSubViewModel.OpenFromPath(item.Path.LocalPath);
+            }
         }
     }
 

+ 1 - 1
tests/Directory.Build.props

@@ -1,7 +1,7 @@
 <Project>
     <PropertyGroup>
         <CodeAnalysisRuleSet>../Custom.ruleset</CodeAnalysisRuleSet>
-		<AvaloniaVersion>11.3.3</AvaloniaVersion>
+		<AvaloniaVersion>11.3.0</AvaloniaVersion>
     </PropertyGroup>
     <ItemGroup>
         <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />