|
@@ -1,10 +1,13 @@
|
|
|
-using System.Collections.Immutable;
|
|
|
+using System.Collections.Concurrent;
|
|
|
+using System.Collections.Immutable;
|
|
|
using System.Linq;
|
|
|
using System.Threading.Tasks;
|
|
|
using Avalonia;
|
|
|
using Avalonia.Controls.ApplicationLifetimes;
|
|
|
using Avalonia.Input;
|
|
|
using Avalonia.Media;
|
|
|
+using Avalonia.Platform.Storage;
|
|
|
+using Avalonia.Threading;
|
|
|
using PixiEditor.Helpers.Extensions;
|
|
|
using Drawie.Backend.Core.Numerics;
|
|
|
using Drawie.Backend.Core.Surfaces;
|
|
@@ -18,6 +21,8 @@ using PixiEditor.Models.Handlers;
|
|
|
using PixiEditor.Models.IO;
|
|
|
using PixiEditor.Models.Layers;
|
|
|
using Drawie.Numerics;
|
|
|
+using PixiEditor.Helpers.Constants;
|
|
|
+using PixiEditor.Models.Commands;
|
|
|
using PixiEditor.UI.Common.Fonts;
|
|
|
using PixiEditor.ViewModels.Dock;
|
|
|
using PixiEditor.ViewModels.Document;
|
|
@@ -27,6 +32,13 @@ namespace PixiEditor.ViewModels.SubViewModels;
|
|
|
[Command.Group("PixiEditor.Clipboard", "CLIPBOARD")]
|
|
|
internal class ClipboardViewModel : SubViewModel<ViewModelMain>
|
|
|
{
|
|
|
+ private ConcurrentDictionary<string, Task> clipboardTasks = new();
|
|
|
+ private bool canPasteImage;
|
|
|
+ private string lastTextInClipboard;
|
|
|
+ private bool areNodesInClipboard;
|
|
|
+ private bool areCelsInClipboard;
|
|
|
+ private bool hasImageInClipboard;
|
|
|
+
|
|
|
public ClipboardViewModel(ViewModelMain owner)
|
|
|
: base(owner)
|
|
|
{
|
|
@@ -62,52 +74,58 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
|
|
|
|
|
|
var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
|
|
|
|
|
|
- Guid[] guids = doc.StructureHelper.GetAllLayers().Select(x => x.Id).ToArray();
|
|
|
- ClipboardController.TryPasteFromClipboard(doc, pasteAsNewLayer);
|
|
|
-
|
|
|
- doc.Operations.InvokeCustomAction(() =>
|
|
|
+ Dispatcher.UIThread.InvokeAsync(async () =>
|
|
|
{
|
|
|
- Guid[] newGuids = doc.StructureHelper.GetAllLayers().Select(x => x.Id).ToArray();
|
|
|
+ Guid[] guids = doc.StructureHelper.GetAllLayers().Select(x => x.Id).ToArray();
|
|
|
+ await ClipboardController.TryPasteFromClipboard(doc, pasteAsNewLayer);
|
|
|
|
|
|
- var diff = newGuids.Except(guids).ToArray();
|
|
|
- if (diff.Length > 0)
|
|
|
+ doc.Operations.InvokeCustomAction(() =>
|
|
|
{
|
|
|
- doc.Operations.ClearSoftSelectedMembers();
|
|
|
- doc.Operations.SetSelectedMember(diff[0]);
|
|
|
+ Guid[] newGuids = doc.StructureHelper.GetAllLayers().Select(x => x.Id).ToArray();
|
|
|
|
|
|
- for (int i = 1; i < diff.Length; i++)
|
|
|
+ var diff = newGuids.Except(guids).ToArray();
|
|
|
+ if (diff.Length > 0)
|
|
|
{
|
|
|
- doc.Operations.AddSoftSelectedMember(diff[i]);
|
|
|
+ doc.Operations.ClearSoftSelectedMembers();
|
|
|
+ doc.Operations.SetSelectedMember(diff[0]);
|
|
|
+
|
|
|
+ for (int i = 1; i < diff.Length; i++)
|
|
|
+ {
|
|
|
+ doc.Operations.AddSoftSelectedMember(diff[i]);
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
+ });
|
|
|
});
|
|
|
}
|
|
|
|
|
|
[Command.Basic("PixiEditor.Clipboard.PasteReferenceLayer", "PASTE_REFERENCE_LAYER",
|
|
|
"PASTE_REFERENCE_LAYER_DESCRIPTIVE", CanExecute = "PixiEditor.Clipboard.CanPaste",
|
|
|
Icon = PixiPerfectIcons.PasteReferenceLayer, AnalyticsTrack = true)]
|
|
|
- public async Task PasteReferenceLayer(IDataObject data)
|
|
|
+ public void PasteReferenceLayer(IDataObject data)
|
|
|
{
|
|
|
var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
|
|
|
|
|
|
- DataImage imageData =
|
|
|
- (data == null
|
|
|
- ? await ClipboardController.GetImagesFromClipboard()
|
|
|
- : ClipboardController.GetImage(new[] { data })).First();
|
|
|
- using var surface = imageData.Image;
|
|
|
+ Dispatcher.UIThread.InvokeAsync(async () =>
|
|
|
+ {
|
|
|
+ DataImage imageData =
|
|
|
+ (data == null
|
|
|
+ ? await ClipboardController.GetImagesFromClipboard()
|
|
|
+ : ClipboardController.GetImage(new[] { data })).First();
|
|
|
+ using var surface = imageData.Image;
|
|
|
|
|
|
- var bitmap = imageData.Image.ToWriteableBitmap();
|
|
|
+ var bitmap = imageData.Image.ToWriteableBitmap();
|
|
|
|
|
|
- byte[] pixels = bitmap.ExtractPixels();
|
|
|
+ byte[] pixels = bitmap.ExtractPixels();
|
|
|
|
|
|
- doc.Operations.ImportReferenceLayer(
|
|
|
- pixels.ToImmutableArray(),
|
|
|
- imageData.Image.Size);
|
|
|
+ doc.Operations.ImportReferenceLayer(
|
|
|
+ pixels.ToImmutableArray(),
|
|
|
+ imageData.Image.Size);
|
|
|
|
|
|
- if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
|
|
- {
|
|
|
- desktop.MainWindow!.Activate();
|
|
|
- }
|
|
|
+ if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
|
|
+ {
|
|
|
+ desktop.MainWindow!.Activate();
|
|
|
+ }
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
[Command.Internal("PixiEditor.Clipboard.PasteReferenceLayerFromPath")]
|
|
@@ -130,143 +148,153 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
|
|
|
[Command.Basic("PixiEditor.Clipboard.PasteColorAsSecondary", true, "PASTE_COLOR_SECONDARY",
|
|
|
"PASTE_COLOR_SECONDARY_DESCRIPTIVE", CanExecute = "PixiEditor.Clipboard.CanPasteColor",
|
|
|
IconEvaluator = "PixiEditor.Clipboard.PasteColorIcon", AnalyticsTrack = true)]
|
|
|
- public async Task PasteColor(bool secondary)
|
|
|
+ public void PasteColor(bool secondary)
|
|
|
{
|
|
|
- if (!ColorHelper.ParseAnyFormat((await ClipboardController.Clipboard.GetTextAsync())?.Trim() ?? string.Empty,
|
|
|
- out var result))
|
|
|
+ Dispatcher.UIThread.InvokeAsync(async () =>
|
|
|
{
|
|
|
- return;
|
|
|
- }
|
|
|
+ if (!ColorHelper.ParseAnyFormat(
|
|
|
+ (await ClipboardController.Clipboard.GetTextAsync())?.Trim() ?? string.Empty,
|
|
|
+ out var result))
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- if (!secondary)
|
|
|
- {
|
|
|
- Owner.ColorsSubViewModel.PrimaryColor = result.Value;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- Owner.ColorsSubViewModel.SecondaryColor = result.Value;
|
|
|
- }
|
|
|
+ if (!secondary)
|
|
|
+ {
|
|
|
+ Owner.ColorsSubViewModel.PrimaryColor = result.Value;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Owner.ColorsSubViewModel.SecondaryColor = result.Value;
|
|
|
+ }
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
[Command.Basic("PixiEditor.Clipboard.PasteNodes", "PASTE_NODES", "PASTE_NODES_DESCRIPTIVE",
|
|
|
ShortcutContexts = [typeof(NodeGraphDockViewModel)], Key = Key.V, Modifiers = KeyModifiers.Control,
|
|
|
CanExecute = "PixiEditor.Clipboard.CanPasteNodes", Icon = PixiPerfectIcons.Paste, AnalyticsTrack = true)]
|
|
|
- public async Task PasteNodes()
|
|
|
+ public void PasteNodes()
|
|
|
{
|
|
|
var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
|
|
|
if (doc is null)
|
|
|
return;
|
|
|
|
|
|
- Guid[] toDuplicate = await ClipboardController.GetNodeIds();
|
|
|
+ Dispatcher.UIThread.InvokeAsync(async () =>
|
|
|
+ {
|
|
|
+ Guid[] toDuplicate = await ClipboardController.GetNodeIds();
|
|
|
|
|
|
- List<Guid> newIds = new();
|
|
|
+ List<Guid> newIds = new();
|
|
|
|
|
|
- Dictionary<Guid, Guid> nodeMapping = new();
|
|
|
+ Dictionary<Guid, Guid> nodeMapping = new();
|
|
|
|
|
|
- using var block = doc.Operations.StartChangeBlock();
|
|
|
+ using var block = doc.Operations.StartChangeBlock();
|
|
|
|
|
|
- foreach (var nodeId in toDuplicate)
|
|
|
- {
|
|
|
- Guid? newId = doc.Operations.DuplicateNode(nodeId);
|
|
|
- if (newId != null)
|
|
|
+ foreach (var nodeId in toDuplicate)
|
|
|
{
|
|
|
- newIds.Add(newId.Value);
|
|
|
- nodeMapping.Add(nodeId, newId.Value);
|
|
|
+ Guid? newId = doc.Operations.DuplicateNode(nodeId);
|
|
|
+ if (newId != null)
|
|
|
+ {
|
|
|
+ newIds.Add(newId.Value);
|
|
|
+ nodeMapping.Add(nodeId, newId.Value);
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- if (newIds.Count == 0)
|
|
|
- return;
|
|
|
+ if (newIds.Count == 0)
|
|
|
+ return;
|
|
|
|
|
|
- await block.ExecuteQueuedActions();
|
|
|
+ await block.ExecuteQueuedActions();
|
|
|
|
|
|
- ConnectRelatedNodes(doc, nodeMapping);
|
|
|
+ ConnectRelatedNodes(doc, nodeMapping);
|
|
|
|
|
|
- doc.Operations.InvokeCustomAction(() =>
|
|
|
- {
|
|
|
- foreach (var node in doc.NodeGraph.AllNodes)
|
|
|
+ doc.Operations.InvokeCustomAction(() =>
|
|
|
{
|
|
|
- node.IsNodeSelected = false;
|
|
|
- }
|
|
|
+ foreach (var node in doc.NodeGraph.AllNodes)
|
|
|
+ {
|
|
|
+ node.IsNodeSelected = false;
|
|
|
+ }
|
|
|
|
|
|
- foreach (var node in newIds)
|
|
|
- {
|
|
|
- var nodeInstance = doc.NodeGraph.AllNodes.FirstOrDefault(x => x.Id == node);
|
|
|
- if (nodeInstance != null)
|
|
|
+ foreach (var node in newIds)
|
|
|
{
|
|
|
- nodeInstance.IsNodeSelected = true;
|
|
|
+ var nodeInstance = doc.NodeGraph.AllNodes.FirstOrDefault(x => x.Id == node);
|
|
|
+ if (nodeInstance != null)
|
|
|
+ {
|
|
|
+ nodeInstance.IsNodeSelected = true;
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
+ });
|
|
|
});
|
|
|
}
|
|
|
|
|
|
[Command.Basic("PixiEditor.Clipboard.PasteCels", "PASTE_CELS", "PASTE_CELS_DESCRIPTIVE",
|
|
|
CanExecute = "PixiEditor.Clipboard.CanPasteCels", Key = Key.V, Modifiers = KeyModifiers.Control,
|
|
|
ShortcutContexts = [typeof(TimelineDockViewModel)], Icon = PixiPerfectIcons.Paste, AnalyticsTrack = true)]
|
|
|
- public async Task PasteCels()
|
|
|
+ public void PasteCels()
|
|
|
{
|
|
|
var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
|
|
|
if (doc is null)
|
|
|
return;
|
|
|
|
|
|
- var cels = await ClipboardController.GetCelIds();
|
|
|
+ Dispatcher.UIThread.InvokeAsync(async () =>
|
|
|
+ {
|
|
|
+ var cels = await ClipboardController.GetCelIds();
|
|
|
|
|
|
- if (cels.Length == 0)
|
|
|
- return;
|
|
|
+ if (cels.Length == 0)
|
|
|
+ return;
|
|
|
|
|
|
- using var block = doc.Operations.StartChangeBlock();
|
|
|
+ using var block = doc.Operations.StartChangeBlock();
|
|
|
|
|
|
- List<Guid> newCels = new();
|
|
|
- List<ICelHandler> celsToSelect = new();
|
|
|
+ List<Guid> newCels = new();
|
|
|
+ List<ICelHandler> celsToSelect = new();
|
|
|
|
|
|
- int minStartFrame = int.MaxValue;
|
|
|
-
|
|
|
- foreach (var cel in cels)
|
|
|
- {
|
|
|
- var foundCel = doc.AnimationDataViewModel.AllCels.FirstOrDefault(x => x.Id == cel);
|
|
|
- if (foundCel == null)
|
|
|
- continue;
|
|
|
-
|
|
|
- celsToSelect.Add(foundCel);
|
|
|
- minStartFrame = Math.Min(minStartFrame, foundCel.StartFrameBindable);
|
|
|
- }
|
|
|
-
|
|
|
- int delta = doc.AnimationDataViewModel.ActiveFrameBindable - minStartFrame;
|
|
|
+ int minStartFrame = int.MaxValue;
|
|
|
|
|
|
- foreach (var cel in celsToSelect)
|
|
|
- {
|
|
|
- int celFrame = cel.StartFrameBindable + delta;
|
|
|
- Guid? newCel = doc.AnimationDataViewModel.CreateCel(cel.LayerGuid,
|
|
|
- celFrame, cel.LayerGuid,
|
|
|
- cel.StartFrameBindable);
|
|
|
- if (newCel != null)
|
|
|
+ foreach (var cel in cels)
|
|
|
{
|
|
|
- int duration = cel.DurationBindable;
|
|
|
- doc.Operations.ChangeCelLength(newCel.Value, celFrame, duration);
|
|
|
- newCels.Add(newCel.Value);
|
|
|
+ var foundCel = doc.AnimationDataViewModel.AllCels.FirstOrDefault(x => x.Id == cel);
|
|
|
+ if (foundCel == null)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ celsToSelect.Add(foundCel);
|
|
|
+ minStartFrame = Math.Min(minStartFrame, foundCel.StartFrameBindable);
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- doc.Operations.InvokeCustomAction(() =>
|
|
|
- {
|
|
|
- foreach (var cel in doc.AnimationDataViewModel.AllCels)
|
|
|
+ int delta = doc.AnimationDataViewModel.ActiveFrameBindable - minStartFrame;
|
|
|
+
|
|
|
+ foreach (var cel in celsToSelect)
|
|
|
{
|
|
|
- cel.IsSelected = false;
|
|
|
+ int celFrame = cel.StartFrameBindable + delta;
|
|
|
+ Guid? newCel = doc.AnimationDataViewModel.CreateCel(cel.LayerGuid,
|
|
|
+ celFrame, cel.LayerGuid,
|
|
|
+ cel.StartFrameBindable);
|
|
|
+ if (newCel != null)
|
|
|
+ {
|
|
|
+ int duration = cel.DurationBindable;
|
|
|
+ doc.Operations.ChangeCelLength(newCel.Value, celFrame, duration);
|
|
|
+ newCels.Add(newCel.Value);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- foreach (var cel in newCels)
|
|
|
+ doc.Operations.InvokeCustomAction(() =>
|
|
|
{
|
|
|
- var celInstance = doc.AnimationDataViewModel.AllCels.FirstOrDefault(x => x.Id == cel);
|
|
|
- if (celInstance != null)
|
|
|
+ foreach (var cel in doc.AnimationDataViewModel.AllCels)
|
|
|
{
|
|
|
- celInstance.IsSelected = true;
|
|
|
+ cel.IsSelected = false;
|
|
|
}
|
|
|
- }
|
|
|
+
|
|
|
+ foreach (var cel in newCels)
|
|
|
+ {
|
|
|
+ var celInstance = doc.AnimationDataViewModel.AllCels.FirstOrDefault(x => x.Id == cel);
|
|
|
+ if (celInstance != null)
|
|
|
+ {
|
|
|
+ celInstance.IsSelected = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
});
|
|
|
}
|
|
|
|
|
|
-
|
|
|
- [Command.Basic("PixiEditor.Clipboard.Copy", "COPY", "COPY_DESCRIPTIVE", CanExecute = "PixiEditor.Clipboard.CanCopy",
|
|
|
+ [Command.Basic("PixiEditor.Clipboard.Copy", "COPY", "COPY_DESCRIPTIVE",
|
|
|
+ CanExecute = "PixiEditor.Clipboard.CanCopy",
|
|
|
Key = Key.C, Modifiers = KeyModifiers.Control,
|
|
|
ShortcutContexts = [typeof(ViewportWindowViewModel), typeof(LayersDockViewModel)],
|
|
|
MenuItemPath = "EDIT/COPY", MenuItemOrder = 3, Icon = PixiPerfectIcons.Copy, AnalyticsTrack = true)]
|
|
@@ -332,13 +360,16 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
|
|
|
"COPY_COLOR_HEX_DESCRIPTIVE", IconEvaluator = "PixiEditor.Clipboard.CopyColorIcon", AnalyticsTrack = true)]
|
|
|
[Command.Basic("PixiEditor.Clipboard.CopyPrimaryColorAsRgb", CopyColor.PrimaryRGB, "COPY_COLOR_RGB",
|
|
|
"COPY_COLOR_RGB_DESCRIPTIVE", IconEvaluator = "PixiEditor.Clipboard.CopyColorIcon", AnalyticsTrack = true)]
|
|
|
- [Command.Basic("PixiEditor.Clipboard.CopySecondaryColorAsHex", CopyColor.SecondaryHEX, "COPY_COLOR_SECONDARY_HEX",
|
|
|
+ [Command.Basic("PixiEditor.Clipboard.CopySecondaryColorAsHex", CopyColor.SecondaryHEX,
|
|
|
+ "COPY_COLOR_SECONDARY_HEX",
|
|
|
"COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE", IconEvaluator = "PixiEditor.Clipboard.CopyColorIcon",
|
|
|
AnalyticsTrack = true)]
|
|
|
- [Command.Basic("PixiEditor.Clipboard.CopySecondaryColorAsRgb", CopyColor.SecondardRGB, "COPY_COLOR_SECONDARY_RGB",
|
|
|
+ [Command.Basic("PixiEditor.Clipboard.CopySecondaryColorAsRgb", CopyColor.SecondardRGB,
|
|
|
+ "COPY_COLOR_SECONDARY_RGB",
|
|
|
"COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE", IconEvaluator = "PixiEditor.Clipboard.CopyColorIcon",
|
|
|
AnalyticsTrack = true)]
|
|
|
- [Command.Filter("PixiEditor.Clipboard.CopyColorToClipboard", "COPY_COLOR_TO_CLIPBOARD", "COPY_COLOR", Key = Key.C,
|
|
|
+ [Command.Filter("PixiEditor.Clipboard.CopyColorToClipboard", "COPY_COLOR_TO_CLIPBOARD", "COPY_COLOR",
|
|
|
+ Key = Key.C,
|
|
|
Modifiers = KeyModifiers.Shift | KeyModifiers.Alt, AnalyticsTrack = true)]
|
|
|
public async Task CopyColorAsHex(CopyColor color)
|
|
|
{
|
|
@@ -364,45 +395,75 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
|
|
|
[Evaluator.CanExecute("PixiEditor.Clipboard.CanPaste")]
|
|
|
public bool CanPaste(object parameter)
|
|
|
{
|
|
|
- return Owner.DocumentIsNotNull(null) && parameter is IDataObject data
|
|
|
- ? ClipboardController.IsImage(data)
|
|
|
- : ClipboardController.IsImageInClipboard().Result;
|
|
|
+ if (!Owner.DocumentIsNotNull(null)) return false;
|
|
|
+
|
|
|
+ if (parameter is IDataObject data)
|
|
|
+ return ClipboardController.IsImage(data);
|
|
|
+
|
|
|
+ QueueCheckCanPasteImage();
|
|
|
+ return canPasteImage;
|
|
|
+ }
|
|
|
+
|
|
|
+ [Evaluator.CanExecute("PixiEditor.Clipboard.HasImageInClipboard")]
|
|
|
+ public bool HasImageInClipboard()
|
|
|
+ {
|
|
|
+ QueueHasImageInClipboard();
|
|
|
+ return hasImageInClipboard;
|
|
|
}
|
|
|
|
|
|
[Evaluator.CanExecute("PixiEditor.Clipboard.CanCopyCels")]
|
|
|
public bool CanCopyCels()
|
|
|
{
|
|
|
return Owner.DocumentIsNotNull(null) &&
|
|
|
- Owner.DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.AllCels.Any(x => x.IsSelected);
|
|
|
+ Owner.DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.AllCels.Any(
|
|
|
+ x => x.IsSelected);
|
|
|
}
|
|
|
|
|
|
- [Evaluator.CanExecute("PixiEditor.Clipboard.CanCopyNodes")]
|
|
|
+ [Evaluator.CanExecute("PixiEditor.Clipboard.CanCopyNodes",
|
|
|
+ nameof(Owner.DocumentManagerSubViewModel),
|
|
|
+ nameof(Owner.DocumentManagerSubViewModel.ActiveDocument),
|
|
|
+ nameof(Owner.DocumentManagerSubViewModel.ActiveDocument.NodeGraph))]
|
|
|
public bool CanCopyNodes()
|
|
|
{
|
|
|
return Owner.DocumentIsNotNull(null) &&
|
|
|
Owner.DocumentManagerSubViewModel.ActiveDocument.NodeGraph.AllNodes.Any(x => x.IsNodeSelected);
|
|
|
}
|
|
|
|
|
|
- [Evaluator.CanExecute("PixiEditor.Clipboard.CanPasteNodes")]
|
|
|
+
|
|
|
+ [Evaluator.CanExecute("PixiEditor.Clipboard.CanPasteNodes",
|
|
|
+ nameof(Owner.DocumentManagerSubViewModel),
|
|
|
+ nameof(Owner.DocumentManagerSubViewModel.ActiveDocument), nameof(Owner.DocumentManagerSubViewModel.ActiveDocument.NodeGraph))]
|
|
|
public bool CanPasteNodes()
|
|
|
{
|
|
|
- return Owner.DocumentIsNotNull(null) && ClipboardController.AreNodesInClipboard().Result;
|
|
|
+ if (!Owner.DocumentIsNotNull(null)) return false;
|
|
|
+
|
|
|
+ QueueCheckNodesInClipboard();
|
|
|
+ return areNodesInClipboard;
|
|
|
}
|
|
|
|
|
|
- [Evaluator.CanExecute("PixiEditor.Clipboard.CanPasteCels")]
|
|
|
+ [Evaluator.CanExecute("PixiEditor.Clipboard.CanPasteCels",
|
|
|
+ nameof(Owner.DocumentManagerSubViewModel),
|
|
|
+ nameof(Owner.DocumentManagerSubViewModel.ActiveDocument),
|
|
|
+ nameof(Owner.DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel))]
|
|
|
public bool CanPasteCels()
|
|
|
{
|
|
|
- return Owner.DocumentIsNotNull(null) && ClipboardController.AreCelsInClipboard().Result;
|
|
|
+ if (!Owner.DocumentIsNotNull(null)) return false;
|
|
|
+
|
|
|
+ QueueCheckCelsInClipboard();
|
|
|
+ return areCelsInClipboard;
|
|
|
}
|
|
|
|
|
|
[Evaluator.CanExecute("PixiEditor.Clipboard.CanPasteColor")]
|
|
|
- public static async Task<bool> CanPasteColor()
|
|
|
+ public bool CanPasteColor()
|
|
|
{
|
|
|
- return ColorHelper.ParseAnyFormat(
|
|
|
- (await ClipboardController.Clipboard.GetTextAsync())?.Trim() ?? string.Empty, out _);
|
|
|
+ QueueFetchTextFromClipboard();
|
|
|
+ return ColorHelper.ParseAnyFormat(lastTextInClipboard?.Trim() ?? string.Empty, out _);
|
|
|
}
|
|
|
|
|
|
- [Evaluator.CanExecute("PixiEditor.Clipboard.CanCopy")]
|
|
|
+ [Evaluator.CanExecute("PixiEditor.Clipboard.CanCopy",
|
|
|
+ nameof(Owner.DocumentManagerSubViewModel.ActiveDocument),
|
|
|
+ nameof(Owner.DocumentManagerSubViewModel.ActiveDocument.TransformViewModel.TransformActive),
|
|
|
+ nameof(Owner.DocumentManagerSubViewModel.ActiveDocument.SelectedStructureMember))]
|
|
|
public bool CanCopy()
|
|
|
{
|
|
|
return Owner.DocumentManagerSubViewModel.ActiveDocument != null &&
|
|
@@ -412,15 +473,15 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
|
|
|
}
|
|
|
|
|
|
[Evaluator.Icon("PixiEditor.Clipboard.PasteColorIcon")]
|
|
|
- public static async Task<IImage> GetPasteColorIcon()
|
|
|
+ public IImage GetPasteColorIcon()
|
|
|
{
|
|
|
Color color;
|
|
|
|
|
|
- color = ColorHelper.ParseAnyFormat((await ClipboardController.Clipboard.GetTextAsync())?.Trim() ?? string.Empty,
|
|
|
+ QueueFetchTextFromClipboard();
|
|
|
+ color = ColorHelper.ParseAnyFormat(lastTextInClipboard?.Trim() ?? string.Empty,
|
|
|
out var result)
|
|
|
? result.Value.ToOpaqueMediaColor()
|
|
|
: Colors.Transparent;
|
|
|
-
|
|
|
return ColorSearchResult.GetIcon(color.ToOpaqueColor());
|
|
|
}
|
|
|
|
|
@@ -480,6 +541,63 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private void QueueHasImageInClipboard()
|
|
|
+ {
|
|
|
+ QueueClipboardTask("HasImageInClipboard", ClipboardController.IsImageInClipboard, hasImageInClipboard,
|
|
|
+ x => hasImageInClipboard = x);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void QueueCheckCanPasteImage()
|
|
|
+ {
|
|
|
+ QueueClipboardTask("CheckCanPasteImage", ClipboardController.IsImageInClipboard, canPasteImage,
|
|
|
+ x => canPasteImage = x);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void QueueFetchTextFromClipboard()
|
|
|
+ {
|
|
|
+ QueueClipboardTask("FetchTextFromClipboard", ClipboardController.GetTextFromClipboard, lastTextInClipboard,
|
|
|
+ x => lastTextInClipboard = x);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void QueueCheckNodesInClipboard()
|
|
|
+ {
|
|
|
+ QueueClipboardTask("CheckNodesInClipboard", ClipboardController.AreNodesInClipboard, areNodesInClipboard,
|
|
|
+ x => areNodesInClipboard = x);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void QueueCheckCelsInClipboard()
|
|
|
+ {
|
|
|
+ QueueClipboardTask("CheckCelsInClipboard", ClipboardController.AreCelsInClipboard, areCelsInClipboard,
|
|
|
+ x => areCelsInClipboard = x);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void QueueClipboardTask<T>(string key, Func<Task<T>> task, T value, Action<T> updateAction)
|
|
|
+ {
|
|
|
+ if (clipboardTasks.TryGetValue(key, out var t))
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var newTask = Task.Run(
|
|
|
+ async () =>
|
|
|
+ {
|
|
|
+ T result = await task();
|
|
|
+ if (!EqualityComparer<T>.Default.Equals(result, value))
|
|
|
+ {
|
|
|
+ updateAction(result);
|
|
|
+
|
|
|
+ Dispatcher.UIThread.Invoke(() =>
|
|
|
+ {
|
|
|
+ CommandController.CanExecuteChanged("PixiEditor.Clipboard");
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ clipboardTasks.Remove(key, out _);
|
|
|
+ });
|
|
|
+
|
|
|
+ clipboardTasks.TryAdd(key, newTask);
|
|
|
+ }
|
|
|
+
|
|
|
public enum CopyColor
|
|
|
{
|
|
|
PrimaryHEX,
|