1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 20:07:25 +08:00

Merge pull request #22674 from peppy/skin-editor-layer-select

Add the ability to select a target layer in the skin editor
This commit is contained in:
Dean Herbert 2023-02-22 12:51:15 +09:00 committed by GitHub
commit 4f9f4b3970
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 112 additions and 25 deletions

View File

@ -7,6 +7,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Framework.Threading;
using osu.Game.Graphics.Sprites;
@ -22,12 +23,12 @@ namespace osu.Game.Overlays.SkinEditor
{
public Action<Type>? RequestPlacement;
private readonly CompositeDrawable? target;
private readonly SkinComponentsContainer? target;
private FillFlowContainer fill = null!;
public SkinComponentToolbox(CompositeDrawable? target = null)
: base(SkinEditorStrings.Components)
public SkinComponentToolbox(SkinComponentsContainer? target = null)
: base(target?.Lookup.Ruleset == null ? SkinEditorStrings.Components : LocalisableString.Interpolate($"{SkinEditorStrings.Components} ({target.Lookup.Ruleset.Name})"))
{
this.target = target;
}
@ -50,7 +51,7 @@ namespace osu.Game.Overlays.SkinEditor
{
fill.Clear();
var skinnableTypes = SerialisedDrawableInfo.GetAllAvailableDrawables();
var skinnableTypes = SerialisedDrawableInfo.GetAllAvailableDrawables(target?.Lookup.Ruleset);
foreach (var type in skinnableTypes)
attemptAddComponent(type);
}

View File

@ -25,6 +25,7 @@ using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Overlays.OSD;
using osu.Game.Overlays.Settings;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components;
using osu.Game.Screens.Edit.Components.Menus;
@ -70,6 +71,8 @@ namespace osu.Game.Overlays.SkinEditor
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
private readonly Bindable<SkinComponentsContainerLookup?> selectedTarget = new Bindable<SkinComponentsContainerLookup?>();
private bool hasBegunMutating;
private Container? content;
@ -251,6 +254,8 @@ namespace osu.Game.Overlays.SkinEditor
}, true);
SelectedComponents.BindCollectionChanged((_, _) => Scheduler.AddOnce(populateSettings), true);
selectedTarget.BindValueChanged(targetChanged, true);
}
public bool OnPressed(KeyBindingPressEvent<PlatformAction> e)
@ -298,8 +303,6 @@ namespace osu.Game.Overlays.SkinEditor
changeHandler?.Dispose();
SelectedComponents.Clear();
// Immediately clear the previous blueprint container to ensure it doesn't try to interact with the old target.
content?.Clear();
@ -308,25 +311,73 @@ namespace osu.Game.Overlays.SkinEditor
void loadBlueprintContainer()
{
Debug.Assert(content != null);
selectedTarget.Default = getFirstTarget()?.Lookup;
changeHandler = new SkinEditorChangeHandler(targetScreen);
changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true);
changeHandler.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true);
if (!availableTargets.Any(t => t.Lookup.Equals(selectedTarget.Value)))
selectedTarget.SetDefault();
}
}
content.Child = new SkinBlueprintContainer(targetScreen);
private void targetChanged(ValueChangedEvent<SkinComponentsContainerLookup?> target)
{
foreach (var toolbox in componentsSidebar.OfType<SkinComponentToolbox>())
toolbox.Expire();
componentsSidebar.Child = new SkinComponentToolbox(getFirstTarget() as CompositeDrawable)
if (target.NewValue == null)
return;
Debug.Assert(content != null);
SelectedComponents.Clear();
var skinComponentsContainer = getTarget(target.NewValue);
if (skinComponentsContainer == null)
return;
changeHandler = new SkinEditorChangeHandler(skinComponentsContainer);
changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true);
changeHandler.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true);
content.Child = new SkinBlueprintContainer(skinComponentsContainer);
componentsSidebar.Children = new[]
{
new EditorSidebarSection("Current working layer")
{
RequestPlacement = type =>
Children = new Drawable[]
{
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);
new SettingsDropdown<SkinComponentsContainerLookup?>
{
Items = availableTargets.Select(t => t.Lookup),
Current = selectedTarget,
}
}
};
},
};
// If the new target has a ruleset, let's show ruleset-specific items at the top, and the rest below.
if (target.NewValue.Ruleset != null)
{
componentsSidebar.Add(new SkinComponentToolbox(skinComponentsContainer)
{
RequestPlacement = requestPlacement
});
}
// Remove the ruleset from the lookup to get base components.
componentsSidebar.Add(new SkinComponentToolbox(getTarget(new SkinComponentsContainerLookup(target.NewValue.Target)))
{
RequestPlacement = requestPlacement
});
void requestPlacement(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)}.");
SelectedComponents.Clear();
placeComponent(component);
}
}
@ -360,7 +411,7 @@ namespace osu.Game.Overlays.SkinEditor
/// <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();
var targetContainer = getTarget(selectedTarget.Value);
if (targetContainer == null)
return false;
@ -399,11 +450,11 @@ namespace osu.Game.Overlays.SkinEditor
private IEnumerable<SkinComponentsContainer> availableTargets => targetScreen.ChildrenOfType<SkinComponentsContainer>();
private ISerialisableDrawableContainer? getFirstTarget() => availableTargets.FirstOrDefault();
private SkinComponentsContainer? getFirstTarget() => availableTargets.FirstOrDefault();
private ISerialisableDrawableContainer? getTarget(SkinComponentsContainerLookup.TargetArea target)
private SkinComponentsContainer? getTarget(SkinComponentsContainerLookup? target)
{
return availableTargets.FirstOrDefault(c => c.Lookup.Target == target);
return availableTargets.FirstOrDefault(c => c.Lookup.Equals(target));
}
private void revert()
@ -415,7 +466,7 @@ namespace osu.Game.Overlays.SkinEditor
currentSkin.Value.ResetDrawableTarget(t);
// add back default components
getTarget(t.Lookup.Target)?.Reload();
getTarget(t.Lookup)?.Reload();
}
}

View File

@ -1,6 +1,9 @@
// 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;
using System.ComponentModel;
using osu.Framework.Extensions;
using osu.Game.Rulesets;
namespace osu.Game.Skinning
@ -8,7 +11,7 @@ namespace osu.Game.Skinning
/// <summary>
/// Represents a lookup of a collection of elements that make up a particular skinnable <see cref="TargetArea"/> of the game.
/// </summary>
public class SkinComponentsContainerLookup : ISkinComponentLookup
public class SkinComponentsContainerLookup : ISkinComponentLookup, IEquatable<SkinComponentsContainerLookup>
{
/// <summary>
/// The target area / layer of the game for which skin components will be returned.
@ -27,12 +30,44 @@ namespace osu.Game.Skinning
Ruleset = ruleset;
}
public override string ToString()
{
if (Ruleset == null) return Target.GetDescription();
return $"{Target.GetDescription()} (\"{Ruleset.Name}\" only)";
}
public bool Equals(SkinComponentsContainerLookup? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Target == other.Target && (ReferenceEquals(Ruleset, other.Ruleset) || Ruleset?.Equals(other.Ruleset) == true);
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((SkinComponentsContainerLookup)obj);
}
public override int GetHashCode()
{
return HashCode.Combine((int)Target, Ruleset);
}
/// <summary>
/// Represents a particular area or part of a game screen whose layout can be customised using the skin editor.
/// </summary>
public enum TargetArea
{
[Description("HUD")]
MainHUDComponents,
[Description("Song select")]
SongSelect
}
}