Merge pull request #22144 from ItsShamed/skin/argon-song-progress-cleaner

Add "argon" variant of song progress display
This commit is contained in:
Bartłomiej Dach 2023-01-19 00:25:39 +01:00 committed by GitHub
commit 593f5f6d56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 599 additions and 114 deletions

View File

@ -41,6 +41,8 @@ namespace osu.Game.Tests.Skins
"Archives/modified-default-20220818.osk", "Archives/modified-default-20220818.osk",
// Covers longest combo counter // Covers longest combo counter
"Archives/modified-default-20221012.osk", "Archives/modified-default-20221012.osk",
// Covers Argon variant of song progress bar
"Archives/modified-argon-20221024.osk",
// Covers TextElement and BeatmapInfoDrawable // Covers TextElement and BeatmapInfoDrawable
"Archives/modified-default-20221102.osk", "Archives/modified-default-20221102.osk",
// Covers BPM counter. // Covers BPM counter.

View File

@ -20,6 +20,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
var implementation = skin is LegacySkin var implementation = skin is LegacySkin
? CreateLegacyImplementation() ? CreateLegacyImplementation()
: skin is ArgonSkin
? CreateArgonImplementation()
: CreateDefaultImplementation(); : CreateDefaultImplementation();
implementation.Anchor = Anchor.Centre; implementation.Anchor = Anchor.Centre;
@ -29,6 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}); });
protected abstract Drawable CreateDefaultImplementation(); protected abstract Drawable CreateDefaultImplementation();
protected virtual Drawable CreateArgonImplementation() => CreateDefaultImplementation();
protected abstract Drawable CreateLegacyImplementation(); protected abstract Drawable CreateLegacyImplementation();
} }
} }

View File

@ -14,7 +14,7 @@ using osu.Game.Screens.Play.HUD;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
[TestFixture] [TestFixture]
public partial class TestSceneSongProgressGraph : OsuTestScene public partial class TestSceneDefaultSongProgressGraph : OsuTestScene
{ {
private TestSongProgressGraph graph; private TestSongProgressGraph graph;
@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Gameplay
graph.Objects = objects; graph.Objects = objects;
} }
private partial class TestSongProgressGraph : SongProgressGraph private partial class TestSongProgressGraph : DefaultSongProgressGraph
{ {
public int CreationCount { get; private set; } public int CreationCount { get; private set; }

View File

@ -188,7 +188,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestInputDoesntWorkWhenHUDHidden() public void TestInputDoesntWorkWhenHUDHidden()
{ {
SongProgressBar? getSongProgress() => hudOverlay.ChildrenOfType<SongProgressBar>().SingleOrDefault(); ArgonSongProgress? getSongProgress() => hudOverlay.ChildrenOfType<ArgonSongProgress>().SingleOrDefault();
bool seeked = false; bool seeked = false;
@ -204,8 +204,8 @@ namespace osu.Game.Tests.Visual.Gameplay
Debug.Assert(progress != null); Debug.Assert(progress != null);
progress.ShowHandle = true; progress.Interactive.Value = true;
progress.OnSeek += _ => seeked = true; progress.ChildrenOfType<ArgonSongProgressBar>().Single().OnSeek += _ => seeked = true;
}); });
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);

View File

@ -2,14 +2,14 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -28,50 +28,62 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
Add(gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, skip_target_time)); FrameStabilityContainer frameStabilityContainer;
Add(gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, skip_target_time)
{
Child = frameStabilityContainer = new FrameStabilityContainer
{
MaxCatchUpFrames = 1
}
});
Dependencies.CacheAs<IGameplayClock>(gameplayClockContainer); Dependencies.CacheAs<IGameplayClock>(gameplayClockContainer);
Dependencies.CacheAs<IFrameStableClock>(frameStabilityContainer);
} }
[SetUpSteps] [SetUpSteps]
public void SetupSteps() public void SetupSteps()
{ {
AddStep("reset clock", () => gameplayClockContainer.Reset()); AddStep("reset clock", () => gameplayClockContainer.Reset());
AddStep("set hit objects", setHitObjects); AddStep("set hit objects", () => this.ChildrenOfType<SongProgress>().ForEach(progress => progress.Objects = Beatmap.Value.Beatmap.HitObjects));
AddStep("hook seeking", () =>
{
applyToDefaultProgress(d => d.ChildrenOfType<DefaultSongProgressBar>().Single().OnSeek += t => gameplayClockContainer.Seek(t));
applyToArgonProgress(d => d.ChildrenOfType<ArgonSongProgressBar>().Single().OnSeek += t => gameplayClockContainer.Seek(t));
});
AddStep("seek to intro", () => gameplayClockContainer.Seek(skip_target_time));
AddStep("start", () => gameplayClockContainer.Start());
} }
[Test] [Test]
public void TestDisplay() public void TestBasic()
{ {
AddStep("seek to intro", () => gameplayClockContainer.Seek(skip_target_time)); AddToggleStep("toggle seeking", b =>
AddStep("start", gameplayClockContainer.Start); {
applyToDefaultProgress(s => s.Interactive.Value = b);
applyToArgonProgress(s => s.Interactive.Value = b);
});
AddToggleStep("toggle graph", b =>
{
applyToDefaultProgress(s => s.ShowGraph.Value = b);
applyToArgonProgress(s => s.ShowGraph.Value = b);
});
AddStep("stop", gameplayClockContainer.Stop); AddStep("stop", gameplayClockContainer.Stop);
} }
[Test] private void applyToArgonProgress(Action<ArgonSongProgress> action) =>
public void TestToggleSeeking() this.ChildrenOfType<ArgonSongProgress>().ForEach(action);
{
void applyToDefaultProgress(Action<DefaultSongProgress> action) => private void applyToDefaultProgress(Action<DefaultSongProgress> action) =>
this.ChildrenOfType<DefaultSongProgress>().ForEach(action); this.ChildrenOfType<DefaultSongProgress>().ForEach(action);
AddStep("allow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = true));
AddStep("hide graph", () => applyToDefaultProgress(s => s.ShowGraph.Value = false));
AddStep("disallow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = false));
AddStep("allow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = true));
AddStep("show graph", () => applyToDefaultProgress(s => s.ShowGraph.Value = true));
}
private void setHitObjects()
{
var objects = new List<HitObject>();
for (double i = 0; i < 5000; i++)
objects.Add(new HitObject { StartTime = i });
this.ChildrenOfType<SongProgress>().ForEach(progress => progress.Objects = objects);
}
protected override Drawable CreateDefaultImplementation() => new DefaultSongProgress(); protected override Drawable CreateDefaultImplementation() => new DefaultSongProgress();
protected override Drawable CreateArgonImplementation() => new ArgonSongProgress();
protected override Drawable CreateLegacyImplementation() => new LegacySongProgress(); protected override Drawable CreateLegacyImplementation() => new LegacySongProgress();
} }
} }

View File

@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("all interactive elements removed", () => this.ChildrenOfType<Player>().All(p => AddUntilStep("all interactive elements removed", () => this.ChildrenOfType<Player>().All(p =>
!p.ChildrenOfType<PlayerSettingsOverlay>().Any() && !p.ChildrenOfType<PlayerSettingsOverlay>().Any() &&
!p.ChildrenOfType<HoldForMenuButton>().Any() && !p.ChildrenOfType<HoldForMenuButton>().Any() &&
p.ChildrenOfType<SongProgressBar>().SingleOrDefault()?.ShowHandle == false)); p.ChildrenOfType<ArgonSongProgressBar>().SingleOrDefault()?.Interactive == false));
AddStep("restore config hud visibility", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue)); AddStep("restore config hud visibility", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
} }

View File

@ -48,17 +48,14 @@ namespace osu.Game.Graphics.UserInterface
} }
} }
private Colour4[] tierColours; private IReadOnlyList<Colour4> tierColours;
public Colour4[] TierColours public IReadOnlyList<Colour4> TierColours
{ {
get => tierColours; get => tierColours;
set set
{ {
if (value.Length == 0 || value == tierColours) tierCount = value.Count;
return;
tierCount = value.Length;
tierColours = value; tierColours = value;
graphNeedsUpdate = true; graphNeedsUpdate = true;

View File

@ -0,0 +1,117 @@
// 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.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Screens.Play.HUD
{
public partial class ArgonSongProgress : SongProgress
{
private readonly SongProgressInfo info;
private readonly ArgonSongProgressGraph graph;
private readonly ArgonSongProgressBar bar;
private readonly Container graphContainer;
private const float bar_height = 10;
[SettingSource("Show difficulty graph", "Whether a graph displaying difficulty throughout the beatmap should be shown")]
public Bindable<bool> ShowGraph { get; } = new BindableBool(true);
[Resolved]
private Player? player { get; set; }
public ArgonSongProgress()
{
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
Masking = true;
CornerRadius = 5;
Children = new Drawable[]
{
info = new SongProgressInfo
{
Origin = Anchor.TopLeft,
Name = "Info",
Anchor = Anchor.TopLeft,
RelativeSizeAxes = Axes.X,
ShowProgress = false
},
bar = new ArgonSongProgressBar(bar_height)
{
Name = "Seek bar",
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
OnSeek = time => player?.Seek(time),
},
graphContainer = new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Masking = true,
CornerRadius = 5,
Child = graph = new ArgonSongProgressGraph
{
Name = "Difficulty graph",
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive
},
RelativeSizeAxes = Axes.X,
},
};
RelativeSizeAxes = Axes.X;
}
[BackgroundDependencyLoader]
private void load()
{
info.TextColour = Colour4.White;
info.Font = OsuFont.Torus.With(size: 18, weight: FontWeight.Bold);
}
protected override void LoadComplete()
{
base.LoadComplete();
Interactive.BindValueChanged(_ => bar.Interactive = Interactive.Value, true);
ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true);
}
protected override void UpdateObjects(IEnumerable<HitObject> objects)
{
graph.Objects = objects;
info.StartTime = bar.StartTime = FirstHitTime;
info.EndTime = bar.EndTime = LastHitTime;
}
private void updateGraphVisibility()
{
graph.FadeTo(ShowGraph.Value ? 1 : 0, 200, Easing.In);
bar.ShowBackground = !ShowGraph.Value;
}
protected override void Update()
{
base.Update();
Height = bar.Height + bar_height + info.Height;
graphContainer.Height = bar.Height;
}
protected override void UpdateProgress(double progress, bool isIntro)
{
bar.TrackTime = GameplayClock.CurrentTime;
if (isIntro)
bar.CurrentTime = 0;
else
bar.CurrentTime = FrameStableClock.CurrentTime;
}
}
}

View File

@ -0,0 +1,266 @@
// 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.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osu.Framework.Utils;
using osu.Game.Graphics;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Play.HUD
{
public partial class ArgonSongProgressBar : SliderBar<double>
{
public Action<double>? OnSeek { get; set; }
// Parent will handle restricting the area of valid input.
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
private readonly float barHeight;
private readonly RoundedBar playfieldBar;
private readonly RoundedBar catchupBar;
private readonly Box background;
private readonly BindableBool showBackground = new BindableBool();
private readonly ColourInfo mainColour;
private readonly ColourInfo mainColourDarkened;
private ColourInfo catchUpColour;
private ColourInfo catchUpColourDarkened;
public bool ShowBackground
{
get => showBackground.Value;
set => showBackground.Value = value;
}
public double StartTime
{
private get => CurrentNumber.MinValue;
set => CurrentNumber.MinValue = value;
}
public double EndTime
{
private get => CurrentNumber.MaxValue;
set => CurrentNumber.MaxValue = value;
}
public double CurrentTime
{
private get => CurrentNumber.Value;
set => CurrentNumber.Value = value;
}
public double TrackTime
{
private get => currentTrackTime.Value;
set => currentTrackTime.Value = value;
}
private double length => EndTime - StartTime;
private readonly BindableNumber<double> currentTrackTime;
public bool Interactive { get; set; }
public ArgonSongProgressBar(float barHeight)
{
currentTrackTime = new BindableDouble();
setupAlternateValue();
StartTime = 0;
EndTime = 1;
RelativeSizeAxes = Axes.X;
Height = this.barHeight = barHeight;
CornerRadius = 5;
Masking = true;
Children = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
Colour = Colour4.White.Darken(1 + 1 / 4f)
},
catchupBar = new RoundedBar
{
Name = "Audio bar",
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
CornerRadius = 5,
AlwaysPresent = true,
RelativeSizeAxes = Axes.Both
},
playfieldBar = new RoundedBar
{
Name = "Playfield bar",
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
CornerRadius = 5,
AccentColour = mainColour = Color4.White,
RelativeSizeAxes = Axes.Both
},
};
mainColourDarkened = Colour4.White.Darken(1 / 3f);
}
private void setupAlternateValue()
{
CurrentNumber.MaxValueChanged += v => currentTrackTime.MaxValue = v;
CurrentNumber.MinValueChanged += v => currentTrackTime.MinValue = v;
CurrentNumber.PrecisionChanged += v => currentTrackTime.Precision = v;
}
private float normalizedReference
{
get
{
if (EndTime - StartTime == 0)
return 1;
return (float)((TrackTime - StartTime) / length);
}
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
catchUpColour = colours.BlueLight;
catchUpColourDarkened = colours.BlueDark;
showBackground.BindValueChanged(_ => updateBackground(), true);
}
private void updateBackground()
{
background.FadeTo(showBackground.Value ? 1 / 4f : 0, 200, Easing.In);
playfieldBar.TransformTo(nameof(playfieldBar.AccentColour), ShowBackground ? mainColour : mainColourDarkened, 200, Easing.In);
}
protected override bool OnHover(HoverEvent e)
{
if (Interactive)
this.ResizeHeightTo(barHeight * 3.5f, 200, Easing.Out);
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
this.ResizeHeightTo(barHeight, 800, Easing.OutQuint);
base.OnHoverLost(e);
}
protected override void UpdateValue(float value)
{
// Handled in Update
}
protected override void Update()
{
base.Update();
playfieldBar.Length = (float)Interpolation.Lerp(playfieldBar.Length, NormalizedValue, Math.Clamp(Time.Elapsed / 40, 0, 1));
catchupBar.Length = (float)Interpolation.Lerp(catchupBar.Length, normalizedReference, Math.Clamp(Time.Elapsed / 40, 0, 1));
if (TrackTime < CurrentTime)
ChangeChildDepth(catchupBar, -1);
else
ChangeChildDepth(catchupBar, 0);
float timeDelta = (float)(Math.Abs(CurrentTime - TrackTime));
const float colour_transition_threshold = 20000;
catchupBar.AccentColour = Interpolation.ValueAt(
Math.Min(timeDelta, colour_transition_threshold),
ShowBackground ? mainColour : mainColourDarkened,
ShowBackground ? catchUpColour : catchUpColourDarkened,
0, colour_transition_threshold,
Easing.OutQuint);
catchupBar.Alpha = Math.Max(1, catchupBar.Length);
}
private ScheduledDelegate? scheduledSeek;
protected override void OnUserChange(double value)
{
scheduledSeek?.Cancel();
scheduledSeek = Schedule(() =>
{
if (Interactive)
OnSeek?.Invoke(value);
});
}
private partial class RoundedBar : Container
{
private readonly Box fill;
private readonly Container mask;
private float length;
public RoundedBar()
{
Masking = true;
Children = new[]
{
mask = new Container
{
Masking = true,
RelativeSizeAxes = Axes.Y,
Size = new Vector2(1),
Child = fill = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Colour4.White
}
}
};
}
public float Length
{
get => length;
set
{
length = value;
mask.Width = value * DrawWidth;
fill.Width = value * DrawWidth;
}
}
public new float CornerRadius
{
get => base.CornerRadius;
set
{
base.CornerRadius = value;
mask.CornerRadius = value;
}
}
public ColourInfo AccentColour
{
get => fill.Colour;
set => fill.Colour = value;
}
}
}
}

View File

@ -0,0 +1,64 @@
// 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.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Screens.Play.HUD
{
public partial class ArgonSongProgressGraph : SegmentedGraph<int>
{
private IEnumerable<HitObject>? objects;
public IEnumerable<HitObject> Objects
{
set
{
objects = value;
const int granularity = 200;
int[] values = new int[granularity];
if (!objects.Any())
return;
double firstHit = objects.First().StartTime;
double lastHit = objects.Max(o => o.GetEndTime());
if (lastHit == 0)
lastHit = objects.Last().StartTime;
double interval = (lastHit - firstHit + 1) / granularity;
foreach (var h in objects)
{
double endTime = h.GetEndTime();
Debug.Assert(endTime >= h.StartTime);
int startRange = (int)((h.StartTime - firstHit) / interval);
int endRange = (int)((endTime - firstHit) / interval);
for (int i = startRange; i <= endRange; i++)
values[i]++;
}
Values = values;
}
}
public ArgonSongProgressGraph()
: base(5)
{
var colours = new List<Colour4>();
for (int i = 0; i < 5; i++)
colours.Add(Colour4.White.Darken(1 + 1 / 5f).Opacity(1 / 5f));
TierColours = colours;
}
}
}

View File

@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
using osuTK; using osuTK;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD
@ -23,27 +22,16 @@ namespace osu.Game.Screens.Play.HUD
private const float transition_duration = 200; private const float transition_duration = 200;
private readonly SongProgressBar bar; private readonly DefaultSongProgressBar bar;
private readonly SongProgressGraph graph; private readonly DefaultSongProgressGraph graph;
private readonly SongProgressInfo info; private readonly SongProgressInfo info;
/// <summary>
/// Whether seeking is allowed and the progress bar should be shown.
/// </summary>
public readonly Bindable<bool> AllowSeeking = new Bindable<bool>();
[SettingSource("Show difficulty graph", "Whether a graph displaying difficulty throughout the beatmap should be shown")] [SettingSource("Show difficulty graph", "Whether a graph displaying difficulty throughout the beatmap should be shown")]
public Bindable<bool> ShowGraph { get; } = new BindableBool(true); public Bindable<bool> ShowGraph { get; } = new BindableBool(true);
public override bool HandleNonPositionalInput => AllowSeeking.Value;
public override bool HandlePositionalInput => AllowSeeking.Value;
[Resolved] [Resolved]
private Player? player { get; set; } private Player? player { get; set; }
[Resolved]
private DrawableRuleset? drawableRuleset { get; set; }
public DefaultSongProgress() public DefaultSongProgress()
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
@ -58,7 +46,7 @@ namespace osu.Game.Screens.Play.HUD
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
}, },
graph = new SongProgressGraph graph = new DefaultSongProgressGraph
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
@ -66,7 +54,7 @@ namespace osu.Game.Screens.Play.HUD
Height = graph_height, Height = graph_height,
Margin = new MarginPadding { Bottom = bottom_bar_height }, Margin = new MarginPadding { Bottom = bottom_bar_height },
}, },
bar = new SongProgressBar(bottom_bar_height, graph_height, handle_size) bar = new DefaultSongProgressBar(bottom_bar_height, graph_height, handle_size)
{ {
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
@ -75,34 +63,18 @@ namespace osu.Game.Screens.Play.HUD
}; };
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
base.LoadComplete();
if (drawableRuleset != null)
{
if (player?.Configuration.AllowUserInteraction == true)
((IBindable<bool>)AllowSeeking).BindTo(drawableRuleset.HasReplayLoaded);
}
graph.FillColour = bar.FillColour = colours.BlueLighter; graph.FillColour = bar.FillColour = colours.BlueLighter;
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
AllowSeeking.BindValueChanged(_ => updateBarVisibility(), true); Interactive.BindValueChanged(_ => updateBarVisibility(), true);
ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true); ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true);
}
protected override void PopIn() base.LoadComplete();
{
this.FadeIn(500, Easing.OutQuint);
}
protected override void PopOut()
{
this.FadeOut(100);
} }
protected override void UpdateObjects(IEnumerable<HitObject> objects) protected override void UpdateObjects(IEnumerable<HitObject> objects)
@ -133,7 +105,7 @@ namespace osu.Game.Screens.Play.HUD
private void updateBarVisibility() private void updateBarVisibility()
{ {
bar.ShowHandle = AllowSeeking.Value; bar.Interactive = Interactive.Value;
updateInfoMargin(); updateInfoMargin();
} }
@ -150,7 +122,7 @@ namespace osu.Game.Screens.Play.HUD
private void updateInfoMargin() private void updateInfoMargin()
{ {
float finalMargin = bottom_bar_height + (AllowSeeking.Value ? handle_size.Y : 0) + (ShowGraph.Value ? graph_height : 0); float finalMargin = bottom_bar_height + (Interactive.Value ? handle_size.Y : 0) + (ShowGraph.Value ? graph_height : 0);
info.TransformTo(nameof(info.Margin), new MarginPadding { Bottom = finalMargin }, transition_duration, Easing.In); info.TransformTo(nameof(info.Margin), new MarginPadding { Bottom = finalMargin }, transition_duration, Easing.In);
} }
} }

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -15,17 +13,17 @@ using osu.Framework.Threading;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD
{ {
public partial class SongProgressBar : SliderBar<double> public partial class DefaultSongProgressBar : SliderBar<double>
{ {
public Action<double> OnSeek; /// <summary>
/// Action which is invoked when a seek is requested, with the proposed millisecond value for the seek operation.
/// </summary>
public Action<double>? OnSeek { get; set; }
private readonly Box fill; /// <summary>
private readonly Container handleBase; /// Whether the progress bar should allow interaction, ie. to perform seek operations.
private readonly Container handleContainer; /// </summary>
public bool Interactive
private bool showHandle;
public bool ShowHandle
{ {
get => showHandle; get => showHandle;
set set
@ -59,7 +57,13 @@ namespace osu.Game.Screens.Play.HUD
set => CurrentNumber.Value = value; set => CurrentNumber.Value = value;
} }
public SongProgressBar(float barHeight, float handleBarHeight, Vector2 handleSize) private readonly Box fill;
private readonly Container handleBase;
private readonly Container handleContainer;
private bool showHandle;
public DefaultSongProgressBar(float barHeight, float handleBarHeight, Vector2 handleSize)
{ {
CurrentNumber.MinValue = 0; CurrentNumber.MinValue = 0;
CurrentNumber.MaxValue = 1; CurrentNumber.MaxValue = 1;
@ -142,7 +146,7 @@ namespace osu.Game.Screens.Play.HUD
handleBase.X = newX; handleBase.X = newX;
} }
private ScheduledDelegate scheduledSeek; private ScheduledDelegate? scheduledSeek;
protected override void OnUserChange(double value) protected override void OnUserChange(double value)
{ {

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Objects;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD
{ {
public partial class SongProgressGraph : SquareGraph public partial class DefaultSongProgressGraph : SquareGraph
{ {
private IEnumerable<HitObject> objects; private IEnumerable<HitObject> objects;

View File

@ -4,6 +4,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -16,10 +18,19 @@ namespace osu.Game.Screens.Play.HUD
{ {
// Some implementations of this element allow seeking during gameplay playback. // Some implementations of this element allow seeking during gameplay playback.
// Set a sane default of never handling input to override the behaviour provided by OverlayContainer. // Set a sane default of never handling input to override the behaviour provided by OverlayContainer.
public override bool HandleNonPositionalInput => false; public override bool HandleNonPositionalInput => Interactive.Value;
public override bool HandlePositionalInput => false; public override bool HandlePositionalInput => Interactive.Value;
protected override bool BlockScrollInput => false; protected override bool BlockScrollInput => false;
/// <summary>
/// Whether interaction should be allowed (ie. seeking). If <c>false</c>, interaction controls will not be displayed.
/// </summary>
/// <remarks>
/// By default, this will be automatically decided based on the gameplay state.
/// </remarks>
public readonly Bindable<bool> Interactive = new Bindable<bool>();
public bool UsesFixedAnchor { get; set; } public bool UsesFixedAnchor { get; set; }
[Resolved] [Resolved]
@ -63,14 +74,21 @@ namespace osu.Game.Screens.Play.HUD
protected virtual void UpdateObjects(IEnumerable<HitObject> objects) { } protected virtual void UpdateObjects(IEnumerable<HitObject> objects) { }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(DrawableRuleset? drawableRuleset) private void load(DrawableRuleset? drawableRuleset, Player? player)
{ {
if (drawableRuleset != null) if (drawableRuleset != null)
{ {
if (player?.Configuration.AllowUserInteraction == true)
((IBindable<bool>)Interactive).BindTo(drawableRuleset.HasReplayLoaded);
Objects = drawableRuleset.Objects; Objects = drawableRuleset.Objects;
} }
} }
protected override void PopIn() => this.FadeIn(500, Easing.OutQuint);
protected override void PopOut() => this.FadeOut(100);
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();

View File

@ -10,6 +10,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using System; using System;
using osu.Framework.Graphics.Sprites;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD
{ {
@ -27,13 +28,33 @@ namespace osu.Game.Screens.Play.HUD
private double songLength => endTime - startTime; private double songLength => endTime - startTime;
private const int margin = 10; public FontUsage Font
{
set
{
timeCurrent.Font = value;
timeLeft.Font = value;
progress.Font = value;
}
}
public Colour4 TextColour
{
set
{
timeCurrent.Colour = value;
timeLeft.Colour = value;
progress.Colour = value;
}
}
public double StartTime public double StartTime
{ {
set => startTime = value; set => startTime = value;
} }
public bool ShowProgress { get; init; } = true;
public double EndTime public double EndTime
{ {
set => endTime = value; set => endTime = value;
@ -76,6 +97,7 @@ namespace osu.Game.Screens.Play.HUD
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Alpha = ShowProgress ? 1 : 0,
Child = new UprightAspectMaintainingContainer Child = new UprightAspectMaintainingContainer
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
@ -99,15 +121,15 @@ namespace osu.Game.Screens.Play.HUD
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Child = new UprightAspectMaintainingContainer Child = new UprightAspectMaintainingContainer
{ {
Origin = Anchor.Centre, Origin = Anchor.CentreRight,
Anchor = Anchor.Centre, Anchor = Anchor.CentreRight,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Scaling = ScaleMode.Vertical, Scaling = ScaleMode.Vertical,
ScalingFactor = 0.5f, ScalingFactor = 0.5f,
Child = timeLeft = new SizePreservingSpriteText Child = timeLeft = new SizePreservingSpriteText
{ {
Origin = Anchor.Centre, Origin = Anchor.CentreRight,
Anchor = Anchor.Centre, Anchor = Anchor.CentreRight,
Colour = colours.BlueLighter, Colour = colours.BlueLighter,
Font = OsuFont.Numeric, Font = OsuFont.Numeric,
} }
@ -128,7 +150,7 @@ namespace osu.Game.Screens.Play.HUD
if (currentPercent != previousPercent) if (currentPercent != previousPercent)
{ {
progress.Text = currentPercent.ToString() + @"%"; progress.Text = currentPercent + @"%";
previousPercent = currentPercent; previousPercent = currentPercent;
} }

View File

@ -108,6 +108,7 @@ namespace osu.Game.Skinning
var accuracy = container.OfType<DefaultAccuracyCounter>().FirstOrDefault(); var accuracy = container.OfType<DefaultAccuracyCounter>().FirstOrDefault();
var combo = container.OfType<DefaultComboCounter>().FirstOrDefault(); var combo = container.OfType<DefaultComboCounter>().FirstOrDefault();
var ppCounter = container.OfType<PerformancePointsCounter>().FirstOrDefault(); var ppCounter = container.OfType<PerformancePointsCounter>().FirstOrDefault();
var songProgress = container.OfType<ArgonSongProgress>().FirstOrDefault();
if (score != null) if (score != null)
{ {
@ -158,6 +159,12 @@ namespace osu.Game.Skinning
// origin flipped to match scale above. // origin flipped to match scale above.
hitError2.Origin = Anchor.CentreLeft; hitError2.Origin = Anchor.CentreLeft;
} }
if (songProgress != null)
{
songProgress.Position = new Vector2(0, -10);
songProgress.Scale = new Vector2(0.9f, 1);
}
} }
}) })
{ {
@ -167,7 +174,7 @@ namespace osu.Game.Skinning
new DefaultScoreCounter(), new DefaultScoreCounter(),
new DefaultAccuracyCounter(), new DefaultAccuracyCounter(),
new DefaultHealthDisplay(), new DefaultHealthDisplay(),
new DefaultSongProgress(), new ArgonSongProgress(),
new BarHitErrorMeter(), new BarHitErrorMeter(),
new BarHitErrorMeter(), new BarHitErrorMeter(),
new PerformancePointsCounter() new PerformancePointsCounter()

View File

@ -15,6 +15,10 @@ namespace osu.Game.Skinning
{ {
private CircularProgress circularProgress = null!; private CircularProgress circularProgress = null!;
// Legacy song progress doesn't support interaction for now.
public override bool HandleNonPositionalInput => false;
public override bool HandlePositionalInput => false;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
@ -56,16 +60,6 @@ namespace osu.Game.Skinning
}; };
} }
protected override void PopIn()
{
this.FadeIn(500, Easing.OutQuint);
}
protected override void PopOut()
{
this.FadeOut(100);
}
protected override void UpdateProgress(double progress, bool isIntro) protected override void UpdateProgress(double progress, bool isIntro)
{ {
if (isIntro) if (isIntro)

View File

@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
BorderColour = Color4.White, BorderColour = Color4.White,
BorderThickness = 5, BorderThickness = 3,
Masking = true, Masking = true,
Children = new Drawable[] Children = new Drawable[]
@ -142,8 +142,15 @@ namespace osu.Game.Tests.Visual
c.AutoSizeAxes = Axes.None; c.AutoSizeAxes = Axes.None;
c.Size = Vector2.Zero; c.Size = Vector2.Zero;
c.RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None; if (autoSize)
c.AutoSizeAxes = autoSize ? Axes.Both : Axes.None; c.AutoSizeAxes = Axes.Both;
else
{
c.RelativeSizeAxes = Axes.Both;
c.Anchor = Anchor.Centre;
c.Origin = Anchor.Centre;
c.Size = new Vector2(0.97f);
}
} }
outlineBox.Alpha = autoSize ? 1 : 0; outlineBox.Alpha = autoSize ? 1 : 0;