mirror of
https://github.com/ppy/osu.git
synced 2025-02-02 00:12:54 +08:00
Merge branch 'master' into osu-ruleset-multi-touch-basic
This commit is contained in:
commit
2fba80dfd3
@ -0,0 +1,19 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||||
|
{
|
||||||
|
public partial class TestSceneManiaModFadeIn : ModTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
|
[TestCase(0.5f)]
|
||||||
|
[TestCase(0.1f)]
|
||||||
|
[TestCase(0.7f)]
|
||||||
|
public void TestCoverage(float coverage) => CreateModTest(new ModTestData { Mod = new ManiaModFadeIn { Coverage = { Value = coverage } }, PassCondition = () => true });
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||||
|
{
|
||||||
|
public partial class TestSceneManiaModHidden : ModTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
|
[TestCase(0.5f)]
|
||||||
|
[TestCase(0.2f)]
|
||||||
|
[TestCase(0.8f)]
|
||||||
|
public void TestCoverage(float coverage) => CreateModTest(new ModTestData { Mod = new ManiaModHidden { Coverage = { Value = coverage } }, PassCondition = () => true });
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
|
||||||
@ -18,5 +19,13 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModHidden)).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModHidden)).ToArray();
|
||||||
|
|
||||||
protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll;
|
protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll;
|
||||||
|
|
||||||
|
public override BindableNumber<float> Coverage { get; } = new BindableFloat(0.5f)
|
||||||
|
{
|
||||||
|
Precision = 0.1f,
|
||||||
|
MinValue = 0.1f,
|
||||||
|
MaxValue = 0.7f,
|
||||||
|
Default = 0.5f,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
@ -13,6 +14,14 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override LocalisableString Description => @"Keys fade out before you hit them!";
|
public override LocalisableString Description => @"Keys fade out before you hit them!";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
|
public override BindableNumber<float> Coverage { get; } = new BindableFloat(0.5f)
|
||||||
|
{
|
||||||
|
Precision = 0.1f,
|
||||||
|
MinValue = 0.2f,
|
||||||
|
MaxValue = 0.8f,
|
||||||
|
Default = 0.5f,
|
||||||
|
};
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModFadeIn)).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModFadeIn)).ToArray();
|
||||||
|
|
||||||
protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll;
|
protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll;
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -22,6 +24,9 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected abstract CoverExpandDirection ExpandDirection { get; }
|
protected abstract CoverExpandDirection ExpandDirection { get; }
|
||||||
|
|
||||||
|
[SettingSource("Coverage", "The proportion of playfield height that notes will be hidden for.")]
|
||||||
|
public abstract BindableNumber<float> Coverage { get; }
|
||||||
|
|
||||||
public virtual void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset)
|
public virtual void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield;
|
ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield;
|
||||||
@ -36,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
{
|
{
|
||||||
c.RelativeSizeAxes = Axes.Both;
|
c.RelativeSizeAxes = Axes.Both;
|
||||||
c.Direction = ExpandDirection;
|
c.Direction = ExpandDirection;
|
||||||
c.Coverage = 0.5f;
|
c.Coverage = Coverage.Value;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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.
BIN
osu.Game.Tests/Resources/Archives/modified-default-20230117.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-default-20230117.osk
Normal file
Binary file not shown.
@ -41,10 +41,14 @@ 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.
|
||||||
"Archives/modified-default-20221205.osk"
|
"Archives/modified-default-20221205.osk",
|
||||||
|
// Covers judgement counter.
|
||||||
|
"Archives/modified-default-20230117.osk"
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -20,7 +20,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
var implementation = skin is LegacySkin
|
var implementation = skin is LegacySkin
|
||||||
? CreateLegacyImplementation()
|
? CreateLegacyImplementation()
|
||||||
: CreateDefaultImplementation();
|
: skin is ArgonSkin
|
||||||
|
? CreateArgonImplementation()
|
||||||
|
: CreateDefaultImplementation();
|
||||||
|
|
||||||
implementation.Anchor = Anchor.Centre;
|
implementation.Anchor = Anchor.Centre;
|
||||||
implementation.Origin = Anchor.Centre;
|
implementation.Origin = 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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; }
|
||||||
|
|
@ -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);
|
||||||
|
144
osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs
Normal file
144
osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Mania;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Screens.Play.HUD.JudgementCounter;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public partial class TestSceneJudgementCounter : OsuTestScene
|
||||||
|
{
|
||||||
|
private ScoreProcessor scoreProcessor = null!;
|
||||||
|
private JudgementTally judgementTally = null!;
|
||||||
|
private TestJudgementCounterDisplay counterDisplay = null!;
|
||||||
|
|
||||||
|
private readonly Bindable<JudgementResult> lastJudgementResult = new Bindable<JudgementResult>();
|
||||||
|
|
||||||
|
private int iteration;
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetupSteps() => AddStep("Create components", () =>
|
||||||
|
{
|
||||||
|
var ruleset = CreateRuleset();
|
||||||
|
|
||||||
|
Debug.Assert(ruleset != null);
|
||||||
|
|
||||||
|
scoreProcessor = new ScoreProcessor(ruleset);
|
||||||
|
Child = new DependencyProvidingContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
CachedDependencies = new (Type, object)[] { (typeof(ScoreProcessor), scoreProcessor), (typeof(Ruleset), ruleset) },
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
judgementTally = new JudgementTally(),
|
||||||
|
new DependencyProvidingContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
CachedDependencies = new (Type, object)[] { (typeof(JudgementTally), judgementTally) },
|
||||||
|
Child = counterDisplay = new TestJudgementCounterDisplay
|
||||||
|
{
|
||||||
|
Margin = new MarginPadding { Top = 100 },
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
|
private void applyOneJudgement(HitResult result)
|
||||||
|
{
|
||||||
|
lastJudgementResult.Value = new OsuJudgementResult(new HitObject
|
||||||
|
{
|
||||||
|
StartTime = iteration * 10000
|
||||||
|
}, new OsuJudgement())
|
||||||
|
{
|
||||||
|
Type = result,
|
||||||
|
};
|
||||||
|
scoreProcessor.ApplyResult(lastJudgementResult.Value);
|
||||||
|
|
||||||
|
iteration++;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddJudgementsToCounters()
|
||||||
|
{
|
||||||
|
AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.Great), 2);
|
||||||
|
AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.Miss), 2);
|
||||||
|
AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.Meh), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddWhilstHidden()
|
||||||
|
{
|
||||||
|
AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.LargeTickHit), 2);
|
||||||
|
AddAssert("Check value added whilst hidden", () => hiddenCount() == 2);
|
||||||
|
AddStep("Show all judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.All);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChangeFlowDirection()
|
||||||
|
{
|
||||||
|
AddStep("Set direction vertical", () => counterDisplay.FlowDirection.Value = Direction.Vertical);
|
||||||
|
AddStep("Set direction horizontal", () => counterDisplay.FlowDirection.Value = Direction.Horizontal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestToggleJudgementNames()
|
||||||
|
{
|
||||||
|
AddStep("Hide judgement names", () => counterDisplay.ShowJudgementNames.Value = false);
|
||||||
|
AddWaitStep("wait some", 2);
|
||||||
|
AddAssert("Assert hidden", () => counterDisplay.CounterFlow.Children.First().ResultName.Alpha == 0);
|
||||||
|
AddStep("Hide judgement names", () => counterDisplay.ShowJudgementNames.Value = true);
|
||||||
|
AddWaitStep("wait some", 2);
|
||||||
|
AddAssert("Assert shown", () => counterDisplay.CounterFlow.Children.First().ResultName.Alpha == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHideMaxValue()
|
||||||
|
{
|
||||||
|
AddStep("Hide max judgement", () => counterDisplay.ShowMaxJudgement.Value = false);
|
||||||
|
AddWaitStep("wait some", 2);
|
||||||
|
AddAssert("Check max hidden", () => counterDisplay.CounterFlow.ChildrenOfType<JudgementCounter>().First().Alpha == 0);
|
||||||
|
AddStep("Show max judgement", () => counterDisplay.ShowMaxJudgement.Value = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCycleDisplayModes()
|
||||||
|
{
|
||||||
|
AddStep("Show basic judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.Simple);
|
||||||
|
AddWaitStep("wait some", 2);
|
||||||
|
AddAssert("Check only basic", () => counterDisplay.CounterFlow.ChildrenOfType<JudgementCounter>().Last().Alpha == 0);
|
||||||
|
AddStep("Show normal judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.Normal);
|
||||||
|
AddStep("Show all judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.All);
|
||||||
|
AddWaitStep("wait some", 2);
|
||||||
|
AddAssert("Check all visible", () => counterDisplay.CounterFlow.ChildrenOfType<JudgementCounter>().Last().Alpha == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int hiddenCount()
|
||||||
|
{
|
||||||
|
var num = counterDisplay.CounterFlow.Children.First(child => child.Result.Type == HitResult.LargeTickHit);
|
||||||
|
return num.Result.ResultCount.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class TestJudgementCounterDisplay : JudgementCounterDisplay
|
||||||
|
{
|
||||||
|
public new FillFlowContainer<JudgementCounter> CounterFlow => base.CounterFlow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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) =>
|
|
||||||
this.ChildrenOfType<DefaultSongProgress>().ForEach(action);
|
|
||||||
|
|
||||||
AddStep("allow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = true));
|
private void applyToDefaultProgress(Action<DefaultSongProgress> action) =>
|
||||||
AddStep("hide graph", () => applyToDefaultProgress(s => s.ShowGraph.Value = false));
|
this.ChildrenOfType<DefaultSongProgress>().ForEach(action);
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -580,10 +580,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
/// Ensures stability is maintained on different sort modes for items with equal properties.
|
/// Ensures stability is maintained on different sort modes for items with equal properties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSortingStability()
|
public void TestSortingStabilityDateAdded()
|
||||||
{
|
{
|
||||||
var sets = new List<BeatmapSetInfo>();
|
var sets = new List<BeatmapSetInfo>();
|
||||||
int idOffset = 0;
|
|
||||||
|
|
||||||
AddStep("Populuate beatmap sets", () =>
|
AddStep("Populuate beatmap sets", () =>
|
||||||
{
|
{
|
||||||
@ -593,38 +592,34 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
var set = TestResources.CreateTestBeatmapSetInfo();
|
var set = TestResources.CreateTestBeatmapSetInfo();
|
||||||
|
|
||||||
|
set.DateAdded = DateTimeOffset.FromUnixTimeSeconds(i);
|
||||||
|
|
||||||
// only need to set the first as they are a shared reference.
|
// only need to set the first as they are a shared reference.
|
||||||
var beatmap = set.Beatmaps.First();
|
var beatmap = set.Beatmaps.First();
|
||||||
|
|
||||||
beatmap.Metadata.Artist = $"artist {i / 2}";
|
beatmap.Metadata.Artist = "a";
|
||||||
beatmap.Metadata.Title = $"title {9 - i}";
|
beatmap.Metadata.Title = "b";
|
||||||
|
|
||||||
sets.Add(set);
|
sets.Add(set);
|
||||||
}
|
}
|
||||||
|
|
||||||
idOffset = sets.First().OnlineID;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
loadBeatmaps(sets);
|
loadBeatmaps(sets);
|
||||||
|
|
||||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
|
||||||
AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b));
|
|
||||||
|
|
||||||
AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false));
|
AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false));
|
||||||
AddAssert("Items are in reverse order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + sets.Count - index - 1).All(b => b));
|
AddAssert("Items remain in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
||||||
|
|
||||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
||||||
AddAssert("Items reset to original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b));
|
AddAssert("Items remain in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ensures stability is maintained on different sort modes while a new item is added to the carousel.
|
/// Ensures stability is maintained on different sort modes while a new item is added to the carousel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSortingStabilityWithNewItems()
|
public void TestSortingStabilityWithRemovedAndReaddedItem()
|
||||||
{
|
{
|
||||||
List<BeatmapSetInfo> sets = new List<BeatmapSetInfo>();
|
List<BeatmapSetInfo> sets = new List<BeatmapSetInfo>();
|
||||||
int idOffset = 0;
|
|
||||||
|
|
||||||
AddStep("Populuate beatmap sets", () =>
|
AddStep("Populuate beatmap sets", () =>
|
||||||
{
|
{
|
||||||
@ -640,16 +635,68 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
beatmap.Metadata.Artist = "same artist";
|
beatmap.Metadata.Artist = "same artist";
|
||||||
beatmap.Metadata.Title = "same title";
|
beatmap.Metadata.Title = "same title";
|
||||||
|
|
||||||
|
// testing the case where DateAdded happens to equal (quite rare).
|
||||||
|
set.DateAdded = DateTimeOffset.UnixEpoch;
|
||||||
|
|
||||||
sets.Add(set);
|
sets.Add(set);
|
||||||
}
|
}
|
||||||
|
|
||||||
idOffset = sets.First().OnlineID;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Guid[] originalOrder = null!;
|
||||||
|
|
||||||
loadBeatmaps(sets);
|
loadBeatmaps(sets);
|
||||||
|
|
||||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
||||||
assertOriginalOrderMaintained();
|
|
||||||
|
AddAssert("Items in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
||||||
|
AddStep("Save order", () => originalOrder = carousel.BeatmapSets.Select(s => s.ID).ToArray());
|
||||||
|
|
||||||
|
AddStep("Remove item", () => carousel.RemoveBeatmapSet(sets[1]));
|
||||||
|
AddStep("Re-add item", () => carousel.UpdateBeatmapSet(sets[1]));
|
||||||
|
|
||||||
|
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
||||||
|
|
||||||
|
AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false));
|
||||||
|
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures stability is maintained on different sort modes while a new item is added to the carousel.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestSortingStabilityWithNewItems()
|
||||||
|
{
|
||||||
|
List<BeatmapSetInfo> sets = new List<BeatmapSetInfo>();
|
||||||
|
|
||||||
|
AddStep("Populuate beatmap sets", () =>
|
||||||
|
{
|
||||||
|
sets.Clear();
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
var set = TestResources.CreateTestBeatmapSetInfo(3);
|
||||||
|
|
||||||
|
// only need to set the first as they are a shared reference.
|
||||||
|
var beatmap = set.Beatmaps.First();
|
||||||
|
|
||||||
|
beatmap.Metadata.Artist = "same artist";
|
||||||
|
beatmap.Metadata.Title = "same title";
|
||||||
|
|
||||||
|
// testing the case where DateAdded happens to equal (quite rare).
|
||||||
|
set.DateAdded = DateTimeOffset.UnixEpoch;
|
||||||
|
|
||||||
|
sets.Add(set);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Guid[] originalOrder = null!;
|
||||||
|
|
||||||
|
loadBeatmaps(sets);
|
||||||
|
|
||||||
|
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
||||||
|
|
||||||
|
AddAssert("Items in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
||||||
|
AddStep("Save order", () => originalOrder = carousel.BeatmapSets.Select(s => s.ID).ToArray());
|
||||||
|
|
||||||
AddStep("Add new item", () =>
|
AddStep("Add new item", () =>
|
||||||
{
|
{
|
||||||
@ -661,19 +708,18 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
beatmap.Metadata.Artist = "same artist";
|
beatmap.Metadata.Artist = "same artist";
|
||||||
beatmap.Metadata.Title = "same title";
|
beatmap.Metadata.Title = "same title";
|
||||||
|
|
||||||
|
set.DateAdded = DateTimeOffset.FromUnixTimeSeconds(1);
|
||||||
|
|
||||||
carousel.UpdateBeatmapSet(set);
|
carousel.UpdateBeatmapSet(set);
|
||||||
|
|
||||||
|
// add set to expected ordering
|
||||||
|
originalOrder = originalOrder.Prepend(set.ID).ToArray();
|
||||||
});
|
});
|
||||||
|
|
||||||
assertOriginalOrderMaintained();
|
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
||||||
|
|
||||||
AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false));
|
AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false));
|
||||||
assertOriginalOrderMaintained();
|
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
||||||
|
|
||||||
void assertOriginalOrderMaintained()
|
|
||||||
{
|
|
||||||
AddAssert("Items remain in original order",
|
|
||||||
() => carousel.BeatmapSets.Select(s => s.OnlineID), () => Is.EqualTo(carousel.BeatmapSets.Select((set, index) => idOffset + index)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -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;
|
||||||
|
@ -19,6 +19,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"join the real-time discussion");
|
public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"join the real-time discussion");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Mention"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString MentionUser => new TranslatableString(getKey(@"mention_user"), @"Mention");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ namespace osu.Game.Online.Chat
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class StandAloneChatDisplay : CompositeDrawable
|
public partial class StandAloneChatDisplay : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
public readonly Bindable<Channel> Channel = new Bindable<Channel>();
|
public readonly Bindable<Channel> Channel = new Bindable<Channel>();
|
||||||
|
|
||||||
protected readonly ChatTextBox TextBox;
|
protected readonly ChatTextBox TextBox;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -24,6 +25,7 @@ using osu.Game.Online.Chat;
|
|||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
using ChatStrings = osu.Game.Localisation.ChatStrings;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Chat
|
namespace osu.Game.Overlays.Chat
|
||||||
{
|
{
|
||||||
@ -65,6 +67,9 @@ namespace osu.Game.Overlays.Chat
|
|||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private UserProfileOverlay? profileOverlay { get; set; }
|
private UserProfileOverlay? profileOverlay { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Bindable<Channel?>? currentChannel { get; set; }
|
||||||
|
|
||||||
private readonly APIUser user;
|
private readonly APIUser user;
|
||||||
private readonly OsuSpriteText drawableText;
|
private readonly OsuSpriteText drawableText;
|
||||||
|
|
||||||
@ -156,6 +161,14 @@ namespace osu.Game.Overlays.Chat
|
|||||||
if (!user.Equals(api.LocalUser.Value))
|
if (!user.Equals(api.LocalUser.Value))
|
||||||
items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, openUserChannel));
|
items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, openUserChannel));
|
||||||
|
|
||||||
|
if (currentChannel?.Value != null)
|
||||||
|
{
|
||||||
|
items.Add(new OsuMenuItem(ChatStrings.MentionUser, MenuItemType.Standard, () =>
|
||||||
|
{
|
||||||
|
currentChannel.Value.TextBoxMessage.Value += $"@{user.Username} ";
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
return items.ToArray();
|
return items.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,14 +15,12 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IGameplayClock gameplayClock { get; set; } = null!;
|
private IGameplayClock gameplayClock { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved]
|
||||||
private DrawableRuleset? drawableRuleset { get; set; }
|
private IFrameStableClock? frameStableClock { get; set; }
|
||||||
|
|
||||||
public int Value { get; private set; }
|
public int Value { get; private set; }
|
||||||
|
|
||||||
// Even though `FrameStabilityContainer` caches as a `GameplayClock`, we need to check it directly via `drawableRuleset`
|
private IGameplayClock clock => frameStableClock ?? gameplayClock;
|
||||||
// as this calculator is not contained within the `FrameStabilityContainer` and won't see the dependency.
|
|
||||||
private IGameplayClock clock => drawableRuleset?.FrameStableClock ?? gameplayClock;
|
|
||||||
|
|
||||||
public ClicksPerSecondCalculator()
|
public ClicksPerSecondCalculator()
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
{
|
{
|
@ -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;
|
||||||
|
|
@ -0,0 +1,82 @@
|
|||||||
|
// 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 osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
||||||
|
{
|
||||||
|
public partial class JudgementCounter : VisibilityContainer
|
||||||
|
{
|
||||||
|
public BindableBool ShowName = new BindableBool();
|
||||||
|
public Bindable<FillDirection> Direction = new Bindable<FillDirection>();
|
||||||
|
|
||||||
|
public readonly JudgementTally.JudgementCount Result;
|
||||||
|
|
||||||
|
public JudgementCounter(JudgementTally.JudgementCount result) => Result = result;
|
||||||
|
|
||||||
|
public OsuSpriteText ResultName = null!;
|
||||||
|
private FillFlowContainer flowContainer = null!;
|
||||||
|
private JudgementRollingCounter counter = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours, IBindable<RulesetInfo> ruleset)
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChild = flowContainer = new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
counter = new JudgementRollingCounter
|
||||||
|
{
|
||||||
|
Current = Result.ResultCount
|
||||||
|
},
|
||||||
|
ResultName = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Alpha = 0,
|
||||||
|
Font = OsuFont.Numeric.With(size: 8),
|
||||||
|
Text = ruleset.Value.CreateInstance().GetDisplayNameForHitResult(Result.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = Result.Type;
|
||||||
|
|
||||||
|
Colour = result.IsBasic() ? colours.ForHitResult(Result.Type) : !result.IsBonus() ? colours.PurpleLight : colours.PurpleLighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
ShowName.BindValueChanged(value =>
|
||||||
|
ResultName.FadeTo(value.NewValue ? 1 : 0, JudgementCounterDisplay.TRANSFORM_DURATION, Easing.OutQuint), true);
|
||||||
|
|
||||||
|
Direction.BindValueChanged(direction =>
|
||||||
|
{
|
||||||
|
flowContainer.Direction = direction.NewValue;
|
||||||
|
changeAnchor(direction.NewValue == FillDirection.Vertical ? Anchor.TopLeft : Anchor.BottomLeft);
|
||||||
|
|
||||||
|
void changeAnchor(Anchor anchor) => counter.Anchor = ResultName.Anchor = counter.Origin = ResultName.Origin = anchor;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
base.LoadComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopIn() => this.FadeIn(JudgementCounterDisplay.TRANSFORM_DURATION, Easing.OutQuint);
|
||||||
|
protected override void PopOut() => this.FadeOut(100);
|
||||||
|
|
||||||
|
private sealed partial class JudgementRollingCounter : RollingCounter<int>
|
||||||
|
{
|
||||||
|
protected override OsuSpriteText CreateSpriteText()
|
||||||
|
=> base.CreateSpriteText().With(s => s.Font = s.Font.With(fixedWidth: true, size: 16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,139 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
||||||
|
{
|
||||||
|
public partial class JudgementCounterDisplay : CompositeDrawable, ISkinnableDrawable
|
||||||
|
{
|
||||||
|
public const int TRANSFORM_DURATION = 250;
|
||||||
|
|
||||||
|
public bool UsesFixedAnchor { get; set; }
|
||||||
|
|
||||||
|
[SettingSource("Display mode")]
|
||||||
|
public Bindable<DisplayMode> Mode { get; set; } = new Bindable<DisplayMode>();
|
||||||
|
|
||||||
|
[SettingSource("Counter direction")]
|
||||||
|
public Bindable<Direction> FlowDirection { get; set; } = new Bindable<Direction>();
|
||||||
|
|
||||||
|
[SettingSource("Show judgement names")]
|
||||||
|
public BindableBool ShowJudgementNames { get; set; } = new BindableBool(true);
|
||||||
|
|
||||||
|
[SettingSource("Show max judgement")]
|
||||||
|
public BindableBool ShowMaxJudgement { get; set; } = new BindableBool(true);
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private JudgementTally tally { get; set; } = null!;
|
||||||
|
|
||||||
|
protected FillFlowContainer<JudgementCounter> CounterFlow = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
InternalChild = CounterFlow = new FillFlowContainer<JudgementCounter>
|
||||||
|
{
|
||||||
|
Direction = getFillDirection(FlowDirection.Value),
|
||||||
|
Spacing = new Vector2(10),
|
||||||
|
AutoSizeAxes = Axes.Both
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var result in tally.Results)
|
||||||
|
CounterFlow.Add(createCounter(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
FlowDirection.BindValueChanged(direction =>
|
||||||
|
{
|
||||||
|
var convertedDirection = getFillDirection(direction.NewValue);
|
||||||
|
|
||||||
|
CounterFlow.Direction = convertedDirection;
|
||||||
|
|
||||||
|
foreach (var counter in CounterFlow.Children)
|
||||||
|
counter.Direction.Value = convertedDirection;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
Mode.BindValueChanged(_ => updateMode(), true);
|
||||||
|
|
||||||
|
ShowMaxJudgement.BindValueChanged(value =>
|
||||||
|
{
|
||||||
|
var firstChild = CounterFlow.Children.FirstOrDefault();
|
||||||
|
firstChild.FadeTo(value.NewValue ? 1 : 0, TRANSFORM_DURATION, Easing.OutQuint);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateMode()
|
||||||
|
{
|
||||||
|
foreach (var counter in CounterFlow.Children)
|
||||||
|
{
|
||||||
|
if (shouldShow(counter))
|
||||||
|
counter.Show();
|
||||||
|
else
|
||||||
|
counter.Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shouldShow(JudgementCounter counter)
|
||||||
|
{
|
||||||
|
if (counter.Result.Type.IsBasic())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
switch (Mode.Value)
|
||||||
|
{
|
||||||
|
case DisplayMode.Simple:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case DisplayMode.Normal:
|
||||||
|
return !counter.Result.Type.IsBonus();
|
||||||
|
|
||||||
|
case DisplayMode.All:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private FillDirection getFillDirection(Direction flow)
|
||||||
|
{
|
||||||
|
switch (flow)
|
||||||
|
{
|
||||||
|
case Direction.Horizontal:
|
||||||
|
return FillDirection.Horizontal;
|
||||||
|
|
||||||
|
case Direction.Vertical:
|
||||||
|
return FillDirection.Vertical;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(flow), flow, @"Unsupported direction");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JudgementCounter createCounter(JudgementTally.JudgementCount info) =>
|
||||||
|
new JudgementCounter(info)
|
||||||
|
{
|
||||||
|
State = { Value = Visibility.Hidden },
|
||||||
|
ShowName = { BindTarget = ShowJudgementNames }
|
||||||
|
};
|
||||||
|
|
||||||
|
public enum DisplayMode
|
||||||
|
{
|
||||||
|
Simple,
|
||||||
|
Normal,
|
||||||
|
All
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
osu.Game/Screens/Play/HUD/JudgementCounter/JudgementTally.cs
Normal file
60
osu.Game/Screens/Play/HUD/JudgementCounter/JudgementTally.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Keeps track of judgements for a current play session, exposing bindable counts which can
|
||||||
|
/// be used for display purposes.
|
||||||
|
/// </summary>
|
||||||
|
public partial class JudgementTally : CompositeDrawable
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private ScoreProcessor scoreProcessor { get; set; } = null!;
|
||||||
|
|
||||||
|
public List<JudgementCount> Results = new List<JudgementCount>();
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(IBindable<RulesetInfo> ruleset)
|
||||||
|
{
|
||||||
|
foreach (var result in ruleset.Value.CreateInstance().GetHitResults())
|
||||||
|
{
|
||||||
|
Results.Add(new JudgementCount
|
||||||
|
{
|
||||||
|
Type = result.result,
|
||||||
|
ResultCount = new BindableInt()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
scoreProcessor.NewJudgement += judgement => updateCount(judgement, false);
|
||||||
|
scoreProcessor.JudgementReverted += judgement => updateCount(judgement, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCount(JudgementResult judgement, bool revert)
|
||||||
|
{
|
||||||
|
foreach (JudgementCount result in Results.Where(result => result.Type == judgement.Type))
|
||||||
|
result.ResultCount.Value = revert ? result.ResultCount.Value - 1 : result.ResultCount.Value + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct JudgementCount
|
||||||
|
{
|
||||||
|
public HitResult Type { get; set; }
|
||||||
|
|
||||||
|
public BindableInt ResultCount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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,19 +18,33 @@ 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]
|
||||||
protected IGameplayClock GameplayClock { get; private set; } = null!;
|
protected IGameplayClock GameplayClock { get; private set; } = null!;
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved]
|
||||||
private DrawableRuleset? drawableRuleset { get; set; }
|
private IFrameStableClock? frameStableClock { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The reference clock is used to accurately tell the current playfield's time (including catch-up lag).
|
||||||
|
/// However, if none is available (i.e. used in tests), we fall back to the gameplay clock.
|
||||||
|
/// </summary>
|
||||||
|
protected IClock FrameStableClock => frameStableClock ?? GameplayClock;
|
||||||
|
|
||||||
private IClock? referenceClock;
|
|
||||||
private IEnumerable<HitObject>? objects;
|
private IEnumerable<HitObject>? objects;
|
||||||
|
|
||||||
public IEnumerable<HitObject> Objects
|
public IEnumerable<HitObject> Objects
|
||||||
@ -58,15 +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()
|
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;
|
||||||
referenceClock = drawableRuleset.FrameStableClock;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
@ -74,9 +96,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
if (objects == null)
|
if (objects == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// The reference clock is used to accurately tell the playfield's time. This is obtained from the drawable ruleset.
|
double currentTime = FrameStableClock.CurrentTime;
|
||||||
// However, if no drawable ruleset is available (i.e. used in tests), we fall back to the gameplay clock.
|
|
||||||
double currentTime = referenceClock?.CurrentTime ?? GameplayClock.CurrentTime;
|
|
||||||
|
|
||||||
bool isInIntro = currentTime < FirstHitTime;
|
bool isInIntro = currentTime < FirstHitTime;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ using osu.Game.Rulesets.Scoring;
|
|||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
|
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
|
||||||
|
using osu.Game.Screens.Play.HUD.JudgementCounter;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
@ -59,6 +60,9 @@ namespace osu.Game.Screens.Play
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly ClicksPerSecondCalculator clicksPerSecondCalculator;
|
private readonly ClicksPerSecondCalculator clicksPerSecondCalculator;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private readonly JudgementTally tally;
|
||||||
|
|
||||||
public Bindable<bool> ShowHealthBar = new Bindable<bool>(true);
|
public Bindable<bool> ShowHealthBar = new Bindable<bool>(true);
|
||||||
|
|
||||||
private readonly DrawableRuleset drawableRuleset;
|
private readonly DrawableRuleset drawableRuleset;
|
||||||
@ -104,6 +108,8 @@ namespace osu.Game.Screens.Play
|
|||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
CreateFailingLayer(),
|
CreateFailingLayer(),
|
||||||
|
//Needs to be initialized before skinnable drawables.
|
||||||
|
tally = new JudgementTally(),
|
||||||
mainComponents = new MainComponentsContainer
|
mainComponents = new MainComponentsContainer
|
||||||
{
|
{
|
||||||
AlwaysPresent = true,
|
AlwaysPresent = true,
|
||||||
|
@ -309,6 +309,8 @@ namespace osu.Game.Screens.Play
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies.CacheAs(DrawableRuleset.FrameStableClock);
|
||||||
|
|
||||||
// add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components.
|
// add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components.
|
||||||
// also give the overlays the ruleset skin provider to allow rulesets to potentially override HUD elements (used to disable combo counters etc.)
|
// also give the overlays the ruleset skin provider to allow rulesets to potentially override HUD elements (used to disable combo counters etc.)
|
||||||
// we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there.
|
// we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there.
|
||||||
|
@ -61,50 +61,75 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
if (!(other is CarouselBeatmapSet otherSet))
|
if (!(other is CarouselBeatmapSet otherSet))
|
||||||
return base.CompareTo(criteria, other);
|
return base.CompareTo(criteria, other);
|
||||||
|
|
||||||
|
int comparison = 0;
|
||||||
|
|
||||||
switch (criteria.Sort)
|
switch (criteria.Sort)
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
case SortMode.Artist:
|
case SortMode.Artist:
|
||||||
return string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.OrdinalIgnoreCase);
|
comparison = string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.OrdinalIgnoreCase);
|
||||||
|
break;
|
||||||
|
|
||||||
case SortMode.Title:
|
case SortMode.Title:
|
||||||
return string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.OrdinalIgnoreCase);
|
comparison = string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.OrdinalIgnoreCase);
|
||||||
|
break;
|
||||||
|
|
||||||
case SortMode.Author:
|
case SortMode.Author:
|
||||||
return string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.OrdinalIgnoreCase);
|
comparison = string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.OrdinalIgnoreCase);
|
||||||
|
break;
|
||||||
|
|
||||||
case SortMode.Source:
|
case SortMode.Source:
|
||||||
return string.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source, StringComparison.OrdinalIgnoreCase);
|
comparison = string.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source, StringComparison.OrdinalIgnoreCase);
|
||||||
|
break;
|
||||||
|
|
||||||
case SortMode.DateAdded:
|
case SortMode.DateAdded:
|
||||||
return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded);
|
comparison = otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded);
|
||||||
|
break;
|
||||||
|
|
||||||
case SortMode.DateRanked:
|
case SortMode.DateRanked:
|
||||||
// Beatmaps which have no ranked date should already be filtered away in this mode.
|
// Beatmaps which have no ranked date should already be filtered away in this mode.
|
||||||
if (BeatmapSet.DateRanked == null || otherSet.BeatmapSet.DateRanked == null)
|
if (BeatmapSet.DateRanked == null || otherSet.BeatmapSet.DateRanked == null)
|
||||||
return 0;
|
break;
|
||||||
|
|
||||||
return otherSet.BeatmapSet.DateRanked.Value.CompareTo(BeatmapSet.DateRanked.Value);
|
comparison = otherSet.BeatmapSet.DateRanked.Value.CompareTo(BeatmapSet.DateRanked.Value);
|
||||||
|
break;
|
||||||
|
|
||||||
case SortMode.LastPlayed:
|
case SortMode.LastPlayed:
|
||||||
return -compareUsingAggregateMax(otherSet, b => (b.LastPlayed ?? DateTimeOffset.MinValue).ToUnixTimeSeconds());
|
comparison = -compareUsingAggregateMax(otherSet, b => (b.LastPlayed ?? DateTimeOffset.MinValue).ToUnixTimeSeconds());
|
||||||
|
break;
|
||||||
|
|
||||||
case SortMode.BPM:
|
case SortMode.BPM:
|
||||||
return compareUsingAggregateMax(otherSet, b => b.BPM);
|
comparison = compareUsingAggregateMax(otherSet, b => b.BPM);
|
||||||
|
break;
|
||||||
|
|
||||||
case SortMode.Length:
|
case SortMode.Length:
|
||||||
return compareUsingAggregateMax(otherSet, b => b.Length);
|
comparison = compareUsingAggregateMax(otherSet, b => b.Length);
|
||||||
|
break;
|
||||||
|
|
||||||
case SortMode.Difficulty:
|
case SortMode.Difficulty:
|
||||||
return compareUsingAggregateMax(otherSet, b => b.StarRating);
|
comparison = compareUsingAggregateMax(otherSet, b => b.StarRating);
|
||||||
|
break;
|
||||||
|
|
||||||
case SortMode.DateSubmitted:
|
case SortMode.DateSubmitted:
|
||||||
// Beatmaps which have no submitted date should already be filtered away in this mode.
|
// Beatmaps which have no submitted date should already be filtered away in this mode.
|
||||||
if (BeatmapSet.DateSubmitted == null || otherSet.BeatmapSet.DateSubmitted == null)
|
if (BeatmapSet.DateSubmitted == null || otherSet.BeatmapSet.DateSubmitted == null)
|
||||||
return 0;
|
break;
|
||||||
|
|
||||||
return otherSet.BeatmapSet.DateSubmitted.Value.CompareTo(BeatmapSet.DateSubmitted.Value);
|
comparison = otherSet.BeatmapSet.DateSubmitted.Value.CompareTo(BeatmapSet.DateSubmitted.Value);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (comparison != 0) return comparison;
|
||||||
|
|
||||||
|
// If the initial sort could not differentiate, attempt to use DateAdded to order sets in a stable fashion.
|
||||||
|
// The directionality of this matches the current SortMode.DateAdded, but we may want to reconsider if that becomes a user decision (ie. asc / desc).
|
||||||
|
comparison = otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded);
|
||||||
|
|
||||||
|
if (comparison != 0) return comparison;
|
||||||
|
|
||||||
|
// If DateAdded fails to break the tie, fallback to our internal GUID for stability.
|
||||||
|
// This basically means it's a stable random sort.
|
||||||
|
return otherSet.BeatmapSet.ID.CompareTo(BeatmapSet.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -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()
|
||||||
|
@ -65,11 +65,14 @@ namespace osu.Game.Skinning
|
|||||||
default:
|
default:
|
||||||
|
|
||||||
this.ScaleTo(0.6f).Then()
|
this.ScaleTo(0.6f).Then()
|
||||||
.ScaleTo(1.1f, fade_in_length * 0.8f).Then()
|
.ScaleTo(1.1f, fade_in_length * 0.8f).Then() // t = 0.8
|
||||||
// this is actually correct to match stable; there were overlapping transforms.
|
.Delay(fade_in_length * 0.2f) // t = 1.0
|
||||||
.ScaleTo(0.9f).Delay(fade_in_length * 0.2f)
|
.ScaleTo(0.9f, fade_in_length * 0.2f).Then() // t = 1.2
|
||||||
.ScaleTo(1.1f).ScaleTo(0.9f, fade_in_length * 0.2f).Then()
|
|
||||||
.ScaleTo(0.95f).ScaleTo(finalScale, fade_in_length * 0.2f);
|
// stable dictates scale of 0.9->1 over time 1.0 to 1.4, but we are already at 1.2.
|
||||||
|
// so we need to force the current value to be correct at 1.2 (0.95) then complete the
|
||||||
|
// second half of the transform.
|
||||||
|
.ScaleTo(0.95f).ScaleTo(finalScale, fade_in_length * 0.2f); // t = 1.4
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user