1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 08:22:56 +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.Overlays.SkinEditor;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK.Input; using osuTK.Input;
@ -27,6 +28,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached] [Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
[Cached]
public readonly EditorClipboard Clipboard = new EditorClipboard();
[SetUpSteps] [SetUpSteps]
public override void SetUpSteps() public override void SetUpSteps()
{ {

View File

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

View File

@ -7,6 +7,7 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -60,6 +61,9 @@ namespace osu.Game.Overlays.SkinEditor
[Resolved] [Resolved]
private RealmAccess realm { get; set; } = null!; private RealmAccess realm { get; set; } = null!;
[Resolved]
private EditorClipboard clipboard { get; set; } = null!;
[Resolved] [Resolved]
private SkinEditorOverlay? skinEditorOverlay { get; set; } private SkinEditorOverlay? skinEditorOverlay { get; set; }
@ -78,6 +82,15 @@ namespace osu.Game.Overlays.SkinEditor
private EditorMenuItem undoMenuItem = null!; private EditorMenuItem undoMenuItem = null!;
private EditorMenuItem redoMenuItem = 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] [Resolved]
private OnScreenDisplay? onScreenDisplay { get; set; } private OnScreenDisplay? onScreenDisplay { get; set; }
@ -143,6 +156,11 @@ namespace osu.Game.Overlays.SkinEditor
{ {
undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo),
redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), 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(); 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(); Show();
game?.RegisterImportHandler(this); game?.RegisterImportHandler(this);
@ -224,6 +257,18 @@ namespace osu.Game.Overlays.SkinEditor
{ {
switch (e.Action) switch (e.Action)
{ {
case PlatformAction.Cut:
Cut();
return true;
case PlatformAction.Copy:
Copy();
return true;
case PlatformAction.Paste:
Paste();
return true;
case PlatformAction.Undo: case PlatformAction.Undo:
Undo(); Undo();
return true; return true;
@ -273,7 +318,14 @@ namespace osu.Game.Overlays.SkinEditor
componentsSidebar.Child = new SkinComponentToolbox(getFirstTarget() as CompositeDrawable) 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; hasBegunMutating = true;
} }
private void placeComponent(Type type) /// <summary>
{ /// Attempt to place a given component in the current target. If successful, the new component will be added to <see cref="SelectedComponents"/>.
if (!(Activator.CreateInstance(type) is ISerialisableDrawable component)) /// </summary>
throw new InvalidOperationException($"Attempted to instantiate a component for placement which was not an {typeof(ISerialisableDrawable)}."); /// <param name="component">The component to be placed.</param>
/// <param name="applyDefaults">Whether to apply default anchor / origin / position values.</param>
placeComponent(component); /// <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)
private void placeComponent(ISerialisableDrawable component, bool applyDefaults = true)
{ {
var targetContainer = getFirstTarget(); var targetContainer = getFirstTarget();
if (targetContainer == null) if (targetContainer == null)
return; return false;
var drawableComponent = (Drawable)component; var drawableComponent = (Drawable)component;
@ -325,10 +375,18 @@ namespace osu.Game.Overlays.SkinEditor
drawableComponent.Y = targetContainer.DrawSize.Y / 2; 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); SelectedComponents.Add(component);
return true;
} }
private void populateSettings() 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 Undo() => changeHandler?.RestoreState(-1);
protected void Redo() => changeHandler?.RestoreState(1); protected void Redo() => changeHandler?.RestoreState(1);
@ -402,8 +502,12 @@ namespace osu.Game.Overlays.SkinEditor
public void DeleteItems(ISerialisableDrawable[] items) public void DeleteItems(ISerialisableDrawable[] items)
{ {
changeHandler?.BeginChange();
foreach (var item in items) foreach (var item in items)
availableTargets.FirstOrDefault(t => t.Components.Contains(item))?.Remove(item); availableTargets.FirstOrDefault(t => t.Components.Contains(item))?.Remove(item);
changeHandler?.EndChange();
} }
#region Drag & drop import handling #region Drag & drop import handling
@ -440,6 +544,7 @@ namespace osu.Game.Overlays.SkinEditor
Position = skinnableTarget.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position), Position = skinnableTarget.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position),
}; };
SelectedComponents.Clear();
placeComponent(sprite, false); placeComponent(sprite, false);
SkinSelectionHandler.ApplyClosestAnchor(sprite); SkinSelectionHandler.ApplyClosestAnchor(sprite);

View File

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