mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 21:43:04 +08:00
Add a basic change handler to the skin editor
This commit is contained in:
parent
2f30306ea2
commit
2cda277c09
@ -24,6 +24,7 @@ using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.OSD;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
using osu.Game.Screens.Edit.Components.Menus;
|
||||
using osu.Game.Skinning;
|
||||
@ -31,7 +32,7 @@ using osu.Game.Skinning;
|
||||
namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
[Cached(typeof(SkinEditor))]
|
||||
public partial class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBindingHandler<PlatformAction>
|
||||
public partial class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBindingHandler<PlatformAction>, IEditorChangeHandler
|
||||
{
|
||||
public const double TRANSITION_DURATION = 300;
|
||||
|
||||
@ -72,6 +73,11 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
private EditorSidebar componentsSidebar = null!;
|
||||
private EditorSidebar settingsSidebar = null!;
|
||||
|
||||
private SkinEditorChangeHandler? changeHandler;
|
||||
|
||||
private EditorMenuItem undoMenuItem = null!;
|
||||
private EditorMenuItem redoMenuItem = null!;
|
||||
|
||||
[Resolved]
|
||||
private OnScreenDisplay? onScreenDisplay { get; set; }
|
||||
|
||||
@ -131,6 +137,14 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()),
|
||||
},
|
||||
},
|
||||
new MenuItem(CommonStrings.MenuBarEdit)
|
||||
{
|
||||
Items = new[]
|
||||
{
|
||||
undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo),
|
||||
redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo),
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
headerText = new OsuTextFlowContainer
|
||||
@ -210,6 +224,14 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case PlatformAction.Undo:
|
||||
Undo();
|
||||
return true;
|
||||
|
||||
case PlatformAction.Redo:
|
||||
Redo();
|
||||
return true;
|
||||
|
||||
case PlatformAction.Save:
|
||||
if (e.Repeat)
|
||||
return false;
|
||||
@ -229,6 +251,8 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
this.targetScreen = targetScreen;
|
||||
|
||||
changeHandler?.Dispose();
|
||||
|
||||
SelectedComponents.Clear();
|
||||
|
||||
// Immediately clear the previous blueprint container to ensure it doesn't try to interact with the old target.
|
||||
@ -241,6 +265,10 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
Debug.Assert(content != null);
|
||||
|
||||
changeHandler = new SkinEditorChangeHandler(targetScreen);
|
||||
changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true);
|
||||
changeHandler.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true);
|
||||
|
||||
content.Child = new SkinBlueprintContainer(targetScreen);
|
||||
|
||||
componentsSidebar.Child = new SkinComponentToolbox(getFirstTarget() as CompositeDrawable)
|
||||
@ -301,6 +329,8 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
|
||||
SelectedComponents.Clear();
|
||||
SelectedComponents.Add(component);
|
||||
|
||||
changeHandler?.SaveState();
|
||||
}
|
||||
|
||||
private void populateSettings()
|
||||
@ -333,6 +363,10 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
}
|
||||
}
|
||||
|
||||
protected void Undo() => changeHandler?.RestoreState(-1);
|
||||
|
||||
protected void Redo() => changeHandler?.RestoreState(1);
|
||||
|
||||
public void Save()
|
||||
{
|
||||
if (!hasBegunMutating)
|
||||
@ -371,6 +405,8 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
foreach (var item in items)
|
||||
availableTargets.FirstOrDefault(t => t.Components.Contains(item))?.Remove(item);
|
||||
|
||||
changeHandler?.SaveState();
|
||||
}
|
||||
|
||||
#region Drag & drop import handling
|
||||
@ -435,5 +471,19 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#region Delegation of IEditorChangeHandler
|
||||
|
||||
public event Action? OnStateChange
|
||||
{
|
||||
add => changeHandler!.OnStateChange += value;
|
||||
remove => changeHandler!.OnStateChange -= value;
|
||||
}
|
||||
|
||||
public void BeginChange() => changeHandler?.BeginChange();
|
||||
public void EndChange() => changeHandler?.EndChange();
|
||||
public void SaveState() => changeHandler?.SaveState();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
73
osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs
Normal file
73
osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
public partial class SkinEditorChangeHandler : EditorChangeHandler
|
||||
{
|
||||
private readonly Drawable targetScreen;
|
||||
|
||||
private ISkinnableTarget? firstTarget => targetScreen.ChildrenOfType<ISkinnableTarget>().FirstOrDefault();
|
||||
|
||||
public SkinEditorChangeHandler(Drawable targetScreen)
|
||||
{
|
||||
// To keep things simple, we are currently only handling the current target screen for undo / redo.
|
||||
// In the future we'll want this to cover all changes, even to skin's `InstantiationInfo`.
|
||||
// We'll also need to consider cases where multiple targets are on screen at the same time.
|
||||
|
||||
this.targetScreen = targetScreen;
|
||||
|
||||
// Save initial state.
|
||||
SaveState();
|
||||
}
|
||||
|
||||
protected override void WriteCurrentStateToStream(MemoryStream stream)
|
||||
{
|
||||
if (firstTarget == null)
|
||||
return;
|
||||
|
||||
var skinnableInfos = firstTarget.CreateSkinnableInfo().ToArray();
|
||||
string json = JsonConvert.SerializeObject(skinnableInfos, new JsonSerializerSettings { Formatting = Formatting.Indented });
|
||||
stream.Write(Encoding.UTF8.GetBytes(json));
|
||||
}
|
||||
|
||||
protected override void ApplyStateChange(byte[] previousState, byte[] newState)
|
||||
{
|
||||
if (firstTarget == null)
|
||||
return;
|
||||
|
||||
var deserializedContent = JsonConvert.DeserializeObject<IEnumerable<SkinnableInfo>>(Encoding.UTF8.GetString(newState));
|
||||
|
||||
if (deserializedContent == null)
|
||||
return;
|
||||
|
||||
SkinnableInfo[] skinnableInfo = deserializedContent.ToArray();
|
||||
Drawable[] targetComponents = firstTarget.Components.OfType<Drawable>().ToArray();
|
||||
|
||||
if (!skinnableInfo.Select(s => s.Type).SequenceEqual(targetComponents.Select(d => d.GetType())))
|
||||
{
|
||||
// Perform a naive full reload for now.
|
||||
firstTarget.Reload(skinnableInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
foreach (var drawable in targetComponents)
|
||||
drawable.ApplySkinnableInfo(skinnableInfo[i++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Screens.Edit
|
||||
@ -11,12 +10,13 @@ namespace osu.Game.Screens.Edit
|
||||
/// <summary>
|
||||
/// Interface for a component that manages changes in the <see cref="Editor"/>.
|
||||
/// </summary>
|
||||
[Cached]
|
||||
public interface IEditorChangeHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Fired whenever a state change occurs.
|
||||
/// </summary>
|
||||
event Action OnStateChange;
|
||||
event Action? OnStateChange;
|
||||
|
||||
/// <summary>
|
||||
/// Begins a bulk state change event. <see cref="EndChange"/> should be invoked soon after.
|
||||
|
Loading…
Reference in New Issue
Block a user