1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 23:07:26 +08:00

Merge pull request #5247 from Aergwyn/collapse-graph-option

Add setting to show the song difficulty graph
This commit is contained in:
Dean Herbert 2020-01-24 14:59:59 +09:00 committed by GitHub
commit b06e4d89b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 190 additions and 90 deletions

View File

@ -1,12 +1,17 @@
// 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 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<Type> 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<HitObject> objects = new List<HitObject>();
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<HitObject>();
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<HitObject>();
for (double i = 0; i < 5000; i++)
objects.Add(new HitObject { StartTime = i });
replaceObjects(objects);
}
private void replaceObjects(List<HitObject> objects)
{
progress.Objects = objects;
graph.Objects = objects;

View File

@ -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,

View File

@ -40,6 +40,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
Bindable = config.GetBindable<bool>(OsuSetting.ShowInterface)
},
new SettingsCheckbox
{
LabelText = "Show difficulty graph on progress bar",
Bindable = config.GetBindable<bool>(OsuSetting.ShowProgressGraph)
},
new SettingsCheckbox
{
LabelText = "Show health display even when you can't fail",
Bindable = config.GetBindable<bool>(OsuSetting.ShowHealthDisplayWhenCantFail),

View File

@ -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;
}

View File

@ -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<double> RequestSeek;
public override bool HandleNonPositionalInput => AllowSeeking;
public override bool HandlePositionalInput => AllowSeeking;
/// <summary>
/// Whether seeking is allowed and the progress bar should be shown.
/// </summary>
public readonly Bindable<bool> AllowSeeking = new Bindable<bool>();
public readonly Bindable<bool> ShowGraph = new Bindable<bool>();
//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<HitObject> 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);
}
}
}

View File

@ -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);
});
}
}
}