mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 08:52:55 +08:00
Merge pull request #22144 from ItsShamed/skin/argon-song-progress-cleaner
Add "argon" variant of song progress display
This commit is contained in:
commit
593f5f6d56
BIN
osu.Game.Tests/Resources/Archives/modified-argon-20221024.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-argon-20221024.osk
Normal file
Binary file not shown.
@ -41,6 +41,8 @@ namespace osu.Game.Tests.Skins
|
||||
"Archives/modified-default-20220818.osk",
|
||||
// Covers longest combo counter
|
||||
"Archives/modified-default-20221012.osk",
|
||||
// Covers Argon variant of song progress bar
|
||||
"Archives/modified-argon-20221024.osk",
|
||||
// Covers TextElement and BeatmapInfoDrawable
|
||||
"Archives/modified-default-20221102.osk",
|
||||
// Covers BPM counter.
|
||||
|
@ -20,7 +20,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
var implementation = skin is LegacySkin
|
||||
? CreateLegacyImplementation()
|
||||
: CreateDefaultImplementation();
|
||||
: skin is ArgonSkin
|
||||
? CreateArgonImplementation()
|
||||
: CreateDefaultImplementation();
|
||||
|
||||
implementation.Anchor = Anchor.Centre;
|
||||
implementation.Origin = Anchor.Centre;
|
||||
@ -29,6 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
});
|
||||
|
||||
protected abstract Drawable CreateDefaultImplementation();
|
||||
protected virtual Drawable CreateArgonImplementation() => CreateDefaultImplementation();
|
||||
protected abstract Drawable CreateLegacyImplementation();
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ using osu.Game.Screens.Play.HUD;
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneSongProgressGraph : OsuTestScene
|
||||
public partial class TestSceneDefaultSongProgressGraph : OsuTestScene
|
||||
{
|
||||
private TestSongProgressGraph graph;
|
||||
|
||||
@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
graph.Objects = objects;
|
||||
}
|
||||
|
||||
private partial class TestSongProgressGraph : SongProgressGraph
|
||||
private partial class TestSongProgressGraph : DefaultSongProgressGraph
|
||||
{
|
||||
public int CreationCount { get; private set; }
|
||||
|
@ -188,7 +188,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestInputDoesntWorkWhenHUDHidden()
|
||||
{
|
||||
SongProgressBar? getSongProgress() => hudOverlay.ChildrenOfType<SongProgressBar>().SingleOrDefault();
|
||||
ArgonSongProgress? getSongProgress() => hudOverlay.ChildrenOfType<ArgonSongProgress>().SingleOrDefault();
|
||||
|
||||
bool seeked = false;
|
||||
|
||||
@ -204,8 +204,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
Debug.Assert(progress != null);
|
||||
|
||||
progress.ShowHandle = true;
|
||||
progress.OnSeek += _ => seeked = true;
|
||||
progress.Interactive.Value = true;
|
||||
progress.ChildrenOfType<ArgonSongProgressBar>().Single().OnSeek += _ => seeked = true;
|
||||
});
|
||||
|
||||
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
||||
|
@ -2,14 +2,14 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
@ -28,50 +28,62 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
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<IFrameStableClock>(frameStabilityContainer);
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetupSteps()
|
||||
{
|
||||
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]
|
||||
public void TestDisplay()
|
||||
public void TestBasic()
|
||||
{
|
||||
AddStep("seek to intro", () => gameplayClockContainer.Seek(skip_target_time));
|
||||
AddStep("start", gameplayClockContainer.Start);
|
||||
AddToggleStep("toggle seeking", b =>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestToggleSeeking()
|
||||
{
|
||||
void applyToDefaultProgress(Action<DefaultSongProgress> action) =>
|
||||
this.ChildrenOfType<DefaultSongProgress>().ForEach(action);
|
||||
private void applyToArgonProgress(Action<ArgonSongProgress> action) =>
|
||||
this.ChildrenOfType<ArgonSongProgress>().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);
|
||||
}
|
||||
private void applyToDefaultProgress(Action<DefaultSongProgress> action) =>
|
||||
this.ChildrenOfType<DefaultSongProgress>().ForEach(action);
|
||||
|
||||
protected override Drawable CreateDefaultImplementation() => new DefaultSongProgress();
|
||||
|
||||
protected override Drawable CreateArgonImplementation() => new ArgonSongProgress();
|
||||
|
||||
protected override Drawable CreateLegacyImplementation() => new LegacySongProgress();
|
||||
}
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("all interactive elements removed", () => this.ChildrenOfType<Player>().All(p =>
|
||||
!p.ChildrenOfType<PlayerSettingsOverlay>().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));
|
||||
}
|
||||
|
@ -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;
|
||||
set
|
||||
{
|
||||
if (value.Length == 0 || value == tierColours)
|
||||
return;
|
||||
|
||||
tierCount = value.Length;
|
||||
tierCount = value.Count;
|
||||
tierColours = value;
|
||||
|
||||
graphNeedsUpdate = true;
|
||||
|
117
osu.Game/Screens/Play/HUD/ArgonSongProgress.cs
Normal file
117
osu.Game/Screens/Play/HUD/ArgonSongProgress.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
266
osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs
Normal file
266
osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
64
osu.Game/Screens/Play/HUD/ArgonSongProgressGraph.cs
Normal file
64
osu.Game/Screens/Play/HUD/ArgonSongProgressGraph.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
@ -23,27 +22,16 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
private const float transition_duration = 200;
|
||||
|
||||
private readonly SongProgressBar bar;
|
||||
private readonly SongProgressGraph graph;
|
||||
private readonly DefaultSongProgressBar bar;
|
||||
private readonly DefaultSongProgressGraph graph;
|
||||
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")]
|
||||
public Bindable<bool> ShowGraph { get; } = new BindableBool(true);
|
||||
|
||||
public override bool HandleNonPositionalInput => AllowSeeking.Value;
|
||||
public override bool HandlePositionalInput => AllowSeeking.Value;
|
||||
|
||||
[Resolved]
|
||||
private Player? player { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private DrawableRuleset? drawableRuleset { get; set; }
|
||||
|
||||
public DefaultSongProgress()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
@ -58,7 +46,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
Anchor = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
graph = new SongProgressGraph
|
||||
graph = new DefaultSongProgressGraph
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Origin = Anchor.BottomLeft,
|
||||
@ -66,7 +54,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
Height = graph_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,
|
||||
Origin = Anchor.BottomLeft,
|
||||
@ -75,34 +63,18 @@ namespace osu.Game.Screens.Play.HUD
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
[BackgroundDependencyLoader]
|
||||
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;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
AllowSeeking.BindValueChanged(_ => updateBarVisibility(), true);
|
||||
Interactive.BindValueChanged(_ => updateBarVisibility(), true);
|
||||
ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true);
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
this.FadeIn(500, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
this.FadeOut(100);
|
||||
base.LoadComplete();
|
||||
}
|
||||
|
||||
protected override void UpdateObjects(IEnumerable<HitObject> objects)
|
||||
@ -133,7 +105,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
private void updateBarVisibility()
|
||||
{
|
||||
bar.ShowHandle = AllowSeeking.Value;
|
||||
bar.Interactive = Interactive.Value;
|
||||
|
||||
updateInfoMargin();
|
||||
}
|
||||
@ -150,7 +122,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@ -15,17 +13,17 @@ using osu.Framework.Threading;
|
||||
|
||||
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;
|
||||
private readonly Container handleBase;
|
||||
private readonly Container handleContainer;
|
||||
|
||||
private bool showHandle;
|
||||
|
||||
public bool ShowHandle
|
||||
/// <summary>
|
||||
/// Whether the progress bar should allow interaction, ie. to perform seek operations.
|
||||
/// </summary>
|
||||
public bool Interactive
|
||||
{
|
||||
get => showHandle;
|
||||
set
|
||||
@ -59,7 +57,13 @@ namespace osu.Game.Screens.Play.HUD
|
||||
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.MaxValue = 1;
|
||||
@ -142,7 +146,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
handleBase.X = newX;
|
||||
}
|
||||
|
||||
private ScheduledDelegate scheduledSeek;
|
||||
private ScheduledDelegate? scheduledSeek;
|
||||
|
||||
protected override void OnUserChange(double value)
|
||||
{
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public partial class SongProgressGraph : SquareGraph
|
||||
public partial class DefaultSongProgressGraph : SquareGraph
|
||||
{
|
||||
private IEnumerable<HitObject> objects;
|
||||
|
@ -4,6 +4,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Timing;
|
||||
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.
|
||||
// Set a sane default of never handling input to override the behaviour provided by OverlayContainer.
|
||||
public override bool HandleNonPositionalInput => false;
|
||||
public override bool HandlePositionalInput => false;
|
||||
public override bool HandleNonPositionalInput => Interactive.Value;
|
||||
public override bool HandlePositionalInput => Interactive.Value;
|
||||
|
||||
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; }
|
||||
|
||||
[Resolved]
|
||||
@ -63,14 +74,21 @@ namespace osu.Game.Screens.Play.HUD
|
||||
protected virtual void UpdateObjects(IEnumerable<HitObject> objects) { }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(DrawableRuleset? drawableRuleset)
|
||||
private void load(DrawableRuleset? drawableRuleset, Player? player)
|
||||
{
|
||||
if (drawableRuleset != null)
|
||||
{
|
||||
if (player?.Configuration.AllowUserInteraction == true)
|
||||
((IBindable<bool>)Interactive).BindTo(drawableRuleset.HasReplayLoaded);
|
||||
|
||||
Objects = drawableRuleset.Objects;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PopIn() => this.FadeIn(500, Easing.OutQuint);
|
||||
|
||||
protected override void PopOut() => this.FadeOut(100);
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
@ -10,6 +10,7 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using System;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
@ -27,13 +28,33 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
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
|
||||
{
|
||||
set => startTime = value;
|
||||
}
|
||||
|
||||
public bool ShowProgress { get; init; } = true;
|
||||
|
||||
public double EndTime
|
||||
{
|
||||
set => endTime = value;
|
||||
@ -76,6 +97,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Alpha = ShowProgress ? 1 : 0,
|
||||
Child = new UprightAspectMaintainingContainer
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
@ -99,15 +121,15 @@ namespace osu.Game.Screens.Play.HUD
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Child = new UprightAspectMaintainingContainer
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreRight,
|
||||
Anchor = Anchor.CentreRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Scaling = ScaleMode.Vertical,
|
||||
ScalingFactor = 0.5f,
|
||||
Child = timeLeft = new SizePreservingSpriteText
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreRight,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Colour = colours.BlueLighter,
|
||||
Font = OsuFont.Numeric,
|
||||
}
|
||||
@ -128,7 +150,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
if (currentPercent != previousPercent)
|
||||
{
|
||||
progress.Text = currentPercent.ToString() + @"%";
|
||||
progress.Text = currentPercent + @"%";
|
||||
previousPercent = currentPercent;
|
||||
}
|
||||
|
||||
|
@ -108,6 +108,7 @@ namespace osu.Game.Skinning
|
||||
var accuracy = container.OfType<DefaultAccuracyCounter>().FirstOrDefault();
|
||||
var combo = container.OfType<DefaultComboCounter>().FirstOrDefault();
|
||||
var ppCounter = container.OfType<PerformancePointsCounter>().FirstOrDefault();
|
||||
var songProgress = container.OfType<ArgonSongProgress>().FirstOrDefault();
|
||||
|
||||
if (score != null)
|
||||
{
|
||||
@ -158,6 +159,12 @@ namespace osu.Game.Skinning
|
||||
// origin flipped to match scale above.
|
||||
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 DefaultAccuracyCounter(),
|
||||
new DefaultHealthDisplay(),
|
||||
new DefaultSongProgress(),
|
||||
new ArgonSongProgress(),
|
||||
new BarHitErrorMeter(),
|
||||
new BarHitErrorMeter(),
|
||||
new PerformancePointsCounter()
|
||||
|
@ -15,6 +15,10 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
private CircularProgress circularProgress = null!;
|
||||
|
||||
// Legacy song progress doesn't support interaction for now.
|
||||
public override bool HandleNonPositionalInput => false;
|
||||
public override bool HandlePositionalInput => false;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
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)
|
||||
{
|
||||
if (isIntro)
|
||||
|
@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
BorderColour = Color4.White,
|
||||
BorderThickness = 5,
|
||||
BorderThickness = 3,
|
||||
Masking = true,
|
||||
|
||||
Children = new Drawable[]
|
||||
@ -142,8 +142,15 @@ namespace osu.Game.Tests.Visual
|
||||
c.AutoSizeAxes = Axes.None;
|
||||
c.Size = Vector2.Zero;
|
||||
|
||||
c.RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None;
|
||||
c.AutoSizeAxes = autoSize ? Axes.Both : Axes.None;
|
||||
if (autoSize)
|
||||
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;
|
||||
|
Loading…
Reference in New Issue
Block a user