diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index c5ea9e6204..eb1695b3df 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -18,9 +18,11 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osu.Game.Storyboards; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Gameplay { @@ -37,6 +39,12 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + [Cached] + private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + + [Cached] + private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock()); + protected override bool HasCustomSteps => true; [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 505f73159f..2d12645811 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -8,12 +8,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Framework.Timing; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Skinning; +using osu.Game.Tests.Beatmaps; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -30,6 +32,12 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + [Cached] + private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + + [Cached] + private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock()); + // best way to check without exposing. private Drawable hideTarget => hudOverlay.KeyCounter; private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index 8f33f6fac5..8150252d45 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -5,11 +5,13 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Framework.Timing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Skinning.Editor; +using osu.Game.Tests.Beatmaps; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -22,6 +24,12 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + [Cached] + private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + + [Cached] + private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock()); + [SetUpSteps] public void SetUpSteps() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index cdf349ff7f..ac5e408d90 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -10,11 +10,13 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Framework.Timing; 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.Tests.Beatmaps; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -29,6 +31,12 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + [Cached] + private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + + [Cached] + private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock()); + private IEnumerable hudOverlays => CreatedDrawables.OfType(); // best way to check without exposing. diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index c0d0ea0721..7a1f724cfb 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -42,12 +42,10 @@ namespace osu.Game.Screens.Play.HUD private const float alpha_when_invalid = 0.3f; - [CanBeNull] - [Resolved(CanBeNull = true)] + [Resolved] private ScoreProcessor scoreProcessor { get; set; } - [Resolved(CanBeNull = true)] - [CanBeNull] + [Resolved] private GameplayState gameplayState { get; set; } [CanBeNull] diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index b27a9c5f5d..e620abb90f 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -73,9 +73,12 @@ namespace osu.Game.Screens.Play [Resolved(canBeNull: true)] private Player player { get; set; } - [Resolved(canBeNull: true)] + [Resolved] private GameplayClock gameplayClock { get; set; } + [Resolved(canBeNull: true)] + private DrawableRuleset drawableRuleset { get; set; } + private IClock referenceClock; public bool UsesFixedAnchor { get; set; } @@ -113,7 +116,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, OsuConfigManager config, DrawableRuleset drawableRuleset) + private void load(OsuColour colours, OsuConfigManager config) { base.LoadComplete(); diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 483e365e78..a586cdcd54 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -2,24 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Framework.Utils; -using osu.Game.Beatmaps; +using osu.Framework.Logging; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Components; using osuTK; @@ -29,26 +22,19 @@ namespace osu.Game.Skinning.Editor { public Action RequestPlacement; - [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor(new DummyRuleset()) - { - Combo = { Value = RNG.Next(1, 1000) }, - TotalScore = { Value = RNG.Next(1000, 10000000) } - }; + private readonly CompositeDrawable target; - [Cached(typeof(HealthProcessor))] - private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); - - public SkinComponentToolbox() + public SkinComponentToolbox(CompositeDrawable target = null) : base("Components") { + this.target = target; } + private FillFlowContainer fill; + [BackgroundDependencyLoader] private void load() { - FillFlowContainer fill; - Child = fill = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -57,6 +43,13 @@ namespace osu.Game.Skinning.Editor Spacing = new Vector2(2) }; + reloadComponents(); + } + + private void reloadComponents() + { + fill.Clear(); + var skinnableTypes = typeof(OsuGame).Assembly.GetTypes() .Where(t => !t.IsInterface) .Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)) @@ -64,18 +57,10 @@ namespace osu.Game.Skinning.Editor .ToArray(); foreach (var type in skinnableTypes) - { - var component = attemptAddComponent(type); - - if (component != null) - { - component.RequestPlacement = t => RequestPlacement?.Invoke(t); - fill.Add(component); - } - } + attemptAddComponent(type); } - private static ToolboxComponentButton attemptAddComponent(Type type) + private void attemptAddComponent(Type type) { try { @@ -83,14 +68,21 @@ namespace osu.Game.Skinning.Editor Debug.Assert(instance != null); - if (!((ISkinnableDrawable)instance).IsEditable) - return null; + if (!((ISkinnableDrawable)instance).IsEditable) return; - return new ToolboxComponentButton(instance); + fill.Add(new ToolboxComponentButton(instance, target) + { + RequestPlacement = t => RequestPlacement?.Invoke(t) + }); } - catch + catch (DependencyNotRegisteredException) { - return null; + // This loading code relies on try-catching any dependency injection errors to know which components are valid for the current target screen. + // If a screen can't provide the required dependencies, a skinnable component should not be displayed in the list. + } + catch (Exception e) + { + Logger.Error(e, $"Skin component {type} could not be loaded in the editor component list due to an error"); } } @@ -101,6 +93,7 @@ namespace osu.Game.Skinning.Editor public override bool PropagateNonPositionalInputSubTree => false; private readonly Drawable component; + private readonly CompositeDrawable dependencySource; public Action RequestPlacement; @@ -109,9 +102,10 @@ namespace osu.Game.Skinning.Editor private const float contracted_size = 60; private const float expanded_size = 120; - public ToolboxComponentButton(Drawable component) + public ToolboxComponentButton(Drawable component, CompositeDrawable dependencySource) { this.component = component; + this.dependencySource = dependencySource; Enabled.Value = true; @@ -143,7 +137,7 @@ namespace osu.Game.Skinning.Editor RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(10) { Bottom = 20 }, Masking = true, - Child = innerContainer = new Container + Child = innerContainer = new DependencyBorrowingContainer(dependencySource) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -186,14 +180,17 @@ namespace osu.Game.Skinning.Editor } } - private class DummyRuleset : Ruleset + public class DependencyBorrowingContainer : Container { - public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException(); - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); - public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); - public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new NotImplementedException(); - public override string Description => string.Empty; - public override string ShortName => string.Empty; + private readonly CompositeDrawable donor; + + public DependencyBorrowingContainer(CompositeDrawable donor) + { + this.donor = donor; + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => + new DependencyContainer(donor?.Dependencies ?? base.CreateChildDependencies(parent)); } } } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 6fe3df6e8a..4cc7e0bcdb 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -49,6 +49,7 @@ namespace osu.Game.Skinning.Editor private Container content; + private EditorSidebar componentsSidebar; private EditorSidebar settingsSidebar; public SkinEditor() @@ -145,16 +146,7 @@ namespace osu.Game.Skinning.Editor { new Drawable[] { - new EditorSidebar - { - Children = new[] - { - new SkinComponentToolbox - { - RequestPlacement = placeComponent - }, - } - }, + componentsSidebar = new EditorSidebar(), content = new Container { Depth = float.MaxValue, @@ -200,7 +192,15 @@ namespace osu.Game.Skinning.Editor Scheduler.AddOnce(loadBlueprintContainer); Scheduler.AddOnce(populateSettings); - void loadBlueprintContainer() => content.Child = new SkinBlueprintContainer(targetScreen); + void loadBlueprintContainer() + { + content.Child = new SkinBlueprintContainer(targetScreen); + + componentsSidebar.Child = new SkinComponentToolbox(getFirstTarget() as CompositeDrawable) + { + RequestPlacement = placeComponent + }; + } } private void skinChanged() @@ -227,12 +227,7 @@ namespace osu.Game.Skinning.Editor private void placeComponent(Type type) { - var target = availableTargets.FirstOrDefault()?.Target; - - if (target == null) - return; - - var targetContainer = getTarget(target.Value); + var targetContainer = getFirstTarget(); if (targetContainer == null) return; @@ -263,6 +258,8 @@ namespace osu.Game.Skinning.Editor private IEnumerable availableTargets => targetScreen.ChildrenOfType(); + private ISkinnableTarget getFirstTarget() => availableTargets.FirstOrDefault(); + private ISkinnableTarget getTarget(SkinnableTarget target) { return availableTargets.FirstOrDefault(c => c.Target == target);