diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs similarity index 75% rename from osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 72fc6d8bd2..8904b54b0d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -19,18 +19,22 @@ using osu.Game.Screens.Play.HUD.HitErrorMeters; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneBarHitErrorMeter : OsuTestScene + public class TestSceneHitErrorMeter : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(HitErrorMeter), + typeof(BarHitErrorMeter), + typeof(ColourHitErrorMeter) }; - private HitErrorMeter meter; - private HitErrorMeter meter2; + private BarHitErrorMeter barMeter; + private BarHitErrorMeter barMeter2; + private ColourHitErrorMeter colourMeter; + private ColourHitErrorMeter colourMeter2; private HitWindows hitWindows; - public TestSceneBarHitErrorMeter() + public TestSceneHitErrorMeter() { recreateDisplay(new OsuHitWindows(), 5); @@ -91,17 +95,31 @@ namespace osu.Game.Tests.Visual.Gameplay } }); - Add(meter = new BarHitErrorMeter(hitWindows, true) + Add(barMeter = new BarHitErrorMeter(hitWindows, true) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, }); - Add(meter2 = new BarHitErrorMeter(hitWindows, false) + Add(barMeter2 = new BarHitErrorMeter(hitWindows, false) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }); + + Add(colourMeter = new ColourHitErrorMeter(hitWindows) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding { Right = 50 } + }); + + Add(colourMeter2 = new ColourHitErrorMeter(hitWindows) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 50 } + }); } private void newJudgement(double offset = 0) @@ -112,8 +130,10 @@ namespace osu.Game.Tests.Visual.Gameplay Type = HitResult.Perfect, }; - meter.OnNewJudgement(judgement); - meter2.OnNewJudgement(judgement); + barMeter.OnNewJudgement(judgement); + barMeter2.OnNewJudgement(judgement); + colourMeter.OnNewJudgement(judgement); + colourMeter2.OnNewJudgement(judgement); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index 9a217ae416..b9b13d7bd8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -1,12 +1,17 @@ // 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.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Framework.Timing; +using osu.Game.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Play; @@ -15,63 +20,125 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public class TestSceneSongProgress : OsuTestScene { - private readonly SongProgress progress; - private readonly TestSongProgressGraph graph; + public override IReadOnlyList RequiredTypes => new[] + { + typeof(SongProgressBar), + }; + + private SongProgress progress; + private TestSongProgressGraph graph; + private readonly Container progressContainer; private readonly StopwatchClock clock; + private readonly FramedClock framedClock; [Cached] private readonly GameplayClock gameplayClock; - private readonly FramedClock framedClock; - public TestSceneSongProgress() { - clock = new StopwatchClock(true); - + clock = new StopwatchClock(); gameplayClock = new GameplayClock(framedClock = new FramedClock(clock)); - Add(progress = new SongProgress + Add(progressContainer = new Container { RelativeSizeAxes = Axes.X, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Height = 100, + Y = -100, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(1), + } }); - - Add(graph = new TestSongProgressGraph - { - RelativeSizeAxes = Axes.X, - Height = 200, - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - }); - - AddWaitStep("wait some", 5); - AddAssert("ensure not created", () => graph.CreationCount == 0); - - AddStep("display values", displayNewValues); - AddWaitStep("wait some", 5); - AddUntilStep("wait for creation count", () => graph.CreationCount == 1); - - AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); - AddWaitStep("wait some", 5); - AddUntilStep("wait for creation count", () => graph.CreationCount == 1); - - AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); - AddWaitStep("wait some", 5); - AddUntilStep("wait for creation count", () => graph.CreationCount == 1); - AddRepeatStep("New Values", displayNewValues, 5); - - AddWaitStep("wait some", 5); - AddAssert("ensure debounced", () => graph.CreationCount == 2); } - private void displayNewValues() + [SetUpSteps] + public void SetupSteps() { - List objects = new List(); + AddStep("add new song progress", () => + { + if (progress != null) + { + progress.Expire(); + progress = null; + } + + progressContainer.Add(progress = new SongProgress + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }); + }); + + AddStep("add new big graph", () => + { + if (graph != null) + { + graph.Expire(); + graph = null; + } + + Add(graph = new TestSongProgressGraph + { + RelativeSizeAxes = Axes.X, + Height = 200, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }); + }); + + AddStep("reset clock", clock.Reset); + } + + [Test] + public void TestGraphRecreation() + { + AddAssert("ensure not created", () => graph.CreationCount == 0); + AddStep("display values", displayRandomValues); + AddUntilStep("wait for creation count", () => graph.CreationCount == 1); + AddRepeatStep("new values", displayRandomValues, 5); + AddWaitStep("wait some", 5); + AddAssert("ensure recreation debounced", () => graph.CreationCount == 2); + } + + [Test] + public void TestDisplay() + { + AddStep("display max values", displayMaxValues); + AddUntilStep("wait for graph", () => graph.CreationCount == 1); + AddStep("start", clock.Start); + AddStep("allow seeking", () => progress.AllowSeeking.Value = true); + AddStep("hide graph", () => progress.ShowGraph.Value = false); + AddStep("disallow seeking", () => progress.AllowSeeking.Value = false); + AddStep("allow seeking", () => progress.AllowSeeking.Value = true); + AddStep("show graph", () => progress.ShowGraph.Value = true); + AddStep("stop", clock.Stop); + } + + private void displayRandomValues() + { + var objects = new List(); for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000) objects.Add(new HitObject { StartTime = i }); + replaceObjects(objects); + } + + private void displayMaxValues() + { + var objects = new List(); + for (double i = 0; i < 5000; i++) + objects.Add(new HitObject { StartTime = i }); + + replaceObjects(objects); + } + + private void replaceObjects(List objects) + { progress.Objects = objects; graph.Objects = objects; diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 947e864a87..7e4ab58e36 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -85,6 +85,7 @@ namespace osu.Game.Configuration Set(OsuSetting.HitLighting, true); Set(OsuSetting.ShowInterface, true); + Set(OsuSetting.ShowProgressGraph, true); Set(OsuSetting.ShowHealthDisplayWhenCantFail, true); Set(OsuSetting.KeyOverlay, false); Set(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth); @@ -150,6 +151,7 @@ namespace osu.Game.Configuration ScoreMeter, FloatingComments, ShowInterface, + ShowProgressGraph, ShowHealthDisplayWhenCantFail, MouseDisableButtons, MouseDisableWheel, diff --git a/osu.Game/Configuration/ScoreMeterType.cs b/osu.Game/Configuration/ScoreMeterType.cs index b85ef9309d..156c4b1377 100644 --- a/osu.Game/Configuration/ScoreMeterType.cs +++ b/osu.Game/Configuration/ScoreMeterType.cs @@ -18,5 +18,14 @@ namespace osu.Game.Configuration [Description("Hit Error (both)")] HitErrorBoth, + + [Description("Colour (left)")] + ColourLeft, + + [Description("Colour (right)")] + ColourRight, + + [Description("Colour (both)")] + ColourBoth, } } diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 9f1d9d9488..d252083411 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -38,6 +38,7 @@ namespace osu.Game.Overlays.Comments private readonly FillFlowContainer content; private readonly DeletedChildrenPlaceholder deletedChildrenPlaceholder; private readonly CommentsShowMoreButton moreButton; + private readonly TotalCommentsCounter commentCounter; public CommentsContainer() { @@ -56,6 +57,7 @@ namespace osu.Game.Overlays.Comments Direction = FillDirection.Vertical, Children = new Drawable[] { + commentCounter = new TotalCommentsCounter(), new CommentsHeader { Sort = { BindTarget = Sort }, @@ -133,6 +135,9 @@ namespace osu.Game.Overlays.Comments if (!IsLoaded) return; + // only reset when changing ID/type. other refetch ops are generally just changing sort order. + commentCounter.Current.Value = 0; + refetchComments(); } @@ -199,6 +204,8 @@ namespace osu.Game.Overlays.Comments moreButton.IsLoading = false; } + commentCounter.Current.Value = response.Total; + moreButton.FadeTo(response.HasMore ? 1 : 0); }, loadCancellation.Token); } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 3f8bc2b0c7..08bc67e43e 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -40,6 +40,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay Bindable = config.GetBindable(OsuSetting.ShowInterface) }, new SettingsCheckbox + { + LabelText = "Show difficulty graph on progress bar", + Bindable = config.GetBindable(OsuSetting.ShowProgressGraph) + }, + new SettingsCheckbox { LabelText = "Show health display even when you can't fail", Bindable = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail), diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 62d6c4648b..66df68418a 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -5,6 +5,8 @@ using System.Linq; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -25,13 +27,13 @@ namespace osu.Game.Screens.Edit.Components private IAdjustableClock adjustableClock; + private readonly BindableNumber tempo = new BindableDouble(1); + [BackgroundDependencyLoader] private void load(IAdjustableClock adjustableClock) { this.adjustableClock = adjustableClock; - PlaybackTabControl tabs; - Children = new Drawable[] { playButton = new IconButton @@ -58,11 +60,18 @@ namespace osu.Game.Screens.Edit.Components RelativeSizeAxes = Axes.Both, Height = 0.5f, Padding = new MarginPadding { Left = 45 }, - Child = tabs = new PlaybackTabControl(), + Child = new PlaybackTabControl { Current = tempo }, } }; - tabs.Current.ValueChanged += tempo => Beatmap.Value.Track.Tempo.Value = tempo.NewValue; + Track?.AddAdjustment(AdjustableProperty.Tempo, tempo); + } + + protected override void Dispose(bool isDisposing) + { + Track?.RemoveAdjustment(AdjustableProperty.Tempo, tempo); + + base.Dispose(isDisposing); } protected override bool OnKeyDown(KeyDownEvent e) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 8d66cef16e..eae94a3c8e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -46,6 +46,8 @@ namespace osu.Game.Screens.Edit public override bool DisallowExternalBeatmapRulesetChanges => true; + public override bool AllowRateAdjustments => false; + [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -262,12 +264,6 @@ namespace osu.Game.Screens.Edit { } - public override void OnResuming(IScreen last) - { - base.OnResuming(last); - Beatmap.Value.Track?.Stop(); - } - public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -291,7 +287,6 @@ namespace osu.Game.Screens.Edit private void resetTrack(bool seekToStart = false) { - Beatmap.Value.Track?.ResetSpeedAdjustments(); Beatmap.Value.Track?.Stop(); if (seekToStart) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 6196ce4026..4d28f00f39 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -77,6 +77,19 @@ namespace osu.Game.Screens.Play.HUD case ScoreMeterType.HitErrorRight: createBar(true); break; + + case ScoreMeterType.ColourBoth: + createColour(false); + createColour(true); + break; + + case ScoreMeterType.ColourLeft: + createColour(false); + break; + + case ScoreMeterType.ColourRight: + createColour(true); + break; } } @@ -90,6 +103,24 @@ namespace osu.Game.Screens.Play.HUD Alpha = 0, }; + completeDisplayLoading(display); + } + + private void createColour(bool rightAligned) + { + var display = new ColourHitErrorMeter(hitWindows) + { + Margin = new MarginPadding(margin), + Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Alpha = 0, + }; + + completeDisplayLoading(display); + } + + private void completeDisplayLoading(HitErrorMeter display) + { Add(display); display.FadeInFromZero(fade_duration, Easing.OutQuint); } diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 03a0f23fb6..208bdd17ad 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -163,30 +163,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters centre.Width = 2.5f; colourBars.Add(centre); - Color4 getColour(HitResult result) - { - switch (result) - { - case HitResult.Meh: - return colours.Yellow; - - case HitResult.Ok: - return colours.Green; - - case HitResult.Good: - return colours.GreenLight; - - case HitResult.Great: - return colours.Blue; - - default: - return colours.BlueLight; - } - } - Drawable createColourBar(HitResult result, float height, bool first = false) { - var colour = getColour(result); + var colour = GetColourForHitResult(result); if (first) { @@ -201,7 +180,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters new Box { RelativeSizeAxes = Axes.Both, - Colour = getColour(result), + Colour = colour, Height = height * gradient_start }, new Box diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs new file mode 100644 index 0000000000..657235bfd4 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -0,0 +1,92 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play.HUD.HitErrorMeters +{ + public class ColourHitErrorMeter : HitErrorMeter + { + private const int animation_duration = 200; + + private readonly JudgementFlow judgementsFlow; + + public ColourHitErrorMeter(HitWindows hitWindows) + : base(hitWindows) + { + AutoSizeAxes = Axes.Both; + InternalChild = judgementsFlow = new JudgementFlow(); + } + + public override void OnNewJudgement(JudgementResult judgement) => judgementsFlow.Push(GetColourForHitResult(HitWindows.ResultFor(judgement.TimeOffset))); + + private class JudgementFlow : FillFlowContainer + { + private const int max_available_judgements = 20; + private const int drawable_judgement_size = 8; + private const int spacing = 2; + + public override IEnumerable FlowingChildren => base.FlowingChildren.Reverse(); + + public JudgementFlow() + { + AutoSizeAxes = Axes.X; + Height = max_available_judgements * (drawable_judgement_size + spacing) - spacing; + Spacing = new Vector2(0, spacing); + Direction = FillDirection.Vertical; + LayoutDuration = animation_duration; + LayoutEasing = Easing.OutQuint; + } + + public void Push(Color4 colour) + { + Add(new HitErrorCircle(colour, drawable_judgement_size)); + + if (Children.Count > max_available_judgements) + Children.FirstOrDefault(c => !c.IsRemoved)?.Remove(); + } + } + + private class HitErrorCircle : Container + { + public bool IsRemoved { get; private set; } + + private readonly Circle circle; + + public HitErrorCircle(Color4 colour, int size) + { + Size = new Vector2(size); + Child = circle = new Circle + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Colour = colour + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + circle.FadeInFromZero(animation_duration, Easing.OutQuint); + circle.MoveToY(-DrawSize.Y); + circle.MoveToY(0, animation_duration, Easing.OutQuint); + } + + public void Remove() + { + IsRemoved = true; + + this.FadeOut(animation_duration, Easing.OutQuint).Expire(); + } + } + } +} diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index dee25048ed..b3edfdedec 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -1,9 +1,12 @@ // Copyright (c) ppy Pty Ltd . 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.Containers; +using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; +using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD.HitErrorMeters { @@ -11,11 +14,38 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { protected readonly HitWindows HitWindows; + [Resolved] + private OsuColour colours { get; set; } + protected HitErrorMeter(HitWindows hitWindows) { HitWindows = hitWindows; } public abstract void OnNewJudgement(JudgementResult judgement); + + protected Color4 GetColourForHitResult(HitResult result) + { + switch (result) + { + case HitResult.Miss: + return colours.Red; + + case HitResult.Meh: + return colours.Yellow; + + case HitResult.Ok: + return colours.Green; + + case HitResult.Good: + return colours.GreenLight; + + case HitResult.Great: + return colours.Blue; + + default: + return colours.BlueLight; + } + } } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 236bdc8442..a5f8051557 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -131,7 +131,6 @@ namespace osu.Game.Screens.Play BindDrawableRuleset(drawableRuleset); Progress.Objects = drawableRuleset.Objects; - Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value; Progress.RequestSeek = time => RequestSeek(time); Progress.ReferenceClock = drawableRuleset.FrameStableClock; } diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index 713d27bd16..aa745f5ba2 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Timing; +using osu.Game.Configuration; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; @@ -18,8 +19,9 @@ namespace osu.Game.Screens.Play { public class SongProgress : OverlayContainer { + private const int info_height = 20; private const int bottom_bar_height = 5; - + private const float graph_height = SquareGraph.Column.WIDTH * 6; private static readonly Vector2 handle_size = new Vector2(10, 18); private const float transition_duration = 200; @@ -30,12 +32,19 @@ namespace osu.Game.Screens.Play public Action RequestSeek; - public override bool HandleNonPositionalInput => AllowSeeking; - public override bool HandlePositionalInput => AllowSeeking; + /// + /// Whether seeking is allowed and the progress bar should be shown. + /// + public readonly Bindable AllowSeeking = new Bindable(); + + public readonly Bindable ShowGraph = new Bindable(); //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). private double lastHitTime => objects.Last().GetEndTime() + 1; + public override bool HandleNonPositionalInput => AllowSeeking.Value; + public override bool HandlePositionalInput => AllowSeeking.Value; + private double firstHitTime => objects.First().StartTime; private IEnumerable objects; @@ -54,27 +63,14 @@ namespace osu.Game.Screens.Play } } - private readonly BindableBool replayLoaded = new BindableBool(); - public IClock ReferenceClock; private IClock gameplayClock; - [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, GameplayClock clock) - { - if (clock != null) - gameplayClock = clock; - - graph.FillColour = bar.FillColour = colours.BlueLighter; - } - public SongProgress() { - const float graph_height = SquareGraph.Column.WIDTH * 6; - - Height = bottom_bar_height + graph_height + handle_size.Y; - Y = bottom_bar_height; + Masking = true; + Height = bottom_bar_height + graph_height + handle_size.Y + info_height; Children = new Drawable[] { @@ -83,8 +79,7 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Bottom = bottom_bar_height + graph_height }, + Height = info_height, }, graph = new SongProgressGraph { @@ -96,7 +91,6 @@ namespace osu.Game.Screens.Play }, bar = new SongProgressBar(bottom_bar_height, graph_height, handle_size) { - Alpha = 0, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, OnSeek = time => RequestSeek?.Invoke(time), @@ -104,46 +98,34 @@ namespace osu.Game.Screens.Play }; } - protected override void LoadComplete() + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, GameplayClock clock, OsuConfigManager config) { base.LoadComplete(); + if (clock != null) + gameplayClock = clock; + + config.BindWith(OsuSetting.ShowProgressGraph, ShowGraph); + + graph.FillColour = bar.FillColour = colours.BlueLighter; + } + + protected override void LoadComplete() + { Show(); - replayLoaded.ValueChanged += loaded => AllowSeeking = loaded.NewValue; - replayLoaded.TriggerChange(); + AllowSeeking.BindValueChanged(_ => updateBarVisibility(), true); + ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true); } public void BindDrawableRuleset(DrawableRuleset drawableRuleset) { - replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); - } - - private bool allowSeeking; - - public bool AllowSeeking - { - get => allowSeeking; - set - { - if (allowSeeking == value) return; - - allowSeeking = value; - updateBarVisibility(); - } - } - - private void updateBarVisibility() - { - bar.FadeTo(allowSeeking ? 1 : 0, transition_duration, Easing.In); - this.MoveTo(new Vector2(0, allowSeeking ? 0 : bottom_bar_height), transition_duration, Easing.In); - - info.Margin = new MarginPadding { Bottom = Height - (allowSeeking ? 0 : handle_size.Y) }; + AllowSeeking.BindTo(drawableRuleset.HasReplayLoaded); } protected override void PopIn() { - updateBarVisibility(); this.FadeIn(500, Easing.OutQuint); } @@ -167,5 +149,28 @@ namespace osu.Game.Screens.Play bar.CurrentTime = gameplayTime; graph.Progress = (int)(graph.ColumnCount * progress); } + + private void updateBarVisibility() + { + bar.ShowHandle = AllowSeeking.Value; + + updateInfoMargin(); + } + + private void updateGraphVisibility() + { + float barHeight = bottom_bar_height + handle_size.Y; + + bar.ResizeHeightTo(ShowGraph.Value ? barHeight + graph_height : barHeight, transition_duration, Easing.In); + graph.MoveToY(ShowGraph.Value ? 0 : bottom_bar_height + graph_height, transition_duration, Easing.In); + + updateInfoMargin(); + } + + private void updateInfoMargin() + { + 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); + } } } diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs index 9df36c9c2b..5052b32335 100644 --- a/osu.Game/Screens/Play/SongProgressBar.cs +++ b/osu.Game/Screens/Play/SongProgressBar.cs @@ -19,6 +19,23 @@ namespace osu.Game.Screens.Play private readonly Box fill; private readonly Container handleBase; + private readonly Container handleContainer; + + private bool showHandle; + + public bool ShowHandle + { + get => showHandle; + set + { + if (value == showHandle) + return; + + showHandle = value; + + handleBase.FadeTo(showHandle ? 1 : 0, 200); + } + } public Color4 FillColour { @@ -74,7 +91,7 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, Width = 2, - Height = barHeight + handleBarHeight, + Alpha = 0, Colour = Color4.White, Position = new Vector2(2, 0), Children = new Drawable[] @@ -84,7 +101,7 @@ namespace osu.Game.Screens.Play Name = "HandleBar box", RelativeSizeAxes = Axes.Both, }, - new Container + handleContainer = new Container { Name = "Handle container", Origin = Anchor.BottomCentre, @@ -116,6 +133,7 @@ namespace osu.Game.Screens.Play { base.Update(); + handleBase.Height = Height - handleContainer.Height; float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, Math.Clamp(Time.Elapsed / 40, 0, 1)); fill.Width = newX; @@ -127,7 +145,11 @@ namespace osu.Game.Screens.Play protected override void OnUserChange(double value) { scheduledSeek?.Cancel(); - scheduledSeek = Schedule(() => OnSeek?.Invoke(value)); + scheduledSeek = Schedule(() => + { + if (showHandle) + OnSeek?.Invoke(value); + }); } } }