mirror of
https://github.com/osukey/osukey.git
synced 2025-06-05 12:57:39 +09: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.Graphics.UserInterface;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Overlays.OSD;
|
using osu.Game.Overlays.OSD;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Components;
|
using osu.Game.Screens.Edit.Components;
|
||||||
using osu.Game.Screens.Edit.Components.Menus;
|
using osu.Game.Screens.Edit.Components.Menus;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -31,7 +32,7 @@ using osu.Game.Skinning;
|
|||||||
namespace osu.Game.Overlays.SkinEditor
|
namespace osu.Game.Overlays.SkinEditor
|
||||||
{
|
{
|
||||||
[Cached(typeof(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;
|
public const double TRANSITION_DURATION = 300;
|
||||||
|
|
||||||
@ -72,6 +73,11 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
private EditorSidebar componentsSidebar = null!;
|
private EditorSidebar componentsSidebar = null!;
|
||||||
private EditorSidebar settingsSidebar = null!;
|
private EditorSidebar settingsSidebar = null!;
|
||||||
|
|
||||||
|
private SkinEditorChangeHandler? changeHandler;
|
||||||
|
|
||||||
|
private EditorMenuItem undoMenuItem = null!;
|
||||||
|
private EditorMenuItem redoMenuItem = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OnScreenDisplay? onScreenDisplay { get; set; }
|
private OnScreenDisplay? onScreenDisplay { get; set; }
|
||||||
|
|
||||||
@ -131,6 +137,14 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()),
|
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
|
headerText = new OsuTextFlowContainer
|
||||||
@ -210,6 +224,14 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
{
|
{
|
||||||
switch (e.Action)
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
|
case PlatformAction.Undo:
|
||||||
|
Undo();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case PlatformAction.Redo:
|
||||||
|
Redo();
|
||||||
|
return true;
|
||||||
|
|
||||||
case PlatformAction.Save:
|
case PlatformAction.Save:
|
||||||
if (e.Repeat)
|
if (e.Repeat)
|
||||||
return false;
|
return false;
|
||||||
@ -229,6 +251,8 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
{
|
{
|
||||||
this.targetScreen = targetScreen;
|
this.targetScreen = targetScreen;
|
||||||
|
|
||||||
|
changeHandler?.Dispose();
|
||||||
|
|
||||||
SelectedComponents.Clear();
|
SelectedComponents.Clear();
|
||||||
|
|
||||||
// Immediately clear the previous blueprint container to ensure it doesn't try to interact with the old target.
|
// 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);
|
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);
|
content.Child = new SkinBlueprintContainer(targetScreen);
|
||||||
|
|
||||||
componentsSidebar.Child = new SkinComponentToolbox(getFirstTarget() as CompositeDrawable)
|
componentsSidebar.Child = new SkinComponentToolbox(getFirstTarget() as CompositeDrawable)
|
||||||
@ -301,6 +329,8 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
|
|
||||||
SelectedComponents.Clear();
|
SelectedComponents.Clear();
|
||||||
SelectedComponents.Add(component);
|
SelectedComponents.Add(component);
|
||||||
|
|
||||||
|
changeHandler?.SaveState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void populateSettings()
|
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()
|
public void Save()
|
||||||
{
|
{
|
||||||
if (!hasBegunMutating)
|
if (!hasBegunMutating)
|
||||||
@ -371,6 +405,8 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
{
|
{
|
||||||
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?.SaveState();
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Drag & drop import handling
|
#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.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit
|
namespace osu.Game.Screens.Edit
|
||||||
@ -11,12 +10,13 @@ namespace osu.Game.Screens.Edit
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface for a component that manages changes in the <see cref="Editor"/>.
|
/// Interface for a component that manages changes in the <see cref="Editor"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Cached]
|
||||||
public interface IEditorChangeHandler
|
public interface IEditorChangeHandler
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired whenever a state change occurs.
|
/// Fired whenever a state change occurs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event Action OnStateChange;
|
event Action? OnStateChange;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Begins a bulk state change event. <see cref="EndChange"/> should be invoked soon after.
|
/// Begins a bulk state change event. <see cref="EndChange"/> should be invoked soon after.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user