From f7d8682c3f50540a8da7069f705db6b96a88196d Mon Sep 17 00:00:00 2001 From: indigo-san Date: Fri, 1 Sep 2023 17:21:30 +0900 Subject: [PATCH 1/3] =?UTF-8?q?Element=E3=81=A7=E3=82=AF=E3=83=AA=E3=83=83?= =?UTF-8?q?=E3=83=97=E3=83=9C=E3=83=BC=E3=83=89=E3=82=92App=E3=82=AF?= =?UTF-8?q?=E3=83=A9=E3=82=B9=E3=81=8B=E3=82=89=E5=8F=96=E5=BE=97=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl/App.axaml.cs | 15 +++++++++++++++ src/Beutl/ViewModels/ElementViewModel.cs | 8 +------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Beutl/App.axaml.cs b/src/Beutl/App.axaml.cs index ba4eb29fb..42d519c3d 100644 --- a/src/Beutl/App.axaml.cs +++ b/src/Beutl/App.axaml.cs @@ -1,7 +1,9 @@ using System.Reflection; using Avalonia; +using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Input.Platform; using Avalonia.Markup.Xaml; using Avalonia.Media; using Avalonia.ReactiveUI; @@ -104,6 +106,19 @@ public override void OnFrameworkInitializationCompleted() base.OnFrameworkInitializationCompleted(); } + public static IClipboard? GetClipboard() + { + switch (Current?.ApplicationLifetime) + { + case IClassicDesktopStyleApplicationLifetime desktop: + return desktop.MainWindow?.Clipboard; + case ISingleViewApplicationLifetime { MainView: { } mainview }: + return TopLevel.GetTopLevel(mainview)?.Clipboard; + default: + return null; + } + } + private MainViewModel GetMainViewModel() { return _mainViewModel ??= new MainViewModel(); diff --git a/src/Beutl/ViewModels/ElementViewModel.cs b/src/Beutl/ViewModels/ElementViewModel.cs index 52370b68e..7b4ba2a7f 100644 --- a/src/Beutl/ViewModels/ElementViewModel.cs +++ b/src/Beutl/ViewModels/ElementViewModel.cs @@ -20,7 +20,6 @@ namespace Beutl.ViewModels; public sealed class ElementViewModel : IDisposable { private readonly CompositeDisposable _disposables = new(); - private IClipboard? _clipboard; public ElementViewModel(Element element, TimelineViewModel timeline) { @@ -163,11 +162,6 @@ public ElementViewModel(Element element, TimelineViewModel timeline) public List KeyBindings { get; } - public void SetClipboard(IClipboard? clipboard) - { - _clipboard = clipboard; - } - public void Dispose() { _disposables.Dispose(); @@ -239,7 +233,7 @@ public async Task SubmitViewModelChanges() private async ValueTask SetClipboard() { - IClipboard? clipboard = _clipboard; + IClipboard? clipboard = App.GetClipboard(); if (clipboard != null) { var jsonNode = new JsonObject(); From 3633cdcdf205cf3af480c9ea83cda5e1f230b10a Mon Sep 17 00:00:00 2001 From: indigo-san Date: Fri, 1 Sep 2023 17:22:01 +0900 Subject: [PATCH 2/3] =?UTF-8?q?Element=E3=82=92=E3=82=B3=E3=83=94=E3=83=BC?= =?UTF-8?q?=E3=81=97=E3=81=9F=E3=81=A8=E3=81=8D=E3=80=81Id=E3=81=8C?= =?UTF-8?q?=E9=87=8D=E8=A4=87=E3=81=99=E3=82=8B=E5=8F=AF=E8=83=BD=E6=80=A7?= =?UTF-8?q?=E3=81=8C=E3=81=82=E3=82=8B=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PooledArrayBufferWriter.cs | 2 + src/Beutl/Services/CoreObjectReborn.cs | 97 +++++++++++++++++++ src/Beutl/ViewModels/ElementViewModel.cs | 12 +-- src/Beutl/Views/ElementView.axaml.cs | 4 - src/Beutl/Views/MainView.axaml.cs | 8 +- 5 files changed, 105 insertions(+), 18 deletions(-) create mode 100644 src/Beutl/Services/CoreObjectReborn.cs diff --git a/src/Beutl.Utilities/PooledArrayBufferWriter.cs b/src/Beutl.Utilities/PooledArrayBufferWriter.cs index a86b1bd68..83afae7e7 100644 --- a/src/Beutl.Utilities/PooledArrayBufferWriter.cs +++ b/src/Beutl.Utilities/PooledArrayBufferWriter.cs @@ -105,6 +105,8 @@ public Span GetSpan(int sizeHint = 0) return _buffer.AsSpan(_index); } + public static T[] GetArray(PooledArrayBufferWriter self) => self._buffer; + private void CheckAndResizeBuffer(int sizeHint) { if (sizeHint < 0) diff --git a/src/Beutl/Services/CoreObjectReborn.cs b/src/Beutl/Services/CoreObjectReborn.cs new file mode 100644 index 000000000..5b759fff1 --- /dev/null +++ b/src/Beutl/Services/CoreObjectReborn.cs @@ -0,0 +1,97 @@ +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; + +using Beutl.Utilities; + +namespace Beutl.Services; + +public static class CoreObjectReborn +{ + private const int DefaultGuidStringSize = 36; + private const int BufferSizeDefault = 16 * 1024; + + private static void RebornCore(T obj, PooledArrayBufferWriter output) + where T : class, ICoreObject, new() + { + var searcher = new ObjectSearcher(obj, v => v is ICoreObject); + + Guid[] ids = searcher.SearchAll() + .Cast() + .Select(v => v.Id) + .Distinct() + .ToArray(); + + // JsonObjectに変換 + var jsonObject = new JsonObject(); + obj.WriteToJson(jsonObject); + + // UTF-8に書き込む + JsonSerializerOptions options = JsonHelper.SerializerOptions; + var writerOptions = new JsonWriterOptions + { + Encoder = options.Encoder, + Indented = options.WriteIndented, + MaxDepth = options.MaxDepth + }; + + using (var writer = new Utf8JsonWriter(output, writerOptions)) + { + jsonObject.WriteTo(writer, options); + } + + // Idを置き換える + Span buffer = PooledArrayBufferWriter.GetArray(output).AsSpan().Slice(0, output.WrittenCount); + Span oldStr = stackalloc byte[DefaultGuidStringSize]; + Span newStr = stackalloc byte[DefaultGuidStringSize]; + foreach (Guid oldId in ids) + { + Guid newId = Guid.NewGuid(); + GuidToUtf8(oldId, oldStr); + GuidToUtf8(newId, newStr); + Span localBuffer = buffer; + + int index; + while ((index = localBuffer.IndexOf(oldStr)) >= 0) + { + localBuffer = localBuffer.Slice(index); + newStr.CopyTo(localBuffer); + } + } + } + + public static void Reborn(T obj, out T newInstance) + where T : class, ICoreObject, new() + { + using var output = new PooledArrayBufferWriter(BufferSizeDefault); + RebornCore(obj, output); + + Span buffer = PooledArrayBufferWriter.GetArray(output).AsSpan().Slice(0, output.WrittenCount); + + JsonObject jsonObj = JsonNode.Parse(buffer)!.AsObject(); + var instance = new T(); + instance.ReadFromJson(jsonObj); + + newInstance = instance; + } + + public static void Reborn(T obj, out string json) + where T : class, ICoreObject, new() + { + using var output = new PooledArrayBufferWriter(BufferSizeDefault); + RebornCore(obj, output); + + Span buffer = PooledArrayBufferWriter.GetArray(output).AsSpan().Slice(0, output.WrittenCount); + json = Encoding.UTF8.GetString(buffer); + } + + private static void GuidToUtf8(Guid id, Span utf8) + { + Span utf16 = stackalloc char[DefaultGuidStringSize]; + + if (!id.TryFormat(utf16, out _)) + throw new Exception("Failed to 'Guid.TryFormat'."); + + Encoding.UTF8.GetBytes(utf16, utf8); + } +} diff --git a/src/Beutl/ViewModels/ElementViewModel.cs b/src/Beutl/ViewModels/ElementViewModel.cs index 7b4ba2a7f..22db393d6 100644 --- a/src/Beutl/ViewModels/ElementViewModel.cs +++ b/src/Beutl/ViewModels/ElementViewModel.cs @@ -8,6 +8,7 @@ using Beutl.Commands; using Beutl.Models; using Beutl.ProjectSystem; +using Beutl.Services; using Beutl.Utilities; using Reactive.Bindings; @@ -236,9 +237,8 @@ private async ValueTask SetClipboard() IClipboard? clipboard = App.GetClipboard(); if (clipboard != null) { - var jsonNode = new JsonObject(); - Model.WriteToJson(jsonNode); - string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions); + CoreObjectReborn.Reborn(Model, out string json); + var data = new DataObject(); data.Set(DataFormats.Text, json); data.Set(Constants.Element, json); @@ -317,11 +317,7 @@ private void OnSplit(TimeSpan timeSpan) TimeSpan forwardLength = absTime - Model.Start; TimeSpan backwardLength = Model.Length - forwardLength; - var jsonNode = new JsonObject(); - Model.WriteToJson(jsonNode); - string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions); - var backwardLayer = new Element(); - backwardLayer.ReadFromJson(JsonNode.Parse(json)!.AsObject()); + CoreObjectReborn.Reborn(Model, out Element backwardLayer); Scene.MoveChild(Model.ZIndex, Model.Start, forwardLength, Model).DoAndRecord(CommandRecorder.Default); backwardLayer.Start = absTime; diff --git a/src/Beutl/Views/ElementView.axaml.cs b/src/Beutl/Views/ElementView.axaml.cs index 79051404c..3e6c8ab03 100644 --- a/src/Beutl/Views/ElementView.axaml.cs +++ b/src/Beutl/Views/ElementView.axaml.cs @@ -72,8 +72,6 @@ private void OnDataContextDetached(ElementViewModel obj) obj.AnimationRequested = (_, _) => Task.CompletedTask; _disposable1?.Dispose(); _disposable1 = null; - - obj.SetClipboard(null); } private void OnDataContextAttached(ElementViewModel obj) @@ -143,8 +141,6 @@ await Dispatcher.UIThread.InvokeAsync(async () => _disposable1 = obj.Model.GetObservable(Element.IsEnabledProperty) .Subscribe(b => Dispatcher.UIThread.InvokeAsync(() => border.Opacity = b ? 1 : 0.5)); - - obj.SetClipboard(TopLevel.GetTopLevel(this)?.Clipboard); } protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) diff --git a/src/Beutl/Views/MainView.axaml.cs b/src/Beutl/Views/MainView.axaml.cs index 25f94d163..582bb5f43 100644 --- a/src/Beutl/Views/MainView.axaml.cs +++ b/src/Beutl/Views/MainView.axaml.cs @@ -706,9 +706,7 @@ private void InitCommands(MainViewModel viewModel) && viewModel.Scene is Scene scene && viewModel.SelectedObject.Value is Element layer) { - var jsonNode = new JsonObject(); - layer.WriteToJson(jsonNode); - string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions); + CoreObjectReborn.Reborn(layer, out string json); var data = new DataObject(); data.Set(DataFormats.Text, json); data.Set(Constants.Element, json); @@ -725,9 +723,7 @@ private void InitCommands(MainViewModel viewModel) && viewModel.Scene is Scene scene && viewModel.SelectedObject.Value is Element layer) { - var jsonNode = new JsonObject(); - layer.WriteToJson(jsonNode); - string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions); + CoreObjectReborn.Reborn(layer, out string json); var data = new DataObject(); data.Set(DataFormats.Text, json); data.Set(Constants.Element, json); From d86fa847a0841f8d176357784c2bff0b519353dc Mon Sep 17 00:00:00 2001 From: indigo-san Date: Fri, 1 Sep 2023 17:45:32 +0900 Subject: [PATCH 3/3] =?UTF-8?q?Id=E3=81=AE=E5=86=8D=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92=E3=82=B3=E3=83=94=E3=83=BC=E6=99=82?= =?UTF-8?q?=E3=81=A7=E3=81=AF=E3=81=AA=E3=81=8F=E3=80=81=E3=83=9A=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E3=83=88=E6=99=82=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl/ViewModels/ElementViewModel.cs | 5 +++-- src/Beutl/Views/MainView.axaml.cs | 8 ++++++-- src/Beutl/Views/Timeline.axaml.cs | 14 ++++++++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/Beutl/ViewModels/ElementViewModel.cs b/src/Beutl/ViewModels/ElementViewModel.cs index 22db393d6..786844fac 100644 --- a/src/Beutl/ViewModels/ElementViewModel.cs +++ b/src/Beutl/ViewModels/ElementViewModel.cs @@ -237,8 +237,9 @@ private async ValueTask SetClipboard() IClipboard? clipboard = App.GetClipboard(); if (clipboard != null) { - CoreObjectReborn.Reborn(Model, out string json); - + var jsonNode = new JsonObject(); + Model.WriteToJson(jsonNode); + string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions); var data = new DataObject(); data.Set(DataFormats.Text, json); data.Set(Constants.Element, json); diff --git a/src/Beutl/Views/MainView.axaml.cs b/src/Beutl/Views/MainView.axaml.cs index 582bb5f43..25f94d163 100644 --- a/src/Beutl/Views/MainView.axaml.cs +++ b/src/Beutl/Views/MainView.axaml.cs @@ -706,7 +706,9 @@ private void InitCommands(MainViewModel viewModel) && viewModel.Scene is Scene scene && viewModel.SelectedObject.Value is Element layer) { - CoreObjectReborn.Reborn(layer, out string json); + var jsonNode = new JsonObject(); + layer.WriteToJson(jsonNode); + string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions); var data = new DataObject(); data.Set(DataFormats.Text, json); data.Set(Constants.Element, json); @@ -723,7 +725,9 @@ private void InitCommands(MainViewModel viewModel) && viewModel.Scene is Scene scene && viewModel.SelectedObject.Value is Element layer) { - CoreObjectReborn.Reborn(layer, out string json); + var jsonNode = new JsonObject(); + layer.WriteToJson(jsonNode); + string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions); var data = new DataObject(); data.Set(DataFormats.Text, json); data.Set(Constants.Element, json); diff --git a/src/Beutl/Views/Timeline.axaml.cs b/src/Beutl/Views/Timeline.axaml.cs index 67af4a15d..92ee29a3f 100644 --- a/src/Beutl/Views/Timeline.axaml.cs +++ b/src/Beutl/Views/Timeline.axaml.cs @@ -108,14 +108,16 @@ private void OnDataContextAttached(TimelineViewModel vm) string? json = await clipboard.GetTextAsync(); if (json != null) { - var layer = new Element(); - layer.ReadFromJson(JsonNode.Parse(json)!.AsObject()); - layer.Start = ViewModel.ClickedFrame; - layer.ZIndex = ViewModel.CalculateClickedLayer(); + var oldElement = new Element(); + oldElement.ReadFromJson(JsonNode.Parse(json)!.AsObject()); + CoreObjectReborn.Reborn(oldElement, out Element newElement); - layer.Save(RandomFileNameGenerator.Generate(Path.GetDirectoryName(ViewModel.Scene.FileName)!, Constants.ElementFileExtension)); + newElement.Start = ViewModel.ClickedFrame; + newElement.ZIndex = ViewModel.CalculateClickedLayer(); - ViewModel.Scene.AddChild(layer).DoAndRecord(CommandRecorder.Default); + newElement.Save(RandomFileNameGenerator.Generate(Path.GetDirectoryName(ViewModel.Scene.FileName)!, Constants.ElementFileExtension)); + + ViewModel.Scene.AddChild(newElement).DoAndRecord(CommandRecorder.Default); } } }