mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 16:02:55 +08:00
Merge pull request #12626 from peppy/skin-bindables
This commit is contained in:
commit
bb496d05f7
36
osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
Normal file
36
osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
Normal file
@ -0,0 +1,36 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Skinning.Editor;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneSkinEditor : PlayerTestScene
|
||||
{
|
||||
private SkinEditor skinEditor;
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("add editor overlay", () =>
|
||||
{
|
||||
skinEditor?.Expire();
|
||||
LoadComponentAsync(skinEditor = new SkinEditor(Player), Add);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestToggleEditor()
|
||||
{
|
||||
AddToggleStep("toggle editor visibility", visible => skinEditor.ToggleVisibility());
|
||||
}
|
||||
|
||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
// 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 osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Skinning.Editor;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneSkinEditorMultipleSkins : SkinnableTestScene
|
||||
{
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("create editor overlay", () =>
|
||||
{
|
||||
SetContents(() =>
|
||||
{
|
||||
var ruleset = new OsuRuleset();
|
||||
var working = CreateWorkingBeatmap(ruleset.RulesetInfo);
|
||||
var beatmap = working.GetPlayableBeatmap(ruleset.RulesetInfo);
|
||||
|
||||
ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||
|
||||
var drawableRuleset = ruleset.CreateDrawableRulesetWith(beatmap);
|
||||
|
||||
var hudOverlay = new HUDOverlay(scoreProcessor, null, drawableRuleset, Array.Empty<Mod>())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
|
||||
// Add any key just to display the key counter visually.
|
||||
hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
|
||||
hudOverlay.ComboCounter.Current.Value = 1;
|
||||
|
||||
return new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
drawableRuleset,
|
||||
hudOverlay,
|
||||
new SkinEditor(hudOverlay),
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
||||
}
|
||||
}
|
@ -36,6 +36,24 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
private BackgroundScreenStack backgroundStack;
|
||||
|
||||
private bool allowScaling = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether user scaling preferences should be applied. Enabled by default.
|
||||
/// </summary>
|
||||
public bool AllowScaling
|
||||
{
|
||||
get => allowScaling;
|
||||
set
|
||||
{
|
||||
if (value == allowScaling)
|
||||
return;
|
||||
|
||||
allowScaling = value;
|
||||
if (IsLoaded) updateSize();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance.
|
||||
/// </summary>
|
||||
@ -139,7 +157,7 @@ namespace osu.Game.Graphics.Containers
|
||||
backgroundStack?.FadeOut(fade_time);
|
||||
}
|
||||
|
||||
bool scaling = targetMode == null || scalingMode.Value == targetMode;
|
||||
bool scaling = AllowScaling && (targetMode == null || scalingMode.Value == targetMode);
|
||||
|
||||
var targetSize = scaling ? new Vector2(sizeX.Value, sizeY.Value) : Vector2.One;
|
||||
var targetPosition = scaling ? new Vector2(posX.Value, posY.Value) * (Vector2.One - targetSize) : Vector2.Zero;
|
||||
|
@ -48,6 +48,7 @@ namespace osu.Game.Input.Bindings
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.ToggleBeatmapListing),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.N }, GlobalAction.ToggleNotifications),
|
||||
new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.ToggleSkinEditor),
|
||||
|
||||
new KeyBinding(InputKey.Escape, GlobalAction.Back),
|
||||
new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back),
|
||||
@ -258,6 +259,9 @@ namespace osu.Game.Input.Bindings
|
||||
EditorNudgeLeft,
|
||||
|
||||
[Description("Nudge selection right")]
|
||||
EditorNudgeRight
|
||||
EditorNudgeRight,
|
||||
|
||||
[Description("Toggle skin editor")]
|
||||
ToggleSkinEditor,
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ using osu.Game.Utils;
|
||||
using LogLevel = osu.Framework.Logging.LogLevel;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Skinning.Editor;
|
||||
|
||||
namespace osu.Game
|
||||
{
|
||||
@ -79,6 +80,8 @@ namespace osu.Game
|
||||
|
||||
private BeatmapSetOverlay beatmapSetOverlay;
|
||||
|
||||
private SkinEditorOverlay skinEditor;
|
||||
|
||||
[Cached]
|
||||
private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender();
|
||||
|
||||
@ -597,6 +600,8 @@ namespace osu.Game
|
||||
screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
receptor = new BackButton.Receptor(),
|
||||
@ -685,6 +690,7 @@ namespace osu.Game
|
||||
var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true);
|
||||
loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true);
|
||||
loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true);
|
||||
loadComponentSingleFile(skinEditor = new SkinEditorOverlay(screenContainer), overlayContent.Add);
|
||||
|
||||
loadComponentSingleFile(new LoginOverlay
|
||||
{
|
||||
@ -968,6 +974,8 @@ namespace osu.Game
|
||||
|
||||
protected virtual void ScreenChanged(IScreen current, IScreen newScreen)
|
||||
{
|
||||
skinEditor.Reset();
|
||||
|
||||
switch (newScreen)
|
||||
{
|
||||
case IntroScreen intro:
|
||||
|
@ -108,17 +108,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a selection target and a function of truth, retrieve the correct ternary state for display.
|
||||
/// </summary>
|
||||
protected TernaryState GetStateFromSelection<T>(IEnumerable<T> selection, Func<T, bool> func)
|
||||
{
|
||||
if (selection.Any(func))
|
||||
return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate;
|
||||
|
||||
return TernaryState.False;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ternary state changes
|
||||
|
@ -236,6 +236,17 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
DeleteSelected();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a selection target and a function of truth, retrieve the correct ternary state for display.
|
||||
/// </summary>
|
||||
protected static TernaryState GetStateFromSelection<TObject>(IEnumerable<TObject> selection, Func<TObject, bool> func)
|
||||
{
|
||||
if (selection.Any(func))
|
||||
return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate;
|
||||
|
||||
return TernaryState.False;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever the deletion of items has been requested.
|
||||
/// </summary>
|
||||
@ -274,8 +285,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
int count = SelectedItems.Count;
|
||||
|
||||
SelectionBox.Text = count > 0 ? count.ToString() : string.Empty;
|
||||
|
||||
SelectionBox.FadeTo(count > 0 ? 1 : 0);
|
||||
|
||||
OnSelectionChanged();
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public class DefaultAccuracyCounter : PercentageCounter, IAccuracyCounter
|
||||
public class DefaultAccuracyCounter : PercentageCounter, IAccuracyCounter, ISkinnableComponent
|
||||
{
|
||||
private readonly Vector2 offset = new Vector2(-20, 5);
|
||||
|
||||
|
@ -11,7 +11,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public class DefaultComboCounter : RollingCounter<int>, IComboCounter
|
||||
public class DefaultComboCounter : RollingCounter<int>, IComboCounter, ISkinnableComponent
|
||||
{
|
||||
private readonly Vector2 offset = new Vector2(20, 5);
|
||||
|
||||
|
@ -16,7 +16,7 @@ using osu.Framework.Utils;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public class DefaultHealthDisplay : HealthDisplay, IHasAccentColour
|
||||
public class DefaultHealthDisplay : HealthDisplay, IHasAccentColour, ISkinnableComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// The base opacity of the glow.
|
||||
|
@ -8,7 +8,7 @@ using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public class DefaultScoreCounter : ScoreCounter
|
||||
public class DefaultScoreCounter : ScoreCounter, ISkinnableComponent
|
||||
{
|
||||
public DefaultScoreCounter()
|
||||
: base(6)
|
||||
|
@ -18,7 +18,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
{
|
||||
public class BarHitErrorMeter : HitErrorMeter
|
||||
public class BarHitErrorMeter : HitErrorMeter, ISkinnableComponent
|
||||
{
|
||||
private readonly Anchor alignment;
|
||||
|
||||
|
14
osu.Game/Screens/Play/HUD/ISkinnableComponent.cs
Normal file
14
osu.Game/Screens/Play/HUD/ISkinnableComponent.cs
Normal file
@ -0,0 +1,14 @@
|
||||
// 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 osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
/// <summary>
|
||||
/// Denotes a drawable which, as a drawable, can be adjusted via skinning specifications.
|
||||
/// </summary>
|
||||
public interface ISkinnableComponent : IDrawable
|
||||
{
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
/// <summary>
|
||||
/// Uses the 'x' symbol and has a pop-out effect while rolling over.
|
||||
/// </summary>
|
||||
public class LegacyComboCounter : CompositeDrawable, IComboCounter
|
||||
public class LegacyComboCounter : CompositeDrawable, IComboCounter, ISkinnableComponent
|
||||
{
|
||||
public Bindable<int> Current { get; } = new BindableInt { MinValue = 0, };
|
||||
|
||||
|
@ -14,6 +14,7 @@ using osu.Framework.Timing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
@ -71,30 +72,38 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
public SongProgress()
|
||||
{
|
||||
Masking = true;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
info = new SongProgressInfo
|
||||
new SongProgressDisplay
|
||||
{
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = info_height,
|
||||
},
|
||||
graph = new SongProgressGraph
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Height = graph_height,
|
||||
Margin = new MarginPadding { Bottom = bottom_bar_height },
|
||||
},
|
||||
bar = new SongProgressBar(bottom_bar_height, graph_height, handle_size)
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
OnSeek = time => RequestSeek?.Invoke(time),
|
||||
Masking = true,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
info = new SongProgressInfo
|
||||
{
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = info_height,
|
||||
},
|
||||
graph = new SongProgressGraph
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Height = graph_height,
|
||||
Margin = new MarginPadding { Bottom = bottom_bar_height },
|
||||
},
|
||||
bar = new SongProgressBar(bottom_bar_height, graph_height, handle_size)
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
OnSeek = time => RequestSeek?.Invoke(time),
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -175,5 +184,11 @@ namespace osu.Game.Screens.Play
|
||||
float finalMargin = bottom_bar_height + (AllowSeeking.Value ? handle_size.Y : 0) + (ShowGraph.Value ? graph_height : 0);
|
||||
info.TransformTo(nameof(info.Margin), new MarginPadding { Bottom = finalMargin }, transition_duration, Easing.In);
|
||||
}
|
||||
|
||||
public class SongProgressDisplay : Container, ISkinnableComponent
|
||||
{
|
||||
// TODO: move actual implementation into this.
|
||||
// exists for skin customisation purposes.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
78
osu.Game/Skinning/Editor/SkinBlueprint.cs
Normal file
78
osu.Game/Skinning/Editor/SkinBlueprint.cs
Normal file
@ -0,0 +1,78 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Skinning.Editor
|
||||
{
|
||||
public class SkinBlueprint : SelectionBlueprint<ISkinnableComponent>
|
||||
{
|
||||
private Container box;
|
||||
|
||||
private Drawable drawable => (Drawable)Item;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the blueprint should be shown even when the <see cref="SelectionBlueprint{T}.Item"/> is not alive.
|
||||
/// </summary>
|
||||
protected virtual bool AlwaysShowWhenSelected => false;
|
||||
|
||||
protected override bool ShouldBeAlive => (drawable.IsAlive && Item.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected);
|
||||
|
||||
public SkinBlueprint(ISkinnableComponent component)
|
||||
: base(component)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
box = new Container
|
||||
{
|
||||
Colour = colours.Yellow,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0.2f,
|
||||
AlwaysPresent = true,
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private Quad drawableQuad;
|
||||
|
||||
public override Quad ScreenSpaceDrawQuad => drawableQuad;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
drawableQuad = drawable.ScreenSpaceDrawQuad;
|
||||
var quad = ToLocalSpace(drawable.ScreenSpaceDrawQuad);
|
||||
|
||||
box.Position = quad.TopLeft;
|
||||
box.Size = quad.Size;
|
||||
box.Rotation = drawable.Rotation;
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => drawable.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
public override Vector2 ScreenSpaceSelectionPoint => drawable.ScreenSpaceDrawQuad.Centre;
|
||||
|
||||
public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad;
|
||||
}
|
||||
}
|
43
osu.Game/Skinning/Editor/SkinBlueprintContainer.cs
Normal file
43
osu.Game/Skinning/Editor/SkinBlueprintContainer.cs
Normal file
@ -0,0 +1,43 @@
|
||||
// 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.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
|
||||
namespace osu.Game.Skinning.Editor
|
||||
{
|
||||
public class SkinBlueprintContainer : BlueprintContainer<ISkinnableComponent>
|
||||
{
|
||||
private readonly Drawable target;
|
||||
|
||||
public SkinBlueprintContainer(Drawable target)
|
||||
{
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
checkForComponents();
|
||||
}
|
||||
|
||||
private void checkForComponents()
|
||||
{
|
||||
foreach (var c in target.ChildrenOfType<ISkinnableComponent>().ToArray()) AddBlueprintFor(c);
|
||||
|
||||
// We'd hope to eventually be running this in a more sensible way, but this handles situations where new drawables become present (ie. during ongoing gameplay)
|
||||
// or when drawables in the target are loaded asynchronously and may not be immediately available when this BlueprintContainer is loaded.
|
||||
Scheduler.AddDelayed(checkForComponents, 1000);
|
||||
}
|
||||
|
||||
protected override SelectionHandler<ISkinnableComponent> CreateSelectionHandler() => new SkinSelectionHandler();
|
||||
|
||||
protected override SelectionBlueprint<ISkinnableComponent> CreateBlueprintFor(ISkinnableComponent component)
|
||||
=> new SkinBlueprint(component);
|
||||
}
|
||||
}
|
79
osu.Game/Skinning/Editor/SkinEditor.cs
Normal file
79
osu.Game/Skinning/Editor/SkinEditor.cs
Normal file
@ -0,0 +1,79 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
|
||||
namespace osu.Game.Skinning.Editor
|
||||
{
|
||||
public class SkinEditor : FocusedOverlayContainer
|
||||
{
|
||||
public const double TRANSITION_DURATION = 500;
|
||||
|
||||
private readonly Drawable target;
|
||||
|
||||
private OsuTextFlowContainer headerText;
|
||||
|
||||
protected override bool StartHidden => true;
|
||||
|
||||
public SkinEditor(Drawable target)
|
||||
{
|
||||
this.target = target;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
InternalChild = new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
headerText = new OsuTextFlowContainer
|
||||
{
|
||||
TextAnchor = Anchor.TopCentre,
|
||||
Padding = new MarginPadding(20),
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.X
|
||||
},
|
||||
new SkinBlueprintContainer(target),
|
||||
}
|
||||
};
|
||||
|
||||
headerText.AddParagraph("Skin editor (preview)", cp => cp.Font = OsuFont.Default.With(size: 24));
|
||||
headerText.AddParagraph("This is a preview of what is to come. Changes are lost on changing screens.", cp =>
|
||||
{
|
||||
cp.Font = OsuFont.Default.With(size: 12);
|
||||
cp.Colour = colours.Yellow;
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
Show();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e) => true;
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e) => true;
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
this.FadeIn(TRANSITION_DURATION, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
this.FadeOut(TRANSITION_DURATION, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
97
osu.Game/Skinning/Editor/SkinEditorOverlay.cs
Normal file
97
osu.Game/Skinning/Editor/SkinEditorOverlay.cs
Normal file
@ -0,0 +1,97 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Input.Bindings;
|
||||
|
||||
namespace osu.Game.Skinning.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// A container which handles loading a skin editor on user request for a specified target.
|
||||
/// This also handles the scaling / positioning adjustment of the target.
|
||||
/// </summary>
|
||||
public class SkinEditorOverlay : CompositeDrawable, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
private readonly ScalingContainer target;
|
||||
private SkinEditor skinEditor;
|
||||
|
||||
private const float visible_target_scale = 0.8f;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
public SkinEditorOverlay(ScalingContainer target)
|
||||
{
|
||||
this.target = target;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
public bool OnPressed(GlobalAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case GlobalAction.Back:
|
||||
if (skinEditor?.State.Value == Visibility.Visible)
|
||||
{
|
||||
skinEditor.ToggleVisibility();
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case GlobalAction.ToggleSkinEditor:
|
||||
if (skinEditor == null)
|
||||
{
|
||||
LoadComponentAsync(skinEditor = new SkinEditor(target), AddInternal);
|
||||
skinEditor.State.BindValueChanged(editorVisibilityChanged);
|
||||
}
|
||||
else
|
||||
skinEditor.ToggleVisibility();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void editorVisibilityChanged(ValueChangedEvent<Visibility> visibility)
|
||||
{
|
||||
if (visibility.NewValue == Visibility.Visible)
|
||||
{
|
||||
target.ScaleTo(visible_target_scale, SkinEditor.TRANSITION_DURATION, Easing.OutQuint);
|
||||
|
||||
target.Masking = true;
|
||||
target.BorderThickness = 5;
|
||||
target.BorderColour = colours.Yellow;
|
||||
target.AllowScaling = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
target.BorderThickness = 0;
|
||||
target.AllowScaling = true;
|
||||
|
||||
target.ScaleTo(1, SkinEditor.TRANSITION_DURATION, Easing.OutQuint).OnComplete(_ => target.Masking = false);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnReleased(GlobalAction action)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exit any existing skin editor due to the game state changing.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
skinEditor?.Hide();
|
||||
skinEditor?.Expire();
|
||||
skinEditor = null;
|
||||
}
|
||||
}
|
||||
}
|
132
osu.Game/Skinning/Editor/SkinSelectionHandler.cs
Normal file
132
osu.Game/Skinning/Editor/SkinSelectionHandler.cs
Normal file
@ -0,0 +1,132 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Skinning.Editor
|
||||
{
|
||||
public class SkinSelectionHandler : SelectionHandler<ISkinnableComponent>
|
||||
{
|
||||
public override bool HandleRotation(float angle)
|
||||
{
|
||||
// TODO: this doesn't correctly account for origin/anchor specs being different in a multi-selection.
|
||||
foreach (var c in SelectedBlueprints)
|
||||
((Drawable)c.Item).Rotation += angle;
|
||||
|
||||
return base.HandleRotation(angle);
|
||||
}
|
||||
|
||||
public override bool HandleScale(Vector2 scale, Anchor anchor)
|
||||
{
|
||||
adjustScaleFromAnchor(ref scale, anchor);
|
||||
|
||||
foreach (var c in SelectedBlueprints)
|
||||
// TODO: this is temporary and will be fixed with a separate refactor of selection transform logic.
|
||||
((Drawable)c.Item).Scale += scale * 0.02f;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool HandleMovement(MoveSelectionEvent<ISkinnableComponent> moveEvent)
|
||||
{
|
||||
foreach (var c in SelectedBlueprints)
|
||||
{
|
||||
Drawable drawable = (Drawable)c.Item;
|
||||
drawable.Position += drawable.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnSelectionChanged()
|
||||
{
|
||||
base.OnSelectionChanged();
|
||||
|
||||
SelectionBox.CanRotate = true;
|
||||
SelectionBox.CanScaleX = true;
|
||||
SelectionBox.CanScaleY = true;
|
||||
SelectionBox.CanReverse = false;
|
||||
}
|
||||
|
||||
protected override void DeleteItems(IEnumerable<ISkinnableComponent> items)
|
||||
{
|
||||
foreach (var i in items)
|
||||
{
|
||||
((Drawable)i).Expire();
|
||||
SelectedItems.Remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint<ISkinnableComponent>> selection)
|
||||
{
|
||||
yield return new OsuMenuItem("Anchor")
|
||||
{
|
||||
Items = createAnchorItems().ToArray()
|
||||
};
|
||||
|
||||
foreach (var item in base.GetContextMenuItemsForSelection(selection))
|
||||
yield return item;
|
||||
|
||||
IEnumerable<AnchorMenuItem> createAnchorItems()
|
||||
{
|
||||
var displayableAnchors = new[]
|
||||
{
|
||||
Anchor.TopLeft,
|
||||
Anchor.TopCentre,
|
||||
Anchor.TopRight,
|
||||
Anchor.CentreLeft,
|
||||
Anchor.Centre,
|
||||
Anchor.CentreRight,
|
||||
Anchor.BottomLeft,
|
||||
Anchor.BottomCentre,
|
||||
Anchor.BottomRight,
|
||||
};
|
||||
|
||||
return displayableAnchors.Select(a =>
|
||||
{
|
||||
return new AnchorMenuItem(a, selection, _ => applyAnchor(a))
|
||||
{
|
||||
State = { Value = GetStateFromSelection(selection, c => ((Drawable)c.Item).Anchor == a) }
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void applyAnchor(Anchor anchor)
|
||||
{
|
||||
foreach (var item in SelectedItems)
|
||||
((Drawable)item).Anchor = anchor;
|
||||
}
|
||||
|
||||
private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference)
|
||||
{
|
||||
// cancel out scale in axes we don't care about (based on which drag handle was used).
|
||||
if ((reference & Anchor.x1) > 0) scale.X = 0;
|
||||
if ((reference & Anchor.y1) > 0) scale.Y = 0;
|
||||
|
||||
// reverse the scale direction if dragging from top or left.
|
||||
if ((reference & Anchor.x0) > 0) scale.X = -scale.X;
|
||||
if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y;
|
||||
}
|
||||
|
||||
public class AnchorMenuItem : TernaryStateMenuItem
|
||||
{
|
||||
public AnchorMenuItem(Anchor anchor, IEnumerable<SelectionBlueprint<ISkinnableComponent>> selection, Action<TernaryState> action)
|
||||
: base(anchor.ToString(), getNextState, MenuItemType.Standard, action)
|
||||
{
|
||||
}
|
||||
|
||||
private static TernaryState getNextState(TernaryState state) => TernaryState.True;
|
||||
}
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
public class LegacyAccuracyCounter : PercentageCounter, IAccuracyCounter
|
||||
public class LegacyAccuracyCounter : PercentageCounter, IAccuracyCounter, ISkinnableComponent
|
||||
{
|
||||
private readonly ISkin skin;
|
||||
|
||||
|
@ -16,7 +16,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
public class LegacyHealthDisplay : CompositeDrawable, IHealthDisplay
|
||||
public class LegacyHealthDisplay : CompositeDrawable, IHealthDisplay, ISkinnableComponent
|
||||
{
|
||||
private const double epic_cutoff = 0.5;
|
||||
|
||||
|
@ -5,11 +5,12 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
public class LegacyScoreCounter : ScoreCounter
|
||||
public class LegacyScoreCounter : ScoreCounter, ISkinnableComponent
|
||||
{
|
||||
private readonly ISkin skin;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user