diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index 762a9c418d..2e5fa59d20 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -16,6 +16,11 @@ namespace osu.Android protected override void OnCreate(Bundle savedInstanceState) { + // The default current directory on android is '/'. + // On some devices '/' maps to the app data directory. On others it maps to the root of the internal storage. + // In order to have a consistent current directory on all devices the full path of the app data directory is set as the current directory. + System.Environment.CurrentDirectory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal); + base.OnCreate(savedInstanceState); Window.AddFlags(WindowManagerFlags.Fullscreen); diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index f576c43e52..2fba0639da 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -65,24 +65,27 @@ namespace osu.Game.Rulesets.Mania.Edit private void performDragMovement(MoveSelectionEvent moveEvent) { + float delta = moveEvent.InstantDelta.Y; + + // When scrolling downwards the anchor position is at the bottom of the screen, however the movement event assumes the anchor is at the top of the screen. + // This causes the delta to assume a positive hitobject position, and which can be corrected for by subtracting the parent height. + if (scrollingInfo.Direction.Value == ScrollingDirection.Down) + delta -= moveEvent.Blueprint.HitObject.Parent.DrawHeight; + foreach (var b in SelectedBlueprints) { var hitObject = b.HitObject; - var objectParent = (HitObjectContainer)hitObject.Parent; - // Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame - // without the position having been updated by the parenting ScrollingHitObjectContainer - hitObject.Y += moveEvent.InstantDelta.Y; + // StartTime could be used to adjust the position if only one movement event was received per frame. + // However this is not the case and ScrollingHitObjectContainer performs movement in UpdateAfterChildren() so the position must also be updated to be valid for further movement events + hitObject.Y += delta; - float targetPosition; + float targetPosition = hitObject.Position.Y; - // If we're scrolling downwards, a position of 0 is actually further away from the hit target - // so we need to flip the vertical coordinate in the hitobject container's space + // The scrolling algorithm always assumes an anchor at the top of the screen, so the position must be flipped when scrolling downwards to reflect a top anchor if (scrollingInfo.Direction.Value == ScrollingDirection.Down) - targetPosition = -hitObject.Position.Y; - else - targetPosition = hitObject.Position.Y; + targetPosition = -targetPosition; objectParent.Remove(hitObject); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs index d4cdabdb07..cf24306623 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs @@ -52,6 +52,13 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position); } + [Test] + public void TestStackedHitObject() + { + AddStep("set stacking", () => hitCircle.StackHeight = 5); + AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.StackedPosition); + } + private class TestBlueprint : HitCircleSelectionBlueprint { public new HitCirclePiece CirclePiece => base.CirclePiece; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/BlueprintPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/BlueprintPiece.cs index 95e926fdfa..b9c77d3f56 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/BlueprintPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/BlueprintPiece.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints /// The to reference properties from. public virtual void UpdateFrom(T hitObject) { - Position = hitObject.Position; + Position = hitObject.StackedPosition; } } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 41a02deaca..0aa8661fd3 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -2,14 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Skinning; using osuTK; @@ -23,12 +20,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private bool cursorExpand; - private Bindable cursorScale; - private Bindable autoCursorScale; - private readonly IBindable beatmap = new Bindable(); - private Container expandTarget; - private Drawable scaleTarget; public OsuCursor() { @@ -43,43 +35,19 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, IBindable beatmap) + private void load() { InternalChild = expandTarget = new Container { RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = scaleTarget = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) + Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) { Origin = Anchor.Centre, Anchor = Anchor.Centre, } }; - - this.beatmap.BindTo(beatmap); - this.beatmap.ValueChanged += _ => calculateScale(); - - cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); - cursorScale.ValueChanged += _ => calculateScale(); - - autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); - autoCursorScale.ValueChanged += _ => calculateScale(); - - calculateScale(); - } - - private void calculateScale() - { - float scale = cursorScale.Value; - - if (autoCursorScale.Value && beatmap.Value != null) - { - // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. - scale *= 1f - 0.7f * (1f + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY; - } - - scaleTarget.Scale = new Vector2(scale); } private const float pressed_scale = 1.2f; diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 6dbdf0114d..6433ced624 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -8,6 +8,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; +using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.UI; using osu.Game.Skinning; @@ -27,6 +29,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly Drawable cursorTrail; + public Bindable CursorScale; + private Bindable userCursorScale; + private Bindable autoCursorScale; + private readonly IBindable beatmap = new Bindable(); + public OsuCursorContainer() { InternalChild = fadeContainer = new Container @@ -37,9 +44,36 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } [BackgroundDependencyLoader(true)] - private void load(OsuRulesetConfigManager config) + private void load(OsuConfigManager config, OsuRulesetConfigManager rulesetConfig, IBindable beatmap) { - config?.BindWith(OsuRulesetSetting.ShowCursorTrail, showTrail); + rulesetConfig?.BindWith(OsuRulesetSetting.ShowCursorTrail, showTrail); + + this.beatmap.BindTo(beatmap); + this.beatmap.ValueChanged += _ => calculateScale(); + + userCursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); + userCursorScale.ValueChanged += _ => calculateScale(); + + autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); + autoCursorScale.ValueChanged += _ => calculateScale(); + + CursorScale = new Bindable(); + CursorScale.ValueChanged += e => ActiveCursor.Scale = cursorTrail.Scale = new Vector2(e.NewValue); + + calculateScale(); + } + + private void calculateScale() + { + float scale = userCursorScale.Value; + + if (autoCursorScale.Value && beatmap.Value != null) + { + // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. + scale *= 1f - 0.7f * (1f + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY; + } + + CursorScale.Value = scale; } protected override void LoadComplete() @@ -95,13 +129,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor protected override void PopIn() { fadeContainer.FadeTo(1, 300, Easing.OutQuint); - ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); + ActiveCursor.ScaleTo(CursorScale.Value, 400, Easing.OutQuint); } protected override void PopOut() { fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); - ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint); + ActiveCursor.ScaleTo(CursorScale.Value * 0.8f, 450, Easing.OutQuint); } private class DefaultCursorTrail : CursorTrail diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 9e5df0d6b1..8347d255fa 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -1,15 +1,15 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.UI.Cursor; -using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osuTK; using osuTK.Graphics; @@ -18,9 +18,11 @@ namespace osu.Game.Rulesets.Osu.UI { public class OsuResumeOverlay : ResumeOverlay { + private Container cursorScaleContainer; private OsuClickToResumeCursor clickToResumeCursor; - private GameplayCursorContainer localCursorContainer; + private OsuCursorContainer localCursorContainer; + private Bindable localCursorScale; public override CursorContainer LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null; @@ -29,22 +31,35 @@ namespace osu.Game.Rulesets.Osu.UI [BackgroundDependencyLoader] private void load() { - Add(clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume }); + Add(cursorScaleContainer = new Container + { + RelativePositionAxes = Axes.Both, + Child = clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume } + }); } public override void Show() { base.Show(); - clickToResumeCursor.ShowAt(GameplayCursor.ActiveCursor.Position); + GameplayCursor.ActiveCursor.Hide(); + cursorScaleContainer.MoveTo(GameplayCursor.ActiveCursor.Position); + clickToResumeCursor.Appear(); if (localCursorContainer == null) + { Add(localCursorContainer = new OsuCursorContainer()); + + localCursorScale = new Bindable(); + localCursorScale.BindTo(localCursorContainer.CursorScale); + localCursorScale.BindValueChanged(scale => cursorScaleContainer.Scale = new Vector2(scale.NewValue), true); + } } public override void Hide() { localCursorContainer?.Expire(); localCursorContainer = null; + GameplayCursor.ActiveCursor.Show(); base.Hide(); } @@ -82,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.UI case OsuAction.RightButton: if (!IsHovered) return false; - this.ScaleTo(new Vector2(2), TRANSITION_TIME, Easing.OutQuint); + this.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint); ResumeRequested?.Invoke(); return true; @@ -93,11 +108,10 @@ namespace osu.Game.Rulesets.Osu.UI public bool OnReleased(OsuAction action) => false; - public void ShowAt(Vector2 activeCursorPosition) => Schedule(() => + public void Appear() => Schedule(() => { updateColour(); - this.MoveTo(activeCursorPosition); - this.ScaleTo(new Vector2(4)).Then().ScaleTo(Vector2.One, 1000, Easing.OutQuint); + this.ScaleTo(4).Then().ScaleTo(1, 1000, Easing.OutQuint); }); private void updateColour() diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs new file mode 100644 index 0000000000..6d7159a825 --- /dev/null +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -0,0 +1,143 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Textures; +using osu.Framework.Testing; +using osu.Game.Audio; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; +using osuTK.Graphics; + +namespace osu.Game.Tests.Gameplay +{ + [HeadlessTest] + public class TestSceneHitObjectAccentColour : OsuTestScene + { + private Container skinContainer; + + [SetUp] + public void Setup() => Schedule(() => Child = skinContainer = new SkinProvidingContainer(new TestSkin())); + + [Test] + public void TestChangeComboIndexBeforeLoad() + { + TestDrawableHitObject hitObject = null; + + AddStep("set combo and add hitobject", () => + { + hitObject = new TestDrawableHitObject(); + hitObject.HitObject.ComboIndex = 1; + + skinContainer.Add(hitObject); + }); + + AddAssert("combo colour is green", () => hitObject.AccentColour.Value == Color4.Green); + } + + [Test] + public void TestChangeComboIndexDuringLoad() + { + TestDrawableHitObject hitObject = null; + + AddStep("add hitobject and set combo", () => + { + skinContainer.Add(hitObject = new TestDrawableHitObject()); + hitObject.HitObject.ComboIndex = 1; + }); + + AddAssert("combo colour is green", () => hitObject.AccentColour.Value == Color4.Green); + } + + [Test] + public void TestChangeComboIndexAfterLoad() + { + TestDrawableHitObject hitObject = null; + + AddStep("add hitobject", () => skinContainer.Add(hitObject = new TestDrawableHitObject())); + AddAssert("combo colour is red", () => hitObject.AccentColour.Value == Color4.Red); + + AddStep("change combo", () => hitObject.HitObject.ComboIndex = 1); + AddAssert("combo colour is green", () => hitObject.AccentColour.Value == Color4.Green); + } + + private class TestDrawableHitObject : DrawableHitObject + { + public TestDrawableHitObject() + : base(new TestHitObjectWithCombo()) + { + } + } + + private class TestHitObjectWithCombo : HitObject, IHasComboInformation + { + public bool NewCombo { get; } = false; + public int ComboOffset { get; } = 0; + + public Bindable IndexInCurrentComboBindable { get; } = new Bindable(); + + public int IndexInCurrentCombo + { + get => IndexInCurrentComboBindable.Value; + set => IndexInCurrentComboBindable.Value = value; + } + + public Bindable ComboIndexBindable { get; } = new Bindable(); + + public int ComboIndex + { + get => ComboIndexBindable.Value; + set => ComboIndexBindable.Value = value; + } + + public Bindable LastInComboBindable { get; } = new Bindable(); + + public bool LastInCombo + { + get => LastInComboBindable.Value; + set => LastInComboBindable.Value = value; + } + } + + private class TestSkin : ISkin + { + public readonly List ComboColours = new List + { + Color4.Red, + Color4.Green + }; + + public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException(); + + public Texture GetTexture(string componentName) => throw new NotImplementedException(); + + public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + + public IBindable GetConfig(TLookup lookup) + { + switch (lookup) + { + case GlobalSkinConfiguration global: + switch (global) + { + case GlobalSkinConfiguration.ComboColours: + return SkinUtils.As(new Bindable>(ComboColours)); + } + + break; + } + + throw new NotImplementedException(); + } + } + } +} diff --git a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs new file mode 100644 index 0000000000..42a3b4cf43 --- /dev/null +++ b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class BeatmapSetInfoEqualityTest + { + [Test] + public void TestOnlineWithOnline() + { + var ourInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 }; + var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 }; + + Assert.AreEqual(ourInfo, otherInfo); + } + + [Test] + public void TestDatabasedWithDatabased() + { + var ourInfo = new BeatmapSetInfo { ID = 123 }; + var otherInfo = new BeatmapSetInfo { ID = 123 }; + + Assert.AreEqual(ourInfo, otherInfo); + } + + [Test] + public void TestDatabasedWithOnline() + { + var ourInfo = new BeatmapSetInfo { ID = 123, OnlineBeatmapSetID = 12 }; + var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 12 }; + + Assert.AreEqual(ourInfo, otherInfo); + } + + [Test] + public void TestCheckNullID() + { + var ourInfo = new BeatmapSetInfo { Status = BeatmapSetOnlineStatus.Loved }; + var otherInfo = new BeatmapSetInfo { Status = BeatmapSetOnlineStatus.Approved }; + + Assert.AreNotEqual(ourInfo, otherInfo); + } + } +} diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 03bc7c7312..a8b83dca38 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -63,6 +63,21 @@ namespace osu.Game.Beatmaps public bool Protected { get; set; } - public bool Equals(BeatmapSetInfo other) => OnlineBeatmapSetID == other?.OnlineBeatmapSetID; + public bool Equals(BeatmapSetInfo other) + { + if (other == null) + return false; + + if (ID != 0 && other.ID != 0) + return ID == other.ID; + + if (OnlineBeatmapSetID.HasValue && other.OnlineBeatmapSetID.HasValue) + return OnlineBeatmapSetID == other.OnlineBeatmapSetID; + + if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash)) + return Hash == other.Hash; + + return ReferenceEquals(this, other); + } } } diff --git a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs index be417f4aac..f91d2e3323 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs @@ -121,7 +121,7 @@ namespace osu.Game.Overlays.AccountCreation multiAccountExplanationText.AddText("? osu! has a policy of "); multiAccountExplanationText.AddText("one account per person!", cp => cp.Colour = colours.Yellow); multiAccountExplanationText.AddText(" Please be aware that creating more than one account per person may result in "); - multiAccountExplanationText.AddText("permanent deactivation of accounts", cp => cp.Colour = colours.Yellow); + multiAccountExplanationText.AddText("permanent deactivation of accounts", cp => cp.Colour = colours.Yellow); multiAccountExplanationText.AddText("."); furtherAssistance.AddText("Need further assistance? Contact us via our "); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c769ccae41..51155ce3fd 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -64,7 +64,8 @@ namespace osu.Game.Rulesets.Edit { drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, workingBeatmap, Array.Empty())) { - Clock = framedClock + Clock = framedClock, + ProcessCustomClock = false }; } catch (Exception e) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 0a8648516e..19bb95d36f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (HitObject is IHasComboInformation combo) { comboIndexBindable = combo.ComboIndexBindable.GetBoundCopy(); - comboIndexBindable.BindValueChanged(_ => updateAccentColour()); + comboIndexBindable.BindValueChanged(_ => updateAccentColour(), true); } updateState(ArmedState.Idle, true); @@ -161,11 +161,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { var drawableNested = CreateNestedHitObject(h) ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}."); - drawableNested.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r); - drawableNested.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r); - drawableNested.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); - - nestedHitObjects.Value.Add(drawableNested); + addNested(drawableNested); AddNestedHitObject(drawableNested); } } @@ -183,14 +179,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// [Obsolete("Use AddNestedHitObject() / ClearNestedHitObjects() / CreateNestedHitObject() instead.")] // can be removed 20200417 - protected virtual void AddNested(DrawableHitObject h) - { - h.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r); - h.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r); - h.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); - - nestedHitObjects.Value.Add(h); - } + protected virtual void AddNested(DrawableHitObject h) => addNested(h); /// /// Invoked by the base to remove all previously-added nested s. @@ -206,6 +195,17 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The drawable representation for . protected virtual DrawableHitObject CreateNestedHitObject(HitObject hitObject) => null; + private void addNested(DrawableHitObject hitObject) + { + // Todo: Exists for legacy purposes, can be removed 20200417 + + hitObject.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r); + hitObject.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r); + hitObject.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); + + nestedHitObjects.Value.Add(hitObject); + } + #region State / Transform Management /// diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 039e324e4b..bef608036b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -124,20 +124,12 @@ namespace osu.Game.Screens.Edit.Compose.Components if (blueprint == null) return; - if (hitObject.IsLoaded) - addBlueprint(); - else - hitObject.OnLoadComplete += _ => addBlueprint(); + blueprint.Selected += onBlueprintSelected; + blueprint.Deselected += onBlueprintDeselected; + blueprint.SelectionRequested += onSelectionRequested; + blueprint.DragRequested += onDragRequested; - void addBlueprint() - { - blueprint.Selected += onBlueprintSelected; - blueprint.Deselected += onBlueprintDeselected; - blueprint.SelectionRequested += onSelectionRequested; - blueprint.DragRequested += onDragRequested; - - selectionBlueprints.Add(blueprint); - } + selectionBlueprints.Add(blueprint); } private void removeBlueprintFor(DrawableHitObject hitObject) => removeBlueprintFor(hitObject.HitObject); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7f08c2f8b9..35408e4003 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -173,6 +173,12 @@ namespace osu.Game.Screens.Edit bottomBackground.Colour = colours.Gray2; } + protected override void Update() + { + base.Update(); + clock.ProcessFrame(); + } + protected override bool OnKeyDown(KeyDownEvent e) { switch (e.Key)