1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 19:22:54 +08:00

Merge pull request #22650 from peppy/skin-editor-clipboard

Add skin editor clipboard support
This commit is contained in:
Bartłomiej Dach 2023-02-21 20:10:32 +01:00 committed by GitHub
commit eb0c3ca174
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 130 additions and 13 deletions

View File

@ -12,6 +12,7 @@ using osu.Game.Overlays.Settings;
using osu.Game.Overlays.SkinEditor;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osu.Game.Skinning;
using osuTK.Input;
@ -27,6 +28,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
[Cached]
public readonly EditorClipboard Clipboard = new EditorClipboard();
[SetUpSteps]
public override void SetUpSteps()
{

View File

@ -12,6 +12,7 @@ using osu.Game.Overlays.SkinEditor;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Play;
using osu.Game.Tests.Gameplay;
using osuTK.Input;
@ -32,6 +33,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached(typeof(IGameplayClock))]
private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock());
[Cached]
public readonly EditorClipboard Clipboard = new EditorClipboard();
[SetUpSteps]
public void SetUpSteps()
{

View File

@ -7,6 +7,7 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -60,6 +61,9 @@ namespace osu.Game.Overlays.SkinEditor
[Resolved]
private RealmAccess realm { get; set; } = null!;
[Resolved]
private EditorClipboard clipboard { get; set; } = null!;
[Resolved]
private SkinEditorOverlay? skinEditorOverlay { get; set; }
@ -78,6 +82,15 @@ namespace osu.Game.Overlays.SkinEditor
private EditorMenuItem undoMenuItem = null!;
private EditorMenuItem redoMenuItem = null!;
private EditorMenuItem cutMenuItem = null!;
private EditorMenuItem copyMenuItem = null!;
private EditorMenuItem cloneMenuItem = null!;
private EditorMenuItem pasteMenuItem = null!;
private readonly BindableWithCurrent<bool> canCut = new BindableWithCurrent<bool>();
private readonly BindableWithCurrent<bool> canCopy = new BindableWithCurrent<bool>();
private readonly BindableWithCurrent<bool> canPaste = new BindableWithCurrent<bool>();
[Resolved]
private OnScreenDisplay? onScreenDisplay { get; set; }
@ -143,6 +156,11 @@ namespace osu.Game.Overlays.SkinEditor
{
undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo),
redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo),
new EditorMenuItemSpacer(),
cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut),
copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy),
pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste),
cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone),
}
},
}
@ -201,6 +219,21 @@ namespace osu.Game.Overlays.SkinEditor
{
base.LoadComplete();
canCut.Current.BindValueChanged(cut => cutMenuItem.Action.Disabled = !cut.NewValue, true);
canCopy.Current.BindValueChanged(copy =>
{
copyMenuItem.Action.Disabled = !copy.NewValue;
cloneMenuItem.Action.Disabled = !copy.NewValue;
}, true);
canPaste.Current.BindValueChanged(paste => pasteMenuItem.Action.Disabled = !paste.NewValue, true);
SelectedComponents.BindCollectionChanged((_, _) =>
{
canCopy.Value = canCut.Value = SelectedComponents.Any();
}, true);
clipboard.Content.BindValueChanged(content => canPaste.Value = !string.IsNullOrEmpty(content.NewValue), true);
Show();
game?.RegisterImportHandler(this);
@ -224,6 +257,18 @@ namespace osu.Game.Overlays.SkinEditor
{
switch (e.Action)
{
case PlatformAction.Cut:
Cut();
return true;
case PlatformAction.Copy:
Copy();
return true;
case PlatformAction.Paste:
Paste();
return true;
case PlatformAction.Undo:
Undo();
return true;
@ -273,7 +318,14 @@ namespace osu.Game.Overlays.SkinEditor
componentsSidebar.Child = new SkinComponentToolbox(getFirstTarget() as CompositeDrawable)
{
RequestPlacement = placeComponent
RequestPlacement = type =>
{
if (!(Activator.CreateInstance(type) is ISerialisableDrawable component))
throw new InvalidOperationException($"Attempted to instantiate a component for placement which was not an {typeof(ISerialisableDrawable)}.");
SelectedComponents.Clear();
placeComponent(component);
}
};
}
}
@ -300,20 +352,18 @@ namespace osu.Game.Overlays.SkinEditor
hasBegunMutating = true;
}
private void placeComponent(Type type)
{
if (!(Activator.CreateInstance(type) is ISerialisableDrawable component))
throw new InvalidOperationException($"Attempted to instantiate a component for placement which was not an {typeof(ISerialisableDrawable)}.");
placeComponent(component);
}
private void placeComponent(ISerialisableDrawable component, bool applyDefaults = true)
/// <summary>
/// Attempt to place a given component in the current target. If successful, the new component will be added to <see cref="SelectedComponents"/>.
/// </summary>
/// <param name="component">The component to be placed.</param>
/// <param name="applyDefaults">Whether to apply default anchor / origin / position values.</param>
/// <returns>Whether placement succeeded. Could fail if no target is available, or if the current target has missing dependency requirements for the component.</returns>
private bool placeComponent(ISerialisableDrawable component, bool applyDefaults = true)
{
var targetContainer = getFirstTarget();
if (targetContainer == null)
return;
return false;
var drawableComponent = (Drawable)component;
@ -325,10 +375,18 @@ namespace osu.Game.Overlays.SkinEditor
drawableComponent.Y = targetContainer.DrawSize.Y / 2;
}
targetContainer.Add(component);
try
{
targetContainer.Add(component);
}
catch
{
// May fail if dependencies are not available, for instance.
return false;
}
SelectedComponents.Clear();
SelectedComponents.Add(component);
return true;
}
private void populateSettings()
@ -361,6 +419,48 @@ namespace osu.Game.Overlays.SkinEditor
}
}
protected void Cut()
{
Copy();
DeleteItems(SelectedComponents.ToArray());
}
protected void Copy()
{
clipboard.Content.Value = JsonConvert.SerializeObject(SelectedComponents.Cast<Drawable>().Select(s => s.CreateSerialisedInfo()).ToArray());
}
protected void Clone()
{
// Avoid attempting to clone if copying is not available (as it may result in pasting something unexpected).
if (!canCopy.Value)
return;
Copy();
Paste();
}
protected void Paste()
{
changeHandler?.BeginChange();
var drawableInfo = JsonConvert.DeserializeObject<SerialisedDrawableInfo[]>(clipboard.Content.Value);
if (drawableInfo == null)
return;
var instances = drawableInfo.Select(d => d.CreateInstance())
.OfType<ISerialisableDrawable>()
.ToArray();
SelectedComponents.Clear();
foreach (var i in instances)
placeComponent(i, false);
changeHandler?.EndChange();
}
protected void Undo() => changeHandler?.RestoreState(-1);
protected void Redo() => changeHandler?.RestoreState(1);
@ -402,8 +502,12 @@ namespace osu.Game.Overlays.SkinEditor
public void DeleteItems(ISerialisableDrawable[] items)
{
changeHandler?.BeginChange();
foreach (var item in items)
availableTargets.FirstOrDefault(t => t.Components.Contains(item))?.Remove(item);
changeHandler?.EndChange();
}
#region Drag & drop import handling
@ -440,6 +544,7 @@ namespace osu.Game.Overlays.SkinEditor
Position = skinnableTarget.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position),
};
SelectedComponents.Clear();
placeComponent(sprite, false);
SkinSelectionHandler.ApplyClosestAnchor(sprite);

View File

@ -11,6 +11,7 @@ using osu.Framework.Input.Events;
using osu.Game.Graphics.Containers;
using osu.Game.Input.Bindings;
using osu.Game.Screens;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components;
using osuTK;
@ -28,6 +29,9 @@ namespace osu.Game.Overlays.SkinEditor
private SkinEditor? skinEditor;
[Cached]
public readonly EditorClipboard Clipboard = new EditorClipboard();
[Resolved]
private OsuGame game { get; set; } = null!;