mirror of
https://github.com/ppy/osu.git
synced 2025-01-18 11:02:57 +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:
commit
4f9f4b3970
osu.Game
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user