Skip to content

Commit

Permalink
Merge pull request #685 from b-editor/fix-clipboard
Browse files Browse the repository at this point in the history
タイムラインの要素のコピー操作を修正
  • Loading branch information
yuto-trd authored Sep 1, 2023
2 parents b57eb3a + d86fa84 commit 20d0510
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 22 deletions.
2 changes: 2 additions & 0 deletions src/Beutl.Utilities/PooledArrayBufferWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ public Span<T> GetSpan(int sizeHint = 0)
return _buffer.AsSpan(_index);
}

public static T[] GetArray(PooledArrayBufferWriter<T> self) => self._buffer;

private void CheckAndResizeBuffer(int sizeHint)
{
if (sizeHint < 0)
Expand Down
15 changes: 15 additions & 0 deletions src/Beutl/App.axaml.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();
Expand Down
97 changes: 97 additions & 0 deletions src/Beutl/Services/CoreObjectReborn.cs
Original file line number Diff line number Diff line change
@@ -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>(T obj, PooledArrayBufferWriter<byte> output)
where T : class, ICoreObject, new()
{
var searcher = new ObjectSearcher(obj, v => v is ICoreObject);

Guid[] ids = searcher.SearchAll()
.Cast<ICoreObject>()
.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<byte> buffer = PooledArrayBufferWriter<byte>.GetArray(output).AsSpan().Slice(0, output.WrittenCount);
Span<byte> oldStr = stackalloc byte[DefaultGuidStringSize];
Span<byte> newStr = stackalloc byte[DefaultGuidStringSize];
foreach (Guid oldId in ids)
{
Guid newId = Guid.NewGuid();
GuidToUtf8(oldId, oldStr);
GuidToUtf8(newId, newStr);
Span<byte> localBuffer = buffer;

int index;
while ((index = localBuffer.IndexOf(oldStr)) >= 0)
{
localBuffer = localBuffer.Slice(index);
newStr.CopyTo(localBuffer);
}
}
}

public static void Reborn<T>(T obj, out T newInstance)
where T : class, ICoreObject, new()
{
using var output = new PooledArrayBufferWriter<byte>(BufferSizeDefault);
RebornCore(obj, output);

Span<byte> buffer = PooledArrayBufferWriter<byte>.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>(T obj, out string json)
where T : class, ICoreObject, new()
{
using var output = new PooledArrayBufferWriter<byte>(BufferSizeDefault);
RebornCore(obj, output);

Span<byte> buffer = PooledArrayBufferWriter<byte>.GetArray(output).AsSpan().Slice(0, output.WrittenCount);
json = Encoding.UTF8.GetString(buffer);
}

private static void GuidToUtf8(Guid id, Span<byte> utf8)
{
Span<char> utf16 = stackalloc char[DefaultGuidStringSize];

if (!id.TryFormat(utf16, out _))
throw new Exception("Failed to 'Guid.TryFormat'.");

Encoding.UTF8.GetBytes(utf16, utf8);
}
}
15 changes: 3 additions & 12 deletions src/Beutl/ViewModels/ElementViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Beutl.Commands;
using Beutl.Models;
using Beutl.ProjectSystem;
using Beutl.Services;
using Beutl.Utilities;

using Reactive.Bindings;
Expand All @@ -20,7 +21,6 @@ namespace Beutl.ViewModels;
public sealed class ElementViewModel : IDisposable
{
private readonly CompositeDisposable _disposables = new();
private IClipboard? _clipboard;

public ElementViewModel(Element element, TimelineViewModel timeline)
{
Expand Down Expand Up @@ -163,11 +163,6 @@ public ElementViewModel(Element element, TimelineViewModel timeline)

public List<KeyBinding> KeyBindings { get; }

public void SetClipboard(IClipboard? clipboard)
{
_clipboard = clipboard;
}

public void Dispose()
{
_disposables.Dispose();
Expand Down Expand Up @@ -239,7 +234,7 @@ public async Task SubmitViewModelChanges()

private async ValueTask<bool> SetClipboard()
{
IClipboard? clipboard = _clipboard;
IClipboard? clipboard = App.GetClipboard();
if (clipboard != null)
{
var jsonNode = new JsonObject();
Expand Down Expand Up @@ -323,11 +318,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;
Expand Down
4 changes: 0 additions & 4 deletions src/Beutl/Views/ElementView.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
14 changes: 8 additions & 6 deletions src/Beutl/Views/Timeline.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down

0 comments on commit 20d0510

Please sign in to comment.